blob: e23005d7a24653f91926fa048a73dc8cd0874aba [file]
/*
* Copyright 2026 Rive
*/
// Vertex shader for depthStencil fills.
//
// Fills never outset in depthStencil mode: a fill vertex is just a literal
// tessellation vertex or contour midpoint. Therefore, these draws don't use
// input attribs or instancing. The patch topology lives in a flat, repeating
// index buffer (See gpu::generateDepthStencilFillIndices()), and each
// invocation derives its attributes from gl_VertexIndex.
//
// For now, strokes stay on DrawType::depthStrokes and draw_path.vert.
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
// No attributes: everything comes from the vertex index.
ATTR_BLOCK_END
#endif
// Locations must match draw_path.vert; the fragment stage is
// draw_depthstencil_object.frag, which is built against that file's varyings.
VARYING_BLOCK_BEGIN
NO_PERSPECTIVE VARYING(0, float4, v_paint);
#ifdef @ENABLE_CLIPPING
@OPTIONALLY_FLAT VARYING(4, half2, v_clipIDs); // [clipID, outerClipID]
#endif
#ifdef @ENABLE_ADVANCED_BLEND
@OPTIONALLY_FLAT VARYING(6, half, v_blendMode);
#endif
#ifdef @ENABLE_MODULATED_IMAGE
NO_PERSPECTIVE VARYING(9, float3, v_image);
#endif
VARYING_BLOCK_END
#ifdef @VERTEX
VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexIdx, _instanceIdx)
{
VARYING_INIT(v_paint, float4);
#ifdef @ENABLE_CLIPPING
VARYING_INIT(v_clipIDs, half2);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
VARYING_INIT(v_blendMode, half);
#endif
#ifdef @ENABLE_MODULATED_IMAGE
VARYING_INIT(v_image, float3);
#endif
// In order to avoid push constants, Rive smuggles in flags via vertex ID.
// This works because there are no actual vertex attrib buffers.
bool isOuterCubicPatch = (_vertexIdx & VERTEX_FLAG_OUTER_CUBIC) != 0;
bool colorWriteDisabled =
(_vertexIdx & VERTEX_FLAG_DISABLE_COLOR_WRITE) != 0;
int vertexIdxNoFlags = _vertexIdx & ((1 << VERTEX_FLAGS_SHIFT) - 1);
// The index buffers align per-patch vertex IDs on pow2 strides,
// specifically so this shader can decode _vertexIdx without divides and
// mods. (Using integer division costs 10% total frame perf on PowerVR and
// 3% on Adreno.)
// NOTE: The patches don't actually have pow2 numbers of vertices; the index
// buffers just never reference those vertex IDs between the end of one
// patch and the beginning of another.
int patchStrideLog2 = DS_PATCH_STRIDE_LOG2(isOuterCubicPatch);
int patchIdx = vertexIdxNoFlags >> patchStrideLog2;
int patchVertexID = vertexIdxNoFlags & ((1 << patchStrideLog2) - 1);
int patchTessVertexStride =
isOuterCubicPatch
// outerCubics tessellate a redundant bowtie vertex at the end of
// every patch (for AA), which is never referenced by the
// depthStencil fill index buffers. But we still need to stride over
// it when indexing into the tessellation texture.
? int(OUTER_CUBIC_PATCH_SEGMENT_SPAN_PLUS_BOWTIE)
: int(MIDPOINT_FAN_PATCH_SEGMENT_SPAN);
bool isMidpointVertex =
!isOuterCubicPatch && patchVertexID == DS_MIDPOINT_VERTEX_ID;
int localVertexID = isMidpointVertex ? 0 : patchVertexID;
// Fetch a vertex that definitely belongs to the contour we're drawing.
int vertexIDOnContour = min(localVertexID, patchTessVertexStride - 1);
int tessVertexIdx = patchIdx * patchTessVertexStride + vertexIDOnContour;
uint4 tessVertexData =
TEXEL_FETCH(@tessVertexTexture, tess_texel_coord(tessVertexIdx));
uint contourIDWithFlags = tessVertexData.w;
// Fetch and unpack the contour referenced by the tessellation vertex.
// NOTE: The contourID is guaranteed to be >= 1 at this point, but clamp it
// anyway because in the event of a bug, a buffer load at index "0u - 1" can
// be very serious and hard to catch.
uint contourID = max(contourIDWithFlags & CONTOUR_ID_MASK, 1u);
uint4 contourData = STORAGE_BUFFER_LOAD4(@contourBuffer, contourID - 1u);
float2 midpoint = uintBitsToFloat(contourData.xy);
uint pathID = contourData.z & 0xffffu;
uint vertexIndex0 = contourData.w;
// Fetch and unpack the path.
float2x2 M = make_float2x2(
uintBitsToFloat(STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u)));
uint4 pathData = STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 1u);
float2 translate = uintBitsToFloat(pathData.xy);
// 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.
// (Only midpoint-fan fan columns have one; the patch buffer gave outer
// cubics and the midpoint the same vertex either way.)
uint mirroredContourFlag =
contourIDWithFlags & MIRRORED_CONTOUR_CONTOUR_FLAG;
if (mirroredContourFlag != 0u && !isOuterCubicPatch && !isMidpointVertex)
{
localVertexID = localVertexID - 1;
}
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.
int replacementTessVertexIdx =
tessVertexIdx + localVertexID - vertexIDOnContour;
uint4 replacementTessVertexData =
TEXEL_FETCH(@tessVertexTexture,
tess_texel_coord(replacementTessVertexIdx));
if ((replacementTessVertexData.w &
(MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)) !=
(contourIDWithFlags & (MIRRORED_CONTOUR_CONTOUR_FLAG | 0xffffu)))
{
// We crossed over into a new contour. Fills are always closed, so
// wrap to the first vertex in the contour.
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 & ~MIRRORED_CONTOUR_CONTOUR_FLAG) |
mirroredContourFlag;
}
float2 origin =
isMidpointVertex ? midpoint : uintBitsToFloat(tessVertexData.xy);
float2 vertexPosition = MUL(M, origin) + translate;
uint2 paintData = STORAGE_BUFFER_LOAD2(@paintBuffer, pathID);
uint paintType = paintData.x & 0xfu;
#ifdef @ENABLE_CLIPPING
if (@ENABLE_CLIPPING)
{
uint clipIDBits =
(paintType == CLIP_UPDATE_PAINT_TYPE ? paintData.y : paintData.x) >>
16;
half clipID = id_bits_to_f16(clipIDBits, uniforms.pathIDGranularity);
// Negative clipID means to update the clip buffer instead of the color
// buffer.
if (paintType == CLIP_UPDATE_PAINT_TYPE)
clipID = -clipID;
v_clipIDs.x = clipID;
}
#endif
#ifdef @ENABLE_ADVANCED_BLEND
if (@ENABLE_ADVANCED_BLEND)
{
v_blendMode = float((paintData.x >> 4) & 0xfu);
}
#endif
// Paint matrices operate on the fragment shader's "_fragCoord", which
// counts from memory row 0. A bottom up target needs it flipped into Rive
// pixel space.
float2 fragCoord = vertexPosition;
#ifdef @ENABLE_RENDER_TARGET_BOTTOM_UP
if (uniforms.renderTargetBottomUp != 0u)
{
fragCoord.y = float(uniforms.renderTargetHeight) - fragCoord.y;
}
#endif
#ifdef @ENABLE_CLIP_RECT
if (@ENABLE_CLIP_RECT)
{
// clipRectInverseMatrix transforms from pixel coordinates to a space
// where the clipRect is the normalized rectangle: [-1, -1, 1, 1].
float2x2 clipRectInverseMatrix = make_float2x2(
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT + 2u));
float4 clipRectInverseTranslate =
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT + 3u);
set_clip_rect_plane_distances(clipRectInverseMatrix,
clipRectInverseTranslate.xy,
fragCoord CLIP_CONTEXT_UNPACK);
}
#endif // ENABLE_CLIP_RECT
// Unpack the paint once we have a position.
if (paintType == SOLID_COLOR_PAINT_TYPE)
{
v_paint = float4(unpackUnorm4x8(paintData.y));
}
#ifdef @ENABLE_CLIPPING
else if (@ENABLE_CLIPPING && paintType == CLIP_UPDATE_PAINT_TYPE)
{
half outerClipID =
id_bits_to_f16(paintData.x >> 16, uniforms.pathIDGranularity);
v_clipIDs.y = outerClipID;
}
#endif
else
{
float2x2 paintMatrix = make_float2x2(
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT));
float4 paintTranslate =
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT + 1u);
// paintData.y (gradient texture row + 1) in the integer part
// additiveness in range 0/256 to 255/256 in the fraction.
v_paint = packGradientData(fragCoord,
paintMatrix,
paintTranslate.xy,
float(paintType),
paintTranslate.zw,
uintBitsToFloat(paintData.y));
// Make this negative to signal to the fragment shader that it's a
// gradient
v_paint.a = -v_paint.a;
}
if (colorWriteDisabled)
{
// Zeroing v_paint is all we need to disable color write; float4(0) gets
// interpreted by the fragment shader as a fully transparent
// SOLID_COLOR_PAINT_TYPE, and then discarded at the blend step.
v_paint = float4(.0, .0, .0, .0);
}
#ifdef @ENABLE_MODULATED_IMAGE
if (@ENABLE_MODULATED_IMAGE && (paintData.x & PAINT_FLAG_HAS_IMAGE) != 0u)
{
float2x2 imageMatrix = make_float2x2(
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT + 4u));
float4 paintTranslateAndLOD =
STORAGE_BUFFER_LOAD4(@paintAuxBuffer,
pathID * PAINT_AUX_ENTRY_ELEMENT_COUNT + 5u);
float2 imageCoord =
MUL(imageMatrix, fragCoord) + paintTranslateAndLOD.xy;
// Add 1 to the LOD because a z value of 0 means "we don't have an
// image"
v_image =
float3(imageCoord.x, imageCoord.y, 1. + paintTranslateAndLOD.z);
}
else
{
v_image = float3(0.0, 0.0, 0.0);
}
#endif
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
#ifdef @POST_INVERT_Y
pos.y = -pos.y;
#endif
uint4 pathData2 = STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 2u);
pos.z = packNormalizedDepth(cast_uint_to_ushort(pathData2.x), 0xffu);
VARYING_PACK(v_paint);
#ifdef @ENABLE_CLIPPING
VARYING_PACK(v_clipIDs);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
VARYING_PACK(v_blendMode);
#endif
#ifdef @ENABLE_MODULATED_IMAGE
VARYING_PACK(v_image);
#endif
EMIT_VERTEX(pos);
}
#endif // @VERTEX