Add more shape drawing to new reorderer/renderer
bug:22480459
Add support for outsetting final bounds based on stroke.
Change-Id: I659318ccec51882bba1906ce3c7042288ce35c30
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index fde12dd..3d35dd5 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 @@
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::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::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));
+ }
+}
+
+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);
+ }
+}
+
+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) {
- 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);
+ 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);
+ }
+ }
}
-namespace VertexBufferRenderFlags {
- enum {
- Offset = 0x1,
- ShadowInterp = 0x2,
- };
-}
-
-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::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::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 0e763d9..ed34ada 100644
--- a/libs/hwui/BakedOpDispatcher.h
+++ b/libs/hwui/BakedOpDispatcher.h
@@ -26,6 +26,10 @@
/**
* 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 983c27b..9c836a0 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -53,7 +53,7 @@
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 @@
// 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 @@
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(bakedOp);
+ allocator.rewindIfLastAlloc(bakedState);
return nullptr;
}
- return bakedOp;
+ 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(bakedState);
+ return nullptr;
+ }
+ return bakedState;
}
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
@@ -160,8 +193,8 @@
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 b585a27..0643a54 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -113,10 +113,10 @@
// 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 9460361..f948f18 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -708,12 +708,36 @@
}
}
-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::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 fc77c61..58b607c 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -245,6 +245,10 @@
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 2cb32c4..f49237c 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 @@
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 @@
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 b966401..8ce5473 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -43,10 +43,15 @@
* 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 @@
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 @@
/* 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 @@
// 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 @@
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 e6020cd..148c940 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -237,26 +237,32 @@
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::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 @@
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 @@
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 6d0e9e0..16a2771 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -140,13 +140,13 @@
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 4bcd96d..1c544b9 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -120,16 +120,16 @@
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 de14abf..8321ff9 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);
}
}
-TEST(BakedOpState, constructAndReject) {
+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, tryConstruct) {
LinearAllocator allocator;
Matrix4 translate100x0;
@@ -65,41 +154,85 @@
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 d6192df..c4d305e 100644
--- a/libs/hwui/tests/unit/ClipAreaTests.cpp
+++ b/libs/hwui/tests/unit/ClipAreaTests.cpp
@@ -92,12 +92,10 @@
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 @@
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 29354a7..7700138 100644
--- a/libs/hwui/tests/unit/DamageAccumulatorTests.cpp
+++ b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp
@@ -31,13 +31,11 @@
// 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 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 068e832..5eac498 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -144,6 +144,32 @@
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 2449ce8..ba9d185 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -44,7 +44,7 @@
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 @@
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) {