| // 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/dom/Document.h" |
| #include "core/fetch/MemoryCache.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/ImageBitmap.h" |
| #include "core/html/HTMLCanvasElement.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/Canvas2DImageBufferSurface.h" |
| #include "platform/graphics/ExpensiveCanvasHeuristicParameters.h" |
| #include "platform/graphics/RecordingImageBufferSurface.h" |
| #include "platform/graphics/StaticBitmapImage.h" |
| #include "platform/graphics/UnacceleratedImageBufferSurface.h" |
| #include "platform/graphics/test/FakeGLES2Interface.h" |
| #include "platform/graphics/test/FakeWebGraphicsContext3DProvider.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "wtf/PtrUtil.h" |
| #include <memory> |
| |
| using ::testing::Mock; |
| |
| namespace blink { |
| |
| enum BitmapOpacity { |
| OpaqueBitmap, |
| TransparentBitmap |
| }; |
| |
| class FakeImageSource : public CanvasImageSource { |
| public: |
| FakeImageSource(IntSize, BitmapOpacity); |
| |
| PassRefPtr<Image> getSourceImageForCanvas(SourceImageStatus*, AccelerationHint, SnapshotReason, const FloatSize&) const override; |
| |
| bool wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const override { return false; } |
| FloatSize elementSize(const FloatSize&) const override { return FloatSize(m_size); } |
| bool isOpaque() const override { return m_isOpaque; } |
| int sourceWidth() override { return m_size.width(); } |
| int sourceHeight() override { return m_size.height(); } |
| |
| ~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) |
| { |
| sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(m_size.width(), m_size.height())); |
| surface->getCanvas()->clear(opacity == OpaqueBitmap ? SK_ColorWHITE : SK_ColorTRANSPARENT); |
| m_image = StaticBitmapImage::create(surface->makeImageSnapshot()); |
| } |
| |
| PassRefPtr<Image> FakeImageSource::getSourceImageForCanvas(SourceImageStatus* status, AccelerationHint, SnapshotReason, const FloatSize&) const |
| { |
| if (status) |
| *status = NormalSourceImageStatus; |
| return m_image; |
| } |
| |
| //============================================================================ |
| |
| class CanvasRenderingContext2DTest : public ::testing::Test { |
| protected: |
| CanvasRenderingContext2DTest(); |
| void SetUp() override; |
| |
| DummyPageHolder& page() const { return *m_dummyPageHolder; } |
| Document& 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(); } |
| unsigned getGlobalAcceleratedImageBufferCount() const { return ImageBuffer::getGlobalAcceleratedImageBufferCount(); } |
| intptr_t getCurrentGPUMemoryUsage() const { return canvasElement().buffer()->getGPUMemoryUsage(); } |
| |
| void createContext(OpacityMode); |
| void TearDown(); |
| void unrefCanvas(); |
| PassRefPtr<Canvas2DLayerBridge> makeBridge(std::unique_ptr<FakeWebGraphicsContext3DProvider>, const IntSize&, Canvas2DLayerBridge::AccelerationMode); |
| |
| private: |
| std::unique_ptr<DummyPageHolder> m_dummyPageHolder; |
| Persistent<Document> m_document; |
| Persistent<HTMLCanvasElement> m_canvasElement; |
| Persistent<MemoryCache> m_globalMemoryCache; |
| |
| class WrapGradients final : public GarbageCollectedFinalized<WrapGradients> { |
| public: |
| static WrapGradients* create() |
| { |
| return 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. |
| Persistent<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 = &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() |
| { |
| ThreadState::current()->collectGarbage(BlinkGC::NoHeapPointersOnStack, BlinkGC::GCWithSweep, BlinkGC::ForcedGC); |
| replaceMemoryCacheForTesting(m_globalMemoryCache.release()); |
| } |
| |
| PassRefPtr<Canvas2DLayerBridge> CanvasRenderingContext2DTest::makeBridge(std::unique_ptr<FakeWebGraphicsContext3DProvider> provider, const IntSize& size, Canvas2DLayerBridge::AccelerationMode accelerationMode) |
| { |
| return adoptRef(new Canvas2DLayerBridge(std::move(provider), size, 0, NonOpaque, accelerationMode, nullptr)); |
| } |
| |
| //============================================================================ |
| |
| 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) \ |
| std::unique_ptr<MockImageBufferSurfaceForOverwriteTesting> mockSurface = wrapUnique(new MockImageBufferSurfaceForOverwriteTesting(IntSize(10, 10), NonOpaque)); \ |
| MockImageBufferSurfaceForOverwriteTesting* surfacePtr = mockSurface.get(); \ |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(mockSurface)); \ |
| 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 std::unique_ptr<MockSurfaceFactory> create(FallbackExpectation expectation) { return wrapUnique(new MockSurfaceFactory(expectation)); } |
| |
| std::unique_ptr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode mode, sk_sp<SkColorSpace> colorSpace) override |
| { |
| EXPECT_EQ(ExpectFallback, m_expectation); |
| m_didFallback = true; |
| return wrapUnique(new UnacceleratedImageBufferSurface(size, mode, InitializeImagePixels, colorSpace)); |
| } |
| |
| ~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(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(1, drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 1, 1, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(canvasElement().getExecutionContext(), &m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setGlobalAlpha(0.5f), drawImage(canvasElement().getExecutionContext(), &m_alphaBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(0, drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 9, 9, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_1(1, drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 11, 11, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(1, translate(-1, 0), drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 1, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, translate(-1, 0), drawImage(canvasElement().getExecutionContext(), &m_opaqueBitmap, 0, 0, 10, 10, 0, 0, 10, 10, exceptionState)); |
| EXPECT_FALSE(exceptionState.hadException()); |
| TEST_OVERDRAW_2(0, setFillStyle(opaqueGradient()), drawImage(canvasElement().getExecutionContext(), &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(canvasElement().getExecutionContext(), &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(canvasElement().getExecutionContext(), &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(canvasElement().getExecutionContext(), &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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderOverdrawLimit) |
| { |
| createContext(NonOpaque); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| NonThrowableExceptionState exceptionState; |
| Element* sourceCanvasElement = document().createElement("canvas", exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement); |
| IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio); |
| std::unique_ptr<UnacceleratedImageBufferSurface> sourceSurface = wrapUnique(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque)); |
| sourceCanvas->createImageBufferUsingSurfaceForTesting(std::move(sourceSurface)); |
| |
| const ImageBitmapOptions defaultOptions; |
| Optional<IntRect> cropRect = IntRect(IntPoint(0, 0), sourceSize); |
| // Go through an ImageBitmap to avoid triggering a display list fallback |
| ImageBitmap* sourceImageBitmap = ImageBitmap::create(sourceCanvas, cropRect, defaultOptions); |
| |
| context2d()->drawImage(canvasElement().getExecutionContext(), sourceImageBitmap, 0, 0, 1, 1, 0, 0, 1, 1, exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, LayerPromotionOverImageSizeRatioLimit) |
| { |
| createContext(NonOpaque); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| NonThrowableExceptionState exceptionState; |
| Element* sourceCanvasElement = document().createElement("canvas", exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| HTMLCanvasElement* sourceCanvas = static_cast<HTMLCanvasElement*>(sourceCanvasElement); |
| IntSize sourceSize(10, 10 * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio + 1); |
| std::unique_ptr<UnacceleratedImageBufferSurface> sourceSurface = wrapUnique(new UnacceleratedImageBufferSurface(sourceSize, NonOpaque)); |
| sourceCanvas->createImageBufferUsingSurfaceForTesting(std::move(sourceSurface)); |
| |
| const ImageBitmapOptions defaultOptions; |
| Optional<IntRect> cropRect = IntRect(IntPoint(0, 0), sourceSize); |
| // Go through an ImageBitmap to avoid triggering a display list fallback |
| ImageBitmap* sourceImageBitmap = ImageBitmap::create(sourceCanvas, cropRect, defaultOptions); |
| |
| context2d()->drawImage(canvasElement().getExecutionContext(), sourceImageBitmap, 0, 0, 1, 1, 0, 0, 1, 1, exceptionState); |
| EXPECT_FALSE(exceptionState.hadException()); |
| |
| EXPECT_TRUE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoLayerPromotionUnderExpensivePathPointCount) |
| { |
| createContext(NonOpaque); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| context2d()->setShadowColor(String("red")); |
| context2d()->setShadowOffsetX(1.0f); |
| context2d()->fillRect(1, 1, 1, 1); |
| |
| EXPECT_FALSE(canvasElement().shouldBeDirectComposited()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NoFallbackWithSmallState) |
| { |
| createContext(NonOpaque); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| 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); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectFallback), Opaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| context2d()->fillText("Text", 0, 5); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, NonOpaqueDisplayListDoesNotFallBackForText) |
| { |
| createContext(NonOpaque); |
| std::unique_ptr<RecordingImageBufferSurface> surface = wrapUnique(new RecordingImageBufferSurface(IntSize(10, 10), MockSurfaceFactory::create(MockSurfaceFactory::ExpectNoFallback), NonOpaque, nullptr)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| context2d()->fillText("Text", 0, 5); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, ImageResourceLifetime) |
| { |
| NonThrowableExceptionState nonThrowableExceptionState; |
| Element* canvasElement = document().createElement("canvas", nonThrowableExceptionState); |
| EXPECT_FALSE(nonThrowableExceptionState.hadException()); |
| HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(canvasElement); |
| canvas->setSize(IntSize(40, 40)); |
| ImageBitmap* imageBitmapDerived = nullptr; |
| { |
| const ImageBitmapOptions defaultOptions; |
| Optional<IntRect> cropRect = IntRect(0, 0, canvas->width(), canvas->height()); |
| ImageBitmap* imageBitmapFromCanvas = ImageBitmap::create(canvas, cropRect, defaultOptions); |
| cropRect = IntRect(0, 0, 20, 20); |
| imageBitmapDerived = ImageBitmap::create(imageBitmapFromCanvas, cropRect, defaultOptions); |
| } |
| CanvasContextCreationAttributes attributes; |
| CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(canvas->getCanvasRenderingContext("2d", attributes)); |
| TrackExceptionState exceptionState; |
| CanvasImageSourceUnion imageSource; |
| imageSource.setImageBitmap(imageBitmapDerived); |
| context->drawImage(canvas->getExecutionContext(), imageSource, 0, 0, exceptionState); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, GPUMemoryUpdateForAcceleratedCanvas) |
| { |
| createContext(NonOpaque); |
| |
| std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque)); |
| FakeAcceleratedImageBufferSurfaceForTesting* fakeAccelerateSurfacePtr = fakeAccelerateSurface.get(); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(fakeAccelerateSurface)); |
| // 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()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| |
| // Switching accelerated mode to non-accelerated mode |
| fakeAccelerateSurfacePtr->setIsAccelerated(false); |
| canvasElement().buffer()->updateGPUMemoryUsage(); |
| EXPECT_EQ(0, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(0u, getGlobalAcceleratedImageBufferCount()); |
| |
| // Switching non-accelerated mode to accelerated mode |
| fakeAccelerateSurfacePtr->setIsAccelerated(true); |
| canvasElement().buffer()->updateGPUMemoryUsage(); |
| EXPECT_EQ(800, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(800, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| |
| // Creating a different accelerated image buffer |
| std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface2 = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 5), NonOpaque)); |
| std::unique_ptr<ImageBuffer> imageBuffer2 = ImageBuffer::create(std::move(fakeAccelerateSurface2)); |
| EXPECT_EQ(800, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(1200, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(2u, getGlobalAcceleratedImageBufferCount()); |
| |
| // Tear down the first image buffer that resides in current canvas element |
| canvasElement().setSize(IntSize(20, 20)); |
| Mock::VerifyAndClearExpectations(fakeAccelerateSurfacePtr); |
| EXPECT_EQ(400, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| |
| // Tear down the second image buffer |
| imageBuffer2.reset(); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(0u, getGlobalAcceleratedImageBufferCount()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, CanvasDisposedBeforeContext) |
| { |
| createContext(NonOpaque); |
| context2d()->fillRect(0, 0, 1, 1); // results in task observer registration |
| |
| context2d()->detachCanvas(); |
| |
| // This is the only method that is callable after detachCanvas |
| // Test passes by not crashing. |
| context2d()->didProcessTask(); |
| |
| // Test passes by not crashing during teardown |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, ContextDisposedBeforeCanvas) |
| { |
| createContext(NonOpaque); |
| |
| canvasElement().detachContext(); |
| // Passes by not crashing later during teardown |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, GetImageDataDisablesAcceleration) |
| { |
| bool savedFixedRenderingMode = RuntimeEnabledFeatures::canvas2dFixedRenderingModeEnabled(); |
| RuntimeEnabledFeatures::setCanvas2dFixedRenderingModeEnabled(false); |
| |
| createContext(NonOpaque); |
| FakeGLES2Interface gl; |
| std::unique_ptr<FakeWebGraphicsContext3DProvider> contextProvider(new FakeWebGraphicsContext3DProvider(&gl)); |
| IntSize size(300, 300); |
| RefPtr<Canvas2DLayerBridge> bridge = makeBridge(std::move(contextProvider), size, Canvas2DLayerBridge::EnableAcceleration); |
| std::unique_ptr<Canvas2DImageBufferSurface> surface(new Canvas2DImageBufferSurface(bridge, size)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(surface)); |
| |
| EXPECT_TRUE(canvasElement().buffer()->isAccelerated()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| EXPECT_EQ(720000, getGlobalGPUMemoryUsage()); |
| |
| TrackExceptionState exceptionState; |
| context2d()->getImageData(0, 0, 1, 1, exceptionState); |
| |
| EXPECT_FALSE(exceptionState.hadException()); |
| if (ExpensiveCanvasHeuristicParameters::GetImageDataForcesNoAcceleration) { |
| EXPECT_FALSE(canvasElement().buffer()->isAccelerated()); |
| EXPECT_EQ(0u, getGlobalAcceleratedImageBufferCount()); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| } else { |
| EXPECT_TRUE(canvasElement().buffer()->isAccelerated()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| EXPECT_EQ(720000, getGlobalGPUMemoryUsage()); |
| } |
| |
| // Restore global state to prevent side-effects on other tests |
| RuntimeEnabledFeatures::setCanvas2dFixedRenderingModeEnabled(savedFixedRenderingMode); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, IsAccelerationOptimalForCanvasContentHeuristic) |
| { |
| createContext(NonOpaque); |
| |
| std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(fakeAccelerateSurface)); |
| |
| NonThrowableExceptionState exceptionState; |
| |
| CanvasRenderingContext2D* context = context2d(); |
| EXPECT_TRUE(context->isAccelerationOptimalForCanvasContent()); |
| |
| context->fillRect(10, 10, 100, 100); |
| EXPECT_TRUE(context->isAccelerationOptimalForCanvasContent()); |
| |
| int numReps = 100; |
| for (int i = 0; i < numReps; i++) { |
| context->fillText("Text", 10, 10, 1); // faster with no acceleration |
| } |
| EXPECT_FALSE(context->isAccelerationOptimalForCanvasContent()); |
| |
| for (int i = 0; i < numReps; i++) { |
| context->fillRect(10, 10, 200, 200); // faster with acceleration |
| } |
| EXPECT_TRUE(context->isAccelerationOptimalForCanvasContent()); |
| |
| for (int i = 0; i < numReps * 100; i++) { |
| context->strokeText("Text", 10, 10, 1); // faster with no acceleration |
| } |
| EXPECT_FALSE(context->isAccelerationOptimalForCanvasContent()); |
| } |
| |
| TEST_F(CanvasRenderingContext2DTest, DisableAcceleration) |
| { |
| createContext(NonOpaque); |
| |
| std::unique_ptr<FakeAcceleratedImageBufferSurfaceForTesting> fakeAccelerateSurface = wrapUnique(new FakeAcceleratedImageBufferSurfaceForTesting(IntSize(10, 10), NonOpaque)); |
| canvasElement().createImageBufferUsingSurfaceForTesting(std::move(fakeAccelerateSurface)); |
| CanvasRenderingContext2D* context = context2d(); |
| |
| // 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()); |
| EXPECT_EQ(1u, getGlobalAcceleratedImageBufferCount()); |
| |
| context->fillRect(10, 10, 100, 100); |
| EXPECT_TRUE(canvasElement().buffer()->isAccelerated()); |
| |
| canvasElement().buffer()->disableAcceleration(); |
| EXPECT_FALSE(canvasElement().buffer()->isAccelerated()); |
| |
| context->fillRect(10, 10, 100, 100); |
| |
| EXPECT_EQ(0, getCurrentGPUMemoryUsage()); |
| EXPECT_EQ(0, getGlobalGPUMemoryUsage()); |
| EXPECT_EQ(0u, getGlobalAcceleratedImageBufferCount()); |
| } |
| |
| } // namespace blink |