Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a6f28e35 authored by Chris Craik's avatar Chris Craik Committed by Android (Google) Code Review
Browse files

Merge "Add more shape drawing to new reorderer/renderer"

parents e3792518 386aa031
Loading
Loading
Loading
Loading
+175 −33
Original line number Diff line number Diff line
@@ -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,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)
@@ -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);
    }
}

@@ -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)
+4 −0
Original line number Diff line number Diff line
@@ -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:
+40 −7
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ struct MergedBakedOpList {
class ResolvedRenderState {
public:
    // TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
    ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) {
    ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) {
        /* TODO: benchmark a fast path for translate-only matrices, such as:
        if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
                && recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
@@ -83,7 +83,17 @@ public:

        // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
        clippedBounds = recordedOp.unmappedBounds;
        if (CC_UNLIKELY(expandForStroke)) {
            // account for non-hairline stroke
            clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
        }
        transform.mapRect(clippedBounds);
        if (CC_UNLIKELY(expandForStroke
                && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
            // account for hairline stroke when stroke may be < 1 scaled pixel
            // Non translate || strokeWidth < 1 is conservative, but will cover all cases
            clippedBounds.outset(0.5f);
        }

        if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
        if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
@@ -129,13 +139,36 @@ class BakedOpState {
public:
    static BakedOpState* tryConstruct(LinearAllocator& allocator,
            const Snapshot& snapshot, const RecordedOp& recordedOp) {
        BakedOpState* bakedOp = new (allocator) BakedOpState(snapshot, recordedOp);
        if (bakedOp->computedState.clippedBounds.isEmpty()) {
        BakedOpState* bakedState = new (allocator) BakedOpState(snapshot, recordedOp, false);
        if (bakedState->computedState.clippedBounds.isEmpty()) {
            // bounds are empty, so op is rejected
            allocator.rewindIfLastAlloc(bakedState);
            return nullptr;
        }
        return bakedState;
    }

    enum class StrokeBehavior {
        // stroking is forced, regardless of style on paint
        Forced,
        // stroking is defined by style on paint
        StyleDefined,
    };

    static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
            const Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
        bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
                ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
                : true;

        BakedOpState* bakedState = new (allocator) BakedOpState(
                snapshot, recordedOp, expandForStroke);
        if (bakedState->computedState.clippedBounds.isEmpty()) {
            // bounds are empty, so op is rejected
            allocator.rewindIfLastAlloc(bakedOp);
            allocator.rewindIfLastAlloc(bakedState);
            return nullptr;
        }
        return bakedOp;
        return bakedState;
    }

    static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
@@ -160,8 +193,8 @@ public:
    const RecordedOp* op;

private:
    BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp)
            : computedState(snapshot, recordedOp)
    BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke)
            : computedState(snapshot, recordedOp, expandForStroke)
            , alpha(snapshot.alpha)
            , roundRectClipState(snapshot.roundRectClipState)
            , projectionPathMask(snapshot.projectionPathMask)
+2 −2
Original line number Diff line number Diff line
@@ -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;
+45 −7
Original line number Diff line number Diff line
@@ -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) {
Loading