/* * Copyright 2023 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/graphite/render/PerEdgeAAQuadRenderStep.h" #include "src/base/SkVx.h" #include "src/core/SkRRectPriv.h" #include "src/gpu/graphite/DrawParams.h" #include "src/gpu/graphite/DrawWriter.h" #include "src/gpu/graphite/render/CommonDepthStencilSettings.h" // This RenderStep is specialized to draw filled rectangles with per-edge AA. // // Each of these "primitives" is represented by a single instance. The instance attributes are // flexible enough to describe per-edge AA quads without relying on uniforms to define its // operation. The attributes encode shape as follows: // // float4 edgeFlags - per-edge AA defined by each component: aa != 0. // float4 quadXs - these values provide the X coordinates of the quadrilateral in top-left CW order. // float4 quadYs - these values provide the Y coordinates of the quadrilateral. // // From the other direction, per-edge AA quads produce instance values like: // - [aa(t,r,b,l) ? 255 : 0] [xs(tl,tr,br,bl)] [ys(tl,tr,br,bl)] // // From this encoding, data can be unpacked for each corner, which are equivalent under // rotational symmetry. Per-edge quads are always mitered and fill the interior, but the // vertices are placed such that the edge coverage ramps can collapse to 0 area on non-AA edges. // // The vertices that describe each corner are placed so that edges and miters calculate // coverage by interpolating a varying and then clamping in the fragment shader. Triangles that // cover the inner and outer curves calculate distance to the curve within the fragment shader. // // See https://docs.google.com/presentation/d/1MCPstNsSlDBhR8CrsJo0r-cZNbu-sEJEvU9W94GOJoY/edit?usp=sharing // for diagrams and explanation of how the geometry is defined. // // PerEdgeAAQuadRenderStep uses the common technique of approximating distance to the level set by // one expansion of the Taylor's series for the level set's equation. Given a level set function // C(x,y), this amounts to calculating C(px,py)/|∇C(px,py)|. For the straight edges the level set // is linear and calculated in the vertex shader and then interpolated exactly over the rectangle. // This provides distances to all four exterior edges within the fragment shader and allows it to // reconstruct a relative position per elliptical corner. Unfortunately this requires the fragment // shader to calculate the length of the gradient for straight edges instead of interpolating // exact device-space distance. // // Unlike AnalyticRRectRenderStep, for per-edge AA quads it's valid to have each pixel calculate a // single corner's coverage that's controlled via the vertex shader. Any bias is a constant 1/2, // so this is also added in the vertex shader. // // Analytic derivatives are used so that a single pipeline can be used regardless of HW derivative // support or for geometry that would prove difficult for forward differencing. The device-space // gradient for ellipses is calculated per-pixel by transforming a per-pixel local gradient vector // with the Jacobian of the inverse local-to-device transform: // // (px,py) is the projected point of (u,v) transformed by a 3x3 matrix, M: // [x(u,v) / w(u,v)] [x] [m00 m01 m02] [u] // (px,py) = [y(u,v) / w(u,v)] where [y] = [m10 m11 m12]X[v] = M*(u,v,1) // [w] [m20 m21 m22] [1] // // C(px,py) can be defined in terms of a local Cl(u,v) as C(px,py) = Cl(p^-1(px,py)), where p^-1 = // // [x'(px,py) / w'(px,py)] [x'] [m00' m01' * m02'] [px] // (u,v) = [y'(px,py) / w'(px,py)] where [y'] = [m10' m11' * m12']X[py] = M^-1*(px,py,0,1) // [w'] [m20' m21' * m22'] [ 1] // // Note that if the 3x3 M was arrived by dropping the 3rd row and column from a 4x4 since we assume // a local 3rd coordinate of 0, M^-1 is not equal to the 4x4 inverse with dropped rows and columns. // // Using the chain rule, then ∇C(px,py) // = ∇Cl(u,v)X[1/w'(px,py) 0 -x'(px,py)/w'(px,py)^2] [m00' m01'] // [ 0 1/w'(px,py) -y'(px,py)/w'(px,py)^2]X[m10' m11'] // [m20' m21'] // // = 1/w'(px,py)*∇Cl(u,v)X[1 0 -x'(px,py)/w'(px,py)] [m00' m01'] // [0 1 -y'(px,py)/w'(px,py)]X[m10' m11'] // [m20' m21'] // // = w(u,v)*∇Cl(u,v)X[1 0 0 -u] [m00' m01'] // [0 1 0 -v]X[m10' m11'] // [m20' m21'] // // = w(u,v)*∇Cl(u,v)X[m00'-m20'u m01'-m21'u] // [m10'-m20'v m11'-m21'v] // // The vertex shader calculates the rightmost 2x2 matrix and interpolates it across the shape since // each component is linear in (u,v). ∇Cl(u,v) is evaluated per pixel in the fragment shader and // depends on which corner and edge being evaluated. w(u,v) is the device-space W coordinate, so // its reciprocal is provided in sk_FragCoord.w. namespace skgpu::graphite { AAFlags; static bool is_clockwise(const EdgeAAQuad& quad) { … } // Represents the per-vertex attributes used in each instance. struct Vertex { … }; // Allowed values for the center weight instance value (selected at record time based on style // and transform), and are defined such that when (insance-weight > vertex-weight) is true, the // vertex should be snapped to the center instead of its regular calculation. static constexpr int kCornerVertexCount = …; // sk_VertexID is divided by this in SkSL static constexpr int kVertexCount = …; static constexpr int kIndexCount = …; static void write_index_buffer(VertexWriter writer) { … } static void write_vertex_buffer(VertexWriter writer) { … } PerEdgeAAQuadRenderStep::PerEdgeAAQuadRenderStep(StaticBufferManager* bufferManager) : … { … } PerEdgeAAQuadRenderStep::~PerEdgeAAQuadRenderStep() { … } std::string PerEdgeAAQuadRenderStep::vertexSkSL() const { … } const char* PerEdgeAAQuadRenderStep::fragmentCoverageSkSL() const { … } void PerEdgeAAQuadRenderStep::writeVertices(DrawWriter* writer, const DrawParams& params, skvx::ushort2 ssboIndices) const { … } void PerEdgeAAQuadRenderStep::writeUniformsAndTextures(const DrawParams&, PipelineDataGatherer*) const { … } } // namespace skgpu::graphite