| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "modules/canvas2d/CanvasRenderingContext2D.h" |
| |
| #include "core/fetch/MemoryCache.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/ImageBitmap.h" |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/html/HTMLDocument.h" |
| #include "core/html/ImageData.h" |
| #include "core/imagebitmap/ImageBitmapOptions.h" |
| #include "core/loader/EmptyClients.h" |
| #include "core/testing/DummyPageHolder.h" |
| #include "modules/canvas2d/CanvasGradient.h" |
| #include "modules/canvas2d/CanvasPattern.h" |
| #include "modules/webgl/WebGLRenderingContext.h" |
| #include "platform/graphics/ExpensiveCanvasHeuristicParameters.h" |
| #include "platform/graphics/RecordingImageBufferSurface.h" |
| #include "platform/graphics/StaticBitmapImage.h" |
| #include "platform/graphics/UnacceleratedImageBufferSurface.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| |
| using ::testing::Mock; |
| |
| namespace blink { |
| |
| enum BitmapOpacity { |
| OpaqueBitmap, |
| TransparentBitmap |
| }; |
| |
| class FakeImageSource : public CanvasImageSource { |
| public: |
| FakeImageSource(IntSize, BitmapOpacity); |
| |
| PassRefPtr<Image> getSourceImageForCanvas(SourceImageStatus*, AccelerationHint, SnapshotReason) const override; |
| |
| bool wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const override { return false; } |
| FloatSize elementSize() const override { return FloatSize(m_size); } |
| bool isOpaque() const override { return m_isOpaque; } |
| |
| ~FakeImageSource() override { } |
| |
| private: |
| IntSize m_size; |
| RefPtr<Image> m_image; |
| bool m_isOpaque; |
| }; |
| |
| FakeImageSource::FakeImageSource(IntSize size, BitmapOpacity opacity) |
| : m_size(size) |
| , m_isOpaque(opacity == OpaqueBitmap) |
| { |
| SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(m_size.width(), m_size.height())); |
| surface->getCanvas()->clear(opacity == OpaqueBitmap ? SK_ColorWHITE : SK_ColorTRANSPARENT); |
| RefPtr<SkImage> image = adoptRef(surface->newImageSnapshot()); |
| m_image = StaticBitmapImage::create(image); |
| } |
| |
| PassRefPtr<Image> FakeImageSource::getSourceImageForCanvas(SourceImageStatus* status, AccelerationHint, SnapshotReason) const |
| { |
| if (status) |
| *status = NormalSourceImageStatus; |
| return m_image; |
| } |
| |
| //============================================================================ |
| |
| class CanvasRenderingContext2DTest : public ::testing::Test { |
| protected: |
| CanvasRenderingContext2DTest(); |
| void SetUp() override; |
| |
| DummyPageHolder& page() const { return *m_dummyPageHolder; } |
| HTMLDocument& document() const { return *m_document; } |
| HTMLCanvasElement& canvasElement() const { return *m_canvasElement; } |
| CanvasRenderingContext2D* context2d() const { return static_cast<CanvasRenderingContext2D*>(canvasElement().renderingContext()); } |
| intptr_t getGlobalGPUMemoryUsage() const { return ImageBuffer::getGlobalGPUMemoryUsage(); } |
| intptr_t getCurrentGPUMemoryUsage() const { return canvasElement().buffer()->getGPUMemoryUsage(); } |
| |
| void createContext(OpacityMode); |
| void TearDown(); |
| |
| private: |
| OwnPtr<DummyPageHolder> m_dummyPageHolder; |
| RefPtrWillBePersistent<HTMLDocument> m_document; |
| RefPtrWillBePersistent<HTMLCanvasElement> m_canvasElement; |
| Persistent<MemoryCache> m_globalMemoryCache; |
| |
| class WrapGradients final : public NoBaseWillBeGarbageCollectedFinalized<WrapGradients> { |
| public: |
| static PassOwnPtrWillBeRawPtr<WrapGradients> create() |
| { |
| return adoptPtrWillBeNoop(new WrapGradients); |
| } |
| |
| DEFINE_INLINE_TRACE() |
| { |
| visitor->trace(m_opaqueGradient); |
| visitor->trace(m_alphaGradient); |
| } |
| |
| StringOrCanvasGradientOrCanvasPattern m_opaqueGradient; |
| StringOrCanvasGradientOrCanvasPattern m_alphaGradient; |
| }; |
| |
| // TODO(Oilpan): avoid tedious part-object wrapper by supporting on-heap ::testing::Tests. |
| OwnPtrWillBePersistent<WrapGradients> m_wrapGradients; |
| |
| protected: |
| // Pre-canned objects for testing |
| Persistent<ImageData> m_fullImageData; |
| Persistent<ImageData> m_partialImageData; |
| FakeImageSource m_opaqueBitmap; |
| FakeImageSource m_alphaBitmap; |
| |
| StringOrCanvasGradientOrCanvasPattern& opaqueGradient() { return m_wrapGradients->m_opaqueGradient; } |
| StringOrCanvasGradientOrCanvasPattern& alphaGradient() { return m_wrapGradients->m_alphaGradient; } |
| }; |
| |
| CanvasRenderingContext2DTest::CanvasRenderingContext2DTest() |
| : m_wrapGradients(WrapGradients::create()) |
| , m_opaqueBitmap(IntSize(10, 10), OpaqueBitmap) |
| , m_alphaBitmap(IntSize(10, 10), TransparentBitmap) |
| { } |
| |
| void CanvasRenderingContext2DTest::createContext(OpacityMode opacityMode) |
| { |
| String canvasType("2d"); |
| CanvasContextCreationAttributes attributes; |
| attributes.setAlpha(opacityMode == NonOpaque); |
| m_canvasElement->getCanvasRenderingContext(canvasType, attributes); |
| } |
| |
| void CanvasRenderingContext2DTest::SetUp() |
| { |
| Page::PageClients pageClients; |
| fillWithEmptyClients(pageClients); |
| m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients); |
| m_document = toHTMLDocument(&m_dummyPageHolder->document()); |
| m_document->documentElement()->setInnerHTML("<body><canvas id='c'></canvas></body>", ASSERT_NO_EXCEPTION); |
| m_document->view()->updateAllLifecyclePhases(); |
| m_canvasElement = toHTMLCanvasElement(m_document->getElementById("c")); |
| |
| m_fullImageData = ImageData::create(IntSize(10, 10)); |
| m_partialImageData = ImageData::create(IntSize(2, 2)); |
| |
| NonThrowableExceptionState exceptionState; |
| CanvasGradient* opaqueGradient = CanvasGradient::create(FloatPoint(0, 0), FloatPoint(10, 0)); |
| opaqueGradient->addColorStop(0, String("green"), exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| opaqueGradient->addColorStop(1, String("blue"), exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| this->opaqueGradient().setCanvasGradient(opaqueGradient); |
| |
| CanvasGradient* alphaGradient = CanvasGradient::create(FloatPoint(0, 0), FloatPoint(10, 0)); |
| alphaGradient->addColorStop(0, String("green"), exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| alphaGradient->addColorStop(1, String("rgba(0, 0, 255, 0.5)"), exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| StringOrCanvasGradientOrCanvasPattern wrappedAlphaGradient; |
| this->alphaGradient().setCanvasGradient(alphaGradient); |
| |
| m_globalMemoryCache = replaceMemoryCacheForTesting(MemoryCache::create()); |
| } |
| |
| void CanvasRenderingContext2DTest::TearDown() |
| { |
| Heap::collectGarbage(BlinkGC::NoHeapPointersOnStack, BlinkGC::GCWithSweep, BlinkGC::ForcedGC); |
| replaceMemoryCacheForTesting(m_globalMemoryCache.release()); |
| } |
| |
| //============================================================================ |
| |
| class FakeAcceleratedImageBufferSurfaceForTesting : public UnacceleratedImageBufferSurface { |
| public: |
| FakeAcceleratedImageBufferSurfaceForTesting(const IntSize& size, OpacityMode mode) |
| : UnacceleratedImageBufferSurface(size, mode) |
| , m_isAccelerated(true) { } |
| ~FakeAcceleratedImageBufferSurfaceForTesting() override { } |
| bool isAccelerated() const override { return m_isAccelerated; } |
| void setIsAccelerated(bool isAccelerated) |
| { |
| if (isAccelerated != m_isAccelerated) |
| m_isAccelerated = isAccelerated; |
| } |
| |
| private: |
| bool m_isAccelerated; |
| }; |
| |
| //============================================================================ |
| |
| class MockImageBufferSurfaceForOverwriteTesting : public UnacceleratedImageBufferSurface { |
| public: |
| MockImageBufferSurfaceForOverwriteTesting(const IntSize& size, OpacityMode mode) : UnacceleratedImageBufferSurface(size, mode) { } |
| ~MockImageBufferSurfaceForOverwriteTesting() override { } |
| bool isRecording() const override { return true; } // otherwise overwrites are not tracked |
| |
| MOCK_METHOD0(willOverwriteCanvas, void()); |
| }; |
| |
| //============================================================================ |
| |
| #define TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| OwnPtr<MockImageBufferSurfaceForOverwriteTesting> mockSurface = adoptPtr(new MockImageBufferSurfaceForOverwriteTesting(IntSize(10, 10), NonOpaque)); \ |
| MockImageBufferSurfaceForOverwriteTesting* surfacePtr = mockSurface.get(); \ |
| canvasElement().createImageBufferUsingSurfaceForTesting(mockSurface.release()); \ |
| EXPECT_CALL(*surfacePtr, willOverwriteCanvas()).Times(EXPECTED_OVERDRAWS); \ |
| context2d()->save(); |
| |
| #define TEST_OVERDRAW_FINALIZE \ |
| context2d()->restore(); \ |
| Mock::VerifyAndClearExpectations(surfacePtr); |
| |
| #define TEST_OVERDRAW_1(EXPECTED_OVERDRAWS, CALL1) \ |
| do { \ |
| TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| context2d()->CALL1; \ |
| TEST_OVERDRAW_FINALIZE \ |
| } while (0) |
| |
| #define TEST_OVERDRAW_2(EXPECTED_OVERDRAWS, CALL1, CALL2) \ |
| do { \ |
| TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| context2d()->CALL1; \ |
| context2d()->CALL2; \ |
| TEST_OVERDRAW_FINALIZE \ |
| } while (0) |
| |
| #define TEST_OVERDRAW_3(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3) \ |
| do { \ |
| TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| context2d()->CALL1; \ |
| context2d()->CALL2; \ |
| context2d()->CALL3; \ |
| TEST_OVERDRAW_FINALIZE \ |
| } while (0) |
| |
| #define TEST_OVERDRAW_4(EXPECTED_OVERDRAWS, CALL1, CALL2, CALL3, CALL4) \ |
| do { \ |
| TEST_OVERDRAW_SETUP(EXPECTED_OVERDRAWS) \ |
| context2d()->CALL1; \ |
| context2d()->CALL2; \ |
| context2d()->CALL3; \ |
| context2d()->CALL4; \ |
| TEST_OVERDRAW_FINALIZE \ |
| } while (0) |
| |
| //============================================================================ |
| |
| class MockSurfaceFactory : public RecordingImageBufferFallbackSurfaceFactory { |
| public: |
| enum FallbackExpectation { |
| ExpectFallback, |
| ExpectNoFallback |
| }; |
| static PassOwnPtr<MockSurfaceFactory> create(FallbackExpectation expectation) { return adoptPtr(new MockSurfaceFactory(expectation)); } |
| |
| PassOwnPtr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode mode) override |
| { |
| EXPECT_EQ(ExpectFallback, m_expectation); |
| m_didFallback = true; |
| return adoptPtr(new UnacceleratedImageBufferSurface(size, mode)); |
| } |
| |
| ~MockSurfaceFactory() override |
| { |
| if (m_expectation == ExpectFallback) { |
| EXPECT_TRUE(m_didFallback); |
| } |
| } |
| |
| private: |
| MockSurfaceFactory(FallbackExpectation expectation) |
| : m_expectation(expectation) |
| , m_didFallback(false) { } |
| |
| FallbackExpectation m_expectation; |
| bool m_didFallback; |
| }; |
| |
| //============================================================================ |
| |
| TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithFillRect) |
| { |
| createContext(NonOpaque); |
| |
| TEST_OVERDRAW_1(1, fillRect(-1, -1, 12, 12)); |
| TEST_OVERDRAW_1(1, fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_1(0, strokeRect(0, 0, 10, 10)); // stroking instead of filling does not overwrite |
| TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_1(0, fillRect(0, 0, 9, 9)); |
| TEST_OVERDRAW_2(0, translate(1, 1), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, translate(1, 1), fillRect(-1, -1, 10, 10)); |
| TEST_OVERDRAW_2(1, setFillStyle(opaqueGradient()), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(0, setFillStyle(alphaGradient()), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_3(0, setGlobalAlpha(0.5), setFillStyle(opaqueGradient()), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 9, 9)); |
| TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_4(0, rect(0, 0, 5, 5), clip(), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithClearRect) |
| { |
| createContext(NonOpaque); |
| |
| TEST_OVERDRAW_1(1, clearRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_1(0, clearRect(0, 0, 9, 9)); |
| TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), clearRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, setFillStyle(alphaGradient()), clearRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(0, translate(1, 1), clearRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, translate(1, 1), clearRect(-1, -1, 10, 10)); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("destination-in")), clearRect(0, 0, 10, 10)); // composite op ignored |
| TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), clearRect(0, 0, 10, 10)); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithDrawImage) |
| { |
| createContext(NonOpaque); |
| NonThrowableExceptionState exceptionState; |
| |
| TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 9, 9, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(1, drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 11, 11, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, translate(-1, 0), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setFillStyle(opaqueGradient()), drawImage(&m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, setFillStyle(alphaGradient()), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); // fillStyle ignored by drawImage |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_3(0, rect(0, 0, 5, 5), clip(), drawImage(&m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithPutImageData) |
| { |
| createContext(NonOpaque); |
| NonThrowableExceptionState exceptionState; |
| |
| // Test putImageData |
| TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(1, putImageData(m_fullImageData.get(), 0, 0, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 0, 0, 1, 1, 8, 8, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, setGlobalAlpha(0.5f), putImageData(m_fullImageData.get(), 0, 0, exceptionState)); // alpha has no effect |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, putImageData(m_partialImageData.get(), 0, 0, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, translate(1, 1), putImageData(m_fullImageData.get(), 0, 0, exceptionState)); // ignores tranforms |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, putImageData(m_fullImageData.get(), 1, 0, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_3(1, rect(0, 0, 5, 5), clip(), putImageData(m_fullImageData.get(), 0, 0, exceptionState)); // ignores clip |
| EXPECT_FALSE(exceptionState.hadException()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, detectOverdrawWithCompositeOperations) |
| { |
| createContext(NonOpaque); |
| |
| // Test composite operators with an opaque rect that covers the entire canvas |
| // Note: all the untested composite operations take the same code path as source-in, |
| // which assumes that the destination may not be overwritten |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 10, 10)); |
| // Test composite operators with a transparent rect that covers the entire canvas |
| TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_3(1, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 10, 10)); |
| TEST_OVERDRAW_3(0, setGlobalAlpha(0.5f), setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 10, 10)); |
| // Test composite operators with an opaque rect that does not cover the entire canvas |
| TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("clear")), fillRect(0, 0, 5, 5)); |
| TEST_OVERDRAW_2(1, setGlobalCompositeOperation(String("copy")), fillRect(0, 0, 5, 5)); |
| TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-over")), fillRect(0, 0, 5, 5)); |
| TEST_OVERDRAW_2(0, setGlobalCompositeOperation(String("source-in")), fillRect(0, 0, 5, 5)); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionByDefault) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderOverdrawLimit) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->setGlobalAlpha(0.5f); // To prevent overdraw optimization |
| for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveOverdrawThreshold - 1; i++) { |
| context2d()->fillRect(0, 0, 10, 10); |
| } |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverOverdrawLimit) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->setGlobalAlpha(0.5f); // To prevent overdraw optimization |
| for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveOverdrawThreshold; i++) { |
| context2d()->fillRect(0, 0, 10, 10); |
| } |
| |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderImageSizeRatioLimit) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| NonThrowableExceptionState exceptionState; |
| RefPtrWillBeRawPtr<Element> sourceCanvasElement = document().createElement("canvas", exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement.get()); |
| IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio); |
| OwnPtr<UnacceleratedImageBufferSurface> sourceSurface = adoptPtr(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque)); |
| sourceCanvas->createImageBufferUsingSurfaceForTesting(sourceSurface.release()); |
| |
| const ImageBitmapOptions defaultOptions; |
| // Go through an ImageBitmap to avoid triggering a display list fallback |
| RefPtrWillBeRawPtr<ImageBitmap> sourceImageBitmap = ImageBitmap::create(sourceCanvas, IntRect(IntPoint(0, 0), sourceSize), defaultOptions); |
| |
| context2d()->drawImage(sourceImageBitmap.get(), 0, 0, 1, 1, 0, 0, 1, 1, exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverImageSizeRatioLimit) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| NonThrowableExceptionState exceptionState; |
| RefPtrWillBeRawPtr<Element> sourceCanvasElement = document().createElement("canvas", exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement.get()); |
| IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio + 1); |
| OwnPtr<UnacceleratedImageBufferSurface> sourceSurface = adoptPtr(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque)); |
| sourceCanvas->createImageBufferUsingSurfaceForTesting(sourceSurface.release()); |
| |
| const ImageBitmapOptions defaultOptions; |
| // Go through an ImageBitmap to avoid triggering a display list fallback |
| RefPtrWillBeRawPtr<ImageBitmap> sourceImageBitmap = ImageBitmap::create(sourceCanvas, IntRect(IntPoint(0, 0), sourceSize), defaultOptions); |
| |
| context2d()->drawImage(sourceImageBitmap.get(), 0, 0, 1, 1, 0, 0, 1, 1, exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderExpensivePathPointCount) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->beginPath(); |
| context2d()->moveTo(7, 5); |
| for (int i = 1; i < ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount-1; i++) { |
| float angleRad = twoPiFloat * i / (ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount - 1); |
| context2d()->lineTo(5 + 2 * cos(angleRad), 5 + 2 * sin(angleRad)); |
| } |
| context2d()->fill(); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverExpensivePathPointCount) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->beginPath(); |
| context2d()->moveTo(7, 5); |
| for (int i = 1; i < ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount + 1; i++) { |
| float angleRad = twoPiFloat * i / (ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount + 1); |
| context2d()->lineTo(5 + 2 * cos(angleRad), 5 + 2 * sin(angleRad)); |
| } |
| context2d()->fill(); |
| |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionWhenPathIsConcave) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->beginPath(); |
| context2d()->moveTo(1, 1); |
| context2d()->lineTo(5, 5); |
| context2d()->lineTo(9, 1); |
| context2d()->lineTo(5, 9); |
| context2d()->fill(); |
| |
| if (ExpensiveCanvasHeuristicParameters::ConcavePathsAreExpensive) { |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } else { |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithRectangleClip) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->beginPath(); |
| context2d()->rect(1, 1, 2, 2); |
| context2d()->clip(); |
| context2d()->fillRect(0, 0, 4, 4); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithComplexClip) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->beginPath(); |
| context2d()->moveTo(1, 1); |
| context2d()->lineTo(5, 5); |
| context2d()->lineTo(9, 1); |
| context2d()->lineTo(5, 9); |
| context2d()->clip(); |
| context2d()->fillRect(0, 0, 4, 4); |
| |
| if (ExpensiveCanvasHeuristicParameters::ComplexClipsAreExpensive) { |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } else { |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionWithBlurredShadow) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->setShadowColor(String("red")); |
| context2d()->setShadowBlur(1.0f); |
| context2d()->fillRect(1, 1, 1, 1); |
| |
| if (ExpensiveCanvasHeuristicParameters::BlurredShadowsAreExpensive) { |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } else { |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionWithSharpShadow) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->setShadowColor(String("red")); |
| context2d()->setShadowOffsetX(1.0f); |
| context2d()->fillRect(1, 1, 1, 1); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoFallbackWithSmallState) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->fillRect(0, 0, 1, 1); // To have a non-empty dirty rect |
| for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveRecordingStackDepth - 1; ++i) { |
| context2d()->save(); |
| context2d()->translate(1.0f, 0.0f); |
| } |
| canvasElement().doDeferredPaintInvalidation(); // To close the current frame |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, FallbackWithLargeState) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->fillRect(0, 0, 1, 1); // To have a non-empty dirty rect |
| for (int i = 0; i < ExpensiveCanvasHeuristicParameters::ExpensiveRecordingStackDepth; ++i) { |
| context2d()->save(); |
| context2d()->translate(1.0f, 0.0f); |
| } |
| canvasElement().doDeferredPaintInvalidation(); // To close the current frame |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, OpaqueDisplayListFallsBackForText) |
| { |
| // Verify that drawing text to an opaque canvas, which is expected to |
| // render with subpixel text anti-aliasing, results in falling out |
| // of display list mode because the current diplay list implementation |
| // does not support pixel geometry settings. |
| // See: crbug.com/583809 |
| createContext(Opaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectFallback), Opaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->fillText("Text", 0, 5); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NonOpaqueDisplayListDoesNotFallBackForText) |
| { |
| createContext(NonOpaque); |
| OwnPtr<RecordingImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(surface.release()); |
| |
| context2d()->fillText("Text", 0, 5); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, ImageResourceLifetime) |
| { |
| NonThrowableExceptionState nonThrowableExceptionState; |
| RefPtrWillBeRawPtr<Element> canvasElement = document().createElement("canvas", nonThrowableExceptionState); |
| EXPECT_FALSE(nonThrowableExceptionState.hadException()); |
| HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(canvasElement.get()); |
| canvas->setHeight(40); |
| canvas->setWidth(40); |
| RefPtrWillBeRawPtr<ImageBitmap> imageBitmapDerived = nullptr; |
| { |
| const ImageBitmapOptions defaultOptions; |
| RefPtrWillBeRawPtr<ImageBitmap> imageBitmapFromCanvas = ImageBitmap::create(canvas, IntRect(0, 0, canvas->width(), canvas->height()), defaultOptions); |
| imageBitmapDerived = ImageBitmap::create(imageBitmapFromCanvas.get(), IntRect(0, 0, 20, 20), defaultOptions); |
| } |
| CanvasContextCreationAttributes attributes; |
| CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(canvas->getCanvasRenderingContext("2d", attributes)); |
| TrackExceptionState exceptionState; |
| CanvasImageSourceUnion imageSource; |
| imageSource.setImageBitmap(imageBitmapDerived); |
| context->drawImage(imageSource, 0, 0, exceptionState); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, GPUMemoryUpdateForAcceleratedCanvas) |
| { |
| createContext(NonOpaque); |
| |
| OwnPtr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = adoptPtr(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque)); |
| FakeAcceleratedImageBufferSurfaceForTesting* fakeAccelerateSurfacePtr = fakeAccelerateSurface.get(); |
| canvasElement().createImageBufferUsingSurfaceForTesting(fakeAccelerateSurface.release()); |
| // 800 = 10 * 10 * 4 * 2 where 10*10 is canvas size, 4 is num of bytes per pixel per buffer, |
| // and 2 is an estimate of num of gpu buffers required |
| EXPECT_EQ(800, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(800, getGlobalGPUMemoryUsage()); |
| |
| // Switching accelerated mode to non-accelerated mode |
| fakeAccelerateSurfacePtr->setIsAccelerated(false); |
| canvasElement().buffer()->updateGPUMemoryUsage(); |
| EXPECT_EQ(0, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| |
| // Switching non-accelerated mode to accelerated mode |
| fakeAccelerateSurfacePtr->setIsAccelerated(true); |
| canvasElement().buffer()->updateGPUMemoryUsage(); |
| EXPECT_EQ(800, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(800, getGlobalGPUMemoryUsage()); |
| |
| // Creating a different accelerated image buffer |
| OwnPtr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface2 = adoptPtr(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 5), NonOpaque)); |
| OwnPtr<ImageBuffer> imageBuffer2 = ImageBuffer::create(fakeAccelerateSurface2.release()); |
| EXPECT_EQ(800, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(1200, getGlobalGPUMemoryUsage()); |
| |
| // Tear down the first image buffer that resides in current canvas element |
| canvasElement().setSize(IntSize(20, 20)); |
| Mock::VerifyAndClearExpectations(fakeAccelerateSurfacePtr); |
| EXPECT_EQ(400, getGlobalGPUMemoryUsage()); |
| |
| // Tear down the second image buffer |
| imageBuffer2.clear(); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| } |
| |
| } // namespace blink |