Stencil support in new recorder/reorderer

bug:22480459
bug:26358504

Adds complex (non-rectangular) clipping support, and overdraw
visualization. Doesn't support stencil clipping in layers.

Change-Id: I8d10c7f1d2769ab5756774ca672344cc09901f87
This commit is contained in:
Chris Craik
2015-12-22 16:32:23 -08:00
parent 33e9b426da
commit e4db79de12
28 changed files with 929 additions and 233 deletions

View File

@@ -108,6 +108,7 @@ ifeq (true, $(HWUI_NEW_OPS))
hwui_src_files += \
BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
BakedOpState.cpp \
OpReorderer.cpp \
RecordingCanvas.cpp

View File

@@ -79,7 +79,9 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
ClipRect renderTargetClip(opList.clip);
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
renderer.renderGlop(nullptr, clip, glop);
}
void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
@@ -183,7 +185,9 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
ClipRect renderTargetClip(opList.clip);
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
renderer.renderGlop(nullptr, clip, glop);
}
static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
@@ -224,7 +228,7 @@ enum class TextRenderType {
};
static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
const Rect* renderClip, TextRenderType renderType) {
const ClipBase* renderClip, TextRenderType renderType) {
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
@@ -272,7 +276,7 @@ static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const Bake
bool forceFinish = (renderType == TextRenderType::Flush);
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect : nullptr;
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
fontRenderer.renderPosText(op.paint, localOpClip,
(const char*) op.glyphs, op.glyphCount, x, y,
op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish);
@@ -287,7 +291,8 @@ static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const Bake
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
const MergedBakedOpList& opList) {
const Rect* clip = opList.clipSideFlags ? &opList.clip : nullptr;
ClipRect renderTargetClip(opList.clip);
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
for (size_t i = 0; i < opList.count; i++) {
const BakedOpState& state = *(opList.states[i]);
const TextOp& op = *(static_cast<const TextOp*>(state.op));
@@ -701,14 +706,13 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR
}
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
const Rect* clip = state.computedState.clipSideFlags ? &state.computedState.clipRect : nullptr;
renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
renderTextOp(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
}
void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
// Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
// TODO: respect clipSideFlags, once we record with bounds
const Rect* renderTargetClip = &state.computedState.clipRect;
auto renderTargetClip = state.computedState.clipState;
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
fontRenderer.setFont(op.paint, SkMatrix::I());

View File

@@ -62,15 +62,17 @@ void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const
void BakedOpRenderer::endLayer() {
mRenderTarget.offscreenBuffer->updateMeshFromRegion();
mRenderTarget.offscreenBuffer = nullptr;
mRenderTarget.lastStencilClip = nullptr;
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
mRenderTarget.frameBufferId = -1;
mRenderTarget.frameBufferId = 0;
}
void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0");
mRenderState.bindFramebuffer(0);
setViewport(width, height);
mCaches.clearGarbage();
@@ -78,9 +80,39 @@ void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& re
if (!mOpaque) {
clearColorBuffer(repaintRect);
}
mRenderState.debugOverdraw(true, true);
}
void BakedOpRenderer::endFrame() {
void BakedOpRenderer::endFrame(const Rect& repaintRect) {
if (CC_UNLIKELY(Properties::debugOverdraw)) {
ClipRect overdrawClip(repaintRect);
Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight);
// overdraw visualization
for (int i = 1; i <= 4; i++) {
if (i < 4) {
// nth level of overdraw tests for n+1 draws per pixel
mRenderState.stencil().enableDebugTest(i + 1, false);
} else {
// 4th level tests for 4 or higher draws per pixel
mRenderState.stencil().enableDebugTest(4, true);
}
SkPaint paint;
paint.setColor(mCaches.getOverdrawColor(i));
Glop glop;
GlopBuilder(mRenderState, mCaches, &glop)
.setRoundRectClipState(nullptr)
.setMeshUnitQuad()
.setFillPaint(paint, 1.0f)
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewMapUnitToRect(viewportRect)
.build();
renderGlop(nullptr, &overdrawClip, glop);
}
mRenderState.stencil().disable();
}
mCaches.pathCache.trim();
mCaches.tessellationCache.trim();
@@ -128,12 +160,104 @@ Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
return texture;
}
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const Rect* clip) {
// clears and re-fills stencil with provided rendertarget space quads,
// and then put stencil into test mode
void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices,
int incrementThreshold) {
mRenderState.stencil().enableWrite(incrementThreshold);
mRenderState.stencil().clear();
Glop glop;
GlopBuilder(mRenderState, mCaches, &glop)
.setRoundRectClipState(nullptr)
.setMeshIndexedQuads(quadVertices.data(), quadVertices.size() / 4)
.setFillBlack()
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewIdentityEmptyBounds()
.build();
mRenderState.render(glop, mRenderTarget.orthoMatrix);
mRenderState.stencil().enableTest(incrementThreshold);
}
void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) {
auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
int quadCount = rectList.getTransformedRectanglesCount();
std::vector<Vertex> rectangleVertices;
rectangleVertices.reserve(quadCount * 4);
for (int i = 0; i < quadCount; i++) {
const TransformedRectangle& tr(rectList.getTransformedRectangle(i));
const Matrix4& transform = tr.getTransform();
Rect bounds = tr.getBounds();
if (transform.rectToRect()) {
// If rectToRect, can simply map bounds before storing verts
transform.mapRect(bounds);
bounds.doIntersect(clip->rect);
if (bounds.isEmpty()) {
continue; // will be outside of scissor, skip
}
}
rectangleVertices.push_back(Vertex{bounds.left, bounds.top});
rectangleVertices.push_back(Vertex{bounds.right, bounds.top});
rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom});
rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom});
if (!transform.rectToRect()) {
// If not rectToRect, must map each point individually
for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) {
transform.mapPoint(cur->x, cur->y);
}
}
}
setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount());
}
void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) {
auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region;
std::vector<Vertex> regionVertices;
SkRegion::Cliperator it(region, clip->rect.toSkIRect());
while (!it.done()) {
const SkIRect& r = it.rect();
regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop});
regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop});
regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom});
regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom});
it.next();
}
setupStencilQuads(regionVertices, 0);
}
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
// prepare scissor / stencil
mRenderState.scissor().setEnabled(clip != nullptr);
if (clip) {
mRenderState.scissor().set(clip->left, mRenderTarget.viewportHeight - clip->bottom,
clip->getWidth(), clip->getHeight());
mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
if (CC_LIKELY(!Properties::debugOverdraw)) {
// only modify stencil mode and content when it's not used for overdraw visualization
if (CC_UNLIKELY(clip->mode != ClipMode::Rectangle)) {
// NOTE: this pointer check is only safe for non-rect clips,
// since rect clips may be created on the stack
if (mRenderTarget.lastStencilClip != clip) {
// Stencil needed, but current stencil isn't up to date
mRenderTarget.lastStencilClip = clip;
if (mRenderTarget.offscreenBuffer) {
LOG_ALWAYS_FATAL("prepare layer stencil");
}
if (clip->mode == ClipMode::RectangleList) {
setupStencilRectList(clip);
} else {
setupStencilRegion(clip);
}
}
} else {
mRenderState.stencil().disable();
}
}
}
// dirty offscreenbuffer
if (dirtyBounds && mRenderTarget.offscreenBuffer) {
// register layer damage to draw-back region
android::Rect dirty(dirtyBounds->left, dirtyBounds->top,
@@ -142,17 +266,18 @@ void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const Rect* clip) {
}
}
void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop) {
void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const ClipBase* clip,
const Glop& glop) {
prepareRender(dirtyBounds, clip);
mRenderState.render(glop, mRenderTarget.orthoMatrix);
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
void BakedOpRenderer::renderFunctor(const FunctorOp& op, const BakedOpState& state) {
prepareRender(&state.computedState.clippedBounds, &state.computedState.clipRect);
prepareRender(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded());
DrawGlInfo info;
auto&& clip = state.computedState.clipRect;
auto&& clip = state.computedState.clipRect();
info.clipLeft = clip.left;
info.clipTop = clip.top;
info.clipRight = clip.right;

View File

@@ -27,6 +27,7 @@ class Caches;
struct Glop;
class Layer;
class RenderState;
struct ClipBase;
/**
* Main rendering manager for a collection of work - one frame + any contained FBOs.
@@ -59,7 +60,7 @@ public:
Caches& caches() { return mCaches; }
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
void endFrame();
void endFrame(const Rect& repaintRect);
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
void endLayer();
@@ -68,21 +69,23 @@ public:
const LightInfo& getLightInfo() const { return mLightInfo; }
void renderGlop(const BakedOpState& state, const Glop& glop) {
bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
renderGlop(&state.computedState.clippedBounds,
useScissor ? &state.computedState.clipRect : nullptr,
state.computedState.getClipIfNeeded(),
glop);
}
void renderFunctor(const FunctorOp& op, const BakedOpState& state);
void renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop);
void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop);
bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; }
void dirtyRenderTarget(const Rect& dirtyRect);
bool didDraw() const { return mHasDrawn; }
private:
void setViewport(uint32_t width, uint32_t height);
void clearColorBuffer(const Rect& clearRect);
void prepareRender(const Rect* dirtyBounds, const Rect* clip);
void prepareRender(const Rect* dirtyBounds, const ClipBase* clip);
void setupStencilRectList(const ClipBase* clip);
void setupStencilRegion(const ClipBase* clip);
void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold);
RenderState& mRenderState;
Caches& mCaches;
@@ -97,6 +100,7 @@ private:
uint32_t viewportWidth = 0;
uint32_t viewportHeight = 0;
Matrix4 orthoMatrix;
const ClipBase* lastStencilClip = nullptr;
} mRenderTarget;
const LightInfo mLightInfo;

View File

@@ -0,0 +1,79 @@
/*
* 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 "BakedOpState.h"
#include "ClipArea.h"
namespace android {
namespace uirenderer {
ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
const RecordedOp& recordedOp, bool expandForStroke) {
// resolvedMatrix = parentMatrix * localMatrix
transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
clippedBounds = recordedOp.unmappedBounds;
if (CC_UNLIKELY(expandForStroke)) {
// account for non-hairline stroke
clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
}
transform.mapRect(clippedBounds);
if (CC_UNLIKELY(expandForStroke
&& (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
// account for hairline stroke when stroke may be < 1 scaled pixel
// Non translate || strokeWidth < 1 is conservative, but will cover all cases
clippedBounds.outset(0.5f);
}
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
clipState = snapshot.mutateClipArea().serializeIntersectedClip(allocator,
recordedOp.localClip, *(snapshot.transform));
LOG_ALWAYS_FATAL_IF(!clipState, "must clip!");
const Rect& clipRect = clipState->rect;
if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
// Rejected based on either empty clip, or bounds not intersecting with clip
if (clipState) {
allocator.rewindIfLastAlloc(clipState);
clipState = nullptr;
}
clippedBounds.setEmpty();
} else {
// Not rejected! compute true clippedBounds and clipSideFlags
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);
}
}
ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) {
transform = *snapshot.transform;
// Since the op doesn't have known bounds, we conservatively set the mapped bounds
// to the current clipRect, and clipSideFlags to Full.
clipState = snapshot.mutateClipArea().serializeClip(allocator);
LOG_ALWAYS_FATAL_IF(!clipState, "clipState required");
clippedBounds = clipState->rect;
transform.mapRect(clippedBounds);
clipSideFlags = OpClipSideFlags::Full;
}
} // namespace uirenderer
} // namespace android

View File

@@ -52,89 +52,35 @@ struct MergedBakedOpList {
*/
class ResolvedRenderState {
public:
// TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) {
/* 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);
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
const RecordedOp& recordedOp, bool expandForStroke);
// 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.getRenderTargetClip());
clipRect.snapToPixelBoundaries();
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
clippedBounds = recordedOp.unmappedBounds;
if (CC_UNLIKELY(expandForStroke)) {
// account for non-hairline stroke
clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
}
transform.mapRect(clippedBounds);
if (CC_UNLIKELY(expandForStroke
&& (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
// account for hairline stroke when stroke may be < 1 scaled pixel
// Non translate || strokeWidth < 1 is conservative, but will cover all cases
clippedBounds.outset(0.5f);
}
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.
*/
}
/**
* Constructor for unbounded ops without transform/clip (namely shadows)
*
* Since the op doesn't have known bounds, we conservatively set the mapped bounds
* to the current clipRect, and clipSideFlags to Full.
*/
ResolvedRenderState(const Snapshot& snapshot) {
transform = *snapshot.transform;
clipRect = snapshot.getRenderTargetClip();
clippedBounds = clipRect;
transform.mapRect(clippedBounds);
clipSideFlags = OpClipSideFlags::Full;
}
// Constructor for unbounded ops without transform/clip (namely shadows)
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
Rect computeLocalSpaceClip() const {
Matrix4 inverse;
inverse.loadInverse(transform);
Rect outClip(clipRect);
Rect outClip(clipRect());
inverse.mapRect(outClip);
return outClip;
}
Matrix4 transform;
Rect clipRect;
const Rect& clipRect() const {
return clipState->rect;
}
bool requiresClip() const {
return clipSideFlags != OpClipSideFlags::None
|| CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
}
// returns the clip if it's needed to draw the operation, otherwise nullptr
const ClipBase* getClipIfNeeded() const {
return requiresClip() ? clipState : nullptr;
}
const ClipBase* clipState = nullptr;
int clipSideFlags = 0;
Rect clippedBounds;
};
@@ -147,8 +93,9 @@ public:
class BakedOpState {
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
const Snapshot& snapshot, const RecordedOp& recordedOp) {
BakedOpState* bakedState = new (allocator) BakedOpState(snapshot, recordedOp, false);
Snapshot& snapshot, const RecordedOp& recordedOp) {
BakedOpState* bakedState = new (allocator) BakedOpState(
allocator, snapshot, recordedOp, false);
if (bakedState->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedState);
@@ -165,13 +112,13 @@ public:
};
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
const Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
: true;
BakedOpState* bakedState = new (allocator) BakedOpState(
snapshot, recordedOp, expandForStroke);
allocator, snapshot, recordedOp, expandForStroke);
if (bakedState->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedState);
@@ -181,11 +128,11 @@ public:
}
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
// clip isn't empty, so construct the op
return new (allocator) BakedOpState(snapshot, shadowOpPtr);
return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
}
static void* operator new(size_t size, LinearAllocator& allocator) {
@@ -202,15 +149,16 @@ public:
const RecordedOp* op;
private:
BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke)
: computedState(snapshot, recordedOp, expandForStroke)
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot,
const RecordedOp& recordedOp, bool expandForStroke)
: computedState(allocator, snapshot, recordedOp, expandForStroke)
, alpha(snapshot.alpha)
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(&recordedOp) {}
BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
: computedState(snapshot)
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
: computedState(allocator, snapshot)
, alpha(snapshot.alpha)
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)

View File

@@ -45,6 +45,21 @@ CanvasState::~CanvasState() {
}
}
void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) {
if (mWidth != viewportWidth || mHeight != viewportHeight) {
mWidth = viewportWidth;
mHeight = viewportHeight;
mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
mCanvas.onViewportInitialized();
}
freeAllSnapshots();
mSnapshot = allocSnapshot(&mFirstSnapshot,
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
mSnapshot->setRelativeLightCenter(Vector3());
mSaveCount = 1;
}
void CanvasState::initializeSaveStack(
int viewportWidth, int viewportHeight,
float clipLeft, float clipTop,

View File

@@ -76,6 +76,12 @@ public:
CanvasState(CanvasStateClient& renderer);
~CanvasState();
/**
* Initializes the first snapshot, computing the projection matrix,
* and stores the dimensions of the render target.
*/
void initializeRecordingSaveStack(int viewportWidth, int viewportHeight);
/**
* Initializes the first snapshot, computing the projection matrix,
* and stores the dimensions of the render target.
@@ -168,6 +174,7 @@ private:
void freeAllSnapshots();
/// indicates that the clip has been changed since the last time it was consumed
// TODO: delete when switching to HWUI_NEW_OPS
bool mDirtyClip;
/// Dimensions of the drawing surface

View File

@@ -15,10 +15,11 @@
*/
#include "ClipArea.h"
#include "utils/LinearAllocator.h"
#include <SkPath.h>
#include <limits>
#include "Rect.h"
#include <type_traits>
namespace android {
namespace uirenderer {
@@ -171,12 +172,18 @@ SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
return rectangleListAsRegion;
}
void RectangleList::transform(const Matrix4& transform) {
for (int index = 0; index < mTransformedRectanglesCount; index++) {
mTransformedRectangles[index].transform(transform);
}
}
/*
* ClipArea
*/
ClipArea::ClipArea()
: mMode(Mode::Rectangle) {
: mMode(ClipMode::Rectangle) {
}
/*
@@ -184,39 +191,44 @@ ClipArea::ClipArea()
*/
void ClipArea::setViewportDimensions(int width, int height) {
mPostViewportClipObserved = false;
mViewportBounds.set(0, 0, width, height);
mClipRect = mViewportBounds;
}
void ClipArea::setEmpty() {
mMode = Mode::Rectangle;
onClipUpdated();
mMode = ClipMode::Rectangle;
mClipRect.setEmpty();
mClipRegion.setEmpty();
mRectangleList.setEmpty();
}
void ClipArea::setClip(float left, float top, float right, float bottom) {
mMode = Mode::Rectangle;
onClipUpdated();
mMode = ClipMode::Rectangle;
mClipRect.set(left, top, right, bottom);
mClipRegion.setEmpty();
}
void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op) {
onClipUpdated();
switch (mMode) {
case Mode::Rectangle:
case ClipMode::Rectangle:
rectangleModeClipRectWithTransform(r, transform, op);
break;
case Mode::RectangleList:
case ClipMode::RectangleList:
rectangleListModeClipRectWithTransform(r, transform, op);
break;
case Mode::Region:
case ClipMode::Region:
regionModeClipRectWithTransform(r, transform, op);
break;
}
}
void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
onClipUpdated();
enterRegionMode();
mClipRegion.op(region, op);
onClipRegionUpdated();
@@ -224,6 +236,7 @@ void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op) {
onClipUpdated();
SkMatrix skTransform;
transform->copyTo(skTransform);
SkPath transformed;
@@ -241,7 +254,7 @@ void ClipArea::enterRectangleMode() {
// Entering rectangle mode discards any
// existing clipping information from the other modes.
// The only way this occurs is by a clip setting operation.
mMode = Mode::Rectangle;
mMode = ClipMode::Rectangle;
}
void ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
@@ -276,8 +289,8 @@ void ClipArea::enterRectangleListMode() {
// Is is only legal to enter rectangle list mode from
// rectangle mode, since rectangle list mode cannot represent
// all clip areas that can be represented by a region.
ALOG_ASSERT(mMode == Mode::Rectangle);
mMode = Mode::RectangleList;
ALOG_ASSERT(mMode == ClipMode::Rectangle);
mMode = ClipMode::RectangleList;
mRectangleList.set(mClipRect, Matrix4::identity());
}
@@ -295,12 +308,11 @@ void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
*/
void ClipArea::enterRegionMode() {
Mode oldMode = mMode;
mMode = Mode::Region;
if (oldMode != Mode::Region) {
if (oldMode == Mode::Rectangle) {
mClipRegion.setRect(mClipRect.left, mClipRect.top,
mClipRect.right, mClipRect.bottom);
ClipMode oldMode = mMode;
mMode = ClipMode::Region;
if (oldMode != ClipMode::Region) {
if (oldMode == ClipMode::Rectangle) {
mClipRegion.setRect(mClipRect.toSkIRect());
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
onClipRegionUpdated();
@@ -330,5 +342,172 @@ void ClipArea::onClipRegionUpdated() {
}
}
/**
* Clip serialization
*/
const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
if (!mPostViewportClipObserved) {
// Only initial clip-to-viewport observed, so no serialization of clip necessary
return nullptr;
}
static_assert(std::is_trivially_destructible<Rect>::value,
"expect Rect to be trivially destructible");
static_assert(std::is_trivially_destructible<RectangleList>::value,
"expect RectangleList to be trivially destructible");
if (mLastSerialization == nullptr) {
switch (mMode) {
case ClipMode::Rectangle:
mLastSerialization = allocator.create<ClipRect>(mClipRect);
break;
case ClipMode::RectangleList:
mLastSerialization = allocator.create<ClipRectList>(mRectangleList);
break;
case ClipMode::Region:
mLastSerialization = allocator.create<ClipRegion>(mClipRegion);
break;
}
}
return mLastSerialization;
}
inline static const Rect& getRect(const ClipBase* scb) {
return reinterpret_cast<const ClipRect*>(scb)->rect;
}
inline static const RectangleList& getRectList(const ClipBase* scb) {
return reinterpret_cast<const ClipRectList*>(scb)->rectList;
}
inline static const SkRegion& getRegion(const ClipBase* scb) {
return reinterpret_cast<const ClipRegion*>(scb)->region;
}
// Conservative check for too many rectangles to fit in rectangle list.
// For simplicity, doesn't account for rect merging
static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
int currentRectCount = clipArea.isRectangleList()
? clipArea.getRectangleList().getTransformedRectanglesCount()
: 1;
int recordedRectCount = (scb->mode == ClipMode::RectangleList)
? getRectList(scb).getTransformedRectanglesCount()
: 1;
return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
}
const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
// if no recordedClip passed, just serialize current state
if (!recordedClip) return serializeClip(allocator);
if (!mLastResolutionResult
|| recordedClip != mLastResolutionClip
|| recordedClipTransform != mLastResolutionTransform) {
mLastResolutionClip = recordedClip;
mLastResolutionTransform = recordedClipTransform;
if (CC_LIKELY(mMode == ClipMode::Rectangle
&& recordedClip->mode == ClipMode::Rectangle
&& recordedClipTransform.rectToRect())) {
// common case - result is a single rectangle
auto rectClip = allocator.create<ClipRect>(getRect(recordedClip));
recordedClipTransform.mapRect(rectClip->rect);
rectClip->rect.doIntersect(mClipRect);
mLastResolutionResult = rectClip;
} else if (CC_UNLIKELY(mMode == ClipMode::Region
|| recordedClip->mode == ClipMode::Region
|| cannotFitInRectangleList(*this, recordedClip))) {
// region case
SkRegion other;
switch (recordedClip->mode) {
case ClipMode::Rectangle:
if (CC_LIKELY(recordedClipTransform.rectToRect())) {
// simple transform, skip creating SkPath
Rect resultClip(getRect(recordedClip));
recordedClipTransform.mapRect(resultClip);
other.setRect(resultClip.toSkIRect());
} else {
SkPath transformedRect = pathFromTransformedRectangle(getRect(recordedClip),
recordedClipTransform);
other.setPath(transformedRect, createViewportRegion());
}
break;
case ClipMode::RectangleList: {
RectangleList transformedList(getRectList(recordedClip));
transformedList.transform(recordedClipTransform);
other = transformedList.convertToRegion(createViewportRegion());
break;
}
case ClipMode::Region:
other = getRegion(recordedClip);
// TODO: handle non-translate transforms properly!
other.translate(recordedClipTransform.getTranslateX(),
recordedClipTransform.getTranslateY());
}
ClipRegion* regionClip = allocator.create<ClipRegion>();
switch (mMode) {
case ClipMode::Rectangle:
regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
break;
case ClipMode::RectangleList:
regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
other, SkRegion::kIntersect_Op);
break;
case ClipMode::Region:
regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
break;
}
regionClip->rect.set(regionClip->region.getBounds());
mLastResolutionResult = regionClip;
} else {
auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
auto&& rectList = rectListClip->rectList;
if (mMode == ClipMode::Rectangle) {
rectList.set(mClipRect, Matrix4::identity());
}
if (recordedClip->mode == ClipMode::Rectangle) {
rectList.intersectWith(getRect(recordedClip), recordedClipTransform);
} else {
const RectangleList& other = getRectList(recordedClip);
for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
auto&& tr = other.getTransformedRectangle(i);
Matrix4 totalTransform(recordedClipTransform);
totalTransform.multiply(tr.getTransform());
rectList.intersectWith(tr.getBounds(), totalTransform);
}
}
rectListClip->rect = rectList.calculateBounds();
mLastResolutionResult = rectListClip;
}
}
return mLastResolutionResult;
}
void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
if (!clip) return; // nothing to do
if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
clipRectWithTransform(getRect(clip), &transform, SkRegion::kIntersect_Op);
} else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
auto&& rectList = getRectList(clip);
for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
auto&& tr = rectList.getTransformedRectangle(i);
Matrix4 totalTransform(transform);
totalTransform.multiply(tr.getTransform());
clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
}
} else {
SkRegion region(getRegion(clip));
// TODO: handle non-translate transforms properly!
region.translate(transform.getTranslateX(), transform.getTranslateY());
clipRegion(region, SkRegion::kIntersect_Op);
}
}
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -16,15 +16,17 @@
#ifndef CLIPAREA_H
#define CLIPAREA_H
#include <SkRegion.h>
#include "Matrix.h"
#include "Rect.h"
#include "utils/Pair.h"
#include <SkRegion.h>
namespace android {
namespace uirenderer {
class LinearAllocator;
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
class TransformedRectangle {
@@ -50,6 +52,12 @@ public:
return mTransform;
}
void transform(const Matrix4& transform) {
Matrix4 t;
t.loadMultiply(transform, mTransform);
mTransform = t;
}
private:
Rect mBounds;
Matrix4 mTransform;
@@ -66,27 +74,62 @@ public:
void setEmpty();
void set(const Rect& bounds, const Matrix4& transform);
bool intersectWith(const Rect& bounds, const Matrix4& transform);
void transform(const Matrix4& transform);
SkRegion convertToRegion(const SkRegion& clip) const;
Rect calculateBounds() const;
private:
enum {
kMaxTransformedRectangles = 5
};
private:
int mTransformedRectanglesCount;
TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
};
class ClipArea {
private:
enum class Mode {
Rectangle,
Region,
RectangleList
};
enum class ClipMode {
Rectangle,
RectangleList,
// region and path - intersected. if either is empty, don't use
Region
};
struct ClipBase {
ClipBase(ClipMode mode)
: mode(mode) {}
ClipBase(const Rect& rect)
: mode(ClipMode::Rectangle)
, rect(rect) {}
const ClipMode mode;
// Bounds of the clipping area, used to define the scissor, and define which
// portion of the stencil is updated/used
Rect rect;
};
struct ClipRect : ClipBase {
ClipRect(const Rect& rect)
: ClipBase(rect) {}
};
struct ClipRectList : ClipBase {
ClipRectList(const RectangleList& rectList)
: ClipBase(ClipMode::RectangleList)
, rectList(rectList) {}
RectangleList rectList;
};
struct ClipRegion : ClipBase {
ClipRegion(const SkRegion& region)
: ClipBase(ClipMode::Region)
, region(region) {}
ClipRegion()
: ClipBase(ClipMode::Region) {}
SkRegion region;
};
class ClipArea {
public:
ClipArea();
@@ -117,17 +160,22 @@ public:
}
bool isRegion() const {
return Mode::Region == mMode;
return ClipMode::Region == mMode;
}
bool isSimple() const {
return mMode == Mode::Rectangle;
return mMode == ClipMode::Rectangle;
}
bool isRectangleList() const {
return mMode == Mode::RectangleList;
return mMode == ClipMode::RectangleList;
}
const ClipBase* serializeClip(LinearAllocator& allocator);
const ClipBase* serializeIntersectedClip(LinearAllocator& allocator,
const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
private:
void enterRectangleMode();
void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
@@ -145,6 +193,13 @@ private:
void ensureClipRegion();
void onClipRegionUpdated();
// Called by every state modifying public method.
void onClipUpdated() {
mPostViewportClipObserved = true;
mLastSerialization = nullptr;
mLastResolutionResult = nullptr;
}
SkRegion createViewportRegion() {
return SkRegion(mViewportBounds.toSkIRect());
}
@@ -155,7 +210,22 @@ private:
pathAsRegion.setPath(path, createViewportRegion());
}
Mode mMode;
ClipMode mMode;
bool mPostViewportClipObserved = false;
/**
* If mLastSerialization is non-null, it represents an already serialized copy
* of the current clip state. If null, it has not been computed.
*/
const ClipBase* mLastSerialization = nullptr;
/**
* This pair of pointers is a single entry cache of most recently seen
*/
const ClipBase* mLastResolutionResult = nullptr;
const ClipBase* mLastResolutionClip = nullptr;
Matrix4 mLastResolutionTransform;
Rect mViewportBounds;
Rect mClipRect;
SkRegion mClipRegion;

View File

@@ -47,6 +47,7 @@ namespace uirenderer {
#if HWUI_NEW_OPS
class BakedOpState;
class BakedOpRenderer;
struct ClipBase;
#else
class OpenGLRenderer;
#endif
@@ -57,7 +58,7 @@ public:
#if HWUI_NEW_OPS
BakedOpRenderer* renderer,
const BakedOpState* bakedState,
const Rect* clip,
const ClipBase* clip,
#else
OpenGLRenderer* renderer,
#endif
@@ -81,7 +82,7 @@ public:
#if HWUI_NEW_OPS
BakedOpRenderer* renderer;
const BakedOpState* bakedState;
const Rect* clip;
const ClipBase* clip;
#else
OpenGLRenderer* renderer;
#endif

View File

@@ -64,7 +64,7 @@ namespace TransformFlags {
// Canvas transform isn't applied to the mesh at draw time,
//since it's already built in.
MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove
MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS
};
};

View File

@@ -460,7 +460,7 @@ void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
deferBeginLayerOp(*new (mAllocator) BeginLayerOp(
saveLayerBounds,
Matrix4::identity(),
saveLayerBounds,
nullptr, // no record-time clip - need only respect defer-time one
&saveLayerPaint));
deferNodeOps(node);
deferEndLayerOp(*new (mAllocator) EndLayerOp());
@@ -604,7 +604,7 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
mCanvasState.getLocalClipBounds(),
mCanvasState.currentSnapshot()->getRelativeLightCenter());
BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
mAllocator, *mCanvasState.writableSnapshot(), shadowOp);
if (CC_LIKELY(bakedOpState)) {
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
}
@@ -681,10 +681,10 @@ void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) return;
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
// apply state from RecordedOp
// apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
mCanvasState.writableSnapshot()->mutateClipArea().applyClip(op.localClip,
*mCanvasState.currentSnapshot()->transform);
mCanvasState.concatMatrix(op.localMatrix);
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
// then apply state from node properties, and defer ops
deferNodePropsAndOps(*op.renderNode);
@@ -706,7 +706,7 @@ void OpReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.currentSnapshot(), op, strokeBehavior);
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
}
@@ -769,7 +769,7 @@ void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) {
const OvalOp* resolvedOp = new (mAllocator) OvalOp(
unmappedBounds,
op.localMatrix,
op.localClipRect,
op.localClip,
op.paint);
deferOvalOp(*resolvedOp);
}
@@ -829,7 +829,7 @@ void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
op.localMatrix,
op.localClipRect,
op.localClip,
op.paint, *op.rx, *op.ry);
deferRoundRectOp(*resolvedOp);
}
@@ -953,7 +953,7 @@ void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) {
LayerOp* drawLayerOp = new (mAllocator) LayerOp(
beginLayerOp.unmappedBounds,
beginLayerOp.localMatrix,
beginLayerOp.localClipRect,
beginLayerOp.localClip,
beginLayerOp.paint,
&mLayerReorderers[finishedLayerIndex].offscreenBuffer);
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);

View File

@@ -192,7 +192,7 @@ public:
const LayerReorderer& fbo0 = mLayerReorderers[0];
renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
renderer.endFrame();
renderer.endFrame(fbo0.repaintRect);
}
void dump() const {
@@ -223,7 +223,7 @@ private:
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
}
// should always be surrounded by a save/restore pair, and not called if DisplayList is null

View File

@@ -33,6 +33,7 @@ class SkPaint;
namespace android {
namespace uirenderer {
struct ClipBase;
class OffscreenBuffer;
class RenderNode;
struct Vertex;
@@ -91,10 +92,10 @@ namespace RecordedOpId {
static_assert(RecordedOpId::ArcOp == 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)
#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint
#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip
#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint)
#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr)
struct RecordedOp {
/* ID from RecordedOpId - generally used for jumping into function tables */
@@ -106,8 +107,8 @@ struct RecordedOp {
/* transform in recording space (vs DisplayList origin) */
const Matrix4 localMatrix;
/* clip in recording space */
const Rect localClipRect;
/* clip in recording space - nullptr if not clipped */
const ClipBase* localClip;
/* optional paint, stored in base object to simplify merging logic */
const SkPaint* paint;
@@ -116,7 +117,7 @@ protected:
: opId(opId)
, unmappedBounds(unmappedBounds)
, localMatrix(localMatrix)
, localClipRect(localClipRect)
, localClip(localClip)
, paint(paint) {}
};
@@ -187,9 +188,9 @@ struct BitmapRectOp : RecordedOp {
};
struct CirclePropsOp : RecordedOp {
CirclePropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
float* x, float* y, float* radius)
: RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClipRect, paint)
: RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint)
, x(x)
, y(y)
, radius(radius) {}
@@ -259,9 +260,9 @@ struct RoundRectOp : RecordedOp {
};
struct RoundRectPropsOp : RecordedOp {
RoundRectPropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
float* left, float* top, float* right, float* bottom, float *rx, float *ry)
: RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClipRect, paint)
: RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint)
, left(left)
, top(top)
, right(right)
@@ -286,12 +287,13 @@ struct RoundRectPropsOp : RecordedOp {
*/
struct ShadowOp : RecordedOp {
ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
const Rect& clipRect, const Vector3& lightCenter)
: RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
const Rect& localClipRect, const Vector3& lightCenter)
: RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr)
, shadowMatrixXY(casterOp.localMatrix)
, shadowMatrixZ(casterOp.localMatrix)
, casterAlpha(casterAlpha)
, casterPath(casterPath)
, localClipRect(localClipRect)
, lightCenter(lightCenter) {
const RenderNode& node = *casterOp.renderNode;
node.applyViewPropertyTransforms(shadowMatrixXY, false);
@@ -301,6 +303,7 @@ struct ShadowOp : RecordedOp {
Matrix4 shadowMatrixZ;
const float casterAlpha;
const SkPath* casterPath;
const Rect localClipRect;
const Vector3 lightCenter;
};
@@ -374,7 +377,7 @@ struct BeginLayerOp : RecordedOp {
*/
struct EndLayerOp : RecordedOp {
EndLayerOp()
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), Rect(), nullptr) {}
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
};
/**
@@ -394,7 +397,7 @@ struct LayerOp : RecordedOp {
, destroy(true) {}
LayerOp(RenderNode& node)
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
, layerHandle(node.getLayerHandle())
, alpha(node.properties().layerProperties().alpha() / 255.0f)
, mode(node.properties().layerProperties().xferMode())

View File

@@ -39,7 +39,7 @@ void RecordingCanvas::reset(int width, int height) {
"prepareDirty called a second time during a recording!");
mDisplayList = new DisplayList();
mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
mState.initializeRecordingSaveStack(width, height);
mDeferredBarrierType = DeferredBarrierType::InOrder;
mState.setDirtyClip(false);
@@ -155,6 +155,8 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
return saveValue;
}
auto previousClip = getRecordedClip(); // note: done while snapshot == previous
snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
@@ -167,7 +169,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
addOp(new (alloc()) BeginLayerOp(
Rect(left, top, right, bottom),
*previous.transform, // transform to *draw* with
previous.getRenderTargetClip(), // clip to *draw* with
previousClip, // clip to *draw* with
refPaint(paint)));
return saveValue;
@@ -229,11 +231,10 @@ void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
}
void RecordingCanvas::drawPaint(const SkPaint& paint) {
// TODO: more efficient recording?
addOp(new (alloc()) RectOp(
mState.getRenderTargetClipBounds(),
mState.getRenderTargetClipBounds(), // OK, since we've not passed transform
Matrix4::identity(),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint)));
}
@@ -253,7 +254,7 @@ void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPa
addOp(new (alloc()) PointsOp(
calcBoundsOfPoints(points, floatCount),
*mState.currentSnapshot()->transform,
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
@@ -264,7 +265,7 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai
addOp(new (alloc()) LinesOp(
calcBoundsOfPoints(points, floatCount),
*mState.currentSnapshot()->transform,
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
}
@@ -272,7 +273,7 @@ void RecordingCanvas::drawRect(float left, float top, float right, float bottom,
addOp(new (alloc()) RectOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint)));
}
@@ -305,7 +306,7 @@ void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const
addOp(new (alloc()) SimpleRectsOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(paint), rectData, vertexCount));
}
@@ -339,7 +340,7 @@ void RecordingCanvas::drawRoundRect(float left, float top, float right, float bo
addOp(new (alloc()) RoundRectOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), rx, ry));
}
@@ -358,7 +359,7 @@ void RecordingCanvas::drawRoundRect(
refBitmapsInShader(paint->value.getShader());
addOp(new (alloc()) RoundRectPropsOp(
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
&paint->value,
&left->value, &top->value, &right->value, &bottom->value,
&rx->value, &ry->value));
@@ -380,7 +381,7 @@ void RecordingCanvas::drawCircle(
refBitmapsInShader(paint->value.getShader());
addOp(new (alloc()) CirclePropsOp(
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
&paint->value,
&x->value, &y->value, &radius->value));
}
@@ -390,7 +391,7 @@ void RecordingCanvas::drawOval(float left, float top, float right, float bottom,
addOp(new (alloc()) OvalOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint)));
}
@@ -399,7 +400,7 @@ void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
addOp(new (alloc()) ArcOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint),
startAngle, sweepAngle, useCenter));
}
@@ -408,7 +409,7 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
addOp(new (alloc()) PathOp(
Rect(path.getBounds()),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), refPath(&path)));
}
@@ -459,7 +460,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr
addOp(new (alloc()) BitmapRectOp(
Rect(dstLeft, dstTop, dstRight, dstBottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(paint), refBitmap(bitmap),
Rect(srcLeft, srcTop, srcRight, srcBottom)));
}
@@ -471,7 +472,7 @@ void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int
addOp(new (alloc()) BitmapMeshOp(
calcBoundsOfPoints(vertices, vertexCount * 2),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
refBuffer<int>(colors, vertexCount))); // 1 color per vertex
@@ -483,7 +484,7 @@ void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_p
addOp(new (alloc()) PatchOp(
Rect(dstLeft, dstTop, dstRight, dstBottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
@@ -499,7 +500,7 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i
addOp(new (alloc()) TextOp(
Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), glyphs, positions, glyphCount, x, y));
drawTextDecorations(x, y, totalAdvance, paint);
}
@@ -509,9 +510,9 @@ void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, con
if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
addOp(new (alloc()) TextOnPathOp(
mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
mState.getLocalClipBounds(), // TODO: explicitly define bounds
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
}
@@ -519,7 +520,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
addOp(new (alloc()) BitmapOp(
Rect(bitmap->width(), bitmap->height()),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
refPaint(paint), refBitmap(*bitmap)));
}
@@ -528,7 +529,7 @@ void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
RenderNodeOp* op = new (alloc()) RenderNodeOp(
Rect(stagingProps.getWidth(), stagingProps.getHeight()),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
renderNode);
int opIndex = addOp(op);
int childIndex = mDisplayList->addChild(op);
@@ -554,16 +555,16 @@ void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) {
addOp(new (alloc()) TextureLayerOp(
Rect(layer->getWidth(), layer->getHeight()),
totalTransform,
mState.getRenderTargetClipBounds(),
getRecordedClip(),
layer));
}
void RecordingCanvas::callDrawGLFunction(Functor* functor) {
mDisplayList->functors.push_back(functor);
addOp(new (alloc()) FunctorOp(
mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
mState.getLocalClipBounds(), // TODO: explicitly define bounds
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
getRecordedClip(),
functor));
}

View File

@@ -36,6 +36,7 @@
namespace android {
namespace uirenderer {
struct ClipBase;
class DeferredLayerUpdater;
struct RecordedOp;
@@ -199,6 +200,9 @@ public:
virtual bool drawTextAbsolutePos() const override { return false; }
private:
const ClipBase* getRecordedClip() {
return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
}
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);

View File

@@ -167,6 +167,7 @@ public:
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
bool clipIsSimple() const { return mClipArea->isSimple(); }
const ClipArea& getClipArea() const { return *mClipArea; }
ClipArea& mutateClipArea() { return *mClipArea; }
/**
* Resets the clip to the specified rect.

View File

@@ -15,6 +15,8 @@
*/
#include "renderstate/Scissor.h"
#include "Rect.h"
#include <utils/Log.h>
namespace android {
@@ -71,6 +73,26 @@ bool Scissor::set(GLint x, GLint y, GLint width, GLint height) {
return false;
}
void Scissor::set(int viewportHeight, const Rect& clip) {
// transform to Y-flipped GL space, and prevent negatives
GLint x = std::max(0, (int)clip.left);
GLint y = std::max(0, viewportHeight - (int)clip.bottom);
GLint width = std::max(0, ((int)clip.right) - x);
GLint height = std::max(0, (viewportHeight - (int)clip.top) - y);
if (x != mScissorX
|| y != mScissorY
|| width != mScissorWidth
|| height != mScissorHeight) {
glScissor(x, y, width, height);
mScissorX = x;
mScissorY = y;
mScissorWidth = width;
mScissorHeight = height;
}
}
void Scissor::reset() {
mScissorX = mScissorY = mScissorWidth = mScissorHeight = 0;
}

View File

@@ -22,11 +22,14 @@
namespace android {
namespace uirenderer {
class Rect;
class Scissor {
friend class RenderState;
public:
bool setEnabled(bool enabled);
bool set(GLint x, GLint y, GLint width, GLint height);
void set(int viewportHeight, const Rect& clip);
void reset();
bool isEnabled() { return mEnabled; }
void dump();

View File

@@ -0,0 +1,67 @@
/*
* 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 "TestSceneBase.h"
class ClippingAnimation;
static TestScene::Registrar _RectGrid(TestScene::Info{
"clip",
"Complex clip cases"
"Low CPU/GPU load.",
TestScene::simpleCreateScene<ClippingAnimation>
});
class ClippingAnimation : public TestScene {
public:
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas& canvas) override {
canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, TestCanvas& canvas) {
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
{
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
canvas.translate(100, 100);
canvas.rotate(45);
canvas.translate(-100, -100);
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
}
canvas.restore();
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
{
SkPath clipCircle;
clipCircle.addCircle(100, 300, 100);
canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op);
canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
}
canvas.restore();
});
canvas.drawRenderNode(card.get());
canvas.insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
card->mutateStagingProperties().setTranslationX(curFrame);
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
};

View File

@@ -22,6 +22,7 @@
#include "tests/common/TestContext.h"
#include "tests/common/TestScene.h"
#include "tests/common/TestUtils.h"
#include "utils/Color.h"
#include <functional>

View File

@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include <BakedOpState.h>
#include <ClipArea.h>
#include <RecordedOp.h>
#include <tests/common/TestUtils.h>
@@ -24,31 +25,33 @@ namespace android {
namespace uirenderer {
TEST(ResolvedRenderState, construct) {
LinearAllocator allocator;
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
SkPaint paint;
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(100, 200), &paint);
ClipRect clip(Rect(100, 200));
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
{
// recorded with transform, no parent transform
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
EXPECT_EQ(Rect(100, 200), state.clipRect);
EXPECT_EQ(Rect(100, 200), state.clipRect());
EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
}
{
// recorded with transform and parent transform
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
Matrix4 expectedTranslate;
expectedTranslate.loadTranslate(20, 40, 0);
EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
// intersection of parent & transformed child clip
EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect);
EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
// translated and also clipped
EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
@@ -57,22 +60,24 @@ TEST(ResolvedRenderState, construct) {
}
TEST(ResolvedRenderState, computeLocalSpaceClip) {
LinearAllocator allocator;
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
SkPaint paint;
RectOp recordedOp(Rect(1000, 1000), translate10x20, Rect(100, 200), &paint);
ClipRect clip(Rect(100, 200));
RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
{
// recorded with transform, no parent transform
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
<< "Local clip rect should be 100x200, offset by -10,-20";
}
{
// recorded with transform + parent transform
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
<< "Local clip rect should be 90x190, offset by -10,-20";
}
@@ -149,6 +154,7 @@ const static StrokeTestCase sStrokeTestCases[] = {
};
TEST(ResolvedRenderState, construct_expandForStroke) {
LinearAllocator allocator;
// Loop over table of test cases and verify different combinations of stroke width and transform
for (auto&& testCase : sStrokeTestCases) {
SkPaint strokedPaint;
@@ -156,14 +162,15 @@ TEST(ResolvedRenderState, construct_expandForStroke) {
strokedPaint.setStyle(SkPaint::kStroke_Style);
strokedPaint.setStrokeWidth(testCase.strokeWidth);
ClipRect clip(Rect(200, 200));
RectOp recordedOp(Rect(50, 50, 150, 150),
Matrix4::identity(), Rect(200, 200), &strokedPaint);
Matrix4::identity(), &clip, &strokedPaint);
Matrix4 snapshotMatrix;
snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp, true);
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true);
testCase.validator(state);
}
}
@@ -175,8 +182,9 @@ TEST(BakedOpState, tryConstruct) {
translate100x0.loadTranslate(100, 0, 0);
SkPaint paint;
ClipRect clip(Rect(100, 200));
{
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(100, 200), &paint);
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
@@ -184,7 +192,7 @@ TEST(BakedOpState, tryConstruct) {
EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
}
{
RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(100, 200), &paint);
RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
@@ -218,7 +226,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
SkPaint paint;
paint.setStyle(SkPaint::kStrokeAndFill_Style);
paint.setStrokeWidth(0.0f);
RectOp rejectOp(Rect(100, 200), Matrix4::identity(), Rect(100, 200), &paint);
ClipRect clip(Rect(100, 200));
RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::StyleDefined);
@@ -231,7 +240,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
SkPaint paint;
paint.setStyle(SkPaint::kStrokeAndFill_Style);
paint.setStrokeWidth(10.0f);
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
ClipRect clip(Rect(200, 200));
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::StyleDefined);
@@ -245,7 +255,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setStrokeWidth(10.0f);
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
ClipRect clip(Rect(200, 200));
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
BakedOpState::StrokeBehavior::Forced);

View File

@@ -119,5 +119,122 @@ TEST(ClipArea, replaceNegative) {
EXPECT_EQ(expected, area.getClipRect());
}
TEST(ClipArea, serializeClip) {
ClipArea area(createClipArea());
LinearAllocator allocator;
// unset clip
EXPECT_EQ(nullptr, area.serializeClip(allocator));
// rect clip
area.setClip(0, 0, 200, 200);
{
auto serializedClip = area.serializeClip(allocator);
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip);
ASSERT_EQ(Rect(200, 200), clipRect->rect);
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
// rect list
Matrix4 rotate;
rotate.loadRotate(2.0f);
area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
{
auto serializedClip = area.serializeClip(allocator);
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
// region
SkPath circlePath;
circlePath.addCircle(100, 100, 100);
area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
{
auto serializedClip = area.serializeClip(allocator);
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::Region, serializedClip->mode);
auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
<< "Clip region should be 200x200";
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
}
TEST(ClipArea, serializeIntersectedClip) {
ClipArea area(createClipArea());
LinearAllocator allocator;
// simple state;
EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
area.setClip(0, 0, 200, 200);
{
auto origRectClip = area.serializeClip(allocator);
ASSERT_NE(nullptr, origRectClip);
EXPECT_EQ(origRectClip, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
}
// rect
{
ClipRect recordedClip(Rect(100, 100));
Matrix4 translateScale;
translateScale.loadTranslate(100, 100, 0);
translateScale.scale(2, 3, 1);
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale);
ASSERT_NE(nullptr, resolvedClip);
ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode);
EXPECT_EQ(Rect(100, 100, 200, 200),
reinterpret_cast<const ClipRect*>(resolvedClip)->rect);
EXPECT_EQ(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip, translateScale))
<< "Must return previous serialization, since input is same";
ClipRect recordedClip2(Rect(100, 100));
EXPECT_NE(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip2, translateScale))
<< "Shouldn't return previous serialization, since matrix location is different";
}
// rect list
Matrix4 rotate;
rotate.loadRotate(2.0f);
area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
{
ClipRect recordedClip(Rect(100, 100));
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity());
ASSERT_NE(nullptr, resolvedClip);
ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode);
auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip);
EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
}
// region
SkPath circlePath;
circlePath.addCircle(100, 100, 100);
area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
{
SkPath ovalPath;
ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200));
ClipRegion recordedClip;
recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200)));
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip,
translate10x20); // Note: only translate for now, others not handled correctly
ASSERT_NE(nullptr, resolvedClip);
ASSERT_EQ(ClipMode::Region, resolvedClip->mode);
auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip);
EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds());
}
}
} // namespace uirenderer
} // namespace android

View File

@@ -27,7 +27,7 @@ struct SimplePair {
int two = 2;
};
TEST(LinearAllocator, alloc) {
TEST(LinearAllocator, create) {
LinearAllocator la;
EXPECT_EQ(0u, la.usedSize());
la.alloc(64);
@@ -35,7 +35,7 @@ TEST(LinearAllocator, alloc) {
// so the usedSize isn't strictly defined
EXPECT_LE(64u, la.usedSize());
EXPECT_GT(80u, la.usedSize());
auto pair = la.alloc<SimplePair>();
auto pair = la.create<SimplePair>();
EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
EXPECT_EQ(1, pair->one);
@@ -47,8 +47,8 @@ TEST(LinearAllocator, dtor) {
{
LinearAllocator la;
for (int i = 0; i < 5; i++) {
la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
la.alloc<SimplePair>();
la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
la.create<SimplePair>();
}
la.alloc(100);
for (int i = 0; i < 5; i++) {
@@ -75,7 +75,7 @@ TEST(LinearAllocator, rewind) {
la.rewindIfLastAlloc(addr, 100);
EXPECT_GT(16u, la.usedSize());
size_t emptySize = la.usedSize();
auto sigdtor = la.alloc<TestUtils::SignalingDtor>();
auto sigdtor = la.create<TestUtils::SignalingDtor>();
sigdtor->setSignal(&destroyed);
EXPECT_EQ(0, destroyed);
EXPECT_LE(emptySize, la.usedSize());

View File

@@ -64,7 +64,7 @@ public:
ADD_FAILURE() << "Layer updates not expected in this test";
}
virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
virtual void endFrame() {}
virtual void endFrame(const Rect& repaintRect) {}
// define virtual defaults for single draw methods
#define X(Type) \
@@ -127,7 +127,7 @@ TEST(OpReorderer, simple) {
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
EXPECT_EQ(2, mIndex++);
}
void endFrame() override {
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(3, mIndex++);
}
};
@@ -327,7 +327,7 @@ RENDERTHREAD_TEST(OpReorderer, textureLayer) {
public:
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect);
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect());
EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds);
Matrix4 expected;
@@ -405,7 +405,7 @@ TEST(OpReorderer, clipped) {
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect());
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
};
@@ -439,7 +439,7 @@ TEST(OpReorderer, saveLayerSimple) {
EXPECT_EQ(1, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds);
EXPECT_EQ(Rect(180, 180), state.computedState.clipRect);
EXPECT_EQ(Rect(180, 180), state.computedState.clipRect());
Matrix4 expectedTransform;
expectedTransform.loadTranslate(-10, -10, 0);
@@ -448,7 +448,7 @@ TEST(OpReorderer, saveLayerSimple) {
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(3, mIndex++);
EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
EXPECT_EQ(Rect(200, 200), state.computedState.clipRect);
EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
};
@@ -494,7 +494,7 @@ TEST(OpReorderer, saveLayerNested) {
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
EXPECT_EQ(7, mIndex++);
}
void endFrame() override {
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(9, mIndex++);
}
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -574,7 +574,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
EXPECT_TRUE(state.computedState.transform.isIdentity())
<< "Transform should be reset within layer";
EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
<< "Damage rect should be used to clip layer content";
}
void endLayer() override {
@@ -586,7 +586,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
EXPECT_EQ(4, mIndex++);
}
void endFrame() override {
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(5, mIndex++);
}
};
@@ -675,7 +675,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
EXPECT_EQ(200u, layer->viewportHeight);
} else { ADD_FAILURE(); }
}
void endFrame() override {
void endFrame(const Rect& repaintRect) override {
EXPECT_EQ(12, mIndex++);
}
};

View File

@@ -33,6 +33,14 @@ static void playbackOps(const DisplayList& displayList,
}
}
#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \
EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \
if ((clipStatePtr)->mode == ClipMode::Rectangle) { \
EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \
} else { \
ADD_FAILURE() << "ClipState not a rect"; \
}
TEST(RecordingCanvas, emptyPlayback) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -41,6 +49,22 @@ TEST(RecordingCanvas, emptyPlayback) {
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
}
TEST(RecordingCanvas, clipRect) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
canvas.drawRect(0, 0, 50, 50, SkPaint());
canvas.drawRect(50, 50, 100, 100, SkPaint());
canvas.restore();
});
ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
<< "Clip should be serialized once";
}
TEST(RecordingCanvas, drawLines) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkPaint paint;
@@ -66,7 +90,7 @@ TEST(RecordingCanvas, drawRect) {
ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
auto op = *(dl->getOps()[0]);
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
EXPECT_EQ(Rect(100, 200), op.localClipRect);
EXPECT_EQ(nullptr, op.localClip);
EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
}
@@ -83,7 +107,7 @@ TEST(RecordingCanvas, drawText) {
playbackOps(*dl, [&count](const RecordedOp& op) {
count++;
ASSERT_EQ(RecordedOpId::TextOp, op.opId);
EXPECT_EQ(Rect(200, 200), op.localClipRect);
EXPECT_EQ(nullptr, op.localClip);
EXPECT_TRUE(op.localMatrix.isIdentity());
EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
<< "Op expected to be 25+ pixels wide, 10+ pixels tall";
@@ -185,7 +209,7 @@ TEST(RecordingCanvas, backgroundAndImage) {
ASSERT_NE(nullptr, op.paint);
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
EXPECT_EQ(Rect(100, 200), op.localClipRect);
EXPECT_EQ(nullptr, op.localClip);
Matrix4 expectedMatrix;
expectedMatrix.loadIdentity();
@@ -194,7 +218,7 @@ TEST(RecordingCanvas, backgroundAndImage) {
ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
EXPECT_EQ(nullptr, op.paint);
EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
EXPECT_EQ(Rect(100, 200), op.localClipRect);
EXPECT_EQ(nullptr, op.localClip);
Matrix4 expectedMatrix;
expectedMatrix.loadTranslate(25, 25, 0);
@@ -219,12 +243,12 @@ TEST(RecordingCanvas, saveLayer_simple) {
case 0:
EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
EXPECT_EQ(Rect(200, 200), op.localClipRect);
EXPECT_EQ(nullptr, op.localClip);
EXPECT_TRUE(op.localMatrix.isIdentity());
break;
case 1:
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
EXPECT_EQ(Rect(180, 160), op.localClipRect);
EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
expectedMatrix.loadTranslate(-10, -20, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -254,8 +278,8 @@ TEST(RecordingCanvas, saveLayer_viewportCrop) {
if (count++ == 1) {
Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
EXPECT_EQ(Rect(100, 100), op.localClipRect) << "Recorded clip rect should be"
" intersection of viewport and saveLayer bounds, in layer space";
EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
// intersection of viewport and saveLayer bounds, in layer space;
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
expectedMatrix.loadTranslate(-100, -100, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -281,7 +305,7 @@ TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
playbackOps(*dl, [&count](const RecordedOp& op) {
if (count++ == 1) {
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
EXPECT_EQ(Rect(100, 100), op.localClipRect);
EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
<< "Recorded op shouldn't see any canvas transform before the saveLayer";
@@ -312,7 +336,16 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) {
// ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
// the parent 200x200 viewport, but prior to rotation
EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
ASSERT_NE(nullptr, op.localClip);
ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
// NOTE: this check relies on saveLayer altering the clip post-viewport init. This
// causes the clip to be recorded by contained draw commands, though it's not necessary
// since the same clip will be computed at draw time. If such a change is made, this
// check could be done at record time by querying the clip, or the clip could be altered
// slightly so that it is serialized.
EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136),
(reinterpret_cast<const ClipRect*>(op.localClip))->rect);
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
expectedMatrix.loadIdentity();
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);

View File

@@ -56,12 +56,12 @@ public:
void* alloc(size_t size);
/**
* Allocates an instance of the template type with the default constructor
* Allocates an instance of the template type with the given construction parameters
* and adds it to the automatic destruction list.
*/
template<class T>
T* alloc() {
T* ret = new (*this) T;
template<class T, typename... Params>
T* create(Params... params) {
T* ret = new (*this) T(params...);
autoDestroy(ret);
return ret;
}