From e3dbdcba4608eb64eaa65b9fe68abe81758e6a2f Mon Sep 17 00:00:00 2001 From: shan Date: Mon, 7 Jul 2025 15:18:29 -0700 Subject: [PATCH] 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