| /* | |
| * Copyright 2023 Rive | |
| */ | |
| // Common functions shared by draw shaders. | |
| #ifdef @VERTEX | |
| VERTEX_TEXTURE_BLOCK_BEGIN | |
| TEXTURE_RGBA32UI(TESS_VERTEX_TEXTURE_IDX, @tessVertexTexture); | |
| VERTEX_TEXTURE_BLOCK_END | |
| VERTEX_STORAGE_BUFFER_BLOCK_BEGIN | |
| STORAGE_BUFFER_U32x4(PATH_BUFFER_IDX, PathBuffer, @pathBuffer); | |
| STORAGE_BUFFER_U32x2(PAINT_BUFFER_IDX, PaintBuffer, @paintBuffer); | |
| STORAGE_BUFFER_F32x4(PAINT_AUX_BUFFER_IDX, PaintAuxBuffer, @paintAuxBuffer); | |
| STORAGE_BUFFER_U32x4(CONTOUR_BUFFER_IDX, ContourBuffer, @contourBuffer); | |
| VERTEX_STORAGE_BUFFER_BLOCK_END | |
| #ifdef @DRAW_PATH | |
| INLINE int2 tess_texel_coord(int texelIndex) | |
| { | |
| return int2(texelIndex & ((1 << TESS_TEXTURE_WIDTH_LOG2) - 1), | |
| texelIndex >> TESS_TEXTURE_WIDTH_LOG2); | |
| } | |
| INLINE float manhattan_pixel_width(float2x2 M, float2 normalized) | |
| { | |
| float2 v = MUL(M, normalized); | |
| return (abs(v.x) + abs(v.y)) * (1. / dot(v, v)); | |
| } | |
| INLINE bool unpack_tessellated_path_vertex(float4 patchVertexData, | |
| float4 mirroredVertexData, | |
| int _instanceID, | |
| OUT(ushort) o_pathID, | |
| OUT(float2) o_vertexPosition | |
| #ifndef @USING_DEPTH_STENCIL | |
| , | |
| OUT(half2) o_edgeDistance | |
| #else | |
| , | |
| OUT(ushort) o_pathZIndex | |
| #endif | |
| VERTEX_CONTEXT_DECL) | |
| { | |
| // Unpack patchVertexData. | |
| int localVertexID = int(patchVertexData.x); | |
| float outset = patchVertexData.y; | |
| float fillCoverage = patchVertexData.z; | |
| int patchSegmentSpan = floatBitsToInt(patchVertexData.w) >> 2; | |
| int vertexType = floatBitsToInt(patchVertexData.w) & 3; | |
| // Fetch a vertex that definitely belongs to the contour we're drawing. | |
| int vertexIDOnContour = min(localVertexID, patchSegmentSpan - 1); | |
| int tessVertexIdx = _instanceID * patchSegmentSpan + vertexIDOnContour; | |
| uint4 tessVertexData = TEXEL_FETCH(@tessVertexTexture, tess_texel_coord(tessVertexIdx)); | |
| uint contourIDWithFlags = tessVertexData.w; | |
| // Fetch and unpack the contour referenced by the tessellation vertex. | |
| uint4 contourData = STORAGE_BUFFER_LOAD4(@contourBuffer, contour_data_idx(contourIDWithFlags)); | |
| float2 midpoint = uintBitsToFloat(contourData.xy); | |
| o_pathID = make_ushort(contourData.z & 0xffffu); | |
| uint vertexIndex0 = contourData.w; | |
| // Fetch and unpack the path. | |
| float2x2 M = make_float2x2(uintBitsToFloat(STORAGE_BUFFER_LOAD4(@pathBuffer, o_pathID * 2u))); | |
| uint4 pathData = STORAGE_BUFFER_LOAD4(@pathBuffer, o_pathID * 2u + 1u); | |
| float2 translate = uintBitsToFloat(pathData.xy); | |
| float strokeRadius = uintBitsToFloat(pathData.z); | |
| #ifdef @USING_DEPTH_STENCIL | |
| o_pathZIndex = make_ushort(pathData.w); | |
| #endif | |
| // Fix the tessellation vertex if we fetched the wrong one in order to guarantee we got the | |
| // correct contour ID and flags, or if we belong to a mirrored contour and this vertex has an | |
| // alternate position when mirrored. | |
| uint mirroredContourFlag = contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG; | |
| if (mirroredContourFlag != 0u) | |
| { | |
| localVertexID = int(mirroredVertexData.x); | |
| outset = mirroredVertexData.y; | |
| fillCoverage = mirroredVertexData.z; | |
| } | |
| if (localVertexID != vertexIDOnContour) | |
| { | |
| // This can peek one vertex before or after the contour, but the tessellator guarantees | |
| // there is always at least one padding vertex at the beginning and end of the data. | |
| tessVertexIdx += localVertexID - vertexIDOnContour; | |
| uint4 replacementTessVertexData = | |
| TEXEL_FETCH(@tessVertexTexture, tess_texel_coord(tessVertexIdx)); | |
| if ((replacementTessVertexData.w & 0xffffu) != (contourIDWithFlags & 0xffffu)) | |
| { | |
| // We crossed over into a new contour. Either wrap to the first vertex in the contour or | |
| // leave it clamped at the final vertex of the contour. | |
| bool isClosed = strokeRadius == .0 || // filled | |
| midpoint.x != .0; // explicity closed stroke | |
| if (isClosed) | |
| { | |
| tessVertexData = | |
| TEXEL_FETCH(@tessVertexTexture, tess_texel_coord(int(vertexIndex0))); | |
| } | |
| } | |
| else | |
| { | |
| tessVertexData = replacementTessVertexData; | |
| } | |
| // MIRRORED_CONTOUR_CONTOUR_FLAG is not preserved at vertexIndex0. Preserve it here. By not | |
| // preserving this flag, the normal and mirrored contour can both share the same contour | |
| // record. | |
| contourIDWithFlags = tessVertexData.w | mirroredContourFlag; | |
| } | |
| // Finish unpacking tessVertexData. | |
| float theta = uintBitsToFloat(tessVertexData.z); | |
| float2 norm = float2(sin(theta), -cos(theta)); | |
| float2 origin = uintBitsToFloat(tessVertexData.xy); | |
| float2 postTransformVertexOffset; | |
| if (strokeRadius != .0) // Is this a stroke? | |
| { | |
| // Ensure strokes always emit clockwise triangles. | |
| outset *= sign(determinant(M)); | |
| // Joins only emanate from the outer side of the stroke. | |
| if ((contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u) | |
| outset = min(outset, .0); | |
| if ((contourIDWithFlags & RIGHT_JOIN_CONTOUR_FLAG) != 0u) | |
| outset = max(outset, .0); | |
| float aaRadius = manhattan_pixel_width(M, norm) * AA_RADIUS; | |
| half globalCoverage = 1.; | |
| if (aaRadius > strokeRadius) | |
| { | |
| // The stroke is narrower than the AA ramp. Instead of emitting subpixel geometry, make | |
| // the stroke as wide as the AA ramp and apply a global coverage multiplier. | |
| globalCoverage = make_half(strokeRadius) / make_half(aaRadius); | |
| strokeRadius = aaRadius; | |
| } | |
| // Extend the vertex by half the width of the AA ramp. | |
| float2 vertexOffset = MUL(norm, strokeRadius + aaRadius); // Bloat stroke width for AA. | |
| #ifndef @USING_DEPTH_STENCIL | |
| // Calculate the AA distance to both the outset and inset edges of the stroke. The fragment | |
| // shader will use whichever is lesser. | |
| float x = outset * (strokeRadius + aaRadius); | |
| o_edgeDistance = make_half2((1. / (aaRadius * 2.)) * (float2(x, -x) + strokeRadius) + .5); | |
| #endif | |
| uint joinType = contourIDWithFlags & JOIN_TYPE_MASK; | |
| if (joinType != 0u) | |
| { | |
| // This vertex belongs to a miter or bevel join. Begin by finding the bisector, which is | |
| // the same as the miter line. The first two vertices in the join peek forward to figure | |
| // out the bisector, and the final two peek backward. | |
| int peekDir = 2; | |
| if ((contourIDWithFlags & JOIN_TANGENT_0_CONTOUR_FLAG) == 0u) | |
| peekDir = -peekDir; | |
| if ((contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG) != 0u) | |
| peekDir = -peekDir; | |
| int2 otherJoinTexelCoord = tess_texel_coord(tessVertexIdx + peekDir); | |
| uint4 otherJoinData = TEXEL_FETCH(@tessVertexTexture, otherJoinTexelCoord); | |
| float otherJoinTheta = uintBitsToFloat(otherJoinData.z); | |
| float joinAngle = abs(otherJoinTheta - theta); | |
| if (joinAngle > PI) | |
| joinAngle = 2. * PI - joinAngle; | |
| bool isTan0 = (contourIDWithFlags & JOIN_TANGENT_0_CONTOUR_FLAG) != 0u; | |
| bool isLeftJoin = (contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u; | |
| float bisectTheta = joinAngle * (isTan0 == isLeftJoin ? -.5 : .5) + theta; | |
| float2 bisector = float2(sin(bisectTheta), -cos(bisectTheta)); | |
| float bisectPixelWidth = manhattan_pixel_width(M, bisector); | |
| // Generalize everything to a "miter-clip", which is proposed in the SVG-2 draft. Bevel | |
| // joins are converted to miter-clip joins with a miter limit of 1/2 pixel. They | |
| // technically bleed out 1/2 pixel when drawn this way, but they seem to look fine and | |
| // there is not an obvious solution to antialias them without an ink bleed. | |
| float miterRatio = cos(joinAngle * .5); | |
| float clipRadius; | |
| if ((joinType == MITER_CLIP_JOIN_CONTOUR_FLAG) || | |
| (joinType == MITER_REVERT_JOIN_CONTOUR_FLAG && miterRatio >= .25)) | |
| { | |
| // Miter! (Or square cap.) | |
| // We currently use hard coded miter limits: | |
| // * 1 for square caps being emulated as miter-clip joins. | |
| // * 4, which is the SVG default, for all other miter joins. | |
| float miterInverseLimit = | |
| (contourIDWithFlags & EMULATED_STROKE_CAP_CONTOUR_FLAG) != 0u ? 1. : .25; | |
| clipRadius = strokeRadius * (1. / max(miterRatio, miterInverseLimit)); | |
| } | |
| else | |
| { | |
| // Bevel! (Or butt cap.) | |
| clipRadius = strokeRadius * miterRatio + /* 1/2px bleed! */ bisectPixelWidth * .5; | |
| } | |
| float clipAARadius = clipRadius + bisectPixelWidth * AA_RADIUS; | |
| if ((contourIDWithFlags & JOIN_TANGENT_INNER_CONTOUR_FLAG) != 0u) | |
| { | |
| // Reposition the inner join vertices at the miter-clip positions. Leave the outer | |
| // join vertices as duplicates on the surrounding curve endpoints. We emit duplicate | |
| // vertex positions because we need a hard stop on the clip distance (see below). | |
| // | |
| // Use aaRadius here because we're tracking AA on the mitered edge, NOT the outer | |
| // clip edge. | |
| float strokeAARaidus = strokeRadius + aaRadius; | |
| // clipAARadius must be 1/16 of an AA ramp (~1/16 pixel) longer than the miter | |
| // length before we start clipping, to ensure we are solving for a numerically | |
| // stable intersection. | |
| float slop = aaRadius * .125; | |
| if (strokeAARaidus <= clipAARadius * miterRatio + slop) | |
| { | |
| // The miter point is before the clip line. Extend out to the miter point. | |
| float miterAARadius = strokeAARaidus * (1. / miterRatio); | |
| vertexOffset = bisector * miterAARadius; | |
| } | |
| else | |
| { | |
| // The clip line is before the miter point. Find where the clip line and the | |
| // mitered edge intersect. | |
| float2 bisectAAOffset = bisector * clipAARadius; | |
| float2 k = float2(dot(vertexOffset, vertexOffset), | |
| dot(bisectAAOffset, bisectAAOffset)); | |
| vertexOffset = MUL(k, inverse(float2x2(vertexOffset, bisectAAOffset))); | |
| } | |
| } | |
| // The clip distance tells us how to antialias the outer clipped edge. Since joins only | |
| // emanate from the outset side of the stroke, we can repurpose the inset distance as | |
| // the clip distance. | |
| float2 pt = abs(outset) * vertexOffset; | |
| float clipDistance = | |
| (clipAARadius - dot(pt, bisector)) / (bisectPixelWidth * (AA_RADIUS * 2.)); | |
| #ifndef @USING_DEPTH_STENCIL | |
| if ((contourIDWithFlags & LEFT_JOIN_CONTOUR_FLAG) != 0u) | |
| o_edgeDistance.y = make_half(clipDistance); | |
| else | |
| o_edgeDistance.x = make_half(clipDistance); | |
| #endif | |
| } | |
| #ifndef @USING_DEPTH_STENCIL | |
| o_edgeDistance *= globalCoverage; | |
| // Bias o_edgeDistance.y slightly upwards in order to guarantee o_edgeDistance.y is >= 0 at | |
| // every pixel. "o_edgeDistance.y < 0" is used to differentiate between strokes and fills. | |
| o_edgeDistance.y = max(o_edgeDistance.y, make_half(1e-4)); | |
| #endif | |
| postTransformVertexOffset = MUL(M, outset * vertexOffset); | |
| // Throw away the fan triangles since we're a stroke. | |
| if (vertexType != STROKE_VERTEX) | |
| return false; | |
| } | |
| else // This is a fill. | |
| { | |
| // Place the fan point. | |
| if (vertexType == FAN_MIDPOINT_VERTEX) | |
| origin = midpoint; | |
| // Offset the vertex for Manhattan AA. | |
| postTransformVertexOffset = sign(MUL(outset * norm, inverse(M))) * AA_RADIUS; | |
| if ((contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG) != 0u) | |
| fillCoverage = -fillCoverage; | |
| #ifndef @USING_DEPTH_STENCIL | |
| // "o_edgeDistance.y < 0" indicates to the fragment shader that this is a fill. | |
| o_edgeDistance = make_half2(fillCoverage, -1); | |
| #endif | |
| // If we're actually just drawing a triangle, throw away the entire patch except a single | |
| // fan triangle. | |
| if ((contourIDWithFlags & RETROFITTED_TRIANGLE_CONTOUR_FLAG) != 0u && | |
| vertexType != FAN_VERTEX) | |
| return false; | |
| } | |
| o_vertexPosition = MUL(M, origin) + postTransformVertexOffset + translate; | |
| return true; | |
| } | |
| #endif // @DRAW_PATH | |
| #ifdef @DRAW_INTERIOR_TRIANGLES | |
| INLINE float2 unpack_interior_triangle_vertex(float3 triangleVertex, | |
| OUT(ushort) o_pathID, | |
| OUT(half) o_windingWeight VERTEX_CONTEXT_DECL) | |
| { | |
| o_pathID = make_ushort(floatBitsToUint(triangleVertex.z) & 0xffffu); | |
| float2x2 M = make_float2x2(uintBitsToFloat(STORAGE_BUFFER_LOAD4(@pathBuffer, o_pathID * 2u))); | |
| uint4 pathData = STORAGE_BUFFER_LOAD4(@pathBuffer, o_pathID * 2u + 1u); | |
| float2 translate = uintBitsToFloat(pathData.xy); | |
| o_windingWeight = float(floatBitsToInt(triangleVertex.z) >> 16) * sign(determinant(M)); | |
| return MUL(M, triangleVertex.xy) + translate; | |
| } | |
| #endif // @DRAW_INTERIOR_TRIANGLES | |
| #endif // @VERTEX |