Added backdrop effects pipeline (blur)

This commit is contained in:
Zachary Levy
2026-04-28 22:12:25 -07:00
parent ff29dbd92f
commit 16989cbb71
29 changed files with 2931 additions and 415 deletions
+84 -96
View File
@@ -52,12 +52,14 @@ emit_rectangle :: proc(x, y, width, height: f32, color: Color, vertices: []Verte
vertices[offset + 5] = solid_vertex({x, y + height}, color)
}
// Internal
prepare_sdf_primitive_textured :: proc(
// Internal — submit an SDF primitive with optional texture binding.
// Replaces the old prepare_sdf_primitive and prepare_sdf_primitive_textured.
@(private)
prepare_sdf_primitive_ex :: proc(
layer: ^Layer,
prim: Primitive,
texture_id: Texture_Id,
sampler: Sampler_Preset,
prim: Base_2D_Primitive,
texture_id: Texture_Id = INVALID_TEXTURE,
sampler: Sampler_Preset = DFT_SAMPLER,
) {
offset := u32(len(GLOB.tmp_primitives))
append(&GLOB.tmp_primitives, prim)
@@ -65,6 +67,23 @@ prepare_sdf_primitive_textured :: proc(
append_or_extend_sub_batch(scissor, layer, .SDF, offset, 1, texture_id, sampler)
}
// Internal — resolve Texture_Fill zero-initialized fields to their defaults.
// Odin structs zero-initialize; Color{} and Rectangle{} are all-zero which is not a
// useful tint or UV rect. This proc substitutes sensible defaults for zero values.
@(private)
resolve_texture_defaults :: #force_inline proc(
tf: Texture_Fill,
) -> (
tint: Color,
uv: Rectangle,
sampler: Sampler_Preset,
) {
tint = tf.tint == Color{} ? DFT_TINT : tf.tint
uv = tf.uv_rect == Rectangle{} ? DFT_UV_RECT : tf.uv_rect
sampler = tf.sampler
return
}
//Internal
//
// Compute the visual center of a center-parametrized shape after applying
@@ -89,7 +108,7 @@ rotated_aabb_half_extents :: proc(half_width, half_height, cos_angle, sin_angle:
return {half_width * cos_abs + half_height * sin_abs, half_width * sin_abs + half_height * cos_abs}
}
// Pack sin/cos into the Primitive.rotation_sc field as two f16 values.
// Pack sin/cos into the Base_2D_Primitive.rotation_sc field as two f16 values.
pack_rotation_sc :: #force_inline proc(sin_angle, cos_angle: f32) -> u32 {
return pack_f16_pair(f16(sin_angle), f16(cos_angle))
}
@@ -97,7 +116,7 @@ pack_rotation_sc :: #force_inline proc(sin_angle, cos_angle: f32) -> u32 {
// Internal
//
// Build an RRect Primitive with bounds, params, and rotation computed from rectangle geometry.
// Build an RRect Base_2D_Primitive with bounds, params, and rotation computed from rectangle geometry.
// The caller sets color, flags, and uv fields on the returned primitive before submitting.
build_rrect_primitive :: proc(
rect: Rectangle,
@@ -105,7 +124,7 @@ build_rrect_primitive :: proc(
origin: Vec2,
rotation: f32,
feather_px: f32,
) -> Primitive {
) -> Base_2D_Primitive {
max_radius := min(rect.width, rect.height) * 0.5
clamped_top_left := clamp(radii.top_left, 0, max_radius)
clamped_top_right := clamp(radii.top_right, 0, max_radius)
@@ -141,7 +160,7 @@ build_rrect_primitive :: proc(
bounds_half_height = expanded.y
}
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
center_x - bounds_half_width - padding,
center_y - bounds_half_height - padding,
@@ -165,7 +184,7 @@ build_rrect_primitive :: proc(
// Internal
//
// Build an RRect Primitive for a circle (fully-rounded square RRect).
// Build an RRect Base_2D_Primitive for a circle (fully-rounded square RRect).
// The caller sets color, flags, and uv fields on the returned primitive before submitting.
build_circle_primitive :: proc(
center: Vec2,
@@ -173,7 +192,7 @@ build_circle_primitive :: proc(
origin: Vec2,
rotation: f32,
feather_px: f32,
) -> Primitive {
) -> Base_2D_Primitive {
half_feather := feather_px * 0.5
padding := half_feather / GLOB.dpi_scaling
dpi_scale := GLOB.dpi_scaling
@@ -184,7 +203,7 @@ build_circle_primitive :: proc(
actual_center = compute_pivot_center(center, origin, sin_a, cos_a)
}
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
actual_center.x - radius - padding,
actual_center.y - radius - padding,
@@ -203,7 +222,7 @@ build_circle_primitive :: proc(
// Internal
//
// Build an Ellipse Primitive with bounds, params, and rotation computed from ellipse geometry.
// Build an Ellipse Base_2D_Primitive with bounds, params, and rotation computed from ellipse geometry.
// The caller sets color, flags, and uv fields on the returned primitive before submitting.
build_ellipse_primitive :: proc(
center: Vec2,
@@ -211,7 +230,7 @@ build_ellipse_primitive :: proc(
origin: Vec2,
rotation: f32,
feather_px: f32,
) -> Primitive {
) -> Base_2D_Primitive {
half_feather := feather_px * 0.5
padding := half_feather / GLOB.dpi_scaling
dpi_scale := GLOB.dpi_scaling
@@ -235,7 +254,7 @@ build_ellipse_primitive :: proc(
bound_vertical = expanded.y
}
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
actual_center.x - bound_horizontal - padding,
actual_center.y - bound_vertical - padding,
@@ -253,7 +272,7 @@ build_ellipse_primitive :: proc(
// Internal
//
// Build an NGon Primitive with bounds, params, and rotation computed from polygon geometry.
// Build an NGon Base_2D_Primitive with bounds, params, and rotation computed from polygon geometry.
// The caller sets color, flags, and uv fields on the returned primitive before submitting.
build_polygon_primitive :: proc(
center: Vec2,
@@ -262,7 +281,7 @@ build_polygon_primitive :: proc(
origin: Vec2,
rotation: f32,
feather_px: f32,
) -> Primitive {
) -> Base_2D_Primitive {
half_feather := feather_px * 0.5
padding := half_feather / GLOB.dpi_scaling
dpi_scale := GLOB.dpi_scaling
@@ -276,7 +295,7 @@ build_polygon_primitive :: proc(
rotation_radians := math.to_radians(rotation)
sin_rot, cos_rot := math.sincos(rotation_radians)
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
actual_center.x - radius - padding,
actual_center.y - radius - padding,
@@ -295,7 +314,7 @@ build_polygon_primitive :: proc(
// Internal
//
// Build a Ring_Arc Primitive with bounds and params computed from ring/arc geometry.
// Build a Ring_Arc Base_2D_Primitive with bounds and params computed from ring/arc geometry.
// Pre-computes the angular boundary normals on the CPU so the fragment shader needs
// no per-pixel sin/cos. The radial SDF uses max(inner-r, r-outer) which correctly
// handles pie slices (inner_radius = 0) and full rings.
@@ -309,7 +328,7 @@ build_ring_arc_primitive :: proc(
rotation: f32,
feather_px: f32,
) -> (
Primitive,
Base_2D_Primitive,
Shape_Flags,
) {
half_feather := feather_px * 0.5
@@ -347,7 +366,7 @@ build_ring_arc_primitive :: proc(
arc_flags = arc_span <= math.PI ? {.Arc_Narrow} : {.Arc_Wide}
}
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
actual_center.x - outer_radius - padding,
actual_center.y - outer_radius - padding,
@@ -365,39 +384,53 @@ build_ring_arc_primitive :: proc(
return prim, arc_flags
}
// Apply gradient and outline effects to a primitive. Sets flags, uv.effects, and expands bounds.
// Apply brush fill and outline to a primitive, then submit it.
// Dispatches to the correct sub-batch based on the Brush variant.
// All parameters (outline_width) are in logical pixels, matching the rest of the public API.
// The helper converts to physical pixels for GPU packing internally.
@(private)
apply_shape_effects :: proc(
prim: ^Primitive,
apply_brush_and_outline :: proc(
layer: ^Layer,
prim: ^Base_2D_Primitive,
kind: Shape_Kind,
gradient: Gradient,
brush: Brush,
outline_color: Color,
outline_width: f32,
extra_flags: Shape_Flags = {},
) {
flags: Shape_Flags = extra_flags
gradient_dir_sc: u32 = 0
switch g in gradient {
// Fill — determined by the Brush variant.
texture_id := INVALID_TEXTURE
sampler := DFT_SAMPLER
switch b in brush {
case Color: prim.color = b
case Linear_Gradient:
flags += {.Gradient}
prim.uv.effects.gradient_color = g.end_color
rad := math.to_radians(g.angle)
prim.color = b.start_color
prim.effects.gradient_color = b.end_color
rad := math.to_radians(b.angle)
sin_a, cos_a := math.sincos(rad)
gradient_dir_sc = pack_f16_pair(f16(cos_a), f16(sin_a))
prim.effects.gradient_dir_sc = pack_f16_pair(f16(cos_a), f16(sin_a))
case Radial_Gradient:
flags += {.Gradient, .Gradient_Radial}
prim.uv.effects.gradient_color = g.outer_color
case:
prim.color = b.inner_color
prim.effects.gradient_color = b.outer_color
case Texture_Fill:
flags += {.Textured}
tint, uv, sam := resolve_texture_defaults(b)
prim.color = tint
prim.uv_rect = {uv.x, uv.y, uv.width, uv.height}
texture_id = b.id
sampler = sam
}
outline_packed: u32 = 0
// Outline — orthogonal to all Brush variants.
if outline_width > 0 {
flags += {.Outline}
prim.uv.effects.outline_color = outline_color
outline_packed = pack_f16_pair(f16(outline_width * GLOB.dpi_scaling), 0)
prim.effects.outline_color = outline_color
prim.effects.outline_packed = pack_f16_pair(f16(outline_width * GLOB.dpi_scaling), 0)
// Expand bounds to contain the outline (bounds are in logical pixels)
prim.bounds[0] -= outline_width
prim.bounds[1] -= outline_width
@@ -410,9 +443,8 @@ apply_shape_effects :: proc(
flags += {.Rotated}
}
prim.uv.effects.gradient_dir_sc = gradient_dir_sc
prim.uv.effects.outline_packed = outline_packed
prim.flags = pack_kind_flags(kind, flags)
prepare_sdf_primitive_ex(layer, prim^, texture_id, sampler)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -430,8 +462,7 @@ apply_shape_effects :: proc(
rectangle :: proc(
layer: ^Layer,
rect: Rectangle,
color: Color,
gradient: Gradient = nil,
brush: Brush,
outline_color: Color = {},
outline_width: f32 = 0,
radii: Rectangle_Radii = {},
@@ -440,36 +471,7 @@ rectangle :: proc(
feather_px: f32 = DFT_FEATHER_PX,
) {
prim := build_rrect_primitive(rect, radii, origin, rotation, feather_px)
prim.color = color
apply_shape_effects(&prim, .RRect, gradient, outline_color, outline_width)
prepare_sdf_primitive(layer, prim)
}
// Draw a rectangle with a texture fill via SDF with optional per-corner rounding radii.
// Texture and gradient/outline are mutually exclusive (they share the same storage in the
// primitive). To outline a textured rect, draw the texture first, then a stroke-only rect on top.
// Origin semantics: see `rectangle`.
rectangle_texture :: proc(
layer: ^Layer,
rect: Rectangle,
id: Texture_Id,
tint: Color = DFT_TINT,
uv_rect: Rectangle = DFT_UV_RECT,
sampler: Sampler_Preset = DFT_SAMPLER,
radii: Rectangle_Radii = {},
origin: Vec2 = {},
rotation: f32 = 0,
feather_px: f32 = DFT_FEATHER_PX,
) {
prim := build_rrect_primitive(rect, radii, origin, rotation, feather_px)
prim.color = tint
tex_flags: Shape_Flags = {.Textured}
if prim.rotation_sc != 0 {
tex_flags += {.Rotated}
}
prim.flags = pack_kind_flags(.RRect, tex_flags)
prim.uv.uv_rect = {uv_rect.x, uv_rect.y, uv_rect.width, uv_rect.height}
prepare_sdf_primitive_textured(layer, prim, id, sampler)
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -488,8 +490,7 @@ circle :: proc(
layer: ^Layer,
center: Vec2,
radius: f32,
color: Color,
gradient: Gradient = nil,
brush: Brush,
outline_color: Color = {},
outline_width: f32 = 0,
origin: Vec2 = {},
@@ -497,9 +498,7 @@ circle :: proc(
feather_px: f32 = DFT_FEATHER_PX,
) {
prim := build_circle_primitive(center, radius, origin, rotation, feather_px)
prim.color = color
apply_shape_effects(&prim, .RRect, gradient, outline_color, outline_width)
prepare_sdf_primitive(layer, prim)
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -512,8 +511,7 @@ ellipse :: proc(
layer: ^Layer,
center: Vec2,
radius_horizontal, radius_vertical: f32,
color: Color,
gradient: Gradient = nil,
brush: Brush,
outline_color: Color = {},
outline_width: f32 = 0,
origin: Vec2 = {},
@@ -521,9 +519,7 @@ ellipse :: proc(
feather_px: f32 = DFT_FEATHER_PX,
) {
prim := build_ellipse_primitive(center, radius_horizontal, radius_vertical, origin, rotation, feather_px)
prim.color = color
apply_shape_effects(&prim, .Ellipse, gradient, outline_color, outline_width)
prepare_sdf_primitive(layer, prim)
apply_brush_and_outline(layer, &prim, .Ellipse, brush, outline_color, outline_width)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -538,8 +534,7 @@ polygon :: proc(
center: Vec2,
sides: int,
radius: f32,
color: Color,
gradient: Gradient = nil,
brush: Brush,
outline_color: Color = {},
outline_width: f32 = 0,
origin: Vec2 = {},
@@ -549,9 +544,7 @@ polygon :: proc(
if sides < 3 do return
prim := build_polygon_primitive(center, sides, radius, origin, rotation, feather_px)
prim.color = color
apply_shape_effects(&prim, .NGon, gradient, outline_color, outline_width)
prepare_sdf_primitive(layer, prim)
apply_brush_and_outline(layer, &prim, .NGon, brush, outline_color, outline_width)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -566,8 +559,7 @@ ring :: proc(
layer: ^Layer,
center: Vec2,
inner_radius, outer_radius: f32,
color: Color,
gradient: Gradient = nil,
brush: Brush,
outline_color: Color = {},
outline_width: f32 = 0,
start_angle: f32 = 0,
@@ -586,9 +578,7 @@ ring :: proc(
rotation,
feather_px,
)
prim.color = color
apply_shape_effects(&prim, .Ring_Arc, gradient, outline_color, outline_width, arc_flags)
prepare_sdf_primitive(layer, prim)
apply_brush_and_outline(layer, &prim, .Ring_Arc, brush, outline_color, outline_width, arc_flags)
}
// ---------------------------------------------------------------------------------------------------------------------
@@ -600,7 +590,7 @@ ring :: proc(
line :: proc(
layer: ^Layer,
start_position, end_position: Vec2,
color: Color,
brush: Brush,
thickness: f32 = DFT_STROKE_THICKNESS,
outline_color: Color = {},
outline_width: f32 = 0,
@@ -627,14 +617,13 @@ line :: proc(
// Expand bounds for rotation
bounds_half := rotated_aabb_half_extents(half_length + cap_radius, half_thickness, cos_angle, sin_angle)
prim := Primitive {
prim := Base_2D_Primitive {
bounds = {
center_x - bounds_half.x - padding,
center_y - bounds_half.y - padding,
center_x + bounds_half.x + padding,
center_y + bounds_half.y + padding,
},
color = color,
rotation_sc = pack_rotation_sc(sin_angle, cos_angle),
}
prim.params.rrect = RRect_Params {
@@ -647,15 +636,14 @@ line :: proc(
},
half_feather = half_feather,
}
apply_shape_effects(&prim, .RRect, nil, outline_color, outline_width)
prepare_sdf_primitive(layer, prim)
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
}
// Draw a line strip via decomposed SDF line segments.
line_strip :: proc(
layer: ^Layer,
points: []Vec2,
color: Color,
brush: Brush,
thickness: f32 = DFT_STROKE_THICKNESS,
outline_color: Color = {},
outline_width: f32 = 0,
@@ -663,7 +651,7 @@ line_strip :: proc(
) {
if len(points) < 2 do return
for i in 0 ..< len(points) - 1 {
line(layer, points[i], points[i + 1], color, thickness, outline_color, outline_width, feather_px)
line(layer, points[i], points[i + 1], brush, thickness, outline_color, outline_width, feather_px)
}
}