Initial commit of new Canvas operation recording / replay
Done: - drawRect, drawBitmap, drawColor, drawPaint, drawRenderNode, drawRegion - Recording with new DisplayList format - batching & reordering - Stateless op reorder - Stateless op rendering - Frame lifecycle (clear, geterror, cleanup) Not done: - SaveLayer (clipped and unclipped) - HW layers - Complex clipping - Ripple projection - Z reordering - Z shadows - onDefer prefetching (text + task kickoff) - round rect clip - linear allocation for std collections - AssetAtlas support Change-Id: Iaf98c1a3aeab5fa47cc8f9c6d964420abc0e7691
This commit is contained in:
@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
|
||||
|
||||
HWUI_NEW_OPS := false
|
||||
|
||||
hwui_src_files := \
|
||||
font/CacheTexture.cpp \
|
||||
font/Font.cpp \
|
||||
@ -85,6 +87,17 @@ hwui_cflags := \
|
||||
-Wall -Wno-unused-parameter -Wunreachable-code \
|
||||
-ffast-math -O3 -Werror
|
||||
|
||||
|
||||
ifeq (true, $(HWUI_NEW_OPS))
|
||||
hwui_src_files += \
|
||||
BakedOpRenderer.cpp \
|
||||
OpReorderer.cpp \
|
||||
RecordingCanvas.cpp
|
||||
|
||||
hwui_cflags += -DHWUI_NEW_OPS
|
||||
|
||||
endif
|
||||
|
||||
ifndef HWUI_COMPILE_SYMBOLS
|
||||
hwui_cflags += -fvisibility=hidden
|
||||
endif
|
||||
@ -172,6 +185,13 @@ LOCAL_SRC_FILES += \
|
||||
unit_tests/LinearAllocatorTests.cpp \
|
||||
unit_tests/StringUtilsTests.cpp
|
||||
|
||||
ifeq (true, $(HWUI_NEW_OPS))
|
||||
LOCAL_SRC_FILES += \
|
||||
unit_tests/BakedOpStateTests.cpp \
|
||||
unit_tests/RecordingCanvasTests.cpp \
|
||||
unit_tests/OpReordererTests.cpp
|
||||
endif
|
||||
|
||||
include $(BUILD_NATIVE_TEST)
|
||||
|
||||
# ------------------------
|
||||
|
126
libs/hwui/BakedOpRenderer.cpp
Normal file
126
libs/hwui/BakedOpRenderer.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 "BakedOpRenderer.h"
|
||||
|
||||
#include "Caches.h"
|
||||
#include "Glop.h"
|
||||
#include "GlopBuilder.h"
|
||||
#include "renderstate/RenderState.h"
|
||||
#include "utils/GLUtils.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
|
||||
Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
|
||||
if (!texture) {
|
||||
return caches.textureCache.get(bitmap);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
|
||||
bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
|
||||
renderState.scissor().setEnabled(useScissor);
|
||||
if (useScissor) {
|
||||
const Rect& clip = state.computedState.clipRect;
|
||||
renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
|
||||
clip.getWidth(), clip.getHeight());
|
||||
}
|
||||
renderState.render(glop, orthoMatrix);
|
||||
didDraw = true;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::startFrame(Info& info) {
|
||||
info.renderState.setViewport(info.viewportWidth, info.viewportHeight);
|
||||
info.renderState.blend().syncEnabled();
|
||||
Caches::getInstance().clearGarbage();
|
||||
|
||||
if (!info.opaque) {
|
||||
// TODO: partial invalidate!
|
||||
info.renderState.scissor().setEnabled(false);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
info.didDraw = true;
|
||||
}
|
||||
}
|
||||
void BakedOpRenderer::endFrame(Info& info) {
|
||||
info.caches.pathCache.trim();
|
||||
info.caches.tessellationCache.trim();
|
||||
|
||||
#if DEBUG_OPENGL
|
||||
GLUtils::dumpGLErrors();
|
||||
#endif
|
||||
|
||||
#if DEBUG_MEMORY_USAGE
|
||||
info.caches.dumpMemoryUsage();
|
||||
#else
|
||||
if (Properties::debugLevel & kDebugMemory) {
|
||||
info.caches.dumpMemoryUsage();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) {
|
||||
LOG_ALWAYS_FATAL("unsupported operation");
|
||||
}
|
||||
|
||||
void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
|
||||
info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
|
||||
Texture* texture = info->getTexture(op.bitmap);
|
||||
if (!texture) return;
|
||||
const AutoTexture autoCleanup(texture);
|
||||
|
||||
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
|
||||
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
|
||||
Glop glop;
|
||||
GlopBuilder(info->renderState, info->caches, &glop)
|
||||
.setRoundRectClipState(state.roundRectClipState)
|
||||
.setMeshTexturedUnitQuad(texture->uvMapper)
|
||||
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
|
||||
.setTransform(state.computedState.transform, TransformFlags::None)
|
||||
.setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
|
||||
.build();
|
||||
info->renderGlop(state, glop);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
|
||||
Glop glop;
|
||||
GlopBuilder(info->renderState, info->caches, &glop)
|
||||
.setRoundRectClipState(state.roundRectClipState)
|
||||
.setMeshUnitQuad()
|
||||
.setFillPaint(*op.paint, state.alpha)
|
||||
.setTransform(state.computedState.transform, TransformFlags::None)
|
||||
.setModelViewMapUnitToRect(op.unmappedBounds)
|
||||
.build();
|
||||
info->renderGlop(state, glop);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) {
|
||||
Glop glop;
|
||||
GlopBuilder(info->renderState, info->caches, &glop)
|
||||
.setRoundRectClipState(state.roundRectClipState)
|
||||
.setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
|
||||
.setFillPaint(*op.paint, state.alpha)
|
||||
.setTransform(state.computedState.transform, TransformFlags::None)
|
||||
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
|
||||
.build();
|
||||
info->renderGlop(state, glop);
|
||||
}
|
||||
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
75
libs/hwui/BakedOpRenderer.h
Normal file
75
libs/hwui/BakedOpRenderer.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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_BAKED_OP_RENDERER_H
|
||||
#define ANDROID_HWUI_BAKED_OP_RENDERER_H
|
||||
|
||||
#include "BakedOpState.h"
|
||||
#include "Matrix.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class Caches;
|
||||
struct Glop;
|
||||
class RenderState;
|
||||
|
||||
class BakedOpRenderer {
|
||||
public:
|
||||
class Info {
|
||||
public:
|
||||
Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque)
|
||||
: renderState(renderState)
|
||||
, caches(caches)
|
||||
, opaque(opaque)
|
||||
, viewportWidth(viewportWidth)
|
||||
, viewportHeight(viewportHeight) {
|
||||
orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
|
||||
}
|
||||
|
||||
Texture* getTexture(const SkBitmap* bitmap);
|
||||
|
||||
void renderGlop(const BakedOpState& state, const Glop& glop);
|
||||
RenderState& renderState;
|
||||
Caches& caches;
|
||||
|
||||
bool didDraw = false;
|
||||
bool opaque;
|
||||
|
||||
|
||||
// where should these live? layer state object?
|
||||
int viewportWidth;
|
||||
int viewportHeight;
|
||||
Matrix4 orthoMatrix;
|
||||
};
|
||||
|
||||
static void startFrame(Info& info);
|
||||
static void endFrame(Info& info);
|
||||
|
||||
/**
|
||||
* Declare all "onBitmapOp(...)" style function for every op type.
|
||||
*
|
||||
* These functions will perform the actual rendering of the individual operations in OpenGL,
|
||||
* given the transform/clip and other state built into the BakedOpState object passed in.
|
||||
*/
|
||||
#define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state);
|
||||
MAP_OPS(BAKED_OP_RENDERER_METHOD);
|
||||
};
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_BAKED_OP_RENDERER_H
|
142
libs/hwui/BakedOpState.h
Normal file
142
libs/hwui/BakedOpState.h
Normal file
@ -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_BAKED_OP_STATE_H
|
||||
#define ANDROID_HWUI_BAKED_OP_STATE_H
|
||||
|
||||
#include "Matrix.h"
|
||||
#include "RecordedOp.h"
|
||||
#include "Rect.h"
|
||||
#include "Snapshot.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
namespace OpClipSideFlags {
|
||||
enum {
|
||||
None = 0x0,
|
||||
Left = 0x1,
|
||||
Top = 0x2,
|
||||
Right = 0x4,
|
||||
Bottom = 0x8,
|
||||
Full = 0xF,
|
||||
// ConservativeFull = 0x1F needed?
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot
|
||||
*/
|
||||
class ResolvedRenderState {
|
||||
public:
|
||||
// TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
|
||||
ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) {
|
||||
/* TODO: benchmark a fast path for translate-only matrices, such as:
|
||||
if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
|
||||
&& recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
|
||||
float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX();
|
||||
float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY();
|
||||
transform.loadTranslate(translateX, translateY, 0);
|
||||
|
||||
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
|
||||
clipRect = recordedOp.localClipRect;
|
||||
clipRect.translate(translateX, translateY);
|
||||
clipRect.doIntersect(snapshot.getClipRect());
|
||||
clipRect.snapToPixelBoundaries();
|
||||
|
||||
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
|
||||
clippedBounds = recordedOp.unmappedBounds;
|
||||
clippedBounds.translate(translateX, translateY);
|
||||
} ... */
|
||||
|
||||
// resolvedMatrix = parentMatrix * localMatrix
|
||||
transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
|
||||
|
||||
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
|
||||
clipRect = recordedOp.localClipRect;
|
||||
snapshot.transform->mapRect(clipRect);
|
||||
clipRect.doIntersect(snapshot.getClipRect());
|
||||
clipRect.snapToPixelBoundaries();
|
||||
|
||||
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
|
||||
clippedBounds = recordedOp.unmappedBounds;
|
||||
transform.mapRect(clippedBounds);
|
||||
|
||||
if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
|
||||
if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
|
||||
if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
|
||||
if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
|
||||
clippedBounds.doIntersect(clipRect);
|
||||
|
||||
/**
|
||||
* TODO: once we support complex clips, we may want to reject to avoid that work where
|
||||
* possible. Should we:
|
||||
* 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp)
|
||||
* 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection
|
||||
* and early return null in one place.
|
||||
*/
|
||||
}
|
||||
Matrix4 transform;
|
||||
Rect clipRect;
|
||||
int clipSideFlags = 0;
|
||||
Rect clippedBounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Self-contained op wrapper, containing all resolved state required to draw the op.
|
||||
*
|
||||
* Stashed pointers within all point to longer lived objects, with no ownership implied.
|
||||
*/
|
||||
class BakedOpState {
|
||||
public:
|
||||
static BakedOpState* tryConstruct(LinearAllocator& allocator,
|
||||
const Snapshot& snapshot, const RecordedOp& recordedOp) {
|
||||
BakedOpState* bakedOp = new (allocator) BakedOpState(
|
||||
snapshot, recordedOp);
|
||||
if (bakedOp->computedState.clippedBounds.isEmpty()) {
|
||||
// bounds are empty, so op is rejected
|
||||
allocator.rewindIfLastAlloc(bakedOp);
|
||||
return nullptr;
|
||||
}
|
||||
return bakedOp;
|
||||
}
|
||||
|
||||
static void* operator new(size_t size, LinearAllocator& allocator) {
|
||||
return allocator.alloc(size);
|
||||
}
|
||||
|
||||
// computed state:
|
||||
const ResolvedRenderState computedState;
|
||||
|
||||
// simple state (straight pointer/value storage):
|
||||
const float alpha;
|
||||
const RoundRectClipState* roundRectClipState;
|
||||
const ProjectionPathMask* projectionPathMask;
|
||||
const RecordedOp* op;
|
||||
|
||||
private:
|
||||
BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp)
|
||||
: computedState(snapshot, recordedOp)
|
||||
, alpha(snapshot.alpha)
|
||||
, roundRectClipState(snapshot.roundRectClipState)
|
||||
, projectionPathMask(snapshot.projectionPathMask)
|
||||
, op(&recordedOp) {}
|
||||
};
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_BAKED_OP_STATE_H
|
@ -528,7 +528,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
|
||||
int insertBatchIndex = mBatches.size();
|
||||
if (!mBatches.empty()) {
|
||||
if (state->mBounds.isEmpty()) {
|
||||
// don't know the bounds for op, so add to last batch and start from scratch on next op
|
||||
// don't know the bounds for op, so create new batch and start from scratch on next op
|
||||
DrawBatch* b = new DrawBatch(deferInfo);
|
||||
b->add(op, state, deferInfo.opaqueOverBounds);
|
||||
mBatches.push_back(b);
|
||||
|
@ -49,7 +49,7 @@ typedef const void* mergeid_t;
|
||||
|
||||
class DeferredDisplayState {
|
||||
public:
|
||||
/** static void* operator new(size_t size); PURPOSELY OMITTED **/
|
||||
static void* operator new(size_t size) = delete;
|
||||
static void* operator new(size_t size, LinearAllocator& allocator) {
|
||||
return allocator.alloc(size);
|
||||
}
|
||||
@ -61,7 +61,6 @@ public:
|
||||
bool mClipValid;
|
||||
Rect mClip;
|
||||
int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared
|
||||
bool mClipped;
|
||||
mat4 mMatrix;
|
||||
float mAlpha;
|
||||
const RoundRectClipState* mRoundRectClipState;
|
||||
|
@ -21,7 +21,13 @@
|
||||
|
||||
#include "Debug.h"
|
||||
#include "DisplayList.h"
|
||||
#include "RenderNode.h"
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
#include "RecordedOp.h"
|
||||
#else
|
||||
#include "DisplayListOp.h"
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
@ -61,8 +67,13 @@ void DisplayListData::cleanupResources() {
|
||||
regions.clear();
|
||||
}
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
size_t DisplayListData::addChild(RenderNodeOp* op) {
|
||||
mReferenceHolders.push_back(op->renderNode);
|
||||
#else
|
||||
size_t DisplayListData::addChild(DrawRenderNodeOp* op) {
|
||||
mReferenceHolders.push_back(op->renderNode());
|
||||
mReferenceHolders.push_back(op->renderNode);
|
||||
#endif
|
||||
size_t index = mChildren.size();
|
||||
mChildren.push_back(op);
|
||||
return index;
|
||||
|
@ -55,11 +55,12 @@ class OpenGLRenderer;
|
||||
class Rect;
|
||||
class Layer;
|
||||
|
||||
class ClipRectOp;
|
||||
class SaveLayerOp;
|
||||
class SaveOp;
|
||||
class RestoreToCountOp;
|
||||
#if HWUI_NEW_OPS
|
||||
struct RecordedOp;
|
||||
struct RenderNodeOp;
|
||||
#else
|
||||
class DrawRenderNodeOp;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Holds data used in the playback a tree of DisplayLists.
|
||||
@ -107,6 +108,7 @@ struct ReplayStateStruct : public PlaybackStateStruct {
|
||||
*/
|
||||
class DisplayListData {
|
||||
friend class DisplayListCanvas;
|
||||
friend class RecordingCanvas;
|
||||
public:
|
||||
struct Chunk {
|
||||
// range of included ops in DLD::displayListOps
|
||||
@ -139,11 +141,21 @@ public:
|
||||
Vector<Functor*> functors;
|
||||
|
||||
const std::vector<Chunk>& getChunks() const {
|
||||
return chunks;
|
||||
return chunks;
|
||||
}
|
||||
#if HWUI_NEW_OPS
|
||||
const std::vector<RecordedOp*>& getOps() const {
|
||||
return ops;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
size_t addChild(RenderNodeOp* childOp);
|
||||
const std::vector<RenderNodeOp*>& children() { return mChildren; }
|
||||
#else
|
||||
size_t addChild(DrawRenderNodeOp* childOp);
|
||||
const std::vector<DrawRenderNodeOp*>& children() { return mChildren; }
|
||||
#endif
|
||||
|
||||
void ref(VirtualLightRefBase* prop) {
|
||||
mReferenceHolders.push_back(prop);
|
||||
@ -157,10 +169,18 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
#if HWUI_NEW_OPS
|
||||
std::vector<RecordedOp*> ops;
|
||||
#endif
|
||||
|
||||
std::vector< sp<VirtualLightRefBase> > mReferenceHolders;
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
std::vector<RenderNodeOp*> mChildren;
|
||||
#else
|
||||
// list of children display lists for quick, non-drawing traversal
|
||||
std::vector<DrawRenderNodeOp*> mChildren;
|
||||
#endif
|
||||
|
||||
std::vector<Chunk> chunks;
|
||||
|
||||
|
@ -558,16 +558,18 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
|
||||
|
||||
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
|
||||
int opIndex = addDrawOp(op);
|
||||
#if !HWUI_NEW_OPS
|
||||
int childIndex = mDisplayListData->addChild(op);
|
||||
|
||||
// update the chunk's child indices
|
||||
DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
|
||||
chunk.endChildIndex = childIndex + 1;
|
||||
|
||||
if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
|
||||
if (op->renderNode->stagingProperties().isProjectionReceiver()) {
|
||||
// use staging property, since recording on UI thread
|
||||
mDisplayListData->projectionReceiveIndex = opIndex;
|
||||
}
|
||||
#endif
|
||||
return opIndex;
|
||||
}
|
||||
|
||||
|
@ -1397,25 +1397,26 @@ private:
|
||||
class DrawRenderNodeOp : public DrawBoundedOp {
|
||||
friend class RenderNode; // grant RenderNode access to info of child
|
||||
friend class DisplayListData; // grant DisplayListData access to info of child
|
||||
friend class DisplayListCanvas;
|
||||
public:
|
||||
DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
|
||||
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
|
||||
, mRenderNode(renderNode)
|
||||
, renderNode(renderNode)
|
||||
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
|
||||
, mTransformFromParent(transformFromParent)
|
||||
, mSkipInOrderDraw(false) {}
|
||||
|
||||
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
|
||||
bool useQuickReject) override {
|
||||
if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
|
||||
mRenderNode->defer(deferStruct, level + 1);
|
||||
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
|
||||
renderNode->defer(deferStruct, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
|
||||
bool useQuickReject) override {
|
||||
if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
|
||||
mRenderNode->replay(replayStruct, level + 1);
|
||||
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
|
||||
renderNode->replay(replayStruct, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1424,18 +1425,16 @@ public:
|
||||
}
|
||||
|
||||
virtual void output(int level, uint32_t logFlags) const override {
|
||||
OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName());
|
||||
if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
|
||||
mRenderNode->output(level + 1);
|
||||
OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName());
|
||||
if (renderNode && (logFlags & kOpLogFlag_Recurse)) {
|
||||
renderNode->output(level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* name() override { return "DrawRenderNode"; }
|
||||
|
||||
RenderNode* renderNode() { return mRenderNode; }
|
||||
|
||||
private:
|
||||
RenderNode* mRenderNode;
|
||||
RenderNode* renderNode;
|
||||
|
||||
/**
|
||||
* This RenderNode was drawn into a DisplayList with the canvas in a state that will likely
|
||||
|
@ -461,12 +461,12 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
|
||||
// Transform
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void GlopBuilder::setTransform(const Matrix4& canvas,
|
||||
const int transformFlags) {
|
||||
GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) {
|
||||
TRIGGER_STAGE(kTransformStage);
|
||||
|
||||
mOutGlop->transform.canvas = canvas;
|
||||
mOutGlop->transform.transformFlags = transformFlags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -632,5 +632,42 @@ void GlopBuilder::build() {
|
||||
mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds);
|
||||
}
|
||||
|
||||
void GlopBuilder::dump(const Glop& glop) {
|
||||
ALOGD("Glop Mesh");
|
||||
const Glop::Mesh& mesh = glop.mesh;
|
||||
ALOGD(" primitive mode: %d", mesh.primitiveMode);
|
||||
ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices);
|
||||
|
||||
const Glop::Mesh::Vertices& vertices = glop.mesh.vertices;
|
||||
ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d",
|
||||
vertices.bufferObject, vertices.attribFlags,
|
||||
vertices.position, vertices.texCoord, vertices.color, vertices.stride);
|
||||
ALOGD(" element count: %d", mesh.elementCount);
|
||||
|
||||
ALOGD("Glop Fill");
|
||||
const Glop::Fill& fill = glop.fill;
|
||||
ALOGD(" program %p", fill.program);
|
||||
if (fill.texture.texture) {
|
||||
ALOGD(" texture %p, target %d, filter %d, clamp %d",
|
||||
fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp);
|
||||
if (fill.texture.textureTransform) {
|
||||
fill.texture.textureTransform->dump("texture transform");
|
||||
}
|
||||
}
|
||||
ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f",
|
||||
fill.color.a, fill.color.r, fill.color.g, fill.color.b);
|
||||
ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None,
|
||||
" filterMode %d", (int)fill.filterMode);
|
||||
ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d",
|
||||
fill.skiaShaderData.skiaShaderType);
|
||||
|
||||
ALOGD("Glop transform");
|
||||
glop.transform.modelView.dump("model view");
|
||||
glop.transform.canvas.dump("canvas");
|
||||
|
||||
ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst);
|
||||
ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds));
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
@ -71,9 +71,9 @@ public:
|
||||
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
|
||||
|
||||
GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) {
|
||||
setTransform(*snapshot.transform, transformFlags);
|
||||
return *this;
|
||||
return setTransform(*snapshot.transform, transformFlags);
|
||||
}
|
||||
GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
|
||||
|
||||
GlopBuilder& setModelViewMapUnitToRect(const Rect destination);
|
||||
GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination);
|
||||
@ -98,11 +98,12 @@ public:
|
||||
GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
|
||||
|
||||
void build();
|
||||
|
||||
static void dump(const Glop& glop);
|
||||
private:
|
||||
void setFill(int color, float alphaScale,
|
||||
SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
|
||||
const SkShader* shader, const SkColorFilter* colorFilter);
|
||||
void setTransform(const Matrix4& canvas, const int transformFlags);
|
||||
|
||||
enum StageFlags {
|
||||
kInitialStage = 0,
|
||||
|
@ -126,6 +126,9 @@ public:
|
||||
void loadMultiply(const Matrix4& u, const Matrix4& v);
|
||||
|
||||
void loadOrtho(float left, float right, float bottom, float top, float near, float far);
|
||||
void loadOrtho(int width, int height) {
|
||||
loadOrtho(0, width, height, 0, -1, 1);
|
||||
}
|
||||
|
||||
uint8_t getType() const;
|
||||
|
||||
|
404
libs/hwui/OpReorderer.cpp
Normal file
404
libs/hwui/OpReorderer.cpp
Normal file
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* 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 "OpReorderer.h"
|
||||
|
||||
#include "utils/PaintUtils.h"
|
||||
#include "RenderNode.h"
|
||||
|
||||
#include "SkCanvas.h"
|
||||
#include "utils/Trace.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class BatchBase {
|
||||
|
||||
public:
|
||||
BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
|
||||
: mBatchId(batchId)
|
||||
, mMerging(merging) {
|
||||
mBounds = op->computedState.clippedBounds;
|
||||
mOps.push_back(op);
|
||||
}
|
||||
|
||||
bool intersects(const Rect& rect) const {
|
||||
if (!rect.intersects(mBounds)) return false;
|
||||
|
||||
for (const BakedOpState* op : mOps) {
|
||||
if (rect.intersects(op->computedState.clippedBounds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
batchid_t getBatchId() const { return mBatchId; }
|
||||
bool isMerging() const { return mMerging; }
|
||||
|
||||
const std::vector<BakedOpState*>& getOps() const { return mOps; }
|
||||
|
||||
void dump() const {
|
||||
ALOGD(" Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
|
||||
}
|
||||
protected:
|
||||
batchid_t mBatchId;
|
||||
Rect mBounds;
|
||||
std::vector<BakedOpState*> mOps;
|
||||
bool mMerging;
|
||||
};
|
||||
|
||||
class OpBatch : public BatchBase {
|
||||
public:
|
||||
static void* operator new(size_t size, LinearAllocator& allocator) {
|
||||
return allocator.alloc(size);
|
||||
}
|
||||
|
||||
OpBatch(batchid_t batchId, BakedOpState* op)
|
||||
: BatchBase(batchId, op, false) {
|
||||
}
|
||||
|
||||
void batchOp(BakedOpState* op) {
|
||||
mBounds.unionWith(op->computedState.clippedBounds);
|
||||
mOps.push_back(op);
|
||||
}
|
||||
};
|
||||
|
||||
class MergingOpBatch : public BatchBase {
|
||||
public:
|
||||
static void* operator new(size_t size, LinearAllocator& allocator) {
|
||||
return allocator.alloc(size);
|
||||
}
|
||||
|
||||
MergingOpBatch(batchid_t batchId, BakedOpState* op)
|
||||
: BatchBase(batchId, op, true) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
|
||||
* and clip side flags. Positive bounds delta means new bounds fit in old.
|
||||
*/
|
||||
static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
|
||||
float boundsDelta) {
|
||||
bool currentClipExists = currentFlags & side;
|
||||
bool newClipExists = newFlags & side;
|
||||
|
||||
// if current is clipped, we must be able to fit new bounds in current
|
||||
if (boundsDelta > 0 && currentClipExists) return false;
|
||||
|
||||
// if new is clipped, we must be able to fit current bounds in new
|
||||
if (boundsDelta < 0 && newClipExists) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool paintIsDefault(const SkPaint& paint) {
|
||||
return paint.getAlpha() == 255
|
||||
&& paint.getColorFilter() == nullptr
|
||||
&& paint.getShader() == nullptr;
|
||||
}
|
||||
|
||||
static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
|
||||
return a.getAlpha() == b.getAlpha()
|
||||
&& a.getColorFilter() == b.getColorFilter()
|
||||
&& a.getShader() == b.getShader();
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if a (mergeable) op can be merged into this batch
|
||||
*
|
||||
* If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
|
||||
* important to consider all paint attributes used in the draw calls in deciding both a) if an
|
||||
* op tries to merge at all, and b) if the op can merge with another set of ops
|
||||
*
|
||||
* False positives can lead to information from the paints of subsequent merged operations being
|
||||
* dropped, so we make simplifying qualifications on the ops that can merge, per op type.
|
||||
*/
|
||||
bool canMergeWith(BakedOpState* op) const {
|
||||
bool isTextBatch = getBatchId() == OpBatchType::Text
|
||||
|| getBatchId() == OpBatchType::ColorText;
|
||||
|
||||
// Overlapping other operations is only allowed for text without shadow. For other ops,
|
||||
// multiDraw isn't guaranteed to overdraw correctly
|
||||
if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
|
||||
if (intersects(op->computedState.clippedBounds)) return false;
|
||||
}
|
||||
|
||||
const BakedOpState* lhs = op;
|
||||
const BakedOpState* rhs = mOps[0];
|
||||
|
||||
if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
|
||||
|
||||
// Identical round rect clip state means both ops will clip in the same way, or not at all.
|
||||
// As the state objects are const, we can compare their pointers to determine mergeability
|
||||
if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
|
||||
if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
|
||||
|
||||
/* Clipping compatibility check
|
||||
*
|
||||
* Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
|
||||
* clip for that side.
|
||||
*/
|
||||
const int currentFlags = mClipSideFlags;
|
||||
const int newFlags = op->computedState.clipSideFlags;
|
||||
if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
|
||||
const Rect& opBounds = op->computedState.clippedBounds;
|
||||
float boundsDelta = mBounds.left - opBounds.left;
|
||||
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
|
||||
boundsDelta = mBounds.top - opBounds.top;
|
||||
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
|
||||
|
||||
// right and bottom delta calculation reversed to account for direction
|
||||
boundsDelta = opBounds.right - mBounds.right;
|
||||
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
|
||||
boundsDelta = opBounds.bottom - mBounds.bottom;
|
||||
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
|
||||
}
|
||||
|
||||
const SkPaint* newPaint = op->op->paint;
|
||||
const SkPaint* oldPaint = mOps[0]->op->paint;
|
||||
|
||||
if (newPaint == oldPaint) {
|
||||
// if paints are equal, then modifiers + paint attribs don't need to be compared
|
||||
return true;
|
||||
} else if (newPaint && !oldPaint) {
|
||||
return paintIsDefault(*newPaint);
|
||||
} else if (!newPaint && oldPaint) {
|
||||
return paintIsDefault(*oldPaint);
|
||||
}
|
||||
return paintsAreEquivalent(*newPaint, *oldPaint);
|
||||
}
|
||||
|
||||
void mergeOp(BakedOpState* op) {
|
||||
mBounds.unionWith(op->computedState.clippedBounds);
|
||||
mOps.push_back(op);
|
||||
|
||||
const int newClipSideFlags = op->computedState.clipSideFlags;
|
||||
mClipSideFlags |= newClipSideFlags;
|
||||
|
||||
const Rect& opClip = op->computedState.clipRect;
|
||||
if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
|
||||
if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
|
||||
if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
|
||||
if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
|
||||
}
|
||||
|
||||
private:
|
||||
int mClipSideFlags = 0;
|
||||
Rect mClipRect;
|
||||
};
|
||||
|
||||
class NullClient: public CanvasStateClient {
|
||||
void onViewportInitialized() override {}
|
||||
void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
|
||||
GLuint getTargetFbo() const override { return 0; }
|
||||
};
|
||||
static NullClient sNullClient;
|
||||
|
||||
OpReorderer::OpReorderer()
|
||||
: mCanvasState(sNullClient) {
|
||||
}
|
||||
|
||||
void OpReorderer::defer(int viewportWidth, int viewportHeight,
|
||||
const std::vector< sp<RenderNode> >& nodes) {
|
||||
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
|
||||
0, 0, viewportWidth, viewportHeight, Vector3());
|
||||
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 DisplayListData& data = node->getDisplayListData();
|
||||
deferImpl(data.getChunks(), data.getOps());
|
||||
}
|
||||
mCanvasState.restore();
|
||||
}
|
||||
}
|
||||
|
||||
void OpReorderer::defer(int viewportWidth, int viewportHeight,
|
||||
const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops) {
|
||||
ATRACE_NAME("prepare drawing commands");
|
||||
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
|
||||
0, 0, viewportWidth, viewportHeight, Vector3());
|
||||
deferImpl(chunks, ops);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
|
||||
*
|
||||
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
|
||||
* BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
|
||||
*/
|
||||
#define OP_RECIEVER(Type) \
|
||||
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
|
||||
void OpReorderer::deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
|
||||
const std::vector<RecordedOp*>& ops) {
|
||||
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
|
||||
MAP_OPS(OP_RECIEVER)
|
||||
};
|
||||
for (const DisplayListData::Chunk& chunk : chunks) {
|
||||
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
|
||||
const RecordedOp* op = ops[opIndex];
|
||||
receivers[op->opId](*this, *op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
|
||||
ATRACE_NAME("flush drawing commands");
|
||||
for (const BatchBase* batch : mBatches) {
|
||||
// TODO: different behavior based on batch->isMerging()
|
||||
for (const BakedOpState* op : batch->getOps()) {
|
||||
receivers[op->op->opId](arg, *op->op, *op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
|
||||
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
|
||||
}
|
||||
|
||||
void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
|
||||
if (op.renderNode->nothingToDraw()) {
|
||||
return;
|
||||
}
|
||||
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
|
||||
|
||||
// apply state from RecordedOp
|
||||
mCanvasState.concatMatrix(op.localMatrix);
|
||||
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)) {
|
||||
// not rejected do ops...
|
||||
const DisplayListData& data = op.renderNode->getDisplayListData();
|
||||
deferImpl(data.getChunks(), data.getOps());
|
||||
}
|
||||
mCanvasState.restore();
|
||||
}
|
||||
|
||||
static batchid_t tessellatedBatchId(const SkPaint& paint) {
|
||||
return paint.getPathEffect()
|
||||
? OpBatchType::AlphaMaskTexture
|
||||
: (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
|
||||
}
|
||||
|
||||
void OpReorderer::onBitmapOp(const BitmapOp& op) {
|
||||
BakedOpState* bakedStateOp = bakeOpState(op);
|
||||
if (!bakedStateOp) return; // quick rejected
|
||||
|
||||
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
|
||||
// TODO: AssetAtlas
|
||||
|
||||
deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
|
||||
}
|
||||
|
||||
void OpReorderer::onRectOp(const RectOp& op) {
|
||||
BakedOpState* bakedStateOp = bakeOpState(op);
|
||||
if (!bakedStateOp) return; // quick rejected
|
||||
deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
|
||||
}
|
||||
|
||||
void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
|
||||
BakedOpState* bakedStateOp = bakeOpState(op);
|
||||
if (!bakedStateOp) return; // quick rejected
|
||||
deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
|
||||
}
|
||||
|
||||
// iterate back toward target to see if anything drawn since should overlap the new op
|
||||
// if no target, merging ops still interate to find similar batch to insert after
|
||||
void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
|
||||
BatchBase** targetBatch, size_t* insertBatchIndex) const {
|
||||
for (size_t i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
|
||||
BatchBase* overBatch = mBatches[i];
|
||||
|
||||
if (overBatch == *targetBatch) break;
|
||||
|
||||
// TODO: also consider shader shared between batch types
|
||||
if (batchId == overBatch->getBatchId()) {
|
||||
*insertBatchIndex = i + 1;
|
||||
if (!*targetBatch) break; // found insert position, quit
|
||||
}
|
||||
|
||||
if (overBatch->intersects(clippedBounds)) {
|
||||
// NOTE: it may be possible to optimize for special cases where two operations
|
||||
// of the same batch/paint could swap order, such as with a non-mergeable
|
||||
// (clipped) and a mergeable text operation
|
||||
*targetBatch = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
|
||||
OpBatch* targetBatch = mBatchLookup[batchId];
|
||||
|
||||
size_t insertBatchIndex = mBatches.size();
|
||||
if (targetBatch) {
|
||||
locateInsertIndex(batchId, op->computedState.clippedBounds,
|
||||
(BatchBase**)(&targetBatch), &insertBatchIndex);
|
||||
}
|
||||
|
||||
if (targetBatch) {
|
||||
targetBatch->batchOp(op);
|
||||
} else {
|
||||
// new non-merging batch
|
||||
targetBatch = new (mAllocator) OpBatch(batchId, op);
|
||||
mBatchLookup[batchId] = targetBatch;
|
||||
mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
|
||||
}
|
||||
}
|
||||
|
||||
// insertion point of a new batch, will hopefully be immediately after similar batch
|
||||
// (generally, should be similar shader)
|
||||
void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
|
||||
MergingOpBatch* targetBatch = nullptr;
|
||||
|
||||
// Try to merge with any existing batch with same mergeId
|
||||
auto getResult = mMergingBatches[batchId].find(mergeId);
|
||||
if (getResult != mMergingBatches[batchId].end()) {
|
||||
targetBatch = getResult->second;
|
||||
if (!targetBatch->canMergeWith(op)) {
|
||||
targetBatch = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t insertBatchIndex = mBatches.size();
|
||||
locateInsertIndex(batchId, op->computedState.clippedBounds,
|
||||
(BatchBase**)(&targetBatch), &insertBatchIndex);
|
||||
|
||||
if (targetBatch) {
|
||||
targetBatch->mergeOp(op);
|
||||
} else {
|
||||
// new merging batch
|
||||
targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
|
||||
mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
|
||||
|
||||
mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
|
||||
}
|
||||
}
|
||||
|
||||
void OpReorderer::dump() {
|
||||
for (const BatchBase* batch : mBatches) {
|
||||
batch->dump();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
142
libs/hwui/OpReorderer.h
Normal file
142
libs/hwui/OpReorderer.h
Normal file
@ -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_OP_REORDERER_H
|
||||
#define ANDROID_HWUI_OP_REORDERER_H
|
||||
|
||||
#include "BakedOpState.h"
|
||||
#include "CanvasState.h"
|
||||
#include "DisplayList.h"
|
||||
#include "RecordedOp.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class BakedOpState;
|
||||
class BatchBase;
|
||||
class MergingOpBatch;
|
||||
class OpBatch;
|
||||
class Rect;
|
||||
|
||||
typedef int batchid_t;
|
||||
typedef const void* mergeid_t;
|
||||
|
||||
namespace OpBatchType {
|
||||
enum {
|
||||
None = 0, // Don't batch
|
||||
Bitmap,
|
||||
Patch,
|
||||
AlphaVertices,
|
||||
Vertices,
|
||||
AlphaMaskTexture,
|
||||
Text,
|
||||
ColorText,
|
||||
|
||||
Count // must be last
|
||||
};
|
||||
}
|
||||
|
||||
class OpReorderer {
|
||||
public:
|
||||
OpReorderer();
|
||||
|
||||
// TODO: not final, just presented this way for simplicity. Layers too?
|
||||
void defer(int viewportWidth, int viewportHeight, const std::vector< sp<RenderNode> >& nodes);
|
||||
|
||||
void defer(int viewportWidth, int viewportHeight,
|
||||
const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops);
|
||||
typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
|
||||
|
||||
/**
|
||||
* replayBakedOps() is templated based on what class will recieve ops being replayed.
|
||||
*
|
||||
* It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
|
||||
* state->op->opId to lookup a receiver that will be called when the op is replayed.
|
||||
*
|
||||
* For example a BitmapOp would resolve, via the lambda lookup, to calling:
|
||||
*
|
||||
* StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
|
||||
*/
|
||||
#define BAKED_OP_RECEIVER(Type) \
|
||||
[](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
|
||||
StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \
|
||||
},
|
||||
template <typename StaticReceiver, typename Arg>
|
||||
void replayBakedOps(Arg* arg) {
|
||||
static BakedOpReceiver receivers[] = {
|
||||
MAP_OPS(BAKED_OP_RECEIVER)
|
||||
};
|
||||
StaticReceiver::startFrame(*arg);
|
||||
replayBakedOpsImpl((void*)arg, receivers);
|
||||
StaticReceiver::endFrame(*arg);
|
||||
}
|
||||
private:
|
||||
BakedOpState* bakeOpState(const RecordedOp& recordedOp);
|
||||
|
||||
void deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
|
||||
const std::vector<RecordedOp*>& ops);
|
||||
|
||||
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
|
||||
|
||||
/**
|
||||
* Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
|
||||
*
|
||||
* These private methods are called from within deferImpl to defer each individual op
|
||||
* type differently.
|
||||
*/
|
||||
#define INTERNAL_OP_HANDLER(Type) \
|
||||
void on##Type(const Type& op);
|
||||
MAP_OPS(INTERNAL_OP_HANDLER)
|
||||
|
||||
// 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 locateInsertIndex(int batchId, const Rect& clippedBounds,
|
||||
BatchBase** targetBatch, size_t* insertBatchIndex) const;
|
||||
|
||||
void deferUnmergeableOp(BakedOpState* op, batchid_t batchId);
|
||||
|
||||
// insertion point of a new batch, will hopefully be immediately after similar batch
|
||||
// (generally, should be similar shader)
|
||||
void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
|
||||
|
||||
void dump();
|
||||
|
||||
std::vector<BatchBase*> mBatches;
|
||||
|
||||
/**
|
||||
* Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
|
||||
* MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
|
||||
* collide, which avoids the need to resolve mergeid collisions.
|
||||
*/
|
||||
std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count];
|
||||
|
||||
// Maps batch ids to the most recent *non-merging* batch of that id
|
||||
OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
|
||||
CanvasState mCanvasState;
|
||||
|
||||
// contains ResolvedOps and Batches
|
||||
LinearAllocator mAllocator;
|
||||
|
||||
size_t mEarliestBatchIndex = 0;
|
||||
};
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_OP_REORDERER_H
|
118
libs/hwui/RecordedOp.h
Normal file
118
libs/hwui/RecordedOp.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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_RECORDED_OP_H
|
||||
#define ANDROID_HWUI_RECORDED_OP_H
|
||||
|
||||
#include "utils/LinearAllocator.h"
|
||||
#include "Rect.h"
|
||||
#include "Matrix.h"
|
||||
|
||||
#include "SkXfermode.h"
|
||||
|
||||
class SkBitmap;
|
||||
class SkPaint;
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class RenderNode;
|
||||
struct Vertex;
|
||||
|
||||
/**
|
||||
* The provided macro is executed for each op type in order, with the results separated by commas.
|
||||
*
|
||||
* This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs.
|
||||
*/
|
||||
#define MAP_OPS(OP_FN) \
|
||||
OP_FN(BitmapOp) \
|
||||
OP_FN(RectOp) \
|
||||
OP_FN(RenderNodeOp) \
|
||||
OP_FN(SimpleRectsOp)
|
||||
|
||||
// Generate OpId enum
|
||||
#define IDENTITY_FN(Type) Type,
|
||||
namespace RecordedOpId {
|
||||
enum {
|
||||
MAP_OPS(IDENTITY_FN)
|
||||
Count,
|
||||
};
|
||||
}
|
||||
static_assert(RecordedOpId::BitmapOp == 0,
|
||||
"First index must be zero for LUTs to work");
|
||||
|
||||
#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint
|
||||
#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect
|
||||
#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint)
|
||||
#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr)
|
||||
|
||||
struct RecordedOp {
|
||||
/* ID from RecordedOpId - generally used for jumping into function tables */
|
||||
const int opId;
|
||||
|
||||
/* bounds in *local* space, without accounting for DisplayList transformation */
|
||||
const Rect unmappedBounds;
|
||||
|
||||
/* transform in recording space (vs DisplayList origin) */
|
||||
const Matrix4 localMatrix;
|
||||
|
||||
/* clip in recording space */
|
||||
const Rect localClipRect;
|
||||
|
||||
/* optional paint, stored in base object to simplify merging logic */
|
||||
const SkPaint* paint;
|
||||
protected:
|
||||
RecordedOp(unsigned int opId, BASE_PARAMS)
|
||||
: opId(opId)
|
||||
, unmappedBounds(unmappedBounds)
|
||||
, localMatrix(localMatrix)
|
||||
, localClipRect(localClipRect)
|
||||
, paint(paint) {}
|
||||
};
|
||||
|
||||
struct RenderNodeOp : RecordedOp {
|
||||
RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
|
||||
: SUPER_PAINTLESS(RenderNodeOp)
|
||||
, renderNode(renderNode) {}
|
||||
RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
|
||||
};
|
||||
|
||||
struct BitmapOp : RecordedOp {
|
||||
BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
|
||||
: SUPER(BitmapOp)
|
||||
, bitmap(bitmap) {}
|
||||
const SkBitmap* bitmap;
|
||||
// TODO: asset atlas/texture id lookup?
|
||||
};
|
||||
|
||||
struct RectOp : RecordedOp {
|
||||
RectOp(BASE_PARAMS)
|
||||
: SUPER(RectOp) {}
|
||||
};
|
||||
|
||||
struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
|
||||
SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
|
||||
: SUPER(SimpleRectsOp)
|
||||
, vertices(vertices)
|
||||
, vertexCount(vertexCount) {}
|
||||
Vertex* vertices;
|
||||
const size_t vertexCount;
|
||||
};
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_RECORDED_OP_H
|
402
libs/hwui/RecordingCanvas.cpp
Normal file
402
libs/hwui/RecordingCanvas.cpp
Normal file
@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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 "RecordingCanvas.h"
|
||||
|
||||
#include "RecordedOp.h"
|
||||
#include "RenderNode.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
RecordingCanvas::RecordingCanvas(size_t width, size_t height)
|
||||
: mState(*this)
|
||||
, mResourceCache(ResourceCache::getInstance()) {
|
||||
reset(width, height);
|
||||
}
|
||||
|
||||
RecordingCanvas::~RecordingCanvas() {
|
||||
LOG_ALWAYS_FATAL_IF(mDisplayListData,
|
||||
"Destroyed a RecordingCanvas during a record!");
|
||||
}
|
||||
|
||||
void RecordingCanvas::reset(int width, int height) {
|
||||
LOG_ALWAYS_FATAL_IF(mDisplayListData,
|
||||
"prepareDirty called a second time during a recording!");
|
||||
mDisplayListData = new DisplayListData();
|
||||
|
||||
mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
|
||||
|
||||
mDeferredBarrierType = kBarrier_InOrder;
|
||||
mState.setDirtyClip(false);
|
||||
mRestoreSaveCount = -1;
|
||||
}
|
||||
|
||||
DisplayListData* RecordingCanvas::finishRecording() {
|
||||
mPaintMap.clear();
|
||||
mRegionMap.clear();
|
||||
mPathMap.clear();
|
||||
DisplayListData* data = mDisplayListData;
|
||||
mDisplayListData = nullptr;
|
||||
mSkiaCanvasProxy.reset(nullptr);
|
||||
return data;
|
||||
}
|
||||
|
||||
SkCanvas* RecordingCanvas::asSkCanvas() {
|
||||
LOG_ALWAYS_FATAL_IF(!mDisplayListData,
|
||||
"attempting to get an SkCanvas when we are not recording!");
|
||||
if (!mSkiaCanvasProxy) {
|
||||
mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
|
||||
}
|
||||
|
||||
// SkCanvas instances default to identity transform, but should inherit
|
||||
// the state of this Canvas; if this code was in the SkiaCanvasProxy
|
||||
// constructor, we couldn't cache mSkiaCanvasProxy.
|
||||
SkMatrix parentTransform;
|
||||
getMatrix(&parentTransform);
|
||||
mSkiaCanvasProxy.get()->setMatrix(parentTransform);
|
||||
|
||||
return mSkiaCanvasProxy.get();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// android/graphics/Canvas state operations
|
||||
// ----------------------------------------------------------------------------
|
||||
// Save (layer)
|
||||
int RecordingCanvas::save(SkCanvas::SaveFlags flags) {
|
||||
return mState.save((int) flags);
|
||||
}
|
||||
|
||||
void RecordingCanvas::RecordingCanvas::restore() {
|
||||
if (mRestoreSaveCount < 0) {
|
||||
restoreToCount(getSaveCount() - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
mRestoreSaveCount--;
|
||||
mState.restore();
|
||||
}
|
||||
|
||||
void RecordingCanvas::restoreToCount(int saveCount) {
|
||||
mRestoreSaveCount = saveCount;
|
||||
mState.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
|
||||
SkCanvas::SaveFlags flags) {
|
||||
LOG_ALWAYS_FATAL("TODO");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Matrix
|
||||
void RecordingCanvas::rotate(float degrees) {
|
||||
if (degrees == 0) return;
|
||||
|
||||
mState.rotate(degrees);
|
||||
}
|
||||
|
||||
void RecordingCanvas::scale(float sx, float sy) {
|
||||
if (sx == 1 && sy == 1) return;
|
||||
|
||||
mState.scale(sx, sy);
|
||||
}
|
||||
|
||||
void RecordingCanvas::skew(float sx, float sy) {
|
||||
mState.skew(sx, sy);
|
||||
}
|
||||
|
||||
void RecordingCanvas::translate(float dx, float dy) {
|
||||
if (dx == 0 && dy == 0) return;
|
||||
|
||||
mState.translate(dx, dy, 0);
|
||||
}
|
||||
|
||||
// Clip
|
||||
bool RecordingCanvas::getClipBounds(SkRect* outRect) const {
|
||||
Rect bounds = mState.getLocalClipBounds();
|
||||
*outRect = SkRect::MakeLTRB(bounds.left, bounds.top, bounds.right, bounds.bottom);
|
||||
return !(outRect->isEmpty());
|
||||
}
|
||||
bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
|
||||
return mState.quickRejectConservative(left, top, right, bottom);
|
||||
}
|
||||
bool RecordingCanvas::quickRejectPath(const SkPath& path) const {
|
||||
SkRect bounds = path.getBounds();
|
||||
return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
|
||||
}
|
||||
bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
|
||||
return mState.clipRect(left, top, right, bottom, op);
|
||||
}
|
||||
bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
|
||||
return mState.clipPath(path, op);
|
||||
}
|
||||
bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
|
||||
return mState.clipRegion(region, op);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// android/graphics/Canvas draw operations
|
||||
// ----------------------------------------------------------------------------
|
||||
void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
|
||||
SkPaint paint;
|
||||
paint.setColor(color);
|
||||
paint.setXfermodeMode(mode);
|
||||
drawPaint(paint);
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawPaint(const SkPaint& paint) {
|
||||
// TODO: more efficient recording?
|
||||
Matrix4 identity;
|
||||
identity.loadIdentity();
|
||||
|
||||
addOp(new (alloc()) RectOp(
|
||||
mState.getRenderTargetClipBounds(),
|
||||
identity,
|
||||
mState.getRenderTargetClipBounds(),
|
||||
refPaint(&paint)));
|
||||
}
|
||||
|
||||
// Geometry
|
||||
void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
|
||||
addOp(new (alloc()) RectOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
refPaint(&paint)));
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
|
||||
if (rects == nullptr) return;
|
||||
|
||||
Vertex* rectData = (Vertex*) mDisplayListData->allocator.alloc(vertexCount * sizeof(Vertex));
|
||||
Vertex* vertex = rectData;
|
||||
|
||||
float left = FLT_MAX;
|
||||
float top = FLT_MAX;
|
||||
float right = FLT_MIN;
|
||||
float bottom = FLT_MIN;
|
||||
for (int index = 0; index < vertexCount; index += 4) {
|
||||
float l = rects[index + 0];
|
||||
float t = rects[index + 1];
|
||||
float r = rects[index + 2];
|
||||
float b = rects[index + 3];
|
||||
|
||||
Vertex::set(vertex++, l, t);
|
||||
Vertex::set(vertex++, r, t);
|
||||
Vertex::set(vertex++, l, b);
|
||||
Vertex::set(vertex++, r, b);
|
||||
|
||||
left = std::min(left, l);
|
||||
top = std::min(top, t);
|
||||
right = std::max(right, r);
|
||||
bottom = std::max(bottom, b);
|
||||
}
|
||||
addOp(new (alloc()) SimpleRectsOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
refPaint(paint), rectData, vertexCount));
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
|
||||
if (paint.getStyle() == SkPaint::kFill_Style
|
||||
&& (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
|
||||
int count = 0;
|
||||
Vector<float> rects;
|
||||
SkRegion::Iterator it(region);
|
||||
while (!it.done()) {
|
||||
const SkIRect& r = it.rect();
|
||||
rects.push(r.fLeft);
|
||||
rects.push(r.fTop);
|
||||
rects.push(r.fRight);
|
||||
rects.push(r.fBottom);
|
||||
count += 4;
|
||||
it.next();
|
||||
}
|
||||
drawSimpleRects(rects.array(), count, &paint);
|
||||
} else {
|
||||
SkRegion::Iterator it(region);
|
||||
while (!it.done()) {
|
||||
const SkIRect& r = it.rect();
|
||||
drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
|
||||
float rx, float ry, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
|
||||
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
|
||||
// Bitmap-based
|
||||
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
|
||||
save(SkCanvas::kMatrix_SaveFlag);
|
||||
translate(left, top);
|
||||
drawBitmap(&bitmap, paint);
|
||||
restore();
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
|
||||
const SkPaint* paint) {
|
||||
if (matrix.isIdentity()) {
|
||||
drawBitmap(&bitmap, paint);
|
||||
} else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
|
||||
&& MathUtils::isPositive(matrix.getScaleX())
|
||||
&& MathUtils::isPositive(matrix.getScaleY())) {
|
||||
// SkMatrix::isScaleTranslate() not available in L
|
||||
SkRect src;
|
||||
SkRect dst;
|
||||
bitmap.getBounds(&src);
|
||||
matrix.mapRect(&dst, src);
|
||||
drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
|
||||
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
|
||||
} else {
|
||||
save(SkCanvas::kMatrix_SaveFlag);
|
||||
concat(matrix);
|
||||
drawBitmap(&bitmap, paint);
|
||||
restore();
|
||||
}
|
||||
}
|
||||
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
|
||||
float srcRight, float srcBottom, float dstLeft, float dstTop,
|
||||
float dstRight, float dstBottom, const SkPaint* paint) {
|
||||
if (srcLeft == 0 && srcTop == 0
|
||||
&& srcRight == bitmap.width()
|
||||
&& srcBottom == bitmap.height()
|
||||
&& (srcBottom - srcTop == dstBottom - dstTop)
|
||||
&& (srcRight - srcLeft == dstRight - dstLeft)) {
|
||||
// transform simple rect to rect drawing case into position bitmap ops, since they merge
|
||||
save(SkCanvas::kMatrix_SaveFlag);
|
||||
translate(dstLeft, dstTop);
|
||||
drawBitmap(&bitmap, paint);
|
||||
restore();
|
||||
} else {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
}
|
||||
void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
|
||||
const float* vertices, const int* colors, const SkPaint* paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
|
||||
float dstLeft, float dstTop, float dstRight, float dstBottom,
|
||||
const SkPaint* paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
|
||||
// Text
|
||||
void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
|
||||
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
|
||||
float boundsRight, float boundsBottom, float totalAdvance) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
|
||||
int posCount, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
|
||||
float hOffset, float vOffset, const SkPaint& paint) {
|
||||
LOG_ALWAYS_FATAL("TODO!");
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
|
||||
addOp(new (alloc()) BitmapOp(
|
||||
Rect(0, 0, bitmap->width(), bitmap->height()),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
refPaint(paint), refBitmap(*bitmap)));
|
||||
}
|
||||
void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
|
||||
RenderNodeOp* op = new (alloc()) RenderNodeOp(
|
||||
Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
renderNode);
|
||||
int opIndex = addOp(op);
|
||||
int childIndex = mDisplayListData->addChild(op);
|
||||
|
||||
// update the chunk's child indices
|
||||
DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
|
||||
chunk.endChildIndex = childIndex + 1;
|
||||
|
||||
if (renderNode->stagingProperties().isProjectionReceiver()) {
|
||||
// use staging property, since recording on UI thread
|
||||
mDisplayListData->projectionReceiveIndex = opIndex;
|
||||
}
|
||||
}
|
||||
|
||||
size_t RecordingCanvas::addOp(RecordedOp* op) {
|
||||
// TODO: validate if "addDrawOp" quickrejection logic is useful before adding
|
||||
int insertIndex = mDisplayListData->ops.size();
|
||||
mDisplayListData->ops.push_back(op);
|
||||
if (mDeferredBarrierType != kBarrier_None) {
|
||||
// op is first in new chunk
|
||||
mDisplayListData->chunks.emplace_back();
|
||||
DisplayListData::Chunk& newChunk = mDisplayListData->chunks.back();
|
||||
newChunk.beginOpIndex = insertIndex;
|
||||
newChunk.endOpIndex = insertIndex + 1;
|
||||
newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
|
||||
|
||||
int nextChildIndex = mDisplayListData->children().size();
|
||||
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
|
||||
mDeferredBarrierType = kBarrier_None;
|
||||
} else {
|
||||
// standard case - append to existing chunk
|
||||
mDisplayListData->chunks.back().endOpIndex = insertIndex + 1;
|
||||
}
|
||||
return insertIndex;
|
||||
}
|
||||
|
||||
void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
|
||||
if (!shader) return;
|
||||
|
||||
// If this paint has an SkShader that has an SkBitmap add
|
||||
// it to the bitmap pile
|
||||
SkBitmap bitmap;
|
||||
SkShader::TileMode xy[2];
|
||||
if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
|
||||
refBitmap(bitmap);
|
||||
return;
|
||||
}
|
||||
SkShader::ComposeRec rec;
|
||||
if (shader->asACompose(&rec)) {
|
||||
refBitmapsInShader(rec.fShaderA);
|
||||
refBitmapsInShader(rec.fShaderB);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
300
libs/hwui/RecordingCanvas.h
Normal file
300
libs/hwui/RecordingCanvas.h
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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_RECORDING_CANVAS_H
|
||||
#define ANDROID_HWUI_RECORDING_CANVAS_H
|
||||
|
||||
#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 "SkDrawFilter.h"
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class OpReceiver;
|
||||
struct RecordedOp;
|
||||
|
||||
class RecordingCanvas: public Canvas, public CanvasStateClient {
|
||||
public:
|
||||
RecordingCanvas(size_t width, size_t height);
|
||||
virtual ~RecordingCanvas();
|
||||
|
||||
void reset(int width, int height);
|
||||
DisplayListData* finishRecording();
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
|
||||
// ----------------------------------------------------------------------------
|
||||
void insertReorderBarrier(bool enableReorder) {}
|
||||
void drawRenderNode(RenderNode* renderNode);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CanvasStateClient interface
|
||||
// ----------------------------------------------------------------------------
|
||||
virtual void onViewportInitialized() override {}
|
||||
virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {}
|
||||
virtual GLuint getTargetFbo() const override { return -1; }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// android/graphics/Canvas interface
|
||||
// ----------------------------------------------------------------------------
|
||||
virtual SkCanvas* asSkCanvas() override;
|
||||
|
||||
virtual void setBitmap(const SkBitmap& bitmap) override {
|
||||
LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap.");
|
||||
}
|
||||
|
||||
virtual bool isOpaque() override { return false; }
|
||||
virtual int width() override { return mState.getWidth(); }
|
||||
virtual int height() override { return mState.getHeight(); }
|
||||
|
||||
virtual void setHighContrastText(bool highContrastText) override {
|
||||
mHighContrastText = highContrastText;
|
||||
}
|
||||
virtual bool isHighContrastText() override { return mHighContrastText; }
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// android/graphics/Canvas state operations
|
||||
// ----------------------------------------------------------------------------
|
||||
// Save (layer)
|
||||
virtual int getSaveCount() const override { return mState.getSaveCount(); }
|
||||
virtual int save(SkCanvas::SaveFlags flags) override;
|
||||
virtual void restore() override;
|
||||
virtual void restoreToCount(int saveCount) override;
|
||||
|
||||
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
|
||||
SkCanvas::SaveFlags flags) override;
|
||||
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
|
||||
int alpha, SkCanvas::SaveFlags flags) override {
|
||||
SkPaint paint;
|
||||
paint.setAlpha(alpha);
|
||||
return saveLayer(left, top, right, bottom, &paint, flags);
|
||||
}
|
||||
|
||||
// Matrix
|
||||
virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
|
||||
virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); }
|
||||
|
||||
virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); }
|
||||
virtual void rotate(float degrees) override;
|
||||
virtual void scale(float sx, float sy) override;
|
||||
virtual void skew(float sx, float sy) override;
|
||||
virtual void translate(float dx, float dy) override;
|
||||
|
||||
// Clip
|
||||
virtual bool getClipBounds(SkRect* outRect) const override;
|
||||
virtual bool quickRejectRect(float left, float top, float right, float bottom) const override;
|
||||
virtual bool quickRejectPath(const SkPath& path) const override;
|
||||
|
||||
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override;
|
||||
virtual bool clipPath(const SkPath* path, SkRegion::Op op) override;
|
||||
virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override;
|
||||
|
||||
// Misc
|
||||
virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); }
|
||||
virtual void setDrawFilter(SkDrawFilter* filter) override {
|
||||
mDrawFilter.reset(SkSafeRef(filter));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// android/graphics/Canvas draw operations
|
||||
// ----------------------------------------------------------------------------
|
||||
virtual void drawColor(int color, SkXfermode::Mode mode) override;
|
||||
virtual void drawPaint(const SkPaint& paint) override;
|
||||
|
||||
// Geometry
|
||||
virtual void drawPoint(float x, float y, const SkPaint& paint) override {
|
||||
float points[2] = { x, y };
|
||||
drawPoints(points, 2, paint);
|
||||
}
|
||||
virtual void drawPoints(const float* points, int count, const SkPaint& paint) override;
|
||||
virtual void drawLine(float startX, float startY, float stopX, float stopY,
|
||||
const SkPaint& paint) override {
|
||||
float points[4] = { startX, startY, stopX, stopY };
|
||||
drawLines(points, 4, paint);
|
||||
}
|
||||
virtual void drawLines(const float* points, int count, const SkPaint& paint) override;
|
||||
virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
|
||||
virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
|
||||
virtual void drawRoundRect(float left, float top, float right, float bottom,
|
||||
float rx, float ry, const SkPaint& paint) override;
|
||||
virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
|
||||
virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override;
|
||||
virtual void drawArc(float left, float top, float right, float bottom,
|
||||
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
|
||||
virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
|
||||
virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
|
||||
const float* verts, const float* tex, const int* colors,
|
||||
const uint16_t* indices, int indexCount, const SkPaint& paint) override
|
||||
{ /* RecordingCanvas does not support drawVertices(); ignore */ }
|
||||
|
||||
// Bitmap-based
|
||||
virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
|
||||
virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
|
||||
const SkPaint* paint) override;
|
||||
virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
|
||||
float srcRight, float srcBottom, float dstLeft, float dstTop,
|
||||
float dstRight, float dstBottom, const SkPaint* paint) override;
|
||||
virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
|
||||
const float* vertices, const int* colors, const SkPaint* paint) override;
|
||||
virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
|
||||
float dstLeft, float dstTop, float dstRight, float dstBottom,
|
||||
const SkPaint* paint) override;
|
||||
|
||||
// Text
|
||||
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
|
||||
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
|
||||
float boundsRight, float boundsBottom, float totalAdvance) override;
|
||||
virtual void drawPosText(const uint16_t* text, const float* positions, int count,
|
||||
int posCount, const SkPaint& paint) override;
|
||||
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
|
||||
float hOffset, float vOffset, const SkPaint& paint) override;
|
||||
virtual bool drawTextAbsolutePos() const override { return false; }
|
||||
|
||||
private:
|
||||
enum DeferredBarrierType {
|
||||
kBarrier_None,
|
||||
kBarrier_InOrder,
|
||||
kBarrier_OutOfOrder,
|
||||
};
|
||||
|
||||
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
|
||||
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
|
||||
|
||||
|
||||
size_t addOp(RecordedOp* op);
|
||||
// ----------------------------------------------------------------------------
|
||||
// lazy object copy
|
||||
// ----------------------------------------------------------------------------
|
||||
LinearAllocator& alloc() { return mDisplayListData->allocator; }
|
||||
|
||||
void refBitmapsInShader(const SkShader* shader);
|
||||
|
||||
template<class T>
|
||||
inline const T* refBuffer(const T* srcBuffer, int32_t count) {
|
||||
if (!srcBuffer) return nullptr;
|
||||
|
||||
T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
|
||||
memcpy(dstBuffer, srcBuffer, count * sizeof(T));
|
||||
return dstBuffer;
|
||||
}
|
||||
|
||||
inline char* refText(const char* text, size_t byteLength) {
|
||||
return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
|
||||
}
|
||||
|
||||
inline const SkPath* refPath(const SkPath* path) {
|
||||
if (!path) return nullptr;
|
||||
|
||||
// The points/verbs within the path are refcounted so this copy operation
|
||||
// is inexpensive and maintains the generationID of the original path.
|
||||
const SkPath* cachedPath = new SkPath(*path);
|
||||
mDisplayListData->pathResources.push_back(cachedPath);
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
inline const SkPaint* refPaint(const SkPaint* paint) {
|
||||
if (!paint) return nullptr;
|
||||
|
||||
// If there is a draw filter apply it here and store the modified paint
|
||||
// so that we don't need to modify the paint every time we access it.
|
||||
SkTLazy<SkPaint> filteredPaint;
|
||||
if (mDrawFilter.get()) {
|
||||
filteredPaint.set(*paint);
|
||||
mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type);
|
||||
paint = filteredPaint.get();
|
||||
}
|
||||
|
||||
// compute the hash key for the paint and check the cache.
|
||||
const uint32_t key = paint->getHash();
|
||||
const SkPaint* cachedPaint = mPaintMap.valueFor(key);
|
||||
// In the unlikely event that 2 unique paints have the same hash we do a
|
||||
// object equality check to ensure we don't erroneously dedup them.
|
||||
if (cachedPaint == nullptr || *cachedPaint != *paint) {
|
||||
cachedPaint = new SkPaint(*paint);
|
||||
std::unique_ptr<const SkPaint> copy(cachedPaint);
|
||||
mDisplayListData->paints.push_back(std::move(copy));
|
||||
|
||||
// replaceValueFor() performs an add if the entry doesn't exist
|
||||
mPaintMap.replaceValueFor(key, cachedPaint);
|
||||
refBitmapsInShader(cachedPaint->getShader());
|
||||
}
|
||||
|
||||
return cachedPaint;
|
||||
}
|
||||
|
||||
inline const SkRegion* refRegion(const SkRegion* region) {
|
||||
if (!region) {
|
||||
return region;
|
||||
}
|
||||
|
||||
const SkRegion* cachedRegion = mRegionMap.valueFor(region);
|
||||
// TODO: Add generation ID to SkRegion
|
||||
if (cachedRegion == nullptr) {
|
||||
std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
|
||||
cachedRegion = copy.get();
|
||||
mDisplayListData->regions.push_back(std::move(copy));
|
||||
|
||||
// replaceValueFor() performs an add if the entry doesn't exist
|
||||
mRegionMap.replaceValueFor(region, cachedRegion);
|
||||
}
|
||||
|
||||
return cachedRegion;
|
||||
}
|
||||
|
||||
inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
|
||||
// Note that this assumes the bitmap is immutable. There are cases this won't handle
|
||||
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
|
||||
// contents, and drawing again. The only fix would be to always copy it the first time,
|
||||
// which doesn't seem worth the extra cycles for this unlikely case.
|
||||
SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
|
||||
alloc().autoDestroy(localBitmap);
|
||||
mDisplayListData->bitmapResources.push_back(localBitmap);
|
||||
return localBitmap;
|
||||
}
|
||||
|
||||
inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
|
||||
mDisplayListData->patchResources.push_back(patch);
|
||||
mResourceCache.incrementRefcount(patch);
|
||||
return patch;
|
||||
}
|
||||
|
||||
DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap;
|
||||
DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap;
|
||||
DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
|
||||
|
||||
CanvasState mState;
|
||||
std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
|
||||
ResourceCache& mResourceCache;
|
||||
DeferredBarrierType mDeferredBarrierType = kBarrier_None;
|
||||
DisplayListData* mDisplayListData = nullptr;
|
||||
bool mHighContrastText = false;
|
||||
SkAutoTUnref<SkDrawFilter> mDrawFilter;
|
||||
int mRestoreSaveCount = -1;
|
||||
}; // class RecordingCanvas
|
||||
|
||||
}; // namespace uirenderer
|
||||
}; // namespace android
|
||||
|
||||
#endif // ANDROID_HWUI_RECORDING_CANVAS_H
|
@ -25,6 +25,9 @@
|
||||
|
||||
#include "DamageAccumulator.h"
|
||||
#include "Debug.h"
|
||||
#if HWUI_NEW_OPS
|
||||
#include "RecordedOp.h"
|
||||
#endif
|
||||
#include "DisplayListOp.h"
|
||||
#include "LayerRenderer.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
@ -47,7 +50,7 @@ void RenderNode::debugDumpLayers(const char* prefix) {
|
||||
}
|
||||
if (mDisplayListData) {
|
||||
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
|
||||
mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix);
|
||||
mDisplayListData->children()[i]->renderNode->debugDumpLayers(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +175,7 @@ void RenderNode::copyTo(proto::RenderNode *pnode) {
|
||||
pnode->clear_children();
|
||||
if (mDisplayListData) {
|
||||
for (auto&& child : mDisplayListData->children()) {
|
||||
child->mRenderNode->copyTo(pnode->add_children());
|
||||
child->renderNode->copyTo(pnode->add_children());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -332,6 +335,10 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
|
||||
info.damageAccumulator->popTransform();
|
||||
}
|
||||
|
||||
void RenderNode::syncProperties() {
|
||||
mProperties = mStagingProperties;
|
||||
}
|
||||
|
||||
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
// Push the animators first so that setupStartValueIfNecessary() is called
|
||||
// before properties() is trampled by stagingProperties(), as they are
|
||||
@ -343,7 +350,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
mDirtyPropertyFields = 0;
|
||||
damageSelf(info);
|
||||
info.damageAccumulator->popTransform();
|
||||
mProperties = mStagingProperties;
|
||||
syncProperties();
|
||||
applyLayerPropertiesToLayer(info);
|
||||
// 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
|
||||
@ -364,35 +371,39 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
|
||||
mLayer->setBlend(props.needsBlending());
|
||||
}
|
||||
|
||||
void RenderNode::syncDisplayList() {
|
||||
// Make sure we inc first so that we don't fluctuate between 0 and 1,
|
||||
// which would thrash the layer cache
|
||||
if (mStagingDisplayListData) {
|
||||
for (auto&& child : mStagingDisplayListData->children()) {
|
||||
child->renderNode->incParentRefCount();
|
||||
}
|
||||
}
|
||||
deleteDisplayListData();
|
||||
mDisplayListData = mStagingDisplayListData;
|
||||
mStagingDisplayListData = nullptr;
|
||||
if (mDisplayListData) {
|
||||
for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
|
||||
(*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
|
||||
if (mNeedsDisplayListDataSync) {
|
||||
mNeedsDisplayListDataSync = false;
|
||||
// Make sure we inc first so that we don't fluctuate between 0 and 1,
|
||||
// which would thrash the layer cache
|
||||
if (mStagingDisplayListData) {
|
||||
for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) {
|
||||
mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount();
|
||||
}
|
||||
}
|
||||
// Damage with the old display list first then the new one to catch any
|
||||
// changes in isRenderable or, in the future, bounds
|
||||
damageSelf(info);
|
||||
deleteDisplayListData();
|
||||
mDisplayListData = mStagingDisplayListData;
|
||||
mStagingDisplayListData = nullptr;
|
||||
if (mDisplayListData) {
|
||||
for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
|
||||
(*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
|
||||
}
|
||||
}
|
||||
syncDisplayList();
|
||||
damageSelf(info);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderNode::deleteDisplayListData() {
|
||||
if (mDisplayListData) {
|
||||
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
|
||||
mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
|
||||
for (auto&& child : mDisplayListData->children()) {
|
||||
child->renderNode->decParentRefCount();
|
||||
}
|
||||
}
|
||||
delete mDisplayListData;
|
||||
@ -407,13 +418,17 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
|
||||
info.prepareTextures = cache.prefetchAndMarkInUse(
|
||||
info.canvasContext, subtree->bitmapResources[i]);
|
||||
}
|
||||
for (size_t i = 0; i < subtree->children().size(); i++) {
|
||||
DrawRenderNodeOp* op = subtree->children()[i];
|
||||
RenderNode* childNode = op->mRenderNode;
|
||||
for (auto&& op : subtree->children()) {
|
||||
RenderNode* childNode = op->renderNode;
|
||||
#if HWUI_NEW_OPS
|
||||
info.damageAccumulator->pushTransform(&op->localMatrix);
|
||||
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
|
||||
#else
|
||||
info.damageAccumulator->pushTransform(&op->mTransformFromParent);
|
||||
bool childFunctorsNeedLayer = functorsNeedLayer
|
||||
// Recorded with non-rect clip, or canvas-rotated by parent
|
||||
|| op->mRecordedWithPotentialStencilClip;
|
||||
#endif
|
||||
childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
|
||||
info.damageAccumulator->popTransform();
|
||||
}
|
||||
@ -426,8 +441,8 @@ void RenderNode::destroyHardwareResources() {
|
||||
mLayer = nullptr;
|
||||
}
|
||||
if (mDisplayListData) {
|
||||
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
|
||||
mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources();
|
||||
for (auto&& child : mDisplayListData->children()) {
|
||||
child->renderNode->destroyHardwareResources();
|
||||
}
|
||||
if (mNeedsDisplayListDataSync) {
|
||||
// Next prepare tree we are going to push a new display list, so we can
|
||||
@ -449,6 +464,34 @@ void RenderNode::decParentRefCount() {
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderNode::applyViewProperties(CanvasState& canvasState) const {
|
||||
const Outline& outline = properties().getOutline();
|
||||
if (properties().getAlpha() <= 0
|
||||
|| (outline.getShouldClip() && outline.isEmpty())
|
||||
|| properties().getScaleX() == 0
|
||||
|| properties().getScaleY() == 0) {
|
||||
return false; // rejected
|
||||
}
|
||||
|
||||
if (properties().getLeft() != 0 || properties().getTop() != 0) {
|
||||
canvasState.translate(properties().getLeft(), properties().getTop());
|
||||
}
|
||||
if (properties().getStaticMatrix()) {
|
||||
canvasState.concatMatrix(*properties().getStaticMatrix());
|
||||
} else if (properties().getAnimationMatrix()) {
|
||||
canvasState.concatMatrix(*properties().getAnimationMatrix());
|
||||
}
|
||||
if (properties().hasTransformMatrix()) {
|
||||
if (properties().isTransformTranslateOnly()) {
|
||||
canvasState.translate(properties().getTranslationX(), properties().getTranslationY());
|
||||
} else {
|
||||
canvasState.concatMatrix(*properties().getTransformMatrix());
|
||||
}
|
||||
}
|
||||
return !canvasState.quickRejectConservative(
|
||||
0, 0, properties().getWidth(), properties().getHeight());
|
||||
}
|
||||
|
||||
/*
|
||||
* For property operations, we pass a savecount of 0, since the operations aren't part of the
|
||||
* displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
|
||||
@ -580,6 +623,7 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform)
|
||||
* which are flagged to not draw in the standard draw loop.
|
||||
*/
|
||||
void RenderNode::computeOrdering() {
|
||||
#if !HWUI_NEW_OPS
|
||||
ATRACE_CALL();
|
||||
mProjectedNodes.clear();
|
||||
|
||||
@ -588,14 +632,16 @@ void RenderNode::computeOrdering() {
|
||||
if (mDisplayListData == nullptr) return;
|
||||
for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
|
||||
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
|
||||
childOp->mRenderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
|
||||
childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderNode::computeOrderingImpl(
|
||||
DrawRenderNodeOp* opState,
|
||||
std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
|
||||
const mat4* transformFromProjectionSurface) {
|
||||
#if !HWUI_NEW_OPS
|
||||
mProjectedNodes.clear();
|
||||
if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return;
|
||||
|
||||
@ -619,7 +665,7 @@ void RenderNode::computeOrderingImpl(
|
||||
bool haveAppliedPropertiesToProjection = false;
|
||||
for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
|
||||
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
|
||||
RenderNode* child = childOp->mRenderNode;
|
||||
RenderNode* child = childOp->renderNode;
|
||||
|
||||
std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
|
||||
const mat4* projectionTransform = nullptr;
|
||||
@ -642,6 +688,7 @@ void RenderNode::computeOrderingImpl(
|
||||
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class DeferOperationHandler {
|
||||
@ -701,11 +748,12 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
|
||||
|
||||
void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
|
||||
std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
|
||||
#if !HWUI_NEW_OPS
|
||||
if (chunk.beginChildIndex == chunk.endChildIndex) return;
|
||||
|
||||
for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
|
||||
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
|
||||
RenderNode* child = childOp->mRenderNode;
|
||||
RenderNode* child = childOp->renderNode;
|
||||
float childZ = child->properties().getZ();
|
||||
|
||||
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
|
||||
@ -719,6 +767,7 @@ void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
|
||||
|
||||
// Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
|
||||
std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -824,7 +873,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
|
||||
while (shadowIndex < endIndex || drawIndex < endIndex) {
|
||||
if (shadowIndex < endIndex) {
|
||||
DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
|
||||
RenderNode* caster = casterOp->mRenderNode;
|
||||
RenderNode* caster = casterOp->renderNode;
|
||||
const float casterZ = zTranslatedNodes[shadowIndex].key;
|
||||
// attempt to render the shadow if the caster about to be drawn is its caster,
|
||||
// OR if its caster's Z value is similar to the previous potential caster
|
||||
@ -869,7 +918,7 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
|
||||
const DisplayListOp* op =
|
||||
(mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]);
|
||||
const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op);
|
||||
const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties();
|
||||
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
|
||||
renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
|
||||
|
||||
// If the projection reciever has an outline, we mask projected content to it
|
||||
|
@ -29,8 +29,8 @@
|
||||
|
||||
#include "AnimatorManager.h"
|
||||
#include "Debug.h"
|
||||
#include "Matrix.h"
|
||||
#include "DisplayList.h"
|
||||
#include "Matrix.h"
|
||||
#include "RenderProperties.h"
|
||||
|
||||
#include <vector>
|
||||
@ -43,6 +43,7 @@ class SkRegion;
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class CanvasState;
|
||||
class DisplayListOp;
|
||||
class DisplayListCanvas;
|
||||
class OpenGLRenderer;
|
||||
@ -74,6 +75,7 @@ class RenderNode;
|
||||
* attached.
|
||||
*/
|
||||
class RenderNode : public VirtualLightRefBase {
|
||||
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
|
||||
public:
|
||||
enum DirtyPropertyMask {
|
||||
GENERIC = 1 << 1,
|
||||
@ -176,8 +178,25 @@ public:
|
||||
|
||||
AnimatorManager& animators() { return mAnimatorManager; }
|
||||
|
||||
// Returns false if the properties dictate the subtree contained in this RenderNode won't render
|
||||
bool applyViewProperties(CanvasState& canvasState) const;
|
||||
|
||||
void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
|
||||
|
||||
bool nothingToDraw() const {
|
||||
const Outline& outline = properties().getOutline();
|
||||
return mDisplayListData == nullptr
|
||||
|| properties().getAlpha() <= 0
|
||||
|| (outline.getShouldClip() && outline.isEmpty())
|
||||
|| properties().getScaleX() == 0
|
||||
|| properties().getScaleY() == 0;
|
||||
}
|
||||
|
||||
// Only call if RenderNode has DisplayListData...
|
||||
const DisplayListData& getDisplayListData() const {
|
||||
return *mDisplayListData;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
|
||||
|
||||
@ -235,6 +254,10 @@ private:
|
||||
const char* mText;
|
||||
};
|
||||
|
||||
|
||||
void syncProperties();
|
||||
void syncDisplayList();
|
||||
|
||||
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
|
||||
void pushStagingPropertiesChanges(TreeInfo& info);
|
||||
void pushStagingDisplayListChanges(TreeInfo& info);
|
||||
|
@ -18,12 +18,22 @@
|
||||
#include <utils/Singleton.h>
|
||||
|
||||
#include "DisplayList.h"
|
||||
#if HWUI_NEW_OPS
|
||||
#include "RecordingCanvas.h"
|
||||
#else
|
||||
#include "DisplayListCanvas.h"
|
||||
#endif
|
||||
#include "microbench/MicroBench.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace android::uirenderer;
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
typedef RecordingCanvas TestCanvas;
|
||||
#else
|
||||
typedef DisplayListCanvas TestCanvas;
|
||||
#endif
|
||||
|
||||
BENCHMARK_NO_ARG(BM_DisplayListData_alloc);
|
||||
void BM_DisplayListData_alloc::Run(int iters) {
|
||||
StartBenchmarkTiming();
|
||||
@ -48,7 +58,7 @@ void BM_DisplayListData_alloc_theoretical::Run(int iters) {
|
||||
|
||||
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty);
|
||||
void BM_DisplayListCanvas_record_empty::Run(int iters) {
|
||||
DisplayListCanvas canvas(100, 100);
|
||||
TestCanvas canvas(100, 100);
|
||||
canvas.finishRecording();
|
||||
|
||||
StartBenchmarkTiming();
|
||||
@ -62,7 +72,7 @@ void BM_DisplayListCanvas_record_empty::Run(int iters) {
|
||||
|
||||
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore);
|
||||
void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
|
||||
DisplayListCanvas canvas(100, 100);
|
||||
TestCanvas canvas(100, 100);
|
||||
canvas.finishRecording();
|
||||
|
||||
StartBenchmarkTiming();
|
||||
@ -80,7 +90,7 @@ void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
|
||||
|
||||
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate);
|
||||
void BM_DisplayListCanvas_record_translate::Run(int iters) {
|
||||
DisplayListCanvas canvas(100, 100);
|
||||
TestCanvas canvas(100, 100);
|
||||
canvas.finishRecording();
|
||||
|
||||
StartBenchmarkTiming();
|
||||
|
@ -28,6 +28,11 @@
|
||||
#include "renderstate/Stencil.h"
|
||||
#include "protos/hwui.pb.h"
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
#include "BakedOpRenderer.h"
|
||||
#include "OpReorderer.h"
|
||||
#endif
|
||||
|
||||
#include <cutils/properties.h>
|
||||
#include <google/protobuf/io/zero_copy_stream_impl.h>
|
||||
#include <private/hwui/DrawGlInfo.h>
|
||||
@ -121,9 +126,11 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
|
||||
|
||||
bool CanvasContext::initialize(ANativeWindow* window) {
|
||||
setSurface(window);
|
||||
#if !HWUI_NEW_OPS
|
||||
if (mCanvas) return false;
|
||||
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
|
||||
mCanvas->initProperties();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -162,11 +169,13 @@ void CanvasContext::makeCurrent() {
|
||||
}
|
||||
|
||||
void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
|
||||
#if !HWUI_NEW_OPS
|
||||
bool success = layerUpdater->apply();
|
||||
LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
|
||||
if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
|
||||
mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool wasSkipped(FrameInfo* info) {
|
||||
@ -239,8 +248,10 @@ void CanvasContext::notifyFramePending() {
|
||||
}
|
||||
|
||||
void CanvasContext::draw() {
|
||||
#if !HWUI_NEW_OPS
|
||||
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
|
||||
"drawRenderNode called on a context with no canvas or surface!");
|
||||
#endif
|
||||
|
||||
SkRect dirty;
|
||||
mDamageAccumulator.finish(&dirty);
|
||||
@ -254,6 +265,8 @@ void CanvasContext::draw() {
|
||||
mCurrentFrameInfo->markIssueDrawCommandsStart();
|
||||
|
||||
Frame frame = mEglManager.beginFrame(mEglSurface);
|
||||
|
||||
#if !HWUI_NEW_OPS
|
||||
if (frame.width() != mCanvas->getViewportWidth()
|
||||
|| frame.height() != mCanvas->getViewportHeight()) {
|
||||
// can't rely on prior content of window if viewport size changes
|
||||
@ -417,6 +430,16 @@ void CanvasContext::draw() {
|
||||
// Even if we decided to cancel the frame, from the perspective of jank
|
||||
// metrics the frame was swapped at this point
|
||||
mCurrentFrameInfo->markSwapBuffers();
|
||||
#else
|
||||
OpReorderer reorderer;
|
||||
reorderer.defer(frame.width(), frame.height(), mRenderNodes);
|
||||
BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
|
||||
frame.width(), frame.height(), mOpaque);
|
||||
reorderer.replayBakedOps<BakedOpRenderer>(&info);
|
||||
|
||||
bool drew = info.didDraw;
|
||||
SkRect screenDirty = SkRect::MakeWH(frame.width(), frame.height());
|
||||
#endif
|
||||
|
||||
if (drew) {
|
||||
if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <AnimationContext.h>
|
||||
#include <DisplayListCanvas.h>
|
||||
#include <RecordingCanvas.h>
|
||||
#include <RenderNode.h>
|
||||
#include <renderthread/RenderProxy.h>
|
||||
#include <renderthread/RenderTask.h>
|
||||
@ -39,6 +40,13 @@ using namespace android::uirenderer;
|
||||
using namespace android::uirenderer::renderthread;
|
||||
using namespace android::uirenderer::test;
|
||||
|
||||
#if HWUI_NEW_OPS
|
||||
typedef RecordingCanvas TestCanvas;
|
||||
#else
|
||||
typedef DisplayListCanvas TestCanvas;
|
||||
#endif
|
||||
|
||||
|
||||
class ContextFactory : public IContextFactory {
|
||||
public:
|
||||
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
|
||||
@ -46,13 +54,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static DisplayListCanvas* startRecording(RenderNode* node) {
|
||||
DisplayListCanvas* renderer = new DisplayListCanvas(
|
||||
static TestCanvas* startRecording(RenderNode* node) {
|
||||
TestCanvas* renderer = new TestCanvas(
|
||||
node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
|
||||
return renderer;
|
||||
}
|
||||
|
||||
static void endRecording(DisplayListCanvas* renderer, RenderNode* node) {
|
||||
static void endRecording(TestCanvas* renderer, RenderNode* node) {
|
||||
node->setStagingDisplayList(renderer->finishRecording());
|
||||
delete renderer;
|
||||
}
|
||||
@ -67,7 +75,7 @@ public:
|
||||
frameCount = fc;
|
||||
}
|
||||
}
|
||||
virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0;
|
||||
virtual void createContent(int width, int height, TestCanvas* renderer) = 0;
|
||||
virtual void doFrame(int frameNr) = 0;
|
||||
|
||||
template <class T>
|
||||
@ -102,7 +110,7 @@ public:
|
||||
|
||||
android::uirenderer::Rect DUMMY;
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(rootNode);
|
||||
TestCanvas* renderer = startRecording(rootNode);
|
||||
animation.createContent(width, height, renderer);
|
||||
endRecording(renderer, rootNode);
|
||||
|
||||
@ -132,7 +140,7 @@ public:
|
||||
class ShadowGridAnimation : public TreeContentAnimation {
|
||||
public:
|
||||
std::vector< sp<RenderNode> > cards;
|
||||
void createContent(int width, int height, DisplayListCanvas* renderer) override {
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
|
||||
renderer->insertReorderBarrier(true);
|
||||
|
||||
@ -163,7 +171,7 @@ private:
|
||||
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(node.get());
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
|
||||
endRecording(renderer, node.get());
|
||||
return node;
|
||||
@ -179,7 +187,7 @@ static Benchmark _ShadowGrid(BenchmarkInfo{
|
||||
class ShadowGrid2Animation : public TreeContentAnimation {
|
||||
public:
|
||||
std::vector< sp<RenderNode> > cards;
|
||||
void createContent(int width, int height, DisplayListCanvas* renderer) override {
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
|
||||
renderer->insertReorderBarrier(true);
|
||||
|
||||
@ -210,7 +218,7 @@ private:
|
||||
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(node.get());
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
|
||||
endRecording(renderer, node.get());
|
||||
return node;
|
||||
@ -226,7 +234,7 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{
|
||||
class RectGridAnimation : public TreeContentAnimation {
|
||||
public:
|
||||
sp<RenderNode> card;
|
||||
void createContent(int width, int height, DisplayListCanvas* renderer) override {
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
|
||||
renderer->insertReorderBarrier(true);
|
||||
|
||||
@ -247,7 +255,7 @@ private:
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(node.get());
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
|
||||
|
||||
SkRegion region;
|
||||
@ -275,7 +283,7 @@ static Benchmark _RectGrid(BenchmarkInfo{
|
||||
class OvalAnimation : public TreeContentAnimation {
|
||||
public:
|
||||
sp<RenderNode> card;
|
||||
void createContent(int width, int height, DisplayListCanvas* renderer) override {
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
|
||||
renderer->insertReorderBarrier(true);
|
||||
|
||||
@ -297,7 +305,7 @@ private:
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(node.get());
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
@ -317,7 +325,7 @@ static Benchmark _Oval(BenchmarkInfo{
|
||||
class PartialDamageTest : public TreeContentAnimation {
|
||||
public:
|
||||
std::vector< sp<RenderNode> > cards;
|
||||
void createContent(int width, int height, DisplayListCanvas* renderer) override {
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
static SkColor COLORS[] = {
|
||||
0xFFF44336,
|
||||
0xFF9C27B0,
|
||||
@ -342,7 +350,7 @@ public:
|
||||
cards[0]->mutateStagingProperties().setTranslationY(curFrame);
|
||||
cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(cards[0].get());
|
||||
TestCanvas* renderer = startRecording(cards[0].get());
|
||||
renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
|
||||
SkXfermode::kSrcOver_Mode);
|
||||
endRecording(renderer, cards[0].get());
|
||||
@ -370,7 +378,7 @@ private:
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
DisplayListCanvas* renderer = startRecording(node.get());
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
|
||||
endRecording(renderer, node.get());
|
||||
return node;
|
||||
@ -383,3 +391,43 @@ static Benchmark _PartialDamage(BenchmarkInfo{
|
||||
"EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
|
||||
TreeContentAnimation::run<PartialDamageTest>
|
||||
});
|
||||
|
||||
|
||||
class SimpleRectGridAnimation : public TreeContentAnimation {
|
||||
public:
|
||||
sp<RenderNode> card;
|
||||
void createContent(int width, int height, TestCanvas* renderer) override {
|
||||
SkPaint paint;
|
||||
paint.setColor(0xFF00FFFF);
|
||||
renderer->drawRect(0, 0, width, height, paint);
|
||||
|
||||
card = createCard(40, 40, 200, 200);
|
||||
renderer->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);
|
||||
}
|
||||
private:
|
||||
sp<RenderNode> createCard(int x, int y, int width, int height) {
|
||||
sp<RenderNode> node = new RenderNode();
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
TestCanvas* renderer = startRecording(node.get());
|
||||
SkPaint paint;
|
||||
paint.setColor(0xFFFF00FF);
|
||||
renderer->drawRect(0, 0, width, height, paint);
|
||||
|
||||
endRecording(renderer, node.get());
|
||||
return node;
|
||||
}
|
||||
};
|
||||
static Benchmark _SimpleRectGrid(BenchmarkInfo{
|
||||
"simplerectgrid",
|
||||
"A simple collection of rects. "
|
||||
"Low CPU/GPU load.",
|
||||
TreeContentAnimation::run<SimpleRectGridAnimation>
|
||||
});
|
||||
|
97
libs/hwui/unit_tests/BakedOpStateTests.cpp
Normal file
97
libs/hwui/unit_tests/BakedOpStateTests.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 <BakedOpState.h>
|
||||
#include <RecordedOp.h>
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
TEST(ResolvedRenderState, resolution) {
|
||||
Matrix4 identity;
|
||||
identity.loadIdentity();
|
||||
|
||||
Matrix4 translate10x20;
|
||||
translate10x20.loadTranslate(10, 20, 0);
|
||||
|
||||
SkPaint paint;
|
||||
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint);
|
||||
{
|
||||
// recorded with transform, no parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp);
|
||||
EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
|
||||
EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200));
|
||||
EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped
|
||||
}
|
||||
{
|
||||
// recorded with transform and parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(0, 0, 100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp);
|
||||
|
||||
Matrix4 expectedTranslate;
|
||||
expectedTranslate.loadTranslate(20, 40, 0);
|
||||
EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate);
|
||||
|
||||
// intersection of parent & transformed child clip
|
||||
EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200));
|
||||
|
||||
// translated and also clipped
|
||||
EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BakedOpState, constructAndReject) {
|
||||
LinearAllocator allocator;
|
||||
|
||||
Matrix4 identity;
|
||||
identity.loadIdentity();
|
||||
|
||||
Matrix4 translate100x0;
|
||||
translate100x0.loadTranslate(100, 0, 0);
|
||||
|
||||
SkPaint paint;
|
||||
{
|
||||
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
|
||||
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
|
||||
|
||||
EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
|
||||
EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
|
||||
}
|
||||
{
|
||||
RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
|
||||
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
|
||||
|
||||
EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
|
||||
EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
|
||||
}
|
||||
}
|
||||
|
||||
#define UNSUPPORTED_OP(Info, Type) \
|
||||
static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
|
||||
|
||||
class Info {
|
||||
public:
|
||||
int index = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -101,10 +101,9 @@ TEST(ClipArea, paths) {
|
||||
EXPECT_FALSE(area.isEmpty());
|
||||
EXPECT_FALSE(area.isSimple());
|
||||
EXPECT_FALSE(area.isRectangleList());
|
||||
|
||||
Rect clipRect(area.getClipRect());
|
||||
clipRect.dump("clipRect");
|
||||
Rect expected(0, 0, r * 2, r * 2);
|
||||
expected.dump("expected");
|
||||
EXPECT_EQ(expected, clipRect);
|
||||
SkRegion clipRegion(area.getClipRegion());
|
||||
auto skRect(clipRegion.getBounds());
|
||||
|
161
libs/hwui/unit_tests/OpReordererTests.cpp
Normal file
161
libs/hwui/unit_tests/OpReordererTests.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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 <BakedOpState.h>
|
||||
#include <OpReorderer.h>
|
||||
#include <RecordedOp.h>
|
||||
#include <RecordingCanvas.h>
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
#define UNSUPPORTED_OP(Info, Type) \
|
||||
static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
|
||||
|
||||
class Info {
|
||||
public:
|
||||
int index = 0;
|
||||
};
|
||||
|
||||
class SimpleReceiver {
|
||||
public:
|
||||
static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
|
||||
EXPECT_EQ(1, info->index++);
|
||||
}
|
||||
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
|
||||
EXPECT_EQ(0, info->index++);
|
||||
}
|
||||
UNSUPPORTED_OP(Info, RenderNodeOp)
|
||||
UNSUPPORTED_OP(Info, SimpleRectsOp)
|
||||
static void startFrame(Info& info) {}
|
||||
static void endFrame(Info& info) {}
|
||||
};
|
||||
TEST(OpReorderer, simple) {
|
||||
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
SkBitmap bitmap;
|
||||
bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
|
||||
|
||||
canvas.drawRect(0, 0, 100, 200, SkPaint());
|
||||
canvas.drawBitmap(bitmap, 10, 10, nullptr);
|
||||
});
|
||||
|
||||
OpReorderer reorderer;
|
||||
reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
|
||||
|
||||
Info info;
|
||||
reorderer.replayBakedOps<SimpleReceiver>(&info);
|
||||
}
|
||||
|
||||
|
||||
static int SIMPLE_BATCHING_LOOPS = 5;
|
||||
class SimpleBatchingReceiver {
|
||||
public:
|
||||
static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
|
||||
EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
|
||||
}
|
||||
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
|
||||
EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
|
||||
}
|
||||
UNSUPPORTED_OP(Info, RenderNodeOp)
|
||||
UNSUPPORTED_OP(Info, SimpleRectsOp)
|
||||
static void startFrame(Info& info) {}
|
||||
static void endFrame(Info& info) {}
|
||||
};
|
||||
TEST(OpReorderer, simpleBatching) {
|
||||
auto dld = TestUtils::createDLD<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
|
||||
SkBitmap bitmap;
|
||||
bitmap.setInfo(SkImageInfo::MakeUnknown(10, 10));
|
||||
|
||||
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
|
||||
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
|
||||
canvas.translate(0, 10);
|
||||
canvas.drawRect(0, 0, 10, 10, SkPaint());
|
||||
canvas.drawBitmap(bitmap, 5, 0, nullptr);
|
||||
}
|
||||
canvas.restore();
|
||||
});
|
||||
|
||||
OpReorderer reorderer;
|
||||
reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
|
||||
|
||||
Info info;
|
||||
reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
|
||||
EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
|
||||
}
|
||||
|
||||
class RenderNodeReceiver {
|
||||
public:
|
||||
UNSUPPORTED_OP(Info, BitmapOp)
|
||||
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
|
||||
switch(info->index++) {
|
||||
case 0:
|
||||
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
|
||||
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
|
||||
break;
|
||||
case 1:
|
||||
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
|
||||
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
|
||||
break;
|
||||
default:
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
UNSUPPORTED_OP(Info, RenderNodeOp)
|
||||
UNSUPPORTED_OP(Info, SimpleRectsOp)
|
||||
static void startFrame(Info& info) {}
|
||||
static void endFrame(Info& info) {}
|
||||
};
|
||||
TEST(OpReorderer, renderNode) {
|
||||
sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorWHITE);
|
||||
canvas.drawRect(0, 0, 100, 100, paint);
|
||||
});
|
||||
|
||||
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.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
canvas.translate(40, 40);
|
||||
canvas.drawRenderNode(childPtr);
|
||||
canvas.restore();
|
||||
});
|
||||
|
||||
TestUtils::syncNodePropertiesAndDisplayList(child);
|
||||
TestUtils::syncNodePropertiesAndDisplayList(parent);
|
||||
|
||||
std::vector< sp<RenderNode> > nodes;
|
||||
nodes.push_back(parent.get());
|
||||
|
||||
OpReorderer reorderer;
|
||||
reorderer.defer(200, 200, nodes);
|
||||
|
||||
Info info;
|
||||
reorderer.replayBakedOps<RenderNodeReceiver>(&info);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
112
libs/hwui/unit_tests/RecordingCanvasTests.cpp
Normal file
112
libs/hwui/unit_tests/RecordingCanvasTests.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 <RecordedOp.h>
|
||||
#include <RecordingCanvas.h>
|
||||
#include <unit_tests/TestUtils.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
static void playbackOps(const std::vector<DisplayListData::Chunk>& chunks,
|
||||
const std::vector<RecordedOp*>& ops, std::function<void(const RecordedOp&)> opReciever) {
|
||||
for (const DisplayListData::Chunk& chunk : chunks) {
|
||||
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
|
||||
opReciever(*ops[opIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, emptyPlayback) {
|
||||
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
canvas.restore();
|
||||
});
|
||||
playbackOps(dld->getChunks(), dld->getOps(), [](const RecordedOp& op) { FAIL(); });
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, testSimpleRectRecord) {
|
||||
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
canvas.drawRect(10, 20, 90, 180, SkPaint());
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
|
||||
count++;
|
||||
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
|
||||
ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
|
||||
});
|
||||
ASSERT_EQ(1, count); // only one observed
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, backgroundAndImage) {
|
||||
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
SkBitmap bitmap;
|
||||
bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorBLUE);
|
||||
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
{
|
||||
// a background!
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
canvas.drawRect(0, 0, 100, 200, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
{
|
||||
// an image!
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
canvas.translate(25, 25);
|
||||
canvas.scale(2, 2);
|
||||
canvas.drawBitmap(bitmap, 0, 0, nullptr);
|
||||
canvas.restore();
|
||||
}
|
||||
canvas.restore();
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
|
||||
if (count == 0) {
|
||||
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
ASSERT_NE(nullptr, op.paint);
|
||||
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
|
||||
EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
|
||||
|
||||
Matrix4 expectedMatrix;
|
||||
expectedMatrix.loadIdentity();
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
|
||||
} else {
|
||||
ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
|
||||
EXPECT_EQ(nullptr, op.paint);
|
||||
EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
|
||||
|
||||
Matrix4 expectedMatrix;
|
||||
expectedMatrix.loadTranslate(25, 25, 0);
|
||||
expectedMatrix.scale(2, 2, 1);
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
|
||||
}
|
||||
count++;
|
||||
});
|
||||
ASSERT_EQ(2, count); // two draws observed
|
||||
}
|
||||
|
||||
}
|
||||
}
|
80
libs/hwui/unit_tests/TestUtils.h
Normal file
80
libs/hwui/unit_tests/TestUtils.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 TEST_UTILS_H
|
||||
#define TEST_UTILS_H
|
||||
|
||||
#include <Matrix.h>
|
||||
#include <Snapshot.h>
|
||||
#include <RenderNode.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
#define EXPECT_MATRIX_APPROX_EQ(a, b) \
|
||||
EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
|
||||
|
||||
class TestUtils {
|
||||
public:
|
||||
static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (!MathUtils::areEqual(a[i], b[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) {
|
||||
std::unique_ptr<Snapshot> snapshot(new Snapshot());
|
||||
snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op);
|
||||
*(snapshot->transform) = transform;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
template<class CanvasType>
|
||||
static std::unique_ptr<DisplayListData> createDLD(int width, int height,
|
||||
std::function<void(CanvasType& canvas)> canvasCallback) {
|
||||
CanvasType canvas(width, height);
|
||||
canvasCallback(canvas);
|
||||
return std::unique_ptr<DisplayListData>(canvas.finishRecording());
|
||||
}
|
||||
|
||||
template<class CanvasType>
|
||||
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
|
||||
std::function<void(CanvasType& canvas)> canvasCallback) {
|
||||
sp<RenderNode> node = new RenderNode();
|
||||
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
|
||||
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
|
||||
CanvasType canvas(
|
||||
node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
|
||||
canvasCallback(canvas);
|
||||
node->setStagingDisplayList(canvas.finishRecording());
|
||||
return node;
|
||||
}
|
||||
|
||||
static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
|
||||
node->syncProperties();
|
||||
node->syncDisplayList();
|
||||
}
|
||||
}; // class TestUtils
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
#endif /* TEST_UTILS_H */
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <SkColorFilter.h>
|
||||
#include <SkDrawLooper.h>
|
||||
#include <SkShader.h>
|
||||
#include <SkXfermode.h>
|
||||
|
||||
namespace android {
|
||||
|
Reference in New Issue
Block a user