Add ability to render non-Clay primitives + proper layering support #8
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import clay "../clay"
|
||||||
import "../renderer"
|
import "../renderer"
|
||||||
import "core:c"
|
import "core:c"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import clay "library:clay"
|
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
WINDOW_WIDTH :: 1024
|
WINDOW_WIDTH :: 1024
|
||||||
@@ -165,8 +165,43 @@ destroy :: proc() {
|
|||||||
update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool {
|
update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool {
|
||||||
frame_time := f32(delta_time) / 1000.0
|
frame_time := f32(delta_time) / 1000.0
|
||||||
input := input()
|
input := input()
|
||||||
render_cmds := layout()
|
mouse_x, mouse_y: f32
|
||||||
renderer.prepare(device, window, cmd_buffer, &render_cmds, input.mouse_delta, frame_time)
|
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
|
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()
|
clay.BeginLayout()
|
||||||
|
|
||||||
if clay.UI()(
|
if clay.UI()(
|
||||||
@@ -247,5 +283,5 @@ layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
|
|||||||
clay.Text("Test Text", &body_text)
|
clay.Text("Test Text", &body_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
return clay.EndLayout()
|
return renderer.ClayBatch{bounds, clay.EndLayout()}
|
||||||
}
|
}
|
||||||
|
@@ -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("Loaded", len(frag_raw), "frag bytes")
|
||||||
log.debug("ShaderType:", SHADER_TYPE)
|
log.debug("ShaderType:", SHADER_TYPE)
|
||||||
|
|
||||||
|
|
||||||
vert_info := sdl.GPUShaderCreateInfo {
|
vert_info := sdl.GPUShaderCreateInfo {
|
||||||
code_size = len(vert_raw),
|
code_size = len(vert_raw),
|
||||||
code = raw_data(vert_raw),
|
code = raw_data(vert_raw),
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
|
import clay "../clay"
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "core:c"
|
import "core:c"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import clay "library:clay"
|
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
import sdl_ttf "vendor:sdl3/ttf"
|
import sdl_ttf "vendor:sdl3/ttf"
|
||||||
|
|
||||||
@@ -25,8 +25,35 @@ quad_pipeline: QuadPipeline
|
|||||||
text_pipeline: TextPipeline
|
text_pipeline: TextPipeline
|
||||||
odin_context: runtime.Context
|
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 {
|
Layer :: struct {
|
||||||
|
bounds: Rectangle,
|
||||||
quad_instance_start: u32,
|
quad_instance_start: u32,
|
||||||
quad_len: u32,
|
quad_len: u32,
|
||||||
text_instance_start: u32,
|
text_instance_start: u32,
|
||||||
@@ -35,6 +62,7 @@ Layer :: struct {
|
|||||||
text_vertex_len: u32,
|
text_vertex_len: u32,
|
||||||
text_index_start: u32,
|
text_index_start: u32,
|
||||||
text_index_len: u32,
|
text_index_len: u32,
|
||||||
|
curr_scissor_index: u32,
|
||||||
scissors: [dynamic]Scissor,
|
scissors: [dynamic]Scissor,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +96,7 @@ init :: proc(
|
|||||||
text_pipeline = create_text_pipeline(device, window)
|
text_pipeline = create_text_pipeline(device, window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
|
clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
|
||||||
context = odin_context
|
context = odin_context
|
||||||
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
log.error("Clay error:", errorData.errorType, errorData.errorText)
|
||||||
@@ -95,26 +124,9 @@ destroy :: proc(device: ^sdl.GPUDevice) {
|
|||||||
destroy_text_pipeline(device)
|
destroy_text_pipeline(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload data to the GPU
|
/// Sets up renderer to begin upload to the GPU. Returns starting `Layer` to begin processing primitives for
|
||||||
prepare :: proc(
|
begin_prepare :: proc() -> Layer {
|
||||||
device: ^sdl.GPUDevice,
|
// Prepare to upload to GPU
|
||||||
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)})
|
|
||||||
|
|
||||||
clear(&layers)
|
clear(&layers)
|
||||||
clear(&tmp_quads)
|
clear(&tmp_quads)
|
||||||
clear(&tmp_text)
|
clear(&tmp_text)
|
||||||
@@ -125,12 +137,82 @@ prepare :: proc(
|
|||||||
layer := Layer {
|
layer := Layer {
|
||||||
scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator),
|
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{}
|
scissor := Scissor{}
|
||||||
|
|
||||||
// Parse render commands
|
// Parse render commands
|
||||||
for i in 0 ..< int(render_commands.length) {
|
for i in 0 ..< int(batch.cmds.length) {
|
||||||
render_command := clay.RenderCommandArray_Get(render_commands, cast(i32)i)
|
render_command := clay.RenderCommandArray_Get(&batch.cmds, cast(i32)i)
|
||||||
bounds := render_command.boundingBox
|
// 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) {
|
switch (render_command.commandType) {
|
||||||
case clay.RenderCommandType.None:
|
case clay.RenderCommandType.None:
|
||||||
@@ -173,27 +255,28 @@ prepare :: proc(
|
|||||||
bounds := sdl.Rect {
|
bounds := sdl.Rect {
|
||||||
c.int(bounds.x * dpi_scaling),
|
c.int(bounds.x * dpi_scaling),
|
||||||
c.int(bounds.y * dpi_scaling),
|
c.int(bounds.y * dpi_scaling),
|
||||||
c.int(bounds.width * dpi_scaling),
|
c.int(bounds.w * dpi_scaling),
|
||||||
c.int(bounds.height * dpi_scaling),
|
c.int(bounds.h * dpi_scaling),
|
||||||
}
|
}
|
||||||
new := new_scissor(&scissor)
|
|
||||||
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
||||||
|
new := new_scissor(&scissor)
|
||||||
append(&layer.scissors, scissor)
|
append(&layer.scissors, scissor)
|
||||||
}
|
|
||||||
scissor = new
|
scissor = new
|
||||||
|
}
|
||||||
|
|
||||||
scissor.bounds = bounds
|
scissor.bounds = bounds
|
||||||
case clay.RenderCommandType.ScissorEnd:
|
case clay.RenderCommandType.ScissorEnd:
|
||||||
new := new_scissor(&scissor)
|
|
||||||
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
||||||
|
new := new_scissor(&scissor)
|
||||||
append(&layer.scissors, scissor)
|
append(&layer.scissors, scissor)
|
||||||
}
|
|
||||||
scissor = new
|
scissor = new
|
||||||
|
}
|
||||||
case clay.RenderCommandType.Rectangle:
|
case clay.RenderCommandType.Rectangle:
|
||||||
render_data := render_command.renderData.rectangle
|
render_data := render_command.renderData.rectangle
|
||||||
color := f32_color(render_data.backgroundColor)
|
color := f32_color(render_data.backgroundColor)
|
||||||
cr := render_data.cornerRadius
|
cr := render_data.cornerRadius
|
||||||
quad := Quad {
|
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},
|
corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft},
|
||||||
color = color,
|
color = color,
|
||||||
}
|
}
|
||||||
@@ -205,7 +288,7 @@ prepare :: proc(
|
|||||||
cr := render_data.cornerRadius
|
cr := render_data.cornerRadius
|
||||||
//TODO dedicated border pipeline
|
//TODO dedicated border pipeline
|
||||||
quad := Quad {
|
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},
|
corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft},
|
||||||
color = f32_color(clay.Color{0.0, 0.0, 0.0, 0.0}),
|
color = f32_color(clay.Color{0.0, 0.0, 0.0, 0.0}),
|
||||||
border_color = f32_color(render_data.color),
|
border_color = f32_color(render_data.color),
|
||||||
@@ -221,15 +304,9 @@ prepare :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO start new layers with z-index changes
|
if scissor.quad_len != 0 || scissor.text_len != 0 {
|
||||||
append(&layer.scissors, scissor)
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render primitives
|
/// Render primitives
|
||||||
|
Reference in New Issue
Block a user