Backdrop Path + Cybersteel (#23)
Co-authored-by: Zachary Levy <zachary@sunforge.is> Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct Uniforms
|
||||
{
|
||||
float2 inv_working_size;
|
||||
uint pair_count;
|
||||
uint mode;
|
||||
float2 direction;
|
||||
float inv_downsample_factor;
|
||||
float _pad0;
|
||||
float4 kernel0[32];
|
||||
};
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float4 out_color [[color(0)]];
|
||||
};
|
||||
|
||||
struct main0_in
|
||||
{
|
||||
float2 p_local [[user(locn0)]];
|
||||
float4 f_color [[user(locn1)]];
|
||||
float2 f_half_size [[user(locn2), flat]];
|
||||
float4 f_radii [[user(locn3), flat]];
|
||||
float f_half_feather [[user(locn4), flat]];
|
||||
};
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
float3 blur_sample(thread const float2& uv, constant Uniforms& _108, texture2d<float> blur_input_tex, sampler blur_input_texSmplr)
|
||||
{
|
||||
float3 color = blur_input_tex.sample(blur_input_texSmplr, uv).xyz * _108.kernel0[0].x;
|
||||
float2 axis_step = _108.direction * _108.inv_working_size;
|
||||
for (uint i = 1u; i < _108.pair_count; i++)
|
||||
{
|
||||
float w = _108.kernel0[i].x;
|
||||
float off = _108.kernel0[i].y;
|
||||
float2 step_uv = axis_step * off;
|
||||
color += (blur_input_tex.sample(blur_input_texSmplr, (uv - step_uv)).xyz * w);
|
||||
color += (blur_input_tex.sample(blur_input_texSmplr, (uv + step_uv)).xyz * w);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
float sdRoundedBox(thread const float2& p, thread const float2& b, thread const float4& r)
|
||||
{
|
||||
float2 _36;
|
||||
if (p.x > 0.0)
|
||||
{
|
||||
_36 = r.xy;
|
||||
}
|
||||
else
|
||||
{
|
||||
_36 = r.zw;
|
||||
}
|
||||
float2 rxy = _36;
|
||||
float _50;
|
||||
if (p.y > 0.0)
|
||||
{
|
||||
_50 = rxy.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_50 = rxy.y;
|
||||
}
|
||||
float rr = _50;
|
||||
float2 q = abs(p) - b;
|
||||
if (rr == 0.0)
|
||||
{
|
||||
return fast::max(q.x, q.y);
|
||||
}
|
||||
q += float2(rr);
|
||||
return (fast::min(fast::max(q.x, q.y), 0.0) + length(fast::max(q, float2(0.0)))) - rr;
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
float sdf_alpha(thread const float& d, thread const float& h)
|
||||
{
|
||||
return 1.0 - smoothstep(-h, h, d);
|
||||
}
|
||||
|
||||
fragment main0_out main0(main0_in in [[stage_in]], constant Uniforms& _108 [[buffer(0)]], texture2d<float> blur_input_tex [[texture(0)]], sampler blur_input_texSmplr [[sampler(0)]], float4 gl_FragCoord [[position]])
|
||||
{
|
||||
main0_out out = {};
|
||||
if (_108.mode == 0u)
|
||||
{
|
||||
float2 uv = gl_FragCoord.xy * _108.inv_working_size;
|
||||
float2 param = uv;
|
||||
float3 color = blur_sample(param, _108, blur_input_tex, blur_input_texSmplr);
|
||||
out.out_color = float4(color, 1.0);
|
||||
return out;
|
||||
}
|
||||
float2 param_1 = in.p_local;
|
||||
float2 param_2 = in.f_half_size;
|
||||
float4 param_3 = in.f_radii;
|
||||
float d = sdRoundedBox(param_1, param_2, param_3);
|
||||
if (d > in.f_half_feather)
|
||||
{
|
||||
discard_fragment();
|
||||
}
|
||||
float grad_magnitude = fast::max(fwidth(d), 9.9999999747524270787835121154785e-07);
|
||||
float d_n = d / grad_magnitude;
|
||||
float h_n = in.f_half_feather / grad_magnitude;
|
||||
float2 uv_1 = (gl_FragCoord.xy * _108.inv_downsample_factor) * _108.inv_working_size;
|
||||
float3 color_1 = blur_input_tex.sample(blur_input_texSmplr, uv_1).xyz;
|
||||
float3 tinted = mix(color_1, color_1 * in.f_color.xyz, float3(in.f_color.w));
|
||||
float param_4 = d_n;
|
||||
float param_5 = h_n;
|
||||
float coverage = sdf_alpha(param_4, param_5);
|
||||
out.out_color = float4(tinted * coverage, coverage);
|
||||
return out;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||
#pragma clang diagnostic ignored "-Wmissing-braces"
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
template<typename T, size_t Num>
|
||||
struct spvUnsafeArray
|
||||
{
|
||||
T elements[Num ? Num : 1];
|
||||
|
||||
thread T& operator [] (size_t pos) thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const thread T& operator [] (size_t pos) const thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
device T& operator [] (size_t pos) device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const device T& operator [] (size_t pos) const device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
constexpr const constant T& operator [] (size_t pos) const constant
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
threadgroup T& operator [] (size_t pos) threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const threadgroup T& operator [] (size_t pos) const threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
};
|
||||
|
||||
struct Uniforms
|
||||
{
|
||||
float4x4 projection;
|
||||
float dpi_scale;
|
||||
uint mode;
|
||||
float2 _pad0;
|
||||
};
|
||||
|
||||
struct Gaussian_Blur_Primitive
|
||||
{
|
||||
float4 bounds;
|
||||
float4 radii;
|
||||
float2 half_size;
|
||||
float half_feather;
|
||||
uint color;
|
||||
};
|
||||
|
||||
struct Gaussian_Blur_Primitive_1
|
||||
{
|
||||
float4 bounds;
|
||||
float4 radii;
|
||||
float2 half_size;
|
||||
float half_feather;
|
||||
uint color;
|
||||
};
|
||||
|
||||
struct Gaussian_Blur_Primitives
|
||||
{
|
||||
Gaussian_Blur_Primitive_1 primitives[1];
|
||||
};
|
||||
|
||||
constant spvUnsafeArray<float2, 6> _97 = spvUnsafeArray<float2, 6>({ float2(0.0), float2(1.0, 0.0), float2(0.0, 1.0), float2(0.0, 1.0), float2(1.0, 0.0), float2(1.0) });
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float2 p_local [[user(locn0)]];
|
||||
float4 f_color [[user(locn1)]];
|
||||
float2 f_half_size [[user(locn2)]];
|
||||
float4 f_radii [[user(locn3)]];
|
||||
float f_half_feather [[user(locn4)]];
|
||||
float4 gl_Position [[position]];
|
||||
};
|
||||
|
||||
vertex main0_out main0(constant Uniforms& _13 [[buffer(0)]], const device Gaussian_Blur_Primitives& _69 [[buffer(1)]], uint gl_VertexIndex [[vertex_id]], uint gl_InstanceIndex [[instance_id]])
|
||||
{
|
||||
main0_out out = {};
|
||||
if (_13.mode == 0u)
|
||||
{
|
||||
float2 ndc = float2((int(gl_VertexIndex) == 1) ? 3.0 : (-1.0), (int(gl_VertexIndex) == 2) ? 3.0 : (-1.0));
|
||||
out.gl_Position = float4(ndc, 0.0, 1.0);
|
||||
out.p_local = float2(0.0);
|
||||
out.f_color = float4(0.0);
|
||||
out.f_half_size = float2(0.0);
|
||||
out.f_radii = float4(0.0);
|
||||
out.f_half_feather = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Gaussian_Blur_Primitive p;
|
||||
p.bounds = _69.primitives[int(gl_InstanceIndex)].bounds;
|
||||
p.radii = _69.primitives[int(gl_InstanceIndex)].radii;
|
||||
p.half_size = _69.primitives[int(gl_InstanceIndex)].half_size;
|
||||
p.half_feather = _69.primitives[int(gl_InstanceIndex)].half_feather;
|
||||
p.color = _69.primitives[int(gl_InstanceIndex)].color;
|
||||
float2 corner = _97[int(gl_VertexIndex)];
|
||||
float2 world_pos = mix(p.bounds.xy, p.bounds.zw, corner);
|
||||
float2 center = (p.bounds.xy + p.bounds.zw) * 0.5;
|
||||
out.p_local = (world_pos - center) * _13.dpi_scale;
|
||||
out.f_color = unpack_unorm4x8_to_float(p.color);
|
||||
out.f_half_size = p.half_size;
|
||||
out.f_radii = p.radii;
|
||||
out.f_half_feather = p.half_feather;
|
||||
out.gl_Position = _13.projection * float4(world_pos * _13.dpi_scale, 0.0, 1.0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct Uniforms
|
||||
{
|
||||
float2 inv_source_size;
|
||||
uint downsample_factor;
|
||||
uint _pad0;
|
||||
};
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float4 out_color [[color(0)]];
|
||||
};
|
||||
|
||||
fragment main0_out main0(constant Uniforms& _18 [[buffer(0)]], texture2d<float> source_tex [[texture(0)]], sampler source_texSmplr [[sampler(0)]], float4 gl_FragCoord [[position]])
|
||||
{
|
||||
main0_out out = {};
|
||||
float2 src_block_center = gl_FragCoord.xy * float(_18.downsample_factor);
|
||||
if (_18.downsample_factor == 1u)
|
||||
{
|
||||
float2 uv = src_block_center * _18.inv_source_size;
|
||||
out.out_color = source_tex.sample(source_texSmplr, uv);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_18.downsample_factor == 2u)
|
||||
{
|
||||
float2 uv_1 = src_block_center * _18.inv_source_size;
|
||||
out.out_color = source_tex.sample(source_texSmplr, uv_1);
|
||||
}
|
||||
else
|
||||
{
|
||||
float off = float(_18.downsample_factor) * 0.25;
|
||||
float2 uv_tl = (src_block_center + float2(-off, -off)) * _18.inv_source_size;
|
||||
float2 uv_tr = (src_block_center + float2(off, -off)) * _18.inv_source_size;
|
||||
float2 uv_bl = (src_block_center + float2(-off, off)) * _18.inv_source_size;
|
||||
float2 uv_br = (src_block_center + float2(off)) * _18.inv_source_size;
|
||||
float4 c = ((source_tex.sample(source_texSmplr, uv_tl) + source_tex.sample(source_texSmplr, uv_tr)) + source_tex.sample(source_texSmplr, uv_bl)) + source_tex.sample(source_texSmplr, uv_br);
|
||||
out.out_color = c * 0.25;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float4 gl_Position [[position]];
|
||||
};
|
||||
|
||||
vertex main0_out main0(uint gl_VertexIndex [[vertex_id]])
|
||||
{
|
||||
main0_out out = {};
|
||||
float2 ndc = float2((int(gl_VertexIndex) == 1) ? 3.0 : (-1.0), (int(gl_VertexIndex) == 2) ? 3.0 : (-1.0));
|
||||
out.gl_Position = float4(ndc, 0.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -24,8 +24,8 @@ struct main0_in
|
||||
float4 f_params [[user(locn2)]];
|
||||
float4 f_params2 [[user(locn3)]];
|
||||
uint f_flags [[user(locn4)]];
|
||||
uint f_rotation_sc [[user(locn5)]];
|
||||
uint4 f_uv_or_effects [[user(locn6)]];
|
||||
float4 f_uv_rect [[user(locn6), flat]];
|
||||
uint4 f_effects [[user(locn7)]];
|
||||
};
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
@@ -109,11 +109,6 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
float h = 0.5;
|
||||
float2 half_size = in.f_params.xy;
|
||||
float2 p_local = in.f_local_or_uv;
|
||||
if ((flags & 16u) != 0u)
|
||||
{
|
||||
float2 sc = float2(as_type<half2>(in.f_rotation_sc));
|
||||
p_local = float2((sc.y * p_local.x) + (sc.x * p_local.y), ((-sc.x) * p_local.x) + (sc.y * p_local.y));
|
||||
}
|
||||
if (kind == 1u)
|
||||
{
|
||||
float4 corner_radii = float4(in.f_params.zw, in.f_params2.xy);
|
||||
@@ -163,16 +158,16 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
{
|
||||
float d_start = dot(p_local, n_start);
|
||||
float d_end = dot(p_local, n_end);
|
||||
float _372;
|
||||
float _338;
|
||||
if (arc_bits == 1u)
|
||||
{
|
||||
_372 = fast::max(d_start, d_end);
|
||||
_338 = fast::max(d_start, d_end);
|
||||
}
|
||||
else
|
||||
{
|
||||
_372 = fast::min(d_start, d_end);
|
||||
_338 = fast::min(d_start, d_end);
|
||||
}
|
||||
float d_wedge = _372;
|
||||
float d_wedge = _338;
|
||||
d = fast::max(d, d_wedge);
|
||||
}
|
||||
half_size = float2(outer);
|
||||
@@ -187,7 +182,7 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
if ((flags & 2u) != 0u)
|
||||
{
|
||||
float4 gradient_start = in.f_color;
|
||||
float4 gradient_end = unpack_unorm4x8_to_float(in.f_uv_or_effects.x);
|
||||
float4 gradient_end = unpack_unorm4x8_to_float(in.f_effects.x);
|
||||
if ((flags & 4u) != 0u)
|
||||
{
|
||||
float t_1 = length(p_local / half_size);
|
||||
@@ -198,7 +193,7 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
}
|
||||
else
|
||||
{
|
||||
float2 direction = float2(as_type<half2>(in.f_uv_or_effects.z));
|
||||
float2 direction = float2(as_type<half2>(in.f_effects.z));
|
||||
float t_2 = (dot(p_local / half_size, direction) * 0.5) + 0.5;
|
||||
float4 param_11 = gradient_start;
|
||||
float4 param_12 = gradient_end;
|
||||
@@ -210,7 +205,7 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
{
|
||||
if ((flags & 1u) != 0u)
|
||||
{
|
||||
float4 uv_rect = as_type<float4>(in.f_uv_or_effects);
|
||||
float4 uv_rect = in.f_uv_rect;
|
||||
float2 local_uv = ((p_local / half_size) * 0.5) + float2(0.5);
|
||||
float2 uv = mix(uv_rect.xy, uv_rect.zw, local_uv);
|
||||
shape_color = in.f_color * tex.sample(texSmplr, uv);
|
||||
@@ -222,8 +217,8 @@ fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[textur
|
||||
}
|
||||
if ((flags & 8u) != 0u)
|
||||
{
|
||||
float4 ol_color = unpack_unorm4x8_to_float(in.f_uv_or_effects.y);
|
||||
float ol_width = float2(as_type<half2>(in.f_uv_or_effects.w)).x / grad_magnitude;
|
||||
float4 ol_color = unpack_unorm4x8_to_float(in.f_effects.y);
|
||||
float ol_width = float2(as_type<half2>(in.f_effects.w)).x / grad_magnitude;
|
||||
float param_14 = d;
|
||||
float param_15 = h;
|
||||
float fill_cov = sdf_alpha(param_14, param_15);
|
||||
|
||||
Binary file not shown.
@@ -10,7 +10,7 @@ struct Uniforms
|
||||
uint mode;
|
||||
};
|
||||
|
||||
struct Primitive
|
||||
struct Core_2D_Primitive
|
||||
{
|
||||
float4 bounds;
|
||||
uint color;
|
||||
@@ -19,10 +19,11 @@ struct Primitive
|
||||
float _pad;
|
||||
float4 params;
|
||||
float4 params2;
|
||||
uint4 uv_or_effects;
|
||||
float4 uv_rect;
|
||||
uint4 effects;
|
||||
};
|
||||
|
||||
struct Primitive_1
|
||||
struct Core_2D_Primitive_1
|
||||
{
|
||||
float4 bounds;
|
||||
uint color;
|
||||
@@ -31,12 +32,13 @@ struct Primitive_1
|
||||
float _pad;
|
||||
float4 params;
|
||||
float4 params2;
|
||||
uint4 uv_or_effects;
|
||||
float4 uv_rect;
|
||||
uint4 effects;
|
||||
};
|
||||
|
||||
struct Primitives
|
||||
struct Core_2D_Primitives
|
||||
{
|
||||
Primitive_1 primitives[1];
|
||||
Core_2D_Primitive_1 primitives[1];
|
||||
};
|
||||
|
||||
struct main0_out
|
||||
@@ -46,8 +48,8 @@ struct main0_out
|
||||
float4 f_params [[user(locn2)]];
|
||||
float4 f_params2 [[user(locn3)]];
|
||||
uint f_flags [[user(locn4)]];
|
||||
uint f_rotation_sc [[user(locn5)]];
|
||||
uint4 f_uv_or_effects [[user(locn6)]];
|
||||
float4 f_uv_rect [[user(locn6)]];
|
||||
uint4 f_effects [[user(locn7)]];
|
||||
float4 gl_Position [[position]];
|
||||
};
|
||||
|
||||
@@ -58,7 +60,7 @@ struct main0_in
|
||||
float4 v_color [[attribute(2)]];
|
||||
};
|
||||
|
||||
vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer(0)]], const device Primitives& _75 [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
|
||||
vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer(0)]], const device Core_2D_Primitives& _75 [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
|
||||
{
|
||||
main0_out out = {};
|
||||
if (_12.mode == 0u)
|
||||
@@ -68,13 +70,13 @@ vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer
|
||||
out.f_params = float4(0.0);
|
||||
out.f_params2 = float4(0.0);
|
||||
out.f_flags = 0u;
|
||||
out.f_rotation_sc = 0u;
|
||||
out.f_uv_or_effects = uint4(0u);
|
||||
out.f_uv_rect = float4(0.0);
|
||||
out.f_effects = uint4(0u);
|
||||
out.gl_Position = _12.projection * float4(in.v_position * _12.dpi_scale, 0.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Primitive p;
|
||||
Core_2D_Primitive p;
|
||||
p.bounds = _75.primitives[int(gl_InstanceIndex)].bounds;
|
||||
p.color = _75.primitives[int(gl_InstanceIndex)].color;
|
||||
p.flags = _75.primitives[int(gl_InstanceIndex)].flags;
|
||||
@@ -82,17 +84,25 @@ vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer
|
||||
p._pad = _75.primitives[int(gl_InstanceIndex)]._pad;
|
||||
p.params = _75.primitives[int(gl_InstanceIndex)].params;
|
||||
p.params2 = _75.primitives[int(gl_InstanceIndex)].params2;
|
||||
p.uv_or_effects = _75.primitives[int(gl_InstanceIndex)].uv_or_effects;
|
||||
p.uv_rect = _75.primitives[int(gl_InstanceIndex)].uv_rect;
|
||||
p.effects = _75.primitives[int(gl_InstanceIndex)].effects;
|
||||
float2 corner = in.v_position;
|
||||
float2 world_pos = mix(p.bounds.xy, p.bounds.zw, corner);
|
||||
float2 center = (p.bounds.xy + p.bounds.zw) * 0.5;
|
||||
float2 local = (world_pos - center) * _12.dpi_scale;
|
||||
uint flags = (p.flags >> 8u) & 255u;
|
||||
if ((flags & 16u) != 0u)
|
||||
{
|
||||
float2 sc = float2(as_type<half2>(p.rotation_sc));
|
||||
local = float2((sc.y * local.x) + (sc.x * local.y), ((-sc.x) * local.x) + (sc.y * local.y));
|
||||
}
|
||||
out.f_color = unpack_unorm4x8_to_float(p.color);
|
||||
out.f_local_or_uv = (world_pos - center) * _12.dpi_scale;
|
||||
out.f_local_or_uv = local;
|
||||
out.f_params = p.params;
|
||||
out.f_params2 = p.params2;
|
||||
out.f_flags = p.flags;
|
||||
out.f_rotation_sc = p.rotation_sc;
|
||||
out.f_uv_or_effects = p.uv_or_effects;
|
||||
out.f_uv_rect = p.uv_rect;
|
||||
out.f_effects = p.effects;
|
||||
out.gl_Position = _12.projection * float4(world_pos * _12.dpi_scale, 0.0, 1.0);
|
||||
}
|
||||
return out;
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,155 @@
|
||||
#version 450 core
|
||||
|
||||
// Unified backdrop blur fragment shader.
|
||||
// Handles both the 1D separable blur passes (mode 0, used for BOTH the H-pass and V-pass;
|
||||
// `direction` picks the axis) and the composite pass (mode 1, reads the fully-blurred
|
||||
// working texture, masks via RRect SDF, applies tint, and writes to source_texture with
|
||||
// premultiplied-over blending). Working textures are sized at the full swapchain resolution;
|
||||
// downsampled content occupies only a sub-rect at downsample factor > 1 (set via viewport).
|
||||
//
|
||||
// The composite blends with source_texture via the standard premultiplied-over blend state
|
||||
// (ONE, ONE_MINUS_SRC_ALPHA).
|
||||
//
|
||||
// Backdrop primitives are tint-only — there is no outline. A specialized edge effect
|
||||
// (e.g. liquid-glass-style refraction outlines) would be implemented as a dedicated
|
||||
// primitive type with its own pipeline.
|
||||
//
|
||||
// Two modes, structurally distinct:
|
||||
//
|
||||
// Mode 0: 1D separable blur. Used for BOTH the H-pass and V-pass; `direction` (set in the
|
||||
// per-pass uniforms) picks (1,0) for H or (0,1) for V. Reads the previous working-
|
||||
// res texture and writes the next working-res texture. Fullscreen-triangle vertex
|
||||
// output; gl_FragCoord.xy is in working-res target pixel space; UV =
|
||||
// gl_FragCoord.xy * inv_working_size.
|
||||
//
|
||||
// Mode 1: composite. Reads the fully-blurred working-res texture, applies the SDF mask and
|
||||
// tint, writes to source_texture. Instanced unit-quad vertex output covering the
|
||||
// per-primitive bounds; gl_FragCoord.xy is in the full-resolution render target;
|
||||
// UV into the blurred working texture =
|
||||
// (gl_FragCoord.xy * inv_downsample_factor) * inv_working_size.
|
||||
// No kernel is applied here — the blur is already complete.
|
||||
//
|
||||
// V-blur is run as its own working→working pass rather than folded into the composite. The
|
||||
// folded variant produced a horizontal-vs-vertical asymmetry artifact: when V-blur sampled
|
||||
// the H-blur output through the bilinear-upsample/SDF-mask/tint pipeline in one shader
|
||||
// invocation, horizontal source features ended up looking sharper than vertical ones.
|
||||
// Matching V's structure exactly to H's restores symmetry.
|
||||
|
||||
const uint MAX_KERNEL_PAIRS = 32;
|
||||
|
||||
// --- Inputs from vertex shader ---
|
||||
layout(location = 0) in vec2 p_local;
|
||||
layout(location = 1) in mediump vec4 f_color;
|
||||
layout(location = 2) flat in vec2 f_half_size;
|
||||
layout(location = 3) flat in vec4 f_radii;
|
||||
layout(location = 4) flat in float f_half_feather;
|
||||
|
||||
// --- Output ---
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
// --- Sampler ---
|
||||
// Mode 0: bound to downsample_texture. Mode 1: bound to h_blur_texture.
|
||||
layout(set = 2, binding = 0) uniform sampler2D blur_input_tex;
|
||||
|
||||
// --- Uniforms (set 3) ---
|
||||
// Per-bracket-substage. `mode` matches the vertex shader's mode (0 = H, 1 = V).
|
||||
// `direction` selects the kernel axis for blur offsets.
|
||||
// `kernel` holds the per-sigma weight/offset pairs computed CPU-side using the
|
||||
// linear-sampling pair adjustment (RAD/Rákos).
|
||||
layout(set = 3, binding = 0) uniform Uniforms {
|
||||
vec2 inv_working_size; // 1.0 / working-resolution texture dimensions
|
||||
uint pair_count; // number of (weight, offset) pairs; pair[0] is the center
|
||||
uint mode; // 0 = H-blur, 1 = V-composite
|
||||
vec2 direction; // (1,0) for H, (0,1) for V — multiplied into the kernel offset
|
||||
float inv_downsample_factor; // 1.0 / downsample_factor (mode 1 only; mode 0 ignores)
|
||||
float _pad0;
|
||||
vec4 kernel[MAX_KERNEL_PAIRS]; // .x = weight (paired-sum for idx>0), .y = offset (texels)
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- SDF helper --------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
float sdRoundedBox(vec2 p, vec2 b, vec4 r) {
|
||||
vec2 rxy = (p.x > 0.0) ? r.xy : r.zw;
|
||||
float rr = (p.y > 0.0) ? rxy.x : rxy.y;
|
||||
vec2 q = abs(p) - b;
|
||||
if (rr == 0.0) {
|
||||
return max(q.x, q.y);
|
||||
}
|
||||
q += rr;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr;
|
||||
}
|
||||
|
||||
float sdf_alpha(float d, float h) {
|
||||
return 1.0 - smoothstep(-h, h, d);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Blur sample loop --------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
vec3 blur_sample(vec2 uv) {
|
||||
vec3 color = kernel[0].x * texture(blur_input_tex, uv).rgb;
|
||||
|
||||
// Per-pair offset in texel space, projected onto the active axis.
|
||||
vec2 axis_step = direction * inv_working_size;
|
||||
|
||||
for (uint i = 1u; i < pair_count; i += 1u) {
|
||||
float w = kernel[i].x;
|
||||
float off = kernel[i].y;
|
||||
vec2 step_uv = off * axis_step;
|
||||
color += w * texture(blur_input_tex, uv - step_uv).rgb;
|
||||
color += w * texture(blur_input_tex, uv + step_uv).rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Main --------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
if (mode == 0u) {
|
||||
// ---- Mode 0: 1D separable blur (used for both H-pass and V-pass).
|
||||
// gl_FragCoord is in working-res target pixel space; sample the previous working-res
|
||||
// texture along `direction` with the kernel.
|
||||
vec2 uv = gl_FragCoord.xy * inv_working_size;
|
||||
vec3 color = blur_sample(uv);
|
||||
out_color = vec4(color, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Mode 1: composite per-primitive.
|
||||
// RRect SDF — early discard for fragments well outside the masked region.
|
||||
float d = sdRoundedBox(p_local, f_half_size, f_radii);
|
||||
if (d > f_half_feather) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// fwidth-based normalization for AA (matches main pipeline approach).
|
||||
float grad_magnitude = max(fwidth(d), 1e-6);
|
||||
float d_n = d / grad_magnitude;
|
||||
float h_n = f_half_feather / grad_magnitude;
|
||||
|
||||
// Sample the fully-blurred working-res texture. gl_FragCoord is full-res; convert to
|
||||
// working-res UV via inv_downsample_factor. No kernel is applied — the H+V blur passes
|
||||
// already produced the final blurred image; this is just an upsample + tint.
|
||||
vec2 uv = (gl_FragCoord.xy * inv_downsample_factor) * inv_working_size;
|
||||
vec3 color = texture(blur_input_tex, uv).rgb;
|
||||
|
||||
// Tint composition: inside the masked region the panel is fully opaque — it completely
|
||||
// hides the original framebuffer content, just like real frosted glass and like iOS
|
||||
// UIBlurEffect / CSS backdrop-filter. f_color.rgb specifies the tint color; f_color.a
|
||||
// specifies the tint *mix strength* (NOT panel opacity). At alpha=0 we see the pure
|
||||
// blur; at alpha=255 we see the blur fully multiplied by the tint color.
|
||||
//
|
||||
// Output is premultiplied to match the ONE, ONE_MINUS_SRC_ALPHA blend state. Coverage
|
||||
// (the SDF mask's edge AA) modulates only the alpha channel, never the panel-vs-source
|
||||
// blend; that way edge pixels still feather correctly while mid-panel pixels stay fully
|
||||
// opaque.
|
||||
mediump vec3 tinted = mix(color, color * f_color.rgb, f_color.a);
|
||||
mediump float coverage = sdf_alpha(d_n, h_n);
|
||||
out_color = vec4(tinted * coverage, coverage);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#version 450 core
|
||||
|
||||
// Unified backdrop blur vertex shader.
|
||||
// Handles both the 1D separable blur passes (fullscreen triangle, mode 0; used for
|
||||
// BOTH the H-pass and V-pass) and the composite pass (instanced unit-quad over
|
||||
// Gaussian_Blur_Primitive storage buffer, mode 1) for the second PSO of the backdrop bracket.
|
||||
// The first PSO (downsample) uses backdrop_fullscreen.vert.
|
||||
//
|
||||
// No vertex buffer for either mode. Mode 0 uses gl_VertexIndex 0..2 for a single
|
||||
// fullscreen triangle; mode 1 uses gl_VertexIndex 0..5 for a unit-quad (two
|
||||
// triangles, TRIANGLELIST topology) and gl_InstanceIndex to select the primitive.
|
||||
//
|
||||
// Mode 0 viewport+scissor are CPU-set per sigma group to the work region (union AABB
|
||||
// of that group's backdrop primitives + halo, clamped to swapchain bounds). Mode 1
|
||||
// renders into source_texture with the screen-space orthographic projection; the
|
||||
// per-primitive bounds drive the quad in screen space.
|
||||
//
|
||||
// Backdrop primitives have NO rotation — backdrop sampling is in screen space, so
|
||||
// a rotated mask over a stationary blur sample would look wrong.
|
||||
|
||||
// --- Outputs to fragment shader ---
|
||||
// p_local: shape-local position in physical pixels (origin at shape center).
|
||||
// Only meaningful in mode 1 (V-composite). Zero-init for mode 0.
|
||||
layout(location = 0) out vec2 p_local;
|
||||
// f_color: tint, unpacked from primitive.color. Only meaningful in mode 1.
|
||||
layout(location = 1) out mediump vec4 f_color;
|
||||
// f_half_size: RRect half extents in physical pixels (mode 1 only).
|
||||
layout(location = 2) flat out vec2 f_half_size;
|
||||
// f_radii: per-corner radii in physical pixels (mode 1 only).
|
||||
layout(location = 3) flat out vec4 f_radii;
|
||||
// f_half_feather: SDF anti-aliasing feather (mode 1 only).
|
||||
layout(location = 4) flat out float f_half_feather;
|
||||
|
||||
// --- Uniforms (set 1) ---
|
||||
// Backdrop pipeline's own uniform block — distinct from the main pipeline's
|
||||
// Vertex_Uniforms_2D. `mode` selects between H-blur (0) and V-composite (1).
|
||||
layout(set = 1, binding = 0) uniform Uniforms {
|
||||
mat4 projection;
|
||||
float dpi_scale;
|
||||
uint mode; // 0 = H-blur, 1 = V-composite
|
||||
vec2 _pad0;
|
||||
};
|
||||
|
||||
// --- Gaussian blur primitive storage buffer (set 0) ---
|
||||
// 48 bytes, std430-natural layout (no implicit padding). vec4 members are
|
||||
// front-loaded so their 16-byte alignment is satisfied without holes; the
|
||||
// vec2 and scalar tail packs tight to land the struct at a clean 48-byte
|
||||
// stride (a multiple of 16, so the array stride needs no rounding either).
|
||||
// Field semantics match the CPU-side Gaussian_Blur_Primitive declared in
|
||||
// levlib/draw/backdrop.odin; keep both in sync.
|
||||
//
|
||||
// Gaussian blur primitives are tint-only: outline is intentionally absent. Specialized
|
||||
// edge effects (e.g. liquid-glass-style refraction outlines) would be a dedicated
|
||||
// primitive type with its own pipeline rather than a flag bit here.
|
||||
struct Gaussian_Blur_Primitive {
|
||||
vec4 bounds; // 0-15: min_xy, max_xy (world-space)
|
||||
vec4 radii; // 16-31: per-corner radii (physical px)
|
||||
vec2 half_size; // 32-39: RRect half extents (physical px)
|
||||
float half_feather; // 40-43: SDF anti-aliasing feather (physical px)
|
||||
uint color; // 44-47: tint, packed RGBA u8x4
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 0) readonly buffer Gaussian_Blur_Primitives {
|
||||
Gaussian_Blur_Primitive primitives[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
if (mode == 0u) {
|
||||
// ---- Mode 0: H-blur fullscreen triangle ----
|
||||
// gl_VertexIndex 0 -> ( -1, -1)
|
||||
// gl_VertexIndex 1 -> ( 3, -1)
|
||||
// gl_VertexIndex 2 -> ( -1, 3)
|
||||
vec2 ndc = vec2(
|
||||
(gl_VertexIndex == 1) ? 3.0 : -1.0,
|
||||
(gl_VertexIndex == 2) ? 3.0 : -1.0);
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
|
||||
// Mode 0 doesn't read the per-primitive varyings; zero-init for safety.
|
||||
p_local = vec2(0.0);
|
||||
f_color = vec4(0.0);
|
||||
f_half_size = vec2(0.0);
|
||||
f_radii = vec4(0.0);
|
||||
f_half_feather = 0.0;
|
||||
} else {
|
||||
// ---- Mode 1: V-composite instanced unit-quad over Gaussian_Blur_Primitive ----
|
||||
Gaussian_Blur_Primitive p = primitives[gl_InstanceIndex];
|
||||
|
||||
// Unit-quad corners for TRIANGLELIST (2 triangles, 6 vertices):
|
||||
// index 0 -> (0,0) index 3 -> (0,1)
|
||||
// index 1 -> (1,0) index 4 -> (1,0)
|
||||
// index 2 -> (0,1) index 5 -> (1,1)
|
||||
vec2 quad_corners[6] = vec2[6](
|
||||
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0),
|
||||
vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0));
|
||||
vec2 corner = quad_corners[gl_VertexIndex];
|
||||
|
||||
vec2 world_pos = mix(p.bounds.xy, p.bounds.zw, corner);
|
||||
vec2 center = 0.5 * (p.bounds.xy + p.bounds.zw);
|
||||
|
||||
// Shape-local position in physical pixels (no rotation for backdrops).
|
||||
p_local = (world_pos - center) * dpi_scale;
|
||||
|
||||
f_color = unpackUnorm4x8(p.color);
|
||||
f_half_size = p.half_size;
|
||||
f_radii = p.radii;
|
||||
f_half_feather = p.half_feather;
|
||||
|
||||
gl_Position = projection * vec4(world_pos * dpi_scale, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#version 450 core
|
||||
|
||||
// Backdrop downsample fragment shader.
|
||||
// Reads source_texture (full-resolution snapshot of pre-bracket framebuffer contents) and
|
||||
// writes a downsampled copy at factor 1, 2, or 4. The output is the working texture (sized
|
||||
// at full swapchain resolution); larger factors only fill a sub-rect of it via the CPU-set
|
||||
// viewport. See backdrop.odin for the factor selection table (Flutter-style).
|
||||
//
|
||||
// Shader paths by factor:
|
||||
//
|
||||
// factor=1: identity copy. One bilinear tap aligned to the source pixel center. Useful
|
||||
// when sigma is small enough that any downsample round-trip would visibly soften
|
||||
// the output (Flutter does this for sigma_phys ≤ 4).
|
||||
//
|
||||
// factor=2: each output covers a 2×2 source block. Single bilinear tap at the shared
|
||||
// corner reads all 4 source pixels with 0.25 weight.
|
||||
//
|
||||
// factor=4: each output covers a 4×4 source block. We use 4 bilinear taps, each at the
|
||||
// shared corner of a 2×2 sub-block. Each tap reads 4 source pixels uniformly;
|
||||
// combined, the 4 taps sample 16 source pixels arranged uniformly across the
|
||||
// block (full coverage at factor=4). The factor>=4 path is structured so the
|
||||
// same shader code would extend to factor=8 (16 pixels of 64) or factor=16 (16
|
||||
// of 256) if the CPU-side cap is ever raised, though the current cap is 4.
|
||||
//
|
||||
// The viewport+scissor are set by the CPU to limit output to the layer's work region in
|
||||
// working-texture coords (work_region_phys / factor), clamped to the texture bounds.
|
||||
|
||||
layout(set = 3, binding = 0) uniform Uniforms {
|
||||
vec2 inv_source_size; // 1.0 / source_texture pixel dimensions
|
||||
uint downsample_factor; // 1, 2, 4, 8, or 16
|
||||
uint _pad0;
|
||||
};
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D source_tex;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
// Output pixel index (i): gl_FragCoord.xy - 0.5. Source-pixel block top-left for this
|
||||
// output: i * factor. Center of the block: i*factor + factor/2 = gl_FragCoord.xy * factor.
|
||||
vec2 src_block_center = gl_FragCoord.xy * float(downsample_factor);
|
||||
|
||||
if (downsample_factor == 1u) {
|
||||
// Identity copy. UV at src_block_center hits the source pixel center directly.
|
||||
vec2 uv = src_block_center * inv_source_size;
|
||||
out_color = texture(source_tex, uv);
|
||||
} else if (downsample_factor == 2u) {
|
||||
// Single tap at the shared corner of the 2×2 source block; one bilinear sample reads
|
||||
// all 4 source pixels with equal 0.25 weights — uniform 2×2 box filter for free.
|
||||
vec2 uv = src_block_center * inv_source_size;
|
||||
out_color = texture(source_tex, uv);
|
||||
} else {
|
||||
// Four taps at offsets ±(factor/4) from the block center. Each tap lands on a corner
|
||||
// shared by 4 source pixels of a (factor/2)×(factor/2) sub-block (equivalent at the
|
||||
// bilinear level), giving a 4-tap = 16-source-pixel uniform sample of the block.
|
||||
float off = float(downsample_factor) * 0.25;
|
||||
vec2 uv_tl = (src_block_center + vec2(-off, -off)) * inv_source_size;
|
||||
vec2 uv_tr = (src_block_center + vec2(off, -off)) * inv_source_size;
|
||||
vec2 uv_bl = (src_block_center + vec2(-off, off)) * inv_source_size;
|
||||
vec2 uv_br = (src_block_center + vec2(off, off)) * inv_source_size;
|
||||
vec4 c = texture(source_tex, uv_tl)
|
||||
+ texture(source_tex, uv_tr)
|
||||
+ texture(source_tex, uv_bl)
|
||||
+ texture(source_tex, uv_br);
|
||||
out_color = c * 0.25;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#version 450 core
|
||||
|
||||
// Fullscreen-triangle vertex shader for the backdrop downsample and H-blur sub-passes.
|
||||
// Emits a single triangle covering NDC [-1,1]^2; the rasterizer clips edges outside.
|
||||
// No vertex buffer; uses gl_VertexIndex to pick corners.
|
||||
//
|
||||
// The CPU sets the viewport (and matching scissor) per layer-bracket to limit work to
|
||||
// the union AABB of the layer's backdrop primitives, expanded by 3*max_sigma and
|
||||
// clamped to swapchain bounds. The fragment shader uses gl_FragCoord (absolute pixel
|
||||
// space in the bound target) plus an inv-size uniform to compute its own UVs — see
|
||||
// each fragment shader for the per-pass sampling math.
|
||||
|
||||
void main() {
|
||||
// gl_VertexIndex 0 -> ( -1, -1)
|
||||
// gl_VertexIndex 1 -> ( 3, -1)
|
||||
// gl_VertexIndex 2 -> ( -1, 3)
|
||||
vec2 ndc = vec2(
|
||||
(gl_VertexIndex == 1) ? 3.0 : -1.0,
|
||||
(gl_VertexIndex == 2) ? 3.0 : -1.0);
|
||||
gl_Position = vec4(ndc, 0.0, 1.0);
|
||||
}
|
||||
@@ -6,8 +6,8 @@ layout(location = 1) in vec2 f_local_or_uv;
|
||||
layout(location = 2) in vec4 f_params;
|
||||
layout(location = 3) in vec4 f_params2;
|
||||
layout(location = 4) flat in uint f_flags;
|
||||
layout(location = 5) flat in uint f_rotation_sc;
|
||||
layout(location = 6) flat in uvec4 f_uv_or_effects;
|
||||
layout(location = 6) flat in vec4 f_uv_rect;
|
||||
layout(location = 7) flat in uvec4 f_effects;
|
||||
|
||||
// --- Output ---
|
||||
layout(location = 0) out vec4 out_color;
|
||||
@@ -83,16 +83,7 @@ void main() {
|
||||
float h = 0.5; // half-feather width; overwritten per shape kind
|
||||
vec2 half_size = f_params.xy; // used by RRect and as reference size for gradients
|
||||
|
||||
vec2 p_local = f_local_or_uv;
|
||||
|
||||
// Apply inverse rotation using pre-computed sin/cos (no per-pixel trig).
|
||||
// .Rotated flag = bit 4 = 16u
|
||||
if ((flags & 16u) != 0u) {
|
||||
vec2 sc = unpackHalf2x16(f_rotation_sc); // .x = sin(angle), .y = cos(angle)
|
||||
// Inverse rotation matrix R(-angle) = [[cos, sin], [-sin, cos]]
|
||||
p_local = vec2(sc.y * p_local.x + sc.x * p_local.y,
|
||||
-sc.x * p_local.x + sc.y * p_local.y);
|
||||
}
|
||||
vec2 p_local = f_local_or_uv; // arrives rotated; vertex shader handled .Rotated
|
||||
|
||||
if (kind == 1u) {
|
||||
// RRect — half_feather in params2.z
|
||||
@@ -151,7 +142,7 @@ void main() {
|
||||
if ((flags & 2u) != 0u) {
|
||||
// Gradient active (bit 1)
|
||||
mediump vec4 gradient_start = f_color;
|
||||
mediump vec4 gradient_end = unpackUnorm4x8(f_uv_or_effects.x);
|
||||
mediump vec4 gradient_end = unpackUnorm4x8(f_effects.x);
|
||||
|
||||
if ((flags & 4u) != 0u) {
|
||||
// Radial gradient (bit 2): t from distance to center
|
||||
@@ -159,13 +150,13 @@ void main() {
|
||||
shape_color = gradient_2color(gradient_start, gradient_end, t);
|
||||
} else {
|
||||
// Linear gradient: direction pre-computed on CPU as (cos, sin) f16 pair
|
||||
vec2 direction = unpackHalf2x16(f_uv_or_effects.z);
|
||||
vec2 direction = unpackHalf2x16(f_effects.z);
|
||||
mediump float t = dot(p_local / half_size, direction) * 0.5 + 0.5;
|
||||
shape_color = gradient_2color(gradient_start, gradient_end, t);
|
||||
}
|
||||
} else if ((flags & 1u) != 0u) {
|
||||
// Textured (bit 0) — RRect only in practice
|
||||
vec4 uv_rect = uintBitsToFloat(f_uv_or_effects);
|
||||
// Textured (bit 0)
|
||||
vec4 uv_rect = f_uv_rect;
|
||||
vec2 local_uv = p_local / half_size * 0.5 + 0.5;
|
||||
vec2 uv = mix(uv_rect.xy, uv_rect.zw, local_uv);
|
||||
shape_color = f_color * texture(tex, uv);
|
||||
@@ -180,9 +171,9 @@ void main() {
|
||||
// AA at d=ol_width. The outline band's coverage is total_cov - fill_cov.
|
||||
// Output is premultiplied: blend state is ONE, ONE_MINUS_SRC_ALPHA.
|
||||
if ((flags & 8u) != 0u) {
|
||||
mediump vec4 ol_color = unpackUnorm4x8(f_uv_or_effects.y);
|
||||
// Outline width in f_uv_or_effects.w (low f16 half)
|
||||
float ol_width = unpackHalf2x16(f_uv_or_effects.w).x / grad_magnitude;
|
||||
mediump vec4 ol_color = unpackUnorm4x8(f_effects.y);
|
||||
// Outline width in f_effects.w (low f16 half)
|
||||
float ol_width = unpackHalf2x16(f_effects.w).x / grad_magnitude;
|
||||
|
||||
float fill_cov = sdf_alpha(d, h);
|
||||
float total_cov = sdf_alpha(d - ol_width, h);
|
||||
|
||||
@@ -11,8 +11,9 @@ layout(location = 1) out vec2 f_local_or_uv;
|
||||
layout(location = 2) out vec4 f_params;
|
||||
layout(location = 3) out vec4 f_params2;
|
||||
layout(location = 4) flat out uint f_flags;
|
||||
layout(location = 5) flat out uint f_rotation_sc;
|
||||
layout(location = 6) flat out uvec4 f_uv_or_effects;
|
||||
|
||||
layout(location = 6) flat out vec4 f_uv_rect;
|
||||
layout(location = 7) flat out uvec4 f_effects;
|
||||
|
||||
// ---------- Uniforms (single block — avoids spirv-cross reordering on Metal) ----------
|
||||
layout(set = 1, binding = 0) uniform Uniforms {
|
||||
@@ -22,7 +23,10 @@ layout(set = 1, binding = 0) uniform Uniforms {
|
||||
};
|
||||
|
||||
// ---------- SDF primitive storage buffer ----------
|
||||
struct Primitive {
|
||||
// Mirrors the CPU-side Core_2D_Primitive in core_2d.odin. Named with the
|
||||
// subsystem prefix so a project-wide grep on the type name matches both the GLSL
|
||||
// declaration and the Odin declaration.
|
||||
struct Core_2D_Primitive {
|
||||
vec4 bounds; // 0-15
|
||||
uint color; // 16-19
|
||||
uint flags; // 20-23
|
||||
@@ -30,41 +34,56 @@ struct Primitive {
|
||||
float _pad; // 28-31
|
||||
vec4 params; // 32-47
|
||||
vec4 params2; // 48-63
|
||||
uvec4 uv_or_effects; // 64-79
|
||||
vec4 uv_rect; // 64-79: texture UV coordinates (read when .Textured)
|
||||
uvec4 effects; // 80-95: gradient/outline parameters (read when .Gradient/.Outline)
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 0) readonly buffer Primitives {
|
||||
Primitive primitives[];
|
||||
layout(std430, set = 0, binding = 0) readonly buffer Core_2D_Primitives {
|
||||
Core_2D_Primitive primitives[];
|
||||
};
|
||||
|
||||
// ---------- Entry point ----------
|
||||
void main() {
|
||||
if (mode == 0u) {
|
||||
// ---- Mode 0: Tessellated (legacy) ----
|
||||
// ---- Mode 0: Tessellated (used for text and arbitrary user geometry) ----
|
||||
f_color = v_color;
|
||||
f_local_or_uv = v_uv;
|
||||
f_params = vec4(0.0);
|
||||
f_params2 = vec4(0.0);
|
||||
f_flags = 0u;
|
||||
f_rotation_sc = 0u;
|
||||
f_uv_or_effects = uvec4(0);
|
||||
f_uv_rect = vec4(0.0);
|
||||
f_effects = uvec4(0);
|
||||
|
||||
gl_Position = projection * vec4(v_position * dpi_scale, 0.0, 1.0);
|
||||
} else {
|
||||
// ---- Mode 1: SDF instanced quads ----
|
||||
Primitive p = primitives[gl_InstanceIndex];
|
||||
Core_2D_Primitive p = primitives[gl_InstanceIndex];
|
||||
|
||||
vec2 corner = v_position; // unit quad corners: (0,0)-(1,1)
|
||||
vec2 world_pos = mix(p.bounds.xy, p.bounds.zw, corner);
|
||||
vec2 center = 0.5 * (p.bounds.xy + p.bounds.zw);
|
||||
|
||||
// Compute shape-local position. Apply inverse rotation here in the vertex
|
||||
// shader; the rasterizer interpolates the rotated values across the quad,
|
||||
// which is mathematically equivalent to per-fragment rotation under 2D ortho
|
||||
// projection. Frees one fragment-shader varying and per-pixel rotation math.
|
||||
vec2 local = (world_pos - center) * dpi_scale;
|
||||
uint flags = (p.flags >> 8u) & 0xFFu;
|
||||
if ((flags & 16u) != 0u) {
|
||||
// Rotated flag (bit 4); rotation_sc holds packed f16 (sin, cos).
|
||||
// Inverse rotation matrix R(-angle) = [[cos, sin], [-sin, cos]].
|
||||
vec2 sc = unpackHalf2x16(p.rotation_sc);
|
||||
local = vec2(sc.y * local.x + sc.x * local.y,
|
||||
-sc.x * local.x + sc.y * local.y);
|
||||
}
|
||||
|
||||
f_color = unpackUnorm4x8(p.color);
|
||||
f_local_or_uv = (world_pos - center) * dpi_scale; // shape-centered physical pixels
|
||||
f_local_or_uv = local; // shape-local physical pixels (rotated if .Rotated set)
|
||||
f_params = p.params;
|
||||
f_params2 = p.params2;
|
||||
f_flags = p.flags;
|
||||
f_rotation_sc = p.rotation_sc;
|
||||
f_uv_or_effects = p.uv_or_effects;
|
||||
f_uv_rect = p.uv_rect;
|
||||
f_effects = p.effects;
|
||||
|
||||
gl_Position = projection * vec4(world_pos * dpi_scale, 0.0, 1.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user