blob: e6fd32dc86afae5698de69f827ba1aacd15d8edd [file]
/*
* Copyright 2022 Rive
*/
// undef COVERAGE_TYPE first because this file gets included multiple times with
// different defines in the Metal library.
#undef COVERAGE_TYPE
#ifdef @ENABLE_FEATHER
#define COVERAGE_TYPE float4
#else
#define COVERAGE_TYPE half2
#endif
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
#if defined(@DRAW_INTERIOR_TRIANGLES) || defined(@FEATHER_ATLAS_BLIT)
ATTR(0, packed_float3, @a_triangleVertex);
#else
// Unreal compiler breaks if these declarations are multi line.
// clang-format off
ATTR(0, float4, @a_patchVertexData); // [localVertexID, outset, fillCoverage, vertexType]
// clang-format on
ATTR(1, float4, @a_mirroredVertexData);
#endif
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
NO_PERSPECTIVE VARYING(0, float4, v_paint);
#ifdef @FEATHER_ATLAS_BLIT
NO_PERSPECTIVE VARYING(1, float2, v_atlasCoord);
#elif !defined(@RENDER_MODE_DEPTH_STENCIL)
#ifdef @DRAW_INTERIOR_TRIANGLES
@OPTIONALLY_FLAT VARYING(1, half, v_windingWeight);
#else
NO_PERSPECTIVE VARYING(2, COVERAGE_TYPE, v_coverages);
#endif //@DRAW_INTERIOR_TRIANGLES
@OPTIONALLY_FLAT VARYING(3, half, v_pathID);
#endif // !@RENDER_MODE_DEPTH_STENCIL
#ifdef @ENABLE_CLIPPING
#ifdef @FEATHER_ATLAS_BLIT
@OPTIONALLY_FLAT VARYING(4, half, v_clipID); // [clipID, outerClipID]
#else
@OPTIONALLY_FLAT VARYING(4, half2, v_clipIDs); // [clipID, outerClipID]
#endif
#endif // @ENABLE_CLIPPING
#if defined(@ENABLE_CLIP_RECT) && !defined(@RENDER_MODE_DEPTH_STENCIL)
NO_PERSPECTIVE VARYING(5, float4, v_clipRect);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
@OPTIONALLY_FLAT VARYING(6, half, v_blendMode);
#endif
#ifdef @RENDER_MODE_CLOCKWISE_ATOMIC
FLAT VARYING(7, uint2, v_coveragePlacement);
VARYING(8, float2, v_coverageCoord);
#endif
#ifdef @ENABLE_MODULATED_IMAGE
NO_PERSPECTIVE VARYING(9, float3, v_image);
#endif
VARYING_BLOCK_END
#ifdef @VERTEX
VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexID, _instanceID)
{
#if defined(@DRAW_INTERIOR_TRIANGLES) || defined(@FEATHER_ATLAS_BLIT)
ATTR_UNPACK(_vertexID, attrs, @a_triangleVertex, float3);
#else
ATTR_UNPACK(_vertexID, attrs, @a_patchVertexData, float4);
ATTR_UNPACK(_vertexID, attrs, @a_mirroredVertexData, float4);
#endif
VARYING_INIT(v_paint, float4);
#if defined(@ENABLE_MODULATED_IMAGE)
VARYING_INIT(v_image, float3);
#endif
#ifdef @FEATHER_ATLAS_BLIT
VARYING_INIT(v_atlasCoord, float2);
#elif !defined(@RENDER_MODE_DEPTH_STENCIL)
#ifdef @DRAW_INTERIOR_TRIANGLES
VARYING_INIT(v_windingWeight, half);
#else
VARYING_INIT(v_coverages, COVERAGE_TYPE);
#endif //@DRAW_INTERIOR_TRIANGLES
VARYING_INIT(v_pathID, half);
#endif // !@RENDER_MODE_DEPTH_STENCIL
#ifdef @ENABLE_CLIPPING
#ifdef @FEATHER_ATLAS_BLIT
VARYING_INIT(v_clipID, half);
#else
VARYING_INIT(v_clipIDs, half2);
#endif
#endif // @ENABLE_CLIPPING
#if defined(@ENABLE_CLIP_RECT) && !defined(@RENDER_MODE_DEPTH_STENCIL)
VARYING_INIT(v_clipRect, float4);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
VARYING_INIT(v_blendMode, half);
#endif
#ifdef @RENDER_MODE_CLOCKWISE_ATOMIC
VARYING_INIT(v_coveragePlacement, uint2);
VARYING_INIT(v_coverageCoord, float2);
#endif
bool shouldDiscardVertex = false;
uint pathID;
float2 vertexPosition;
#ifdef @RENDER_MODE_DEPTH_STENCIL
ushort pathZIndex;
#endif
#ifdef @FEATHER_ATLAS_BLIT
vertexPosition = unpack_atlas_coverage_vertex(
@a_triangleVertex,
pathID,
#ifdef @RENDER_MODE_DEPTH_STENCIL
pathZIndex,
#endif
v_atlasCoord VERTEX_CONTEXT_UNPACK);
#elif defined(@DRAW_INTERIOR_TRIANGLES)
vertexPosition = unpack_interior_triangle_vertex(
@a_triangleVertex,
pathID
#ifdef @RENDER_MODE_DEPTH_STENCIL
,
pathZIndex
#else
,
v_windingWeight
#endif
VERTEX_CONTEXT_UNPACK);
#else // !@DRAW_INTERIOR_TRIANGLES
float4 coverages;
shouldDiscardVertex = !unpack_tessellated_path_vertex(
@a_patchVertexData,
@a_mirroredVertexData,
_instanceID,
pathID,
vertexPosition
#ifndef @RENDER_MODE_DEPTH_STENCIL
,
coverages
#else
,
pathZIndex
#endif
VERTEX_CONTEXT_UNPACK);
#ifndef @RENDER_MODE_DEPTH_STENCIL
#ifdef @ENABLE_FEATHER
v_coverages = coverages;
#else
v_coverages.xy = cast_float2_to_half2(coverages.xy);
#endif
#endif
#endif // !DRAW_INTERIOR_TRIANGLES
// The paint lives in the second half of the path record's second uint4.
uint2 paintData = STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 1u).zw;
#if !defined(@FEATHER_ATLAS_BLIT) && !defined(@RENDER_MODE_DEPTH_STENCIL)
// Encode the integral pathID as a "half" that we know the hardware will see
// as a unique value in the fragment shader.
v_pathID = id_bits_to_f16(pathID, uniforms.pathIDGranularity);
// Indicate even-odd fill rule by making pathID negative.
if ((paintData.x & PAINT_FLAG_EVEN_ODD_FILL) != 0u)
v_pathID = -v_pathID;
#endif // !@FEATHER_ATLAS_BLIT && !@RENDER_MODE_DEPTH_STENCIL
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;
#ifdef @FEATHER_ATLAS_BLIT
v_clipID = clipID;
#else
v_clipIDs.x = clipID;
#endif
}
#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);
#ifndef @RENDER_MODE_DEPTH_STENCIL
v_clipRect = find_clip_rect_coverage_distances(
clipRectInverseMatrix,
clipRectInverseTranslate.xy,
fragCoord);
#else // !@RENDER_MODE_DEPTH_STENCIL => @RENDER_MODE_DEPTH_STENCIL
set_clip_rect_plane_distances(
clipRectInverseMatrix,
clipRectInverseTranslate.xy,
fragCoord CLIP_CONTEXT_UNPACK);
#endif // @RENDER_MODE_DEPTH_STENCIL
}
#endif // ENABLE_CLIP_RECT
// Unpack the paint once we have a position.
if (paintType == SOLID_COLOR_PAINT_TYPE)
{
v_paint = float4(unpackUnorm4x8(paintData.y));
}
#if defined(@ENABLE_CLIPPING) && !defined(@FEATHER_ATLAS_BLIT)
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);
float additivenessComplement = uintBitsToFloat(paintData.y);
v_paint = packGradientData(
fragCoord,
paintMatrix,
paintTranslate.xy,
paintTranslate.zw,
paintType,
additivenessComplement,
1.0); // coverage
}
#if defined(@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"
float imageZ = 1. + paintTranslateAndLOD.z;
if ((paintData.x & PAINT_FLAG_LAYER_MASK) != 0u)
{
// A layer-mask apply. The sampled texel is coverage, not color, so
// the fragment shader has to treat it differently -- and there is
// no varying to spare for a per-draw flag. Signal it by making z
// negative and packing the mode into it, the same sentinel idiom
// this shader already uses twice (a negative paint.a means
// "gradient", a zero image.z means "no image"). The mask is always
// sampled 1:1 with the target, so LOD 0 is exact and there is
// nothing lost by giving up the LOD field here.
uint mode = (paintData.x & PAINT_LAYER_MASK_MODE_MASK) >>
PAINT_LAYER_MASK_MODE_SHIFT;
imageZ = -(1. + float(mode));
}
v_image = float3(imageCoord.x, imageCoord.y, imageZ);
}
else
{
v_image = float3(0.0, 0.0, 0.0);
}
#endif
float4 pos;
if (!shouldDiscardVertex)
{
pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
#ifdef @POST_INVERT_Y
pos.y = -pos.y;
#endif
#ifdef @RENDER_MODE_DEPTH_STENCIL
pos.z = packNormalizedDepth(pathZIndex, 0xffu);
#elif defined(@RENDER_MODE_CLOCKWISE_ATOMIC)
uint4 coverageData = STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 3u);
v_coveragePlacement = coverageData.xy;
v_coverageCoord = vertexPosition + uintBitsToFloat(coverageData.zw);
#endif
}
else
{
pos = float4(
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue);
}
VARYING_PACK(v_paint);
#if defined(@ENABLE_MODULATED_IMAGE)
VARYING_PACK(v_image);
#endif
#ifdef @FEATHER_ATLAS_BLIT
VARYING_PACK(v_atlasCoord);
#elif !defined(@RENDER_MODE_DEPTH_STENCIL)
#ifdef @DRAW_INTERIOR_TRIANGLES
VARYING_PACK(v_windingWeight);
#else
VARYING_PACK(v_coverages);
#endif //@DRAW_INTERIOR_TRIANGLES
VARYING_PACK(v_pathID);
#endif // !@RENDER_MODE_DEPTH_STENCIL
#ifdef @ENABLE_CLIPPING
#ifdef @FEATHER_ATLAS_BLIT
VARYING_PACK(v_clipID);
#else
VARYING_PACK(v_clipIDs);
#endif
#endif // @ENABLE_CLIPPING
#if defined(@ENABLE_CLIP_RECT) && !defined(@RENDER_MODE_DEPTH_STENCIL)
VARYING_PACK(v_clipRect);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
VARYING_PACK(v_blendMode);
#endif
#ifdef @RENDER_MODE_CLOCKWISE_ATOMIC
VARYING_PACK(v_coveragePlacement);
VARYING_PACK(v_coverageCoord);
#endif
EMIT_VERTEX(pos);
}
#endif
#ifdef @FRAGMENT
FRAG_STORAGE_BUFFER_BLOCK_BEGIN
FRAG_STORAGE_BUFFER_BLOCK_END
// Add a function here for fragments to unpack the paint since we're the ones
// who packed it in the vertex shader.
INLINE half4 find_paint_color(
#ifdef @ENABLE_MODULATED_IMAGE
float3 image,
#endif
#ifdef @ENABLE_ADVANCED_BLEND
ushort blendMode,
#endif
float4 paint FRAGMENT_CONTEXT_DECL)
{
#ifdef @ENABLE_ADVANCED_BLEND
bool paintHasAdvancedBlend = @ENABLE_ADVANCED_BLEND &&
blendMode != BLEND_SRC_OVER;
#else
const bool paintHasAdvancedBlend = false;
#endif
half4 color;
if (paint.a >= .0) // Is the paint a solid color?
{
// The CPU sent 'paint' unmultiplied for advanced-blend draws and
// premultiplied otherwise, matching paintHasAdvancedBlend.
color = cast_float4_to_half4(paint);
}
else // Paint is a gradient (linear or radial)?
{
float2 gradientTexCoord = getGradientUV(
paint,
uniforms.gradTextureYScale,
uniforms.gradTextureYBias);
color = TEXTURE_SAMPLE_LOD(@gradTexture, gradSampler, gradientTexCoord, .0);
// Gradients are always unmultiplied so we don't lose color data while
// doing the hardware filter.
if (!paintHasAdvancedBlend)
{
half additivenessComplement = getGradientAdditivenessComplement(paint);
color.rgb *= color.a;
color.a *= additivenessComplement;
}
}
#if defined(@ENABLE_MODULATED_IMAGE)
if (@ENABLE_MODULATED_IMAGE && image.z < 0.0)
{
// Layer mask: the texel *is* the coverage. Return it premultiplied and
// unmodulated -- the blend step derives the factor from it and never
// composites it as color. Luminance is taken on the premultiplied value
// on purpose: luma(rgb/a)*a == dot(rgb, coeffs) identically, with no
// divide and no 0/0 at a == 0.
return TEXTURE_SAMPLE_DYNAMIC_LOD(
@imageTexture,
imageSampler,
image.rg,
make_half(.0));
}
if (@ENABLE_MODULATED_IMAGE && image.z > 0.0)
{
half lod = image.z - 1.;
half4 imageColor = TEXTURE_SAMPLE_DYNAMIC_LOD(
@imageTexture,
imageSampler,
image.rg,
lod);
// Images are always premultiplied so the (transparent) background color
// doesn't bleed into the edges during the hardware filter; unmultiply
// to match this draw's convention if needed.
if (paintHasAdvancedBlend)
imageColor = make_half4(unmultiply_rgb(imageColor), imageColor.a);
color *= imageColor;
}
#endif
return color;
}
#if !defined(@DRAW_INTERIOR_TRIANGLES) && !defined(@FEATHER_ATLAS_BLIT)
// Add functions here for fragments to unpack and evaluate coverage since we're
// the ones who packed the coverage components in the vertex shader.
INLINE half find_stroke_coverage(COVERAGE_TYPE coverages TEXTURE_CONTEXT_DECL)
{
#ifdef @ENABLE_FEATHER
if (@ENABLE_FEATHER && is_feathered_stroke(coverages))
return eval_feathered_stroke(coverages TEXTURE_CONTEXT_FORWARD);
else
#endif // @ENABLE_FEATHER
return min(coverages.x, coverages.y);
}
INLINE half find_fill_coverage(COVERAGE_TYPE coverages TEXTURE_CONTEXT_DECL)
{
#if defined(@ENABLE_FEATHER)
if (@ENABLE_FEATHER && is_feathered_fill(coverages))
return eval_feathered_fill(coverages TEXTURE_CONTEXT_FORWARD);
else
#endif // @ENABLE_FEATHER
return coverages.x;
}
INLINE half find_frag_coverage(COVERAGE_TYPE coverages TEXTURE_CONTEXT_DECL)
{
if (is_stroke(coverages))
return find_stroke_coverage(coverages TEXTURE_CONTEXT_FORWARD);
else // Fill. (Back-face culling handles the sign of coverages.x.)
return find_fill_coverage(coverages TEXTURE_CONTEXT_FORWARD);
}
INLINE half apply_frag_coverage(
half initialCoverage,
COVERAGE_TYPE coverages TEXTURE_CONTEXT_DECL)
{
if (is_stroke(coverages))
{
half fragCoverage = find_stroke_coverage(coverages TEXTURE_CONTEXT_FORWARD);
return max(fragCoverage, initialCoverage);
}
else // Fill. (Back-face culling handles the sign of coverages.x.)
{
half fragCoverage = find_fill_coverage(coverages TEXTURE_CONTEXT_FORWARD);
return initialCoverage + fragCoverage;
}
}
#endif // !@DRAW_INTERIOR_TRIANGLES && !@FEATHER_ATLAS_BLIT
#endif // @FRAGMENT