Add more shape drawing to new reorderer/renderer
bug:22480459 Add support for outsetting final bounds based on stroke. Change-Id: I659318ccec51882bba1906ce3c7042288ce35c30
This commit is contained in:
@@ -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::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::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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(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 @@ 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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 @@ 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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user