blob: 4189db7beab6ffc80c33d5026edfab609b4acb82 [file] [log] [blame]
// 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 "cc/gl_renderer.h"
#include "cc/draw_quad.h"
#include "cc/prioritized_resource_manager.h"
#include "cc/resource_provider.h"
#include "cc/test/fake_web_compositor_output_surface.h"
#include "cc/test/fake_web_graphics_context_3d.h"
#include "cc/test/render_pass_test_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "ui/gfx/transform.h"
using namespace WebKit;
using namespace WebKitTests;
namespace cc {
namespace {
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 == GL_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)
{
m_memoryAllocationChangedCallback->onMemoryAllocationChanged(allocation);
}
private:
int m_frame;
WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* m_memoryAllocationChangedCallback;
};
class FakeRendererClient : public RendererClient {
public:
FakeRendererClient()
: m_setFullRootLayerDamageCount(0)
, m_lastCallWasSetVisibility(0)
, m_rootLayer(LayerImpl::create(1))
, m_memoryAllocationLimitBytes(PrioritizedResourceManager::defaultMemoryAllocationLimit())
{
m_rootLayer->createRenderSurface();
RenderPass::Id renderPassId = m_rootLayer->renderSurface()->renderPassId();
scoped_ptr<RenderPass> rootRenderPass = RenderPass::Create();
rootRenderPass->SetNew(renderPassId, gfx::Rect(), gfx::Rect(), gfx::Transform());
m_renderPassesInDrawOrder.push_back(rootRenderPass.get());
m_renderPasses.set(renderPassId, rootRenderPass.Pass());
}
// RendererClient methods.
virtual const gfx::Size& deviceViewportSize() const OVERRIDE { static gfx::Size fakeSize(1, 1); return fakeSize; }
virtual const LayerTreeSettings& settings() const OVERRIDE { static LayerTreeSettings fakeSettings; return fakeSettings; }
virtual void didLoseContext() OVERRIDE { }
virtual void onSwapBuffersComplete() OVERRIDE { }
virtual void setFullRootLayerDamage() OVERRIDE { m_setFullRootLayerDamageCount++; }
virtual void setManagedMemoryPolicy(const ManagedMemoryPolicy& policy) OVERRIDE { m_memoryAllocationLimitBytes = policy.bytesLimitWhenVisible; }
virtual void enforceManagedMemoryPolicy(const ManagedMemoryPolicy& policy) OVERRIDE { if (m_lastCallWasSetVisibility) *m_lastCallWasSetVisibility = false; }
virtual bool hasImplThread() const OVERRIDE { return false; }
// Methods added for test.
int setFullRootLayerDamageCount() const { return m_setFullRootLayerDamageCount; }
void setLastCallWasSetVisibilityPointer(bool* lastCallWasSetVisibility) { m_lastCallWasSetVisibility = lastCallWasSetVisibility; }
RenderPass* rootRenderPass() { return m_renderPassesInDrawOrder.back(); }
const RenderPassList& renderPassesInDrawOrder() const { return m_renderPassesInDrawOrder; }
const RenderPassIdHashMap& renderPasses() const { return m_renderPasses; }
size_t memoryAllocationLimitBytes() const { return m_memoryAllocationLimitBytes; }
private:
int m_setFullRootLayerDamageCount;
bool* m_lastCallWasSetVisibility;
scoped_ptr<LayerImpl> m_rootLayer;
RenderPassList m_renderPassesInDrawOrder;
RenderPassIdHashMap m_renderPasses;
size_t m_memoryAllocationLimitBytes;
};
class FakeRendererGL : public GLRenderer {
public:
FakeRendererGL(RendererClient* client, ResourceProvider* resourceProvider) : GLRenderer(client, resourceProvider) { }
// GLRenderer methods.
// Changing visibility to public.
using GLRenderer::initialize;
using GLRenderer::isFramebufferDiscarded;
using GLRenderer::drawQuad;
using GLRenderer::beginDrawingFrame;
};
class GLRendererTest : public testing::Test {
protected:
GLRendererTest()
: m_suggestHaveBackbufferYes(1, true)
, m_suggestHaveBackbufferNo(1, false)
, m_context(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new FrameCountingMemoryAllocationSettingContext())))
, m_resourceProvider(ResourceProvider::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<FrameCountingMemoryAllocationSettingContext*>(m_context->context3D()); }
WebGraphicsMemoryAllocation m_suggestHaveBackbufferYes;
WebGraphicsMemoryAllocation m_suggestHaveBackbufferNo;
scoped_ptr<GraphicsContext> m_context;
FakeRendererClient m_mockClient;
scoped_ptr<ResourceProvider> m_resourceProvider;
FakeRendererGL m_renderer;
};
// Test GLRenderer discardFramebuffer functionality:
// Suggest recreating framebuffer when one already exists.
// Expected: it does nothing.
TEST_F(GLRendererTest, SuggestBackbufferYesWhenItAlreadyExistsShouldDoNothing)
{
context()->setMemoryAllocation(m_suggestHaveBackbufferYes);
EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount());
EXPECT_FALSE(m_renderer.isFramebufferDiscarded());
swapBuffers();
EXPECT_EQ(1, context()->frameCount());
}
// Test GLRenderer 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(GLRendererTest, SuggestBackbufferNoShouldDiscardBackbufferAndDamageRootLayerWhileNotVisible)
{
m_renderer.setVisible(false);
context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
EXPECT_EQ(1, m_mockClient.setFullRootLayerDamageCount());
EXPECT_TRUE(m_renderer.isFramebufferDiscarded());
}
// Test GLRenderer discardFramebuffer functionality:
// Suggest discarding framebuffer when one exists and the renderer is visible.
// Expected: the allocation is ignored.
TEST_F(GLRendererTest, SuggestBackbufferNoDoNothingWhenVisible)
{
m_renderer.setVisible(true);
context()->setMemoryAllocation(m_suggestHaveBackbufferNo);
EXPECT_EQ(0, m_mockClient.setFullRootLayerDamageCount());
EXPECT_FALSE(m_renderer.isFramebufferDiscarded());
}
// Test GLRenderer discardFramebuffer functionality:
// Suggest discarding framebuffer when one does not exist.
// Expected: it does nothing.
TEST_F(GLRendererTest, 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 GLRenderer discardFramebuffer functionality:
// Begin drawing a frame while a framebuffer is discarded.
// Expected: will recreate framebuffer.
TEST_F(GLRendererTest, 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(GLRendererTest, 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, gfx::Rect(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 == GL_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 != GL_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 GLRendererTest, and you can't mix TEST() and TEST_F() with the same name, hence LRC2.
TEST(GLRendererTest2, initializationDoesNotMakeSynchronousCalls)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> context(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new ForbidSynchronousCallContext)));
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(context.get()));
FakeRendererGL 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(GLRendererTest2, initializationWithQuicklyLostContextDoesNotAssert)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> context(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new LoseContextOnFirstGetContext)));
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(context.get()));
FakeRendererGL 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(GLRendererTest2, initializationWithoutGpuMemoryManagerExtensionSupportShouldDefaultToNonZeroAllocation)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> context(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new ContextThatDoesNotSupportMemoryManagmentExtensions)));
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(context.get()));
FakeRendererGL 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(GLRendererTest2, opaqueBackground)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> outputSurface(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new ClearCountingContext)));
ClearCountingContext* context = static_cast<ClearCountingContext*>(outputSurface->context3D());
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
FakeRendererGL renderer(&mockClient, resourceProvider.get());
mockClient.rootRenderPass()->has_transparent_background = 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.
#ifdef NDEBUG
EXPECT_EQ(0, context->clearCount());
#else
EXPECT_EQ(1, context->clearCount());
#endif
}
TEST(GLRendererTest2, transparentBackground)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> outputSurface(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new ClearCountingContext)));
ClearCountingContext* context = static_cast<ClearCountingContext*>(outputSurface->context3D());
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
FakeRendererGL renderer(&mockClient, resourceProvider.get());
mockClient.rootRenderPass()->has_transparent_background = true;
EXPECT_TRUE(renderer.initialize());
renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses());
EXPECT_EQ(1, context->clearCount());
}
class VisibilityChangeIsLastCallTrackingContext : public FakeWebGraphicsContext3D {
public:
VisibilityChangeIsLastCallTrackingContext()
: m_lastCallWasSetVisibility(0)
{
}
// WebGraphicsContext3D methods.
virtual void setVisibilityCHROMIUM(bool visible) {
if (!m_lastCallWasSetVisibility)
return;
DCHECK(*m_lastCallWasSetVisibility == false);
*m_lastCallWasSetVisibility = true;
}
virtual void flush() { if (m_lastCallWasSetVisibility) *m_lastCallWasSetVisibility = false; }
virtual void deleteTexture(WebGLId) { if (m_lastCallWasSetVisibility) *m_lastCallWasSetVisibility = false; }
virtual void deleteFramebuffer(WebGLId) { if (m_lastCallWasSetVisibility) *m_lastCallWasSetVisibility = false; }
virtual void deleteRenderbuffer(WebGLId) { if (m_lastCallWasSetVisibility) *m_lastCallWasSetVisibility = false; }
// This method would normally do a glSwapBuffers under the hood.
virtual WebString getString(WebKit::WGC3Denum name)
{
if (name == GL_EXTENSIONS)
return WebString("GL_CHROMIUM_set_visibility GL_CHROMIUM_gpu_memory_manager GL_CHROMIUM_discard_framebuffer");
return WebString();
}
// Methods added for test.
void setLastCallWasSetVisibilityPointer(bool* lastCallWasSetVisibility) { m_lastCallWasSetVisibility = lastCallWasSetVisibility; }
private:
bool* m_lastCallWasSetVisibility;
};
TEST(GLRendererTest2, visibilityChangeIsLastCall)
{
FakeRendererClient mockClient;
scoped_ptr<GraphicsContext> outputSurface(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new VisibilityChangeIsLastCallTrackingContext)));
VisibilityChangeIsLastCallTrackingContext* context = static_cast<VisibilityChangeIsLastCallTrackingContext*>(outputSurface->context3D());
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
FakeRendererGL renderer(&mockClient, resourceProvider.get());
EXPECT_TRUE(renderer.initialize());
bool lastCallWasSetVisiblity = false;
// Ensure that the call to setVisibilityCHROMIUM is the last call issue to the GPU
// process, after glFlush is called, and after the RendererClient's enforceManagedMemoryPolicy
// is called. Plumb this tracking between both the RenderClient and the Context by giving
// them both a pointer to a variable on the stack.
context->setLastCallWasSetVisibilityPointer(&lastCallWasSetVisiblity);
mockClient.setLastCallWasSetVisibilityPointer(&lastCallWasSetVisiblity);
renderer.setVisible(true);
renderer.drawFrame(mockClient.renderPassesInDrawOrder(), mockClient.renderPasses());
renderer.setVisible(false);
EXPECT_TRUE(lastCallWasSetVisiblity);
}
class TextureStateTrackingContext : public FakeWebGraphicsContext3D {
public:
TextureStateTrackingContext()
: m_activeTexture(GL_INVALID_ENUM)
, m_inDraw(false)
{
}
virtual WebString getString(WGC3Denum name)
{
if (name == GL_EXTENSIONS)
return WebString("GL_OES_EGL_image_external");
return WebString();
}
// We shouldn't set any texture parameters during the draw sequence, although
// we might when creating the quads.
void setInDraw() { m_inDraw = true; }
virtual void texParameteri(WGC3Denum target, WGC3Denum pname, WGC3Dint param)
{
if (m_inDraw)
ADD_FAILURE();
}
virtual void activeTexture(WGC3Denum texture)
{
EXPECT_NE(texture, m_activeTexture);
m_activeTexture = texture;
}
WGC3Denum activeTexture() const { return m_activeTexture; }
private:
bool m_inDraw;
WGC3Denum m_activeTexture;
};
TEST(GLRendererTest2, activeTextureState)
{
FakeRendererClient fakeClient;
scoped_ptr<GraphicsContext> outputSurface(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new TextureStateTrackingContext)));
TextureStateTrackingContext* context = static_cast<TextureStateTrackingContext*>(outputSurface->context3D());
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
FakeRendererGL renderer(&fakeClient, resourceProvider.get());
EXPECT_TRUE(renderer.initialize());
cc::RenderPass::Id id(1, 1);
scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
pass->SetNew(id, gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 100, 100), gfx::Transform());
pass->AppendOneOfEveryQuadType(resourceProvider.get());
context->setInDraw();
cc::DirectRenderer::DrawingFrame drawingFrame;
renderer.beginDrawingFrame(drawingFrame);
EXPECT_EQ(context->activeTexture(), GL_TEXTURE0);
for (cc::QuadList::backToFrontIterator it = pass->quad_list.backToFrontBegin();
it != pass->quad_list.backToFrontEnd(); ++it) {
renderer.drawQuad(drawingFrame, *it);
}
EXPECT_EQ(context->activeTexture(), GL_TEXTURE0);
}
} // namespace
} // namespace cc