Texture Rendering (#9)

Co-authored-by: Zachary Levy <zachary@sunforge.is>
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
2026-04-22 00:05:08 +00:00
parent 64de816647
commit 0d424cbd6e
19 changed files with 1765 additions and 357 deletions

View File

@@ -68,6 +68,19 @@ emit_rectangle :: proc(x, y, width, height: f32, color: Color, vertices: []Verte
vertices[offset + 5] = solid_vertex({x, y + height}, color)
}
@(private = "file")
prepare_sdf_primitive_textured :: proc(
layer: ^Layer,
prim: Primitive,
texture_id: Texture_Id,
sampler: Sampler_Preset,
) {
offset := u32(len(GLOB.tmp_primitives))
append(&GLOB.tmp_primitives, prim)
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
append_or_extend_sub_batch(scissor, layer, .SDF, offset, 1, texture_id, sampler)
}
// ----- Drawing functions ----
pixel :: proc(layer: ^Layer, pos: [2]f32, color: Color) {
@@ -83,6 +96,7 @@ rectangle_gradient :: proc(
temp_allocator := context.temp_allocator,
) {
vertices := make([]Vertex, 6, temp_allocator)
defer delete(vertices, temp_allocator)
corner_top_left := [2]f32{rect.x, rect.y}
corner_top_right := [2]f32{rect.x + rect.width, rect.y}
@@ -115,6 +129,7 @@ circle_sector :: proc(
vertex_count := segment_count * 3
vertices := make([]Vertex, vertex_count, temp_allocator)
defer delete(vertices, temp_allocator)
start_radians := math.to_radians(start_angle)
end_radians := math.to_radians(end_angle)
@@ -167,6 +182,7 @@ circle_gradient :: proc(
vertex_count := segment_count * 3
vertices := make([]Vertex, vertex_count, temp_allocator)
defer delete(vertices, temp_allocator)
step_angle := math.TAU / f32(segment_count)
@@ -238,6 +254,7 @@ triangle_lines :: proc(
temp_allocator := context.temp_allocator,
) {
vertices := make([]Vertex, 18, temp_allocator)
defer delete(vertices, temp_allocator)
write_offset := 0
if !needs_transform(origin, rotation) {
@@ -273,6 +290,7 @@ triangle_fan :: proc(
triangle_count := len(points) - 2
vertex_count := triangle_count * 3
vertices := make([]Vertex, vertex_count, temp_allocator)
defer delete(vertices, temp_allocator)
if !needs_transform(origin, rotation) {
for i in 1 ..< len(points) - 1 {
@@ -312,6 +330,7 @@ triangle_strip :: proc(
triangle_count := len(points) - 2
vertex_count := triangle_count * 3
vertices := make([]Vertex, vertex_count, temp_allocator)
defer delete(vertices, temp_allocator)
if !needs_transform(origin, rotation) {
for i in 0 ..< triangle_count {
@@ -352,17 +371,20 @@ triangle_strip :: proc(
// ----- SDF drawing functions ----
// Compute new center position after rotating a center-parametrized shape
// around a pivot point. The pivot is at (center + origin) in world space.
// Compute the visual center of a center-parametrized shape after applying
// Convention B origin semantics: `center` is where the origin-point lands in
// world space; the visual center is offset by -origin and then rotated around
// the landing point.
// visual_center = center + R(θ) · (-origin)
// When θ=0: visual_center = center - origin (pure positioning shift).
// When origin={0,0}: visual_center = center (no change).
@(private = "file")
compute_pivot_center :: proc(center: [2]f32, origin: [2]f32, rotation_deg: f32) -> [2]f32 {
if origin == {0, 0} do return center
theta := math.to_radians(rotation_deg)
cos_angle, sin_angle := math.cos(theta), math.sin(theta)
// pivot = center + origin; new_center = pivot + R(θ) * (center - pivot)
return(
center +
origin +
{cos_angle * (-origin.x) - sin_angle * (-origin.y), sin_angle * (-origin.x) + cos_angle * (-origin.y)} \
)
}
@@ -378,6 +400,13 @@ rotated_aabb_half_extents :: proc(half_width, half_height, rotation_radians: f32
// Draw a filled rectangle via SDF (analytical anti-aliasing at all orientations).
// `roundness` is a 01 fraction controlling uniform corner rounding — 0 is sharp, 1 is fully rounded.
// For per-corner pixel-precise rounding, use `rectangle_corners` instead.
//
// Origin semantics:
// `origin` is a local offset from the rect's top-left corner that selects both the positioning
// anchor and the rotation pivot. `rect.x, rect.y` specifies where that anchor point lands in
// world space. When `origin = {0, 0}` (default), `rect.x, rect.y` is the top-left corner.
// When `origin = center_of_rectangle(rect)`, `rect.x, rect.y` is the visual center.
// Rotation always occurs around the anchor point.
rectangle :: proc(
layer: ^Layer,
rect: Rectangle,
@@ -394,6 +423,7 @@ rectangle :: proc(
// Draw a stroked rectangle via SDF (analytical anti-aliasing at all orientations).
// `roundness` is a 01 fraction controlling uniform corner rounding — 0 is sharp, 1 is fully rounded.
// For per-corner pixel-precise rounding, use `rectangle_corners_lines` instead.
// Origin semantics: see `rectangle`.
rectangle_lines :: proc(
layer: ^Layer,
rect: Rectangle,
@@ -409,6 +439,7 @@ rectangle_lines :: proc(
}
// Draw a rectangle with per-corner rounding radii via SDF.
// Origin semantics: see `rectangle`.
rectangle_corners :: proc(
layer: ^Layer,
rect: Rectangle,
@@ -430,12 +461,12 @@ rectangle_corners :: proc(
half_width := rect.width * 0.5
half_height := rect.height * 0.5
rotation_radians: f32 = 0
center_x := rect.x + half_width
center_y := rect.y + half_height
center_x := rect.x + half_width - origin.x
center_y := rect.y + half_height - origin.y
if needs_transform(origin, rotation) {
rotation_radians = math.to_radians(rotation)
transform := build_pivot_rotation({rect.x, rect.y}, origin, rotation)
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
new_center := apply_transform(transform, {half_width, half_height})
center_x = new_center.x
center_y = new_center.y
@@ -474,6 +505,7 @@ rectangle_corners :: proc(
}
// Draw a stroked rectangle with per-corner rounding radii via SDF.
// Origin semantics: see `rectangle`.
rectangle_corners_lines :: proc(
layer: ^Layer,
rect: Rectangle,
@@ -496,12 +528,12 @@ rectangle_corners_lines :: proc(
half_width := rect.width * 0.5
half_height := rect.height * 0.5
rotation_radians: f32 = 0
center_x := rect.x + half_width
center_y := rect.y + half_height
center_x := rect.x + half_width - origin.x
center_y := rect.y + half_height - origin.y
if needs_transform(origin, rotation) {
rotation_radians = math.to_radians(rotation)
transform := build_pivot_rotation({rect.x, rect.y}, origin, rotation)
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
new_center := apply_transform(transform, {half_width, half_height})
center_x = new_center.x
center_y = new_center.y
@@ -539,7 +571,114 @@ rectangle_corners_lines :: proc(
prepare_sdf_primitive(layer, prim)
}
// Draw a rectangle with a texture fill via SDF. Supports rounded corners via `roundness`,
// rotation, and analytical anti-aliasing on the shape silhouette.
// Origin semantics: see `rectangle`.
rectangle_texture :: proc(
layer: ^Layer,
rect: Rectangle,
id: Texture_Id,
tint: Color = WHITE,
uv_rect: Rectangle = {0, 0, 1, 1},
sampler: Sampler_Preset = .Linear_Clamp,
roundness: f32 = 0,
origin: [2]f32 = {0, 0},
rotation: f32 = 0,
soft_px: f32 = 1.0,
) {
cr := min(rect.width, rect.height) * clamp(roundness, 0, 1) * 0.5
rectangle_texture_corners(
layer,
rect,
{cr, cr, cr, cr},
id,
tint,
uv_rect,
sampler,
origin,
rotation,
soft_px,
)
}
// Draw a rectangle with a texture fill and per-corner rounding radii via SDF.
// Origin semantics: see `rectangle`.
rectangle_texture_corners :: proc(
layer: ^Layer,
rect: Rectangle,
radii: [4]f32,
id: Texture_Id,
tint: Color = WHITE,
uv_rect: Rectangle = {0, 0, 1, 1},
sampler: Sampler_Preset = .Linear_Clamp,
origin: [2]f32 = {0, 0},
rotation: f32 = 0,
soft_px: f32 = 1.0,
) {
max_radius := min(rect.width, rect.height) * 0.5
top_left := clamp(radii[0], 0, max_radius)
top_right := clamp(radii[1], 0, max_radius)
bottom_right := clamp(radii[2], 0, max_radius)
bottom_left := clamp(radii[3], 0, max_radius)
padding := soft_px / GLOB.dpi_scaling
dpi_scale := GLOB.dpi_scaling
half_width := rect.width * 0.5
half_height := rect.height * 0.5
rotation_radians: f32 = 0
center_x := rect.x + half_width - origin.x
center_y := rect.y + half_height - origin.y
if needs_transform(origin, rotation) {
rotation_radians = math.to_radians(rotation)
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
new_center := apply_transform(transform, {half_width, half_height})
center_x = new_center.x
center_y = new_center.y
}
bounds_half_width, bounds_half_height := half_width, half_height
if rotation_radians != 0 {
expanded := rotated_aabb_half_extents(half_width, half_height, rotation_radians)
bounds_half_width = expanded.x
bounds_half_height = expanded.y
}
prim := Primitive {
bounds = {
center_x - bounds_half_width - padding,
center_y - bounds_half_height - padding,
center_x + bounds_half_width + padding,
center_y + bounds_half_height + padding,
},
color = tint,
kind_flags = pack_kind_flags(.RRect, {.Textured}),
rotation = rotation_radians,
uv_rect = {uv_rect.x, uv_rect.y, uv_rect.width, uv_rect.height},
}
prim.params.rrect = RRect_Params {
half_size = {half_width * dpi_scale, half_height * dpi_scale},
radii = {
top_right * dpi_scale,
bottom_right * dpi_scale,
top_left * dpi_scale,
bottom_left * dpi_scale,
},
soft_px = soft_px,
stroke_px = 0,
}
prepare_sdf_primitive_textured(layer, prim, id, sampler)
}
// Draw a filled circle via SDF.
//
// Origin semantics (Convention B):
// `origin` is a local offset from the shape's center that selects both the positioning anchor
// and the rotation pivot. The `center` parameter specifies where that anchor point lands in
// world space. When `origin = {0, 0}` (default), `center` is the visual center.
// When `origin = {r, 0}`, the point `r` pixels to the right of the shape center lands at
// `center`, shifting the shape left by `r`.
circle :: proc(
layer: ^Layer,
center: [2]f32,
@@ -576,6 +715,7 @@ circle :: proc(
}
// Draw a stroked circle via SDF.
// Origin semantics: see `circle`.
circle_lines :: proc(
layer: ^Layer,
center: [2]f32,
@@ -613,6 +753,7 @@ circle_lines :: proc(
}
// Draw a filled ellipse via SDF.
// Origin semantics: see `circle`.
ellipse :: proc(
layer: ^Layer,
center: [2]f32,
@@ -659,6 +800,7 @@ ellipse :: proc(
}
// Draw a stroked ellipse via SDF.
// Origin semantics: see `circle`.
ellipse_lines :: proc(
layer: ^Layer,
center: [2]f32,
@@ -709,6 +851,7 @@ ellipse_lines :: proc(
}
// Draw a filled ring arc via SDF.
// Origin semantics: see `circle`.
ring :: proc(
layer: ^Layer,
center: [2]f32,
@@ -751,6 +894,7 @@ ring :: proc(
}
// Draw stroked ring arc outlines via SDF.
// Origin semantics: see `circle`.
ring_lines :: proc(
layer: ^Layer,
center: [2]f32,