// Copyright 2012 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 "config.h" #include "CCRendererGL.h" #include "CCDrawQuad.h" #include "CCPrioritizedTextureManager.h" #include "CCResourceProvider.h" #include "CCSettings.h" #include "CCSingleThreadProxy.h" #include "CCTestCommon.h" #include "FakeWebCompositorOutputSurface.h" #include "FakeWebGraphicsContext3D.h" #include "GraphicsContext3D.h" #include "WebCompositorInitializer.h" #include #include #include using namespace cc; using namespace WebKit; using namespace WebKitTests; class FrameCountingMemoryAllocationSettingContext : public FakeWebGraphicsContext3D { public: FrameCountingMemoryAllocationSettingContext() : m_frame(0) { } // WebGraphicsContext3D methods. // This method would normally do a glSwapBuffers under the hood. virtual void prepareTexture() { m_frame++; } virtual void setMemoryAllocationChangedCallbackCHROMIUM(WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) { m_memoryAllocationChangedCallback = callback; } virtual WebString getString(WebKit::WGC3Denum name) { if (name == GraphicsContext3D::EXTENSIONS) return WebString("GL_CHROMIUM_set_visibility GL_CHROMIUM_gpu_memory_manager GL_CHROMIUM_discard_framebuffer"); return WebString(); } // Methods added for test. int frameCount() { return m_frame; } void setMemoryAllocation(WebGraphicsMemoryAllocation allocation) { ASSERT(CCProxy::isImplThread()); // In single threaded mode we expect this callback on main thread. DebugScopedSetMainThread main; m_memoryAllocationChangedCallback->onMemoryAllocationChanged(allocation); } private: int m_frame; WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* m_memoryAllocationChangedCallback; }; class FakeCCRendererClient : public CCRendererClient { public: FakeCCRendererClient() : m_setFullRootLayerDamageCount(0) , m_rootLayer(CCLayerImpl::create(1)) , m_memoryAllocationLimitBytes(CCPrioritizedTextureManager::defaultMemoryAllocationLimit()) { m_rootLayer->createRenderSurface(); CCRenderPass::Id renderPassId = m_rootLayer->renderSurface()->renderPassId(); scoped_ptr rootRenderPass = CCRenderPass::create(renderPassId, IntRect(), WebTransformationMatrix()); m_renderPassesInDrawOrder.push_back(rootRenderPass.get()); m_renderPasses.set(renderPassId, rootRenderPass.Pass()); } // CCRendererClient methods. virtual const IntSize& deviceViewportSize() const OVERRIDE { static IntSize fakeSize(1, 1); return fakeSize; } virtual const CCLayerTreeSettings& settings() const OVERRIDE { static CCLayerTreeSettings fakeSettings; return fakeSettings; } virtual void didLoseContext() OVERRIDE { } virtual void onSwapBuffersComplete() OVERRIDE { } virtual void setFullRootLayerDamage() OVERRIDE { m_setFullRootLayerDamageCount++; } virtual void releaseContentsTextures() OVERRIDE { } virtual void setMemoryAllocationLimitBytes(size_t bytes) OVERRIDE { m_memoryAllocationLimitBytes = bytes; } // Methods added for test. int setFullRootLayerDamageCount() const { return m_setFullRootLayerDamageCount; } CCRenderPass* rootRenderPass() { return m_renderPassesInDrawOrder.back(); } const CCRenderPassList& renderPassesInDrawOrder() const { return m_renderPassesInDrawOrder; } const CCRenderPassIdHashMap& renderPasses() const { return m_renderPasses; } size_t memoryAllocationLimitBytes() const { return m_memoryAllocationLimitBytes; } private: int m_setFullRootLayerDamageCount; DebugScopedSetImplThread m_implThread; OwnPtr m_rootLayer; CCRenderPassList m_renderPassesInDrawOrder; CCRenderPassIdHashMap m_renderPasses; size_t m_memoryAllocationLimitBytes; }; class FakeCCRendererGL : public CCRendererGL { public: FakeCCRendererGL(CCRendererClient* client, CCResourceProvider* resourceProvider) : CCRendererGL(client, resourceProvider) { } // CCRendererGL methods. // Changing visibility to public. using CCRendererGL::initialize; using CCRendererGL::isFramebufferDiscarded; }; class CCRendererGLTest : public testing::Test { protected: CCRendererGLTest() : m_suggestHaveBackbufferYes(1, true) , m_suggestHaveBackbufferNo(1, false) , m_compositorInitializer(0) , m_context(FakeWebCompositorOutputSurface::create(adoptPtr(new FrameCountingMemoryAllocationSettingContext()))) , m_resourceProvider(CCResourceProvider::create(m_context.get())) , m_renderer(&m_mockClient, m_resourceProvider.get()) { } virtual void SetUp() { m_renderer.initialize(); } void swapBuffers() { m_renderer.swapBuffers(); } FrameCountingMemoryAllocationSettingContext* context() { return static_cast(m_context->context3D()); } WebGraphicsMemoryAllocation m_suggestHaveBackbufferYes; WebGraphicsMemoryAllocation m_suggestHaveBackbufferNo; WebCompositorInitializer m_compositorInitializer; OwnPtr m_context; FakeCCRendererClient m_mockClient; OwnPtr m_resourceProvider; FakeCCRendererGL m_renderer; CCScopedSettings m_scopedSettings; }; // Test CCRendererGL discardFramebuffer functionality: // Suggest recreating framebuffer when one already exists. // Expected: it does nothing. TEST_F(CCRendererGLTest, SuggestBackbufferYesWhenItAlreadyExistsShouldDoNothing) { context()->setMemoryAllocation(m_suggestHaveBackbufferYes); EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount()); EXPECT_FALSE(m_renderer.isFramebufferDiscarded()); swapBuffers(); EXPECT_EQ(1, context()->frameCount()); } // Test CCRendererGL discardFramebuffer functionality: // Suggest discarding framebuffer when one exists and the renderer is not visible. // Expected: it is discarded and damage tracker is reset. TEST_F(CCRendererGLTest, SuggestBackbufferNoShouldDiscardBackbufferAndDamageRootLayerWhileNotVisible) { m_renderer.setVisible(false); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount()); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); } // Test CCRendererGL discardFramebuffer functionality: // Suggest discarding framebuffer when one exists and the renderer is visible. // Expected: the allocation is ignored. TEST_F(CCRendererGLTest, SuggestBackbufferNoDoNothingWhenVisible) { m_renderer.setVisible(true); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount()); EXPECT_FALSE(m_renderer.isFramebufferDiscarded()); } // Test CCRendererGL discardFramebuffer functionality: // Suggest discarding framebuffer when one does not exist. // Expected: it does nothing. TEST_F(CCRendererGLTest, SuggestBackbufferNoWhenItDoesntExistShouldDoNothing) { m_renderer.setVisible(false); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount()); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount()); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); } // Test CCRendererGL discardFramebuffer functionality: // Begin drawing a frame while a framebuffer is discarded. // Expected: will recreate framebuffer. TEST_F(CCRendererGLTest, DiscardedBackbufferIsRecreatedForScopeDuration) { m_renderer.setVisible(false); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount()); m_renderer.setVisible(true); m_renderer.drawFrame(m_mockClient.renderPassesInDrawOrder(), m_mockClient.renderPasses()); EXPECT_FALSE(m_renderer.isFramebufferDiscarded()); swapBuffers(); EXPECT_EQ(1, context()->frameCount()); } TEST_F(CCRendererGLTest, FramebufferDiscardedAfterReadbackWhenNotVisible) { m_renderer.setVisible(false); context()->setMemoryAllocation(m_suggestHaveBackbufferNo); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount()); char pixels[4]; m_renderer.drawFrame(m_mockClient.renderPassesInDrawOrder(), m_mockClient.renderPasses()); EXPECT_FALSE(m_renderer.isFramebufferDiscarded()); m_renderer.getFramebufferPixels(pixels, IntRect(0, 0, 1, 1)); EXPECT_TRUE(m_renderer.isFramebufferDiscarded()); EXPECT_EQ(2, m_mockClient.setFullRootLayerDamageCount()); } class ForbidSynchronousCallContext : public FakeWebGraphicsContext3D { public: ForbidSynchronousCallContext() { } virtual bool getActiveAttrib(WebGLId program, WGC3Duint index, ActiveInfo&) { ADD_FAILURE(); return false; } virtual bool getActiveUniform(WebGLId program, WGC3Duint index, ActiveInfo&) { ADD_FAILURE(); return false; } virtual void getAttachedShaders(WebGLId program, WGC3Dsizei maxCount, WGC3Dsizei* count, WebGLId* shaders) { ADD_FAILURE(); } virtual WGC3Dint getAttribLocation(WebGLId program, const WGC3Dchar* name) { ADD_FAILURE(); return 0; } virtual void getBooleanv(WGC3Denum pname, WGC3Dboolean* value) { ADD_FAILURE(); } virtual void getBufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); } virtual Attributes getContextAttributes() { ADD_FAILURE(); return m_attrs; } virtual WGC3Denum getError() { ADD_FAILURE(); return 0; } virtual void getFloatv(WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); } virtual void getFramebufferAttachmentParameteriv(WGC3Denum target, WGC3Denum attachment, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); } virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value) { if (pname == GraphicsContext3D::MAX_TEXTURE_SIZE) *value = 1024; // MAX_TEXTURE_SIZE is cached client side, so it's OK to query. else ADD_FAILURE(); } // We allow querying the shader compilation and program link status in debug mode, but not release. virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value) { #ifndef NDEBUG *value = 1; #else ADD_FAILURE(); #endif } virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value) { #ifndef NDEBUG *value = 1; #else ADD_FAILURE(); #endif } virtual WebString getString(WGC3Denum name) { // We allow querying the extension string. // FIXME: It'd be better to check that we only do this before starting any other expensive work (like starting a compilation) if (name != GraphicsContext3D::EXTENSIONS) ADD_FAILURE(); return WebString(); } virtual WebString getProgramInfoLog(WebGLId program) { ADD_FAILURE(); return WebString(); } virtual void getRenderbufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); } virtual WebString getShaderInfoLog(WebGLId shader) { ADD_FAILURE(); return WebString(); } virtual void getShaderPrecisionFormat(WGC3Denum shadertype, WGC3Denum precisiontype, WGC3Dint* range, WGC3Dint* precision) { ADD_FAILURE(); } virtual WebString getShaderSource(WebGLId shader) { ADD_FAILURE(); return WebString(); } virtual void getTexParameterfv(WGC3Denum target, WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); } virtual void getTexParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); } virtual void getUniformfv(WebGLId program, WGC3Dint location, WGC3Dfloat* value) { ADD_FAILURE(); } virtual void getUniformiv(WebGLId program, WGC3Dint location, WGC3Dint* value) { ADD_FAILURE(); } virtual WGC3Dint getUniformLocation(WebGLId program, const WGC3Dchar* name) { ADD_FAILURE(); return 0; } virtual void getVertexAttribfv(WGC3Duint index, WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); } virtual void getVertexAttribiv(WGC3Duint index, WGC3Denum pname, WGC3Dint* value) { ADD_FAILURE(); } virtual WGC3Dsizeiptr getVertexAttribOffset(WGC3Duint index, WGC3Denum pname) { ADD_FAILURE(); return 0; } }; // This test isn't using the same fixture as CCRendererGLTest, and you can't mix TEST() and TEST_F() with the same name, hence LRC2. TEST(CCRendererGLTest2, initializationDoesNotMakeSynchronousCalls) { CCScopedSettings scopedSettings; FakeCCRendererClient mockClient; OwnPtr context(FakeWebCompositorOutputSurface::create(adoptPtr(new ForbidSynchronousCallContext))); OwnPtr resourceProvider(CCResourceProvider::create(context.get())); FakeCCRendererGL renderer(&mockClient, resourceProvider.get()); EXPECT_TRUE(renderer.initialize()); } class LoseContextOnFirstGetContext : public FakeWebGraphicsContext3D { public: LoseContextOnFirstGetContext() : m_contextLost(false) { } virtual bool makeContextCurrent() OVERRIDE { return !m_contextLost; } virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value) OVERRIDE { m_contextLost = true; *value = 0; } virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value) OVERRIDE { m_contextLost = true; *value = 0; } virtual WGC3Denum getGraphicsResetStatusARB() OVERRIDE { return m_contextLost ? 1 : 0; } private: bool m_contextLost; }; TEST(CCRendererGLTest2, initializationWithQuicklyLostContextDoesNotAssert) { CCScopedSettings scopedSettings; FakeCCRendererClient mockClient; OwnPtr context(FakeWebCompositorOutputSurface::create(adoptPtr(new LoseContextOnFirstGetContext))); OwnPtr resourceProvider(CCResourceProvider::create(context.get())); FakeCCRendererGL renderer(&mockClient, resourceProvider.get()); renderer.initialize(); } class ContextThatDoesNotSupportMemoryManagmentExtensions : public FakeWebGraphicsContext3D { public: ContextThatDoesNotSupportMemoryManagmentExtensions() { } // WebGraphicsContext3D methods. // This method would normally do a glSwapBuffers under the hood. virtual void prepareTexture() { } virtual void setMemoryAllocationChangedCallbackCHROMIUM(WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) { } virtual WebString getString(WebKit::WGC3Denum name) { return WebString(); } }; TEST(CCRendererGLTest2, initializationWithoutGpuMemoryManagerExtensionSupportShouldDefaultToNonZeroAllocation) { FakeCCRendererClient mockClient; OwnPtr context(FakeWebCompositorOutputSurface::create(adoptPtr(new ContextThatDoesNotSupportMemoryManagmentExtensions))); OwnPtr resourceProvider(CCResourceProvider::create(context.get())); FakeCCRendererGL renderer(&mockClient, resourceProvider.get()); renderer.initialize(); EXPECT_GT(mockClient.memoryAllocationLimitBytes(), 0ul); } class ClearCountingContext : public FakeWebGraphicsContext3D { public: ClearCountingContext() : m_clear(0) { } virtual void clear(WGC3Dbitfield) { m_clear++; } int clearCount() const { return m_clear; } private: int m_clear; }; TEST(CCRendererGLTest2, opaqueBackground) { FakeCCRendererClient mockClient; OwnPtr ccContext(FakeWebCompositorOutputSurface::create(adoptPtr(new ClearCountingContext))); ClearCountingContext* context = static_cast(ccContext->context3D()); OwnPtr resourceProvider(CCResourceProvider::create(ccContext.get())); FakeCCRendererGL renderer(&mockClient, resourceProvider.get()); mockClient.rootRenderPass()->setHasTransparentBackground(false); EXPECT_TRUE(renderer.initialize()); renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses()); // On DEBUG builds, render passes with opaque background clear to blue to // easily see regions that were not drawn on the screen. #if defined(NDEBUG) EXPECT_EQ(0, context->clearCount()); #else EXPECT_EQ(1, context->clearCount()); #endif } TEST(CCRendererGLTest2, transparentBackground) { FakeCCRendererClient mockClient; OwnPtr ccContext(FakeWebCompositorOutputSurface::create(adoptPtr(new ClearCountingContext))); ClearCountingContext* context = static_cast(ccContext->context3D()); OwnPtr resourceProvider(CCResourceProvider::create(ccContext.get())); FakeCCRendererGL renderer(&mockClient, resourceProvider.get()); mockClient.rootRenderPass()->setHasTransparentBackground(true); EXPECT_TRUE(renderer.initialize()); renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses()); EXPECT_EQ(1, context->clearCount()); }