Major rendering updates
This commit is contained in:
@@ -5,8 +5,13 @@ import "core:log"
|
||||
import "core:mem"
|
||||
import sdl "vendor:sdl3"
|
||||
|
||||
// Vertex layout for tessellated and text geometry.
|
||||
// IMPORTANT: `color` must be premultiplied alpha (RGB channels pre-scaled by alpha).
|
||||
// The tessellated fragment shader passes vertex color through directly — it does NOT
|
||||
// premultiply. The blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied-over).
|
||||
// Use `premultiply_color` when constructing vertices manually for `prepare_shape`.
|
||||
Vertex :: struct {
|
||||
position: [2]f32,
|
||||
position: Vec2,
|
||||
uv: [2]f32,
|
||||
color: Color,
|
||||
}
|
||||
@@ -23,99 +28,127 @@ TextBatch :: struct {
|
||||
// ----- SDF primitive types -----------
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// The SDF path evaluates one of four signed distance functions per primitive, dispatched
|
||||
// by Shape_Kind encoded in the low byte of Primitive.flags:
|
||||
//
|
||||
// RRect — rounded rectangle with per-corner radii (sdRoundedBox). Also covers circles
|
||||
// (uniform radii = half-size), capsule-style line segments (rotated, max rounding),
|
||||
// and other RRect-reducible shapes.
|
||||
// NGon — regular polygon with N sides and optional rounding.
|
||||
// Ellipse — approximate ellipse (non-exact SDF, suitable for UI but not for shape merging).
|
||||
// Ring_Arc — annular ring with optional angular clipping. Covers full rings, partial arcs,
|
||||
// pie slices (inner_radius = 0), and loading spinners.
|
||||
Shape_Kind :: enum u8 {
|
||||
Solid = 0,
|
||||
Solid = 0, // tessellated path (mode marker; not a real SDF kind)
|
||||
RRect = 1,
|
||||
Circle = 2,
|
||||
NGon = 2,
|
||||
Ellipse = 3,
|
||||
Segment = 4,
|
||||
Ring_Arc = 5,
|
||||
NGon = 6,
|
||||
Ring_Arc = 4,
|
||||
}
|
||||
|
||||
Shape_Flag :: enum u8 {
|
||||
Stroke,
|
||||
Textured,
|
||||
Textured, // bit 0: sample texture using uv.uv_rect (mutually exclusive with Gradient)
|
||||
Gradient, // bit 1: 2-color gradient using uv.effects.gradient_color as end/outer color
|
||||
Gradient_Radial, // bit 2: if set with Gradient, radial from center; else linear at angle
|
||||
Outline, // bit 3: outer outline band using uv.effects.outline_color; CPU expands bounds by outline_width
|
||||
Rotated, // bit 4: shape has non-zero rotation; rotation_sc contains packed sin/cos
|
||||
Arc_Narrow, // bit 5: ring arc span ≤ π — intersect half-planes. Neither Arc bit = full ring.
|
||||
Arc_Wide, // bit 6: ring arc span > π — union half-planes. Neither Arc bit = full ring.
|
||||
}
|
||||
|
||||
Shape_Flags :: bit_set[Shape_Flag;u8]
|
||||
|
||||
RRect_Params :: struct {
|
||||
half_size: [2]f32,
|
||||
radii: [4]f32,
|
||||
soft_px: f32,
|
||||
stroke_px: f32,
|
||||
}
|
||||
|
||||
Circle_Params :: struct {
|
||||
radius: f32,
|
||||
soft_px: f32,
|
||||
stroke_px: f32,
|
||||
_: [5]f32,
|
||||
}
|
||||
|
||||
Ellipse_Params :: struct {
|
||||
radii: [2]f32,
|
||||
soft_px: f32,
|
||||
stroke_px: f32,
|
||||
_: [4]f32,
|
||||
}
|
||||
|
||||
Segment_Params :: struct {
|
||||
a: [2]f32,
|
||||
b: [2]f32,
|
||||
width: f32,
|
||||
soft_px: f32,
|
||||
_: [2]f32,
|
||||
}
|
||||
|
||||
Ring_Arc_Params :: struct {
|
||||
inner_radius: f32,
|
||||
outer_radius: f32,
|
||||
start_rad: f32,
|
||||
end_rad: f32,
|
||||
soft_px: f32,
|
||||
_: [3]f32,
|
||||
half_size: [2]f32,
|
||||
radii: [4]f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
}
|
||||
|
||||
NGon_Params :: struct {
|
||||
radius: f32,
|
||||
rotation: f32,
|
||||
sides: f32,
|
||||
soft_px: f32,
|
||||
stroke_px: f32,
|
||||
_: [3]f32,
|
||||
radius: f32,
|
||||
sides: f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
}
|
||||
|
||||
Ellipse_Params :: struct {
|
||||
radii: [2]f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
}
|
||||
|
||||
Ring_Arc_Params :: struct {
|
||||
inner_radius: f32, // inner radius in physical pixels (0 for pie slice)
|
||||
outer_radius: f32, // outer radius in physical pixels
|
||||
normal_start: [2]f32, // pre-computed outward normal of start edge: (sin(start), -cos(start))
|
||||
normal_end: [2]f32, // pre-computed outward normal of end edge: (-sin(end), cos(end))
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
}
|
||||
|
||||
Shape_Params :: struct #raw_union {
|
||||
rrect: RRect_Params,
|
||||
circle: Circle_Params,
|
||||
ellipse: Ellipse_Params,
|
||||
segment: Segment_Params,
|
||||
ring_arc: Ring_Arc_Params,
|
||||
ngon: NGon_Params,
|
||||
ellipse: Ellipse_Params,
|
||||
ring_arc: Ring_Arc_Params,
|
||||
raw: [8]f32,
|
||||
}
|
||||
|
||||
#assert(size_of(Shape_Params) == 32)
|
||||
|
||||
// GPU layout: 64 bytes, std430-compatible. The shader declares this as a storage buffer struct.
|
||||
// GPU-side storage for 2-color gradient parameters and/or outline parameters.
|
||||
// Packed into 16 bytes to alias with uv_rect in the Uv_Or_Effects raw union.
|
||||
// The shader reads gradient_color and outline_color via unpackUnorm4x8.
|
||||
// gradient_dir_sc stores the pre-computed gradient direction as (cos, sin) in f16 pair
|
||||
// via unpackHalf2x16. outline_packed stores outline_width as f16 via unpackHalf2x16.
|
||||
Gradient_Outline :: struct {
|
||||
gradient_color: Color, // 0: end (linear) or outer (radial) gradient color
|
||||
outline_color: Color, // 4: outline band color
|
||||
gradient_dir_sc: u32, // 8: packed f16 pair: low = cos(angle), high = sin(angle) — pre-computed gradient direction
|
||||
outline_packed: u32, // 12: packed f16 pair: low = outline_width (f16, physical pixels), high = reserved
|
||||
}
|
||||
|
||||
#assert(size_of(Gradient_Outline) == 16)
|
||||
|
||||
// Uv_Or_Effects aliases the final 16 bytes of a Primitive. When .Textured is set,
|
||||
// uv_rect holds texture-atlas coordinates. When .Gradient or .Outline is set,
|
||||
// effects holds 2-color gradient parameters and/or outline parameters.
|
||||
// Textured and Gradient are mutually exclusive; if both are set, Gradient takes precedence.
|
||||
Uv_Or_Effects :: struct #raw_union {
|
||||
uv_rect: [4]f32, // u_min, v_min, u_max, v_max (default {0,0,1,1})
|
||||
effects: Gradient_Outline, // gradient + outline parameters
|
||||
}
|
||||
|
||||
// GPU layout: 80 bytes, std430-compatible. The shader declares this as a storage buffer struct.
|
||||
// The low byte of `flags` encodes the Shape_Kind (0 = tessellated, 1-4 = SDF kinds).
|
||||
// Bits 8-15 encode Shape_Flags (Textured, Gradient, Gradient_Radial, Outline, Rotated, Arc_Narrow, Arc_Wide).
|
||||
// rotation_sc stores pre-computed sin/cos of the rotation angle as a packed f16 pair,
|
||||
// avoiding per-pixel trigonometry in the fragment shader. Only read when .Rotated is set.
|
||||
Primitive :: struct {
|
||||
bounds: [4]f32, // 0: min_x, min_y, max_x, max_y (world-space, pre-DPI)
|
||||
color: Color, // 16: u8x4, unpacked in shader via unpackUnorm4x8
|
||||
kind_flags: u32, // 20: (kind as u32) | (flags as u32 << 8)
|
||||
rotation: f32, // 24: shader self-rotation in radians (used by RRect, Ellipse)
|
||||
_pad: f32, // 28: alignment to vec4 boundary
|
||||
params: Shape_Params, // 32: two vec4s of shape params
|
||||
uv_rect: [4]f32, // 64: u_min, v_min, u_max, v_max (default {0,0,1,1})
|
||||
bounds: [4]f32, // 0: min_x, min_y, max_x, max_y (world-space, pre-DPI)
|
||||
color: Color, // 16: u8x4, fill color / gradient start color / texture tint
|
||||
flags: u32, // 20: low byte = Shape_Kind, bits 8+ = Shape_Flags
|
||||
rotation_sc: u32, // 24: packed f16 pair: low = sin(angle), high = cos(angle). Requires .Rotated flag.
|
||||
_pad: f32, // 28: reserved for future use
|
||||
params: Shape_Params, // 32: per-kind shape parameters (raw union, 32 bytes)
|
||||
uv: Uv_Or_Effects, // 64: texture coords or gradient/outline parameters
|
||||
}
|
||||
|
||||
#assert(size_of(Primitive) == 80)
|
||||
|
||||
// Pack shape kind and flags into the Primitive.flags field. The low byte encodes the Shape_Kind
|
||||
// (which also serves as the SDF mode marker — kind > 0 means SDF path). The tessellated path
|
||||
// leaves the field at 0 (Solid kind, set by vertex shader zero-initialization).
|
||||
pack_kind_flags :: #force_inline proc(kind: Shape_Kind, flags: Shape_Flags) -> u32 {
|
||||
return u32(kind) | (u32(transmute(u8)flags) << 8)
|
||||
}
|
||||
|
||||
// Pack two f16 values into a single u32 for GPU consumption via unpackHalf2x16.
|
||||
// Used to pack gradient_dir_sc (cos/sin) and outline_packed (width/reserved) in Gradient_Outline.
|
||||
pack_f16_pair :: #force_inline proc(low, high: f16) -> u32 {
|
||||
return u32(transmute(u16)low) | (u32(transmute(u16)high) << 16)
|
||||
}
|
||||
|
||||
Pipeline_2D_Base :: struct {
|
||||
sdl_pipeline: ^sdl.GPUGraphicsPipeline,
|
||||
vertex_buffer: Buffer,
|
||||
@@ -208,19 +241,23 @@ create_pipeline_2d_base :: proc(
|
||||
target_info = sdl.GPUGraphicsPipelineTargetInfo {
|
||||
color_target_descriptions = &sdl.GPUColorTargetDescription {
|
||||
format = sdl.GetGPUSwapchainTextureFormat(device, window),
|
||||
// Premultiplied-alpha blending: src outputs RGB pre-multiplied by alpha,
|
||||
// so src factor is ONE (not SRC_ALPHA). This eliminates the per-pixel
|
||||
// divide in the outline path and is the standard blend mode used by
|
||||
// Skia, Flutter, and GPUI.
|
||||
blend_state = sdl.GPUColorTargetBlendState {
|
||||
enable_blend = true,
|
||||
enable_color_write_mask = true,
|
||||
src_color_blendfactor = .SRC_ALPHA,
|
||||
src_color_blendfactor = .ONE,
|
||||
dst_color_blendfactor = .ONE_MINUS_SRC_ALPHA,
|
||||
color_blend_op = .ADD,
|
||||
src_alpha_blendfactor = .SRC_ALPHA,
|
||||
src_alpha_blendfactor = .ONE,
|
||||
dst_alpha_blendfactor = .ONE_MINUS_SRC_ALPHA,
|
||||
alpha_blend_op = .ADD,
|
||||
color_write_mask = sdl.GPUColorComponentFlags{.R, .G, .B, .A},
|
||||
},
|
||||
},
|
||||
num_color_targets = 1,
|
||||
num_color_targets = 1,
|
||||
},
|
||||
vertex_input_state = sdl.GPUVertexInputState {
|
||||
vertex_buffer_descriptions = &sdl.GPUVertexBufferDescription {
|
||||
@@ -300,7 +337,7 @@ create_pipeline_2d_base :: proc(
|
||||
}
|
||||
|
||||
// Upload white pixel and unit quad data in a single command buffer
|
||||
white_pixel := [4]u8{255, 255, 255, 255}
|
||||
white_pixel := Color{255, 255, 255, 255}
|
||||
white_transfer_buf := sdl.CreateGPUTransferBuffer(
|
||||
device,
|
||||
sdl.GPUTransferBufferCreateInfo{usage = .UPLOAD, size = size_of(white_pixel)},
|
||||
@@ -578,7 +615,7 @@ draw_layer :: proc(
|
||||
|
||||
for &batch in GLOB.tmp_sub_batches[scissor.sub_batch_start:][:scissor.sub_batch_len] {
|
||||
switch batch.kind {
|
||||
case .Shapes:
|
||||
case .Tessellated:
|
||||
if current_mode != .Tessellated {
|
||||
push_globals(cmd_buffer, width, height, .Tessellated)
|
||||
current_mode = .Tessellated
|
||||
|
||||
Reference in New Issue
Block a user