| /* |
| * Copyright 2021 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "experimental/graphite/src/Renderer.h" |
| |
| #include "experimental/graphite/src/DrawWriter.h" |
| #include "experimental/graphite/src/UniformManager.h" |
| #include "experimental/graphite/src/geom/Shape.h" |
| #include "experimental/graphite/src/geom/Transform_graphite.h" |
| #include "include/core/SkPathTypes.h" |
| #include "include/core/SkRect.h" |
| #include "src/core/SkUniformData.h" |
| #include "src/gpu/BufferWriter.h" |
| #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" |
| |
| namespace skgpu { |
| |
| namespace { |
| |
| // TODO: These settings are actually shared by tessellating path renderers, so will be exposed later |
| |
| // Returns the stencil settings to use for a standard Redbook "stencil" pass. |
| constexpr DepthStencilSettings fillrule_settings(bool evenOdd) { |
| // Increments clockwise triangles and decrements counterclockwise. Used for "winding" fill. |
| constexpr DepthStencilSettings::Face kIncCW = { |
| /*stencilFail=*/ StencilOp::kKeep, |
| /*depthFail=*/ StencilOp::kKeep, |
| /*dsPass=*/ StencilOp::kIncWrap, |
| /*stencilCompare=*/CompareOp::kAlways, |
| /*readMask=*/ 0xffffffff, |
| /*writeMask=*/ 0xffffffff |
| }; |
| constexpr DepthStencilSettings::Face kDecCCW = { |
| /*stencilFail=*/ StencilOp::kKeep, |
| /*depthFail=*/ StencilOp::kKeep, |
| /*dsPass=*/ StencilOp::kDecWrap, |
| /*stencilCompare=*/CompareOp::kAlways, |
| /*readMask=*/ 0xffffffff, |
| /*writeMask=*/ 0xffffffff |
| }; |
| |
| // Toggles the bottom stencil bit. Used for "even-odd" fill. |
| constexpr DepthStencilSettings::Face kToggle = { |
| /*stencilFail=*/ StencilOp::kKeep, |
| /*depthFail=*/ StencilOp::kKeep, |
| /*dsPass=*/ StencilOp::kInvert, |
| /*stencilCompare=*/CompareOp::kAlways, |
| /*readMask=*/ 0xffffffff, |
| /*writeMask=*/ 0x00000001 |
| }; |
| |
| // Always use ref = 0, disable depths, but still use greater depth test. |
| constexpr DepthStencilSettings kWindingFill = { |
| /*frontStencil=*/kIncCW, |
| /*backStencil=*/ kDecCCW, |
| /*refValue=*/ 0, |
| /*stencilTest=*/ true, |
| /*depthCompare=*/CompareOp::kAlways, // kGreater once steps know the right depth value |
| /*depthTest=*/ true, |
| /*depthWrite=*/ false |
| }; |
| constexpr DepthStencilSettings kEvenOddFill = { |
| /*frontStencil=*/kToggle, |
| /*backStencil=*/ kToggle, |
| /*refValue=*/ 0, |
| /*stencilTest=*/ true, |
| /*depthCompare=*/CompareOp::kAlways, // kGreater once steps know the right depth value |
| /*depthTest=*/ true, |
| /*depthWrite=*/ false |
| }; |
| |
| return evenOdd ? kEvenOddFill : kWindingFill; |
| } |
| |
| // Returns the stencil settings to use for a standard Redbook "fill" pass. Allows non-zero |
| // stencil values to pass and write a color, and resets the stencil value back to zero; discards |
| // immediately on stencil values of zero (or does the inverse of these operations when the path |
| // requires filling everything else). |
| constexpr DepthStencilSettings cover_settings(bool inverse) { |
| // Resets non-zero bits to 0, passes when not zero. We set depthFail to kZero because if we |
| // encounter that case, the kNotEqual=0 stencil test passed, so it does need to be set back to 0 |
| // and the dsPass op won't be run. In practice, since the stencil steps will fail the same depth |
| // test, the stencil value will likely not be non-zero, but best to be explicit. |
| constexpr DepthStencilSettings::Face kNormal = { |
| /*stencilFail=*/ StencilOp::kKeep, |
| /*depthFail=*/ StencilOp::kZero, |
| /*dsPass=*/ StencilOp::kZero, |
| /*stencilCompare=*/CompareOp::kNotEqual, |
| /*readMask=*/ 0xffffffff, |
| /*writeMask=*/ 0xffffffff |
| }; |
| |
| // Resets non-zero bits to 0, passes when zero |
| constexpr DepthStencilSettings::Face kInverted = { |
| /*stencilFail=*/ StencilOp::kZero, |
| /*depthFail=*/ StencilOp::kKeep, |
| /*dsPass=*/ StencilOp::kKeep, |
| /*stencilCompare=*/CompareOp::kEqual, |
| /*readMask=*/ 0xffffffff, |
| /*writeMask=*/ 0xffffffff |
| }; |
| |
| // Always use ref = 0, enabled depth writes, and greater depth test, both |
| // front and back use the same stencil settings. |
| constexpr DepthStencilSettings kNormalDSS = { |
| /*frontStencil=*/kNormal, |
| /*frontStencil=*/kNormal, |
| /*refValue=*/ 0, |
| /*stencilTest=*/ true, |
| /*depthCompare=*/CompareOp::kAlways, // kGreater once steps know the right depth value |
| /*depthTest=*/ true, |
| /*depthWrite=*/ true |
| }; |
| constexpr DepthStencilSettings kInvertedDSS = { |
| /*frontStencil=*/kInverted, |
| /*backStencil=*/ kInverted, |
| /*refValue=*/ 0, |
| /*stencilTest=*/ true, |
| /*depthCompare=*/CompareOp::kAlways, // kGreater once steps know the right depth value |
| /*depthTest=*/ true, |
| /*depthWrite=*/ true |
| }; |
| return inverse ? kInvertedDSS : kNormalDSS; |
| } |
| |
| class StencilFanRenderStep final : public RenderStep { |
| public: |
| StencilFanRenderStep(bool evenOdd) |
| : RenderStep(Flags::kRequiresMSAA, |
| /*uniforms=*/{}, |
| PrimitiveType::kTriangles, |
| fillrule_settings(evenOdd), |
| /*vertexAttrs=*/{{"position", |
| VertexAttribType::kFloat3, |
| SkSLType::kFloat3}}, |
| /*instanceAttrs=*/{}) {} |
| |
| ~StencilFanRenderStep() override {} |
| |
| const char* name() const override { return "stencil-fan"; } |
| |
| const char* vertexSkSL() const override { |
| return " float4 devPosition = float4(position.xy, 0.0, position.z);\n"; |
| } |
| |
| void writeVertices(DrawWriter* writer, |
| const SkIRect& bounds, |
| const Transform& localToDevice, |
| const Shape& shape) const override { |
| // TODO: Have Shape provide a path-like iterator so we don't actually have to convert non |
| // paths to SkPath just to iterate their pts/verbs |
| SkPath path = shape.asPath(); |
| DrawWriter::Vertices verts{*writer}; |
| for (PathMiddleOutFanIter it(path); !it.done();) { |
| for (auto [p0, p1, p2] : it.nextStack()) { |
| // TODO: PathMiddleOutFanIter should use SkV2 instead of SkPoint? |
| SkV2 p[3] = {{p0.fX, p0.fY}, {p1.fX, p1.fY}, {p2.fX, p2.fY}}; |
| SkV4 devPoints[3]; |
| localToDevice.mapPoints(p, devPoints, 3); |
| |
| // TODO: Support reserving maxTrianglesInFans*3 vertices outside the loop, with |
| // automatic returns of unused verts. |
| verts.append(3) << devPoints[0].x << devPoints[0].y << devPoints[0].w // p0 |
| << devPoints[1].x << devPoints[1].y << devPoints[1].w // p1 |
| << devPoints[2].x << devPoints[2].y << devPoints[2].w; // p2 |
| } |
| } |
| } |
| |
| sk_sp<SkUniformData> writeUniforms(Layout layout, |
| const SkIRect&, |
| const Transform&, |
| const Shape&) const override { |
| // Control points are pre-transformed to device space on the CPU, so no uniforms needed. |
| return nullptr; |
| } |
| }; |
| |
| // TODO: Hand off to csmartdalton, this should roughly correspond to the fStencilPathProgram stage |
| // of skgpu::v1::PathStencilCoverOp using the PathCurveTessellator |
| /* |
| class StencilCurvesRenderStep : public RenderStep { |
| public: |
| StencilCurvesRenderStep() {} |
| |
| ~StencilCurvesRenderStep() override {} |
| |
| const char* name() const override { return "stencil-curves"; } |
| bool requiresStencil() const override { return true; } |
| bool requiresMSAA() const override { return true; } |
| bool performsShading() const override { return false; } |
| |
| private: |
| }; |
| */ |
| |
| // TODO: Hand off to csmartdalton, this should roughly correspond to the fCoverBBoxProgram stage |
| // of skgpu::v1::PathStencilCoverOp. |
| class FillBoundsRenderStep final : public RenderStep { |
| public: |
| // TODO: Will need to add kRequiresStencil when we support specifying stencil settings and |
| // the Renderer includes the stenciling step first. |
| FillBoundsRenderStep(bool inverseFill) |
| : RenderStep(Flags::kPerformsShading, |
| /*uniforms=*/{}, |
| PrimitiveType::kTriangles, |
| cover_settings(inverseFill), |
| /*vertexAttrs=*/{{"position", |
| VertexAttribType::kFloat3, |
| SkSLType::kFloat3}}, |
| /*instanceAttrs=*/{}) |
| , fInverseFill(inverseFill) {} |
| |
| ~FillBoundsRenderStep() override {} |
| |
| const char* name() const override { return "fill-bounds"; } |
| |
| const char* vertexSkSL() const override { |
| return " float4 devPosition = float4(position.xy, 0.0, position.z);\n"; |
| } |
| |
| void writeVertices(DrawWriter* writer, |
| const SkIRect& bounds, |
| const Transform& localToDevice, |
| const Shape& shape) const override { |
| SkV4 devPoints[4]; // ordered TL, TR, BR, BL |
| |
| if (fInverseFill) { |
| // TODO: When we handle local coords, we'd need to map these corners by the inverse. |
| devPoints[0] = {(float) bounds.fLeft, (float) bounds.fTop, 0.f, 1.f}; |
| devPoints[1] = {(float) bounds.fRight, (float) bounds.fTop, 0.f, 1.f}; |
| devPoints[2] = {(float) bounds.fRight, (float) bounds.fBottom, 0.f, 1.f}; |
| devPoints[3] = {(float) bounds.fLeft, (float) bounds.fBottom, 0.f, 1.f}; |
| } else { |
| localToDevice.mapPoints(shape.bounds(), devPoints); |
| } |
| |
| DrawWriter::Vertices verts{*writer}; |
| verts.append(6) << devPoints[0].x << devPoints[0].y << devPoints[0].w // TL |
| << devPoints[3].x << devPoints[3].y << devPoints[3].w // BL |
| << devPoints[1].x << devPoints[1].y << devPoints[1].w // TR |
| << devPoints[1].x << devPoints[1].y << devPoints[1].w // TR |
| << devPoints[3].x << devPoints[3].y << devPoints[3].w // BL |
| << devPoints[2].x << devPoints[2].y << devPoints[2].w;// BR |
| } |
| |
| sk_sp<SkUniformData> writeUniforms(Layout layout, |
| const SkIRect&, |
| const Transform& localToDevice, |
| const Shape&) const override { |
| // Positions are pre-transformed on the CPU so no uniforms needed |
| return nullptr; |
| } |
| |
| private: |
| const bool fInverseFill; |
| }; |
| |
| } // anonymous namespace |
| |
| const Renderer& Renderer::StencilAndFillPath(SkPathFillType fillType) { |
| // Because each fill type uses a different stencil settings, there is one Renderer per type. |
| // However, at each stage (stencil vs. cover), there are only two RenderSteps to branch on. |
| static const StencilFanRenderStep kWindingStencilFan{false}; |
| static const StencilFanRenderStep kEvenOddStencilFan{true}; |
| static const FillBoundsRenderStep kFill{false}; |
| static const FillBoundsRenderStep kInverseFill{true}; |
| |
| // TODO: Uncomment and include the curve stenciling steps to draw curved paths |
| static const Renderer kWindingRenderer{"stencil-and-fill[winding]", |
| &kWindingStencilFan, |
| /*&kWindingStencilCurves,*/ |
| &kFill}; |
| static const Renderer kInverseWindingRenderer{"stencil-and-fill[inverse-winding]", |
| &kWindingStencilFan, |
| /*&kWindingStencilCurves,*/ |
| &kInverseFill}; |
| static const Renderer kEvenOddRenderer{"stencil-and-fill[evenodd]", |
| &kEvenOddStencilFan, |
| /*&kEvenOddStencilCurves,*/ |
| &kFill}; |
| static const Renderer kInverseEvenOddRenderer{"stencil-and-fill[inverse-evenodd]", |
| &kEvenOddStencilFan, |
| /*&kEvenOddStencilCurves,*/ |
| &kInverseFill}; |
| |
| switch(fillType) { |
| case SkPathFillType::kWinding: return kWindingRenderer; |
| case SkPathFillType::kEvenOdd: return kEvenOddRenderer; |
| case SkPathFillType::kInverseWinding: return kInverseWindingRenderer; |
| case SkPathFillType::kInverseEvenOdd: return kInverseEvenOddRenderer; |
| } |
| SkUNREACHABLE; |
| } |
| |
| } // namespace skgpu |