Loading libs/hwui/BakedOpDispatcher.cpp +175 −33 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -27,6 +28,7 @@ #include <algorithm> #include <math.h> #include <SkPaintDefaults.h> namespace android { namespace uirenderer { Loading Loading @@ -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; Loading @@ -225,10 +311,73 @@ 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) { 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) Loading @@ -239,29 +388,24 @@ void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, co .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); } } Loading Loading @@ -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) Loading libs/hwui/BakedOpDispatcher.h +4 −0 Original line number Diff line number Diff line Loading @@ -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: Loading libs/hwui/BakedOpState.h +40 −7 Original line number Diff line number Diff line Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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) Loading libs/hwui/Canvas.h +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading libs/hwui/OpReorderer.cpp +45 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading Loading
libs/hwui/BakedOpDispatcher.cpp +175 −33 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -27,6 +28,7 @@ #include <algorithm> #include <math.h> #include <SkPaintDefaults.h> namespace android { namespace uirenderer { Loading Loading @@ -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; Loading @@ -225,10 +311,73 @@ 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) { 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) Loading @@ -239,29 +388,24 @@ void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, co .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); } } Loading Loading @@ -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) Loading
libs/hwui/BakedOpDispatcher.h +4 −0 Original line number Diff line number Diff line Loading @@ -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: Loading
libs/hwui/BakedOpState.h +40 −7 Original line number Diff line number Diff line Loading @@ -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)) { Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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) Loading
libs/hwui/Canvas.h +2 −2 Original line number Diff line number Diff line Loading @@ -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; Loading
libs/hwui/OpReorderer.cpp +45 −7 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading