Basic texture support

This commit is contained in:
Zachary Levy
2026-04-21 13:01:02 -07:00
parent f85187eff3
commit a4623a13b5
17 changed files with 1375 additions and 216 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) {
@@ -358,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)} \
)
}
@@ -384,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,
@@ -400,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,
@@ -415,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,
@@ -436,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
@@ -480,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,
@@ -502,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
@@ -545,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,
@@ -582,6 +715,7 @@ circle :: proc(
}
// Draw a stroked circle via SDF.
// Origin semantics: see `circle`.
circle_lines :: proc(
layer: ^Layer,
center: [2]f32,
@@ -619,6 +753,7 @@ circle_lines :: proc(
}
// Draw a filled ellipse via SDF.
// Origin semantics: see `circle`.
ellipse :: proc(
layer: ^Layer,
center: [2]f32,
@@ -665,6 +800,7 @@ ellipse :: proc(
}
// Draw a stroked ellipse via SDF.
// Origin semantics: see `circle`.
ellipse_lines :: proc(
layer: ^Layer,
center: [2]f32,
@@ -715,6 +851,7 @@ ellipse_lines :: proc(
}
// Draw a filled ring arc via SDF.
// Origin semantics: see `circle`.
ring :: proc(
layer: ^Layer,
center: [2]f32,
@@ -757,6 +894,7 @@ ring :: proc(
}
// Draw stroked ring arc outlines via SDF.
// Origin semantics: see `circle`.
ring_lines :: proc(
layer: ^Layer,
center: [2]f32,