diff options
-rw-r--r-- | libs/hwui/Android.mk | 2 | ||||
-rw-r--r-- | libs/hwui/BakedOpRenderer.cpp | 88 | ||||
-rw-r--r-- | libs/hwui/BakedOpRenderer.h | 31 | ||||
-rw-r--r-- | libs/hwui/DisplayListCanvas.h | 17 | ||||
-rw-r--r-- | libs/hwui/LayerCache.cpp | 21 | ||||
-rw-r--r-- | libs/hwui/Properties.cpp | 18 | ||||
-rw-r--r-- | libs/hwui/Properties.h | 2 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.h | 7 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 34 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.h | 10 | ||||
-rw-r--r-- | libs/hwui/renderstate/OffscreenBufferPool.cpp | 196 | ||||
-rw-r--r-- | libs/hwui/renderstate/OffscreenBufferPool.h | 142 | ||||
-rw-r--r-- | libs/hwui/renderstate/RenderState.cpp | 15 | ||||
-rw-r--r-- | libs/hwui/renderstate/RenderState.h | 7 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 6 | ||||
-rw-r--r-- | libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp | 121 | ||||
-rw-r--r-- | libs/hwui/unit_tests/TestUtils.h | 2 | ||||
-rw-r--r-- | libs/hwui/utils/Macros.h | 3 |
18 files changed, 561 insertions, 161 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 5cdd7232923c..4acad67f716a 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -9,6 +9,7 @@ hwui_src_files := \ 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 @@ LOCAL_SRC_FILES += \ 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 938099209148..1aa291f23a1e 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 @@ void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, 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 4c3a2d0fb88a..d6d9cb139328 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -29,33 +29,6 @@ class Layer; 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 @@ public: , 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 fc08504ff60a..609103b89644 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 @@ public: 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 39cadd198c83..b117754347ed 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -14,14 +14,15 @@ * limitations under the License. */ -#include <GLES2/gl2.h> - -#include <utils/Log.h> +#include "LayerCache.h" #include "Caches.h" -#include "LayerCache.h" #include "Properties.h" +#include <utils/Log.h> + +#include <GLES2/gl2.h> + namespace android { namespace uirenderer { @@ -29,15 +30,9 @@ namespace uirenderer { // 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 e81818679f3e..0669596b21ca 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -37,6 +37,7 @@ bool Properties::useBufferAge = true; 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 @@ int Properties::overrideSpotShadowStrength = -1; 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 @@ bool Properties::load() { 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 1293c786a0bd..1dde7e054aab 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -267,6 +267,8 @@ public: 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 fc84c98b76a5..f26b0c827604 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 @@ public: 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 15ca718481fe..e177f9a86a2c 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -248,22 +248,31 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { } } -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 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { 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 ca7789e6f19a..0bd5b65f86aa 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -608,12 +608,16 @@ public: && 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 000000000000..6b44557a377b --- /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 000000000000..f0fd82d0444f --- /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 9637117bc11a..4fa820058fb2 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -90,6 +90,8 @@ void RenderState::onGLContextDestroyed() { } */ + mLayerPool.clear(); + // TODO: reset all cached state in state objects std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); mAssetAtlas.terminate(); @@ -106,6 +108,19 @@ void RenderState::onGLContextDestroyed() { 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 3cda1708da75..dcd5ea69a9fe 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 @@ public: 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 @@ public: Scissor& scissor() { return *mScissor; } Stencil& stencil() { return *mStencil; } + OffscreenBufferPool& layerPool() { return mLayerPool; } + void dump(); private: @@ -116,6 +121,8 @@ private: 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 1b89960afc44..f094b2d0c289 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -599,7 +599,7 @@ void CanvasContext::destroyHardwareResources() { // 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 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) { 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 000000000000..ba921572fd09 --- /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 23e5398b9307..efa28ae90262 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/unit_tests/TestUtils.h @@ -109,9 +109,11 @@ public: 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 5ca9083aab87..ccf2287400dc 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 */ |