commit d57308a2f57e36e77491b6fdd355dc18da0e94ed
parent 692919af61da66514a1a3495d6e8afe0a5fa3712
Author: Lee Salzman <lsalzman@mozilla.com>
Date: Sat, 8 Nov 2025 16:49:20 +0000
Bug 1999048 - Use PBOs for SourceSurfaceWebgl readbacks. r=aosmond
When pruning GPU texture memory, SourceSurfaceWebgls containing GPU snapshots
may suddenly force a readback if there are existing references keeping alive the
SourceSurfaceWebgls.
Since the intention is to preserve the contents of the surface, but it doesn't
it doesn't require an immediate readback, using PBOs as an intermediate staging
buffer should alleviate some of the performance overhead of these readbacks.
Differential Revision: https://phabricator.services.mozilla.com/D271846
Diffstat:
5 files changed, 457 insertions(+), 213 deletions(-)
diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp
@@ -25,6 +25,7 @@
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/gfx/AAStroke.h"
#include "mozilla/gfx/Blur.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/DrawTargetSkia.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/HelpersSkia.h"
@@ -238,8 +239,9 @@ SharedContextWebgl::~SharedContextWebgl() {
}
ClearZeroBuffer();
ClearAllTextures();
- UnlinkSurfaceTextures();
+ UnlinkSurfaceTextures(true);
UnlinkGlyphCaches();
+ ClearSnapshotPBOs();
}
gl::GLContext* SharedContextWebgl::GetGLContext() {
@@ -269,21 +271,22 @@ void SharedContextWebgl::ExitTlsScope() {
// Remove any SourceSurface user data associated with this TextureHandle.
inline void SharedContextWebgl::UnlinkSurfaceTexture(
- const RefPtr<TextureHandle>& aHandle) {
+ const RefPtr<TextureHandle>& aHandle, bool aForce) {
if (RefPtr<SourceSurface> surface = aHandle->GetSurface()) {
// Ensure any WebGL snapshot textures get unlinked.
if (surface->GetType() == SurfaceType::WEBGL) {
- static_cast<SourceSurfaceWebgl*>(surface.get())->OnUnlinkTexture(this);
+ static_cast<SourceSurfaceWebgl*>(surface.get())
+ ->OnUnlinkTexture(this, aForce);
}
surface->RemoveUserData(&mTextureHandleKey);
}
}
// Unlinks TextureHandles from any SourceSurface user data.
-void SharedContextWebgl::UnlinkSurfaceTextures() {
+void SharedContextWebgl::UnlinkSurfaceTextures(bool aForce) {
for (RefPtr<TextureHandle> handle = mTextureHandles.getFirst(); handle;
handle = handle->getNext()) {
- UnlinkSurfaceTexture(handle);
+ UnlinkSurfaceTexture(handle, aForce);
}
}
@@ -409,6 +412,7 @@ void SharedContextWebgl::ClearCachesIfNecessary() {
ClearEmptyTextureMemory();
}
ClearLastTexture();
+ ClearSnapshotPBOs();
}
// Try to initialize a new WebGL context. Verifies that the requested size does
@@ -1165,7 +1169,11 @@ bool DrawTargetWebgl::PrepareSkia() {
}
bool DrawTargetWebgl::EnsureDataSnapshot() {
- return HasDataSnapshot() || PrepareSkia();
+ // If there is already a data snapshot, there is nothing to do. If there is a
+ // snapshot that has a pending PBO readback, then try to force the readback.
+ // Otherwise, read back the WebGL framebuffer into the Skia DT.
+ return HasDataSnapshot() || (mSnapshot && mSnapshot->ForceReadFromPBO()) ||
+ PrepareSkia();
}
void DrawTargetWebgl::PrepareShmem() { PrepareSkia(); }
@@ -1205,11 +1213,12 @@ already_AddRefed<SourceSurface> DrawTargetWebgl::GetOptimizedSnapshot(
return GetDataSnapshot();
}
-// Read from the WebGL context into a buffer. This handles both swizzling BGRA
-// to RGBA and flipping the image.
+// Read from the WebGL context into a buffer, either a memory buffer or a PBO.
+// This handles both swizzling BGRA to RGBA and flipping the image.
bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
SurfaceFormat aFormat, const IntRect& aBounds,
- TextureHandle* aHandle) {
+ TextureHandle* aHandle,
+ const RefPtr<WebGLBuffer>& aBuffer) {
MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
aFormat == SurfaceFormat::B8G8R8X8 ||
aFormat == SurfaceFormat::A8);
@@ -1218,7 +1227,8 @@ bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
// for reading.
if (aHandle) {
BindScratchFramebuffer(aHandle, false);
- } else if (mCurrentTarget && !mTargetHandle && mCurrentTarget->mIsClear) {
+ } else if (!aBuffer && mCurrentTarget && !mTargetHandle &&
+ mCurrentTarget->mIsClear) {
// If reading from a target that is still clear, then avoid the readback by
// just clearing the data.
SkPixmap(MakeSkiaImageInfo(aBounds.Size(), aFormat), aDstData, aDstStride)
@@ -1230,8 +1240,14 @@ bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
desc.srcOffset = *ivec2::From(aBounds);
desc.size = *uvec2::FromSize(aBounds);
desc.packState.rowLength = aDstStride / BytesPerPixel(aFormat);
- Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
- mWebgl->ReadPixelsInto(desc, range);
+ if (aBuffer) {
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, aBuffer);
+ mWebgl->ReadPixelsPbo(desc, 0);
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0);
+ } else {
+ Range<uint8_t> range = {aDstData, size_t(aDstStride) * aBounds.height};
+ mWebgl->ReadPixelsInto(desc, range);
+ }
// Restore the actual framebuffer after reading is done.
if (aHandle) {
@@ -1267,6 +1283,117 @@ already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
return surface.forget();
}
+already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO(
+ SourceSurfaceWebgl* aOwner, TextureHandle* aHandle) {
+ // Allocate a PBO, and read from the WebGL context into it.
+ SurfaceFormat format = SurfaceFormat::UNKNOWN;
+ IntRect bounds;
+ if (aHandle) {
+ format = aHandle->GetFormat();
+ bounds = aHandle->GetBounds();
+ } else {
+ format = mCurrentTarget->GetFormat();
+ bounds = mCurrentTarget->GetRect();
+ }
+ int32_t stride = GetAlignedStride<16>(bounds.width, BytesPerPixel(format));
+ size_t bufSize = BufferSizeFromStrideAndHeight(stride, bounds.height);
+ if (!bufSize) {
+ return nullptr;
+ }
+
+ // If the PBO is too large to fit within the memory limit by itself, then
+ // don't try to use a PBO.
+ size_t maxPBOMemory =
+ StaticPrefs::gfx_canvas_accelerated_max_snapshot_pbo_memory();
+ if (bufSize > maxPBOMemory) {
+ return nullptr;
+ }
+
+ RefPtr<WebGLBuffer> pbo = mWebgl->CreateBuffer();
+ if (!pbo) {
+ return nullptr;
+ }
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pbo);
+ mWebgl->UninitializedBufferData_SizeOnly(LOCAL_GL_PIXEL_PACK_BUFFER, bufSize,
+ LOCAL_GL_STREAM_READ);
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0);
+ if (!ReadInto(nullptr, stride, format, bounds, aHandle, pbo)) {
+ return nullptr;
+ }
+
+ // If there are existing snapshot PBOs, check if adding this PBO would exceed
+ // the memory limit for snapshot PBOs. This happens after the new PBO was set
+ // up and the readback initiated, in case purging an old PBO causes a stall
+ // which can be used to cover the latency of the readback for the new PBO.
+ ClearSnapshotPBOs(maxPBOMemory - std::min(bufSize, maxPBOMemory));
+
+ mUsedSnapshotPBOMemory += bufSize;
+ mSnapshotPBOs.emplace_back(aOwner);
+ return pbo.forget();
+}
+
+already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshotFromPBO(
+ const RefPtr<WebGLBuffer>& aBuffer, SurfaceFormat aFormat,
+ const IntSize& aSize) {
+ // For an existing PBO where a readback has been initiated previously, create
+ // a new data surface and copy the PBO's data into the data surface.
+ int32_t stride = GetAlignedStride<16>(aSize.width, BytesPerPixel(aFormat));
+ size_t bufSize = BufferSizeFromStrideAndHeight(stride, aSize.height);
+ if (!bufSize) {
+ return nullptr;
+ }
+ RefPtr<DataSourceSurface> surface =
+ Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, stride);
+ if (!surface) {
+ return nullptr;
+ }
+ DataSourceSurface::ScopedMap dstMap(surface, DataSourceSurface::WRITE);
+ if (!dstMap.IsMapped()) {
+ return nullptr;
+ }
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, aBuffer);
+ Range<uint8_t> range = {dstMap.GetData(), bufSize};
+ bool success = static_cast<WebGL2Context*>(mWebgl.get())
+ ->GetBufferSubData(LOCAL_GL_PIXEL_PACK_BUFFER, 0, range);
+ mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0);
+ if (success) {
+ return surface.forget();
+ }
+ return nullptr;
+}
+
+void SharedContextWebgl::RemoveSnapshotPBO(
+ SourceSurfaceWebgl* aOwner, already_AddRefed<WebGLBuffer> aBuffer) {
+ RefPtr<WebGLBuffer> buffer(aBuffer);
+ MOZ_ASSERT(aOwner && buffer);
+ IntSize size = aOwner->GetSize();
+ SurfaceFormat format = aOwner->GetFormat();
+ int32_t stride = GetAlignedStride<16>(size.width, BytesPerPixel(format));
+ size_t bufSize = BufferSizeFromStrideAndHeight(stride, size.height);
+ // If the queue is empty, no memory should be used. Otherwise, deduct the
+ // usage from the queue.
+ if (mSnapshotPBOs.empty()) {
+ mUsedSnapshotPBOMemory = 0;
+ } else if (bufSize) {
+ mUsedSnapshotPBOMemory -= std::min(mUsedSnapshotPBOMemory, bufSize);
+ }
+}
+
+void SharedContextWebgl::ClearSnapshotPBOs(size_t aMaxMemory) {
+ // Force any pending readback PBOs to convert to actual data.
+ while (!mSnapshotPBOs.empty() &&
+ (!aMaxMemory || mUsedSnapshotPBOMemory > aMaxMemory)) {
+ RefPtr<SourceSurfaceWebgl> snapshot(mSnapshotPBOs.front());
+ mSnapshotPBOs.pop_front();
+ if (snapshot) {
+ snapshot->ForceReadFromPBO();
+ }
+ }
+ if (mSnapshotPBOs.empty()) {
+ mUsedSnapshotPBOMemory = 0;
+ }
+}
+
// Utility method to install the target before reading a snapshot.
bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
if (!PrepareContext(false)) {
@@ -1286,6 +1413,16 @@ already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
return mSharedContext->ReadSnapshot();
}
+already_AddRefed<WebGLBuffer> DrawTargetWebgl::ReadSnapshotIntoPBO(
+ SourceSurfaceWebgl* aOwner) {
+ AutoRestoreContext restore(this);
+ if (!PrepareContext(false)) {
+ return nullptr;
+ }
+ mProfile.OnReadback();
+ return mSharedContext->ReadSnapshotIntoPBO(aOwner);
+}
+
already_AddRefed<SourceSurface> DrawTargetWebgl::GetBackingSurface() {
return Snapshot();
}
@@ -4787,6 +4924,180 @@ already_AddRefed<TextureHandle> SharedContextWebgl::DrawStrokeMask(
return handle.forget();
}
+// Attempts to draw a path using WGR (or AAStroke), when possible.
+bool SharedContextWebgl::DrawWGRPath(
+ const Path* aPath, const IntRect& aIntBounds, const Rect& aQuantBounds,
+ const Matrix& aPathXform, RefPtr<PathCacheEntry>& aEntry,
+ const DrawOptions& aOptions, const StrokeOptions* aStrokeOptions,
+ AAStrokeMode aAAStrokeMode, const Pattern& aPattern,
+ const Maybe<DeviceColor>& aColor) {
+ const PathSkia* pathSkia = static_cast<const PathSkia*>(aPath);
+ const Matrix& currentTransform = mCurrentTarget->GetTransform();
+ if (aEntry->GetVertexRange().IsValid()) {
+ // If there is a valid cached vertex data in the path vertex buffer, then
+ // just draw that. We must draw at integer pixel boundaries (using
+ // intBounds instead of quantBounds) due to WGR's reliance on pixel center
+ // location.
+ mCurrentTarget->mProfile.OnCacheHit();
+ return DrawRectAccel(Rect(aIntBounds.TopLeft(), Size(1, 1)), aPattern,
+ aOptions, Nothing(), nullptr, false, true, true, false,
+ nullptr, &aEntry->GetVertexRange());
+ }
+
+ // printf_stderr("Generating... verbs %d, points %d\n",
+ // int(pathSkia->GetPath().countVerbs()),
+ // int(pathSkia->GetPath().countPoints()));
+ WGR::OutputVertex* outputBuffer = nullptr;
+ size_t outputBufferCapacity = 0;
+ if (mWGROutputBuffer) {
+ outputBuffer = mWGROutputBuffer.get();
+ outputBufferCapacity = mPathVertexCapacity / sizeof(WGR::OutputVertex);
+ }
+ Maybe<WGR::VertexBuffer> wgrVB;
+ Maybe<AAStroke::VertexBuffer> strokeVB;
+ if (!aStrokeOptions) {
+ if (aPath == mUnitCirclePath) {
+ auto scaleFactors = aPathXform.ScaleFactors();
+ if (scaleFactors.AreScalesSame()) {
+ Point center = aPathXform.GetTranslation() - aQuantBounds.TopLeft();
+ float radius = scaleFactors.xScale;
+ AAStroke::VertexBuffer vb = AAStroke::aa_stroke_filled_circle(
+ center.x, center.y, radius, (AAStroke::OutputVertex*)outputBuffer,
+ outputBufferCapacity);
+ if (!vb.len || (outputBuffer && vb.len > outputBufferCapacity)) {
+ AAStroke::aa_stroke_vertex_buffer_release(vb);
+ } else {
+ strokeVB = Some(vb);
+ }
+ }
+ }
+ if (!strokeVB) {
+ wgrVB = GeneratePathVertexBuffer(
+ aEntry->GetPath(), IntRect(-aIntBounds.TopLeft(), mViewportSize),
+ mRasterizationTruncates, outputBuffer, outputBufferCapacity);
+ }
+ } else {
+ if (aAAStrokeMode != AAStrokeMode::Unsupported) {
+ auto scaleFactors = currentTransform.ScaleFactors();
+ if (scaleFactors.AreScalesSame()) {
+ strokeVB = GenerateStrokeVertexBuffer(aEntry->GetPath(), aStrokeOptions,
+ scaleFactors.xScale, outputBuffer,
+ outputBufferCapacity);
+ }
+ }
+ if (!strokeVB && mPathWGRStroke) {
+ // If stroking, then generate a path to fill the stroked region. This
+ // path will need to be quantized again because it differs from the
+ // path used for the cache entry, but this allows us to avoid
+ // generating a fill path on a cache hit.
+ Maybe<Rect> cullRect;
+ Matrix invTransform = currentTransform;
+ if (invTransform.Invert()) {
+ // Transform the stroking clip rect from device space to local
+ // space.
+ Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
+ invRect.RoundOut();
+ cullRect = Some(invRect);
+ }
+ SkPath fillPath;
+ if (pathSkia->GetFillPath(*aStrokeOptions, aPathXform, fillPath,
+ cullRect)) {
+ // printf_stderr(" stroke fill... verbs %d, points %d\n",
+ // int(fillPath.countVerbs()),
+ // int(fillPath.countPoints()));
+ if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
+ mWGRPathBuilder, fillPath, aQuantBounds, aPathXform)) {
+ wgrVB = GeneratePathVertexBuffer(
+ *qp, IntRect(-aIntBounds.TopLeft(), mViewportSize),
+ mRasterizationTruncates, outputBuffer, outputBufferCapacity);
+ }
+ }
+ }
+ }
+ if (!wgrVB && !strokeVB) {
+ // Failed to generate any vertex data.
+ return false;
+ }
+ const uint8_t* vbData =
+ wgrVB ? (const uint8_t*)wgrVB->data : (const uint8_t*)strokeVB->data;
+ if (outputBuffer && !vbData) {
+ vbData = (const uint8_t*)outputBuffer;
+ }
+ size_t vbLen = wgrVB ? wgrVB->len : strokeVB->len;
+ uint32_t vertexBytes =
+ uint32_t(std::min(vbLen * sizeof(WGR::OutputVertex), size_t(UINT32_MAX)));
+ // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
+ // int(vertexBytes));
+ if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
+ vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
+ // If the vertex data is too large to fit in the remaining path vertex
+ // buffer, then orphan the contents of the vertex buffer to make room
+ // for it.
+ if (mPathCache) {
+ mPathCache->ClearVertexRanges();
+ }
+ ResetPathVertexBuffer();
+ }
+ if (vertexBytes > mPathVertexCapacity - mPathVertexOffset) {
+ // There is insufficient space in the path buffer to fit vertex data.
+ if (wgrVB) {
+ WGR::wgr_vertex_buffer_release(wgrVB.ref());
+ } else {
+ AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
+ }
+ return false;
+ }
+ // If there is actually room to fit the vertex data in the vertex buffer
+ // after orphaning as necessary, then upload the data to the next
+ // available offset in the buffer.
+ PathVertexRange vertexRange(
+ uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)), uint32_t(vbLen));
+ // printf_stderr(" ... offset %d\n", mPathVertexOffset);
+ // Normal glBufferSubData interleaved with draw calls causes performance
+ // issues on Mali, so use our special unsynchronized version. This is
+ // safe as we never update regions referenced by pending draw calls.
+ mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset, vertexBytes,
+ vbData,
+ /* unsynchronized */ true);
+ mPathVertexOffset += vertexBytes;
+ if (wgrVB) {
+ WGR::wgr_vertex_buffer_release(wgrVB.ref());
+ } else {
+ AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
+ }
+ if (strokeVB && aAAStrokeMode == AAStrokeMode::Mask) {
+ // Attempt to generate a stroke mask for path.
+ if (RefPtr<TextureHandle> handle =
+ DrawStrokeMask(vertexRange, aIntBounds.Size())) {
+ // Finally, draw the rendered stroke mask.
+ if (aEntry) {
+ aEntry->Link(handle);
+ }
+ mCurrentTarget->mProfile.OnCacheMiss();
+ SurfacePattern maskPattern(nullptr, ExtendMode::CLAMP,
+ Matrix::Translation(aQuantBounds.TopLeft()),
+ SamplingFilter::GOOD);
+ return DrawRectAccel(aQuantBounds, maskPattern, aOptions, aColor, &handle,
+ false, true, true);
+ }
+ } else {
+ // Remember the vertex range in the cache entry so that it can be
+ // reused later.
+ if (aEntry) {
+ aEntry->SetVertexRange(vertexRange);
+ }
+
+ // Finally, draw the uploaded vertex data.
+ mCurrentTarget->mProfile.OnCacheMiss();
+ return DrawRectAccel(Rect(aIntBounds.TopLeft(), Size(1, 1)), aPattern,
+ aOptions, Nothing(), nullptr, false, true, true, false,
+ nullptr, &vertexRange);
+ }
+ // If we failed to draw the vertex data for some reason, then fall back
+ // to the texture rasterization path.
+ return false;
+}
+
bool SharedContextWebgl::DrawPathAccel(
const Path* aPath, const Pattern& aPattern, const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions, bool aAllowStrokeAlpha,
@@ -4917,168 +5228,22 @@ bool SharedContextWebgl::DrawPathAccel(
if (mPathVertexCapacity > 0 && !handle && entry && !aShadow &&
aOptions.mAntialiasMode != AntialiasMode::NONE &&
- SupportsPattern(aPattern) &&
entry->GetPath().mPath.num_types <= mPathMaxComplexity) {
- if (entry->GetVertexRange().IsValid()) {
- // If there is a valid cached vertex data in the path vertex buffer, then
- // just draw that. We must draw at integer pixel boundaries (using
- // intBounds instead of quantBounds) due to WGR's reliance on pixel center
- // location.
- mCurrentTarget->mProfile.OnCacheHit();
- return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
- aOptions, Nothing(), nullptr, false, true, true,
- false, nullptr, &entry->GetVertexRange());
- }
-
- // printf_stderr("Generating... verbs %d, points %d\n",
- // int(pathSkia->GetPath().countVerbs()),
- // int(pathSkia->GetPath().countPoints()));
- WGR::OutputVertex* outputBuffer = nullptr;
- size_t outputBufferCapacity = 0;
- if (mWGROutputBuffer) {
- outputBuffer = mWGROutputBuffer.get();
- outputBufferCapacity = mPathVertexCapacity / sizeof(WGR::OutputVertex);
- }
- Maybe<WGR::VertexBuffer> wgrVB;
- Maybe<AAStroke::VertexBuffer> strokeVB;
- if (!aStrokeOptions) {
- if (aPath == mUnitCirclePath) {
- auto scaleFactors = pathXform.ScaleFactors();
- if (scaleFactors.AreScalesSame()) {
- Point center = pathXform.GetTranslation() - quantBounds.TopLeft();
- float radius = scaleFactors.xScale;
- AAStroke::VertexBuffer vb = AAStroke::aa_stroke_filled_circle(
- center.x, center.y, radius, (AAStroke::OutputVertex*)outputBuffer,
- outputBufferCapacity);
- if (!vb.len || (outputBuffer && vb.len > outputBufferCapacity)) {
- AAStroke::aa_stroke_vertex_buffer_release(vb);
- } else {
- strokeVB = Some(vb);
- }
- }
- }
- if (!strokeVB) {
- wgrVB = GeneratePathVertexBuffer(
- entry->GetPath(), IntRect(-intBounds.TopLeft(), mViewportSize),
- mRasterizationTruncates, outputBuffer, outputBufferCapacity);
- }
- } else {
- if (aaStrokeMode != AAStrokeMode::Unsupported) {
- auto scaleFactors = currentTransform.ScaleFactors();
- if (scaleFactors.AreScalesSame()) {
- strokeVB = GenerateStrokeVertexBuffer(
- entry->GetPath(), aStrokeOptions, scaleFactors.xScale,
- outputBuffer, outputBufferCapacity);
- }
- }
- if (!strokeVB && mPathWGRStroke) {
- // If stroking, then generate a path to fill the stroked region. This
- // path will need to be quantized again because it differs from the
- // path used for the cache entry, but this allows us to avoid
- // generating a fill path on a cache hit.
- Maybe<Rect> cullRect;
- Matrix invTransform = currentTransform;
- if (invTransform.Invert()) {
- // Transform the stroking clip rect from device space to local
- // space.
- Rect invRect = invTransform.TransformBounds(Rect(mClipRect));
- invRect.RoundOut();
- cullRect = Some(invRect);
- }
- SkPath fillPath;
- if (pathSkia->GetFillPath(*aStrokeOptions, pathXform, fillPath,
- cullRect)) {
- // printf_stderr(" stroke fill... verbs %d, points %d\n",
- // int(fillPath.countVerbs()),
- // int(fillPath.countPoints()));
- if (Maybe<QuantizedPath> qp = GenerateQuantizedPath(
- mWGRPathBuilder, fillPath, quantBounds, pathXform)) {
- wgrVB = GeneratePathVertexBuffer(
- *qp, IntRect(-intBounds.TopLeft(), mViewportSize),
- mRasterizationTruncates, outputBuffer, outputBufferCapacity);
- }
- }
- }
- }
- if (wgrVB || strokeVB) {
- const uint8_t* vbData =
- wgrVB ? (const uint8_t*)wgrVB->data : (const uint8_t*)strokeVB->data;
- if (outputBuffer && !vbData) {
- vbData = (const uint8_t*)outputBuffer;
- }
- size_t vbLen = wgrVB ? wgrVB->len : strokeVB->len;
- uint32_t vertexBytes = uint32_t(
- std::min(vbLen * sizeof(WGR::OutputVertex), size_t(UINT32_MAX)));
- // printf_stderr(" ... %d verts, %d bytes\n", int(vbLen),
- // int(vertexBytes));
- if (vertexBytes > mPathVertexCapacity - mPathVertexOffset &&
- vertexBytes <= mPathVertexCapacity - sizeof(kRectVertexData)) {
- // If the vertex data is too large to fit in the remaining path vertex
- // buffer, then orphan the contents of the vertex buffer to make room
- // for it.
- if (mPathCache) {
- mPathCache->ClearVertexRanges();
- }
- ResetPathVertexBuffer();
- }
- if (vertexBytes <= mPathVertexCapacity - mPathVertexOffset) {
- // If there is actually room to fit the vertex data in the vertex buffer
- // after orphaning as necessary, then upload the data to the next
- // available offset in the buffer.
- PathVertexRange vertexRange(
- uint32_t(mPathVertexOffset / sizeof(WGR::OutputVertex)),
- uint32_t(vbLen));
- // printf_stderr(" ... offset %d\n", mPathVertexOffset);
- // Normal glBufferSubData interleaved with draw calls causes performance
- // issues on Mali, so use our special unsynchronized version. This is
- // safe as we never update regions referenced by pending draw calls.
- mWebgl->BufferSubData(LOCAL_GL_ARRAY_BUFFER, mPathVertexOffset,
- vertexBytes, vbData,
- /* unsynchronized */ true);
- mPathVertexOffset += vertexBytes;
- if (wgrVB) {
- WGR::wgr_vertex_buffer_release(wgrVB.ref());
- } else {
- AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
- }
- if (strokeVB && aaStrokeMode == AAStrokeMode::Mask) {
- // Attempt to generate a stroke mask for path.
- if (RefPtr<TextureHandle> handle =
- DrawStrokeMask(vertexRange, intBounds.Size())) {
- // Finally, draw the rendered stroke mask.
- if (entry) {
- entry->Link(handle);
- }
- mCurrentTarget->mProfile.OnCacheMiss();
- SurfacePattern maskPattern(
- nullptr, ExtendMode::CLAMP,
- Matrix::Translation(quantBounds.TopLeft()),
- SamplingFilter::GOOD);
- return DrawRectAccel(quantBounds, maskPattern, aOptions, color,
- &handle, false, true, true);
- }
- } else {
- // Remember the vertex range in the cache entry so that it can be
- // reused later.
- if (entry) {
- entry->SetVertexRange(vertexRange);
- }
-
- // Finally, draw the uploaded vertex data.
- mCurrentTarget->mProfile.OnCacheMiss();
- return DrawRectAccel(Rect(intBounds.TopLeft(), Size(1, 1)), aPattern,
- aOptions, Nothing(), nullptr, false, true, true,
- false, nullptr, &vertexRange);
- }
- } else {
- if (wgrVB) {
- WGR::wgr_vertex_buffer_release(wgrVB.ref());
- } else {
- AAStroke::aa_stroke_vertex_buffer_release(strokeVB.ref());
+ if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
+ if (Maybe<SurfacePattern> gradient =
+ mCurrentTarget->LinearGradientToSurface(WidenToDouble(bounds),
+ aPattern)) {
+ if (DrawWGRPath(aPath, intBounds, quantBounds, pathXform, entry,
+ aOptions, aStrokeOptions, aaStrokeMode, gradient.ref(),
+ color)) {
+ return true;
}
}
- // If we failed to draw the vertex data for some reason, then fall through
- // to the texture rasterization path.
+ } else if (SupportsPattern(aPattern) &&
+ DrawWGRPath(aPath, intBounds, quantBounds, pathXform, entry,
+ aOptions, aStrokeOptions, aaStrokeMode, aPattern,
+ color)) {
+ return true;
}
}
diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h
@@ -7,6 +7,7 @@
#ifndef _MOZILLA_GFX_DRAWTARGETWEBGL_H
#define _MOZILLA_GFX_DRAWTARGETWEBGL_H
+#include <deque>
#include <memory>
#include <vector>
@@ -62,7 +63,9 @@ class SharedTextureHandle;
class StandaloneTexture;
class GlyphCache;
class PathCache;
+class PathCacheEntry;
struct PathVertexRange;
+enum class AAStrokeMode;
// SharedContextWebgl stores most of the actual WebGL state that may be used by
// any number of DrawTargetWebgl's that use it. Foremost, it holds the actual
@@ -285,6 +288,11 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
// Cached unit circle path
RefPtr<Path> mUnitCirclePath;
+ // The total bytes used by pending snapshot PBOs.
+ size_t mUsedSnapshotPBOMemory = 0;
+ // The owning surfaces initiating snapshot PBO readbacks.
+ std::deque<ThreadSafeWeakPtr<SourceSurfaceWebgl>> mSnapshotPBOs;
+
bool Initialize();
bool CreateShaders();
void ResetPathVertexBuffer();
@@ -338,7 +346,8 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
void InitTexParameters(WebGLTexture* aTex, bool aFilter = true);
bool ReadInto(uint8_t* aDstData, int32_t aDstStride, SurfaceFormat aFormat,
- const IntRect& aBounds, TextureHandle* aHandle = nullptr);
+ const IntRect& aBounds, TextureHandle* aHandle = nullptr,
+ const RefPtr<WebGLBuffer>& aBuffer = nullptr);
already_AddRefed<DataSourceSurface> ReadSnapshot(
TextureHandle* aHandle = nullptr);
already_AddRefed<TextureHandle> WrapSnapshot(const IntSize& aSize,
@@ -347,6 +356,15 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
already_AddRefed<TextureHandle> CopySnapshot(
const IntRect& aRect, TextureHandle* aHandle = nullptr);
+ already_AddRefed<WebGLBuffer> ReadSnapshotIntoPBO(
+ SourceSurfaceWebgl* aOwner, TextureHandle* aHandle = nullptr);
+ already_AddRefed<DataSourceSurface> ReadSnapshotFromPBO(
+ const RefPtr<WebGLBuffer>& aBuffer, SurfaceFormat aFormat,
+ const IntSize& aSize);
+ void RemoveSnapshotPBO(SourceSurfaceWebgl* aOwner,
+ already_AddRefed<WebGLBuffer> aBuffer);
+ void ClearSnapshotPBOs(size_t aMaxMemory = 0);
+
already_AddRefed<WebGLTexture> GetCompatibleSnapshot(
SourceSurface* aSurface, RefPtr<TextureHandle>* aHandle = nullptr,
bool aCheckTarget = true) const;
@@ -413,6 +431,12 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
already_AddRefed<TextureHandle> DrawStrokeMask(
const PathVertexRange& aVertexRange, const IntSize& aSize);
+ bool DrawWGRPath(const Path* aPath, const IntRect& aIntBounds,
+ const Rect& aQuantBounds, const Matrix& aPathXform,
+ RefPtr<PathCacheEntry>& aEntry, const DrawOptions& aOptions,
+ const StrokeOptions* aStrokeOptions,
+ AAStrokeMode aAAStrokeMode, const Pattern& aPattern,
+ const Maybe<DeviceColor>& aColor);
bool DrawPathAccel(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions,
const StrokeOptions* aStrokeOptions = nullptr,
@@ -443,8 +467,9 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
bool RemoveSharedTexture(const RefPtr<SharedTexture>& aTexture);
bool RemoveStandaloneTexture(const RefPtr<StandaloneTexture>& aTexture);
- void UnlinkSurfaceTextures();
- void UnlinkSurfaceTexture(const RefPtr<TextureHandle>& aHandle);
+ void UnlinkSurfaceTextures(bool aForce = false);
+ void UnlinkSurfaceTexture(const RefPtr<TextureHandle>& aHandle,
+ bool aForce = false);
void UnlinkGlyphCaches();
void AddHeapData(const void* aBuf);
@@ -811,6 +836,7 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool ReadInto(uint8_t* aDstData, int32_t aDstStride);
already_AddRefed<DataSourceSurface> ReadSnapshot();
+ already_AddRefed<WebGLBuffer> ReadSnapshotIntoPBO(SourceSurfaceWebgl* aOwner);
already_AddRefed<TextureHandle> CopySnapshot(const IntRect& aRect);
already_AddRefed<TextureHandle> CopySnapshot() {
return CopySnapshot(GetRect());
diff --git a/dom/canvas/SourceSurfaceWebgl.cpp b/dom/canvas/SourceSurfaceWebgl.cpp
@@ -7,6 +7,7 @@
#include "SourceSurfaceWebgl.h"
#include "DrawTargetWebglInternal.h"
+#include "WebGLBuffer.h"
namespace mozilla::gfx {
@@ -25,25 +26,61 @@ SourceSurfaceWebgl::~SourceSurfaceWebgl() {
// Signal that the texture handle is not being used now.
mHandle->ClearSurface();
}
+ if (mReadBuffer) {
+ if (RefPtr<SharedContextWebgl> sharedContext = {mSharedContext}) {
+ sharedContext->RemoveSnapshotPBO(this, mReadBuffer.forget());
+ }
+ mReadBuffer = nullptr;
+ }
}
-// Read back the contents of the target or texture handle for data use.
-inline bool SourceSurfaceWebgl::EnsureData() {
+// Read back the contents of the target or texture handle for data use. This
+// may attempt a readback into a PBO for performance, unless forced to
+// immediately read into data.
+inline bool SourceSurfaceWebgl::EnsureData(bool aForce) {
if (mData) {
return true;
}
- if (!mDT) {
+
+ if (mReadBuffer) {
+ // If there is a pending PBO readback, then copy the contents of the PBO.
+ if (RefPtr<SharedContextWebgl> sharedContext = {mSharedContext}) {
+ mData = sharedContext->ReadSnapshotFromPBO(mReadBuffer, mFormat, mSize);
+ sharedContext->RemoveSnapshotPBO(this, mReadBuffer.forget());
+ }
+ mReadBuffer = nullptr;
+ return !!mData;
+ }
+
+ if (RefPtr<DrawTargetWebgl> dt = {mDT}) {
+ if (!aForce) {
+ mReadBuffer = dt->ReadSnapshotIntoPBO(this);
+ }
+ if (!mReadBuffer) {
+ mData = dt->ReadSnapshot();
+ }
+ } else if (mHandle) {
// Assume that the target changed, so there should be a texture handle
// holding a copy. Try to read data from the copy since we can't read
// from the target.
- if (!mHandle || !mSharedContext) {
- return false;
+ if (RefPtr<SharedContextWebgl> sharedContext = {mSharedContext}) {
+ if (!aForce) {
+ mReadBuffer = sharedContext->ReadSnapshotIntoPBO(this, mHandle);
+ }
+ if (!mReadBuffer) {
+ mData = sharedContext->ReadSnapshot(mHandle);
+ }
}
- mData = mSharedContext->ReadSnapshot(mHandle);
- } else {
- mData = mDT->ReadSnapshot();
}
- return !!mData;
+ return mData || mReadBuffer;
+}
+
+bool SourceSurfaceWebgl::ForceReadFromPBO() {
+ if (mReadBuffer && EnsureData()) {
+ MOZ_ASSERT(!mReadBuffer);
+ return true;
+ }
+ return false;
}
uint8_t* SourceSurfaceWebgl::GetData() {
@@ -78,21 +115,25 @@ void SourceSurfaceWebgl::Unmap() {
// possible, or read back into data, if necessary, to preserve this particular
// version of the framebuffer.
void SourceSurfaceWebgl::DrawTargetWillChange(bool aNeedHandle) {
- MOZ_ASSERT(mDT);
+ RefPtr<DrawTargetWebgl> dt(mDT);
+ if (!dt) {
+ MOZ_ASSERT_UNREACHABLE("No DrawTargetWebgl for SourceSurfaceWebgl");
+ return;
+ }
// Only try to copy into a new texture handle if we don't already have data.
// However, we still might need to immediately draw this snapshot to a WebGL
// target, which would require a subsequent upload, so also copy into a new
// handle even if we already have data in that case since it is faster than
// uploading.
- if ((!mData || aNeedHandle) && !mHandle) {
+ if ((aNeedHandle || (!mData && !mReadBuffer)) && !mHandle) {
// Prefer copying the framebuffer to a texture if possible.
- mHandle = mDT->CopySnapshot();
+ mHandle = dt->CopySnapshot();
if (mHandle) {
// Link this surface to the handle.
mHandle->SetSurface(this);
} else {
// If that fails, then try to just read the data to a surface.
- EnsureData();
+ EnsureData(false);
}
}
mDT = nullptr;
@@ -120,7 +161,8 @@ void SourceSurfaceWebgl::SetHandle(TextureHandle* aHandle) {
// Handler for when the owner DrawTargetWebgl is destroying the cached texture
// handle that has been allocated for this snapshot.
-void SourceSurfaceWebgl::OnUnlinkTexture(SharedContextWebgl* aContext) {
+void SourceSurfaceWebgl::OnUnlinkTexture(SharedContextWebgl* aContext,
+ bool aForce) {
// If we get here, then we must have copied a snapshot, which only happens
// if the target changed.
MOZ_ASSERT(!mDT);
@@ -129,46 +171,46 @@ void SourceSurfaceWebgl::OnUnlinkTexture(SharedContextWebgl* aContext) {
// try to draw with this snapshot, we might have allocated an external texture
// handle in the texture cache that still links to this snapshot and can cause
// us to end up here inside OnUnlinkTexture.
- MOZ_ASSERT(mHandle || mData);
- if (!mData) {
- mData = aContext->ReadSnapshot(mHandle);
+ MOZ_ASSERT(mHandle || mData || mReadBuffer);
+ if (!mData && !mReadBuffer) {
+ if (!aForce) {
+ mReadBuffer = aContext->ReadSnapshotIntoPBO(this, mHandle);
+ }
+ if (!mReadBuffer) {
+ mData = aContext->ReadSnapshot(mHandle);
+ }
}
mHandle = nullptr;
}
already_AddRefed<SourceSurface> SourceSurfaceWebgl::ExtractSubrect(
const IntRect& aRect) {
- // Ensure we have a texture source available to extract from.
- if (!(mDT || (mHandle && mSharedContext)) || aRect.IsEmpty() ||
- !GetRect().Contains(aRect)) {
+ // Ensure the subrect is actually in bounds.
+ if (aRect.IsEmpty() || !GetRect().Contains(aRect)) {
return nullptr;
}
RefPtr<TextureHandle> subHandle;
RefPtr<SharedContextWebgl> sharedContext;
- if (mDT) {
+ if (RefPtr<DrawTargetWebgl> dt = {mDT}) {
// If this is still a snapshot linked to a target, then copy from the
// target.
- subHandle = mDT->CopySnapshot(aRect);
- if (!subHandle) {
- return nullptr;
- }
- sharedContext = mDT->mSharedContext;
- } else {
+ subHandle = dt->CopySnapshot(aRect);
+ sharedContext = dt->mSharedContext;
+ } else if (mHandle) {
// Otherwise, we have a handle, but we need to verify it is still linked to
// a valid context.
sharedContext = mSharedContext;
- if (!sharedContext) {
- return nullptr;
- }
- // Try to copy directly from the handle using the context.
- subHandle = sharedContext->CopySnapshot(aRect, mHandle);
- if (!subHandle) {
- return nullptr;
+ if (sharedContext) {
+ // Try to copy directly from the handle using the context.
+ subHandle = sharedContext->CopySnapshot(aRect, mHandle);
}
}
- RefPtr<SourceSurfaceWebgl> surface = new SourceSurfaceWebgl(sharedContext);
- surface->SetHandle(subHandle);
- return surface.forget();
+ if (subHandle && sharedContext) {
+ RefPtr<SourceSurfaceWebgl> surface = new SourceSurfaceWebgl(sharedContext);
+ surface->SetHandle(subHandle);
+ return surface.forget();
+ }
+ return nullptr;
}
} // namespace mozilla::gfx
diff --git a/dom/canvas/SourceSurfaceWebgl.h b/dom/canvas/SourceSurfaceWebgl.h
@@ -10,6 +10,10 @@
#include "mozilla/WeakPtr.h"
#include "mozilla/gfx/2D.h"
+namespace mozilla {
+class WebGLBuffer;
+} // namespace mozilla
+
namespace mozilla::gfx {
class DrawTargetWebgl;
@@ -46,7 +50,8 @@ class SourceSurfaceWebgl : public DataSourceSurface {
explicit SourceSurfaceWebgl(const RefPtr<SharedContextWebgl>& aSharedContext);
- bool EnsureData();
+ bool EnsureData(bool aForce = true);
+ bool ForceReadFromPBO();
void DrawTargetWillChange(bool aNeedHandle);
@@ -54,12 +59,13 @@ class SourceSurfaceWebgl : public DataSourceSurface {
void SetHandle(TextureHandle* aHandle);
- void OnUnlinkTexture(SharedContextWebgl* aContext);
+ void OnUnlinkTexture(SharedContextWebgl* aContext, bool aForce);
DrawTargetWebgl* GetTarget() const { return mDT.get(); }
SurfaceFormat mFormat = SurfaceFormat::UNKNOWN;
IntSize mSize;
+ RefPtr<WebGLBuffer> mReadBuffer;
// Any data that has been read back from the WebGL context for mapping.
RefPtr<DataSourceSurface> mData;
// The draw target that currently owns the texture for this surface.
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -6728,6 +6728,11 @@
value: 512 * 1024 * 1024
mirror: always
+- name: gfx.canvas.accelerated.max-snapshot-pbo-memory
+ type: RelaxedAtomicUint32
+ value: 64 * 1024 * 1024
+ mirror: always
+
- name: gfx.canvas.accelerated.max-surface-size
type: RelaxedAtomicUint32
value: 5280