Skip to content

Commit

Permalink
OffscreenCanvas.transferToImageBitmap() context specific code should …
Browse files Browse the repository at this point in the history
…exist in the context

https://bugs.webkit.org/show_bug.cgi?id=275105
rdar://129217576

Reviewed by Antti Koivisto.

OffscreenCanvas::transferToImageBitmap() has code conditionalized to
different canvas rendering contexts. Move this code to
the contexts, into overrides of new virtual function
CanvasRenderingContext::transferToImageBuffer().

This way the contexts are able to manage the buffers themselves in
future commits.

Fix the error-case where the out-of-memory causes an exception instead
of null ImageBitmap for all backends. The exception should be
DOM UnknownError, that is used to signify out-of-memory.

* Source/WebCore/html/CanvasBase.h:
* Source/WebCore/html/OffscreenCanvas.cpp:
(WebCore::OffscreenCanvas::transferToImageBitmap):
* Source/WebCore/html/canvas/CanvasRenderingContext.cpp:
(WebCore::CanvasRenderingContext::transferToImageBuffer):
* Source/WebCore/html/canvas/CanvasRenderingContext.h:
* Source/WebCore/html/canvas/GPUCanvasContext.h:
* Source/WebCore/html/canvas/GPUCanvasContextCocoa.h:
* Source/WebCore/html/canvas/GPUCanvasContextCocoa.mm:
(WebCore::GPUCanvasContextCocoa::transferToImageBuffer):
(WebCore::GPUCanvasContextCocoa::getCurrentTextureAsImageBitmap): Deleted.
* Source/WebCore/html/canvas/ImageBitmapRenderingContext.cpp:
(WebCore::ImageBitmapRenderingContext::setOutputBitmap):
(WebCore::ImageBitmapRenderingContext::setBlank):
(WebCore::ImageBitmapRenderingContext::transferToImageBuffer):
* Source/WebCore/html/canvas/ImageBitmapRenderingContext.h:
* Source/WebCore/html/canvas/OffscreenCanvasRenderingContext2D.cpp:
(WebCore::OffscreenCanvasRenderingContext2D::transferToImageBuffer):
* Source/WebCore/html/canvas/OffscreenCanvasRenderingContext2D.h:
* Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::transferToImageBuffer):
(WebCore::WebGLRenderingContextBase::markDrawingBuffersDirtyAfterTransfer): Deleted.
* Source/WebCore/html/canvas/WebGLRenderingContextBase.h:

Canonical link: https://commits.webkit.org/279872@main
  • Loading branch information
kkinnunen-apple authored and mnutt committed Jun 30, 2024
1 parent b4ed929 commit 5b1469a
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
Testing height: 100 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got result with size: 100x100
Testing height: 100 type: webgl
Got context: "[object WebGLRenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgpu
Got context: "null"
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got result with size: 100x100
Testing height: 100000000 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100000000 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl
Got context: "[object WebGLRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgpu
Got context: "null"
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100000000 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
PASS successfullyParsed is true

TEST COMPLETE

Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
<body>
<script src="../../resources/js-test.js"></script>
<script>
onload = () => {
if (window.testRunner) {
testRunner.dumpAsText();
function runTest() {
if (!window.OffscreenCanvas) {
debug("OffscreenCanvas not enabled");
return;
}
let offscreenCanvas = new OffscreenCanvas(1, 100000000);
offscreenCanvas.getContext('2d');
offscreenCanvas.width = 100;
offscreenCanvas.transferToImageBitmap();
}
let types = ["none", "2d", "webgl", "webgl2", "webgpu", "bitmaprenderer"];
let heights = [100, 100000000];
for (let height of heights) {
for (let type of types) {
debug(`Testing height: ${height} type: ${type} `);
let canvas = new OffscreenCanvas(1, height);
if (type != "none") {
let ctx = canvas.getContext(type);
debug(`Got context: "${ctx}"`);
}
canvas.width = 100;
try {
let result = canvas.transferToImageBitmap();
if (result)
debug(`Got result with size: ${result.width}x${result.height}`);
else
debug(`Got result: "${result}"`);
} catch (e) {
debug(`Got exception: "${e}"`);
}
delete ctx;
delete canvas;
}
}
}
runTest();
</script>
</body>
Original file line number Diff line number Diff line change
@@ -1,2 +1,43 @@
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 67108864).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 67108864).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 67108864).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 67108864).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 67108864).
Testing height: 100 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got result with size: 100x100
Testing height: 100 type: webgl
Got context: "[object WebGLRenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgpu
Got context: "[object GPUCanvasContext]"
Got result with size: 100x100
Testing height: 100 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got result with size: 100x100
Testing height: 100000000 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100000000 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl
Got context: "[object WebGLRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgpu
Got context: "[object GPUCanvasContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
PASS successfullyParsed is true

TEST COMPLETE

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
CONSOLE MESSAGE: Canvas area exceeds the maximum limit (width * height > 268435456).
Testing height: 100 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got result with size: 100x100
Testing height: 100 type: webgl
Got context: "[object WebGLRenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got result with size: 100x100
Testing height: 100 type: webgpu
Got context: "[object GPUCanvasContext]"
Got result with size: 100x100
Testing height: 100 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got result with size: 100x100
Testing height: 100000000 type: none
Got exception: "InvalidStateError: The object is in an invalid state."
Testing height: 100000000 type: 2d
Got context: "[object OffscreenCanvasRenderingContext2D]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl
Got context: "[object WebGLRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgl2
Got context: "[object WebGL2RenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: webgpu
Got context: "[object GPUCanvasContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
Testing height: 100000000 type: bitmaprenderer
Got context: "[object ImageBitmapRenderingContext]"
Got exception: "UnknownError: The operation failed for an unknown transient reason (e.g. out of memory)."
PASS successfullyParsed is true

TEST COMPLETE

3 changes: 2 additions & 1 deletion Source/WebCore/html/CanvasBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ class CanvasBase {
void setNoiseInjectionSalt(NoiseInjectionHashSalt salt) { m_canvasNoiseHashSalt = salt; }
bool havePendingCanvasNoiseInjection() const { return m_canvasNoiseInjection.haveDirtyRects(); }

// FIXME(https://bugs.webkit.org/show_bug.cgi?id=275100): The image buffer from CanvasBase should be moved to CanvasRenderingContext2DBase.
virtual bool hasCreatedImageBuffer() const { return false; }
RefPtr<ImageBuffer> allocateImageBuffer() const;

protected:
explicit CanvasBase(IntSize, const std::optional<NoiseInjectionHashSalt>&);
Expand All @@ -163,7 +165,6 @@ class CanvasBase {
virtual void setSize(const IntSize&);

RefPtr<ImageBuffer> setImageBuffer(RefPtr<ImageBuffer>&&) const;
RefPtr<ImageBuffer> allocateImageBuffer() const;
String lastFillText() const { return m_lastFillText; }
void addCanvasNeedingPreparationForDisplayOrFlush();
void removeCanvasNeedingPreparationForDisplayOrFlush();
Expand Down
69 changes: 7 additions & 62 deletions Source/WebCore/html/OffscreenCanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,68 +304,13 @@ ExceptionOr<RefPtr<ImageBitmap>> OffscreenCanvas::transferToImageBitmap()
{
if (m_detached || !m_context)
return Exception { ExceptionCode::InvalidStateError };

if (is<OffscreenCanvasRenderingContext2D>(*m_context) || is<ImageBitmapRenderingContext>(*m_context)) {
if (!width() || !height())
return { RefPtr<ImageBitmap> { nullptr } };

if (!m_hasCreatedImageBuffer) {
auto buffer = allocateImageBuffer();
if (!buffer)
return { RefPtr<ImageBitmap> { nullptr } };
return { ImageBitmap::create(buffer.releaseNonNull(), originClean()) };
}

if (!buffer())
return { RefPtr<ImageBitmap> { nullptr } };

RefPtr<ImageBuffer> bitmap;
if (RefPtr context = dynamicDowncast<OffscreenCanvasRenderingContext2D>(*m_context)) {
// As the canvas context state is stored in GraphicsContext, which is owned
// by buffer(), to avoid resetting the context state, we have to make a copy and
// clear the original buffer rather than returning the original buffer.
bitmap = buffer()->clone();
context->clearCanvas();
} else {
// ImageBitmapRenderingContext doesn't use the context state, so we can just take its
// buffer, and then call transferFromImageBitmap(nullptr) which will trigger it to allocate
// a new blank bitmap.
bitmap = buffer();
downcast<ImageBitmapRenderingContext>(*m_context).transferFromImageBitmap(nullptr);
}
clearCopiedImage();
if (!bitmap)
return { RefPtr<ImageBitmap> { nullptr } };
return { ImageBitmap::create(bitmap.releaseNonNull(), originClean(), false, false) };
}

#if ENABLE(WEBGL)
if (auto* webGLContext = dynamicDowncast<WebGLRenderingContextBase>(*m_context)) {
// FIXME: We're supposed to create an ImageBitmap using the backing
// store from this canvas (or its context), but for now we'll just
// create a new bitmap and paint into it.
auto buffer = allocateImageBuffer();
if (!buffer)
return { RefPtr<ImageBitmap> { nullptr } };
if (webGLContext->compositingResultsNeedUpdating())
webGLContext->prepareForDisplay();
RefPtr gc3d = webGLContext->graphicsContextGL();
gc3d->drawSurfaceBufferToImageBuffer(GraphicsContextGL::SurfaceBuffer::DisplayBuffer, *buffer);
webGLContext->markDrawingBuffersDirtyAfterTransfer();
return { ImageBitmap::create(buffer.releaseNonNull(), originClean()) };
}
#endif

if (auto* context = dynamicDowncast<GPUCanvasContext>(*m_context)) {
auto buffer = allocateImageBuffer();
if (!buffer)
return Exception { ExceptionCode::OutOfMemoryError };

Ref<ImageBuffer> bufferRef = buffer.releaseNonNull();
return context->getCurrentTextureAsImageBitmap(bufferRef, originClean());
}

return Exception { ExceptionCode::NotSupportedError };
if (size().isEmpty())
return { RefPtr<ImageBitmap> { nullptr } };
clearCopiedImage();
RefPtr buffer = m_context->transferToImageBuffer();
if (!buffer)
return Exception { ExceptionCode::UnknownError }; // UnknownError is used for DOM out-of-memory.
return { ImageBitmap::create(buffer.releaseNonNull(), originClean()) };
}

static String toEncodingMimeType(const String& mimeType)
Expand Down
8 changes: 7 additions & 1 deletion Source/WebCore/html/canvas/CanvasRenderingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"
Expand Down Expand Up @@ -108,6 +108,12 @@ void CanvasRenderingContext::setContentsToLayer(GraphicsLayer& layer)
layer.setContentsDisplayDelegate(layerContentsDisplayDelegate(), GraphicsLayer::ContentsLayerPurpose::Canvas);
}

RefPtr<ImageBuffer> CanvasRenderingContext::transferToImageBuffer()
{
ASSERT_NOT_REACHED(); // Implemented and called only for offscreen capable contexts.
return nullptr;
}

PixelFormat CanvasRenderingContext::pixelFormat() const
{
return PixelFormat::BGRA8;
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/html/canvas/CanvasRenderingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ class CanvasRenderingContext : public ScriptWrappable, public CanMakeWeakPtr<Can
virtual RefPtr<GraphicsLayerContentsDisplayDelegate> layerContentsDisplayDelegate();
virtual void setContentsToLayer(GraphicsLayer&);

// Returns the drawing buffer and runs the compositing steps of transferToImageBitmap.
virtual RefPtr<ImageBuffer> transferToImageBuffer();

bool hasActiveInspectorCanvasCallTracer() const { return m_hasActiveInspectorCanvasCallTracer; }
void setHasActiveInspectorCanvasCallTracer(bool hasActiveInspectorCanvasCallTracer) { m_hasActiveInspectorCanvasCallTracer = hasActiveInspectorCanvasCallTracer; }

Expand Down
1 change: 0 additions & 1 deletion Source/WebCore/html/canvas/GPUCanvasContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ class GPUCanvasContext : public GPUBasedCanvasRenderingContext {
virtual ExceptionOr<void> configure(GPUCanvasConfiguration&&) = 0;
virtual void unconfigure() = 0;
virtual ExceptionOr<RefPtr<GPUTexture>> getCurrentTexture() = 0;
virtual ExceptionOr<RefPtr<ImageBitmap>> getCurrentTextureAsImageBitmap(ImageBuffer&, bool originClean) = 0;

bool isWebGPU() const override { return true; }

Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/html/canvas/GPUCanvasContextCocoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class GPUCanvasContextCocoa final : public GPUCanvasContext {
ExceptionOr<void> configure(GPUCanvasConfiguration&&) override;
void unconfigure() override;
ExceptionOr<RefPtr<GPUTexture>> getCurrentTexture() override;
ExceptionOr<RefPtr<ImageBitmap>> getCurrentTextureAsImageBitmap(ImageBuffer&, bool originClean) override;
RefPtr<ImageBuffer> transferToImageBuffer() override;

bool isWebGPU() const override { return true; }

Expand Down
14 changes: 7 additions & 7 deletions Source/WebCore/html/canvas/GPUCanvasContextCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -199,20 +199,20 @@ static GPUIntegerCoordinate getCanvasHeight(const GPUCanvasContext::CanvasType&
return canvasBase().buffer();
}

ExceptionOr<RefPtr<ImageBitmap>> GPUCanvasContextCocoa::getCurrentTextureAsImageBitmap(ImageBuffer& buffer, bool originClean)
RefPtr<ImageBuffer> GPUCanvasContextCocoa::transferToImageBuffer()
{
auto buffer = canvasBase().allocateImageBuffer();
if (!buffer)
return nullptr;
Ref<ImageBuffer> bufferRef = buffer.releaseNonNull();
if (m_configuration) {
buffer.flushDrawingContext();
if (m_compositorIntegration)
m_compositorIntegration->paintCompositedResultsToCanvas(buffer, m_configuration->frameCount);
m_compositorIntegration->paintCompositedResultsToCanvas(bufferRef, m_configuration->frameCount);
m_currentTexture = nullptr;
if (m_presentationContext)
m_presentationContext->present(true);

return { ImageBitmap::create(buffer, originClean) };
}

return { ImageBitmap::create(buffer, originClean) };
return bufferRef;
}

GPUCanvasContext::CanvasType GPUCanvasContextCocoa::canvas()
Expand Down
Loading

0 comments on commit 5b1469a

Please sign in to comment.