Fixes: 27941148 Make OffscreenBuffer lifecycle an explicit (and tested) contract between FrameBuilder and BakedOpRenderer, entirely separate from dispatch. This makes it safe to reject any rendering work via overdraw content rejection (before it gets to a BakedOpDispatcher). Adds a couple tests around OffscreenBuffer leaks, and switches OffscreenBuffer tests to RENDERTHREAD_TEST macro, as appropriate. Change-Id: Id114b835d042708ae921028fb4b17e5fa485fe64
208 lines
6.7 KiB
C++
208 lines
6.7 KiB
C++
/*
|
|
* 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)
|
|
: GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
|
|
, renderState(renderState)
|
|
, viewportWidth(viewportWidth)
|
|
, viewportHeight(viewportHeight)
|
|
, texture(caches) {
|
|
uint32_t width = computeIdealDimension(viewportWidth);
|
|
uint32_t height = computeIdealDimension(viewportHeight);
|
|
caches.textureState().activateTexture(0);
|
|
texture.resize(width, height, GL_RGBA);
|
|
texture.blend = true;
|
|
texture.setWrap(GL_CLAMP_TO_EDGE);
|
|
// not setting filter on texture, since it's set when drawing, based on transform
|
|
}
|
|
|
|
Rect OffscreenBuffer::getTextureCoordinates() {
|
|
const float texX = 1.0f / static_cast<float>(texture.width());
|
|
const float texY = 1.0f / static_cast<float>(texture.height());
|
|
return Rect(0, viewportHeight * texY, viewportWidth * texX, 0);
|
|
}
|
|
|
|
void OffscreenBuffer::dirty(Rect dirtyArea) {
|
|
dirtyArea.doIntersect(0, 0, viewportWidth, viewportHeight);
|
|
if (!dirtyArea.isEmpty()) {
|
|
region.orSelf(android::Rect(dirtyArea.left, dirtyArea.top,
|
|
dirtyArea.right, dirtyArea.bottom));
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// entire area will be repainted (and may be smaller) so clear usage region
|
|
layer->region.clear();
|
|
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
|