diff options
author | 2015-12-09 21:29:58 +0000 | |
---|---|---|
committer | 2015-12-09 21:29:58 +0000 | |
commit | a6f28e352d48c285b78f35c8c70dcafb82325dbb (patch) | |
tree | 10ba1a7f95b3bb87a3c25e979549743a13219c2a | |
parent | e37925187889c0bf0d79f6adffbcb953ed02fc0c (diff) | |
parent | 386aa031793bb037ec43b6cdbd8908c343cc86cb (diff) |
Merge "Add more shape drawing to new reorderer/renderer"
-rw-r--r-- | libs/hwui/BakedOpDispatcher.cpp | 208 | ||||
-rw-r--r-- | libs/hwui/BakedOpDispatcher.h | 4 | ||||
-rw-r--r-- | libs/hwui/BakedOpState.h | 47 | ||||
-rw-r--r-- | libs/hwui/Canvas.h | 4 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.cpp | 52 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.h | 4 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 5 | ||||
-rw-r--r-- | libs/hwui/RecordedOp.h | 50 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 67 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.h | 4 | ||||
-rw-r--r-- | libs/hwui/Texture.h | 12 | ||||
-rw-r--r-- | libs/hwui/tests/unit/BakedOpStateTests.cpp | 205 | ||||
-rw-r--r-- | libs/hwui/tests/unit/ClipAreaTests.cpp | 9 | ||||
-rw-r--r-- | libs/hwui/tests/unit/DamageAccumulatorTests.cpp | 14 | ||||
-rw-r--r-- | libs/hwui/tests/unit/OpReordererTests.cpp | 26 | ||||
-rw-r--r-- | libs/hwui/tests/unit/RecordingCanvasTests.cpp | 6 |
16 files changed, 583 insertions, 134 deletions
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index fde12dd261f5..3d35dd5b1fe8 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -20,6 +20,7 @@ #include "Caches.h" #include "Glop.h" #include "GlopBuilder.h" +#include "PathTessellator.h" #include "renderstate/OffscreenBufferPool.h" #include "renderstate/RenderState.h" #include "utils/GLUtils.h" @@ -27,6 +28,7 @@ #include <algorithm> #include <math.h> +#include <SkPaintDefaults.h> namespace android { namespace uirenderer { @@ -206,6 +208,90 @@ void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer&, const EndLayerOp&, const LOG_ALWAYS_FATAL("unsupported operation"); } +namespace VertexBufferRenderFlags { + enum { + Offset = 0x1, + ShadowInterp = 0x2, + }; +} + +static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state, + const VertexBuffer& vertexBuffer, float translateX, float translateY, + const SkPaint& paint, int vertexBufferRenderFlags) { + if (CC_LIKELY(vertexBuffer.getVertexCount())) { + bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp; + const int transformFlags = TransformFlags::OffsetByFudgeFactor; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshVertexBuffer(vertexBuffer, shadowInterp) + .setFillPaint(paint, state.alpha) + .setTransform(state.computedState.transform, transformFlags) + .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) + .build(); + renderer.renderGlop(state, glop); + } +} + +static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state, + const SkPath& path, const SkPaint& paint) { + VertexBuffer vertexBuffer; + // TODO: try clipping large paths to viewport + PathTessellator::tessellatePath(path, &paint, state.computedState.transform, vertexBuffer); + renderVertexBuffer(renderer, state, vertexBuffer, 0.0f, 0.0f, paint, 0); +} + +static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, + PathTexture& texture, const RecordedOp& op) { + Rect dest(texture.width, texture.height); + dest.translate(texture.left + op.unmappedBounds.left - texture.offset, + texture.top + op.unmappedBounds.top - texture.offset); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillPathTexturePaint(texture, *(op.paint), state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(dest) + .build(); + renderer.renderGlop(state, glop); +} + +SkRect getBoundsOfFill(const RecordedOp& op) { + SkRect bounds = op.unmappedBounds.toSkRect(); + if (op.paint->getStyle() == SkPaint::kStrokeAndFill_Style) { + float outsetDistance = op.paint->getStrokeWidth() / 2; + bounds.outset(outsetDistance, outsetDistance); + } + return bounds; +} + +void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op, const BakedOpState& state) { + // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) + if (op.paint->getStyle() != SkPaint::kStroke_Style + || op.paint->getPathEffect() != nullptr + || op.useCenter) { + PathTexture* texture = renderer.caches().pathCache.getArc( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), + op.startAngle, op.sweepAngle, op.useCenter, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, *texture, op); + } + } else { + SkRect rect = getBoundsOfFill(op); + SkPath path; + if (op.useCenter) { + path.moveTo(rect.centerX(), rect.centerY()); + } + path.arcTo(rect, op.startAngle, op.sweepAngle, !op.useCenter); + if (op.useCenter) { + path.close(); + } + renderConvexPath(renderer, state, path, *(op.paint)); + } +} + void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) { Texture* texture = renderer.getTexture(op.bitmap); if (!texture) return; @@ -225,43 +311,101 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op } void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) { - LOG_ALWAYS_FATAL("todo"); + VertexBuffer buffer; + PathTessellator::tessellateLines(op.points, op.floatCount, op.paint, + state.computedState.transform, buffer); + int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; + renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); } -void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshUnitQuad() - .setFillPaint(*op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(op.unmappedBounds) - .build(); - renderer.renderGlop(state, glop); +void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, const BakedOpState& state) { + if (op.paint->getPathEffect() != nullptr) { + PathTexture* texture = renderer.caches().pathCache.getOval( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, *texture, op); + } + } else { + SkPath path; + SkRect rect = getBoundsOfFill(op); + path.addOval(rect); + renderConvexPath(renderer, state, path, *(op.paint)); + } } -namespace VertexBufferRenderFlags { - enum { - Offset = 0x1, - ShadowInterp = 0x2, - }; +void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) { + PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, *texture, op); + } } -static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state, - const VertexBuffer& vertexBuffer, float translateX, float translateY, - SkPaint& paint, int vertexBufferRenderFlags) { - if (CC_LIKELY(vertexBuffer.getVertexCount())) { - bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp; - const int transformFlags = TransformFlags::OffsetByFudgeFactor; - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshVertexBuffer(vertexBuffer, shadowInterp) - .setFillPaint(paint, state.alpha) - .setTransform(state.computedState.transform, transformFlags) - .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) - .build(); - renderer.renderGlop(state, glop); +void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op, const BakedOpState& state) { + VertexBuffer buffer; + PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint, + state.computedState.transform, buffer); + int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; + renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); +} + +// See SkPaintDefaults.h +#define SkPaintDefaults_MiterLimit SkIntToScalar(4) + +void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { + if (op.paint->getStyle() != SkPaint::kFill_Style) { + // only fill + default miter is supported by drawConvexPath, since others must handle joins + static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed"); + if (CC_UNLIKELY(op.paint->getPathEffect() != nullptr + || op.paint->getStrokeJoin() != SkPaint::kMiter_Join + || op.paint->getStrokeMiter() != SkPaintDefaults_MiterLimit)) { + PathTexture* texture = renderer.caches().pathCache.getRect( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, *texture, op); + } + } else { + SkPath path; + path.addRect(getBoundsOfFill(op)); + renderConvexPath(renderer, state, path, *(op.paint)); + } + } else { + if (op.paint->isAntiAlias() && !state.computedState.transform.isSimple()) { + SkPath path; + path.addRect(op.unmappedBounds.toSkRect()); + renderConvexPath(renderer, state, path, *(op.paint)); + } else { + // render simple unit quad, no tessellation required + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshUnitQuad() + .setFillPaint(*op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); + } + } +} + +void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op, const BakedOpState& state) { + if (op.paint->getPathEffect() != nullptr) { + PathTexture* texture = renderer.caches().pathCache.getRoundRect( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), + op.rx, op.ry, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, *texture, op); + } + } else { + const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect( + state.computedState.transform, *(op.paint), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry); + renderVertexBuffer(renderer, state, *buffer, + op.unmappedBounds.left, op.unmappedBounds.top, *(op.paint), 0); } } @@ -323,8 +467,6 @@ void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, co void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { OffscreenBuffer* buffer = *op.layerHandle; - // TODO: extend this to handle HW layers & paint properties which - // reside in node.properties().layerProperties() float layerAlpha = op.alpha * state.alpha; Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h index 0e763d9c660b..ed34ada90272 100644 --- a/libs/hwui/BakedOpDispatcher.h +++ b/libs/hwui/BakedOpDispatcher.h @@ -26,6 +26,10 @@ namespace uirenderer { /** * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer. + * + * onXXXOp methods must either render directly with the renderer, or call a static renderYYY + * method to render content. There should never be draw content rejection in BakedOpDispatcher - + * it must happen at a higher level (except in error-ish cases, like texture-too-big). */ class BakedOpDispatcher { public: diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index 983c27b4a511..9c836a00bfea 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -53,7 +53,7 @@ struct MergedBakedOpList { class ResolvedRenderState { public: // TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates - ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) { + 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)) { @@ -83,7 +83,17 @@ public: // 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; @@ -129,13 +139,36 @@ class BakedOpState { public: static BakedOpState* tryConstruct(LinearAllocator& allocator, const Snapshot& snapshot, const RecordedOp& recordedOp) { - BakedOpState* bakedOp = new (allocator) BakedOpState(snapshot, recordedOp); - if (bakedOp->computedState.clippedBounds.isEmpty()) { + BakedOpState* bakedState = new (allocator) BakedOpState(snapshot, recordedOp, false); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; + } + + enum class StrokeBehavior { + // stroking is forced, regardless of style on paint + Forced, + // stroking is defined by style on paint + StyleDefined, + }; + + static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, + const 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); + if (bakedState->computedState.clippedBounds.isEmpty()) { // bounds are empty, so op is rejected - allocator.rewindIfLastAlloc(bakedOp); + allocator.rewindIfLastAlloc(bakedState); return nullptr; } - return bakedOp; + return bakedState; } static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, @@ -160,8 +193,8 @@ public: const RecordedOp* op; private: - BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp) - : computedState(snapshot, recordedOp) + BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) + : computedState(snapshot, recordedOp, expandForStroke) , alpha(snapshot.alpha) , roundRectClipState(snapshot.roundRectClipState) , projectionPathMask(snapshot.projectionPathMask) diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index b585a2799cad..0643a54c88c5 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -113,10 +113,10 @@ public: // Geometry virtual void drawPoint(float x, float y, const SkPaint& paint) = 0; - virtual void drawPoints(const float* points, int count, const SkPaint& paint) = 0; + virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) = 0; virtual void drawLine(float startX, float startY, float stopX, float stopY, const SkPaint& paint) = 0; - virtual void drawLines(const float* points, int count, const SkPaint& paint) = 0; + virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) = 0; virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) = 0; virtual void drawRegion(const SkRegion& region, const SkPaint& paint) = 0; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index 9460361f9451..f948f187451d 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -708,12 +708,36 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { } } -static batchid_t tessellatedBatchId(const SkPaint& paint) { +/** + * Defers an unmergeable, strokeable op, accounting correctly + * for paint's style on the bounds being computed. + */ +void OpReorderer::onStrokeableOp(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); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); +} + +/** + * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will + * be used, since they trigger significantly different rendering paths. + * + * Note: not used for lines/points, since they don't currently support path effects. + */ +static batchid_t tessBatchId(const RecordedOp& op) { + const SkPaint& paint = *(op.paint); return paint.getPathEffect() ? OpBatchType::AlphaMaskTexture : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); } +void OpReorderer::onArcOp(const ArcOp& op) { + onStrokeableOp(op, tessBatchId(op)); +} + void OpReorderer::onBitmapOp(const BitmapOp& op) { BakedOpState* bakedState = tryBakeOpState(op); if (!bakedState) return; // quick rejected @@ -734,15 +758,29 @@ void OpReorderer::onBitmapOp(const BitmapOp& op) { } void OpReorderer::onLinesOp(const LinesOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint)); + batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; + onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); +} + +void OpReorderer::onOvalOp(const OvalOp& op) { + onStrokeableOp(op, tessBatchId(op)); +} + +void OpReorderer::onPathOp(const PathOp& op) { + onStrokeableOp(op, OpBatchType::Bitmap); +} + +void OpReorderer::onPointsOp(const PointsOp& op) { + batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; + onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); } void OpReorderer::onRectOp(const RectOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint)); + onStrokeableOp(op, tessBatchId(op)); +} + +void OpReorderer::onRoundRectOp(const RoundRectOp& op) { + onStrokeableOp(op, tessBatchId(op)); } void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index fc77c617fd89..58b607cb9182 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -245,6 +245,10 @@ private: mFrameAllocatedPaths.emplace_back(new SkPath); return mFrameAllocatedPaths.back().get(); } + + void onStrokeableOp(const RecordedOp& op, batchid_t batchId, + BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined); + /** * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type. * diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 2cb32c404525..f49237c9c069 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -40,6 +40,7 @@ #include <SkCanvas.h> #include <SkColor.h> +#include <SkPaintDefaults.h> #include <SkPathOps.h> #include <SkShader.h> #include <SkTypeface.h> @@ -1908,9 +1909,6 @@ void OpenGLRenderer::drawArc(float left, float top, float right, float bottom, drawConvexPath(path, p); } -// See SkPaintDefaults.h -#define SkPaintDefaults_MiterLimit SkIntToScalar(4) - void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, const SkPaint* p) { if (mState.currentlyIgnored() @@ -1921,6 +1919,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, if (p->getStyle() != SkPaint::kFill_Style) { // only fill style is supported by drawConvexPath, since others have to handle joins + static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed"); if (p->getPathEffect() != nullptr || p->getStrokeJoin() != SkPaint::kMiter_Join || p->getStrokeMiter() != SkPaintDefaults_MiterLimit) { mCaches.textureState().activateTexture(0); diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index b96640144657..8ce5473aeb4f 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -43,10 +43,15 @@ struct Vertex; * This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs. */ #define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \ + U_OP_FN(ArcOp) \ M_OP_FN(BitmapOp) \ U_OP_FN(LinesOp) \ + U_OP_FN(OvalOp) \ + U_OP_FN(PathOp) \ + U_OP_FN(PointsOp) \ U_OP_FN(RectOp) \ U_OP_FN(RenderNodeOp) \ + U_OP_FN(RoundRectOp) \ U_OP_FN(ShadowOp) \ U_OP_FN(SimpleRectsOp) \ M_OP_FN(TextOp) \ @@ -74,7 +79,7 @@ namespace RecordedOpId { Count, }; } -static_assert(RecordedOpId::BitmapOp == 0, +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 @@ -86,7 +91,7 @@ struct RecordedOp { /* ID from RecordedOpId - generally used for jumping into function tables */ const int opId; - /* bounds in *local* space, without accounting for DisplayList transformation */ + /* bounds in *local* space, without accounting for DisplayList transformation, or stroke */ const Rect unmappedBounds; /* transform in recording space (vs DisplayList origin) */ @@ -128,6 +133,17 @@ struct RenderNodeOp : RecordedOp { // Standard Ops //////////////////////////////////////////////////////////////////////////////////////////////////// +struct ArcOp : RecordedOp { + ArcOp(BASE_PARAMS, float startAngle, float sweepAngle, bool useCenter) + : SUPER(ArcOp) + , startAngle(startAngle) + , sweepAngle(sweepAngle) + , useCenter(useCenter) {} + const float startAngle; + const float sweepAngle; + const bool useCenter; +}; + struct BitmapOp : RecordedOp { BitmapOp(BASE_PARAMS, const SkBitmap* bitmap) : SUPER(BitmapOp) @@ -145,11 +161,41 @@ struct LinesOp : RecordedOp { const int floatCount; }; +struct OvalOp : RecordedOp { + OvalOp(BASE_PARAMS) + : SUPER(OvalOp) {} +}; + +struct PathOp : RecordedOp { + PathOp(BASE_PARAMS, const SkPath* path) + : SUPER(PathOp) + , path(path) {} + const SkPath* path; +}; + +struct PointsOp : RecordedOp { + PointsOp(BASE_PARAMS, const float* points, const int floatCount) + : SUPER(PointsOp) + , points(points) + , floatCount(floatCount) {} + const float* points; + const int floatCount; +}; + struct RectOp : RecordedOp { RectOp(BASE_PARAMS) : SUPER(RectOp) {} }; +struct RoundRectOp : RecordedOp { + RoundRectOp(BASE_PARAMS, float rx, float ry) + : SUPER(RoundRectOp) + , rx(rx) + , ry(ry) {} + const float rx; + const float ry; +}; + /** * Real-time, dynamic-lit shadow. * diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index e6020cdba266..148c94083047 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -237,26 +237,32 @@ void RecordingCanvas::drawPaint(const SkPaint& paint) { refPaint(&paint))); } +static Rect calcBoundsOfPoints(const float* points, int floatCount) { + Rect unmappedBounds(points[0], points[1], points[0], points[1]); + for (int i = 2; i < floatCount; i += 2) { + unmappedBounds.expandToCover(points[i], points[i + 1]); + } + return unmappedBounds; +} + // Geometry -void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); +void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) { + if (floatCount < 2) return; + floatCount &= ~0x1; // round down to nearest two + + addOp(new (alloc()) PointsOp( + calcBoundsOfPoints(points, floatCount), + *mState.currentSnapshot()->transform, + mState.getRenderTargetClipBounds(), + refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); } void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { if (floatCount < 4) return; floatCount &= ~0x3; // round down to nearest four - Rect unmappedBounds(points[0], points[1], points[0], points[1]); - for (int i = 2; i < floatCount; i += 2) { - unmappedBounds.expandToCover(points[i], points[i + 1]); - } - - // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced - // 1.0 stroke, treat 1.0 as minimum. - unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f); - addOp(new (alloc()) LinesOp( - unmappedBounds, + calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, mState.getRenderTargetClipBounds(), refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); @@ -330,20 +336,42 @@ void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { } void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + addOp(new (alloc()) RoundRectOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint), rx, ry)); } + void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + if (radius <= 0) return; + drawOval(x - radius, y - radius, x + radius, y + radius, paint); } + void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + addOp(new (alloc()) OvalOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint))); } + void RecordingCanvas::drawArc(float left, float top, float right, float bottom, - float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { + addOp(new (alloc()) ArcOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint), + startAngle, sweepAngle, useCenter)); } + void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + addOp(new (alloc()) PathOp( + Rect(path.getBounds()), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint), refPath(&path))); } // Bitmap-based @@ -375,6 +403,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, restore(); } } + void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { @@ -392,10 +421,12 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr LOG_ALWAYS_FATAL("TODO!"); } } + void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) { LOG_ALWAYS_FATAL("TODO!"); } + void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 6d0e9e0e0f7a..16a277162738 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -140,13 +140,13 @@ public: float points[2] = { x, y }; drawPoints(points, 2, paint); } - virtual void drawPoints(const float* points, int count, const SkPaint& paint) override; + virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) override; virtual void drawLine(float startX, float startY, float stopX, float stopY, const SkPaint& paint) override { float points[4] = { startX, startY, stopX, stopY }; drawLines(points, 4, paint); } - virtual void drawLines(const float* points, int count, const SkPaint& paint) override; + virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) override; virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; virtual void drawRoundRect(float left, float top, float right, float bottom, diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 4bcd96dd32f7..1c544b929e64 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -120,16 +120,16 @@ private: class AutoTexture { public: - AutoTexture(const Texture* texture): mTexture(texture) { } + AutoTexture(const Texture* texture) + : texture(texture) {} ~AutoTexture() { - if (mTexture && mTexture->cleanup) { - mTexture->deleteTexture(); - delete mTexture; + if (texture && texture->cleanup) { + texture->deleteTexture(); + delete texture; } } -private: - const Texture* mTexture; + const Texture *const texture; }; // class AutoTexture }; // namespace uirenderer diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp index de14abf6a89b..8321ff92268c 100644 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -23,41 +23,130 @@ namespace android { namespace uirenderer { -TEST(ResolvedRenderState, resolution) { - Matrix4 identity; - identity.loadIdentity(); - +TEST(ResolvedRenderState, construct) { Matrix4 translate10x20; translate10x20.loadTranslate(10, 20, 0); SkPaint paint; - RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint); + RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(100, 200), &paint); { // recorded with transform, no parent transform - auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200)); - ResolvedRenderState state(*parentSnapshot, recordedOp); + auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + ResolvedRenderState state(*parentSnapshot, recordedOp, false); EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20); - EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200)); - EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped + EXPECT_EQ(Rect(0, 0, 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(0, 0, 100, 200)); - ResolvedRenderState state(*parentSnapshot, recordedOp); + auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); + ResolvedRenderState state(*parentSnapshot, recordedOp, false); Matrix4 expectedTranslate; expectedTranslate.loadTranslate(20, 40, 0); - EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate); + EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform); // intersection of parent & transformed child clip - EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200)); + EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect); // translated and also clipped - EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200)); + EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags); + } +} + +const float HAIRLINE = 0.0f; + +// Note: bounds will be conservative, but not precise for non-hairline +// - use approx bounds checks for these +const float SEMI_HAIRLINE = 0.3f; + +struct StrokeTestCase { + float scale; + float strokeWidth; + const std::function<void(const ResolvedRenderState&)> validator; +}; + +const static StrokeTestCase sStrokeTestCases[] = { + { + 1, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds); + } + }, + { + 1, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(49.5f, 49.5f, 150.5f, 150.5f)); + EXPECT_TRUE(Rect(49, 49, 151, 151).contains(state.clippedBounds)); + } + }, + { + 1, 20, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds); + } + }, + + // 3x3 scale: + { + 3, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(149.5f, 149.5f, 200, 200), state.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags); + } + }, + { + 3, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(149.5f, 149.5f, 200, 200)); + EXPECT_TRUE(Rect(149, 149, 200, 200).contains(state.clippedBounds)); + } + }, + { + 3, 20, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(120, 120, 200, 200)); + EXPECT_TRUE(Rect(119, 119, 200, 200).contains(state.clippedBounds)); + } + }, + + // 0.5f x 0.5f scale + { + 0.5f, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds); + } + }, + { + 0.5f, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(24.5f, 24.5f, 75.5f, 75.5f)); + EXPECT_TRUE(Rect(24, 24, 76, 76).contains(state.clippedBounds)); + } + }, + { + 0.5f, 20, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(19.5f, 19.5f, 80.5f, 80.5f)); + EXPECT_TRUE(Rect(19, 19, 81, 81).contains(state.clippedBounds)); + } + } +}; + +TEST(ResolvedRenderState, construct_expandForStroke) { + // Loop over table of test cases and verify different combinations of stroke width and transform + for (auto&& testCase : sStrokeTestCases) { + SkPaint strokedPaint; + strokedPaint.setAntiAlias(true); + strokedPaint.setStyle(SkPaint::kStroke_Style); + strokedPaint.setStrokeWidth(testCase.strokeWidth); + + RectOp recordedOp(Rect(50, 50, 150, 150), + Matrix4::identity(), Rect(200, 200), &strokedPaint); + + Matrix4 snapshotMatrix; + snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1); + auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200)); + + ResolvedRenderState state(*parentSnapshot, recordedOp, true); + testCase.validator(state); } } -TEST(BakedOpState, constructAndReject) { +TEST(BakedOpState, tryConstruct) { LinearAllocator allocator; Matrix4 translate100x0; @@ -65,41 +154,85 @@ TEST(BakedOpState, constructAndReject) { SkPaint paint; { - RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint); - auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200)); - BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp); + RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(100, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp); - EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed - EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op + EXPECT_EQ(nullptr, bakedState); // rejected by clip, so not constructed + EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op } { - RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(0, 0, 100, 200), &paint); - auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200)); - BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp); + RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(100, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, successOp); - EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed - EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op + EXPECT_NE(nullptr, bakedState); // NOT rejected by clip, so will be constructed + EXPECT_LE(64u, allocator.usedSize()); // relatively large alloc for non-rejected op } } -TEST(BakedOpState, oplessConstructAndReject) { +TEST(BakedOpState, tryShadowOpConstruct) { LinearAllocator allocator; { - auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 0, 0)); // empty - BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip + BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); - EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed - EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op + EXPECT_EQ(nullptr, bakedState); // rejected by clip, so not constructed + EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op } { - auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 200)); - BakedOpState* bakedOp = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); - EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed - EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op - EXPECT_EQ((ShadowOp*)0x1234, bakedOp->op); + ASSERT_NE(nullptr, bakedState); // NOT rejected by clip, so will be constructed + EXPECT_LE(64u, allocator.usedSize()); // relatively large alloc for non-rejected op } } +TEST(BakedOpState, tryStrokeableOpConstruct) { + LinearAllocator allocator; + { + // check regular rejection + SkPaint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setStrokeWidth(0.0f); + RectOp rejectOp(Rect(0, 0, 100, 200), Matrix4::identity(), Rect(100, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::StyleDefined); + + EXPECT_EQ(nullptr, bakedState); + EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op + } + { + // check simple unscaled expansion + SkPaint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setStrokeWidth(10.0f); + RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::StyleDefined); + + ASSERT_NE(nullptr, bakedState); + EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); + EXPECT_EQ(0, bakedState->computedState.clipSideFlags); + } + { + // check simple unscaled expansion, and fill style with stroke forced + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setStrokeWidth(10.0f); + RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::Forced); + + ASSERT_NE(nullptr, bakedState); + EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); + EXPECT_EQ(0, bakedState->computedState.clipSideFlags); + } } -} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp index d6192df08bc3..c4d305e5de18 100644 --- a/libs/hwui/tests/unit/ClipAreaTests.cpp +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -92,12 +92,10 @@ TEST(ClipArea, basics) { TEST(ClipArea, paths) { ClipArea area(createClipArea()); - Matrix4 transform; - transform.loadIdentity(); SkPath path; SkScalar r = 100; path.addCircle(r, r, r); - area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op); + area.clipPathWithTransform(path, &Matrix4::identity(), SkRegion::kIntersect_Op); EXPECT_FALSE(area.isEmpty()); EXPECT_FALSE(area.isSimple()); EXPECT_FALSE(area.isRectangleList()); @@ -116,11 +114,10 @@ TEST(ClipArea, replaceNegative) { ClipArea area(createClipArea()); area.setClip(0, 0, 100, 100); - Matrix4 transform; - transform.loadIdentity(); Rect expected(-50, -50, 50, 50); - area.clipRectWithTransform(expected, &transform, SkRegion::kReplace_Op); + area.clipRectWithTransform(expected, &Matrix4::identity(), SkRegion::kReplace_Op); EXPECT_EQ(expected, area.getClipRect()); } + } } diff --git a/libs/hwui/tests/unit/DamageAccumulatorTests.cpp b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp index 29354a79efbd..77001382115a 100644 --- a/libs/hwui/tests/unit/DamageAccumulatorTests.cpp +++ b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp @@ -31,13 +31,11 @@ using namespace android::uirenderer; // as the output. TEST(DamageAccumulator, identity) { DamageAccumulator da; - Matrix4 identity; SkRect curDirty; - identity.loadIdentity(); - da.pushTransform(&identity); + da.pushTransform(&Matrix4::identity()); da.dirty(50, 50, 100, 100); { - da.pushTransform(&identity); + da.pushTransform(&Matrix4::identity()); da.peekAtDirty(&curDirty); ASSERT_EQ(SkRect(), curDirty); da.popTransform(); @@ -68,15 +66,13 @@ TEST(DamageAccumulator, translate) { // Test that dirty rectangles are being unioned across "siblings TEST(DamageAccumulator, union) { DamageAccumulator da; - Matrix4 identity; SkRect curDirty; - identity.loadIdentity(); - da.pushTransform(&identity); + da.pushTransform(&Matrix4::identity()); { - da.pushTransform(&identity); + da.pushTransform(&Matrix4::identity()); da.dirty(50, 50, 100, 100); da.popTransform(); - da.pushTransform(&identity); + da.pushTransform(&Matrix4::identity()); da.dirty(150, 50, 200, 125); da.popTransform(); } diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index 068e832bac5c..5eac498a3816 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -144,6 +144,32 @@ TEST(OpReorderer, simple) { EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end } +TEST(OpReorderer, simpleStroke) { + class SimpleStrokeTestRenderer : public TestRendererBase { + public: + void onPointsOp(const PointsOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + // even though initial bounds are empty... + EXPECT_TRUE(op.unmappedBounds.isEmpty()) + << "initial bounds should be empty, since they're unstroked"; + EXPECT_EQ(Rect(45, 45, 55, 55), state.computedState.clippedBounds) + << "final bounds should account for stroke"; + } + }; + + auto node = TestUtils::createNode(0, 0, 100, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint strokedPaint; + strokedPaint.setStrokeWidth(10); + canvas.drawPoint(50, 50, strokedPaint); + }); + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + createSyncedNodeList(node), sLightCenter); + SimpleStrokeTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + TEST(OpReorderer, simpleRejection) { auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 2449ce870b95..ba9d18559d73 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -44,7 +44,7 @@ TEST(RecordingCanvas, emptyPlayback) { TEST(RecordingCanvas, drawLines) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { SkPaint paint; - paint.setStrokeWidth(20); + paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line canvas.drawLines(&points[0], 7, paint); }); @@ -54,8 +54,8 @@ TEST(RecordingCanvas, drawLines) { ASSERT_EQ(RecordedOpId::LinesOp, op->opId); EXPECT_EQ(4, ((LinesOp*)op)->floatCount) << "float count must be rounded down to closest multiple of 4"; - EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds) - << "unmapped bounds must be size of line, outset by 1/2 stroke width"; + EXPECT_EQ(Rect(0, 0, 20, 10), op->unmappedBounds) + << "unmapped bounds must be size of line, and not outset for stroke width"; } TEST(RecordingCanvas, drawRect) { |