commit c12eb5a680ef3b15cadbd757111efb8baafaf459
parent 8893737b3a4b7a1c8b19da19a6042a863303a67d
Author: Lee Salzman <lsalzman@mozilla.com>
Date: Sun, 9 Nov 2025 22:41:06 +0000
Bug 1999149 - Avoid double copies from SourceSurfaceWebgl to CanvasTranslator data shmems. r=aosmond
When reading from a SourceSurfaceWebgl into a shmem, two copies can occur. First, the SourceSurfaceWebgl
reads its GPU texture into a memory buffer, and secondly, copies from this memory buffer into a shmem.
Quite often, the SourceSurfaceWebgl's memory buffer will never be subsequently accessed, negating the
benefit of the initial copy.
Instead, we can let the SourceSurfaceWebgl read directly into the shmem buffer. The SourceSurfaceWebgl
can subsequently read from this buffer so long as it remains valid. Internally, it keeps a copy-on-write
reference to the data so that should the shmem be modified or go away, it can do a secondary copy into
a new memory buffer that may be modified. If the surface is never modified and does not outlive the
shmem, this secondary copy is thus mostly avoided in the best case, and in the worst case is no slower
than the previous strategy if a double-copy occurs.
Differential Revision: https://phabricator.services.mozilla.com/D271912
Diffstat:
12 files changed, 160 insertions(+), 64 deletions(-)
diff --git a/dom/canvas/DrawTargetWebgl.cpp b/dom/canvas/DrawTargetWebgl.cpp
@@ -1258,7 +1258,7 @@ bool SharedContextWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride,
}
already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
- TextureHandle* aHandle) {
+ TextureHandle* aHandle, uint8_t* aData, int32_t aStride) {
// Allocate a data surface, map it, and read from the WebGL context into the
// surface.
SurfaceFormat format = SurfaceFormat::UNKNOWN;
@@ -1271,7 +1271,9 @@ already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
bounds = mCurrentTarget->GetRect();
}
RefPtr<DataSourceSurface> surface =
- Factory::CreateDataSourceSurface(bounds.Size(), format);
+ aData ? Factory::CreateWrappingDataSourceSurface(aData, aStride,
+ bounds.Size(), format)
+ : Factory::CreateDataSourceSurface(bounds.Size(), format);
if (!surface) {
return nullptr;
}
@@ -1283,6 +1285,10 @@ already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshot(
return surface.forget();
}
+static inline int32_t GetPBOStride(int32_t aWidth, SurfaceFormat aFormat) {
+ return GetAlignedStride<16>(aWidth, BytesPerPixel(aFormat));
+}
+
already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO(
SourceSurfaceWebgl* aOwner, TextureHandle* aHandle) {
// Allocate a PBO, and read from the WebGL context into it.
@@ -1295,8 +1301,8 @@ already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO(
format = mCurrentTarget->GetFormat();
bounds = mCurrentTarget->GetRect();
}
- int32_t stride = GetAlignedStride<16>(bounds.width, BytesPerPixel(format));
- size_t bufSize = BufferSizeFromStrideAndHeight(stride, bounds.height);
+ int32_t pboStride = GetPBOStride(bounds.width, format);
+ size_t bufSize = BufferSizeFromStrideAndHeight(pboStride, bounds.height);
if (!bufSize) {
return nullptr;
}
@@ -1317,7 +1323,7 @@ already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO(
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)) {
+ if (!ReadInto(nullptr, pboStride, format, bounds, aHandle, pbo)) {
return nullptr;
}
@@ -1334,16 +1340,20 @@ already_AddRefed<WebGLBuffer> SharedContextWebgl::ReadSnapshotIntoPBO(
already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshotFromPBO(
const RefPtr<WebGLBuffer>& aBuffer, SurfaceFormat aFormat,
- const IntSize& aSize) {
+ const IntSize& aSize, uint8_t* aData, int32_t aStride) {
// 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);
+ int32_t pboStride = GetPBOStride(aSize.width, aFormat);
+ size_t bufSize =
+ BufferSizeFromStrideAndHeight(aData ? aStride : pboStride, aSize.height);
if (!bufSize) {
return nullptr;
}
RefPtr<DataSourceSurface> surface =
- Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, stride);
+ aData ? Factory::CreateWrappingDataSourceSurface(aData, aStride, aSize,
+ aFormat)
+ : Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
+ pboStride);
if (!surface) {
return nullptr;
}
@@ -1353,8 +1363,9 @@ already_AddRefed<DataSourceSurface> SharedContextWebgl::ReadSnapshotFromPBO(
}
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);
+ bool success = mWebgl->AsWebGL2()->GetBufferSubData(
+ LOCAL_GL_PIXEL_PACK_BUFFER, 0, range, aSize.height,
+ BytesPerPixel(aFormat) * aSize.height, pboStride, dstMap.GetStride());
mWebgl->BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, 0);
if (success) {
return surface.forget();
@@ -1368,8 +1379,8 @@ void SharedContextWebgl::RemoveSnapshotPBO(
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);
+ int32_t pboStride = GetPBOStride(size.width, format);
+ size_t bufSize = BufferSizeFromStrideAndHeight(pboStride, size.height);
// If the queue is empty, no memory should be used. Otherwise, deduct the
// usage from the queue.
if (mSnapshotPBOs.empty()) {
@@ -1404,13 +1415,14 @@ bool DrawTargetWebgl::ReadInto(uint8_t* aDstData, int32_t aDstStride) {
}
// Utility method to install the target before reading a snapshot.
-already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot() {
+already_AddRefed<DataSourceSurface> DrawTargetWebgl::ReadSnapshot(
+ uint8_t* aData, int32_t aStride) {
AutoRestoreContext restore(this);
if (!PrepareContext(false)) {
return nullptr;
}
mProfile.OnReadback();
- return mSharedContext->ReadSnapshot();
+ return mSharedContext->ReadSnapshot(nullptr, aData, aStride);
}
already_AddRefed<WebGLBuffer> DrawTargetWebgl::ReadSnapshotIntoPBO(
@@ -3929,11 +3941,10 @@ already_AddRefed<SourceSurface> SharedContextWebgl::DownscaleBlurInput(
if (fullHandle) {
fullBounds += fullHandle->GetBounds().TopLeft();
}
- static_cast<WebGL2Context*>(mWebgl.get())
- ->BlitFramebuffer(fullBounds.x, fullBounds.y, fullBounds.XMost(),
- fullBounds.YMost(), halfBounds.x, halfBounds.y,
- halfBounds.XMost(), halfBounds.YMost(),
- LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_LINEAR);
+ mWebgl->AsWebGL2()->BlitFramebuffer(
+ fullBounds.x, fullBounds.y, fullBounds.XMost(), fullBounds.YMost(),
+ halfBounds.x, halfBounds.y, halfBounds.XMost(), halfBounds.YMost(),
+ LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_LINEAR);
fullHandle = halfHandle;
fullTex = halfBacking->GetWebGLTexture();
diff --git a/dom/canvas/DrawTargetWebgl.h b/dom/canvas/DrawTargetWebgl.h
@@ -349,7 +349,8 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
const IntRect& aBounds, TextureHandle* aHandle = nullptr,
const RefPtr<WebGLBuffer>& aBuffer = nullptr);
already_AddRefed<DataSourceSurface> ReadSnapshot(
- TextureHandle* aHandle = nullptr);
+ TextureHandle* aHandle = nullptr, uint8_t* aData = nullptr,
+ int32_t aStride = 0);
already_AddRefed<TextureHandle> WrapSnapshot(const IntSize& aSize,
SurfaceFormat aFormat,
RefPtr<WebGLTexture> aTex);
@@ -360,7 +361,7 @@ class SharedContextWebgl : public mozilla::RefCounted<SharedContextWebgl>,
SourceSurfaceWebgl* aOwner, TextureHandle* aHandle = nullptr);
already_AddRefed<DataSourceSurface> ReadSnapshotFromPBO(
const RefPtr<WebGLBuffer>& aBuffer, SurfaceFormat aFormat,
- const IntSize& aSize);
+ const IntSize& aSize, uint8_t* aData = nullptr, int32_t aStride = 0);
void RemoveSnapshotPBO(SourceSurfaceWebgl* aOwner,
already_AddRefed<WebGLBuffer> aBuffer);
void ClearSnapshotPBOs(size_t aMaxMemory = 0);
@@ -835,7 +836,8 @@ class DrawTargetWebgl : public DrawTarget, public SupportsWeakPtr {
bool ShouldUseSubpixelAA(ScaledFont* aFont, const DrawOptions& aOptions);
bool ReadInto(uint8_t* aDstData, int32_t aDstStride);
- already_AddRefed<DataSourceSurface> ReadSnapshot();
+ already_AddRefed<DataSourceSurface> ReadSnapshot(uint8_t* aData = nullptr,
+ int32_t aStride = 0);
already_AddRefed<WebGLBuffer> ReadSnapshotIntoPBO(SourceSurfaceWebgl* aOwner);
already_AddRefed<TextureHandle> CopySnapshot(const IntRect& aRect);
already_AddRefed<TextureHandle> CopySnapshot() {
diff --git a/dom/canvas/SourceSurfaceWebgl.cpp b/dom/canvas/SourceSurfaceWebgl.cpp
@@ -8,6 +8,7 @@
#include "DrawTargetWebglInternal.h"
#include "WebGLBuffer.h"
+#include "mozilla/gfx/Swizzle.h"
namespace mozilla::gfx {
@@ -37,15 +38,26 @@ SourceSurfaceWebgl::~SourceSurfaceWebgl() {
// 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) {
+inline bool SourceSurfaceWebgl::EnsureData(bool aForce, uint8_t* aData,
+ int32_t aStride) {
if (mData) {
+ if (aData) {
+ DataSourceSurface::ScopedMap map(mData, MapType::READ);
+ if (!map.IsMapped()) {
+ return false;
+ }
+ SwizzleData(map.GetData(), map.GetStride(), mFormat, aData, aStride,
+ mFormat, mSize);
+ }
return true;
}
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);
+ mData = sharedContext->ReadSnapshotFromPBO(mReadBuffer, mFormat, mSize,
+ aData, aStride);
+ mOwnsData = !mData || !aData;
sharedContext->RemoveSnapshotPBO(this, mReadBuffer.forget());
}
mReadBuffer = nullptr;
@@ -57,7 +69,8 @@ inline bool SourceSurfaceWebgl::EnsureData(bool aForce) {
mReadBuffer = dt->ReadSnapshotIntoPBO(this);
}
if (!mReadBuffer) {
- mData = dt->ReadSnapshot();
+ mData = dt->ReadSnapshot(aData, aStride);
+ mOwnsData = !mData || !aData;
}
} else if (mHandle) {
// Assume that the target changed, so there should be a texture handle
@@ -68,13 +81,18 @@ inline bool SourceSurfaceWebgl::EnsureData(bool aForce) {
mReadBuffer = sharedContext->ReadSnapshotIntoPBO(this, mHandle);
}
if (!mReadBuffer) {
- mData = sharedContext->ReadSnapshot(mHandle);
+ mData = sharedContext->ReadSnapshot(mHandle, aData, aStride);
+ mOwnsData = !mData || !aData;
}
}
}
return mData || mReadBuffer;
}
+bool SourceSurfaceWebgl::ReadDataInto(uint8_t* aData, int32_t aStride) {
+ return EnsureData(true, aData, aStride);
+}
+
bool SourceSurfaceWebgl::ForceReadFromPBO() {
if (mReadBuffer && EnsureData()) {
MOZ_ASSERT(!mReadBuffer);
@@ -87,7 +105,11 @@ uint8_t* SourceSurfaceWebgl::GetData() {
if (!EnsureData()) {
return nullptr;
}
- return mData->GetData();
+ if (!mOwnsData) {
+ mData = Factory::CopyDataSourceSurface(mData);
+ mOwnsData = true;
+ }
+ return mData ? mData->GetData() : nullptr;
}
int32_t SourceSurfaceWebgl::Stride() {
@@ -101,7 +123,11 @@ bool SourceSurfaceWebgl::Map(MapType aType, MappedSurface* aMappedSurface) {
if (!EnsureData()) {
return false;
}
- return mData->Map(aType, aMappedSurface);
+ if (!mOwnsData && aType != MapType::READ) {
+ mData = Factory::CopyDataSourceSurface(mData);
+ mOwnsData = true;
+ }
+ return mData && mData->Map(aType, aMappedSurface);
}
void SourceSurfaceWebgl::Unmap() {
@@ -178,6 +204,7 @@ void SourceSurfaceWebgl::OnUnlinkTexture(SharedContextWebgl* aContext,
}
if (!mReadBuffer) {
mData = aContext->ReadSnapshot(mHandle);
+ mOwnsData = true;
}
}
mHandle = nullptr;
diff --git a/dom/canvas/SourceSurfaceWebgl.h b/dom/canvas/SourceSurfaceWebgl.h
@@ -44,13 +44,16 @@ class SourceSurfaceWebgl : public DataSourceSurface {
already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) override;
+ bool ReadDataInto(uint8_t* aData, int32_t aStride) override;
+
private:
friend class DrawTargetWebgl;
friend class SharedContextWebgl;
explicit SourceSurfaceWebgl(const RefPtr<SharedContextWebgl>& aSharedContext);
- bool EnsureData(bool aForce = true);
+ bool EnsureData(bool aForce = true, uint8_t* aData = nullptr,
+ int32_t aStride = 0);
bool ForceReadFromPBO();
void DrawTargetWillChange(bool aNeedHandle);
@@ -68,6 +71,8 @@ class SourceSurfaceWebgl : public DataSourceSurface {
RefPtr<WebGLBuffer> mReadBuffer;
// Any data that has been read back from the WebGL context for mapping.
RefPtr<DataSourceSurface> mData;
+ // Whether the data is safe to modify
+ bool mOwnsData = true;
// The draw target that currently owns the texture for this surface.
WeakPtr<DrawTargetWebgl> mDT;
// The actual shared context that any WebGL resources belong to.
diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h
@@ -30,6 +30,8 @@ class WebGL2Context final : public WebGLContext {
virtual bool IsWebGL2() const override { return true; }
+ virtual WebGL2Context* AsWebGL2() override { return this; }
+
// -------------------------------------------------------------------------
// Buffer objects - WebGL2ContextBuffers.cpp
@@ -37,7 +39,9 @@ class WebGL2Context final : public WebGLContext {
uint64_t readOffset, uint64_t writeOffset,
uint64_t size) const;
bool GetBufferSubData(GLenum target, uint64_t srcByteOffset,
- const Range<uint8_t>& dest) const;
+ const Range<uint8_t>& dest, uint64_t numRows = 0,
+ uint64_t rowDataWidth = 0, uint64_t srcStride = 0,
+ uint64_t destStride = 0) const;
// -------------------------------------------------------------------------
// Framebuffer objects - WebGL2ContextFramebuffers.cpp
diff --git a/dom/canvas/WebGL2ContextBuffers.cpp b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -78,24 +78,35 @@ void WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
}
bool WebGL2Context::GetBufferSubData(GLenum target, uint64_t srcByteOffset,
- const Range<uint8_t>& dest) const {
+ const Range<uint8_t>& dest,
+ uint64_t numRows, uint64_t rowDataWidth,
+ uint64_t srcStride,
+ uint64_t destStride) const {
const FuncScope funcScope(*this, "getBufferSubData");
if (IsContextLost()) return false;
const auto& buffer = ValidateBufferSelection(target);
if (!buffer) return false;
- const auto byteLen = dest.length();
- if (!buffer->ValidateRange(srcByteOffset, byteLen)) return false;
+ uint64_t srcLen =
+ numRows > 0 ? srcStride * (numRows - 1) + rowDataWidth : dest.length();
+ uint64_t destLen =
+ numRows > 0 ? destStride * (numRows - 1) + rowDataWidth : dest.length();
+ if (!buffer->ValidateRange(srcByteOffset, srcLen)) return false;
+ if (rowDataWidth > srcStride || rowDataWidth > destStride ||
+ destLen > dest.length()) {
+ ErrorInvalidValue("Destination is outside buffer.");
+ return false;
+ }
////
if (!CheckedInt<GLintptr>(srcByteOffset).isValid() ||
- !CheckedInt<GLsizeiptr>(byteLen).isValid()) {
- ErrorOutOfMemory("offset or size too large for platform.");
+ !CheckedInt<GLsizeiptr>(srcLen).isValid()) {
+ ErrorOutOfMemory("Offset or size too large for platform.");
return false;
}
- const GLsizeiptr glByteLen(byteLen);
+ const GLsizeiptr glByteLen(srcLen);
////
@@ -121,7 +132,7 @@ bool WebGL2Context::GetBufferSubData(GLenum target, uint64_t srcByteOffset,
const ScopedLazyBind readBind(gl, target, buffer);
- if (byteLen) {
+ if (srcLen) {
const bool isTF = (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
GLenum mapTarget = target;
if (isTF) {
@@ -130,9 +141,20 @@ bool WebGL2Context::GetBufferSubData(GLenum target, uint64_t srcByteOffset,
mapTarget = LOCAL_GL_ARRAY_BUFFER;
}
- const auto mappedBytes = gl->fMapBufferRange(
+ const void* mappedBytes = gl->fMapBufferRange(
mapTarget, srcByteOffset, glByteLen, LOCAL_GL_MAP_READ_BIT);
- memcpy(dest.begin().get(), mappedBytes, dest.length());
+ if (numRows > 0 && (destStride != srcStride || rowDataWidth != srcStride)) {
+ const uint8_t* srcRow = (const uint8_t*)mappedBytes;
+ uint8_t* destRow = dest.begin().get();
+ while (numRows > 0) {
+ memcpy(destRow, srcRow, rowDataWidth);
+ srcRow += srcStride;
+ destRow += destStride;
+ --numRows;
+ }
+ } else {
+ memcpy(dest.begin().get(), mappedBytes, srcLen);
+ }
gl->fUnmapBuffer(mapTarget);
if (isTF) {
diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h
@@ -77,6 +77,7 @@ class WebGLSync;
class WebGLTexture;
class WebGLTransformFeedback;
class WebGLVertexArray;
+class WebGL2Context;
namespace dom {
class Document;
@@ -1024,6 +1025,8 @@ class WebGLContext : public VRefCounted, public SupportsWeakPtr {
public:
virtual bool IsWebGL2() const { return false; }
+ virtual WebGL2Context* AsWebGL2() { return nullptr; }
+
struct FailureReason {
nsCString key; // For reporting.
nsCString info;
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h
@@ -883,6 +883,11 @@ class DataSourceSurface : public SourceSurface {
*/
virtual void Invalidate(const IntRect& aDirtyRect) {}
+ /**
+ * Attempt to cache internal data into the supplied memory buffer.
+ */
+ virtual bool ReadDataInto(uint8_t* aData, int32_t aStride) { return false; }
+
protected:
Atomic<int32_t> mMapCount;
};
diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp
@@ -952,7 +952,8 @@ already_AddRefed<DataSourceSurface> Factory::CopyDataSourceSurface(
MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
- aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
+ aSource->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+ aSource->GetFormat() == SurfaceFormat::A8);
DataSourceSurface::ScopedMap srcMap(aSource, DataSourceSurface::READ);
if (NS_WARN_IF(!srcMap.IsMapped())) {
@@ -987,12 +988,14 @@ void Factory::CopyDataSourceSurface(DataSourceSurface* aSource,
MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aSource->GetFormat() == SurfaceFormat::R8G8B8X8 ||
aSource->GetFormat() == SurfaceFormat::B8G8R8A8 ||
- aSource->GetFormat() == SurfaceFormat::B8G8R8X8);
+ aSource->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+ aSource->GetFormat() == SurfaceFormat::A8);
MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 ||
aDest->GetFormat() == SurfaceFormat::R8G8B8X8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8A8 ||
aDest->GetFormat() == SurfaceFormat::B8G8R8X8 ||
- aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16);
+ aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16 ||
+ aDest->GetFormat() == SurfaceFormat::A8);
DataSourceSurface::MappedSurface srcMap;
DataSourceSurface::MappedSurface destMap;
diff --git a/gfx/layers/ipc/CanvasChild.cpp b/gfx/layers/ipc/CanvasChild.cpp
@@ -625,7 +625,7 @@ already_AddRefed<gfx::DataSourceSurface> CanvasChild::GetDataSurface(
}
}
- RecordEvent(RecordedCacheDataSurface(aSurface, true));
+ RecordEvent(RecordedCacheDataSurface(aSurface));
if (!EnsureDataSurfaceShmem(ssSize, ssFormat)) {
return nullptr;
diff --git a/gfx/layers/ipc/CanvasTranslator.cpp b/gfx/layers/ipc/CanvasTranslator.cpp
@@ -304,6 +304,15 @@ ipc::IPCResult CanvasTranslator::RecvSetDataSurfaceBuffer(
return IPC_OK();
}
+void CanvasTranslator::DataSurfaceBufferWillChange() {
+ if (RefPtr<gfx::DataSourceSurface> owner = {mDataSurfaceShmemOwner}) {
+ // Force copy-on-write of contained shmem if applicable
+ gfx::DataSourceSurface::ScopedMap map(
+ owner, gfx::DataSourceSurface::MapType::READ_WRITE);
+ mDataSurfaceShmemOwner = nullptr;
+ }
+}
+
bool CanvasTranslator::SetDataSurfaceBuffer(
ipc::MutableSharedMemoryHandle&& aBufferHandle) {
MOZ_ASSERT(IsInTaskQueue());
@@ -322,6 +331,7 @@ bool CanvasTranslator::SetDataSurfaceBuffer(
return false;
}
+ DataSurfaceBufferWillChange();
mDataSurfaceShmem = aBufferHandle.Map();
if (!mDataSurfaceShmem) {
return false;
@@ -345,22 +355,8 @@ void CanvasTranslator::GetDataSurface(uint64_t aSurfaceRef) {
return;
}
}
- gfx::DataSourceSurface::ScopedMap map(dataSurface,
- gfx::DataSourceSurface::READ);
- if (!map.IsMapped()) {
- return;
- }
-
auto dstSize = dataSurface->GetSize();
- auto srcSize = map.GetSurface()->GetSize();
gfx::SurfaceFormat format = dataSurface->GetFormat();
- int32_t bpp = BytesPerPixel(format);
- int32_t dataFormatWidth = dstSize.width * bpp;
- int32_t srcStride = map.GetStride();
- if (dataFormatWidth > srcStride || srcSize != dstSize) {
- return;
- }
-
int32_t dstStride =
ImageDataSerializer::ComputeRGBStride(format, dstSize.width);
auto requiredSize =
@@ -369,14 +365,25 @@ void CanvasTranslator::GetDataSurface(uint64_t aSurfaceRef) {
return;
}
+ // Ensure any old references to the shmem are copied before modification.
+ DataSurfaceBufferWillChange();
+
+ // Try directly reading the data surface into shmem to avoid further copies.
uint8_t* dst = mDataSurfaceShmem.DataAs<uint8_t>();
- const uint8_t* src = map.GetData();
- const uint8_t* endSrc = src + (srcSize.height * srcStride);
- while (src < endSrc) {
- memcpy(dst, src, dataFormatWidth);
- src += srcStride;
- dst += dstStride;
+ if (dataSurface->ReadDataInto(dst, dstStride)) {
+ mDataSurfaceShmemOwner = dataSurface;
+ return;
+ }
+
+ // Otherwise, map the data surface and do an explicit copy.
+ gfx::DataSourceSurface::ScopedMap map(dataSurface,
+ gfx::DataSourceSurface::MapType::READ);
+ if (!map.IsMapped()) {
+ return;
}
+
+ gfx::SwizzleData(map.GetData(), map.GetStride(), format, dst, dstStride,
+ format, dstSize);
}
already_AddRefed<gfx::SourceSurface> CanvasTranslator::WaitForSurface(
@@ -998,6 +1005,8 @@ void CanvasTranslator::PrepareShmem(
}
void CanvasTranslator::CacheDataSnapshots() {
+ DataSurfaceBufferWillChange();
+
if (mSharedContext) {
// If there are any DrawTargetWebgls, then try to cache their framebuffers
// in software surfaces, just in case the GL context is lost. So long as
@@ -1372,6 +1381,8 @@ bool CanvasTranslator::PushRemoteTexture(
void CanvasTranslator::ClearTextureInfo() {
MOZ_ASSERT(mIPDLClosed);
+ DataSurfaceBufferWillChange();
+
mUsedDataSurfaceForSurfaceDescriptor = nullptr;
mUsedWrapperForSurfaceDescriptor = nullptr;
mUsedSurfaceDescriptorForSurfaceDescriptor = Nothing();
diff --git a/gfx/layers/ipc/CanvasTranslator.h b/gfx/layers/ipc/CanvasTranslator.h
@@ -411,6 +411,8 @@ class CanvasTranslator final : public gfx::InlineTranslator,
*/
bool SetDataSurfaceBuffer(ipc::MutableSharedMemoryHandle&& aBufferHandle);
+ void DataSurfaceBufferWillChange();
+
bool ReadNextEvent(EventType& aEventType);
bool HasPendingEvent();
@@ -526,6 +528,7 @@ class CanvasTranslator final : public gfx::InlineTranslator,
CanvasShmem mCurrentShmem;
gfx::MemReader mCurrentMemReader{0, 0};
ipc::SharedMemoryMapping mDataSurfaceShmem;
+ ThreadSafeWeakPtr<gfx::DataSourceSurface> mDataSurfaceShmemOwner;
UniquePtr<CrossProcessSemaphore> mWriterSemaphore;
UniquePtr<CrossProcessSemaphore> mReaderSemaphore;
TextureType mTextureType = TextureType::Unknown;