diff options
author | 2015-12-22 16:32:23 -0800 | |
---|---|---|
committer | 2016-01-04 17:00:03 -0800 | |
commit | e4db79de127cfe961195f52907af8451026eaa20 (patch) | |
tree | 94be898632cc176b599aa2997072e91628d16dd3 | |
parent | 33e9b426da2863cdd9e675cde018b450a66e9ab4 (diff) |
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
28 files changed, 929 insertions, 233 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 0d1ee46712ee..6f924d35e4ef 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -108,6 +108,7 @@ ifeq (true, $(HWUI_NEW_OPS)) hwui_src_files += \ BakedOpDispatcher.cpp \ BakedOpRenderer.cpp \ + BakedOpState.cpp \ OpReorderer.cpp \ RecordingCanvas.cpp diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 0f0768f08a5e..332a2044dbfb 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -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()); diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index f8282dc1073a..757c12bdd929 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -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; diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index f158e8bb49c3..5bd2c0a1fd5f 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -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; diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp new file mode 100644 index 000000000000..e6b943a606d5 --- /dev/null +++ b/libs/hwui/BakedOpState.cpp @@ -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 diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index b12c0c970352..9df4e3aa5442 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -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); - - // 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); - } + ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke); - 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) diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index cf76e6be46c4..cf2726b5f530 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -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, diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h index 4709ef41915f..b9e87ae5595d 100644 --- a/libs/hwui/CanvasState.h +++ b/libs/hwui/CanvasState.h @@ -80,6 +80,12 @@ public: * 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. + */ void initializeSaveStack(int viewportWidth, int viewportHeight, float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter); @@ -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 diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index 5f166cafd01c..160090dcd8cc 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -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 */ diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index 268301c62fc9..479796db042a 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -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; diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index ff4dc4aef94a..99944985cda8 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -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 diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h index bcf819eb42e0..e72f39621d57 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -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 }; }; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index ad9559ffdf9f..11b2c8a8c282 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -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); diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index dbbce8b15170..4e9b5e6a91f1 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -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 diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index cfdd0d211dac..3bfe10d17738 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -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()) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index f75d8d4f6479..f7f6caff22b6 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -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)); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 470f9ecb5024..1a2ac97f5364 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -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); diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 194aa5735a7e..5fac3a1497c0 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -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. diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp index 95dcd18867d9..61dd8c3200a4 100644 --- a/libs/hwui/renderstate/Scissor.cpp +++ b/libs/hwui/renderstate/Scissor.cpp @@ -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; } diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h index b37ec583686f..f30224470059 100644 --- a/libs/hwui/renderstate/Scissor.h +++ b/libs/hwui/renderstate/Scissor.h @@ -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(); diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp new file mode 100644 index 000000000000..38788f09852e --- /dev/null +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -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); + } +}; diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h index ac781243c25e..935ddcf9212d 100644 --- a/libs/hwui/tests/common/scenes/TestSceneBase.h +++ b/libs/hwui/tests/common/scenes/TestSceneBase.h @@ -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> diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp index f9f5316a58df..3fd822d71310 100644 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -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); diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp index c4d305e5de18..4cae737ab295 100644 --- a/libs/hwui/tests/unit/ClipAreaTests.cpp +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -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 diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp index 78d65ddaa19d..5c442901045e 100644 --- a/libs/hwui/tests/unit/LinearAllocatorTests.cpp +++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp @@ -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()); diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index b28e4361bc4a..0d1311899586 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -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++); } }; diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 08f927ce00fd..a63cb18d7c4b 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -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); diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index e1c6f6c70428..dcbc0dda951a 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -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; } |