Initial HW layer support in new reorderer/renderer
Shares vast majority of clipped savelayer code, with only minor differences in lifecycle. Doesn't yet handle fill region, resize, or window transform. Change-Id: Iabdd71811590d2b937eb11e1b01ce556ade54a5a
This commit is contained in:
@@ -56,6 +56,7 @@ hwui_src_files := \
|
||||
Layer.cpp \
|
||||
LayerCache.cpp \
|
||||
LayerRenderer.cpp \
|
||||
LayerUpdateQueue.cpp \
|
||||
Matrix.cpp \
|
||||
OpenGLRenderer.cpp \
|
||||
Patch.cpp \
|
||||
@@ -204,6 +205,7 @@ LOCAL_SRC_FILES += \
|
||||
unit_tests/ClipAreaTests.cpp \
|
||||
unit_tests/DamageAccumulatorTests.cpp \
|
||||
unit_tests/FatVectorTests.cpp \
|
||||
unit_tests/LayerUpdateQueueTests.cpp \
|
||||
unit_tests/LinearAllocatorTests.cpp \
|
||||
unit_tests/StringUtilsTests.cpp
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ namespace uirenderer {
|
||||
|
||||
OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
|
||||
uint32_t viewportWidth, uint32_t viewportHeight)
|
||||
: texture(caches)
|
||||
: viewportWidth(viewportWidth)
|
||||
, viewportHeight(viewportHeight)
|
||||
, texture(caches)
|
||||
, texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
|
||||
texture.width = textureWidth;
|
||||
texture.height = textureHeight;
|
||||
@@ -52,12 +54,29 @@ OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t
|
||||
// BakedOpRenderer
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
|
||||
OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(uint32_t width, uint32_t height) {
|
||||
// TODO: get from cache!
|
||||
return new OffscreenBuffer(Caches::getInstance(), width, height, width, height);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
|
||||
// destroy and delete, since each clipped saveLayer is only drawn once.
|
||||
offscreenBuffer->texture.deleteTexture();
|
||||
|
||||
// TODO: return texture/offscreenbuffer to cache!
|
||||
delete offscreenBuffer;
|
||||
}
|
||||
|
||||
OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
|
||||
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
|
||||
|
||||
// TODO: really should be caching these!
|
||||
OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
|
||||
mRenderTarget.offscreenBuffer = buffer;
|
||||
OffscreenBuffer* buffer = createOffscreenBuffer(width, height);
|
||||
startLayer(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
|
||||
mRenderTarget.offscreenBuffer = offscreenBuffer;
|
||||
|
||||
// create and bind framebuffer
|
||||
mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
|
||||
@@ -65,7 +84,7 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
|
||||
|
||||
// attach the texture to the FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
buffer->texture.id, 0);
|
||||
offscreenBuffer->texture.id, 0);
|
||||
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
|
||||
LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
|
||||
"framebuffer incomplete!");
|
||||
@@ -75,8 +94,7 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Change the viewport & ortho projection
|
||||
setViewport(width, height);
|
||||
return buffer;
|
||||
setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::endLayer() {
|
||||
@@ -212,23 +230,21 @@ void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op,
|
||||
|
||||
// TODO: extend this to handle HW layers & paint properties which
|
||||
// reside in node.properties().layerProperties()
|
||||
float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
|
||||
float layerAlpha = op.alpha * state.alpha;
|
||||
const bool tryToSnap = state.computedState.transform.isPureTranslate();
|
||||
Glop glop;
|
||||
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
||||
.setRoundRectClipState(state.roundRectClipState)
|
||||
.setMeshTexturedUvQuad(nullptr, buffer->texCoords)
|
||||
.setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
|
||||
.setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
|
||||
.setTransform(state.computedState.transform, TransformFlags::None)
|
||||
.setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
|
||||
.build();
|
||||
renderer.renderGlop(state, glop);
|
||||
|
||||
// destroy and delete, since each clipped saveLayer is only drawn once.
|
||||
buffer->texture.deleteTexture();
|
||||
|
||||
// TODO: return texture/offscreenbuffer to cache!
|
||||
delete buffer;
|
||||
if (op.destroy) {
|
||||
BakedOpRenderer::destroyOffscreenBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
|
||||
@@ -37,7 +37,8 @@ class OffscreenBuffer {
|
||||
public:
|
||||
OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
|
||||
uint32_t viewportWidth, uint32_t viewportHeight);
|
||||
|
||||
uint32_t viewportWidth;
|
||||
uint32_t viewportHeight;
|
||||
Texture texture;
|
||||
Rect texCoords;
|
||||
Region region;
|
||||
@@ -60,12 +61,16 @@ public:
|
||||
, mOpaque(opaque) {
|
||||
}
|
||||
|
||||
static OffscreenBuffer* createOffscreenBuffer(uint32_t width, uint32_t height);
|
||||
static void destroyOffscreenBuffer(OffscreenBuffer*);
|
||||
|
||||
RenderState& renderState() { return mRenderState; }
|
||||
Caches& caches() { return mCaches; }
|
||||
|
||||
void startFrame(uint32_t width, uint32_t height);
|
||||
void endFrame();
|
||||
OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
|
||||
OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
|
||||
void startLayer(OffscreenBuffer* offscreenBuffer);
|
||||
void endLayer();
|
||||
|
||||
Texture* getTexture(const SkBitmap* bitmap);
|
||||
|
||||
@@ -154,7 +154,11 @@ public:
|
||||
return allocator.usedSize();
|
||||
}
|
||||
bool isEmpty() {
|
||||
#if HWUI_NEW_OPS
|
||||
return ops.empty();
|
||||
#else
|
||||
return !hasDrawOps;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -179,7 +183,7 @@ private:
|
||||
// List of functors
|
||||
LsaVector<Functor*> functors;
|
||||
|
||||
bool hasDrawOps;
|
||||
bool hasDrawOps; // only used if !HWUI_NEW_OPS
|
||||
|
||||
void cleanupResources();
|
||||
};
|
||||
|
||||
42
libs/hwui/LayerUpdateQueue.cpp
Normal file
42
libs/hwui/LayerUpdateQueue.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 "LayerUpdateQueue.h"
|
||||
|
||||
#include "RenderNode.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
void LayerUpdateQueue::clear() {
|
||||
mEntries.clear();
|
||||
}
|
||||
|
||||
void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
|
||||
damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
|
||||
if (!damage.isEmpty()) {
|
||||
for (Entry& entry : mEntries) {
|
||||
if (CC_UNLIKELY(entry.renderNode == renderNode)) {
|
||||
entry.damage.unionWith(damage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mEntries.emplace_back(renderNode, damage);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
||||
53
libs/hwui/LayerUpdateQueue.h
Normal file
53
libs/hwui/LayerUpdateQueue.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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_LAYER_UPDATE_QUEUE_H
|
||||
#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
|
||||
|
||||
#include "Rect.h"
|
||||
#include "utils/Macros.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class RenderNode;
|
||||
|
||||
class LayerUpdateQueue {
|
||||
PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue);
|
||||
public:
|
||||
struct Entry {
|
||||
Entry(RenderNode* renderNode, const Rect& damage)
|
||||
: renderNode(renderNode)
|
||||
, damage(damage) {}
|
||||
RenderNode* renderNode;
|
||||
Rect damage;
|
||||
};
|
||||
|
||||
LayerUpdateQueue() {}
|
||||
void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty);
|
||||
void clear();
|
||||
const std::vector<Entry> entries() const { return mEntries; }
|
||||
private:
|
||||
std::vector<Entry> mEntries;
|
||||
};
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "utils/PaintUtils.h"
|
||||
#include "RenderNode.h"
|
||||
#include "LayerUpdateQueue.h"
|
||||
|
||||
#include "SkCanvas.h"
|
||||
#include "utils/Trace.h"
|
||||
@@ -202,6 +203,14 @@ private:
|
||||
Rect mClipRect;
|
||||
};
|
||||
|
||||
OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
|
||||
const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
|
||||
: width(width)
|
||||
, height(height)
|
||||
, offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
|
||||
, beginLayerOp(beginLayerOp)
|
||||
, renderNode(renderNode) {}
|
||||
|
||||
// iterate back toward target to see if anything drawn since should overlap the new op
|
||||
// if no target, merging ops still iterate to find similar batch to insert after
|
||||
void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
|
||||
@@ -288,33 +297,48 @@ void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatche
|
||||
}
|
||||
|
||||
void OpReorderer::LayerReorderer::dump() const {
|
||||
ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
|
||||
this, width, height, offscreenBuffer, beginLayerOp, renderNode);
|
||||
for (const BatchBase* batch : mBatches) {
|
||||
batch->dump();
|
||||
}
|
||||
}
|
||||
|
||||
OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
|
||||
OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
|
||||
uint32_t viewportWidth, uint32_t viewportHeight,
|
||||
const std::vector< sp<RenderNode> >& nodes)
|
||||
: mCanvasState(*this) {
|
||||
ATRACE_NAME("prepare drawing commands");
|
||||
|
||||
mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
|
||||
mLayerStack.push_back(0);
|
||||
mLayerStack.push_back(0);
|
||||
|
||||
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
|
||||
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
|
||||
Vector3());
|
||||
|
||||
// Render all layers to be updated, in order. Defer in reverse order, so that they'll be
|
||||
// updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
|
||||
for (int i = layers.entries().size() - 1; i >= 0; i--) {
|
||||
RenderNode* layerNode = layers.entries()[i].renderNode;
|
||||
const Rect& layerDamage = layers.entries()[i].damage;
|
||||
|
||||
saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
|
||||
mCanvasState.writableSnapshot()->setClip(
|
||||
layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
|
||||
|
||||
if (layerNode->getDisplayList()) {
|
||||
deferImpl(*(layerNode->getDisplayList()));
|
||||
}
|
||||
restoreForLayer();
|
||||
}
|
||||
|
||||
// Defer Fbo0
|
||||
for (const sp<RenderNode>& node : nodes) {
|
||||
if (node->nothingToDraw()) continue;
|
||||
|
||||
// TODO: dedupe this code with onRenderNode()
|
||||
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
|
||||
if (node->applyViewProperties(mCanvasState)) {
|
||||
// not rejected do ops...
|
||||
const DisplayList& displayList = node->getDisplayList();
|
||||
deferImpl(displayList);
|
||||
}
|
||||
mCanvasState.restore();
|
||||
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
|
||||
deferNodePropsAndOps(*node);
|
||||
mCanvasState.restoreToCount(count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +358,23 @@ void OpReorderer::onViewportInitialized() {}
|
||||
|
||||
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
|
||||
|
||||
void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
|
||||
if (node.applyViewProperties(mCanvasState)) {
|
||||
// not rejected so render
|
||||
if (node.getLayer()) {
|
||||
// HW layer
|
||||
LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
|
||||
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
|
||||
if (bakedOpState) {
|
||||
// Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
|
||||
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
|
||||
}
|
||||
} else {
|
||||
deferImpl(*(node.getDisplayList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
|
||||
*
|
||||
@@ -365,11 +406,9 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
|
||||
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
|
||||
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
|
||||
|
||||
// apply RenderProperties state
|
||||
if (op.renderNode->applyViewProperties(mCanvasState)) {
|
||||
// if node not rejected based on properties, do ops...
|
||||
deferImpl(op.renderNode->getDisplayList());
|
||||
}
|
||||
// then apply state from node properties, and defer ops
|
||||
deferNodePropsAndOps(*op.renderNode);
|
||||
|
||||
mCanvasState.restoreToCount(count);
|
||||
}
|
||||
|
||||
@@ -400,10 +439,8 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
|
||||
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
|
||||
}
|
||||
|
||||
// TODO: test rejection at defer time, where the bounds become empty
|
||||
void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
|
||||
const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
|
||||
const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
|
||||
void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
|
||||
const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
|
||||
|
||||
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
|
||||
mCanvasState.writableSnapshot()->transform->loadIdentity();
|
||||
@@ -412,18 +449,27 @@ void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
|
||||
|
||||
// create a new layer, and push its index on the stack
|
||||
mLayerStack.push_back(mLayerReorderers.size());
|
||||
mLayerReorderers.emplace_back(layerWidth, layerHeight);
|
||||
mLayerReorderers.back().beginLayerOp = &op;
|
||||
mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
|
||||
}
|
||||
|
||||
void OpReorderer::restoreForLayer() {
|
||||
// restore canvas, and pop finished layer off of the stack
|
||||
mCanvasState.restore();
|
||||
mLayerStack.pop_back();
|
||||
}
|
||||
|
||||
// TODO: test rejection at defer time, where the bounds become empty
|
||||
void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
|
||||
const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
|
||||
const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
|
||||
saveForLayer(layerWidth, layerHeight, &op, nullptr);
|
||||
}
|
||||
|
||||
void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
|
||||
mCanvasState.restore();
|
||||
|
||||
const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
|
||||
|
||||
// pop finished layer off of the stack
|
||||
int finishedLayerIndex = mLayerStack.back();
|
||||
mLayerStack.pop_back();
|
||||
|
||||
restoreForLayer();
|
||||
|
||||
// record the draw operation into the previous layer's list of draw commands
|
||||
// uses state from the associated beginLayerOp, since it has all the state needed for drawing
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace uirenderer {
|
||||
|
||||
class BakedOpState;
|
||||
class BatchBase;
|
||||
class LayerUpdateQueue;
|
||||
class MergingOpBatch;
|
||||
class OffscreenBuffer;
|
||||
class OpBatch;
|
||||
@@ -64,9 +65,14 @@ class OpReorderer : public CanvasStateClient {
|
||||
*/
|
||||
class LayerReorderer {
|
||||
public:
|
||||
// Create LayerReorderer for Fbo0
|
||||
LayerReorderer(uint32_t width, uint32_t height)
|
||||
: width(width)
|
||||
, height(height) {}
|
||||
: LayerReorderer(width, height, nullptr, nullptr) {};
|
||||
|
||||
// Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
|
||||
// saveLayer, renderNode is present for a HW layer.
|
||||
LayerReorderer(uint32_t width, uint32_t height,
|
||||
const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
|
||||
|
||||
// iterate back toward target to see if anything drawn since should overlap the new op
|
||||
// if no target, merging ops still iterate to find similar batch to insert after
|
||||
@@ -92,12 +98,12 @@ class OpReorderer : public CanvasStateClient {
|
||||
|
||||
void dump() const;
|
||||
|
||||
OffscreenBuffer* offscreenBuffer = nullptr;
|
||||
const BeginLayerOp* beginLayerOp = nullptr;
|
||||
const uint32_t width;
|
||||
const uint32_t height;
|
||||
OffscreenBuffer* offscreenBuffer;
|
||||
const BeginLayerOp* beginLayerOp;
|
||||
const RenderNode* renderNode;
|
||||
private:
|
||||
|
||||
std::vector<BatchBase*> mBatches;
|
||||
|
||||
/**
|
||||
@@ -112,8 +118,8 @@ class OpReorderer : public CanvasStateClient {
|
||||
|
||||
};
|
||||
public:
|
||||
// TODO: not final, just presented this way for simplicity. Layers too?
|
||||
OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
|
||||
OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
|
||||
uint32_t viewportWidth, uint32_t viewportHeight,
|
||||
const std::vector< sp<RenderNode> >& nodes);
|
||||
|
||||
OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
|
||||
@@ -144,8 +150,13 @@ public:
|
||||
// later in the list will be drawn by earlier ones
|
||||
for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
|
||||
LayerReorderer& layer = mLayerReorderers[i];
|
||||
if (!layer.empty()) {
|
||||
layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
|
||||
if (layer.renderNode) {
|
||||
// cached HW layer - can't skip layer if empty
|
||||
renderer.startLayer(layer.offscreenBuffer);
|
||||
layer.replayBakedOpsImpl((void*)&renderer, receivers);
|
||||
renderer.endLayer();
|
||||
} else if (!layer.empty()) { // save layer - skip entire layer if empty
|
||||
layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
|
||||
layer.replayBakedOpsImpl((void*)&renderer, receivers);
|
||||
renderer.endLayer();
|
||||
}
|
||||
@@ -171,12 +182,19 @@ public:
|
||||
virtual GLuint getTargetFbo() const override { return 0; }
|
||||
|
||||
private:
|
||||
void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
|
||||
const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
|
||||
void restoreForLayer();
|
||||
|
||||
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
|
||||
|
||||
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
|
||||
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
|
||||
}
|
||||
|
||||
// should always be surrounded by a save/restore pair
|
||||
void deferNodePropsAndOps(RenderNode& node);
|
||||
|
||||
void deferImpl(const DisplayList& displayList);
|
||||
|
||||
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "utils/LinearAllocator.h"
|
||||
#include "Rect.h"
|
||||
#include "Matrix.h"
|
||||
#include "RenderNode.h"
|
||||
|
||||
#include "SkXfermode.h"
|
||||
|
||||
@@ -136,13 +137,42 @@ struct EndLayerOp : RecordedOp {
|
||||
: RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an OffscreenBuffer.
|
||||
*
|
||||
* Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated,
|
||||
* 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.
|
||||
LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
|
||||
: SUPER(LayerOp)
|
||||
, layerHandle(layerHandle) {}
|
||||
: SUPER_PAINTLESS(LayerOp)
|
||||
, layerHandle(layerHandle)
|
||||
, alpha(paint->getAlpha() / 255.0f)
|
||||
, mode(PaintUtils::getXfermodeDirect(paint))
|
||||
, colorFilter(paint->getColorFilter())
|
||||
, destroy(true) {}
|
||||
|
||||
LayerOp(RenderNode& node)
|
||||
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
|
||||
, layerHandle(node.getLayerHandle())
|
||||
, alpha(node.properties().layerProperties().alpha() / 255.0f)
|
||||
, mode(node.properties().layerProperties().xferMode())
|
||||
, colorFilter(node.properties().layerProperties().colorFilter())
|
||||
, destroy(false) {}
|
||||
|
||||
// Records a handle to the Layer object, since the Layer itself won't be
|
||||
// constructed until after this operation is constructed.
|
||||
OffscreenBuffer** layerHandle;
|
||||
const float alpha;
|
||||
const SkXfermode::Mode mode;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -77,7 +77,6 @@ SkCanvas* RecordingCanvas::asSkCanvas() {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void RecordingCanvas::onViewportInitialized() {
|
||||
|
||||
}
|
||||
|
||||
void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
#include "SkiaCanvasProxy.h"
|
||||
#include "Snapshot.h"
|
||||
|
||||
#include "SkDrawFilter.h"
|
||||
#include "SkPaint.h"
|
||||
#include "SkTLazy.h"
|
||||
#include <SkDrawFilter.h>
|
||||
#include <SkPaint.h>
|
||||
#include <SkTLazy.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Debug.h"
|
||||
#if HWUI_NEW_OPS
|
||||
#include "RecordedOp.h"
|
||||
#include "BakedOpRenderer.h"
|
||||
#endif
|
||||
#include "DisplayListOp.h"
|
||||
#include "LayerRenderer.h"
|
||||
@@ -42,11 +43,15 @@ namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
void RenderNode::debugDumpLayers(const char* prefix) {
|
||||
#if HWUI_NEW_OPS
|
||||
LOG_ALWAYS_FATAL("TODO: dump layer");
|
||||
#else
|
||||
if (mLayer) {
|
||||
ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)",
|
||||
prefix, this, getName(), mLayer, mLayer->getFbo(),
|
||||
mLayer->wasBuildLayered ? "true" : "false");
|
||||
}
|
||||
#endif
|
||||
if (mDisplayList) {
|
||||
for (auto&& child : mDisplayList->getChildren()) {
|
||||
child->renderNode->debugDumpLayers(prefix);
|
||||
@@ -60,18 +65,21 @@ RenderNode::RenderNode()
|
||||
, mDisplayList(nullptr)
|
||||
, mStagingDisplayList(nullptr)
|
||||
, mAnimatorManager(*this)
|
||||
, mLayer(nullptr)
|
||||
, mParentCount(0) {
|
||||
}
|
||||
|
||||
RenderNode::~RenderNode() {
|
||||
deleteDisplayList();
|
||||
delete mStagingDisplayList;
|
||||
#if HWUI_NEW_OPS
|
||||
LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
|
||||
#else
|
||||
if (mLayer) {
|
||||
ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
|
||||
mLayer->postDecStrong();
|
||||
mLayer = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderNode::setStagingDisplayList(DisplayList* displayList) {
|
||||
@@ -240,13 +248,29 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
|
||||
}
|
||||
}
|
||||
|
||||
layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
|
||||
#if HWUI_NEW_OPS
|
||||
return BakedOpRenderer::createOffscreenBuffer(width, height);
|
||||
#else
|
||||
return LayerRenderer::createRenderLayer(renderState, width, height);
|
||||
#endif
|
||||
}
|
||||
|
||||
void destroyLayer(layer_t* layer) {
|
||||
#if HWUI_NEW_OPS
|
||||
BakedOpRenderer::destroyOffscreenBuffer(layer);
|
||||
#else
|
||||
LayerRenderer::destroyLayer(layer);
|
||||
#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)
|
||||
// we need to destroy any Layers we may have had previously
|
||||
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
|
||||
if (CC_UNLIKELY(mLayer)) {
|
||||
LayerRenderer::destroyLayer(mLayer);
|
||||
destroyLayer(mLayer);
|
||||
mLayer = nullptr;
|
||||
}
|
||||
return;
|
||||
@@ -254,14 +278,18 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
|
||||
|
||||
bool transformUpdateNeeded = false;
|
||||
if (!mLayer) {
|
||||
mLayer = LayerRenderer::createRenderLayer(
|
||||
info.canvasContext.getRenderState(), getWidth(), getHeight());
|
||||
applyLayerPropertiesToLayer(info);
|
||||
damageSelf(info);
|
||||
transformUpdateNeeded = true;
|
||||
mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
|
||||
damageSelf(info);
|
||||
transformUpdateNeeded = true;
|
||||
#if HWUI_NEW_OPS
|
||||
} else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) {
|
||||
// TODO: allow it to grow larger
|
||||
if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) {
|
||||
#else
|
||||
} else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
|
||||
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
|
||||
LayerRenderer::destroyLayer(mLayer);
|
||||
#endif
|
||||
destroyLayer(mLayer);
|
||||
mLayer = nullptr;
|
||||
}
|
||||
damageSelf(info);
|
||||
@@ -276,7 +304,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
|
||||
if (info.errorHandler) {
|
||||
std::ostringstream err;
|
||||
err << "Unable to create layer for " << getName();
|
||||
const int maxTextureSize = Caches::getInstance().maxTextureSize;
|
||||
const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize;
|
||||
if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
|
||||
err << ", size " << getWidth() << "x" << getHeight()
|
||||
<< " exceeds max size " << maxTextureSize;
|
||||
@@ -292,9 +320,16 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
|
||||
// update the transform in window of the layer to reset its origin wrt light source position
|
||||
Matrix4 windowTransform;
|
||||
info.damageAccumulator->computeCurrentTransform(&windowTransform);
|
||||
#if HWUI_NEW_OPS
|
||||
// TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
|
||||
#else
|
||||
mLayer->setWindowTransform(windowTransform);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
|
||||
#else
|
||||
if (dirty.intersect(0, 0, getWidth(), getHeight())) {
|
||||
dirty.roundOut(&dirty);
|
||||
mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
|
||||
@@ -304,6 +339,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
|
||||
if (info.renderer && mLayer->deferredUpdateScheduled) {
|
||||
info.renderer->pushLayerUpdate(mLayer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// There might be prefetched layers that need to be accounted for.
|
||||
// That might be us, so tell CanvasContext that this layer is in the
|
||||
@@ -365,7 +401,9 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
damageSelf(info);
|
||||
info.damageAccumulator->popTransform();
|
||||
syncProperties();
|
||||
#if !HWUI_NEW_OPS
|
||||
applyLayerPropertiesToLayer(info);
|
||||
#endif
|
||||
// We could try to be clever and only re-damage if the matrix changed.
|
||||
// However, we don't need to worry about that. The cost of over-damaging
|
||||
// here is only going to be a single additional map rect of this node
|
||||
@@ -376,6 +414,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
}
|
||||
}
|
||||
|
||||
#if !HWUI_NEW_OPS
|
||||
void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
|
||||
if (CC_LIKELY(!mLayer)) return;
|
||||
|
||||
@@ -384,6 +423,7 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
|
||||
mLayer->setColorFilter(props.colorFilter());
|
||||
mLayer->setBlend(props.needsBlending());
|
||||
}
|
||||
#endif
|
||||
|
||||
void RenderNode::syncDisplayList() {
|
||||
// Make sure we inc first so that we don't fluctuate between 0 and 1,
|
||||
@@ -451,7 +491,7 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
|
||||
|
||||
void RenderNode::destroyHardwareResources() {
|
||||
if (mLayer) {
|
||||
LayerRenderer::destroyLayer(mLayer);
|
||||
destroyLayer(mLayer);
|
||||
mLayer = nullptr;
|
||||
}
|
||||
if (mDisplayList) {
|
||||
@@ -978,7 +1018,11 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
const bool drawLayer = false;
|
||||
#else
|
||||
const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
|
||||
#endif
|
||||
// If we are updating the contents of mLayer, we don't want to apply any of
|
||||
// the RenderNode's properties to this issueOperations pass. Those will all
|
||||
// be applied when the layer is drawn, aka when this is true.
|
||||
|
||||
@@ -44,13 +44,22 @@ namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class CanvasState;
|
||||
class DisplayListOp;
|
||||
class DisplayListCanvas;
|
||||
class DisplayListOp;
|
||||
class OpenGLRenderer;
|
||||
class OpReorderer;
|
||||
class Rect;
|
||||
class Layer;
|
||||
class SkiaShader;
|
||||
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
class OffscreenBuffer;
|
||||
typedef OffscreenBuffer layer_t;
|
||||
#else
|
||||
class Layer;
|
||||
typedef Layer layer_t;
|
||||
#endif
|
||||
|
||||
class ClipRectOp;
|
||||
class SaveLayerOp;
|
||||
class SaveOp;
|
||||
@@ -162,11 +171,11 @@ public:
|
||||
return mStagingProperties;
|
||||
}
|
||||
|
||||
int getWidth() {
|
||||
uint32_t getWidth() {
|
||||
return properties().getWidth();
|
||||
}
|
||||
|
||||
int getHeight() {
|
||||
uint32_t getHeight() {
|
||||
return properties().getHeight();
|
||||
}
|
||||
|
||||
@@ -193,9 +202,13 @@ public:
|
||||
}
|
||||
|
||||
// Only call if RenderNode has DisplayList...
|
||||
const DisplayList& getDisplayList() const {
|
||||
return *mDisplayList;
|
||||
const DisplayList* getDisplayList() const {
|
||||
return mDisplayList;
|
||||
}
|
||||
#if HWUI_NEW_OPS
|
||||
OffscreenBuffer* getLayer() const { return mLayer; }
|
||||
OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
|
||||
#endif
|
||||
|
||||
private:
|
||||
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
|
||||
@@ -262,7 +275,9 @@ private:
|
||||
void pushStagingPropertiesChanges(TreeInfo& info);
|
||||
void pushStagingDisplayListChanges(TreeInfo& info);
|
||||
void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
|
||||
#if !HWUI_NEW_OPS
|
||||
void applyLayerPropertiesToLayer(TreeInfo& info);
|
||||
#endif
|
||||
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
|
||||
void pushLayerUpdate(TreeInfo& info);
|
||||
void deleteDisplayList();
|
||||
@@ -287,7 +302,7 @@ private:
|
||||
|
||||
// Owned by RT. Lifecycle is managed by prepareTree(), with the exception
|
||||
// being in ~RenderNode() which may happen on any thread.
|
||||
Layer* mLayer;
|
||||
layer_t* mLayer = nullptr;
|
||||
|
||||
/**
|
||||
* Draw time state - these properties are only set and used during rendering
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
#ifndef TREEINFO_H
|
||||
#define TREEINFO_H
|
||||
|
||||
#include <string>
|
||||
#include "utils/Macros.h"
|
||||
|
||||
#include <utils/Timers.h>
|
||||
|
||||
#include "utils/Macros.h"
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
@@ -30,6 +30,7 @@ class CanvasContext;
|
||||
}
|
||||
|
||||
class DamageAccumulator;
|
||||
class LayerUpdateQueue;
|
||||
class OpenGLRenderer;
|
||||
class RenderState;
|
||||
|
||||
@@ -75,9 +76,14 @@ public:
|
||||
|
||||
// Must not be null during actual usage
|
||||
DamageAccumulator* damageAccumulator = nullptr;
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
LayerUpdateQueue* layerUpdateQueue = nullptr;
|
||||
#else
|
||||
// The renderer that will be drawing the next frame. Use this to push any
|
||||
// layer updates or similar. May be NULL.
|
||||
OpenGLRenderer* renderer = nullptr;
|
||||
#endif
|
||||
ErrorHandler* errorHandler = nullptr;
|
||||
|
||||
struct Out {
|
||||
|
||||
@@ -56,7 +56,9 @@ void BM_OpReorderer_defer::Run(int iters) {
|
||||
|
||||
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
|
||||
void BM_OpReorderer_deferAndRender::Run(int iters) {
|
||||
TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
|
||||
TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
|
||||
RenderState& renderState = thread.renderState();
|
||||
Caches& caches = Caches::getInstance();
|
||||
StartBenchmarkTiming();
|
||||
for (int i = 0; i < iters; i++) {
|
||||
OpReorderer reorderer(200, 200, *sReorderingDisplayList);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Caches.h"
|
||||
#include "DeferredLayerUpdater.h"
|
||||
#include "EglManager.h"
|
||||
#include "LayerUpdateQueue.h"
|
||||
#include "LayerRenderer.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "Properties.h"
|
||||
@@ -198,7 +199,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
|
||||
mCurrentFrameInfo->markSyncStart();
|
||||
|
||||
info.damageAccumulator = &mDamageAccumulator;
|
||||
#if HWUI_NEW_OPS
|
||||
info.layerUpdateQueue = &mLayerUpdateQueue;
|
||||
#else
|
||||
info.renderer = mCanvas;
|
||||
#endif
|
||||
|
||||
mAnimationContext->startFrame(info.mode);
|
||||
for (const sp<RenderNode>& node : mRenderNodes) {
|
||||
@@ -333,7 +338,8 @@ void CanvasContext::draw() {
|
||||
mEglManager.damageFrame(frame, dirty);
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
|
||||
OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
|
||||
mLayerUpdateQueue.clear();
|
||||
BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
|
||||
// TODO: profiler().draw(mCanvas);
|
||||
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
|
||||
@@ -552,7 +558,11 @@ void CanvasContext::buildLayer(RenderNode* node) {
|
||||
|
||||
TreeInfo info(TreeInfo::MODE_FULL, *this);
|
||||
info.damageAccumulator = &mDamageAccumulator;
|
||||
#if HWUI_NEW_OPS
|
||||
info.layerUpdateQueue = &mLayerUpdateQueue;
|
||||
#else
|
||||
info.renderer = mCanvas;
|
||||
#endif
|
||||
info.runAnimations = false;
|
||||
node->prepareTree(info);
|
||||
SkRect ignore;
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
#define CANVASCONTEXT_H_
|
||||
|
||||
#include "DamageAccumulator.h"
|
||||
#include "IContextFactory.h"
|
||||
#include "FrameInfo.h"
|
||||
#include "FrameInfoVisualizer.h"
|
||||
#include "IContextFactory.h"
|
||||
#include "LayerUpdateQueue.h"
|
||||
#include "RenderNode.h"
|
||||
#include "utils/RingBuffer.h"
|
||||
#include "renderthread/RenderTask.h"
|
||||
@@ -83,7 +84,7 @@ public:
|
||||
void draw();
|
||||
void destroy();
|
||||
|
||||
// IFrameCallback, Chroreographer-driven frame callback entry point
|
||||
// IFrameCallback, Choreographer-driven frame callback entry point
|
||||
virtual void doFrame() override;
|
||||
void prepareAndDraw(RenderNode* node);
|
||||
|
||||
@@ -118,7 +119,7 @@ public:
|
||||
|
||||
void addRenderNode(RenderNode* node, bool placeFront) {
|
||||
int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
|
||||
mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
|
||||
mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
|
||||
}
|
||||
|
||||
void removeRenderNode(RenderNode* node) {
|
||||
@@ -166,6 +167,7 @@ private:
|
||||
OpenGLRenderer* mCanvas = nullptr;
|
||||
bool mHaveNewSurface = false;
|
||||
DamageAccumulator mDamageAccumulator;
|
||||
LayerUpdateQueue mLayerUpdateQueue;
|
||||
std::unique_ptr<AnimationContext> mAnimationContext;
|
||||
|
||||
std::vector< sp<RenderNode> > mRenderNodes;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <RenderNode.h>
|
||||
#include <renderthread/RenderProxy.h>
|
||||
#include <renderthread/RenderTask.h>
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
#include "Benchmark.h"
|
||||
#include "TestContext.h"
|
||||
@@ -401,3 +402,27 @@ static Benchmark _SaveLayer(BenchmarkInfo{
|
||||
"Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
|
||||
TreeContentAnimation::run<SaveLayerAnimation>
|
||||
});
|
||||
|
||||
|
||||
class HwLayerAnimation : public TreeContentAnimation {
|
||||
public:
|
||||
sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
|
||||
canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
|
||||
}, true);
|
||||
void createContent(int width, int height, TestCanvas* canvas) override {
|
||||
canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
|
||||
canvas->drawRenderNode(card.get());
|
||||
}
|
||||
void doFrame(int frameNr) override {
|
||||
int curFrame = frameNr % 150;
|
||||
card->mutateStagingProperties().setTranslationX(curFrame);
|
||||
card->mutateStagingProperties().setTranslationY(curFrame);
|
||||
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
}
|
||||
};
|
||||
static Benchmark _HwLayer(BenchmarkInfo{
|
||||
"hwlayer",
|
||||
"A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
|
||||
"Tests the hardware layer codepath.",
|
||||
TreeContentAnimation::run<HwLayerAnimation>
|
||||
});
|
||||
|
||||
82
libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
Normal file
82
libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 <LayerUpdateQueue.h>
|
||||
#include <RenderNode.h>
|
||||
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
TEST(LayerUpdateQueue, construct) {
|
||||
LayerUpdateQueue queue;
|
||||
EXPECT_TRUE(queue.entries().empty());
|
||||
}
|
||||
|
||||
// sync node properties, so properties() reflects correct width and height
|
||||
static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
|
||||
sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
|
||||
TestUtils::syncNodePropertiesAndDisplayList(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
TEST(LayerUpdateQueue, enqueueSimple) {
|
||||
sp<RenderNode> a = createSyncedNode(100, 100);
|
||||
sp<RenderNode> b = createSyncedNode(200, 200);
|
||||
|
||||
LayerUpdateQueue queue;
|
||||
queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75));
|
||||
queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300));
|
||||
|
||||
EXPECT_EQ(2u, queue.entries().size());
|
||||
|
||||
EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
|
||||
EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
|
||||
EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
|
||||
EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
|
||||
}
|
||||
|
||||
TEST(LayerUpdateQueue, enqueueUnion) {
|
||||
sp<RenderNode> a = createSyncedNode(100, 100);
|
||||
|
||||
LayerUpdateQueue queue;
|
||||
queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20));
|
||||
queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40));
|
||||
|
||||
EXPECT_EQ(1u, queue.entries().size());
|
||||
|
||||
EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
|
||||
EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
|
||||
}
|
||||
|
||||
TEST(LayerUpdateQueue, clear) {
|
||||
sp<RenderNode> a = createSyncedNode(100, 100);
|
||||
|
||||
LayerUpdateQueue queue;
|
||||
queue.enqueueLayerWithDamage(a.get(), Rect(100, 100));
|
||||
|
||||
EXPECT_FALSE(queue.entries().empty());
|
||||
|
||||
queue.clear();
|
||||
|
||||
EXPECT_TRUE(queue.entries().empty());
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <OpReorderer.h>
|
||||
#include <RecordedOp.h>
|
||||
#include <RecordingCanvas.h>
|
||||
#include <renderthread/CanvasContext.h> // todo: remove
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
#include <unordered_map>
|
||||
@@ -27,6 +28,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
LayerUpdateQueue sEmptyLayerUpdateQueue;
|
||||
|
||||
/**
|
||||
* Virtual class implemented by each test to redirect static operation / state transitions to
|
||||
@@ -42,7 +44,8 @@ namespace uirenderer {
|
||||
class TestRendererBase {
|
||||
public:
|
||||
virtual ~TestRendererBase() {}
|
||||
virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
|
||||
virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) { ADD_FAILURE(); return nullptr; }
|
||||
virtual void startLayer(OffscreenBuffer*) { ADD_FAILURE(); }
|
||||
virtual void endLayer() { ADD_FAILURE(); }
|
||||
virtual void startFrame(uint32_t width, uint32_t height) {}
|
||||
virtual void endFrame() {}
|
||||
@@ -192,7 +195,8 @@ TEST(OpReorderer, renderNode) {
|
||||
std::vector< sp<RenderNode> > nodes;
|
||||
nodes.push_back(parent.get());
|
||||
|
||||
OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
|
||||
OpReorderer reorderer(sEmptyLayerUpdateQueue,
|
||||
SkRect::MakeWH(200, 200), 200, 200, nodes);
|
||||
|
||||
RenderNodeTestRenderer renderer;
|
||||
reorderer.replayBakedOps<TestDispatcher>(renderer);
|
||||
@@ -216,7 +220,8 @@ TEST(OpReorderer, clipped) {
|
||||
std::vector< sp<RenderNode> > nodes;
|
||||
nodes.push_back(node.get());
|
||||
|
||||
OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
|
||||
OpReorderer reorderer(sEmptyLayerUpdateQueue,
|
||||
SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
|
||||
200, 200, nodes);
|
||||
|
||||
ClippedTestRenderer renderer;
|
||||
@@ -226,7 +231,7 @@ TEST(OpReorderer, clipped) {
|
||||
|
||||
class SaveLayerSimpleTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
|
||||
OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
EXPECT_EQ(180u, width);
|
||||
EXPECT_EQ(180u, height);
|
||||
@@ -268,13 +273,13 @@ TEST(OpReorderer, saveLayerSimple) {
|
||||
|
||||
|
||||
/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
|
||||
* - startLayer2, rect2 endLayer2
|
||||
* - startLayer1, rect1, drawLayer2, endLayer1
|
||||
* - createLayer2, rect2 endLayer2
|
||||
* - createLayer1, rect1, drawLayer2, endLayer1
|
||||
* - startFrame, layerOp1, endFrame
|
||||
*/
|
||||
class SaveLayerNestedTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
|
||||
OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
|
||||
const int index = mIndex++;
|
||||
if (index == 0) {
|
||||
EXPECT_EQ(400u, width);
|
||||
@@ -356,5 +361,162 @@ TEST(OpReorderer, saveLayerContentRejection) {
|
||||
reorderer.replayBakedOps<TestDispatcher>(renderer);
|
||||
}
|
||||
|
||||
class HwLayerSimpleTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
void startLayer(OffscreenBuffer* offscreenBuffer) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
|
||||
}
|
||||
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(1, mIndex++);
|
||||
|
||||
// verify transform is reset
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||
|
||||
// verify damage rect is used as clip
|
||||
EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75));
|
||||
}
|
||||
void endLayer() override {
|
||||
EXPECT_EQ(2, mIndex++);
|
||||
}
|
||||
void startFrame(uint32_t width, uint32_t height) override {
|
||||
EXPECT_EQ(3, mIndex++);
|
||||
}
|
||||
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(4, mIndex++);
|
||||
}
|
||||
void endFrame() override {
|
||||
EXPECT_EQ(5, mIndex++);
|
||||
}
|
||||
};
|
||||
TEST(OpReorderer, hwLayerSimple) {
|
||||
sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorWHITE);
|
||||
canvas.drawRect(0, 0, 100, 100, paint);
|
||||
});
|
||||
node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||
node->setPropertyFieldsDirty(RenderNode::GENERIC);
|
||||
OffscreenBuffer** bufferHandle = node->getLayerHandle();
|
||||
*bufferHandle = (OffscreenBuffer*) 0x0124;
|
||||
|
||||
TestUtils::syncNodePropertiesAndDisplayList(node);
|
||||
|
||||
std::vector< sp<RenderNode> > nodes;
|
||||
nodes.push_back(node.get());
|
||||
|
||||
// only enqueue partial damage
|
||||
LayerUpdateQueue layerUpdateQueue;
|
||||
layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
|
||||
|
||||
OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
|
||||
|
||||
HwLayerSimpleTestRenderer renderer;
|
||||
reorderer.replayBakedOps<TestDispatcher>(renderer);
|
||||
EXPECT_EQ(6, renderer.getIndex());
|
||||
|
||||
// clean up layer pointer, so we can safely destruct RenderNode
|
||||
*bufferHandle = nullptr;
|
||||
}
|
||||
|
||||
|
||||
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
|
||||
* - startLayer(child), rect(grey), endLayer
|
||||
* - createLayer, drawLayer(child), endLayer
|
||||
* - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
|
||||
* - startFrame, drawLayer(parent), endLayerb
|
||||
*/
|
||||
class HwLayerComplexTestRenderer : public TestRendererBase {
|
||||
public:
|
||||
OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
|
||||
EXPECT_EQ(3, mIndex++); // savelayer first
|
||||
return (OffscreenBuffer*)0xabcd;
|
||||
}
|
||||
void startLayer(OffscreenBuffer* offscreenBuffer) override {
|
||||
int index = mIndex++;
|
||||
if (index == 0) {
|
||||
// starting inner layer
|
||||
EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
|
||||
} else if (index == 6) {
|
||||
// starting outer layer
|
||||
EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
|
||||
} else { ADD_FAILURE(); }
|
||||
}
|
||||
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||
int index = mIndex++;
|
||||
if (index == 1) {
|
||||
// inner layer's rect (white)
|
||||
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
|
||||
} else if (index == 7) {
|
||||
// outer layer's rect (grey)
|
||||
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
|
||||
} else { ADD_FAILURE(); }
|
||||
}
|
||||
void endLayer() override {
|
||||
int index = mIndex++;
|
||||
EXPECT_TRUE(index == 2 || index == 5 || index == 9);
|
||||
}
|
||||
void startFrame(uint32_t width, uint32_t height) override {
|
||||
EXPECT_EQ(10, mIndex++);
|
||||
}
|
||||
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||
int index = mIndex++;
|
||||
if (index == 4) {
|
||||
EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
|
||||
} else if (index == 8) {
|
||||
EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
|
||||
} else if (index == 11) {
|
||||
EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
|
||||
} else { ADD_FAILURE(); }
|
||||
}
|
||||
void endFrame() override {
|
||||
EXPECT_EQ(12, mIndex++);
|
||||
}
|
||||
};
|
||||
TEST(OpReorderer, hwLayerComplex) {
|
||||
sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) {
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorWHITE);
|
||||
canvas.drawRect(0, 0, 100, 100, paint);
|
||||
});
|
||||
child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||
child->setPropertyFieldsDirty(RenderNode::GENERIC);
|
||||
*(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
|
||||
|
||||
RenderNode* childPtr = child.get();
|
||||
sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorDKGRAY);
|
||||
canvas.drawRect(0, 0, 200, 200, paint);
|
||||
|
||||
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
|
||||
canvas.drawRenderNode(childPtr);
|
||||
canvas.restore();
|
||||
});
|
||||
parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||
parent->setPropertyFieldsDirty(RenderNode::GENERIC);
|
||||
*(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
|
||||
|
||||
TestUtils::syncNodePropertiesAndDisplayList(child);
|
||||
TestUtils::syncNodePropertiesAndDisplayList(parent);
|
||||
|
||||
std::vector< sp<RenderNode> > nodes;
|
||||
nodes.push_back(parent.get());
|
||||
|
||||
LayerUpdateQueue layerUpdateQueue;
|
||||
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
|
||||
layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
|
||||
|
||||
OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
|
||||
|
||||
HwLayerComplexTestRenderer renderer;
|
||||
reorderer.replayBakedOps<TestDispatcher>(renderer);
|
||||
EXPECT_EQ(13, renderer.getIndex());
|
||||
|
||||
// clean up layer pointers, so we can safely destruct RenderNodes
|
||||
*(child->getLayerHandle()) = nullptr;
|
||||
*(parent->getLayerHandle()) = nullptr;
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
||||
|
||||
@@ -89,15 +89,24 @@ public:
|
||||
return std::unique_ptr<DisplayList>(canvas.finishRecording());
|
||||
}
|
||||
|
||||
template<class CanvasType>
|
||||
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
|
||||
std::function<void(CanvasType& canvas)> canvasCallback) {
|
||||
static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
|
||||
sp<RenderNode> node = new RenderNode();
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
if (onLayer) {
|
||||
node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||
node->setPropertyFieldsDirty(RenderNode::GENERIC);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
CanvasType canvas(
|
||||
node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
|
||||
template<class CanvasType>
|
||||
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
|
||||
std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
|
||||
sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
|
||||
|
||||
auto&& props = node->stagingProperties(); // staging, since not sync'd yet
|
||||
CanvasType canvas(props.getWidth(), props.getHeight());
|
||||
canvasCallback(canvas);
|
||||
node->setStagingDisplayList(canvas.finishRecording());
|
||||
return node;
|
||||
@@ -108,7 +117,7 @@ public:
|
||||
node->syncDisplayList();
|
||||
}
|
||||
|
||||
typedef std::function<void(RenderState& state, Caches& caches)> RtCallback;
|
||||
typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
|
||||
|
||||
class TestTask : public renderthread::RenderTask {
|
||||
public:
|
||||
@@ -120,7 +129,7 @@ public:
|
||||
RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
|
||||
|
||||
renderState.onGLContextCreated();
|
||||
rtCallback(renderState, Caches::getInstance());
|
||||
rtCallback(renderthread::RenderThread::getInstance());
|
||||
renderState.onGLContextDestroyed();
|
||||
};
|
||||
RtCallback rtCallback;
|
||||
|
||||
Reference in New Issue
Block a user