From f11f688ea06636fef9f2364dd2179b132b705f68 Mon Sep 17 00:00:00 2001 From: shan Date: Mon, 7 Jul 2025 15:18:29 -0700 Subject: [PATCH 1/3] Update clay rendering to be able to be rendered to a different layer, and at its own position --- {library/clay => clay}/clay.odin | 0 {library/clay => clay}/linux-arm64/clay.a | Bin {library/clay => clay}/linux/clay.a | Bin {library/clay => clay}/macos-arm64/clay.a | Bin {library/clay => clay}/macos/clay.a | Bin {library/clay => clay}/wasm/clay.o | Bin {library/clay => clay}/windows/clay.lib | Bin examples/main.odin | 46 ++++++- renderer/quad.odin | 1 - renderer/renderer.odin | 161 ++++++++++++++++------ 10 files changed, 160 insertions(+), 48 deletions(-) rename {library/clay => clay}/clay.odin (100%) rename {library/clay => clay}/linux-arm64/clay.a (100%) rename {library/clay => clay}/linux/clay.a (100%) rename {library/clay => clay}/macos-arm64/clay.a (100%) rename {library/clay => clay}/macos/clay.a (100%) rename {library/clay => clay}/wasm/clay.o (100%) rename {library/clay => clay}/windows/clay.lib (100%) diff --git a/library/clay/clay.odin b/clay/clay.odin similarity index 100% rename from library/clay/clay.odin rename to clay/clay.odin diff --git a/library/clay/linux-arm64/clay.a b/clay/linux-arm64/clay.a similarity index 100% rename from library/clay/linux-arm64/clay.a rename to clay/linux-arm64/clay.a diff --git a/library/clay/linux/clay.a b/clay/linux/clay.a similarity index 100% rename from library/clay/linux/clay.a rename to clay/linux/clay.a diff --git a/library/clay/macos-arm64/clay.a b/clay/macos-arm64/clay.a similarity index 100% rename from library/clay/macos-arm64/clay.a rename to clay/macos-arm64/clay.a diff --git a/library/clay/macos/clay.a b/clay/macos/clay.a similarity index 100% rename from library/clay/macos/clay.a rename to clay/macos/clay.a diff --git a/library/clay/wasm/clay.o b/clay/wasm/clay.o similarity index 100% rename from library/clay/wasm/clay.o rename to clay/wasm/clay.o diff --git a/library/clay/windows/clay.lib b/clay/windows/clay.lib similarity index 100% rename from library/clay/windows/clay.lib rename to clay/windows/clay.lib diff --git a/examples/main.odin b/examples/main.odin index 988c959..b33d8d2 100644 --- a/examples/main.odin +++ b/examples/main.odin @@ -1,12 +1,12 @@ package main +import clay "../clay" import "../renderer" import "core:c" import "core:fmt" import "core:log" import "core:mem" import "core:os" -import clay "library:clay" import sdl "vendor:sdl3" WINDOW_WIDTH :: 1024 @@ -165,8 +165,43 @@ destroy :: proc() { update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool { frame_time := f32(delta_time) / 1000.0 input := input() - render_cmds := layout() - renderer.prepare(device, window, cmd_buffer, &render_cmds, input.mouse_delta, frame_time) + mouse_x, mouse_y: f32 + mouse_flags := sdl.GetMouseState(&mouse_x, &mouse_y) + width, height: c.int + sdl.GetWindowSize(window, &width, &height) + + layer := renderer.begin_prepare() + // ===== Begin processing primitives for GPU upload ===== + // Everything after begin_prepare() is uploaded in-order. We pass the layer down + // until we need a new one, after which we call new_layer() + + // Render raw primitive setup + //renderer.prepare_batch(device, window, cmd_buffer, &layer, &primitives) //TODO + + clay_layer_bounds := renderer.Rectangle { + x = f32(width) / 2.0, + y = 0.0, + w = f32(width) / 2.0, + h = f32(height), + } + // Create a new layer, because these two scenes cannot be renderer in the same batch due to overlap + layer = renderer.new_layer(&layer, clay_layer_bounds) + clay_batch := clay_layout(clay_layer_bounds) + renderer.prepare_clay_batch( + device, + window, + cmd_buffer, + &layer, + {mouse_x, mouse_y}, + mouse_flags, + input.mouse_delta, + frame_time, + &clay_batch, + ) + + // This uploads the primitive data to the GPU + renderer.end_prepare(device, cmd_buffer, &layer) + return input.should_quit } @@ -210,7 +245,8 @@ draw :: proc(cmd_buffer: ^sdl.GPUCommandBuffer) { } } -layout :: proc() -> clay.ClayArray(clay.RenderCommand) { +clay_layout :: proc(bounds: renderer.Rectangle) -> renderer.ClayBatch { + clay.SetLayoutDimensions(clay.Dimensions{bounds.w, bounds.h}) clay.BeginLayout() if clay.UI()( @@ -247,5 +283,5 @@ layout :: proc() -> clay.ClayArray(clay.RenderCommand) { clay.Text("Test Text", &body_text) } - return clay.EndLayout() + return renderer.ClayBatch{bounds, clay.EndLayout()} } diff --git a/renderer/quad.odin b/renderer/quad.odin index 5baf4b9..05e2871 100644 --- a/renderer/quad.odin +++ b/renderer/quad.odin @@ -37,7 +37,6 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua log.debug("Loaded", len(frag_raw), "frag bytes") log.debug("ShaderType:", SHADER_TYPE) - vert_info := sdl.GPUShaderCreateInfo { code_size = len(vert_raw), code = raw_data(vert_raw), diff --git a/renderer/renderer.odin b/renderer/renderer.odin index 16a7937..6bb6572 100644 --- a/renderer/renderer.odin +++ b/renderer/renderer.odin @@ -1,11 +1,11 @@ package renderer +import clay "../clay" import "base:runtime" import "core:c" import "core:log" import "core:os" import "core:strings" -import clay "library:clay" import sdl "vendor:sdl3" import sdl_ttf "vendor:sdl3/ttf" @@ -25,8 +25,35 @@ quad_pipeline: QuadPipeline text_pipeline: TextPipeline odin_context: runtime.Context -// TODO New layer for each z-index/batch +// I need to make it so that I can +// a) Add primitives directly to a layer +// b) Create small nested clay-layouts that can be batched with other shit +// Some colletion of items, where its just primtiives, but collections of primitives? And each collection has a type, +// either raw or clay? Or something + +// Prepare - upload to GPU +// With clay commands, this requires converting to my custom primitive types +// With raw commands, this would require no conversion +// Need to go from UI declaration -> processing render cmds + +// I want to process & upload in the same loop, don't want to add an additional pass where I transform +// clay cmds -> my primitives + +// 1) I need to be able to process all clay cmds & all raw primitive cmds in a single iteration +// 2) I need to be able to define (at a layout level) which primitives can be rendered in the same batch +// +// Some kind of queue? Each UI element adds to a temp queue that is reset every frame +// Queue is like +// [ chunk of primitives ] [ chunk of clay primitives ] [ new layer indicator ] [ chunk ] +Rectangle :: struct { + x: f32, + y: f32, + w: f32, + h: f32, +} + Layer :: struct { + bounds: Rectangle, quad_instance_start: u32, quad_len: u32, text_instance_start: u32, @@ -35,6 +62,7 @@ Layer :: struct { text_vertex_len: u32, text_index_start: u32, text_index_len: u32, + curr_scissor_index: u32, scissors: [dynamic]Scissor, } @@ -68,6 +96,7 @@ init :: proc( text_pipeline = create_text_pipeline(device, window) } +@(private = "file") clay_error_handler :: proc "c" (errorData: clay.ErrorData) { context = odin_context log.error("Clay error:", errorData.errorType, errorData.errorText) @@ -95,26 +124,9 @@ destroy :: proc(device: ^sdl.GPUDevice) { destroy_text_pipeline(device) } -/// Upload data to the GPU -prepare :: proc( - device: ^sdl.GPUDevice, - window: ^sdl.Window, - cmd_buffer: ^sdl.GPUCommandBuffer, - render_commands: ^clay.ClayArray(clay.RenderCommand), - mouse_delta: [2]f32, - frame_time: f32, -) { - mouse_x, mouse_y: f32 - mouse_flags := sdl.GetMouseState(&mouse_x, &mouse_y) - // Currently MacOS blocks main thread when resizing, this will be fixed with next SDL3 release - window_w, window_h: c.int - window_size := sdl.GetWindowSize(window, &window_w, &window_h) - - // Update clay internals - clay.SetPointerState(clay.Vector2{mouse_x, mouse_y}, .LEFT in mouse_flags) - clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_delta, frame_time) - clay.SetLayoutDimensions({f32(window_w), f32(window_h)}) - +/// Sets up renderer to begin upload to the GPU. Returns starting `Layer` to begin processing primitives for +begin_prepare :: proc() -> Layer { + // Prepare to upload to GPU clear(&layers) clear(&tmp_quads) clear(&tmp_text) @@ -125,12 +137,82 @@ prepare :: proc( layer := Layer { scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator), } + + return layer +} + +/// Creates a new layer, appending the old one to `layers` +new_layer :: proc(layer: ^Layer, bounds: Rectangle) -> Layer { + append(&layers, layer^) + layer := Layer { + bounds = bounds, + scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator), + } + + return layer +} + +end_prepare :: proc(device: ^sdl.GPUDevice, cmd_buffer: ^sdl.GPUCommandBuffer, layer: ^Layer) { + // Commit last layer worked on + append(&layers, layer^) + + // Upload primitives to GPU + copy_pass := sdl.BeginGPUCopyPass(cmd_buffer) + upload_quads(device, copy_pass) + upload_text(device, copy_pass) + sdl.EndGPUCopyPass(copy_pass) +} + +// ===== Built-in primitive processing ===== +//prepare_batch :: proc( +// device: ^sdl.GPUDevice, +// window: ^sdl.Window, +// cmd_buffer: ^sdl.GPUCommandBuffer, +// layer: ^Layer, +// primitives: ^[]Primitive, +//) { +// scissor := Scissor{} +// +// +//} + +// ====== Clay-specific processing ====== +ClayBatch :: struct { + bounds: Rectangle, + cmds: clay.ClayArray(clay.RenderCommand), +} + +/// Upload data to the GPU +prepare_clay_batch :: proc( + device: ^sdl.GPUDevice, + window: ^sdl.Window, + cmd_buffer: ^sdl.GPUCommandBuffer, + layer: ^Layer, + mouse_pos: [2]f32, + mouse_flags: sdl.MouseButtonFlags, + mouse_wheel_delta: [2]f32, + frame_time: f32, + batch: ^ClayBatch, +) { + // Update clay internals + clay.SetPointerState( + clay.Vector2{mouse_pos.x - layer.bounds.x, mouse_pos.y - layer.bounds.y}, + .LEFT in mouse_flags, + ) + clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_wheel_delta, frame_time) + scissor := Scissor{} // Parse render commands - for i in 0 ..< int(render_commands.length) { - render_command := clay.RenderCommandArray_Get(render_commands, cast(i32)i) - bounds := render_command.boundingBox + for i in 0 ..< int(batch.cmds.length) { + render_command := clay.RenderCommandArray_Get(&batch.cmds, cast(i32)i) + // Translate bounding box of the primitive by the layer position + bounds := Rectangle { + x = render_command.boundingBox.x + layer.bounds.x, + y = render_command.boundingBox.y + layer.bounds.y, + w = render_command.boundingBox.width, + h = render_command.boundingBox.height, + } switch (render_command.commandType) { case clay.RenderCommandType.None: @@ -173,27 +255,28 @@ prepare :: proc( bounds := sdl.Rect { c.int(bounds.x * dpi_scaling), c.int(bounds.y * dpi_scaling), - c.int(bounds.width * dpi_scaling), - c.int(bounds.height * dpi_scaling), + c.int(bounds.w * dpi_scaling), + c.int(bounds.h * dpi_scaling), } - new := new_scissor(&scissor) if scissor.quad_len != 0 || scissor.text_len != 0 { + new := new_scissor(&scissor) append(&layer.scissors, scissor) + scissor = new } - scissor = new + scissor.bounds = bounds case clay.RenderCommandType.ScissorEnd: - new := new_scissor(&scissor) if scissor.quad_len != 0 || scissor.text_len != 0 { + new := new_scissor(&scissor) append(&layer.scissors, scissor) + scissor = new } - scissor = new case clay.RenderCommandType.Rectangle: render_data := render_command.renderData.rectangle color := f32_color(render_data.backgroundColor) cr := render_data.cornerRadius quad := Quad { - position_scale = {bounds.x, bounds.y, bounds.width, bounds.height}, + position_scale = {bounds.x, bounds.y, bounds.w, bounds.h}, corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft}, color = color, } @@ -205,7 +288,7 @@ prepare :: proc( cr := render_data.cornerRadius //TODO dedicated border pipeline quad := Quad { - position_scale = {bounds.x, bounds.y, bounds.width, bounds.height}, + position_scale = {bounds.x, bounds.y, bounds.w, bounds.h}, corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft}, color = f32_color(clay.Color{0.0, 0.0, 0.0, 0.0}), border_color = f32_color(render_data.color), @@ -221,15 +304,9 @@ prepare :: proc( } } - //TODO start new layers with z-index changes - append(&layer.scissors, scissor) - append(&layers, layer) - - // Upload primitives to GPU - copy_pass := sdl.BeginGPUCopyPass(cmd_buffer) - upload_quads(device, copy_pass) - upload_text(device, copy_pass) - sdl.EndGPUCopyPass(copy_pass) + if scissor.quad_len != 0 || scissor.text_len != 0 { + append(&layer.scissors, scissor) + } } /// Render primitives From b0fe8f0d29a1d5e04b25fe48daa888078b7404ee Mon Sep 17 00:00:00 2001 From: shan Date: Tue, 22 Jul 2025 20:59:52 -0700 Subject: [PATCH 2/3] Add proper layering support and support for non-clay layouts --- examples/main.odin | 63 ++++++++-- renderer/quad.odin | 59 +++++---- renderer/renderer.odin | 269 +++++++++++++++++++++++++---------------- renderer/text.odin | 30 +++-- 4 files changed, 268 insertions(+), 153 deletions(-) diff --git a/examples/main.odin b/examples/main.odin index b33d8d2..c4d4969 100644 --- a/examples/main.odin +++ b/examples/main.odin @@ -24,8 +24,6 @@ body_text := clay.TextElementConfig { } main :: proc() { - defer destroy() - when ODIN_DEBUG == true { context.logger = log.create_console_logger(lowest = .Debug) @@ -153,9 +151,12 @@ main :: proc() { last_frame_time = frame_time } + + destroy() } destroy :: proc() { + free_all(context.temp_allocator) renderer.destroy(device) sdl.ReleaseWindowFromGPUDevice(device, window) sdl.DestroyWindow(window) @@ -169,15 +170,22 @@ update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool { mouse_flags := sdl.GetMouseState(&mouse_x, &mouse_y) width, height: c.int sdl.GetWindowSize(window, &width, &height) + window_bounds := renderer.Rectangle { + x = 0.0, + y = 0.0, + w = f32(width), + h = f32(height), + } - layer := renderer.begin_prepare() + layer := renderer.begin_prepare(window_bounds) // ===== Begin processing primitives for GPU upload ===== // Everything after begin_prepare() is uploaded in-order. We pass the layer down // until we need a new one, after which we call new_layer() - // Render raw primitive setup - //renderer.prepare_batch(device, window, cmd_buffer, &layer, &primitives) //TODO + // Process primitives on this layer + layout(layer) + // Process clay-specific primitives clay_layer_bounds := renderer.Rectangle { x = f32(width) / 2.0, y = 0.0, @@ -185,13 +193,10 @@ update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool { h = f32(height), } // Create a new layer, because these two scenes cannot be renderer in the same batch due to overlap - layer = renderer.new_layer(&layer, clay_layer_bounds) + layer = renderer.new_layer(layer, clay_layer_bounds) clay_batch := clay_layout(clay_layer_bounds) renderer.prepare_clay_batch( - device, - window, - cmd_buffer, - &layer, + layer, {mouse_x, mouse_y}, mouse_flags, input.mouse_delta, @@ -200,7 +205,7 @@ update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool { ) // This uploads the primitive data to the GPU - renderer.end_prepare(device, cmd_buffer, &layer) + renderer.end_prepare(device, cmd_buffer) return input.should_quit } @@ -258,7 +263,7 @@ clay_layout :: proc(bounds: renderer.Rectangle) -> renderer.ClayBatch { childAlignment = {x = .Center, y = .Center}, childGap = 32, }, - backgroundColor = {200.0, 200.0, 200.0, 255.0}, + backgroundColor = {200.0, 200.0, 200.0, 100.0}, }, ) { if clay.UI()( @@ -280,8 +285,42 @@ clay_layout :: proc(bounds: renderer.Rectangle) -> renderer.ClayBatch { ) { } + if clay.UI()( + { + id = clay.ID("RoundedRect2"), + backgroundColor = {255.0, 100.0, 100.0, 255.0}, + cornerRadius = clay.CornerRadius { + topLeft = 10, + topRight = 20, + bottomLeft = 40, + bottomRight = 0, + }, + border = clay.BorderElementConfig { + color = {0.0, 0.0, 0.0, 255.0}, + width = clay.BorderAll(5), + }, + layout = {sizing = {clay.SizingFixed(240), clay.SizingFixed(80)}}, + }, + ) { + } + clay.Text("Test Text", &body_text) } return renderer.ClayBatch{bounds, clay.EndLayout()} } + +layout :: proc(layer: ^renderer.Layer) { + bounds := layer.bounds + + test_quad := renderer.quad( + pos = {bounds.x + 200, bounds.y + 200}, + size = {bounds.w / 2.0, bounds.h / 2.0}, + color = {0.2, 0.2, 0.8, 1}, + corner_radii = {5, 10, 0, 20}, + border_color = {0, 0, 0, 1}, + border_width = 10, + ) + + renderer.prepare_quad(layer, test_quad) +} diff --git a/renderer/quad.odin b/renderer/quad.odin index 05e2871..1ad7033 100644 --- a/renderer/quad.odin +++ b/renderer/quad.odin @@ -5,8 +5,6 @@ import "core:mem" import "core:os" import sdl "vendor:sdl3" -tmp_quads: [dynamic]Quad - QuadPipeline :: struct { instance_buffer: Buffer, num_instances: u32, @@ -14,12 +12,30 @@ QuadPipeline :: struct { } Quad :: struct { - position_scale: [4]f32, - corner_radii: [4]f32, - color: [4]f32, - border_color: [4]f32, - border_width: f32, - _: [3]f32, + position_scale: [4]f32, + corner_radii: [4]f32, + color: [4]f32, + border_color: [4]f32, + border_width: f32, + _: [3]f32, +} + +quad :: proc( + pos: [2]f32, + size: [2]f32, + color: [4]f32, + corner_radii := [4]f32{0, 0, 0, 0}, + border_color := [4]f32{0, 0, 0, 0}, + border_width: f32 = 0, +) -> Quad { + return Quad { + {pos.x, pos.y, size.x, size.y}, + corner_radii, + color, + border_color, + border_width, + {0, 0, 0}, + } } @(private) @@ -47,11 +63,11 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua } frag_info := sdl.GPUShaderCreateInfo { - code_size = len(frag_raw), - code = raw_data(frag_raw), - entrypoint = ENTRY_POINT, - format = SHADER_TYPE, - stage = sdl.GPUShaderStage.FRAGMENT, + code_size = len(frag_raw), + code = raw_data(frag_raw), + entrypoint = ENTRY_POINT, + format = SHADER_TYPE, + stage = sdl.GPUShaderStage.FRAGMENT, } vert_shader := sdl.CreateGPUShader(device, vert_info) @@ -151,7 +167,7 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua instance_buffer := create_buffer( device, size_of(Quad) * BUFFER_INIT_SIZE, - sdl.GPUBufferUsageFlags { .VERTEX }, + sdl.GPUBufferUsageFlags{.VERTEX}, ) pipeline := QuadPipeline{instance_buffer, BUFFER_INIT_SIZE, sdl_pipeline} @@ -162,10 +178,11 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua @(private) upload_quads :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { + using global num_quads := u32(len(tmp_quads)) size := num_quads * size_of(Quad) - resize_buffer(device, &quad_pipeline.instance_buffer, size, sdl.GPUBufferUsageFlags { .VERTEX }) + resize_buffer(device, &quad_pipeline.instance_buffer, size, sdl.GPUBufferUsageFlags{.VERTEX}) // Write data i_array := sdl.MapGPUTransferBuffer(device, quad_pipeline.instance_buffer.transfer, false) @@ -192,6 +209,8 @@ draw_quads :: proc( layer: ^Layer, load_op: sdl.GPULoadOp, ) { + using global + if layer.quad_len == 0 { return } @@ -219,17 +238,12 @@ draw_quads :: proc( quad_offset := layer.quad_instance_start - for &scissor, index in layer.scissors { + for &scissor, index in scissors[layer.scissor_start:][:layer.scissor_len] { if scissor.quad_len == 0 { continue } - if scissor.bounds.w == 0 || scissor.bounds.h == 0 { - sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)}) - } else { - sdl.SetGPUScissor(render_pass, scissor.bounds) - } - + sdl.SetGPUScissor(render_pass, scissor.bounds) sdl.DrawGPUPrimitives(render_pass, 6, scissor.quad_len, 0, quad_offset) quad_offset += scissor.quad_len } @@ -237,6 +251,7 @@ draw_quads :: proc( } destroy_quad_pipeline :: proc(device: ^sdl.GPUDevice) { + using global destroy_buffer(device, &quad_pipeline.instance_buffer) sdl.ReleaseGPUGraphicsPipeline(device, quad_pipeline.sdl_pipeline) } diff --git a/renderer/renderer.odin b/renderer/renderer.odin index 6bb6572..73ecbcb 100644 --- a/renderer/renderer.odin +++ b/renderer/renderer.odin @@ -18,33 +18,64 @@ when ODIN_OS == .Darwin { } BUFFER_INIT_SIZE: u32 : 256 +INITIAL_LAYER_SIZE :: 5 +INITIAL_SCISSOR_SIZE :: 10 -dpi_scaling: f32 = 1.0 -layers: [dynamic]Layer -quad_pipeline: QuadPipeline -text_pipeline: TextPipeline -odin_context: runtime.Context +Global :: struct { + dpi_scaling: f32, + curr_layer_index: uint, + layers: [dynamic]Layer, + max_layers: int, + scissors: [dynamic]Scissor, + max_scissors: int, + tmp_text: [dynamic]Text, + max_tmp_text: int, + tmp_quads: [dynamic]Quad, + max_tmp_quads: int, + clay_mem: [^]u8, + odin_context: runtime.Context, + quad_pipeline: QuadPipeline, + text_pipeline: TextPipeline, +} -// I need to make it so that I can -// a) Add primitives directly to a layer -// b) Create small nested clay-layouts that can be batched with other shit -// Some colletion of items, where its just primtiives, but collections of primitives? And each collection has a type, -// either raw or clay? Or something +// TODO every x frames nuke max values in case of edge cases where max gets set very high +// Called at the end of every frame +resize_global :: proc() { + using global -// Prepare - upload to GPU -// With clay commands, this requires converting to my custom primitive types -// With raw commands, this would require no conversion -// Need to go from UI declaration -> processing render cmds + if len(layers) > max_layers do max_layers = len(layers) + shrink(&layers, max_layers) + if len(scissors) > max_scissors do max_scissors = len(scissors) + shrink(&scissors, max_scissors) + if len(tmp_text) > max_tmp_text do max_tmp_text = len(tmp_text) + shrink(&tmp_text, max_tmp_text) + if len(tmp_quads) > max_tmp_quads do max_tmp_quads = len(tmp_quads) + shrink(&tmp_quads, max_tmp_quads) +} -// I want to process & upload in the same loop, don't want to add an additional pass where I transform -// clay cmds -> my primitives +destroy :: proc(device: ^sdl.GPUDevice) { + using global + delete(layers) + delete(scissors) + delete(tmp_text) + delete(tmp_quads) + free(clay_mem) + destroy_quad_pipeline(device) + destroy_text_pipeline(device) +} + +clear_global :: proc() { + using global + + curr_layer_index = 0 + clear(&layers) + clear(&scissors) + clear(&tmp_text) + clear(&tmp_quads) +} + +global: Global -// 1) I need to be able to process all clay cmds & all raw primitive cmds in a single iteration -// 2) I need to be able to define (at a layout level) which primitives can be rendered in the same batch -// -// Some kind of queue? Each UI element adds to a temp queue that is reset every frame -// Queue is like -// [ chunk of primitives ] [ chunk of clay primitives ] [ new layer indicator ] [ chunk ] Rectangle :: struct { x: f32, y: f32, @@ -62,8 +93,8 @@ Layer :: struct { text_vertex_len: u32, text_index_start: u32, text_index_len: u32, - curr_scissor_index: u32, - scissors: [dynamic]Scissor, + scissor_start: u32, + scissor_len: u32, } Scissor :: struct { @@ -82,23 +113,29 @@ init :: proc( window_height: f32, ctx: runtime.Context, ) { - odin_context = ctx - dpi_scaling = sdl.GetWindowDisplayScale(window) - log.debug("Window DPI scaling:", dpi_scaling) - min_memory_size: c.size_t = cast(c.size_t)clay.MinMemorySize() - memory := make([^]u8, min_memory_size) - arena := clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory) + + global = Global { + layers = make([dynamic]Layer, 0, INITIAL_LAYER_SIZE), + scissors = make([dynamic]Scissor, 0, INITIAL_SCISSOR_SIZE), + tmp_quads = make([dynamic]Quad, 0, BUFFER_INIT_SIZE), + tmp_text = make([dynamic]Text, 0, BUFFER_INIT_SIZE), + odin_context = ctx, + dpi_scaling = sdl.GetWindowDisplayScale(window), + clay_mem = make([^]u8, min_memory_size), + quad_pipeline = create_quad_pipeline(device, window), + text_pipeline = create_text_pipeline(device, window), + } + log.debug("Window DPI scaling:", global.dpi_scaling) + arena := clay.CreateArenaWithCapacityAndMemory(min_memory_size, global.clay_mem) clay.Initialize(arena, {window_width, window_height}, {handler = clay_error_handler}) clay.SetMeasureTextFunction(measure_text, nil) - quad_pipeline = create_quad_pipeline(device, window) - text_pipeline = create_text_pipeline(device, window) } @(private = "file") clay_error_handler :: proc "c" (errorData: clay.ErrorData) { - context = odin_context + context = global.odin_context log.error("Clay error:", errorData.errorType, errorData.errorText) } @@ -108,6 +145,7 @@ measure_text :: proc "c" ( config: ^clay.TextElementConfig, user_data: rawptr, ) -> clay.Dimensions { + using global context = odin_context text := string(text.chars[:text.length]) c_text := strings.clone_to_cstring(text, context.temp_allocator) @@ -119,62 +157,79 @@ measure_text :: proc "c" ( return clay.Dimensions{width = f32(w) / dpi_scaling, height = f32(h) / dpi_scaling} } -destroy :: proc(device: ^sdl.GPUDevice) { - destroy_quad_pipeline(device) - destroy_text_pipeline(device) -} - /// Sets up renderer to begin upload to the GPU. Returns starting `Layer` to begin processing primitives for -begin_prepare :: proc() -> Layer { - // Prepare to upload to GPU - clear(&layers) - clear(&tmp_quads) - clear(&tmp_text) +begin_prepare :: proc(bounds: Rectangle) -> ^Layer { + using global + // Cleanup + clear_global() - tmp_quads = make([dynamic]Quad, 0, quad_pipeline.num_instances, context.temp_allocator) - tmp_text = make([dynamic]Text, 0, 20, context.temp_allocator) + // Begin new layer + // Start a new scissor + scissor := Scissor { + bounds = sdl.Rect { + x = i32(bounds.x * dpi_scaling), + y = i32(bounds.y * dpi_scaling), + w = i32(bounds.w * dpi_scaling), + h = i32(bounds.h * dpi_scaling), + }, + } + append(&scissors, scissor) layer := Layer { - scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator), + bounds = bounds, + scissor_len = 1, } - - return layer + append(&layers, layer) + return &layers[curr_layer_index] } -/// Creates a new layer, appending the old one to `layers` -new_layer :: proc(layer: ^Layer, bounds: Rectangle) -> Layer { - append(&layers, layer^) +/// Creates a new layer +new_layer :: proc(old_layer: ^Layer, bounds: Rectangle) -> ^Layer { + using global layer := Layer { - bounds = bounds, - scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator), + bounds = bounds, + quad_instance_start = old_layer.quad_instance_start + old_layer.quad_len, + text_instance_start = old_layer.text_instance_start + old_layer.text_instance_len, + text_vertex_start = old_layer.text_vertex_start + old_layer.text_vertex_len, + text_index_start = old_layer.text_index_start + old_layer.text_instance_len, + scissor_start = old_layer.scissor_start + old_layer.scissor_len, + scissor_len = 1, } + append(&layers, layer) + curr_layer_index += 1 - return layer + scissor := Scissor { + bounds = sdl.Rect { + x = i32(bounds.x * dpi_scaling), + y = i32(bounds.y * dpi_scaling), + w = i32(bounds.w * dpi_scaling), + h = i32(bounds.h * dpi_scaling), + }, + } + append(&scissors, scissor) + return &layers[curr_layer_index] } -end_prepare :: proc(device: ^sdl.GPUDevice, cmd_buffer: ^sdl.GPUCommandBuffer, layer: ^Layer) { - // Commit last layer worked on - append(&layers, layer^) - +end_prepare :: proc(device: ^sdl.GPUDevice, cmd_buffer: ^sdl.GPUCommandBuffer) { // Upload primitives to GPU copy_pass := sdl.BeginGPUCopyPass(cmd_buffer) upload_quads(device, copy_pass) upload_text(device, copy_pass) sdl.EndGPUCopyPass(copy_pass) + + // Resize my dynamic arrays + resize_global() } // ===== Built-in primitive processing ===== -//prepare_batch :: proc( -// device: ^sdl.GPUDevice, -// window: ^sdl.Window, -// cmd_buffer: ^sdl.GPUCommandBuffer, -// layer: ^Layer, -// primitives: ^[]Primitive, -//) { -// scissor := Scissor{} -// -// -//} +// TODO scissoring support if I need it for primitives +prepare_quad :: proc(layer: ^Layer, quad: Quad) { + using global + + append(&tmp_quads, quad) + layer.quad_len += 1 + scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 +} // ====== Clay-specific processing ====== ClayBatch :: struct { @@ -184,9 +239,6 @@ ClayBatch :: struct { /// Upload data to the GPU prepare_clay_batch :: proc( - device: ^sdl.GPUDevice, - window: ^sdl.Window, - cmd_buffer: ^sdl.GPUCommandBuffer, layer: ^Layer, mouse_pos: [2]f32, mouse_flags: sdl.MouseButtonFlags, @@ -194,6 +246,8 @@ prepare_clay_batch :: proc( frame_time: f32, batch: ^ClayBatch, ) { + using global + // Update clay internals clay.SetPointerState( clay.Vector2{mouse_pos.x - layer.bounds.x, mouse_pos.y - layer.bounds.y}, @@ -201,8 +255,6 @@ prepare_clay_batch :: proc( ) clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_wheel_delta, frame_time) - scissor := Scissor{} - // Parse render commands for i in 0 ..< int(batch.cmds.length) { render_command := clay.RenderCommandArray_Get(&batch.cmds, cast(i32)i) @@ -237,6 +289,9 @@ prepare_clay_batch :: proc( } data := sdl_ttf.GetGPUTextDrawData(sdl_text) + if data == nil { + log.error("Failed to find GPUTextDrawData for sdl_text:", c_text) + } if sdl_text == nil { log.error("Could not create SDL text:", sdl.GetError()) @@ -248,29 +303,39 @@ prepare_clay_batch :: proc( layer.text_instance_len += 1 layer.text_vertex_len += u32(data.num_vertices) layer.text_index_len += u32(data.num_indices) - scissor.text_len += 1 + scissors[layer.scissor_start + layer.scissor_len - 1].text_len += 1 } case clay.RenderCommandType.Image: case clay.RenderCommandType.ScissorStart: - bounds := sdl.Rect { - c.int(bounds.x * dpi_scaling), - c.int(bounds.y * dpi_scaling), - c.int(bounds.w * dpi_scaling), - c.int(bounds.h * dpi_scaling), - } - if scissor.quad_len != 0 || scissor.text_len != 0 { - new := new_scissor(&scissor) - append(&layer.scissors, scissor) - scissor = new + if bounds.w == 0 || bounds.h == 0 { + continue } - scissor.bounds = bounds - case clay.RenderCommandType.ScissorEnd: - if scissor.quad_len != 0 || scissor.text_len != 0 { - new := new_scissor(&scissor) - append(&layer.scissors, scissor) - scissor = new + curr_scissor := &scissors[layer.scissor_start + layer.scissor_len - 1] + + if curr_scissor.quad_len != 0 || curr_scissor.text_len != 0 { + // Scissor has some content, need to make a new scissor + new := Scissor { + quad_start = curr_scissor.quad_start + curr_scissor.quad_len, + text_start = curr_scissor.text_start + curr_scissor.text_len, + bounds = sdl.Rect { + c.int(bounds.x * dpi_scaling), + c.int(bounds.y * dpi_scaling), + c.int(bounds.w * dpi_scaling), + c.int(bounds.h * dpi_scaling), + }, + } + append(&scissors, new) + layer.scissor_len += 1 + } else { + curr_scissor.bounds = sdl.Rect { + c.int(bounds.x * dpi_scaling), + c.int(bounds.y * dpi_scaling), + c.int(bounds.w * dpi_scaling), + c.int(bounds.h * dpi_scaling), + } } + case clay.RenderCommandType.ScissorEnd: case clay.RenderCommandType.Rectangle: render_data := render_command.renderData.rectangle color := f32_color(render_data.backgroundColor) @@ -281,12 +346,12 @@ prepare_clay_batch :: proc( color = color, } append(&tmp_quads, quad) + layer.quad_len += 1 - scissor.quad_len += 1 + scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 case clay.RenderCommandType.Border: render_data := render_command.renderData.border cr := render_data.cornerRadius - //TODO dedicated border pipeline quad := Quad { position_scale = {bounds.x, bounds.y, bounds.w, bounds.h}, corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft}, @@ -299,18 +364,15 @@ prepare_clay_batch :: proc( // for our use case we can just chuck these in with the quad pipeline append(&tmp_quads, quad) layer.quad_len += 1 - scissor.quad_len += 1 + scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 case clay.RenderCommandType.Custom: } } - - if scissor.quad_len != 0 || scissor.text_len != 0 { - append(&layer.scissors, scissor) - } } /// Render primitives draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCommandBuffer) { + using global swapchain_texture: ^sdl.GPUTexture w, h: u32 if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &w, &h) { @@ -335,7 +397,7 @@ draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCo index == 0 ? sdl.GPULoadOp.CLEAR : sdl.GPULoadOp.LOAD, ) draw_text(device, window, cmd_buffer, swapchain_texture, w, h, &layer) - //TODO draw other primitives in layer + //TODO draw other primitives in layer once I add support for them :) } } @@ -367,15 +429,8 @@ Globals :: struct { push_globals :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, w: f32, h: f32) { globals := Globals { ortho_rh(left = 0.0, top = 0.0, right = f32(w), bottom = f32(h), near = -1.0, far = 1.0), - dpi_scaling, + global.dpi_scaling, } sdl.PushGPUVertexUniformData(cmd_buffer, 0, &globals, size_of(Globals)) } - -new_scissor :: proc(old: ^Scissor) -> Scissor { - return Scissor { - quad_start = old.quad_start + old.quad_len, - text_start = old.text_start + old.text_len, - } -} diff --git a/renderer/text.odin b/renderer/text.odin index a744885..2b7c035 100644 --- a/renderer/text.odin +++ b/renderer/text.odin @@ -12,8 +12,6 @@ JETBRAINS_MONO_BOLD: u16 : 1 NUM_FONTS :: 2 MAX_FONT_SIZE :: 120 -tmp_text: [dynamic]Text - @(private = "file") jetbrains_mono_regular := #load("res/fonts/JetBrainsMono-Regular.ttf") @(private = "file") @@ -31,7 +29,7 @@ TextPipeline :: struct { } get_font :: proc(id: u16, size: u16) -> ^sdl_ttf.Font { - font := text_pipeline.fonts[id > 1 ? 0 : id][size > 0 ? size : 16] + font := global.text_pipeline.fonts[id > 1 ? 0 : id][size > 0 ? size : 16] if font == nil { log.debug("Font not found for size", size, "+ adding") @@ -45,8 +43,13 @@ get_font :: proc(id: u16, size: u16) -> ^sdl_ttf.Font { os.exit(1) } font = f - _ = sdl_ttf.SetFontSizeDPI(f, f32(size), 72 * i32(dpi_scaling), 72 * i32(dpi_scaling)) - text_pipeline.fonts[id][size] = f + _ = sdl_ttf.SetFontSizeDPI( + f, + f32(size), + 72 * i32(global.dpi_scaling), + 72 * i32(global.dpi_scaling), + ) + global.text_pipeline.fonts[id][size] = f } return font @@ -241,11 +244,14 @@ create_text_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Tex @(private) upload_text :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { + using global + + // TODO maybe don't use tmp here vertices := make([dynamic]TextVert, 0, BUFFER_INIT_SIZE, context.temp_allocator) indices := make([dynamic]c.int, 0, BUFFER_INIT_SIZE, context.temp_allocator) instances := make([dynamic][2]f32, 0, BUFFER_INIT_SIZE, context.temp_allocator) - for &text, index in tmp_text { + for &text, index in global.tmp_text { append(&instances, text.position) data := sdl_ttf.GetGPUTextDrawData(text.ref) for data != nil { @@ -344,6 +350,7 @@ draw_text :: proc( swapchain_h: u32, layer: ^Layer, ) { + using global if layer.text_instance_len == 0 { return } @@ -382,16 +389,12 @@ draw_text :: proc( vertex_offset: i32 = i32(layer.text_vertex_start) instance_offset: u32 = layer.text_instance_start - for &scissor, index in layer.scissors { + for &scissor, index in scissors[layer.scissor_start:][:layer.scissor_len] { if scissor.text_len == 0 { continue } - if scissor.bounds.w == 0 || scissor.bounds.h == 0 { - sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)}) - } else { - sdl.SetGPUScissor(render_pass, scissor.bounds) - } + sdl.SetGPUScissor(render_pass, scissor.bounds) for &text in layer_text[scissor.text_start:scissor.text_start + scissor.text_len] { data := sdl_ttf.GetGPUTextDrawData(text.ref) @@ -433,7 +436,10 @@ draw_text :: proc( } destroy_text_pipeline :: proc(device: ^sdl.GPUDevice) { + using global destroy_buffer(device, &text_pipeline.vertex_buffer) destroy_buffer(device, &text_pipeline.index_buffer) + destroy_buffer(device, &text_pipeline.instance_buffer) + delete(text_pipeline.cache) sdl.ReleaseGPUGraphicsPipeline(device, text_pipeline.sdl_pipeline) } From fcad0ccfc34daf1da8127ba7d65ca32e02e2f33e Mon Sep 17 00:00:00 2001 From: shan Date: Tue, 22 Jul 2025 21:38:31 -0700 Subject: [PATCH 3/3] Add support for raw text primitives & clay z-indexing --- examples/main.odin | 6 ++++- renderer/quad.odin | 2 +- renderer/renderer.odin | 59 +++++++++++++++++++++++++++++++++--------- renderer/text.odin | 39 +++++++++++++++++++++++++--- 4 files changed, 88 insertions(+), 18 deletions(-) diff --git a/examples/main.odin b/examples/main.odin index c4d4969..3099cff 100644 --- a/examples/main.odin +++ b/examples/main.odin @@ -321,6 +321,10 @@ layout :: proc(layer: ^renderer.Layer) { border_color = {0, 0, 0, 1}, border_width = 10, ) - renderer.prepare_quad(layer, test_quad) + + text_ok, text := renderer.text(0, "Raw Text", {bounds.x + 80, bounds.y + 80}) + if text_ok { + renderer.prepare_text(layer, text) + } } diff --git a/renderer/quad.odin b/renderer/quad.odin index 1ad7033..1f573e8 100644 --- a/renderer/quad.odin +++ b/renderer/quad.odin @@ -211,7 +211,7 @@ draw_quads :: proc( ) { using global - if layer.quad_len == 0 { + if layer.quad_instance_len == 0 { return } diff --git a/renderer/renderer.odin b/renderer/renderer.odin index 73ecbcb..0bec6c1 100644 --- a/renderer/renderer.odin +++ b/renderer/renderer.odin @@ -33,6 +33,7 @@ Global :: struct { tmp_quads: [dynamic]Quad, max_tmp_quads: int, clay_mem: [^]u8, + clay_z_index: i16, odin_context: runtime.Context, quad_pipeline: QuadPipeline, text_pipeline: TextPipeline, @@ -68,6 +69,7 @@ clear_global :: proc() { using global curr_layer_index = 0 + clay_z_index = 0 clear(&layers) clear(&scissors) clear(&tmp_text) @@ -86,7 +88,7 @@ Rectangle :: struct { Layer :: struct { bounds: Rectangle, quad_instance_start: u32, - quad_len: u32, + quad_instance_len: u32, text_instance_start: u32, text_instance_len: u32, text_vertex_start: u32, @@ -184,19 +186,20 @@ begin_prepare :: proc(bounds: Rectangle) -> ^Layer { } /// Creates a new layer -new_layer :: proc(old_layer: ^Layer, bounds: Rectangle) -> ^Layer { +new_layer :: proc(prev_layer: ^Layer, bounds: Rectangle) -> ^Layer { using global layer := Layer { bounds = bounds, - quad_instance_start = old_layer.quad_instance_start + old_layer.quad_len, - text_instance_start = old_layer.text_instance_start + old_layer.text_instance_len, - text_vertex_start = old_layer.text_vertex_start + old_layer.text_vertex_len, - text_index_start = old_layer.text_index_start + old_layer.text_instance_len, - scissor_start = old_layer.scissor_start + old_layer.scissor_len, + quad_instance_start = prev_layer.quad_instance_start + prev_layer.quad_instance_len, + text_instance_start = prev_layer.text_instance_start + prev_layer.text_instance_len, + text_vertex_start = prev_layer.text_vertex_start + prev_layer.text_vertex_len, + text_index_start = prev_layer.text_index_start + prev_layer.text_index_len, + scissor_start = prev_layer.scissor_start + prev_layer.scissor_len, scissor_len = 1, } append(&layers, layer) curr_layer_index += 1 + log.debug("Added new layer; curr index", curr_layer_index) scissor := Scissor { bounds = sdl.Rect { @@ -227,10 +230,26 @@ prepare_quad :: proc(layer: ^Layer, quad: Quad) { using global append(&tmp_quads, quad) - layer.quad_len += 1 + layer.quad_instance_len += 1 scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 } +// TODO need to make sure no overlap with clay render command IDs +prepare_text :: proc(layer: ^Layer, text: Text) { + using global + + data := sdl_ttf.GetGPUTextDrawData(text.ref) + if data == nil { + log.error("Failed to find GPUTextDrawData for sdl_text") + } + + append(&tmp_text, text) + layer.text_instance_len += 1 + layer.text_vertex_len += u32(data.num_vertices) + layer.text_index_len += u32(data.num_indices) + scissors[layer.scissor_start + layer.scissor_len - 1].text_len += 1 +} + // ====== Clay-specific processing ====== ClayBatch :: struct { bounds: Rectangle, @@ -239,7 +258,7 @@ ClayBatch :: struct { /// Upload data to the GPU prepare_clay_batch :: proc( - layer: ^Layer, + base_layer: ^Layer, mouse_pos: [2]f32, mouse_flags: sdl.MouseButtonFlags, mouse_wheel_delta: [2]f32, @@ -250,14 +269,17 @@ prepare_clay_batch :: proc( // Update clay internals clay.SetPointerState( - clay.Vector2{mouse_pos.x - layer.bounds.x, mouse_pos.y - layer.bounds.y}, + clay.Vector2{mouse_pos.x - base_layer.bounds.x, mouse_pos.y - base_layer.bounds.y}, .LEFT in mouse_flags, ) clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_wheel_delta, frame_time) + layer := base_layer + // Parse render commands for i in 0 ..< int(batch.cmds.length) { render_command := clay.RenderCommandArray_Get(&batch.cmds, cast(i32)i) + // Translate bounding box of the primitive by the layer position bounds := Rectangle { x = render_command.boundingBox.x + layer.bounds.x, @@ -266,6 +288,18 @@ prepare_clay_batch :: proc( h = render_command.boundingBox.height, } + if render_command.zIndex > clay_z_index { + log.debug( + "Higher zIndex found, creating new layer & setting z_index to", + render_command.zIndex, + ) + layer = new_layer(layer, bounds) + // Update bounds to new layer offset + bounds.x = render_command.boundingBox.x + layer.bounds.x + bounds.y = render_command.boundingBox.y + layer.bounds.y + clay_z_index = render_command.zIndex + } + switch (render_command.commandType) { case clay.RenderCommandType.None: case clay.RenderCommandType.Text: @@ -347,7 +381,7 @@ prepare_clay_batch :: proc( } append(&tmp_quads, quad) - layer.quad_len += 1 + layer.quad_instance_len += 1 scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 case clay.RenderCommandType.Border: render_data := render_command.renderData.border @@ -363,7 +397,7 @@ prepare_clay_batch :: proc( // Technically these should be drawn on top of everything else including children, but // for our use case we can just chuck these in with the quad pipeline append(&tmp_quads, quad) - layer.quad_len += 1 + layer.quad_instance_len += 1 scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1 case clay.RenderCommandType.Custom: } @@ -386,6 +420,7 @@ draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCo } for &layer, index in layers { + log.debug("Drawing layer", index) draw_quads( device, window, diff --git a/renderer/text.odin b/renderer/text.odin index 2b7c035..58d57ca 100644 --- a/renderer/text.odin +++ b/renderer/text.odin @@ -61,6 +61,38 @@ Text :: struct { color: [4]f32, } +text :: proc( + id: u32, + txt: cstring, + pos: [2]f32, + color: [4]f32 = {0.0, 0.0, 0.0, 1.0}, + font_id: u16 = JETBRAINS_MONO_REGULAR, + font_size: u16 = 44, +) -> (bool, Text) { + using global + + sdl_text := text_pipeline.cache[id] + if sdl_text == nil { + sdl_text = sdl_ttf.CreateText(text_pipeline.engine, get_font(font_id, font_size), txt, 0) + text_pipeline.cache[id] = sdl_text + } else { + //TODO if IDs are always unique and never change the underlying text + // can get rid of this + _ = sdl_ttf.SetTextString(sdl_text, txt, 0) + } + + if sdl_text == nil { + log.error("Could not create SDL text:", sdl.GetError()) + return false, Text {} + } else { + return true, Text { + sdl_text, + pos, + color, + } + } +} + // For upload TextVert :: struct { pos_uv: [4]f32, @@ -383,9 +415,8 @@ draw_text :: proc( atlas: ^sdl.GPUTexture - layer_text := tmp_text[layer.text_instance_start:layer.text_instance_start + - layer.text_instance_len] - index_offset: u32 = layer.text_instance_start + layer_text := tmp_text[layer.text_instance_start:][:layer.text_instance_len] + index_offset: u32 = layer.text_index_start vertex_offset: i32 = i32(layer.text_vertex_start) instance_offset: u32 = layer.text_instance_start @@ -396,7 +427,7 @@ draw_text :: proc( sdl.SetGPUScissor(render_pass, scissor.bounds) - for &text in layer_text[scissor.text_start:scissor.text_start + scissor.text_len] { + for &text in layer_text[scissor.text_start:][:scissor.text_len] { data := sdl_ttf.GetGPUTextDrawData(text.ref) for data != nil {