[graphite] Implement stenciled curves RenderStep
Cq-Include-Trybots: luci.skia.skia.primary:Build-Mac-Clang-arm64-Debug-ASAN_Graphite,Build-Mac-Clang-arm64-Debug-Graphite,Build-Mac-Clang-arm64-Debug-Graphite_NoGpu,Build-Mac-Clang-arm64-Debug-iOS_Graphite,Build-Mac-Clang-arm64-Release-Graphite,Build-Mac-Clang-arm64-Release-iOS_Graphite,Build-Mac-Clang-x86_64-Debug-Graphite,Build-Mac-Clang-x86_64-Release-Graphite,Perf-Mac10.15.7-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Graphite,Perf-Mac11-Clang-MacMini9.1-GPU-AppleM1-arm64-Release-All-Graphite,Test-Mac10.15.7-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Graphite,Test-Mac11-Clang-MacMini9.1-GPU-AppleM1-arm64-Debug-All-ASAN_Graphite,Test-Mac11-Clang-MacMini9.1-GPU-AppleM1-arm64-Release-All-Graphite,Test-Mac12-Clang-MacBookPro16.2-GPU-IntelIrisPlus-x86_64-Debug-All-Graphite Bug: skia:12703 Change-Id: Ie80d495f38614f9b5d0b09741ca0d24560ebe870 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/520976 Reviewed-by: Robert Phillips <robertphillips@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com> Auto-Submit: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Jim Van Verth <jvanverth@google.com>
This commit is contained in:
parent
627969bf1b
commit
02ebd1a233
@ -88,6 +88,13 @@ public:
|
||||
SkASSERT(fPendingCount == 0);
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
// Query current pipeline state for validation
|
||||
size_t instanceStride() const { return fInstanceStride; }
|
||||
size_t vertexStride() const { return fVertexStride; }
|
||||
PrimitiveType primitiveType() const { return fPrimitiveType; }
|
||||
#endif
|
||||
|
||||
// Collects new vertex data for a call to CommandBuffer::draw(). Automatically accumulates
|
||||
// vertex data into a buffer, issuing draw and bind calls as needed when a new buffer is
|
||||
// required, so that it is seamless to the caller. The draws do not use instances or indices.
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "src/core/SkPipelineData.h"
|
||||
#include "src/core/SkShaderCodeDictionary.h"
|
||||
|
||||
#include "src/gpu/tessellate/WangsFormula.h"
|
||||
|
||||
namespace skgpu::mtl {
|
||||
|
||||
namespace {
|
||||
@ -168,6 +170,10 @@ std::string get_sksl_vs(const GraphicsPipelineDesc& desc) {
|
||||
sksl += emit_SKSL_uniforms(1, "Step", step->uniforms());
|
||||
}
|
||||
|
||||
// TODO: This is only needed for tessellation path renderers and should be handled using a
|
||||
// helper function injector that the SkSL built-in code snippets can use.
|
||||
sksl += wangs_formula::as_sksl().c_str();
|
||||
|
||||
// Vertex shader function declaration
|
||||
sksl += "void main() {\n";
|
||||
// Vertex shader body
|
||||
|
@ -15,7 +15,10 @@
|
||||
#include "include/core/SkRect.h"
|
||||
#include "src/core/SkUniformData.h"
|
||||
#include "src/gpu/BufferWriter.h"
|
||||
#include "src/gpu/tessellate/AffineMatrix.h"
|
||||
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
|
||||
#include "src/gpu/tessellate/PatchWriter.h"
|
||||
#include "src/gpu/tessellate/PathCurveTessellator.h"
|
||||
#include "src/gpu/tessellate/PathTessellator.h"
|
||||
|
||||
namespace skgpu {
|
||||
@ -185,30 +188,214 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Hand off to csmartdalton, this should roughly correspond to the fStencilPathProgram stage
|
||||
// of skgpu::v1::PathStencilCoverOp using the PathCurveTessellator
|
||||
/*
|
||||
class StencilCurvesRenderStep : public RenderStep {
|
||||
// TODO: This can be shared by the other path tessellators if PatchWriter can provide the correct
|
||||
// index count based on its traits (curve, wedge, or stroke).
|
||||
// Satisfies API requirements for PatchAllocator template to PatchWriter, using
|
||||
// DrawWriter::DynamicInstances.
|
||||
struct DrawWriterAllocator {
|
||||
DrawWriterAllocator(size_t stride, // required for PatchAllocator
|
||||
DrawWriter& writer,
|
||||
BindBufferInfo fixedVertexBuffer,
|
||||
BindBufferInfo fixedIndexBuffer,
|
||||
unsigned int reserveCount)
|
||||
: fInstances(writer, fixedVertexBuffer, fixedIndexBuffer) {
|
||||
SkASSERT(writer.instanceStride() == stride);
|
||||
// TODO: Is it worth re-reserving large chunks after this preallocation is used up? Or will
|
||||
// appending 1 at a time be fine since it's coming from a large vertex buffer alloc anyway?
|
||||
fInstances.reserve(reserveCount);
|
||||
}
|
||||
|
||||
VertexWriter append() {
|
||||
// TODO (skbug.com/13056): Actually compute optimal minimum required index count based on
|
||||
// PatchWriter's tracked segment count^4.
|
||||
static constexpr unsigned int kMaxIndexCount =
|
||||
3 * PathTessellator::NumCurveTrianglesAtResolveLevel(
|
||||
PathTessellator::kMaxFixedResolveLevel);
|
||||
return fInstances.append(kMaxIndexCount, 1);
|
||||
}
|
||||
|
||||
DrawWriter::DynamicInstances fInstances;
|
||||
};
|
||||
|
||||
class StencilCurvesRenderStep final : public RenderStep {
|
||||
// No fan point or stroke params, since this is for filled curves (not strokes or wedges)
|
||||
// No explicit curve type, since we assume infinity is supported on GPUs using graphite
|
||||
// No color or wide color attribs, since it might always be part of the PaintParams
|
||||
// or we'll add a color-only fast path to RenderStep later.
|
||||
static constexpr PatchAttribs kAttribs = PatchAttribs::kNone;
|
||||
using Writer = PatchWriter<DrawWriterAllocator,
|
||||
AddTrianglesWhenChopping,
|
||||
DiscardFlatCurves>;
|
||||
|
||||
public:
|
||||
StencilCurvesRenderStep() {}
|
||||
StencilCurvesRenderStep(bool evenOdd)
|
||||
: RenderStep(Flags::kRequiresMSAA,
|
||||
/*uniforms=*/{},
|
||||
PrimitiveType::kTriangles,
|
||||
fillrule_settings(evenOdd),
|
||||
/*vertexAttrs=*/ {{"resolveLevel_and_idx",
|
||||
VertexAttribType::kFloat2, SkSLType::kFloat2}},
|
||||
/*instanceAttrs=*/{{"p01", VertexAttribType::kFloat4, SkSLType::kFloat4},
|
||||
{"p23", VertexAttribType::kFloat4, SkSLType::kFloat4}}) {
|
||||
SkASSERT(this->instanceStride() == PatchStride(kAttribs));
|
||||
}
|
||||
|
||||
~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; }
|
||||
static size_t FixedVertexBufferSize() {
|
||||
return PathCurveTessellator::FixedVertexBufferSize(PathTessellator::kMaxFixedResolveLevel);
|
||||
}
|
||||
static size_t FixedIndexBufferSize() {
|
||||
return PathCurveTessellator::FixedIndexBufferSize(PathTessellator::kMaxFixedResolveLevel);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const override { return "stencil-curves"; }
|
||||
|
||||
const char* vertexSkSL() const override {
|
||||
// TODO: Share SkSL with GrPathTessellationShader_MiddleOut
|
||||
// TODO: This SkSL depends on wangs_formula::as_sksl(), which is currently manually added in
|
||||
// MtlGraphicsPipeline but could be handled nicer.
|
||||
return R"(
|
||||
float resolveLevel = resolveLevel_and_idx.x;
|
||||
float idxInResolveLevel = resolveLevel_and_idx.y;
|
||||
float2 localcoord;
|
||||
if (isinf(p23.z)) {
|
||||
// This patch is an exact triangle.
|
||||
localcoord = (resolveLevel != 0) ? p01.zw
|
||||
: (idxInResolveLevel != 0) ? p23.xy
|
||||
: p01.xy;
|
||||
} else {
|
||||
float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
|
||||
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
|
||||
float maxResolveLevel;
|
||||
if (isinf(p23.w)) {
|
||||
// Conics are 3 points, with the weight in p3.
|
||||
w = p3.x;
|
||||
maxResolveLevel = wangs_formula_conic_log2(4, p0, p1, p2, w);
|
||||
p1 *= w; // Unproject p1.
|
||||
p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics.
|
||||
} else {
|
||||
// The patch is an integral cubic.
|
||||
maxResolveLevel = wangs_formula_cubic_log2(4, p0, p1, p2, p3,
|
||||
float2x2(1.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
if (resolveLevel > maxResolveLevel) {
|
||||
// This vertex is at a higher resolve level than we need. Demote to a lower
|
||||
// resolveLevel, which will produce a degenerate triangle.
|
||||
idxInResolveLevel = floor(ldexp(idxInResolveLevel,
|
||||
int(maxResolveLevel - resolveLevel)));
|
||||
resolveLevel = maxResolveLevel;
|
||||
}
|
||||
// Promote our location to a discrete position in the maximum fixed resolve level.
|
||||
// This is extra paranoia to ensure we get the exact same fp32 coordinates for
|
||||
// colocated points from different resolve levels (e.g., the vertices T=3/4 and
|
||||
// T=6/8 should be exactly colocated).
|
||||
float fixedVertexID = floor(.5 + ldexp(idxInResolveLevel, int(5 - resolveLevel)));
|
||||
if (0 < fixedVertexID && fixedVertexID < 32) {
|
||||
float T = fixedVertexID * (1 / 32.0);
|
||||
|
||||
// Evaluate at T. Use De Casteljau's for its accuracy and stability.
|
||||
float2 ab = mix(p0, p1, T);
|
||||
float2 bc = mix(p1, p2, T);
|
||||
float2 cd = mix(p2, p3, T);
|
||||
float2 abc = mix(ab, bc, T);
|
||||
float2 bcd = mix(bc, cd, T);
|
||||
float2 abcd = mix(abc, bcd, T);
|
||||
|
||||
// Evaluate the conic weight at T.
|
||||
float u = mix(1.0, w, T);
|
||||
float v = w + 1 - u; // == mix(w, 1, T)
|
||||
float uv = mix(u, v, T);
|
||||
|
||||
localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
|
||||
} else {
|
||||
localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy;
|
||||
}
|
||||
}
|
||||
float4 devPosition = float4(localcoord.xy, 0.0, 1.0);)";
|
||||
}
|
||||
|
||||
void writeVertices(DrawWriter* w,
|
||||
const SkIRect& bounds,
|
||||
const Transform& localToDevice,
|
||||
const Shape& shape) const override {
|
||||
// TODO: Caps check
|
||||
static constexpr int kMaxTessellationSegments = 1 << PathTessellator::kMaxFixedResolveLevel;
|
||||
SkPath path = shape.asPath(); // TODO: Iterate the Shape directly
|
||||
|
||||
BindBufferInfo fixedVertexBuffer = w->bufferManager()->getStaticBuffer(
|
||||
BufferType::kVertex,
|
||||
PathCurveTessellator::WriteFixedVertexBuffer,
|
||||
FixedVertexBufferSize);
|
||||
BindBufferInfo fixedIndexBuffer = w->bufferManager()->getStaticBuffer(
|
||||
BufferType::kIndex,
|
||||
PathCurveTessellator::WriteFixedIndexBuffer,
|
||||
FixedIndexBufferSize);
|
||||
|
||||
int patchReserveCount = PathCurveTessellator::PatchPreallocCount(path.countVerbs());
|
||||
Writer writer{kAttribs, kMaxTessellationSegments,
|
||||
*w, fixedVertexBuffer, fixedIndexBuffer, patchReserveCount};
|
||||
|
||||
// TODO: Is it better to pre-transform on the CPU and only have a matrix uniform to compute
|
||||
// local coords, or is it better to always transform on the GPU (less CPU usage, more
|
||||
// uniform data to upload, dependent on push constants or storage buffers for good batching)
|
||||
|
||||
// Currently no additional transform is applied by the GPU.
|
||||
wangs_formula::VectorXform shaderXform(SkMatrix::I());
|
||||
// TODO: This doesn't handle perspective yet, and ideally wouldn't go through SkMatrix.
|
||||
// It may not be relevant, though, if transforms are applied on the GPU and we only need to
|
||||
// determine an approximate 2x2 for 'shaderXform' and Wang's formula evaluation.
|
||||
AffineMatrix m(localToDevice.matrix().asM33());
|
||||
|
||||
// TODO: For filled curves, the path verb loop is simple enough that it's not too big a deal
|
||||
// to copy the logic from PathCurveTessellator::write_patches. It may be required if we end
|
||||
// up switching to a shape iterator in graphite vs. a path iterator in ganesh, or if
|
||||
// graphite does not control point transformation on the CPU. On the other hand, if we
|
||||
// provide a templated WritePatches function, the iterator could also be a template arg in
|
||||
// addition to PatchWriter's traits. Whatever pattern we choose will be based more on what's
|
||||
// best for the wedge and stroke case, which have more complex loops.
|
||||
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
|
||||
switch (verb) {
|
||||
case SkPathVerb::kQuad: {
|
||||
auto [p0, p1] = m.map2Points(pts);
|
||||
auto p2 = m.map1Point(pts+2);
|
||||
|
||||
writer.writeQuadratic(p0, p1, p2, shaderXform);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkPathVerb::kConic: {
|
||||
auto [p0, p1] = m.map2Points(pts);
|
||||
auto p2 = m.map1Point(pts+2);
|
||||
|
||||
writer.writeConic(p0, p1, p2, *w, shaderXform);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkPathVerb::kCubic: {
|
||||
auto [p0, p1] = m.map2Points(pts);
|
||||
auto [p2, p3] = m.map2Points(pts+2);
|
||||
|
||||
writer.writeCubic(p0, p1, p2, p3, shaderXform);
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sk_sp<SkUniformData> writeUniforms(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 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=*/{},
|
||||
@ -272,25 +459,27 @@ const Renderer& Renderer::StencilAndFillPath(SkPathFillType fillType) {
|
||||
// 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 StencilCurvesRenderStep kWindingStencilCurves{false};
|
||||
static const StencilCurvesRenderStep kEvenOddStencilCurves{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,*/
|
||||
&kWindingStencilCurves,
|
||||
&kFill};
|
||||
static const Renderer kInverseWindingRenderer{"stencil-and-fill[inverse-winding]",
|
||||
&kWindingStencilFan,
|
||||
/*&kWindingStencilCurves,*/
|
||||
&kWindingStencilCurves,
|
||||
&kInverseFill};
|
||||
static const Renderer kEvenOddRenderer{"stencil-and-fill[evenodd]",
|
||||
&kEvenOddStencilFan,
|
||||
/*&kEvenOddStencilCurves,*/
|
||||
&kEvenOddStencilCurves,
|
||||
&kFill};
|
||||
static const Renderer kInverseEvenOddRenderer{"stencil-and-fill[inverse-evenodd]",
|
||||
&kEvenOddStencilFan,
|
||||
/*&kEvenOddStencilCurves,*/
|
||||
&kEvenOddStencilCurves,
|
||||
&kInverseFill};
|
||||
|
||||
switch(fillType) {
|
||||
|
Loading…
Reference in New Issue
Block a user