Fix OffscreenBuffer leak

am: 74af6e2

* commit '74af6e282f8a8f75928a071e8200039517cf5c12':
  Fix OffscreenBuffer leak

Change-Id: I24c16488d73588efe15e64ab711f8d3bc7a580b7
This commit is contained in:
Chris Craik
2016-04-05 20:50:03 +00:00
committed by android-build-merger
9 changed files with 167 additions and 109 deletions

View File

@@ -809,10 +809,6 @@ void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op,
Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
.build();
renderer.renderGlop(state, glop);
if (op.destroy) {
renderer.renderState().layerPool().putOrDelete(buffer);
}
}
}

View File

@@ -37,6 +37,10 @@ OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t h
return buffer;
}
void BakedOpRenderer::recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) {
mRenderState.layerPool().putOrDelete(offscreenBuffer);
}
void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");

View File

@@ -69,6 +69,7 @@ public:
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
void endFrame(const Rect& repaintRect);
WARN_UNUSED_RESULT OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer);
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
void endLayer();
WARN_UNUSED_RESULT OffscreenBuffer* copyToLayer(const Rect& area);

View File

@@ -86,6 +86,7 @@ public:
*/
template <typename StaticDispatcher, typename Renderer>
void replayBakedOps(Renderer& renderer) {
std::vector<OffscreenBuffer*> temporaryLayers;
finishDefer();
/**
* Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to
@@ -129,6 +130,7 @@ public:
} else if (!layer.empty()) {
// save layer - skip entire layer if empty (in which case, LayerOp has null layer).
layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
temporaryLayers.push_back(layer.offscreenBuffer);
GL_CHECKPOINT(MODERATE);
layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
GL_CHECKPOINT(MODERATE);
@@ -145,6 +147,10 @@ public:
GL_CHECKPOINT(MODERATE);
renderer.endFrame(fbo0.repaintRect);
}
for (auto& temporaryLayer : temporaryLayers) {
renderer.recycleTemporaryLayer(temporaryLayer);
}
}
void dump() const {

View File

@@ -501,22 +501,20 @@ struct CopyFromLayerOp : RecordedOp {
* when creating/tracking a SkPaint* during defer isn't worth the bother.
*/
struct LayerOp : RecordedOp {
// Records a one-use (saveLayer) layer for drawing. Once drawn, the layer will be destroyed.
// Records a one-use (saveLayer) layer for drawing.
LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
: SUPER_PAINTLESS(LayerOp)
, layerHandle(layerHandle)
, alpha(paint ? paint->getAlpha() / 255.0f : 1.0f)
, mode(PaintUtils::getXfermodeDirect(paint))
, colorFilter(paint ? paint->getColorFilter() : nullptr)
, destroy(true) {}
, colorFilter(paint ? paint->getColorFilter() : nullptr) {}
LayerOp(RenderNode& node)
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
, layerHandle(node.getLayerHandle())
, alpha(node.properties().layerProperties().alpha() / 255.0f)
, mode(node.properties().layerProperties().xferMode())
, colorFilter(node.properties().layerProperties().colorFilter())
, destroy(false) {}
, colorFilter(node.properties().layerProperties().colorFilter()) {}
// Records a handle to the Layer object, since the Layer itself won't be
// constructed until after this operation is constructed.
@@ -527,9 +525,6 @@ struct LayerOp : RecordedOp {
// pointer to object owned by either LayerProperties, or a recorded Paint object in a
// BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
SkColorFilter* colorFilter;
// whether to destroy the layer, once rendered
const bool destroy;
};
}; // namespace uirenderer

View File

@@ -127,7 +127,7 @@ int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
}
void OffscreenBufferPool::clear() {
for (auto entry : mPool) {
for (auto& entry : mPool) {
delete entry.layer;
}
mPool.clear();

View File

@@ -49,9 +49,12 @@ class TestRendererBase {
public:
virtual ~TestRendererBase() {}
virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
ADD_FAILURE() << "Layer creation not expected in this test";
ADD_FAILURE() << "Temporary layers not expected in this test";
return nullptr;
}
virtual void recycleTemporaryLayer(OffscreenBuffer*) {
ADD_FAILURE() << "Temporary layers not expected in this test";
}
virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
ADD_FAILURE() << "Layer repaint not expected in this test";
}
@@ -710,6 +713,10 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) {
EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
EXPECT_EQ(4, mIndex++);
EXPECT_EQ(nullptr, offscreenBuffer);
}
};
auto node = TestUtils::createNode(0, 0, 200, 200,
@@ -722,7 +729,7 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) {
TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
SaveLayerSimpleTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
EXPECT_EQ(5, renderer.getIndex());
}
RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
@@ -774,6 +781,15 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer
} else { ADD_FAILURE(); }
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
const int index = mIndex++;
// order isn't important, but we need to see both
if (index == 10) {
EXPECT_EQ((OffscreenBuffer*)0x400, offscreenBuffer);
} else if (index == 11) {
EXPECT_EQ((OffscreenBuffer*)0x800, offscreenBuffer);
} else { ADD_FAILURE(); }
}
};
auto node = TestUtils::createNode(0, 0, 800, 800,
@@ -794,7 +810,7 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
SaveLayerNestedTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
EXPECT_EQ(12, renderer.getIndex());
}
RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) {
@@ -1009,10 +1025,15 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) {
}
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(9, mIndex++);
EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
}
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(11, mIndex++);
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
EXPECT_EQ(12, mIndex++);
EXPECT_EQ((OffscreenBuffer*)0xabcd, offscreenBuffer);
}
};
auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping
@@ -1029,7 +1050,7 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) {
TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
SaveLayerUnclippedComplexTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(12, renderer.getIndex());
EXPECT_EQ(13, renderer.getIndex());
}
RENDERTHREAD_TEST(FrameBuilder, hwLayer_simple) {
@@ -1151,6 +1172,9 @@ RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) {
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(12, mIndex++);
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
EXPECT_EQ(13, mIndex++);
}
};
auto child = TestUtils::createNode(50, 50, 150, 150,
@@ -1188,7 +1212,7 @@ RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) {
syncedList, sLightGeometry, Caches::getInstance());
HwLayerComplexTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(13, renderer.getIndex());
EXPECT_EQ(14, renderer.getIndex());
// clean up layer pointers, so we can safely destruct RenderNodes
*(child->getLayerHandle()) = nullptr;
@@ -1592,6 +1616,9 @@ RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) {
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(4, mIndex++);
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
EXPECT_EQ(5, mIndex++);
}
};
auto parent = TestUtils::createNode(0, 0, 200, 200,
@@ -1610,7 +1637,7 @@ RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) {
(FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
ShadowSaveLayerTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(5, renderer.getIndex());
EXPECT_EQ(6, renderer.getIndex());
}
RENDERTHREAD_TEST(FrameBuilder, shadowHwLayer) {
@@ -1839,6 +1866,9 @@ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(3, mIndex++);
}
void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
EXPECT_EQ(4, mIndex++);
}
private:
SaveLayerAlphaData* mOutData;
};
@@ -1864,7 +1894,7 @@ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
// assert, since output won't be valid if we haven't seen a save layer triggered
ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
}
RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {

View File

@@ -30,6 +30,25 @@ const LayerUpdateQueue sEmptyLayerUpdateQueue;
const FrameBuilder::LightGeometry sLightGeometery = { {100, 100, 100}, 50};
const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
RENDERTHREAD_TEST(LeakCheck, saveLayer_overdrawRejection) {
auto node = TestUtils::createNode(0, 0, 100, 100,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restore();
// opaque draw, rejects saveLayer beneath
canvas.drawRect(0, 0, 100, 100, SkPaint());
});
RenderState& renderState = renderThread.renderState();
Caches& caches = Caches::getInstance();
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
TestUtils::createSyncedNodeList(node), sLightGeometery, Caches::getInstance());
BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
}
RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {

View File

@@ -30,119 +30,126 @@ TEST(OffscreenBuffer, computeIdealDimension) {
EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
}
TEST(OffscreenBuffer, construct) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBuffer layer(thread.renderState(), Caches::getInstance(), 49u, 149u);
EXPECT_EQ(49u, layer.viewportWidth);
EXPECT_EQ(149u, layer.viewportHeight);
RENDERTHREAD_TEST(OffscreenBuffer, construct) {
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u);
EXPECT_EQ(49u, layer.viewportWidth);
EXPECT_EQ(149u, layer.viewportHeight);
EXPECT_EQ(64u, layer.texture.width());
EXPECT_EQ(192u, layer.texture.height());
EXPECT_EQ(64u, layer.texture.width());
EXPECT_EQ(192u, layer.texture.height());
EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
});
EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
}
TEST(OffscreenBuffer, getTextureCoordinates) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBuffer layerAligned(thread.renderState(), Caches::getInstance(), 256u, 256u);
EXPECT_EQ(Rect(0, 1, 1, 0),
layerAligned.getTextureCoordinates());
RENDERTHREAD_TEST(OffscreenBuffer, getTextureCoordinates) {
OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
EXPECT_EQ(Rect(0, 1, 1, 0),
layerAligned.getTextureCoordinates());
OffscreenBuffer layerUnaligned(thread.renderState(), Caches::getInstance(), 200u, 225u);
EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0),
layerUnaligned.getTextureCoordinates());
});
OffscreenBuffer layerUnaligned(renderThread.renderState(), Caches::getInstance(), 200u, 225u);
EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0),
layerUnaligned.getTextureCoordinates());
}
TEST(OffscreenBuffer, dirty) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBuffer buffer(thread.renderState(), Caches::getInstance(), 256u, 256u);
buffer.dirty(Rect(-100, -100, 100, 100));
EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds());
});
RENDERTHREAD_TEST(OffscreenBuffer, dirty) {
OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
buffer.dirty(Rect(-100, -100, 100, 100));
EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds());
}
TEST(OffscreenBufferPool, construct) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBufferPool pool;
EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
<< "pool must read size from Properties";
});
RENDERTHREAD_TEST(OffscreenBufferPool, construct) {
OffscreenBufferPool pool;
EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
<< "pool must read size from Properties";
}
TEST(OffscreenBufferPool, getPutClear) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBufferPool pool;
RENDERTHREAD_TEST(OffscreenBufferPool, getPutClear) {
OffscreenBufferPool pool;
auto layer = pool.get(thread.renderState(), 100u, 200u);
EXPECT_EQ(100u, layer->viewportWidth);
EXPECT_EQ(200u, layer->viewportHeight);
auto layer = pool.get(renderThread.renderState(), 100u, 200u);
EXPECT_EQ(100u, layer->viewportWidth);
EXPECT_EQ(200u, layer->viewportHeight);
ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
pool.putOrDelete(layer);
ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
pool.putOrDelete(layer);
ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
auto layer2 = pool.get(thread.renderState(), 102u, 202u);
EXPECT_EQ(layer, layer2) << "layer should be recycled";
ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
auto layer2 = pool.get(renderThread.renderState(), 102u, 202u);
EXPECT_EQ(layer, layer2) << "layer should be recycled";
ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
pool.putOrDelete(layer);
EXPECT_EQ(1u, pool.getCount());
pool.clear();
EXPECT_EQ(0u, pool.getSize());
EXPECT_EQ(0u, pool.getCount());
});
pool.putOrDelete(layer);
EXPECT_EQ(1u, pool.getCount());
pool.clear();
EXPECT_EQ(0u, pool.getSize());
EXPECT_EQ(0u, pool.getCount());
}
TEST(OffscreenBufferPool, resize) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBufferPool pool;
RENDERTHREAD_TEST(OffscreenBufferPool, resize) {
OffscreenBufferPool pool;
auto layer = pool.get(thread.renderState(), 64u, 64u);
layer->dirty(Rect(64, 64));
auto layer = pool.get(renderThread.renderState(), 64u, 64u);
layer->dirty(Rect(64, 64));
// resize in place
ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region";
EXPECT_EQ(60u, layer->viewportWidth);
EXPECT_EQ(55u, layer->viewportHeight);
EXPECT_EQ(64u, layer->texture.width());
EXPECT_EQ(64u, layer->texture.height());
// resize in place
ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region";
EXPECT_EQ(60u, layer->viewportWidth);
EXPECT_EQ(55u, layer->viewportHeight);
EXPECT_EQ(64u, layer->texture.width());
EXPECT_EQ(64u, layer->texture.height());
// resized to use different object in pool
auto layer2 = pool.get(thread.renderState(), 128u, 128u);
layer2->dirty(Rect(128, 128));
EXPECT_FALSE(layer2->region.isEmpty());
pool.putOrDelete(layer2);
ASSERT_EQ(1u, pool.getCount());
// resized to use different object in pool
auto layer2 = pool.get(renderThread.renderState(), 128u, 128u);
layer2->dirty(Rect(128, 128));
EXPECT_FALSE(layer2->region.isEmpty());
pool.putOrDelete(layer2);
ASSERT_EQ(1u, pool.getCount());
ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region";
EXPECT_EQ(120u, layer2->viewportWidth);
EXPECT_EQ(125u, layer2->viewportHeight);
EXPECT_EQ(128u, layer2->texture.width());
EXPECT_EQ(128u, layer2->texture.height());
ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region";
EXPECT_EQ(120u, layer2->viewportWidth);
EXPECT_EQ(125u, layer2->viewportHeight);
EXPECT_EQ(128u, layer2->texture.width());
EXPECT_EQ(128u, layer2->texture.height());
// original allocation now only thing in pool
EXPECT_EQ(1u, pool.getCount());
EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
// original allocation now only thing in pool
EXPECT_EQ(1u, pool.getCount());
EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
pool.putOrDelete(layer2);
});
pool.putOrDelete(layer2);
}
TEST(OffscreenBufferPool, putAndDestroy) {
TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
OffscreenBufferPool pool;
// layer too big to return to the pool
// Note: this relies on the fact that the pool won't reject based on max texture size
auto hugeLayer = pool.get(thread.renderState(), pool.getMaxSize() / 64, 64);
EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
pool.putOrDelete(hugeLayer);
EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
});
RENDERTHREAD_TEST(OffscreenBufferPool, putAndDestroy) {
OffscreenBufferPool pool;
// layer too big to return to the pool
// Note: this relies on the fact that the pool won't reject based on max texture size
auto hugeLayer = pool.get(renderThread.renderState(), pool.getMaxSize() / 64, 64);
EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
pool.putOrDelete(hugeLayer);
EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
}
RENDERTHREAD_TEST(OffscreenBufferPool, clear) {
EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
OffscreenBufferPool pool;
// Create many buffers, with several at each size
std::vector<OffscreenBuffer*> buffers;
for (int size = 32; size <= 128; size += 32) {
for (int i = 0; i < 10; i++) {
buffers.push_back(pool.get(renderThread.renderState(), size, size));
}
}
EXPECT_EQ(0u, pool.getCount()) << "Expect nothing inside";
for (auto& buffer : buffers) pool.putOrDelete(buffer);
EXPECT_EQ(40u, pool.getCount()) << "Expect all items added";
EXPECT_EQ(40, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
pool.clear();
EXPECT_EQ(0u, pool.getCount()) << "Expect all items cleared";
EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
}