Recycle OffscreenBuffers
Change-Id: Ia2e219026f211a5308ecf8209c5f986bb888aadd
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 5cdd723..4acad67 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
font/Font.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
+ renderstate/OffscreenBufferPool.cpp \
renderstate/PixelBufferState.cpp \
renderstate/RenderState.cpp \
renderstate/Scissor.cpp \
@@ -216,6 +217,7 @@
unit_tests/LayerUpdateQueueTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/PathParserTests.cpp \
+ unit_tests/OffscreenBufferPoolTests.cpp \
unit_tests/StringUtilsTests.cpp
ifeq (true, $(HWUI_NEW_OPS))
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 9380992..1aa291f 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -19,98 +19,22 @@
#include "Caches.h"
#include "Glop.h"
#include "GlopBuilder.h"
-#include "VertexBuffer.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "renderstate/RenderState.h"
-#include "utils/FatVector.h"
#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
namespace android {
namespace uirenderer {
////////////////////////////////////////////////////////////////////////////////
-// OffscreenBuffer
-////////////////////////////////////////////////////////////////////////////////
-
-OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
- uint32_t textureWidth, uint32_t textureHeight,
- uint32_t viewportWidth, uint32_t viewportHeight)
- : renderState(renderState)
- , viewportWidth(viewportWidth)
- , viewportHeight(viewportHeight)
- , texture(caches) {
- texture.width = textureWidth;
- texture.height = textureHeight;
-
- caches.textureState().activateTexture(0);
- glGenTextures(1, &texture.id);
- caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
- texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
- // not setting filter on texture, since it's set when rendering, based on transform
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
-}
-
-void OffscreenBuffer::updateMeshFromRegion() {
- // avoid T-junctions as they cause artifacts in between the resultant
- // geometry when complex transforms occur.
- // TODO: generate the safeRegion only if necessary based on rendering transform
- Region safeRegion = Region::createTJunctionFreeRegion(region);
-
- size_t count;
- const android::Rect* rects = safeRegion.getArray(&count);
-
- const float texX = 1.0f / float(viewportWidth);
- const float texY = 1.0f / float(viewportHeight);
-
- FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
- TextureVertex* mesh = &meshVector[0];
- for (size_t i = 0; i < count; i++) {
- const android::Rect* r = &rects[i];
-
- const float u1 = r->left * texX;
- const float v1 = (viewportHeight - r->top) * texY;
- const float u2 = r->right * texX;
- const float v2 = (viewportHeight - r->bottom) * texY;
-
- TextureVertex::set(mesh++, r->left, r->top, u1, v1);
- TextureVertex::set(mesh++, r->right, r->top, u2, v1);
- TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
- TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
- }
- elementCount = count * 6;
- renderState.meshState().genOrUpdateMeshBuffer(&vbo,
- sizeof(TextureVertex) * count * 4,
- &meshVector[0],
- GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
-}
-
-OffscreenBuffer::~OffscreenBuffer() {
- texture.deleteTexture();
- renderState.meshState().deleteMeshBuffer(vbo);
- elementCount = 0;
- vbo = 0;
-}
-
-////////////////////////////////////////////////////////////////////////////////
// BakedOpRenderer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
- uint32_t width, uint32_t height) {
- // TODO: get from cache!
- return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
-}
-
-void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
- // TODO: return texture/offscreenbuffer to cache!
- delete offscreenBuffer;
-}
-
OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
- OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
+ OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
startRepaintLayer(buffer);
return buffer;
}
@@ -357,7 +281,7 @@
renderer.renderGlop(state, glop);
if (op.destroy) {
- BakedOpRenderer::destroyOffscreenBuffer(buffer);
+ renderer.renderState().layerPool().putOrDelete(buffer);
}
}
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 4c3a2d0..d6d9cb1 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -29,33 +29,6 @@
class RenderState;
/**
- * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
- * encompasses enough information to draw it back on screen (minus paint properties, which are held
- * by LayerOp).
- */
-class OffscreenBuffer {
-public:
- OffscreenBuffer(RenderState& renderState, Caches& caches,
- uint32_t textureWidth, uint32_t textureHeight,
- uint32_t viewportWidth, uint32_t viewportHeight);
- ~OffscreenBuffer();
-
- // must be called prior to rendering, to construct/update vertex buffer
- void updateMeshFromRegion();
-
- RenderState& renderState;
- uint32_t viewportWidth;
- uint32_t viewportHeight;
- Texture texture;
-
- // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
- // drawing back to screen / parent FBO.
- Region region;
- GLsizei elementCount = 0;
- GLuint vbo = 0;
-};
-
-/**
* Main rendering manager for a collection of work - one frame + any contained FBOs.
*
* Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
@@ -72,10 +45,6 @@
, mOpaque(opaque) {
}
- static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
- uint32_t width, uint32_t height);
- static void destroyOffscreenBuffer(OffscreenBuffer*);
-
RenderState& renderState() { return mRenderState; }
Caches& caches() { return mCaches; }
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index fc08504..609103b 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,6 +17,14 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
#define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
+#include "Canvas.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RenderNode.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "utils/Macros.h"
+
#include <SkDrawFilter.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -25,13 +33,6 @@
#include <SkTLazy.h>
#include <cutils/compiler.h>
-#include "Canvas.h"
-#include "CanvasState.h"
-#include "DisplayList.h"
-#include "SkiaCanvasProxy.h"
-#include "RenderNode.h"
-#include "ResourceCache.h"
-
namespace android {
namespace uirenderer {
@@ -66,7 +67,7 @@
virtual ~DisplayListCanvas();
void reset(int width, int height);
- __attribute__((warn_unused_result)) DisplayList* finishRecording();
+ WARN_UNUSED_RESULT DisplayList* finishRecording();
// ----------------------------------------------------------------------------
// HWUI Canvas state operations
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 39cadd1..b117754 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-#include <GLES2/gl2.h>
+#include "LayerCache.h"
+
+#include "Caches.h"
+#include "Properties.h"
#include <utils/Log.h>
-#include "Caches.h"
-#include "LayerCache.h"
-#include "Properties.h"
+#include <GLES2/gl2.h>
namespace android {
namespace uirenderer {
@@ -29,15 +30,9 @@
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting layer cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
- }
-}
+LayerCache::LayerCache()
+ : mSize(0)
+ , mMaxSize(Properties::layerPoolSize) {}
LayerCache::~LayerCache() {
clear();
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e818186..0669596 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -37,6 +37,7 @@
bool Properties::enablePartialUpdates = true;
float Properties::textGamma = DEFAULT_TEXT_GAMMA;
+int Properties::layerPoolSize = DEFAULT_LAYER_CACHE_SIZE;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -52,10 +53,19 @@
ProfileType Properties::sProfileType = ProfileType::None;
bool Properties::sDisableProfileBars = false;
+static int property_get_int(const char* key, int defaultValue) {
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+ if (property_get(key, buf, "") > 0) {
+ return atoi(buf);
+ }
+ return defaultValue;
+}
+
static float property_get_float(const char* key, float defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {'\0',};
- if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+ if (property_get(key, buf, "") > 0) {
return atof(buf);
}
return defaultValue;
@@ -114,16 +124,14 @@
showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false);
- debugLevel = kDebugDisabled;
- if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) {
- debugLevel = (DebugLevel) atoi(property);
- }
+ debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled);
skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA);
+ layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE));
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1293c78..1dde7e0 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -267,6 +267,8 @@
static float textGamma;
+ static int layerPoolSize;
+
static DebugLevel debugLevel;
static OverdrawColorSet overdrawColorSet;
static StencilClipDebug debugStencilClip;
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index fc84c98..f26b0c8 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -20,11 +20,12 @@
#include "Canvas.h"
#include "CanvasState.h"
#include "DisplayList.h"
-#include "utils/LinearAllocator.h"
-#include "utils/NinePatch.h"
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
+#include "utils/LinearAllocator.h"
+#include "utils/Macros.h"
+#include "utils/NinePatch.h"
#include <SkDrawFilter.h>
#include <SkPaint.h>
@@ -49,7 +50,7 @@
virtual ~RecordingCanvas();
void reset(int width, int height);
- __attribute__((warn_unused_result)) DisplayList* finishRecording();
+ WARN_UNUSED_RESULT DisplayList* finishRecording();
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 15ca718..e177f9a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -248,22 +248,31 @@
}
}
-layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
#if HWUI_NEW_OPS
- return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+ return renderState.layerPool().get(renderState, width, height);
#else
return LayerRenderer::createRenderLayer(renderState, width, height);
#endif
}
-void destroyLayer(layer_t* layer) {
+static void destroyLayer(layer_t* layer) {
#if HWUI_NEW_OPS
- BakedOpRenderer::destroyOffscreenBuffer(layer);
+ RenderState& renderState = layer->renderState;
+ renderState.layerPool().putOrDelete(layer);
#else
LayerRenderer::destroyLayer(layer);
#endif
}
+static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) {
+#if HWUI_NEW_OPS
+ return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
+#else
+ return layer->layer.getWidth() == width && layer->layer.getHeight() == height;
+#endif
+}
+
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
@@ -278,17 +287,16 @@
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
- damageSelf(info);
- transformUpdateNeeded = true;
+ mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+ damageSelf(info);
+ transformUpdateNeeded = true;
+ } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
#if HWUI_NEW_OPS
- } else if (mLayer->viewportWidth != (uint32_t) getWidth()
- || mLayer->viewportHeight != (uint32_t)getHeight()) {
- // TODO: allow node's layer to grow larger
- if ((uint32_t)getWidth() > mLayer->texture.width
- || (uint32_t)getHeight() > mLayer->texture.height) {
+ RenderState& renderState = mLayer->renderState;
+ if (properties().fitsOnLayer()) {
+ mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
+ } else {
#else
- } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
#endif
destroyLayer(mLayer);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index ca7789e..0bd5b65 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -608,12 +608,16 @@
&& getOutline().getAlpha() != 0.0f;
}
- bool promotedToLayer() const {
+ bool fitsOnLayer() const {
const DeviceInfo* deviceInfo = DeviceInfo::get();
LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized");
+ return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+ && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+ }
+
+ bool promotedToLayer() const {
return mLayerProperties.mType == LayerType::None
- && mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
- && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize()
+ && fitsOnLayer()
&& (mComputedFields.mNeedLayerForFunctors
|| (!MathUtils::isZero(mPrimitiveFields.mAlpha)
&& mPrimitiveFields.mAlpha < 1
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
new file mode 100644
index 0000000..6b44557
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OffscreenBufferPool.h"
+
+#include "Caches.h"
+#include "Properties.h"
+#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
+
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight)
+ : renderState(renderState)
+ , viewportWidth(viewportWidth)
+ , viewportHeight(viewportHeight)
+ , texture(caches) {
+ texture.width = computeIdealDimension(viewportWidth);
+ texture.height = computeIdealDimension(viewportHeight);
+ texture.blend = true;
+
+ caches.textureState().activateTexture(0);
+ glGenTextures(1, &texture.id);
+ caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
+
+ texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+ // not setting filter on texture, since it's set when drawing, based on transform
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+}
+
+void OffscreenBuffer::updateMeshFromRegion() {
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform
+ Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+ size_t count;
+ const android::Rect* rects = safeRegion.getArray(&count);
+
+ const float texX = 1.0f / float(texture.width);
+ const float texY = 1.0f / float(texture.height);
+
+ FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+ TextureVertex* mesh = &meshVector[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (viewportHeight - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (viewportHeight - r->bottom) * texY;
+
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ elementCount = count * 6;
+ renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+ sizeof(TextureVertex) * count * 4,
+ &meshVector[0],
+ GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) {
+ return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+ texture.deleteTexture();
+ renderState.meshState().deleteMeshBuffer(vbo);
+ elementCount = 0;
+ vbo = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OffscreenBufferPool
+///////////////////////////////////////////////////////////////////////////////
+
+OffscreenBufferPool::OffscreenBufferPool()
+ : mMaxSize(Properties::layerPoolSize) {
+}
+
+OffscreenBufferPool::~OffscreenBufferPool() {
+ clear(); // TODO: unique_ptr?
+}
+
+int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
+ int deltaInt = int(lhs.width) - int(rhs.width);
+ if (deltaInt != 0) return deltaInt;
+
+ return int(lhs.height) - int(rhs.height);
+}
+
+void OffscreenBufferPool::clear() {
+ for (auto entry : mPool) {
+ delete entry.layer;
+ }
+ mPool.clear();
+ mSize = 0;
+}
+
+OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
+ const uint32_t width, const uint32_t height) {
+ OffscreenBuffer* layer = nullptr;
+
+ Entry entry(width, height);
+ auto iter = mPool.find(entry);
+
+ if (iter != mPool.end()) {
+ entry = *iter;
+ mPool.erase(iter);
+
+ layer = entry.layer;
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+ mSize -= layer->getSizeInBytes();
+ } else {
+ layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+ }
+
+ return layer;
+}
+
+OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height) {
+ RenderState& renderState = layer->renderState;
+ if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
+ && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+ // resize in place
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+ return layer;
+ }
+ putOrDelete(layer);
+ return get(renderState, width, height);
+}
+
+void OffscreenBufferPool::dump() {
+ for (auto entry : mPool) {
+ ALOGD(" Layer size %dx%d", entry.width, entry.height);
+ }
+}
+
+void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) {
+ const uint32_t size = layer->getSizeInBytes();
+ // Don't even try to cache a layer that's bigger than the cache
+ if (size < mMaxSize) {
+ // TODO: Use an LRU
+ while (mSize + size > mMaxSize) {
+ OffscreenBuffer* victim = mPool.begin()->layer;
+ mSize -= victim->getSizeInBytes();
+ delete victim;
+ mPool.erase(mPool.begin());
+ }
+
+ // clear region, since it's no longer valid
+ layer->region.clear();
+
+ Entry entry(layer);
+
+ mPool.insert(entry);
+ mSize += size;
+ } else {
+ delete layer;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
new file mode 100644
index 0000000..f0fd82d
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/Macros.h"
+
+#include <ui/Region.h>
+
+#include <set>
+
+namespace android {
+namespace uirenderer {
+
+class RenderState;
+
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ */
+class OffscreenBuffer {
+public:
+ OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight);
+ ~OffscreenBuffer();
+
+ // must be called prior to rendering, to construct/update vertex buffer
+ void updateMeshFromRegion();
+
+ static uint32_t computeIdealDimension(uint32_t dimension);
+
+ uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+
+ RenderState& renderState;
+ uint32_t viewportWidth;
+ uint32_t viewportHeight;
+ Texture texture;
+
+ // Portion of layer that has been drawn to. Used to minimize drawing area when
+ // drawing back to screen / parent FBO.
+ Region region;
+ GLsizei elementCount = 0;
+ GLuint vbo = 0;
+};
+
+/**
+ * Pool of OffscreenBuffers allocated, but not currently in use.
+ */
+class OffscreenBufferPool {
+public:
+ OffscreenBufferPool();
+ ~OffscreenBufferPool();
+
+ WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
+ const uint32_t width, const uint32_t height);
+
+ WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height);
+
+ void putOrDelete(OffscreenBuffer* layer);
+
+ /**
+ * Clears the pool. This causes all layers to be deleted.
+ */
+ void clear();
+
+ /**
+ * Returns the maximum size of the pool in bytes.
+ */
+ uint32_t getMaxSize() { return mMaxSize; }
+
+ /**
+ * Returns the current size of the pool in bytes.
+ */
+ uint32_t getSize() { return mSize; }
+
+ size_t getCount() { return mPool.size(); }
+
+ /**
+ * Prints out the content of the pool.
+ */
+ void dump();
+private:
+ struct Entry {
+ Entry() {}
+
+ Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+ : width(OffscreenBuffer::computeIdealDimension(layerWidth))
+ , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+
+ Entry(OffscreenBuffer* layer)
+ : layer(layer)
+ , width(layer->texture.width)
+ , height(layer->texture.height) {
+ }
+
+ static int compare(const Entry& lhs, const Entry& rhs);
+
+ bool operator==(const Entry& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const Entry& other) const {
+ return compare(*this, other) != 0;
+ }
+
+ bool operator<(const Entry& other) const {
+ return Entry::compare(*this, other) < 0;
+ }
+
+ OffscreenBuffer* layer = nullptr;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ }; // struct Entry
+
+ std::multiset<Entry> mPool;
+
+ uint32_t mSize = 0;
+ uint32_t mMaxSize;
+}; // class OffscreenBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 9637117..4fa8200 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -90,6 +90,8 @@
}
*/
+ mLayerPool.clear();
+
// TODO: reset all cached state in state objects
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
mAssetAtlas.terminate();
@@ -106,6 +108,19 @@
mStencil = nullptr;
}
+void RenderState::flush(Caches::FlushMode mode) {
+ switch (mode) {
+ case Caches::FlushMode::Full:
+ // fall through
+ case Caches::FlushMode::Moderate:
+ // fall through
+ case Caches::FlushMode::Layers:
+ mLayerPool.clear();
+ break;
+ }
+ mCaches->flush(mode);
+}
+
void RenderState::setViewport(GLsizei width, GLsizei height) {
mViewportWidth = width;
mViewportHeight = height;
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 3cda170..dcd5ea6 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -21,6 +21,7 @@
#include "Glop.h"
#include "renderstate/Blend.h"
#include "renderstate/MeshState.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "renderstate/PixelBufferState.h"
#include "renderstate/Scissor.h"
#include "renderstate/Stencil.h"
@@ -56,6 +57,8 @@
void onGLContextCreated();
void onGLContextDestroyed();
+ void flush(Caches::FlushMode flushMode);
+
void setViewport(GLsizei width, GLsizei height);
void getViewport(GLsizei* outWidth, GLsizei* outHeight);
@@ -97,6 +100,8 @@
Scissor& scissor() { return *mScissor; }
Stencil& stencil() { return *mStencil; }
+ OffscreenBufferPool& layerPool() { return mLayerPool; }
+
void dump();
private:
@@ -116,6 +121,8 @@
Scissor* mScissor = nullptr;
Stencil* mStencil = nullptr;
+ OffscreenBufferPool mLayerPool;
+
AssetAtlas mAssetAtlas;
std::set<Layer*> mActiveLayers;
std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b89960..f094b2d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -599,7 +599,7 @@
// Make sure to release all the textures we were owning as there won't
// be another draw
caches.textureCache.resetMarkInUse(this);
- caches.flush(Caches::FlushMode::Layers);
+ mRenderThread.renderState().flush(Caches::FlushMode::Layers);
}
}
@@ -609,10 +609,10 @@
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
- Caches::getInstance().flush(Caches::FlushMode::Full);
+ thread.renderState().flush(Caches::FlushMode::Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- Caches::getInstance().flush(Caches::FlushMode::Moderate);
+ thread.renderState().flush(Caches::FlushMode::Moderate);
}
}
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
new file mode 100644
index 0000000..ba92157
--- /dev/null
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <renderstate/OffscreenBufferPool.h>
+
+#include <unit_tests/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OffscreenBuffer, computeIdealDimension) {
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
+ 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);
+
+ EXPECT_EQ(64u, layer.texture.width);
+ EXPECT_EQ(192u, layer.texture.height);
+
+ EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
+ });
+}
+
+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";
+ });
+
+}
+
+TEST(OffscreenBufferPool, getPutClear) {
+ TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) {
+ OffscreenBufferPool pool;
+
+ auto layer = pool.get(thread.renderState(), 100u, 200u);
+ EXPECT_EQ(100u, layer->viewportWidth);
+ EXPECT_EQ(200u, layer->viewportHeight);
+
+ ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+ 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";
+
+ 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;
+
+ auto layer = pool.get(thread.renderState(), 64u, 64u);
+
+ // resize in place
+ ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+ 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);
+ pool.putOrDelete(layer2);
+ ASSERT_EQ(1u, pool.getCount());
+ ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+ 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());
+ });
+}
+
+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)
+ });
+}
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 23e5398..efa28ae 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -109,9 +109,11 @@
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
PropSetupCallback propSetupCallback = nullptr) {
+#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
DeviceInfo::initialize();
+#endif
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083..ccf2287 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -35,4 +35,7 @@
static_assert(std::is_standard_layout<Type>::value, \
#Type " must have standard layout")
+#define WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+
#endif /* MACROS_H */