diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc | 1407 |
1 files changed, 1407 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/chromium/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc new file mode 100644 index 00000000000..b19e9ae1528 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc @@ -0,0 +1,1407 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR 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. + */ + +#include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h" + +#include <utility> + +#include "base/location.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "build/build_config.h" +#include "cc/test/skia_common.h" +#include "cc/test/stub_decode_cache.h" +#include "components/viz/common/resources/single_release_callback.h" +#include "components/viz/common/resources/transferable_resource.h" +#include "components/viz/test/test_gpu_memory_buffer_manager.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/common/capabilities.h" +#include "skia/ext/texture_handle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_thread.h" +#include "third_party/blink/renderer/platform/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/graphics/canvas_resource_host.h" +#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h" +#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" +#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h" +#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h" +#include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" +#include "third_party/blink/renderer/platform/scheduler/child/web_scheduler.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support.h" +#include "third_party/blink/renderer/platform/waitable_event.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/gl/GrGLTypes.h" + +#include <memory> + +using testing::AnyNumber; +using testing::AtLeast; +using testing::InSequence; +using testing::Pointee; +using testing::Return; +using testing::SetArgPointee; +using testing::Test; +using testing::_; + +namespace blink { + +namespace { + +class Canvas2DLayerBridgePtr { + public: + Canvas2DLayerBridgePtr() = default; + Canvas2DLayerBridgePtr(std::unique_ptr<Canvas2DLayerBridge> layer_bridge) + : layer_bridge_(std::move(layer_bridge)) {} + + ~Canvas2DLayerBridgePtr() { Clear(); } + + void Clear() { + if (layer_bridge_) { + layer_bridge_->BeginDestruction(); + layer_bridge_.reset(); + } + } + + void operator=(std::unique_ptr<Canvas2DLayerBridge> layer_bridge) { + DCHECK(!layer_bridge_); // Existing ref must be removed with Clear() + layer_bridge_ = std::move(layer_bridge); + } + + Canvas2DLayerBridge* operator->() { return layer_bridge_.get(); } + Canvas2DLayerBridge* Get() { return layer_bridge_.get(); } + + private: + std::unique_ptr<Canvas2DLayerBridge> layer_bridge_; +}; + +class MockGLES2InterfaceWithImageSupport : public FakeGLES2Interface { + public: + MOCK_METHOD0(Flush, void()); + MOCK_METHOD4(CreateImageCHROMIUM, + GLuint(ClientBuffer, GLsizei, GLsizei, GLenum)); + MOCK_METHOD1(DestroyImageCHROMIUM, void(GLuint)); + MOCK_METHOD2(GenTextures, void(GLsizei, GLuint*)); + MOCK_METHOD2(DeleteTextures, void(GLsizei, const GLuint*)); + // Fake + void GenMailboxCHROMIUM(GLbyte* name) { + name[0] = 1; // Make non-zero mailbox names + } +}; + +class FakePlatformSupport : public TestingPlatformSupport { + public: + void RunUntilStop() const { base::RunLoop().Run(); } + + void StopRunning() const { base::RunLoop().Quit(); } + + private: + gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager() override { + return &test_gpu_memory_buffer_manager_; + } + + viz::TestGpuMemoryBufferManager test_gpu_memory_buffer_manager_; +}; + +class ImageTrackingDecodeCache : public cc::StubDecodeCache { + public: + ImageTrackingDecodeCache() = default; + ~ImageTrackingDecodeCache() override { EXPECT_EQ(num_locked_images_, 0); } + + cc::DecodedDrawImage GetDecodedImageForDraw( + const cc::DrawImage& image) override { + EXPECT_FALSE(disallow_cache_use_); + + num_locked_images_++; + decoded_images_.push_back(image); + SkBitmap bitmap; + bitmap.allocPixelsFlags(SkImageInfo::MakeN32Premul(10, 10), + SkBitmap::kZeroPixels_AllocFlag); + sk_sp<SkImage> sk_image = SkImage::MakeFromBitmap(bitmap); + return cc::DecodedDrawImage(sk_image, SkSize::Make(0, 0), + SkSize::Make(1, 1), kLow_SkFilterQuality, + !budget_exceeded_); + } + + void set_budget_exceeded(bool exceeded) { budget_exceeded_ = exceeded; } + void set_disallow_cache_use(bool disallow) { disallow_cache_use_ = disallow; } + + void DrawWithImageFinished( + const cc::DrawImage& image, + const cc::DecodedDrawImage& decoded_image) override { + EXPECT_FALSE(disallow_cache_use_); + num_locked_images_--; + } + + const std::vector<cc::DrawImage>& decoded_images() const { + return decoded_images_; + } + int num_locked_images() const { return num_locked_images_; } + + private: + std::vector<cc::DrawImage> decoded_images_; + int num_locked_images_ = 0; + bool budget_exceeded_ = false; + bool disallow_cache_use_ = false; +}; + +} // anonymous namespace + +class Canvas2DLayerBridgeTest : public Test { + public: + std::unique_ptr<Canvas2DLayerBridge> MakeBridge( + const IntSize& size, + Canvas2DLayerBridge::AccelerationMode acceleration_mode) { + std::unique_ptr<Canvas2DLayerBridge> bridge = + std::make_unique<Canvas2DLayerBridge>(size, 0, acceleration_mode, + CanvasColorParams()); + bridge->DontUseIdleSchedulingForTesting(); + return bridge; + } + void SetUp() override { + auto factory = [](FakeGLES2Interface* gl, ImageTrackingDecodeCache* cache, + bool* gpu_compositing_disabled) + -> std::unique_ptr<WebGraphicsContext3DProvider> { + *gpu_compositing_disabled = false; + return std::make_unique<FakeWebGraphicsContext3DProvider>(gl, cache); + }; + SharedGpuContext::SetContextProviderFactoryForTesting(WTF::BindRepeating( + factory, WTF::Unretained(&gl_), WTF::Unretained(&image_decode_cache_))); + } + void TearDown() override { SharedGpuContext::ResetForTesting(); } + + protected: + MockGLES2InterfaceWithImageSupport gl_; + ImageTrackingDecodeCache image_decode_cache_; + + void FullLifecycleTest() { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kDisableAcceleration, + CanvasColorParams())); + + const GrGLTextureInfo* texture_info = + skia::GrBackendObjectToGrGLTextureInfo( + bridge->NewImageSnapshot(kPreferAcceleration) + ->PaintImageForCurrentFrame() + .GetSkImage() + ->getTextureHandle(true)); + EXPECT_EQ(texture_info, nullptr); + bridge.Clear(); + } + + void FallbackToSoftwareIfContextLost() { + gl_.SetIsContextLost(true); + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + EXPECT_TRUE(bridge->IsValid()); + EXPECT_FALSE(bridge->IsAccelerated()); + } + + void FallbackToSoftwareOnFailedTextureAlloc() { + { + // No fallback case. + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + EXPECT_TRUE(bridge->IsValid()); + EXPECT_TRUE(bridge->IsAccelerated()); + scoped_refptr<StaticBitmapImage> snapshot = + bridge->NewImageSnapshot(kPreferAcceleration); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_TRUE(snapshot->IsTextureBacked()); + } + + { + // Fallback case. + GrContext* gr = SharedGpuContext::ContextProviderWrapper() + ->ContextProvider() + ->GetGrContext(); + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + EXPECT_TRUE(bridge->IsValid()); + EXPECT_TRUE(bridge->IsAccelerated()); // We don't yet know that + // allocation will fail. + // This will cause SkSurface_Gpu creation to fail without + // Canvas2DLayerBridge otherwise detecting that anything was disabled. + gr->abandonContext(); + scoped_refptr<StaticBitmapImage> snapshot = + bridge->NewImageSnapshot(kPreferAcceleration); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_FALSE(snapshot->IsTextureBacked()); + } + } + + void NoDrawOnContextLostTest() { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + EXPECT_TRUE(bridge->IsValid()); + PaintFlags flags; + uint32_t gen_id = bridge->GetOrCreateResourceProvider()->ContentUniqueID(); + bridge->Canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), flags); + EXPECT_EQ(gen_id, bridge->GetOrCreateResourceProvider()->ContentUniqueID()); + gl_.SetIsContextLost(true); + EXPECT_EQ(gen_id, bridge->GetOrCreateResourceProvider()->ContentUniqueID()); + bridge->Canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), flags); + EXPECT_EQ(gen_id, bridge->GetOrCreateResourceProvider()->ContentUniqueID()); + // This results in the internal surface being torn down in response to the + // context loss. + EXPECT_FALSE(bridge->IsValid()); + EXPECT_EQ(nullptr, bridge->GetOrCreateResourceProvider()); + // The following passes by not crashing + bridge->Canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), flags); + bridge->NewImageSnapshot(kPreferAcceleration); + } + + void PrepareMailboxWhenContextIsLost() { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + + EXPECT_TRUE(bridge->IsAccelerated()); + bridge->FinalizeFrame(); // Trigger the creation of a backing store + // When the context is lost we are not sure if we should still be producing + // GL frames for the compositor or not, so fail to generate frames. + gl_.SetIsContextLost(true); + + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + } + + void PrepareMailboxWhenContextIsLostWithFailedRestore() { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + + bridge->GetOrCreateResourceProvider(); + EXPECT_TRUE(bridge->IsValid()); + // When the context is lost we are not sure if we should still be producing + // GL frames for the compositor or not, so fail to generate frames. + gl_.SetIsContextLost(true); + EXPECT_FALSE(bridge->IsValid()); + + // Restoration will fail because + // Platform::createSharedOffscreenGraphicsContext3DProvider() is stubbed + // in unit tests. This simulates what would happen when attempting to + // restore while the GPU process is down. + bridge->Restore(); + + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + } + + void ReleaseCallbackWithNullContextProviderWrapperTest() { + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, + Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + bridge->FinalizeFrame(); + EXPECT_TRUE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + } + + bool lost_resource = true; + gl_.SetIsContextLost(true); + // Get a new context provider so that the WeakPtr to the old one is null. + // This is the test to make sure that ReleaseMailboxImageResource() handles + // null context_provider_wrapper properly. + SharedGpuContext::ContextProviderWrapper(); + release_callback->Run(gpu::SyncToken(), lost_resource); + } + + void PrepareMailboxAndLoseResourceTest() { + // Prepare a mailbox, then report the resource as lost. + // This test passes by not crashing and not triggering assertions. + { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, + Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + bridge->FinalizeFrame(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + EXPECT_TRUE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + + bool lost_resource = true; + release_callback->Run(gpu::SyncToken(), lost_resource); + } + + // Retry with mailbox released while bridge destruction is in progress. + { + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, + Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + bridge->FinalizeFrame(); + bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback); + // |bridge| goes out of scope and would normally be destroyed, but + // object is kept alive by self references. + } + + // This should cause the bridge to be destroyed. + bool lost_resource = true; + // Before fixing crbug.com/411864, the following line would cause a memory + // use after free that sometimes caused a crash in normal builds and + // crashed consistently with ASAN. + release_callback->Run(gpu::SyncToken(), lost_resource); + } + } + + void AccelerationHintTest() { + { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + PaintFlags flags; + bridge->Canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), flags); + scoped_refptr<StaticBitmapImage> image = + bridge->NewImageSnapshot(kPreferAcceleration); + EXPECT_TRUE(bridge->IsValid()); + EXPECT_TRUE(bridge->IsAccelerated()); + } + + { + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + PaintFlags flags; + bridge->Canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), flags); + scoped_refptr<StaticBitmapImage> image = + bridge->NewImageSnapshot(kPreferNoAcceleration); + EXPECT_TRUE(bridge->IsValid()); + EXPECT_FALSE(bridge->IsAccelerated()); + } + } +}; + +TEST_F(Canvas2DLayerBridgeTest, FullLifecycleSingleThreaded) { + FullLifecycleTest(); +} + +TEST_F(Canvas2DLayerBridgeTest, NoDrawOnContextLost) { + NoDrawOnContextLostTest(); +} + +TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxWhenContextIsLost) { + PrepareMailboxWhenContextIsLost(); +} + +TEST_F(Canvas2DLayerBridgeTest, + PrepareMailboxWhenContextIsLostWithFailedRestore) { + PrepareMailboxWhenContextIsLostWithFailedRestore(); +} + +TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxAndLoseResource) { + PrepareMailboxAndLoseResourceTest(); +} + +TEST_F(Canvas2DLayerBridgeTest, ReleaseCallbackWithNullContextProviderWrapper) { + ReleaseCallbackWithNullContextProviderWrapperTest(); +} + +TEST_F(Canvas2DLayerBridgeTest, AccelerationHint) { + AccelerationHintTest(); +} + +TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareIfContextLost) { + FallbackToSoftwareIfContextLost(); +} + +TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareOnFailedTextureAlloc) { + FallbackToSoftwareOnFailedTextureAlloc(); +} + +class MockLogger : public Canvas2DLayerBridge::Logger { + public: + MOCK_METHOD1(ReportHibernationEvent, + void(Canvas2DLayerBridge::HibernationEvent)); + MOCK_METHOD0(DidStartHibernating, void()); + virtual ~MockLogger() = default; +}; + +class MockCanvasResourceHost : public CanvasResourceHost { + public: + void NotifySurfaceInvalid() {} + void SetNeedsCompositingUpdate() {} + void UpdateMemoryUsage() {} + MOCK_CONST_METHOD1(RestoreCanvasMatrixClipStack, void(PaintCanvas*)); +}; + +void DrawSomething(Canvas2DLayerBridgePtr& bridge) { + bridge->DidDraw(FloatRect(0, 0, 1, 1)); + bridge->FinalizeFrame(); + // Grabbing an image forces a flush + bridge->NewImageSnapshot(kPreferAcceleration); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, HibernationLifeCycle) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_HibernationLifeCycle) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Test exiting hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationEndedNormally)); + + bridge->SetIsHidden(false); + + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, HibernationReEntry) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_HibernationReEntry) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + // Toggle visibility before the task that enters hibernation gets a + // chance to run. + bridge->SetIsHidden(false); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Test exiting hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationEndedNormally)); + + bridge->SetIsHidden(false); + + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, + HibernationLifeCycleWithDeferredRenderingDisabled) +#else +TEST_F(Canvas2DLayerBridgeTest, + DISABLED_HibernationLifeCycleWithDeferredRenderingDisabled) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + MockCanvasResourceHost mock_host; + EXPECT_CALL(mock_host, RestoreCanvasMatrixClipStack(_)).Times(AnyNumber()); + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + + bridge->SetCanvasResourceHost(&mock_host); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + bridge->DisableDeferral(kDisableDeferralReasonUnknown); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_host); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Test exiting hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationEndedNormally)); + EXPECT_CALL(mock_host, RestoreCanvasMatrixClipStack(_)) + .Times(AtLeast(1)); // Because deferred rendering is disabled + bridge->SetIsHidden(false); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_host); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED && CANVAS2D_BACKGROUND_RENDER_SWITCH_TO_CPU +TEST_F(Canvas2DLayerBridgeTest, BackgroundRenderingWhileHibernating) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_BackgroundRenderingWhileHibernating) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Rendering in the background -> temp switch to SW + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge:: + kHibernationEndedWithSwitchToBackgroundRendering)); + DrawSomething(bridge); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Unhide + bridge->SetIsHidden(false); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_TRUE( + bridge->IsAccelerated()); // Becoming visible causes switch back to GPU + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED && CANVAS2D_BACKGROUND_RENDER_SWITCH_TO_CPU +TEST_F(Canvas2DLayerBridgeTest, + BackgroundRenderingWhileHibernatingWithDeferredRenderingDisabled) +#else +TEST_F( + Canvas2DLayerBridgeTest, + DISABLED_BackgroundRenderingWhileHibernatingWithDeferredRenderingDisabled) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + MockCanvasResourceHost mock_canvas_resource_host; + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AnyNumber()); + bridge->SetCanvasResourceHost(&mock_canvas_resource_host); + bridge->DisableDeferral(kDisableDeferralReasonUnknown); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Rendering in the background -> temp switch to SW + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge:: + kHibernationEndedWithSwitchToBackgroundRendering)); + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AtLeast(1)); + DrawSomething(bridge); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Unhide + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AtLeast(1)); + bridge->SetIsHidden(false); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_TRUE( + bridge->IsAccelerated()); // Becoming visible causes switch back to GPU + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED && CANVAS2D_BACKGROUND_RENDER_SWITCH_TO_CPU +TEST_F(Canvas2DLayerBridgeTest, DisableDeferredRenderingWhileHibernating) +#else +TEST_F(Canvas2DLayerBridgeTest, + DISABLED_DisableDeferredRenderingWhileHibernating) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + MockCanvasResourceHost mock_canvas_resource_host; + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AnyNumber()); + bridge->SetCanvasResourceHost(&mock_canvas_resource_host); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Disable deferral while background rendering + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge:: + kHibernationEndedWithSwitchToBackgroundRendering)); + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AtLeast(1)); + bridge->DisableDeferral(kDisableDeferralReasonUnknown); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Unhide + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AtLeast(1)); + bridge->SetIsHidden(false); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); + EXPECT_TRUE( + bridge->IsAccelerated()); // Becoming visible causes switch back to GPU + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + EXPECT_CALL(mock_canvas_resource_host, RestoreCanvasMatrixClipStack(_)) + .Times(AnyNumber()); + bridge.Clear(); + testing::Mock::VerifyAndClearExpectations(&mock_canvas_resource_host); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, TeardownWhileHibernating) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_TeardownWhileHibernating) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Tear down the bridge while hibernating + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge::kHibernationEndedWithTeardown)); + bridge.Clear(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, SnapshotWhileHibernating) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_SnapshotWhileHibernating) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Take a snapshot and verify that it is not accelerated due to hibernation + scoped_refptr<StaticBitmapImage> image = + bridge->NewImageSnapshot(kPreferAcceleration); + EXPECT_FALSE(image->IsTextureBacked()); + image = nullptr; + + // Verify that taking a snapshot did not affect the state of bridge + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_TRUE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // End hibernation normally + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationEndedNormally)) + .Times(1); + bridge->SetIsHidden(false); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, TeardownWhileHibernationIsPending) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_TeardownWhileHibernationIsPending) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + bridge->SetIsHidden(true); + bridge.Clear(); + // In production, we would expect a + // HibernationAbortedDueToDestructionWhileHibernatePending event to be + // fired, but that signal is lost in the unit test due to no longer having + // a bridge to hold the mockLogger. + platform->RunUntilIdle(); + // This test passes by not crashing, which proves that the WeakPtr logic + // is sound. +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, HibernationAbortedDueToPendingTeardown) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_HibernationAbortedDueToPendingTeardown) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge::kHibernationAbortedDueToPendingDestruction)) + .Times(1); + bridge->SetIsHidden(true); + bridge->BeginDestruction(); + platform->RunUntilIdle(); + + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, HibernationAbortedDueToVisibilityChange) +#else +TEST_F(Canvas2DLayerBridgeTest, + DISABLED_HibernationAbortedDueToVisibilityChange) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge::kHibernationAbortedDueToVisibilityChange)) + .Times(1); + bridge->SetIsHidden(true); + bridge->SetIsHidden(false); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, HibernationAbortedDueToLostContext) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_HibernationAbortedDueToLostContext) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + gl_.SetIsContextLost(true); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge::kHibernationAbortedDueGpuContextLoss)) + .Times(1); + + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsHibernating()); +} + +#if CANVAS2D_HIBERNATION_ENABLED +TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxWhileHibernating) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_PrepareMailboxWhileHibernating) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + bridge->DontUseIdleSchedulingForTesting(); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + + // Test PrepareTransferableResource() while hibernating + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_TRUE(bridge->IsValid()); + + // Tear down the bridge on the thread so that 'bridge' can go out of scope + // without crashing due to thread checks + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge::kHibernationEndedWithTeardown)); + bridge.Clear(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); +} + +#if CANVAS2D_HIBERNATION_ENABLED && CANVAS2D_BACKGROUND_RENDER_SWITCH_TO_CPU +TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxWhileBackgroundRendering) +#else +TEST_F(Canvas2DLayerBridgeTest, DISABLED_PrepareMailboxWhileBackgroundRendering) +#endif +{ + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + CanvasColorParams())); + DrawSomething(bridge); + + // Register an alternate Logger for tracking hibernation events + std::unique_ptr<MockLogger> mock_logger = std::make_unique<MockLogger>(); + MockLogger* mock_logger_ptr = mock_logger.get(); + bridge->SetLoggerForTesting(std::move(mock_logger)); + + // Test entering hibernation + std::unique_ptr<WaitableEvent> hibernation_started_event = + std::make_unique<WaitableEvent>(); + EXPECT_CALL( + *mock_logger_ptr, + ReportHibernationEvent(Canvas2DLayerBridge::kHibernationScheduled)); + EXPECT_CALL(*mock_logger_ptr, DidStartHibernating()).Times(1); + bridge->SetIsHidden(true); + platform->RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + + // Rendering in the background -> temp switch to SW + EXPECT_CALL(*mock_logger_ptr, + ReportHibernationEvent( + Canvas2DLayerBridge:: + kHibernationEndedWithSwitchToBackgroundRendering)); + DrawSomething(bridge); + testing::Mock::VerifyAndClearExpectations(mock_logger_ptr); + EXPECT_FALSE(bridge->IsAccelerated()); + EXPECT_FALSE(bridge->IsHibernating()); + EXPECT_TRUE(bridge->IsValid()); + + // Test prepareMailbox while background rendering + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + EXPECT_FALSE(bridge->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_TRUE(bridge->IsValid()); +} + +TEST_F(Canvas2DLayerBridgeTest, GpuMemoryBufferRecycling) { + ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(true); + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + const_cast<gpu::Capabilities&>(SharedGpuContext::ContextProviderWrapper() + ->ContextProvider() + ->GetCapabilities()) + .texture_format_bgra8888 = true; + + viz::TransferableResource resource1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + const GLuint texture_id1 = 1; + const GLuint texture_id2 = 2; + const GLuint image_id1 = 3; + const GLuint image_id2 = 4; + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id1)); + EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id1)); + DrawSomething(bridge); + bridge->PrepareTransferableResource(nullptr, &resource1, &release_callback1); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id2)); + EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id2)); + DrawSomething(bridge); + bridge->PrepareTransferableResource(nullptr, &resource2, &release_callback2); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Check that release resources does not result in destruction due + // to recycling. + EXPECT_CALL(gl_, DestroyImageCHROMIUM(_)).Times(0); + EXPECT_CALL(gl_, DeleteTextures(_, _)).Times(0); + bool lost_resource = false; + release_callback1->Run(gpu::SyncToken(), lost_resource); + release_callback1 = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, DestroyImageCHROMIUM(_)).Times(0); + EXPECT_CALL(gl_, DeleteTextures(_, _)).Times(0); + release_callback2->Run(gpu::SyncToken(), lost_resource); + release_callback2 = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Destroying the bridge results in destruction of cached resources. + EXPECT_CALL(gl_, DestroyImageCHROMIUM(image_id1)).Times(1); + EXPECT_CALL(gl_, DeleteTextures(1, Pointee(texture_id1))).Times(1); + EXPECT_CALL(gl_, DestroyImageCHROMIUM(image_id2)).Times(1); + EXPECT_CALL(gl_, DeleteTextures(1, Pointee(texture_id2))).Times(1); + bridge.Clear(); + + testing::Mock::VerifyAndClearExpectations(&gl_); +} + +TEST_F(Canvas2DLayerBridgeTest, NoGpuMemoryBufferRecyclingWhenPageHidden) { + ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(true); + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + const_cast<gpu::Capabilities&>(SharedGpuContext::ContextProviderWrapper() + ->ContextProvider() + ->GetCapabilities()) + .texture_format_bgra8888 = true; + + viz::TransferableResource resource1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + const GLuint texture_id1 = 1; + const GLuint texture_id2 = 2; + const GLuint image_id1 = 3; + const GLuint image_id2 = 4; + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id1)); + EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id1)); + DrawSomething(bridge); + bridge->PrepareTransferableResource(nullptr, &resource1, &release_callback1); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id2)); + EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id2)); + DrawSomething(bridge); + bridge->PrepareTransferableResource(nullptr, &resource2, &release_callback2); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Release first frame to cache + EXPECT_CALL(gl_, DestroyImageCHROMIUM(_)).Times(0); + EXPECT_CALL(gl_, DeleteTextures(_, _)).Times(0); + bool lost_resource = false; + release_callback1->Run(gpu::SyncToken(), lost_resource); + release_callback1 = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Switching to Hidden frees cached resources immediately + EXPECT_CALL(gl_, DestroyImageCHROMIUM(image_id1)).Times(1); + EXPECT_CALL(gl_, DeleteTextures(1, Pointee(texture_id1))).Times(1); + bridge->SetIsHidden(true); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Release second frame and verify that its resource is destroyed immediately + // due to the layer bridge being hidden + EXPECT_CALL(gl_, DestroyImageCHROMIUM(image_id2)).Times(1); + EXPECT_CALL(gl_, DeleteTextures(1, Pointee(texture_id2))).Times(1); + release_callback2->Run(gpu::SyncToken(), lost_resource); + release_callback2 = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); +} + +TEST_F(Canvas2DLayerBridgeTest, ReleaseGpuMemoryBufferAfterBridgeDestroyed) { + ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(true); + ScopedTestingPlatformSupport<FakePlatformSupport> platform; + const_cast<gpu::Capabilities&>(SharedGpuContext::ContextProviderWrapper() + ->ContextProvider() + ->GetCapabilities()) + .texture_format_bgra8888 = true; + + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + const GLuint texture_id = 1; + const GLuint image_id = 2; + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 150), 0, Canvas2DLayerBridge::kForceAccelerationForTesting, + CanvasColorParams())); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenTextures(1, _)).WillOnce(SetArgPointee<1>(texture_id)); + EXPECT_CALL(gl_, CreateImageCHROMIUM(_, _, _, _)).WillOnce(Return(image_id)); + DrawSomething(bridge); + bridge->PrepareTransferableResource(nullptr, &resource, &release_callback); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Tearing down the bridge does not destroy unreleased resources. + EXPECT_CALL(gl_, DestroyImageCHROMIUM(_)).Times(0); + EXPECT_CALL(gl_, DeleteTextures(_, _)).Times(0); + bridge.Clear(); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, DestroyImageCHROMIUM(image_id)).Times(1); + EXPECT_CALL(gl_, DeleteTextures(1, Pointee(texture_id))).Times(1); + bool lost_resource = false; + release_callback->Run(gpu::SyncToken(), lost_resource); + release_callback = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); +} + +TEST_F(Canvas2DLayerBridgeTest, EnsureCCImageCacheUse) { + auto color_params = + CanvasColorParams(kSRGBCanvasColorSpace, kF16CanvasPixelFormat, kOpaque); + ASSERT_FALSE(color_params.NeedsSkColorSpaceXformCanvas()); + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + color_params)); + std::vector<cc::DrawImage> images = { + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(10, 10)), + SkIRect::MakeWH(10, 10), kNone_SkFilterQuality, + SkMatrix::I(), 0u, color_params.GetStorageGfxColorSpace()), + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(20, 20)), + SkIRect::MakeWH(5, 5), kNone_SkFilterQuality, SkMatrix::I(), + 0u, color_params.GetStorageGfxColorSpace())}; + + bridge->Canvas()->drawImage(images[0].paint_image(), 0u, 0u, nullptr); + bridge->Canvas()->drawImageRect( + images[1].paint_image(), SkRect::MakeWH(5u, 5u), SkRect::MakeWH(5u, 5u), + nullptr, cc::PaintCanvas::kFast_SrcRectConstraint); + bridge->NewImageSnapshot(kPreferAcceleration); + + EXPECT_EQ(image_decode_cache_.decoded_images(), images); +} + +TEST_F(Canvas2DLayerBridgeTest, EnsureCCImageCacheUseWithColorConversion) { + auto color_params = CanvasColorParams(kSRGBCanvasColorSpace, + kRGBA8CanvasPixelFormat, kOpaque); + ASSERT_TRUE(color_params.NeedsSkColorSpaceXformCanvas()); + + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + color_params)); + std::vector<cc::DrawImage> images = { + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(10, 10)), + SkIRect::MakeWH(10, 10), kNone_SkFilterQuality, + SkMatrix::I(), 0u, color_params.GetStorageGfxColorSpace()), + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(20, 20)), + SkIRect::MakeWH(5, 5), kNone_SkFilterQuality, SkMatrix::I(), + 0u, color_params.GetStorageGfxColorSpace())}; + + bridge->Canvas()->drawImage(images[0].paint_image(), 0u, 0u, nullptr); + bridge->Canvas()->drawImageRect( + images[1].paint_image(), SkRect::MakeWH(5u, 5u), SkRect::MakeWH(5u, 5u), + nullptr, cc::PaintCanvas::kFast_SrcRectConstraint); + bridge->NewImageSnapshot(kPreferAcceleration); + + EXPECT_EQ(image_decode_cache_.decoded_images(), images); +} + +TEST_F(Canvas2DLayerBridgeTest, ImagesLockedUntilCacheLimit) { + // Disable deferral so we can inspect the cache state as we use the canvas. + auto color_params = + CanvasColorParams(kSRGBCanvasColorSpace, kF16CanvasPixelFormat, kOpaque); + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + color_params)); + bridge->DisableDeferral(DisableDeferralReason::kDisableDeferralReasonUnknown); + + std::vector<cc::DrawImage> images = { + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(10, 10)), + SkIRect::MakeWH(10, 10), kNone_SkFilterQuality, + SkMatrix::I(), 0u, color_params.GetStorageGfxColorSpace()), + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(20, 20)), + SkIRect::MakeWH(5, 5), kNone_SkFilterQuality, SkMatrix::I(), + 0u, color_params.GetStorageGfxColorSpace()), + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(20, 20)), + SkIRect::MakeWH(5, 5), kNone_SkFilterQuality, SkMatrix::I(), + 0u, color_params.GetStorageGfxColorSpace())}; + + // First 2 images are budgeted, they should remain locked after the op. + bridge->Canvas()->drawImage(images[0].paint_image(), 0u, 0u, nullptr); + bridge->Canvas()->drawImage(images[1].paint_image(), 0u, 0u, nullptr); + EXPECT_EQ(image_decode_cache_.num_locked_images(), 2); + + // Next image is not budgeted, we should unlock all images other than the last + // image. + image_decode_cache_.set_budget_exceeded(true); + bridge->Canvas()->drawImage(images[2].paint_image(), 0u, 0u, nullptr); + EXPECT_EQ(image_decode_cache_.num_locked_images(), 1); + + // Ask the provider to release everything, no locked images should remain. + bridge->GetOrCreateResourceProvider()->ReleaseLockedImages(); + EXPECT_EQ(image_decode_cache_.num_locked_images(), 0); +} + +TEST_F(Canvas2DLayerBridgeTest, ImageCacheOnContextLost) { + // Disable deferral so we use the raster canvas directly. + auto color_params = + CanvasColorParams(kSRGBCanvasColorSpace, kF16CanvasPixelFormat, kOpaque); + Canvas2DLayerBridgePtr bridge(std::make_unique<Canvas2DLayerBridge>( + IntSize(300, 300), 0, Canvas2DLayerBridge::kEnableAcceleration, + color_params)); + bridge->DisableDeferral(DisableDeferralReason::kDisableDeferralReasonUnknown); + + cc::PaintFlags flags; + std::vector<cc::DrawImage> images = { + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(10, 10)), + SkIRect::MakeWH(10, 10), kNone_SkFilterQuality, + SkMatrix::I(), 0u, color_params.GetStorageGfxColorSpace()), + cc::DrawImage(cc::CreateDiscardablePaintImage(gfx::Size(20, 20)), + SkIRect::MakeWH(5, 5), kNone_SkFilterQuality, SkMatrix::I(), + 0u, color_params.GetStorageGfxColorSpace())}; + bridge->Canvas()->drawImage(images[0].paint_image(), 0u, 0u, nullptr); + + // Lose the context and ensure that the image provider is not used. + bridge->GetResourceProvider()->OnContextDestroyed(); + // We should unref all images on the cache when the context is destroyed. + EXPECT_EQ(image_decode_cache_.num_locked_images(), 0); + image_decode_cache_.set_disallow_cache_use(true); + bridge->Canvas()->drawImage(images[1].paint_image(), 0u, 0u, &flags); +} + +} // namespace blink |