Major reorg
This commit is contained in:
+332
-347
@@ -10,6 +10,11 @@ import sdl_ttf "vendor:sdl3/ttf"
|
||||
|
||||
import clay "../vendor/clay"
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Shader format ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL (each constant in the when-block below)
|
||||
when ODIN_OS == .Darwin {
|
||||
PLATFORM_SHADER_FORMAT_FLAG :: sdl.GPUShaderFormatFlag.MSL
|
||||
SHADER_ENTRY :: cstring("main0")
|
||||
@@ -29,10 +34,18 @@ when ODIN_OS == .Darwin {
|
||||
BACKDROP_BLUR_VERT_RAW :: #load("shaders/generated/backdrop_blur.vert.spv")
|
||||
BACKDROP_BLUR_FRAG_RAW :: #load("shaders/generated/backdrop_blur.frag.spv")
|
||||
}
|
||||
|
||||
PLATFORM_SHADER_FORMAT :: sdl.GPUShaderFormat{PLATFORM_SHADER_FORMAT_FLAG}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Defaults and config ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL
|
||||
BUFFER_INIT_SIZE :: 256
|
||||
//INTERNAL
|
||||
INITIAL_LAYER_SIZE :: 5
|
||||
//INTERNAL
|
||||
INITIAL_SCISSOR_SIZE :: 10
|
||||
|
||||
// ----- Default parameter values -----
|
||||
@@ -48,64 +61,70 @@ DFT_TEXT_COLOR :: BLACK // Default text color.
|
||||
DFT_CLEAR_COLOR :: BLACK // Default clear color for end().
|
||||
DFT_SAMPLER :: Sampler_Preset.Linear_Clamp // Default texture sampler preset.
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Global state ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL
|
||||
GLOB: Global
|
||||
|
||||
//INTERNAL
|
||||
Global :: struct {
|
||||
// -- Per-frame staging (hottest — touched by every prepare/upload/clear cycle) --
|
||||
tmp_shape_verts: [dynamic]Vertex, // Tessellated shape vertices staged for GPU upload.
|
||||
tmp_text_verts: [dynamic]Vertex, // Text vertices staged for GPU upload.
|
||||
tmp_text_indices: [dynamic]c.int, // Text index buffer staged for GPU upload.
|
||||
tmp_text_batches: [dynamic]TextBatch, // Text atlas batch metadata for indexed drawing.
|
||||
tmp_primitives: [dynamic]Base_2D_Primitive, // SDF primitives staged for GPU storage buffer upload (base 2D pipeline).
|
||||
tmp_sub_batches: [dynamic]Sub_Batch, // Sub-batch records that drive draw call dispatch.
|
||||
tmp_uncached_text: [dynamic]^sdl_ttf.Text, // Uncached TTF_Text objects destroyed after end() submits.
|
||||
tmp_backdrop_primitives: [dynamic]Backdrop_Primitive, // Backdrop primitives staged for GPU storage buffer upload.
|
||||
layers: [dynamic]Layer, // Draw layers, each with its own scissor stack.
|
||||
scissors: [dynamic]Scissor, // Scissor rects that clip drawing within each layer.
|
||||
tmp_shape_verts: [dynamic]Vertex_2D, // Tessellated shape vertices staged for GPU upload.
|
||||
tmp_text_verts: [dynamic]Vertex_2D, // Text vertices staged for GPU upload.
|
||||
tmp_text_indices: [dynamic]c.int, // Text index buffer staged for GPU upload.
|
||||
tmp_text_batches: [dynamic]Text_Batch, // Text atlas batch metadata for indexed drawing.
|
||||
tmp_primitives: [dynamic]Core_2D_Primitive, // SDF primitives staged for GPU storage buffer upload (core 2D subsystem).
|
||||
tmp_sub_batches: [dynamic]Sub_Batch, // Sub-batch records that drive draw call dispatch.
|
||||
tmp_uncached_text: [dynamic]^sdl_ttf.Text, // Uncached TTF_Text objects destroyed after end() submits.
|
||||
tmp_gaussian_blur_primitives: [dynamic]Gaussian_Blur_Primitive, // Gaussian blur primitives staged for GPU storage buffer upload.
|
||||
layers: [dynamic]Layer, // Draw layers, each with its own scissor stack.
|
||||
scissors: [dynamic]Scissor, // Scissor rects that clip drawing within each layer.
|
||||
|
||||
// -- Per-frame scalars (accessed during prepare and draw_layer) --
|
||||
curr_layer_index: uint, // Index of the currently active layer.
|
||||
dpi_scaling: f32, // Window DPI scale factor applied to all pixel coordinates.
|
||||
clay_z_index: i16, // Tracks z-index for layer splitting during Clay batch processing.
|
||||
cleared: bool, // Whether the render target has been cleared this frame.
|
||||
curr_layer_index: uint, // Index of the currently active layer.
|
||||
dpi_scaling: f32, // Window DPI scale factor applied to all pixel coordinates.
|
||||
clay_z_index: i16, // Tracks z-index for layer splitting during Clay batch processing.
|
||||
cleared: bool, // Whether the render target has been cleared this frame.
|
||||
|
||||
// -- Pipeline (accessed every draw_layer call) --
|
||||
pipeline_2d_base: Pipeline_2D_Base, // The unified 2D GPU pipeline (shaders, buffers, samplers).
|
||||
pipeline_2d_backdrop: Pipeline_2D_Backdrop, // Frosted-glass backdrop blur pipeline (downsample + blur PSOs, working textures).
|
||||
device: ^sdl.GPUDevice, // GPU device handle, stored at init.
|
||||
samplers: [SAMPLER_PRESET_COUNT]^sdl.GPUSampler, // Lazily-created sampler objects, one per Sampler_Preset.
|
||||
// -- Subsystems (accessed every draw_layer call) --
|
||||
core_2d: Core_2D, // The unified 2D GPU pipeline (shaders, buffers, samplers).
|
||||
backdrop: Backdrop, // Frosted-glass backdrop blur subsystem (downsample + blur PSOs, working textures).
|
||||
device: ^sdl.GPUDevice, // GPU device handle, stored at init.
|
||||
samplers: [SAMPLER_PRESET_COUNT]^sdl.GPUSampler, // Lazily-created sampler objects, one per Sampler_Preset.
|
||||
|
||||
// -- Deferred release (processed once per frame at frame boundary) --
|
||||
pending_texture_releases: [dynamic]Texture_Id, // Deferred GPU texture releases, processed next frame.
|
||||
pending_text_releases: [dynamic]^sdl_ttf.Text, // Deferred TTF_Text destroys, processed next frame.
|
||||
pending_texture_releases: [dynamic]Texture_Id, // Deferred GPU texture releases, processed next frame.
|
||||
pending_text_releases: [dynamic]^sdl_ttf.Text, // Deferred TTF_Text destroys, processed next frame.
|
||||
|
||||
// -- Textures (registration is occasional, binding is per draw call) --
|
||||
texture_slots: [dynamic]Texture_Slot, // Registered texture slots indexed by Texture_Id.
|
||||
texture_free_list: [dynamic]u32, // Recycled slot indices available for reuse.
|
||||
texture_slots: [dynamic]Texture_Slot, // Registered texture slots indexed by Texture_Id.
|
||||
texture_free_list: [dynamic]u32, // Recycled slot indices available for reuse.
|
||||
|
||||
// -- Clay (once per frame in prepare_clay_batch) --
|
||||
clay_memory: [^]u8, // Raw memory block backing Clay's internal arena.
|
||||
clay_memory: [^]u8, // Raw memory block backing Clay's internal arena.
|
||||
|
||||
// -- Text (occasional — font registration and text cache lookups) --
|
||||
text_cache: Text_Cache, // Font registry, SDL_ttf engine, and cached TTF_Text objects.
|
||||
text_cache: Text_Cache, // Font registry, SDL_ttf engine, and cached TTF_Text objects.
|
||||
|
||||
// -- Resize tracking (cold — checked once per frame in resize_global) --
|
||||
max_layers: int, // High-water marks for dynamic array shrink heuristic.
|
||||
max_scissors: int,
|
||||
max_shape_verts: int,
|
||||
max_text_verts: int,
|
||||
max_text_indices: int,
|
||||
max_text_batches: int,
|
||||
max_primitives: int,
|
||||
max_sub_batches: int,
|
||||
max_backdrop_primitives: int,
|
||||
max_layers: int, // High-water marks for dynamic array shrink heuristic.
|
||||
max_scissors: int,
|
||||
max_shape_verts: int,
|
||||
max_text_verts: int,
|
||||
max_text_indices: int,
|
||||
max_text_batches: int,
|
||||
max_primitives: int,
|
||||
max_sub_batches: int,
|
||||
max_gaussian_blur_primitives: int,
|
||||
|
||||
// -- Init-only (coldest — set once at init, never written again) --
|
||||
odin_context: runtime.Context, // Odin context captured at init for use in callbacks.
|
||||
odin_context: runtime.Context, // Odin context captured at init for use in callbacks.
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Core types --------------------
|
||||
// ----- Core types ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// A 2D position in world space. Non-distinct alias for [2]f32 — bare literals like {100, 200}
|
||||
@@ -128,7 +147,7 @@ Vec2 :: [2]f32
|
||||
// transparent. This matches the GPU-side layout: the shader unpacks via unpackUnorm4x8 which
|
||||
// reads the bytes in memory order as R, G, B, A and normalizes each to [0, 1].
|
||||
//
|
||||
// When used in the Base_2D_Primitive or Backdrop_Primitive structs (e.g. .color), the 4 bytes
|
||||
// When used in the Core_2D_Primitive or Gaussian_Blur_Primitive structs (e.g. .color), the 4 bytes
|
||||
// are stored as a u32 in native byte order and unpacked by the shader.
|
||||
Color :: [4]u8
|
||||
|
||||
@@ -139,6 +158,13 @@ GREEN :: Color{0, 255, 0, 255}
|
||||
BLUE :: Color{0, 0, 255, 255}
|
||||
BLANK :: Color{0, 0, 0, 0}
|
||||
|
||||
Rectangle :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
// Per-corner rounding radii for rectangles, specified clockwise from top-left.
|
||||
// All values are in logical pixels (pre-DPI-scaling).
|
||||
Rectangle_Radii :: struct {
|
||||
@@ -201,7 +227,7 @@ color_to_f32 :: proc(color: Color) -> [4]f32 {
|
||||
// Pre-multiply RGB channels by alpha. The tessellated vertex path and text path require
|
||||
// premultiplied colors because the blend state is ONE, ONE_MINUS_SRC_ALPHA and the
|
||||
// tessellated fragment shader passes vertex color through without further modification.
|
||||
// Users who construct Vertex structs manually for prepare_shape must premultiply their colors.
|
||||
// Users who construct Vertex_2D structs manually for prepare_shape must premultiply their colors.
|
||||
premultiply_color :: #force_inline proc(color: Color) -> Color {
|
||||
a := u32(color[3])
|
||||
return Color {
|
||||
@@ -212,22 +238,21 @@ premultiply_color :: #force_inline proc(color: Color) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Frame layout types ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL
|
||||
Sub_Batch_Kind :: enum u8 {
|
||||
Tessellated, // non-indexed, white texture or user texture, base 2D mode 0
|
||||
Text, // indexed, atlas texture, base 2D mode 0
|
||||
SDF, // instanced unit quad, base 2D mode 1
|
||||
// instanced unit quad, backdrop pipeline V-composite (indexes Backdrop_Primitive).
|
||||
Tessellated, // non-indexed, white texture or user texture, Core_2D_Mode.Tessellated
|
||||
Text, // indexed, atlas texture, Core_2D_Mode.Tessellated
|
||||
SDF, // instanced unit quad, Core_2D_Mode.SDF
|
||||
// instanced unit quad, backdrop subsystem V-composite (indexes Gaussian_Blur_Primitive).
|
||||
// Bracket-scheduled per layer; see README.md § "Backdrop pipeline" for ordering semantics.
|
||||
Backdrop,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
Sub_Batch :: struct {
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Tessellated: vertex offset; Text: text_batch index; SDF/Backdrop: primitive index
|
||||
@@ -248,12 +273,17 @@ Layer :: struct {
|
||||
scissor_len: u32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
Scissor :: struct {
|
||||
bounds: sdl.Rect,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_len: u32,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Lifecycle ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Initialize the renderer. Returns false if GPU pipeline or text engine creation fails.
|
||||
//
|
||||
// MSAA is intentionally NOT supported. SDF text and shapes compute coverage analytically via
|
||||
@@ -272,46 +302,56 @@ init :: proc(
|
||||
) {
|
||||
min_memory_size: c.size_t = cast(c.size_t)clay.MinMemorySize()
|
||||
|
||||
pipeline, pipeline_ok := create_pipeline_2d_base(device, window)
|
||||
if !pipeline_ok {
|
||||
core, core_ok := create_core_2d(device, window)
|
||||
if !core_ok {
|
||||
return false
|
||||
}
|
||||
|
||||
backdrop_pipeline, backdrop_pipeline_ok := create_pipeline_2d_backdrop(device, window)
|
||||
if !backdrop_pipeline_ok {
|
||||
destroy_pipeline_2d_base(device, &pipeline)
|
||||
backdrop, backdrop_ok := create_backdrop(device, window)
|
||||
if !backdrop_ok {
|
||||
destroy_core_2d(device, &core)
|
||||
return false
|
||||
}
|
||||
|
||||
text_cache, text_ok := init_text_cache(device, allocator)
|
||||
if !text_ok {
|
||||
destroy_pipeline_2d_backdrop(device, &backdrop_pipeline)
|
||||
destroy_pipeline_2d_base(device, &pipeline)
|
||||
destroy_backdrop(device, &backdrop)
|
||||
destroy_core_2d(device, &core)
|
||||
return false
|
||||
}
|
||||
|
||||
GLOB = Global {
|
||||
layers = make([dynamic]Layer, 0, INITIAL_LAYER_SIZE, allocator = allocator),
|
||||
scissors = make([dynamic]Scissor, 0, INITIAL_SCISSOR_SIZE, allocator = allocator),
|
||||
tmp_shape_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_indices = make([dynamic]c.int, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_batches = make([dynamic]TextBatch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make([dynamic]Base_2D_Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
tmp_backdrop_primitives = make([dynamic]Backdrop_Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
device = device,
|
||||
texture_slots = make([dynamic]Texture_Slot, 0, 16, allocator = allocator),
|
||||
texture_free_list = make([dynamic]u32, 0, 16, allocator = allocator),
|
||||
pending_texture_releases = make([dynamic]Texture_Id, 0, 16, allocator = allocator),
|
||||
pending_text_releases = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
odin_context = odin_context,
|
||||
dpi_scaling = sdl.GetWindowDisplayScale(window),
|
||||
clay_memory = make([^]u8, min_memory_size, allocator = allocator),
|
||||
pipeline_2d_base = pipeline,
|
||||
pipeline_2d_backdrop = backdrop_pipeline,
|
||||
text_cache = text_cache,
|
||||
layers = make([dynamic]Layer, 0, INITIAL_LAYER_SIZE, allocator = allocator),
|
||||
scissors = make([dynamic]Scissor, 0, INITIAL_SCISSOR_SIZE, allocator = allocator),
|
||||
tmp_shape_verts = make([dynamic]Vertex_2D, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_verts = make([dynamic]Vertex_2D, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_indices = make([dynamic]c.int, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_batches = make([dynamic]Text_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make(
|
||||
[dynamic]Core_2D_Primitive,
|
||||
0,
|
||||
BUFFER_INIT_SIZE,
|
||||
allocator = allocator,
|
||||
),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
tmp_gaussian_blur_primitives = make(
|
||||
[dynamic]Gaussian_Blur_Primitive,
|
||||
0,
|
||||
BUFFER_INIT_SIZE,
|
||||
allocator = allocator,
|
||||
),
|
||||
device = device,
|
||||
texture_slots = make([dynamic]Texture_Slot, 0, 16, allocator = allocator),
|
||||
texture_free_list = make([dynamic]u32, 0, 16, allocator = allocator),
|
||||
pending_texture_releases = make([dynamic]Texture_Id, 0, 16, allocator = allocator),
|
||||
pending_text_releases = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
odin_context = odin_context,
|
||||
dpi_scaling = sdl.GetWindowDisplayScale(window),
|
||||
clay_memory = make([^]u8, min_memory_size, allocator = allocator),
|
||||
core_2d = core,
|
||||
backdrop = backdrop,
|
||||
text_cache = text_cache,
|
||||
}
|
||||
|
||||
// Reserve slot 0 for INVALID_TEXTURE
|
||||
@@ -345,8 +385,8 @@ resize_global :: proc() {
|
||||
shrink(&GLOB.tmp_primitives, GLOB.max_primitives)
|
||||
if len(GLOB.tmp_sub_batches) > GLOB.max_sub_batches do GLOB.max_sub_batches = len(GLOB.tmp_sub_batches)
|
||||
shrink(&GLOB.tmp_sub_batches, GLOB.max_sub_batches)
|
||||
if len(GLOB.tmp_backdrop_primitives) > GLOB.max_backdrop_primitives do GLOB.max_backdrop_primitives = len(GLOB.tmp_backdrop_primitives)
|
||||
shrink(&GLOB.tmp_backdrop_primitives, GLOB.max_backdrop_primitives)
|
||||
if len(GLOB.tmp_gaussian_blur_primitives) > GLOB.max_gaussian_blur_primitives do GLOB.max_gaussian_blur_primitives = len(GLOB.tmp_gaussian_blur_primitives)
|
||||
shrink(&GLOB.tmp_gaussian_blur_primitives, GLOB.max_gaussian_blur_primitives)
|
||||
}
|
||||
|
||||
destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
@@ -358,7 +398,7 @@ destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
delete(GLOB.tmp_text_batches)
|
||||
delete(GLOB.tmp_primitives)
|
||||
delete(GLOB.tmp_sub_batches)
|
||||
delete(GLOB.tmp_backdrop_primitives)
|
||||
delete(GLOB.tmp_gaussian_blur_primitives)
|
||||
for ttf_text in GLOB.tmp_uncached_text do sdl_ttf.DestroyText(ttf_text)
|
||||
delete(GLOB.tmp_uncached_text)
|
||||
free(GLOB.clay_memory, allocator)
|
||||
@@ -367,12 +407,12 @@ destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
destroy_sampler_pool()
|
||||
for ttf_text in GLOB.pending_text_releases do sdl_ttf.DestroyText(ttf_text)
|
||||
delete(GLOB.pending_text_releases)
|
||||
destroy_pipeline_2d_backdrop(device, &GLOB.pipeline_2d_backdrop)
|
||||
destroy_pipeline_2d_base(device, &GLOB.pipeline_2d_base)
|
||||
destroy_backdrop(device, &GLOB.backdrop)
|
||||
destroy_core_2d(device, &GLOB.core_2d)
|
||||
destroy_text_cache()
|
||||
}
|
||||
|
||||
// Internal
|
||||
//INTERNAL
|
||||
clear_global :: proc() {
|
||||
// Process deferred texture releases from the previous frame
|
||||
process_pending_texture_releases()
|
||||
@@ -394,33 +434,11 @@ clear_global :: proc() {
|
||||
clear(&GLOB.tmp_text_batches)
|
||||
clear(&GLOB.tmp_primitives)
|
||||
clear(&GLOB.tmp_sub_batches)
|
||||
clear(&GLOB.tmp_backdrop_primitives)
|
||||
clear(&GLOB.tmp_gaussian_blur_primitives)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Text measurement (Clay) -------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@(private = "file")
|
||||
measure_text_clay :: proc "c" (
|
||||
text: clay.StringSlice,
|
||||
config: ^clay.TextElementConfig,
|
||||
user_data: rawptr,
|
||||
) -> clay.Dimensions {
|
||||
context = GLOB.odin_context
|
||||
text := string(text.chars[:text.length])
|
||||
c_text := strings.clone_to_cstring(text, context.temp_allocator)
|
||||
defer delete(c_text, context.temp_allocator)
|
||||
width, height: c.int
|
||||
if !sdl_ttf.GetStringSize(get_font(config.fontId, config.fontSize), c_text, 0, &width, &height) {
|
||||
log.panicf("Failed to measure text: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
return clay.Dimensions{width = f32(width) / GLOB.dpi_scaling, height = f32(height) / GLOB.dpi_scaling}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Frame lifecycle ---------------
|
||||
// ----- Frame ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Sets up renderer to begin upload to the GPU. Returns starting `Layer` to begin processing primitives for.
|
||||
@@ -472,133 +490,89 @@ new_layer :: proc(prev_layer: ^Layer, bounds: Rectangle) -> ^Layer {
|
||||
return &GLOB.layers[GLOB.curr_layer_index]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Built-in primitive processing --
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Submit shape vertices (colored triangles) to the given layer for rendering.
|
||||
// TODO: Should probably be renamed to better match tesselated naming conventions in the library.
|
||||
prepare_shape :: proc(layer: ^Layer, vertices: []Vertex) {
|
||||
if len(vertices) == 0 do return
|
||||
offset := u32(len(GLOB.tmp_shape_verts))
|
||||
append(&GLOB.tmp_shape_verts, ..vertices)
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
append_or_extend_sub_batch(scissor, layer, .Tessellated, offset, u32(len(vertices)))
|
||||
}
|
||||
|
||||
// Submit an SDF primitive to the given layer for rendering.
|
||||
prepare_sdf_primitive :: proc(layer: ^Layer, prim: Base_2D_Primitive) {
|
||||
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)
|
||||
}
|
||||
|
||||
// Submit a text element to the given layer for rendering.
|
||||
// Copies SDL_ttf vertices directly (with baked position) and copies indices for indexed drawing.
|
||||
prepare_text :: proc(layer: ^Layer, text: Text) {
|
||||
data := sdl_ttf.GetGPUTextDrawData(text.sdl_text)
|
||||
if data == nil {
|
||||
return // nil is normal for empty text
|
||||
// Render primitives. clear_color is the background fill before any layers are drawn.
|
||||
end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DFT_CLEAR_COLOR) {
|
||||
cmd_buffer := sdl.AcquireGPUCommandBuffer(device)
|
||||
if cmd_buffer == nil {
|
||||
log.panicf("Failed to acquire GPU command buffer: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
// Pre-scan: if any layer this frame has a backdrop sub-batch, route the entire frame to
|
||||
// source_texture so the bracket can sample the pre-bracket framebuffer without a mid-
|
||||
// frame texture copy. Frames without any backdrop hit the existing fast path and never
|
||||
// touch the backdrop pipeline's working textures.
|
||||
has_backdrop := frame_has_backdrop()
|
||||
|
||||
// Snap base position to integer physical pixels to avoid atlas sub-pixel
|
||||
// sampling blur (and the off-by-one bottom-row clip that comes with it).
|
||||
base_x := math.round(text.position[0] * GLOB.dpi_scaling)
|
||||
base_y := math.round(text.position[1] * GLOB.dpi_scaling)
|
||||
// Upload primitives to GPU (vertices, indices, SDF prims, and backdrop prims share one
|
||||
// copy pass so we pay the BeginGPUCopyPass / EndGPUCopyPass cost once per frame).
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
upload(device, copy_pass)
|
||||
if has_backdrop {
|
||||
upload_backdrop_primitives(device, copy_pass)
|
||||
}
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
|
||||
// Premultiply text color once — reused across all glyph vertices.
|
||||
pm_color := premultiply_color(text.color)
|
||||
swapchain_texture: ^sdl.GPUTexture
|
||||
width, height: u32
|
||||
if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &width, &height) {
|
||||
log.panicf("Failed to acquire swapchain texture: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
for data != nil {
|
||||
vertex_start := u32(len(GLOB.tmp_text_verts))
|
||||
index_start := u32(len(GLOB.tmp_text_indices))
|
||||
|
||||
// Copy vertices with baked position offset
|
||||
for i in 0 ..< data.num_vertices {
|
||||
pos := data.xy[i]
|
||||
uv := data.uv[i]
|
||||
append(
|
||||
&GLOB.tmp_text_verts,
|
||||
Vertex{position = {pos.x + base_x, -pos.y + base_y}, uv = {uv.x, uv.y}, color = pm_color},
|
||||
)
|
||||
if swapchain_texture == nil {
|
||||
// Window is minimized or not visible — submit and skip this frame
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd_buffer) {
|
||||
log.panicf("Failed to submit GPU command buffer (minimized window): %s", sdl.GetError())
|
||||
}
|
||||
|
||||
// Copy indices directly
|
||||
append(&GLOB.tmp_text_indices, ..data.indices[:data.num_indices])
|
||||
|
||||
batch_idx := u32(len(GLOB.tmp_text_batches))
|
||||
append(
|
||||
&GLOB.tmp_text_batches,
|
||||
TextBatch {
|
||||
atlas_texture = data.atlas_texture,
|
||||
vertex_start = vertex_start,
|
||||
vertex_count = u32(data.num_vertices),
|
||||
index_start = index_start,
|
||||
index_count = u32(data.num_indices),
|
||||
},
|
||||
)
|
||||
|
||||
// Each atlas chunk is a separate sub-batch (different atlas textures can't coalesce)
|
||||
append_or_extend_sub_batch(scissor, layer, .Text, batch_idx, 1)
|
||||
|
||||
data = data.next
|
||||
}
|
||||
}
|
||||
|
||||
// Submit a text element with a 2D affine transform applied to vertices.
|
||||
// Used by the high-level `text` proc when rotation or a non-zero origin is specified.
|
||||
// NOTE: xform must be in physical (DPI-scaled) pixel space — the caller pre-scales
|
||||
// pos and origin by GLOB.dpi_scaling before building the transform.
|
||||
prepare_text_transformed :: proc(layer: ^Layer, text: Text, transform: Transform_2D) {
|
||||
data := sdl_ttf.GetGPUTextDrawData(text.sdl_text)
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
render_texture := swapchain_texture
|
||||
if has_backdrop {
|
||||
ensure_backdrop_textures(device, sdl.GetGPUSwapchainTextureFormat(device, window), width, height)
|
||||
render_texture = GLOB.backdrop.source_texture
|
||||
}
|
||||
|
||||
// Premultiply text color once — reused across all glyph vertices.
|
||||
pm_color := premultiply_color(text.color)
|
||||
// Premultiply clear color: the blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied),
|
||||
// so the clear color must also be premultiplied for correct background compositing.
|
||||
clear_color_straight := color_to_f32(clear_color)
|
||||
clear_alpha := clear_color_straight[3]
|
||||
clear_color_f32 := [4]f32 {
|
||||
clear_color_straight[0] * clear_alpha,
|
||||
clear_color_straight[1] * clear_alpha,
|
||||
clear_color_straight[2] * clear_alpha,
|
||||
clear_alpha,
|
||||
}
|
||||
|
||||
for data != nil {
|
||||
vertex_start := u32(len(GLOB.tmp_text_verts))
|
||||
index_start := u32(len(GLOB.tmp_text_indices))
|
||||
// Draw layers. One render pass per layer; sub-batches draw in submission order within each scissor.
|
||||
for &layer, index in GLOB.layers {
|
||||
draw_layer(device, window, cmd_buffer, render_texture, width, height, clear_color_f32, &layer)
|
||||
}
|
||||
|
||||
for i in 0 ..< data.num_vertices {
|
||||
pos := data.xy[i]
|
||||
uv := data.uv[i]
|
||||
// SDL_ttf gives glyph positions in physical pixels relative to text origin.
|
||||
// The transform is already in physical-pixel space (caller pre-scaled),
|
||||
// so we apply directly — no per-vertex DPI divide/multiply.
|
||||
append(
|
||||
&GLOB.tmp_text_verts,
|
||||
Vertex{position = apply_transform(transform, {pos.x, -pos.y}), uv = {uv.x, uv.y}, color = pm_color},
|
||||
)
|
||||
}
|
||||
|
||||
append(&GLOB.tmp_text_indices, ..data.indices[:data.num_indices])
|
||||
|
||||
batch_idx := u32(len(GLOB.tmp_text_batches))
|
||||
append(
|
||||
&GLOB.tmp_text_batches,
|
||||
TextBatch {
|
||||
atlas_texture = data.atlas_texture,
|
||||
vertex_start = vertex_start,
|
||||
vertex_count = u32(data.num_vertices),
|
||||
index_start = index_start,
|
||||
index_count = u32(data.num_indices),
|
||||
},
|
||||
// When we rendered into source_texture, copy it to the swapchain. Single
|
||||
// CopyGPUTextureToTexture call per frame, only when backdrop content was present.
|
||||
if has_backdrop {
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
sdl.CopyGPUTextureToTexture(
|
||||
copy_pass,
|
||||
sdl.GPUTextureLocation{texture = GLOB.backdrop.source_texture},
|
||||
sdl.GPUTextureLocation{texture = swapchain_texture},
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
false,
|
||||
)
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
}
|
||||
|
||||
append_or_extend_sub_batch(scissor, layer, .Text, batch_idx, 1)
|
||||
|
||||
data = data.next
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd_buffer) {
|
||||
log.panicf("Failed to submit GPU command buffer: %s", sdl.GetError())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Sub-batch dispatch ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Append a new sub-batch or extend the last one if same kind and contiguous.
|
||||
//
|
||||
// `gaussian_sigma` is only consulted for kind == .Backdrop; two .Backdrop sub-batches with
|
||||
@@ -606,7 +580,7 @@ prepare_text_transformed :: proc(layer: ^Layer, text: Text, transform: Transform
|
||||
// bracket scheduler. Float equality is intentional — user-supplied literal sigmas (e.g.
|
||||
// `sigma = 12`) produce bit-identical floats, and the worst case for two sigmas that differ
|
||||
// only by a ulp is one extra pass pair (correct, just slightly suboptimal).
|
||||
@(private)
|
||||
//INTERNAL
|
||||
append_or_extend_sub_batch :: proc(
|
||||
scissor: ^Scissor,
|
||||
layer: ^Layer,
|
||||
@@ -645,7 +619,7 @@ append_or_extend_sub_batch :: proc(
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Clay ------------------------
|
||||
// ----- Clay ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@(private = "file")
|
||||
@@ -654,6 +628,24 @@ clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
|
||||
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
measure_text_clay :: proc "c" (
|
||||
text: clay.StringSlice,
|
||||
config: ^clay.TextElementConfig,
|
||||
user_data: rawptr,
|
||||
) -> clay.Dimensions {
|
||||
context = GLOB.odin_context
|
||||
text := string(text.chars[:text.length])
|
||||
c_text := strings.clone_to_cstring(text, context.temp_allocator)
|
||||
defer delete(c_text, context.temp_allocator)
|
||||
width, height: c.int
|
||||
if !sdl_ttf.GetStringSize(get_font(config.fontId, config.fontSize), c_text, 0, &width, &height) {
|
||||
log.panicf("Failed to measure text: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
return clay.Dimensions{width = f32(width) / GLOB.dpi_scaling, height = f32(height) / GLOB.dpi_scaling}
|
||||
}
|
||||
|
||||
// Called for each Clay `RenderCommandType.Custom` render command that
|
||||
// `prepare_clay_batch` encounters.
|
||||
//
|
||||
@@ -822,142 +814,18 @@ prepare_clay_batch :: proc(
|
||||
}
|
||||
}
|
||||
|
||||
// Render primitives. clear_color is the background fill before any layers are drawn.
|
||||
end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DFT_CLEAR_COLOR) {
|
||||
cmd_buffer := sdl.AcquireGPUCommandBuffer(device)
|
||||
if cmd_buffer == nil {
|
||||
log.panicf("Failed to acquire GPU command buffer: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
// Pre-scan: if any layer this frame has a backdrop sub-batch, route the entire frame to
|
||||
// source_texture so the bracket can sample the pre-bracket framebuffer without a mid-
|
||||
// frame texture copy. Frames without any backdrop hit the existing fast path and never
|
||||
// touch the backdrop pipeline's working textures.
|
||||
has_backdrop := frame_has_backdrop()
|
||||
|
||||
// Upload primitives to GPU (vertices, indices, SDF prims, and backdrop prims share one
|
||||
// copy pass so we pay the BeginGPUCopyPass / EndGPUCopyPass cost once per frame).
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
upload(device, copy_pass)
|
||||
if has_backdrop {
|
||||
upload_backdrop_primitives(device, copy_pass)
|
||||
}
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
|
||||
swapchain_texture: ^sdl.GPUTexture
|
||||
width, height: u32
|
||||
if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &width, &height) {
|
||||
log.panicf("Failed to acquire swapchain texture: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
if swapchain_texture == nil {
|
||||
// Window is minimized or not visible — submit and skip this frame
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd_buffer) {
|
||||
log.panicf("Failed to submit GPU command buffer (minimized window): %s", sdl.GetError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
render_texture := swapchain_texture
|
||||
if has_backdrop {
|
||||
ensure_backdrop_textures(device, sdl.GetGPUSwapchainTextureFormat(device, window), width, height)
|
||||
render_texture = GLOB.pipeline_2d_backdrop.source_texture
|
||||
}
|
||||
|
||||
// Premultiply clear color: the blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied),
|
||||
// so the clear color must also be premultiplied for correct background compositing.
|
||||
clear_color_straight := color_to_f32(clear_color)
|
||||
clear_alpha := clear_color_straight[3]
|
||||
clear_color_f32 := [4]f32 {
|
||||
clear_color_straight[0] * clear_alpha,
|
||||
clear_color_straight[1] * clear_alpha,
|
||||
clear_color_straight[2] * clear_alpha,
|
||||
clear_alpha,
|
||||
}
|
||||
|
||||
// Draw layers. One render pass per layer; sub-batches draw in submission order within each scissor.
|
||||
for &layer, index in GLOB.layers {
|
||||
draw_layer(device, window, cmd_buffer, render_texture, width, height, clear_color_f32, &layer)
|
||||
}
|
||||
|
||||
// When we rendered into source_texture, copy it to the swapchain. Single
|
||||
// CopyGPUTextureToTexture call per frame, only when backdrop content was present.
|
||||
if has_backdrop {
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
sdl.CopyGPUTextureToTexture(
|
||||
copy_pass,
|
||||
sdl.GPUTextureLocation{texture = GLOB.pipeline_2d_backdrop.source_texture},
|
||||
sdl.GPUTextureLocation{texture = swapchain_texture},
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
false,
|
||||
)
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
}
|
||||
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd_buffer) {
|
||||
log.panicf("Failed to submit GPU command buffer: %s", sdl.GetError())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Utility -----------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
ortho_rh :: proc(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> matrix[4, 4]f32 {
|
||||
return matrix[4, 4]f32{
|
||||
2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left),
|
||||
0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom),
|
||||
0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near),
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
Draw_Mode :: enum u32 {
|
||||
Tessellated = 0,
|
||||
SDF = 1,
|
||||
}
|
||||
|
||||
Vertex_Uniforms :: struct {
|
||||
projection: matrix[4, 4]f32,
|
||||
scale: f32,
|
||||
mode: Draw_Mode,
|
||||
}
|
||||
|
||||
// Push projection, dpi scale, and rendering mode as a single uniform block (slot 0).
|
||||
push_globals :: proc(
|
||||
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||
width: f32,
|
||||
height: f32,
|
||||
mode: Draw_Mode = .Tessellated,
|
||||
) {
|
||||
globals := Vertex_Uniforms {
|
||||
projection = ortho_rh(
|
||||
left = 0.0,
|
||||
top = 0.0,
|
||||
right = f32(width),
|
||||
bottom = f32(height),
|
||||
near = -1.0,
|
||||
far = 1.0,
|
||||
),
|
||||
scale = GLOB.dpi_scaling,
|
||||
mode = mode,
|
||||
}
|
||||
|
||||
sdl.PushGPUVertexUniformData(cmd_buffer, 0, &globals, size_of(Vertex_Uniforms))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Buffer ------------------------
|
||||
// ----- Buffer ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL
|
||||
Buffer :: struct {
|
||||
gpu: ^sdl.GPUBuffer,
|
||||
transfer: ^sdl.GPUTransferBuffer,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
@(require_results)
|
||||
create_buffer :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
@@ -984,6 +852,7 @@ create_buffer :: proc(
|
||||
return Buffer{gpu, transfer, size}, true
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
grow_buffer_if_needed :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
buffer: ^Buffer,
|
||||
@@ -1008,15 +877,26 @@ grow_buffer_if_needed :: proc(
|
||||
}
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
destroy_buffer :: proc(device: ^sdl.GPUDevice, buffer: ^Buffer) {
|
||||
sdl.ReleaseGPUBuffer(device, buffer.gpu)
|
||||
sdl.ReleaseGPUTransferBuffer(device, buffer.transfer)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Transform ------------------------
|
||||
// ----- Math ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//INTERNAL
|
||||
ortho_rh :: proc(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> matrix[4, 4]f32 {
|
||||
return matrix[4, 4]f32{
|
||||
2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left),
|
||||
0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom),
|
||||
0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near),
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// 2x3 affine transform for 2D pivot-rotation.
|
||||
// Used internally by rotation-aware drawing procs.
|
||||
Transform_2D :: struct {
|
||||
@@ -1078,9 +958,114 @@ needs_transform :: #force_inline proc(origin: Vec2, rotation: f32) -> bool {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Procedure Groups ------------------------
|
||||
// ----- Anchors ------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Return Vec2 pixel offsets for use as the `origin` parameter of draw calls.
|
||||
// Composable with normal vector +/- arithmetic.
|
||||
//
|
||||
// Text anchor helpers are in text.odin (they depend on measure_text / SDL_ttf).
|
||||
|
||||
// Returns uniform radii (all corners the same) as a fraction of the shorter side.
|
||||
// `roundness` is clamped to [0, 1]; 0 = sharp corners, 1 = fully rounded (stadium or circle).
|
||||
uniform_radii :: #force_inline proc(rect: Rectangle, roundness: f32) -> Rectangle_Radii {
|
||||
cr := min(rect.width, rect.height) * clamp(roundness, 0, 1) * 0.5
|
||||
return {cr, cr, cr, cr}
|
||||
}
|
||||
|
||||
//----- Rectangle anchors (origin measured from rectangle's top-left) ----------------------------------
|
||||
|
||||
center_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width * 0.5, rectangle.height * 0.5}
|
||||
}
|
||||
|
||||
top_left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {0, 0}
|
||||
}
|
||||
|
||||
top_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width * 0.5, 0}
|
||||
}
|
||||
|
||||
top_right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width, 0}
|
||||
}
|
||||
|
||||
left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {0, rectangle.height * 0.5}
|
||||
}
|
||||
|
||||
right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width, rectangle.height * 0.5}
|
||||
}
|
||||
|
||||
bottom_left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {0, rectangle.height}
|
||||
}
|
||||
|
||||
bottom_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width * 0.5, rectangle.height}
|
||||
}
|
||||
|
||||
bottom_right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 {
|
||||
return {rectangle.width, rectangle.height}
|
||||
}
|
||||
|
||||
//----- Triangle anchors (origin measured from AABB top-left) ----------------------------------
|
||||
|
||||
center_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
|
||||
return (v1 + v2 + v3) / 3 - bounds_min
|
||||
}
|
||||
|
||||
top_left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
return {0, 0}
|
||||
}
|
||||
|
||||
top_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
min_x := min(v1.x, v2.x, v3.x)
|
||||
max_x := max(v1.x, v2.x, v3.x)
|
||||
return {(max_x - min_x) * 0.5, 0}
|
||||
}
|
||||
|
||||
top_right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
min_x := min(v1.x, v2.x, v3.x)
|
||||
max_x := max(v1.x, v2.x, v3.x)
|
||||
return {max_x - min_x, 0}
|
||||
}
|
||||
|
||||
left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
min_y := min(v1.y, v2.y, v3.y)
|
||||
max_y := max(v1.y, v2.y, v3.y)
|
||||
return {0, (max_y - min_y) * 0.5}
|
||||
}
|
||||
|
||||
right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
|
||||
bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)}
|
||||
return {bounds_max.x - bounds_min.x, (bounds_max.y - bounds_min.y) * 0.5}
|
||||
}
|
||||
|
||||
bottom_left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
min_y := min(v1.y, v2.y, v3.y)
|
||||
max_y := max(v1.y, v2.y, v3.y)
|
||||
return {0, max_y - min_y}
|
||||
}
|
||||
|
||||
bottom_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
|
||||
bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)}
|
||||
return {(bounds_max.x - bounds_min.x) * 0.5, bounds_max.y - bounds_min.y}
|
||||
}
|
||||
|
||||
bottom_right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 {
|
||||
bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
|
||||
bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)}
|
||||
return bounds_max - bounds_min
|
||||
}
|
||||
|
||||
//----- Procedure groups ----------------------------------
|
||||
|
||||
center_of :: proc {
|
||||
center_of_rectangle,
|
||||
center_of_triangle,
|
||||
|
||||
Reference in New Issue
Block a user