Compare commits

..

7 Commits

19 changed files with 510 additions and 365 deletions
+36 -17
View File
@@ -103,7 +103,7 @@ TextAlignment :: enum EnumBackingType {
} }
TextElementConfig :: struct { TextElementConfig :: struct {
userData: rawptr, userData: rawptr,
textColor: Color, textColor: Color,
fontId: u16, fontId: u16,
fontSize: u16, fontSize: u16,
@@ -113,9 +113,12 @@ TextElementConfig :: struct {
textAlignment: TextAlignment, textAlignment: TextAlignment,
} }
AspectRatioElementConfig :: struct {
aspectRatio: f32,
}
ImageElementConfig :: struct { ImageElementConfig :: struct {
imageData: rawptr, imageData: rawptr,
sourceDimensions: Dimensions,
} }
CustomElementConfig :: struct { CustomElementConfig :: struct {
@@ -135,9 +138,10 @@ BorderElementConfig :: struct {
width: BorderWidth, width: BorderWidth,
} }
ScrollElementConfig :: struct { ClipElementConfig :: struct {
horizontal: bool, horizontal: bool, // clip overflowing elements on the "X" axis
vertical: bool, vertical: bool, // clip overflowing elements on the "Y" axis
childOffset: Vector2, // offsets the [X,Y] positions of all child elements, primarily for scrolling containers
} }
FloatingAttachPointType :: enum EnumBackingType { FloatingAttachPointType :: enum EnumBackingType {
@@ -169,6 +173,11 @@ FloatingAttachToElement :: enum EnumBackingType {
Root, Root,
} }
FloatingClipToElement :: enum EnumBackingType {
None,
AttachedParent,
}
FloatingElementConfig :: struct { FloatingElementConfig :: struct {
offset: Vector2, offset: Vector2,
expand: Dimensions, expand: Dimensions,
@@ -177,6 +186,7 @@ FloatingElementConfig :: struct {
attachment: FloatingAttachPoints, attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode, pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement, attachTo: FloatingAttachToElement,
clipTo: FloatingClipToElement,
} }
TextRenderData :: struct { TextRenderData :: struct {
@@ -196,7 +206,6 @@ RectangleRenderData :: struct {
ImageRenderData :: struct { ImageRenderData :: struct {
backgroundColor: Color, backgroundColor: Color,
cornerRadius: CornerRadius, cornerRadius: CornerRadius,
sourceDimensions: Dimensions,
imageData: rawptr, imageData: rawptr,
} }
@@ -235,7 +244,7 @@ ScrollContainerData :: struct {
scrollPosition: ^Vector2, scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions, scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions, contentDimensions: Dimensions,
config: ScrollElementConfig, config: ClipElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool, found: bool,
} }
@@ -329,16 +338,17 @@ ClayArray :: struct($type: typeid) {
} }
ElementDeclaration :: struct { ElementDeclaration :: struct {
id: ElementId, id: ElementId,
layout: LayoutConfig, layout: LayoutConfig,
backgroundColor: Color, backgroundColor: Color,
cornerRadius: CornerRadius, cornerRadius: CornerRadius,
image: ImageElementConfig, aspectRatio: AspectRatioElementConfig,
floating: FloatingElementConfig, image: ImageElementConfig,
custom: CustomElementConfig, floating: FloatingElementConfig,
scroll: ScrollElementConfig, custom: CustomElementConfig,
border: BorderElementConfig, clip: ClipElementConfig,
userData: rawptr, border: BorderElementConfig,
userData: rawptr,
} }
ErrorType :: enum EnumBackingType { ErrorType :: enum EnumBackingType {
@@ -385,6 +395,7 @@ foreign Clay {
Hovered :: proc() -> bool --- Hovered :: proc() -> bool ---
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) --- OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
PointerOver :: proc(id: ElementId) -> bool --- PointerOver :: proc(id: ElementId) -> bool ---
GetScrollOffset :: proc() -> Vector2 ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData --- GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) --- SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) --- SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
@@ -437,6 +448,14 @@ PaddingAll :: proc(allPadding: u16) -> Padding {
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding } return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
} }
BorderOutside :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, 0}
}
BorderAll :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, width}
}
CornerRadiusAll :: proc(radius: f32) -> CornerRadius { CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius} return CornerRadius{radius, radius, radius, radius}
} }
@@ -467,4 +486,4 @@ ID :: proc(label: string, index: u32 = 0) -> ElementId {
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId { ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index, _GetParentElementId()) return _HashString(MakeString(label), index, _GetParentElementId())
} }
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+111 -15
View File
@@ -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
@@ -18,14 +18,12 @@ device: ^sdl.GPUDevice
debug_enabled := false debug_enabled := false
body_text := clay.TextElementConfig { body_text := clay.TextElementConfig {
fontId = renderer.JETBRAINS_MONO_REGULAR, fontId = renderer.JETBRAINS_MONO_REGULAR,
fontSize = 44, fontSize = 44,
textColor = { 1.0, 1.0, 1.0, 1.0 }, textColor = {0.0, 0.0, 0.0, 255.0},
} }
main :: proc() { main :: proc() {
defer destroy()
when ODIN_DEBUG == true { when ODIN_DEBUG == true {
context.logger = log.create_console_logger(lowest = .Debug) context.logger = log.create_console_logger(lowest = .Debug)
@@ -88,7 +86,7 @@ main :: proc() {
log.error("Failed to initialize SDL:", sdl.GetError()) log.error("Failed to initialize SDL:", sdl.GetError())
} }
window = sdl.CreateWindow("System Controller", WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_FLAGS) window = sdl.CreateWindow("Test", WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_FLAGS)
if window == nil { if window == nil {
log.error("Failed to create window:", sdl.GetError()) log.error("Failed to create window:", sdl.GetError())
@@ -142,7 +140,9 @@ main :: proc() {
os.exit(1) os.exit(1)
} }
if update(cmd_buffer, frame_time - last_frame_time) { should_quit := update(cmd_buffer, frame_time - last_frame_time)
if should_quit {
log.debug("User command to quit") log.debug("User command to quit")
break program break program
} }
@@ -151,9 +151,12 @@ main :: proc() {
last_frame_time = frame_time last_frame_time = frame_time
} }
destroy()
} }
destroy :: proc() { destroy :: proc() {
free_all(context.temp_allocator)
renderer.destroy(device) renderer.destroy(device)
sdl.ReleaseWindowFromGPUDevice(device, window) sdl.ReleaseWindowFromGPUDevice(device, window)
sdl.DestroyWindow(window) sdl.DestroyWindow(window)
@@ -163,10 +166,46 @@ 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()
mouse_x, mouse_y: f32
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),
}
render_cmds: clay.ClayArray(clay.RenderCommand) = layout() 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()
renderer.prepare(device, window, cmd_buffer, &render_cmds, input.mouse_delta, frame_time) // Process primitives on this layer
layout(layer)
// Process clay-specific primitives
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(
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)
return input.should_quit return input.should_quit
} }
@@ -211,7 +250,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()(
@@ -221,14 +261,70 @@ layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
layoutDirection = .TopToBottom, layoutDirection = .TopToBottom,
sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, sizing = {clay.SizingGrow({}), clay.SizingGrow({})},
childAlignment = {x = .Center, y = .Center}, childAlignment = {x = .Center, y = .Center},
childGap = 16, childGap = 32,
}, },
backgroundColor = {0.2, 0.2, 0.2, 1.0}, backgroundColor = {200.0, 200.0, 200.0, 100.0},
}, },
) { ) {
clay.Text("3D SCENE", &body_text) if clay.UI()(
{
id = clay.ID("RoundedRect"),
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)}},
},
) {
}
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 clay.EndLayout() 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)
text_ok, text := renderer.text(0, "Raw Text", {bounds.x + 80, bounds.y + 80})
if text_ok {
renderer.prepare_text(layer, text)
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-184
View File
@@ -1,184 +0,0 @@
package sdl3_ttf
import sdl "vendor:sdl3"
import "core:c"
foreign import lib "system:SDL3_ttf"
Font :: struct {}
Text :: struct {
text: cstring,
num_lines: c.int,
refcount: c.int,
internal: rawptr,
}
TextEngine :: struct {}
Direction :: enum c.int {
LTR = 0,
RTL,
TTB,
BTT,
}
// Normal == empty
FontStyleFlag :: enum u32 {
BOLD = 0,
ITALIC = 1,
UNDERLINE = 2,
STRIKETHROUGH = 3,
}
FontStyleFlags :: bit_set[FontStyleFlag;u32]
FONT_STYLE_NORMAL :: FontStyleFlags{}
FONT_STYLE_BOLD :: FontStyleFlags{.BOLD}
FONT_STYLE_ITALIC :: FontStyleFlags{.ITALIC}
FONT_STYLE_UNDERLINE :: FontStyleFlags{.UNDERLINE}
FONT_STYLE_STRIKETHROUGH :: FontStyleFlags{.STRIKETHROUGH}
HintingFlags :: enum c.int {
NORMAL = 0,
LIGHT,
MONO,
NONE,
LIGHT_SUBPIXEL,
}
TTF_PROP_FONT_OUTLINE_LINE_CAP_NUMBER :: "SDL_ttf.font.outline.line_cap"
TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER :: "SDL_ttf.font.outline.line_join"
TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER :: "SDL_ttf.font.outline.miter_limit"
HorizontalAlignment :: enum c.int {
INVALID = -1,
LEFT,
CENTER,
RIGHT,
}
GPUAtlasDrawSequence :: struct {
atlas_texture: ^sdl.GPUTexture,
vertex_positions: [^]sdl.FPoint,
uvs: [^]sdl.FPoint, // Normalized
num_verticies: c.int,
indices: [^]c.int,
num_indices: c.int,
next: ^GPUAtlasDrawSequence, // If nil, this is the last text in the sequence
}
GPUTextEngineWinding :: enum c.int {
INVALID = -1,
CLOCKWISE,
COUNTERCLOCKWISE,
}
SubStringFlag :: enum u32 {
TEXT_START,
LINE_START,
LINE_END,
TEXT_END,
}
SubString :: struct {
flags: SubStringFlag,
offset: c.int,
length: c.int,
line_index: c.int,
cluster_index: c.int,
rect: sdl.Rect,
}
/// General
@(default_calling_convention = "c", link_prefix = "TTF_")
foreign lib {
Init :: proc() -> bool ---
CreateGPUTextEngine :: proc(device: ^sdl.GPUDevice) -> ^TextEngine ---
DestroyGPUTextEngine :: proc(engine: ^TextEngine) ---
Quit :: proc() ---
}
/// Fonts
@(default_calling_convention = "c", link_prefix = "TTF_")
foreign lib {
CloseFont :: proc(font: ^Font) ---
FontHasGlyph :: proc(font: ^Font, glyph: u32) -> bool ---
FontIsFixedWidth :: proc(font: ^Font) -> bool ---
GetFontAscent :: proc(font: ^Font) -> c.int ---
GetFontDescent :: proc(font: ^Font) -> c.int ---
GetFontDirection :: proc(font: ^Font) -> Direction ---
GetFontDPI :: proc(font: ^Font, hdpi: ^c.int, vdpi: ^c.int) -> bool ---
GetFontFamilyName :: proc(font: ^Font) -> cstring ---
GetFontGeneration :: proc(font: ^Font) -> u32 ---
GetFontHeight :: proc(font: ^Font) -> c.int ---
GetFontHinting :: proc(font: ^Font) -> HintingFlags ---
GetFontKerning :: proc(font: ^Font) -> bool ---
/// Returns the font's recommended spacing
GetFontLineSkip :: proc(font: ^Font) -> c.int ---
GetFontOutline :: proc(font: ^Font) -> c.int ---
GetFontProperties :: proc(font: ^Font) -> sdl.PropertiesID ---
GetFontSize :: proc(font: ^Font) -> f32 ---
GetFontStyle :: proc(font: ^Font) -> FontStyleFlags ---
GetFontStyleName :: proc(font: ^Font) -> cstring ---
GetFontWrapAlignment :: proc(font: ^Font) -> HorizontalAlignment ---
GetFreeTypeVersion :: proc(major: ^c.int, minor: ^c.int, patch: ^c.int) ---
GetGlyphMetrics :: proc(font: ^Font, glyph: u32, min_x: ^c.int, max_x: ^c.int, min_y: ^c.int, max_y: ^c.int, advance: ^c.int) -> bool ---
GetGlyphScript :: proc(glyph: u32, script: ^c.char, script_size: c.size_t) -> bool ---
/// `stream`: A `sdl.IOStream` to provide a font's file data
/// `close_io`: Close src when the font is closed, false to leave it open
/// `point_size`: Font point size to use for the newly-opened font
OpenFontIO :: proc(stream: ^sdl.IOStream, close_io: bool, point_size: f32) -> ^Font ---
OpenFont :: proc(file: cstring, point_size: f32) -> ^Font ---
SetFontDirection :: proc(font: ^Font, direction: Direction) -> bool ---
SetFontHinting :: proc(font: ^Font, hinting_flags: HintingFlags) ---
SetFontKerning :: proc(font: ^Font, enabled: bool) ---
SetFontLineSkip :: proc(font: ^Font, lineskip: c.int) ---
SetFontOutline :: proc(font: ^Font, outline: c.int) -> bool ---
SetFontScript :: proc(font: ^Font, script: cstring) -> bool ---
SetFontSize :: proc(font: ^Font, pt_size: f32) -> bool ---
SetFontSizeDPI :: proc(font: ^Font, pt_size: f32, hdpi: c.int, vdpi: c.int) -> bool ---
SetFontStyle :: proc(font: ^Font, style: FontStyleFlags) ---
SetFontWrapAlignment :: proc(font: ^Font, horizontal_alignment: HorizontalAlignment) ---
SetGPUTextEngineWinding :: proc(engine: ^TextEngine, winding: GPUTextEngineWinding) ---
}
/// Text
@(default_calling_convention = "c", link_prefix = "TTF_")
foreign lib {
AppendTextString :: proc(text: ^Text, str: cstring, length: c.size_t) -> bool ---
CreateText :: proc(engine: ^TextEngine, font: ^Font, text: cstring, length: c.size_t) -> ^Text ---
DeleteTextString :: proc(text: ^Text, offset: c.int, length: c.int) -> bool ---
DestroyText :: proc(text: ^Text) ---
GetGPUTextDrawData :: proc(text: ^Text) -> ^GPUAtlasDrawSequence ---
GetGPUTextEngineWinding :: proc(engine: ^TextEngine) -> GPUTextEngineWinding ---
GetNextTextSubString :: proc(text: ^Text, substring: ^SubString, next: ^SubString) -> bool ---
GetPreviousTextSubString :: proc(text: ^Text, substring: ^SubString, previous: ^SubString) -> bool ---
/// Calculate the dimensions of a rendered string of UTF-8 text.
GetStringSize :: proc(font: ^Font, text: cstring, length: c.size_t, w: ^c.int, h: ^c.int) -> bool ---
GetStringSizeWrapped :: proc(font: ^Font, text: cstring, length: c.size_t, wrap_width: c.int, w: ^c.int, h: ^c.int) -> bool ---
GetTextColor :: proc(text: ^Text, r: ^u8, g: ^u8, b: ^u8, a: ^u8) -> bool ---
GetTextColorFloat :: proc(text: ^Text, r: ^f32, g: ^f32, b: ^f32, a: ^f32) -> bool ---
GetTextEngine :: proc(text: ^Text) -> ^TextEngine ---
GetTextFont :: proc(text: ^Text) -> ^Font ---
GetTextPosition :: proc(text: ^Text, x: ^c.int, y: ^c.int) -> bool ---
GetTextProperties :: proc(text: ^Text) -> sdl.PropertiesID ---
GetTextSize :: proc(text: ^Text, width: ^c.int, height: ^c.int) -> bool ---
GetTextSubString :: proc(text: ^Text, offset: c.int, substring: ^SubString) -> bool ---
GetTextSubStringForLine :: proc(text: ^Text, line: c.int, substring: ^SubString) -> bool ---
GetTextSubStringForPoint :: proc(text: ^Text, x: c.int, y: c.int, substring: ^SubString) -> bool ---
GetTextSubStringsForRange :: proc(text: ^Text, offset: c.int, length: c.int, count: ^c.int) -> [^]^SubString ---
GetTextWrapping :: proc(text: ^Text, wrap_length: ^c.int) -> bool ---
GetTextWrapWidth :: proc(text: ^Text, wrap_width: ^c.int) -> bool ---
InsertTextString :: proc(text: ^Text, offset: c.int, str: cstring, length: c.size_t) -> bool ---
// Calculate how much of a UTF-8 string will fit in a given width.
MeasureString :: proc(font: ^Font, text: cstring, length: c.size_t, max_width: c.int, measured_width: ^c.int, measured_length: ^c.size_t) -> bool ---
SetTextColor :: proc(text: ^Text, r: u8, g: u8, b: u8, a: u8) -> bool ---
SetTextColorFloat :: proc(text: ^Text, r: f32, g: f32, b: f32, a: f32) -> bool ---
SetTextEngine :: proc(text: ^Text, engine: ^TextEngine) -> bool ---
SetTextFont :: proc(text: ^Text, font: ^Font) -> bool ---
SetTextPosition :: proc(text: ^Text, x: c.int, y: c.int) -> bool ---
SetTextString :: proc(text: ^Text, str: cstring, length: c.size_t) -> bool ---
SetTextWrapping :: proc(text: ^Text, wrap_length: c.int) -> bool ---
SetTextWrapWhitespaceVisible :: proc(text: ^Text, visible: bool) -> bool ---
SetTextWrapWidth :: proc(text: ^Text, wrap_width: c.int) -> bool ---
}
+2 -2
View File
@@ -13,8 +13,8 @@ fi
# Convert GLSL to SPIRV # Convert GLSL to SPIRV
echo "Converting GLSL shaders to SPIRV..." echo "Converting GLSL shaders to SPIRV..."
mkdir -p renderer/res/shaders/compiled mkdir -p res/shaders/compiled
cd renderer/res/shaders/raw || exit cd res/shaders/raw || exit
glslangValidator -V quad.vert -o ../compiled/quad.vert.spv glslangValidator -V quad.vert -o ../compiled/quad.vert.spv
glslangValidator -V quad.frag -o ../compiled/quad.frag.spv glslangValidator -V quad.frag -o ../compiled/quad.frag.spv
glslangValidator -V text.vert -o ../compiled/text.vert.spv glslangValidator -V text.vert -o ../compiled/text.vert.spv
+38 -24
View File
@@ -5,8 +5,6 @@ import "core:mem"
import "core:os" import "core:os"
import sdl "vendor:sdl3" import sdl "vendor:sdl3"
tmp_quads: [dynamic]Quad
QuadPipeline :: struct { QuadPipeline :: struct {
instance_buffer: Buffer, instance_buffer: Buffer,
num_instances: u32, num_instances: u32,
@@ -14,12 +12,30 @@ QuadPipeline :: struct {
} }
Quad :: struct { Quad :: struct {
position_scale: [4]f32, position_scale: [4]f32,
corner_radii: [4]f32, corner_radii: [4]f32,
color: [4]f32, color: [4]f32,
border_color: [4]f32, border_color: [4]f32,
border_width: f32, border_width: f32,
_: [3]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) @(private)
@@ -37,7 +53,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),
@@ -48,11 +63,11 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua
} }
frag_info := sdl.GPUShaderCreateInfo { frag_info := sdl.GPUShaderCreateInfo {
code_size = len(frag_raw), code_size = len(frag_raw),
code = raw_data(frag_raw), code = raw_data(frag_raw),
entrypoint = ENTRY_POINT, entrypoint = ENTRY_POINT,
format = SHADER_TYPE, format = SHADER_TYPE,
stage = sdl.GPUShaderStage.FRAGMENT, stage = sdl.GPUShaderStage.FRAGMENT,
} }
vert_shader := sdl.CreateGPUShader(device, vert_info) vert_shader := sdl.CreateGPUShader(device, vert_info)
@@ -152,7 +167,7 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua
instance_buffer := create_buffer( instance_buffer := create_buffer(
device, device,
size_of(Quad) * BUFFER_INIT_SIZE, size_of(Quad) * BUFFER_INIT_SIZE,
sdl.GPUBufferUsageFlags { .VERTEX }, sdl.GPUBufferUsageFlags{.VERTEX},
) )
pipeline := QuadPipeline{instance_buffer, BUFFER_INIT_SIZE, sdl_pipeline} pipeline := QuadPipeline{instance_buffer, BUFFER_INIT_SIZE, sdl_pipeline}
@@ -163,10 +178,11 @@ create_quad_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Qua
@(private) @(private)
upload_quads :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { upload_quads :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
using global
num_quads := u32(len(tmp_quads)) num_quads := u32(len(tmp_quads))
size := num_quads * size_of(Quad) 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 // Write data
i_array := sdl.MapGPUTransferBuffer(device, quad_pipeline.instance_buffer.transfer, false) i_array := sdl.MapGPUTransferBuffer(device, quad_pipeline.instance_buffer.transfer, false)
@@ -193,7 +209,9 @@ draw_quads :: proc(
layer: ^Layer, layer: ^Layer,
load_op: sdl.GPULoadOp, load_op: sdl.GPULoadOp,
) { ) {
if layer.quad_len == 0 { using global
if layer.quad_instance_len == 0 {
return return
} }
@@ -220,17 +238,12 @@ draw_quads :: proc(
quad_offset := layer.quad_instance_start 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 { if scissor.quad_len == 0 {
continue continue
} }
if scissor.bounds.w == 0 || scissor.bounds.h == 0 { sdl.SetGPUScissor(render_pass, scissor.bounds)
sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)})
} else {
sdl.SetGPUScissor(render_pass, scissor.bounds)
}
sdl.DrawGPUPrimitives(render_pass, 6, scissor.quad_len, 0, quad_offset) sdl.DrawGPUPrimitives(render_pass, 6, scissor.quad_len, 0, quad_offset)
quad_offset += scissor.quad_len quad_offset += scissor.quad_len
} }
@@ -238,6 +251,7 @@ draw_quads :: proc(
} }
destroy_quad_pipeline :: proc(device: ^sdl.GPUDevice) { destroy_quad_pipeline :: proc(device: ^sdl.GPUDevice) {
using global
destroy_buffer(device, &quad_pipeline.instance_buffer) destroy_buffer(device, &quad_pipeline.instance_buffer)
sdl.ReleaseGPUGraphicsPipeline(device, quad_pipeline.sdl_pipeline) sdl.ReleaseGPUGraphicsPipeline(device, quad_pipeline.sdl_pipeline)
} }
+264 -100
View File
@@ -1,13 +1,13 @@
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_ttf "library:sdl3_ttf"
import sdl "vendor:sdl3" import sdl "vendor:sdl3"
import sdl_ttf "vendor:sdl3/ttf"
when ODIN_OS == .Darwin { when ODIN_OS == .Darwin {
SHADER_TYPE :: sdl.GPUShaderFormat{.MSL} SHADER_TYPE :: sdl.GPUShaderFormat{.MSL}
@@ -18,24 +18,85 @@ when ODIN_OS == .Darwin {
} }
BUFFER_INIT_SIZE: u32 : 256 BUFFER_INIT_SIZE: u32 : 256
INITIAL_LAYER_SIZE :: 5
INITIAL_SCISSOR_SIZE :: 10
dpi_scaling: f32 = 1.0 Global :: struct {
layers: [dynamic]Layer dpi_scaling: f32,
quad_pipeline: QuadPipeline curr_layer_index: uint,
text_pipeline: TextPipeline layers: [dynamic]Layer,
odin_context: runtime.Context 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,
clay_z_index: i16,
odin_context: runtime.Context,
quad_pipeline: QuadPipeline,
text_pipeline: TextPipeline,
}
// 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
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)
}
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
clay_z_index = 0
clear(&layers)
clear(&scissors)
clear(&tmp_text)
clear(&tmp_quads)
}
global: Global
Rectangle :: struct {
x: f32,
y: f32,
w: f32,
h: f32,
}
// TODO New layer for each z-index/batch
Layer :: struct { Layer :: struct {
bounds: Rectangle,
quad_instance_start: u32, quad_instance_start: u32,
quad_len: u32, quad_instance_len: u32,
text_instance_start: u32, text_instance_start: u32,
text_instance_len: u32, text_instance_len: u32,
text_vertex_start: u32, text_vertex_start: u32,
text_vertex_len: u32, text_vertex_len: u32,
text_index_start: u32, text_index_start: u32,
text_index_len: u32, text_index_len: u32,
scissors: [dynamic]Scissor, scissor_start: u32,
scissor_len: u32,
} }
Scissor :: struct { Scissor :: struct {
@@ -54,22 +115,29 @@ init :: proc(
window_height: f32, window_height: f32,
ctx: runtime.Context, 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() 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.Initialize(arena, {window_width, window_height}, {handler = clay_error_handler})
clay.SetMeasureTextFunction(measure_text, nil) 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) { clay_error_handler :: proc "c" (errorData: clay.ErrorData) {
context = odin_context context = global.odin_context
log.error("Clay error:", errorData.errorType, errorData.errorText) log.error("Clay error:", errorData.errorType, errorData.errorText)
} }
@@ -79,6 +147,7 @@ measure_text :: proc "c" (
config: ^clay.TextElementConfig, config: ^clay.TextElementConfig,
user_data: rawptr, user_data: rawptr,
) -> clay.Dimensions { ) -> clay.Dimensions {
using global
context = odin_context context = odin_context
text := string(text.chars[:text.length]) text := string(text.chars[:text.length])
c_text := strings.clone_to_cstring(text, context.temp_allocator) c_text := strings.clone_to_cstring(text, context.temp_allocator)
@@ -90,47 +159,146 @@ measure_text :: proc "c" (
return clay.Dimensions{width = f32(w) / dpi_scaling, height = f32(h) / dpi_scaling} return clay.Dimensions{width = f32(w) / dpi_scaling, height = f32(h) / dpi_scaling}
} }
destroy :: proc(device: ^sdl.GPUDevice) { /// Sets up renderer to begin upload to the GPU. Returns starting `Layer` to begin processing primitives for
destroy_quad_pipeline(device) begin_prepare :: proc(bounds: Rectangle) -> ^Layer {
destroy_text_pipeline(device) using global
// Cleanup
clear_global()
// 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 {
bounds = bounds,
scissor_len = 1,
}
append(&layers, layer)
return &layers[curr_layer_index]
}
/// Creates a new layer
new_layer :: proc(prev_layer: ^Layer, bounds: Rectangle) -> ^Layer {
using global
layer := Layer {
bounds = bounds,
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 {
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) {
// 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 =====
// TODO scissoring support if I need it for primitives
prepare_quad :: proc(layer: ^Layer, quad: Quad) {
using global
append(&tmp_quads, quad)
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,
cmds: clay.ClayArray(clay.RenderCommand),
} }
/// Upload data to the GPU /// Upload data to the GPU
prepare :: proc( prepare_clay_batch :: proc(
device: ^sdl.GPUDevice, base_layer: ^Layer,
window: ^sdl.Window, mouse_pos: [2]f32,
cmd_buffer: ^sdl.GPUCommandBuffer, mouse_flags: sdl.MouseButtonFlags,
render_commands: ^clay.ClayArray(clay.RenderCommand), mouse_wheel_delta: [2]f32,
mouse_delta: [2]f32,
frame_time: f32, frame_time: f32,
batch: ^ClayBatch,
) { ) {
mouse_x, mouse_y: f32 using global
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 // Update clay internals
clay.SetPointerState(clay.Vector2{mouse_x, mouse_y}, .LEFT in mouse_flags) clay.SetPointerState(
clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_delta, frame_time) clay.Vector2{mouse_pos.x - base_layer.bounds.x, mouse_pos.y - base_layer.bounds.y},
clay.SetLayoutDimensions({f32(window_w), f32(window_h)}) .LEFT in mouse_flags,
)
clay.UpdateScrollContainers(true, transmute(clay.Vector2)mouse_wheel_delta, frame_time)
clear(&layers) layer := base_layer
clear(&tmp_quads)
clear(&tmp_text)
tmp_quads = make([dynamic]Quad, 0, quad_pipeline.num_instances, context.temp_allocator)
tmp_text = make([dynamic]Text, 0, 20, context.temp_allocator)
layer := Layer {
scissors = make([dynamic]Scissor, 0, 10, context.temp_allocator),
}
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,
}
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) { switch (render_command.commandType) {
case clay.RenderCommandType.None: case clay.RenderCommandType.None:
@@ -151,10 +319,13 @@ prepare :: proc(
text_pipeline.cache[render_command.id] = sdl_text text_pipeline.cache[render_command.id] = sdl_text
} else { } else {
// Update text with c_string // Update text with c_string
sdl_ttf.SetTextString(sdl_text, c_text, 0) _ = sdl_ttf.SetTextString(sdl_text, c_text, 0)
} }
data := sdl_ttf.GetGPUTextDrawData(sdl_text) data := sdl_ttf.GetGPUTextDrawData(sdl_text)
if data == nil {
log.error("Failed to find GPUTextDrawData for sdl_text:", c_text)
}
if sdl_text == nil { if sdl_text == nil {
log.error("Could not create SDL text:", sdl.GetError()) log.error("Could not create SDL text:", sdl.GetError())
@@ -164,53 +335,61 @@ prepare :: proc(
Text{sdl_text, {bounds.x, bounds.y}, f32_color(render_data.textColor)}, Text{sdl_text, {bounds.x, bounds.y}, f32_color(render_data.textColor)},
) )
layer.text_instance_len += 1 layer.text_instance_len += 1
layer.text_vertex_len += u32(data.num_verticies) layer.text_vertex_len += u32(data.num_vertices)
layer.text_index_len += u32(data.num_indices) 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.Image:
case clay.RenderCommandType.ScissorStart: case clay.RenderCommandType.ScissorStart:
bounds := sdl.Rect { if bounds.w == 0 || bounds.h == 0 {
c.int(bounds.x * dpi_scaling), continue
c.int(bounds.y * dpi_scaling),
c.int(bounds.width * dpi_scaling),
c.int(bounds.height * dpi_scaling),
} }
new := new_scissor(&scissor)
if scissor.quad_len != 0 || scissor.text_len != 0 { curr_scissor := &scissors[layer.scissor_start + layer.scissor_len - 1]
append(&layer.scissors, scissor)
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),
}
} }
scissor = new
scissor.bounds = bounds
case clay.RenderCommandType.ScissorEnd: case clay.RenderCommandType.ScissorEnd:
new := new_scissor(&scissor)
if scissor.quad_len != 0 || scissor.text_len != 0 {
append(&layer.scissors, scissor)
}
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.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}, corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft},
color = color, color = color,
} }
append(&tmp_quads, quad) append(&tmp_quads, quad)
layer.quad_len += 1
scissor.quad_len += 1 layer.quad_instance_len += 1
scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1
case clay.RenderCommandType.Border: case clay.RenderCommandType.Border:
render_data := render_command.renderData.border render_data := render_command.renderData.border
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.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}, corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft},
//TODO: I was using a hack here to get the underlying color of the quad in the layout and then pass it into the color = f32_color(clay.Color{0.0, 0.0, 0.0, 0.0}),
// right border color, but Clay got rid of multi color support for borders so I need to just make a dedicated border pipeline
color = f32_color(
clay.Color{render_data.color.r, render_data.color.g, render_data.color.b, 0.0},
),
border_color = f32_color(render_data.color), border_color = f32_color(render_data.color),
// We only support one border width at the moment // We only support one border width at the moment
border_width = f32(render_data.width.top), border_width = f32(render_data.width.top),
@@ -218,25 +397,16 @@ prepare :: proc(
// Technically these should be drawn on top of everything else including children, but // 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 // for our use case we can just chuck these in with the quad pipeline
append(&tmp_quads, quad) append(&tmp_quads, quad)
layer.quad_len += 1 layer.quad_instance_len += 1
scissor.quad_len += 1 scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1
case clay.RenderCommandType.Custom: case clay.RenderCommandType.Custom:
} }
} }
//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)
} }
/// Render primitives /// Render primitives
draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCommandBuffer) { draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCommandBuffer) {
using global
swapchain_texture: ^sdl.GPUTexture swapchain_texture: ^sdl.GPUTexture
w, h: u32 w, h: u32
if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &w, &h) { if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &w, &h) {
@@ -250,6 +420,7 @@ draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCo
} }
for &layer, index in layers { for &layer, index in layers {
log.debug("Drawing layer", index)
draw_quads( draw_quads(
device, device,
window, window,
@@ -261,7 +432,7 @@ draw :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, cmd_buffer: ^sdl.GPUCo
index == 0 ? sdl.GPULoadOp.CLEAR : sdl.GPULoadOp.LOAD, index == 0 ? sdl.GPULoadOp.CLEAR : sdl.GPULoadOp.LOAD,
) )
draw_text(device, window, cmd_buffer, swapchain_texture, w, h, &layer) 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 :)
} }
} }
@@ -293,15 +464,8 @@ Globals :: struct {
push_globals :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, w: f32, h: f32) { push_globals :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, w: f32, h: f32) {
globals := Globals { globals := Globals {
ortho_rh(left = 0.0, top = 0.0, right = f32(w), bottom = f32(h), near = -1.0, far = 1.0), 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)) 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,
}
}
+59 -23
View File
@@ -4,16 +4,14 @@ import "core:c"
import "core:log" import "core:log"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import sdl_ttf "library:sdl3_ttf"
import sdl "vendor:sdl3" import sdl "vendor:sdl3"
import sdl_ttf "vendor:sdl3/ttf"
JETBRAINS_MONO_REGULAR: u16 : 0 JETBRAINS_MONO_REGULAR: u16 : 0
JETBRAINS_MONO_BOLD: u16 : 1 JETBRAINS_MONO_BOLD: u16 : 1
NUM_FONTS :: 2 NUM_FONTS :: 2
MAX_FONT_SIZE :: 120 MAX_FONT_SIZE :: 120
tmp_text: [dynamic]Text
@(private = "file") @(private = "file")
jetbrains_mono_regular := #load("res/fonts/JetBrainsMono-Regular.ttf") jetbrains_mono_regular := #load("res/fonts/JetBrainsMono-Regular.ttf")
@(private = "file") @(private = "file")
@@ -31,7 +29,7 @@ TextPipeline :: struct {
} }
get_font :: proc(id: u16, size: u16) -> ^sdl_ttf.Font { 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 { if font == nil {
log.debug("Font not found for size", size, "+ adding") 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) os.exit(1)
} }
font = f font = f
sdl_ttf.SetFontSizeDPI(f, f32(size), 72 * i32(dpi_scaling), 72 * i32(dpi_scaling)) _ = sdl_ttf.SetFontSizeDPI(
text_pipeline.fonts[id][size] = f f,
f32(size),
72 * i32(global.dpi_scaling),
72 * i32(global.dpi_scaling),
)
global.text_pipeline.fonts[id][size] = f
} }
return font return font
@@ -58,6 +61,38 @@ Text :: struct {
color: [4]f32, 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 // For upload
TextVert :: struct { TextVert :: struct {
pos_uv: [4]f32, pos_uv: [4]f32,
@@ -205,7 +240,7 @@ create_text_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Tex
log.error("Could not create text engine") log.error("Could not create text engine")
os.exit(1) os.exit(1)
} }
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTERCLOCKWISE) sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTER_CLOCKWISE)
// Create buffers // Create buffers
vertex_buffer := create_buffer( vertex_buffer := create_buffer(
@@ -241,18 +276,20 @@ create_text_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Tex
@(private) @(private)
upload_text :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { 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) vertices := make([dynamic]TextVert, 0, BUFFER_INIT_SIZE, context.temp_allocator)
indices := make([dynamic]c.int, 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) 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) append(&instances, text.position)
data := sdl_ttf.GetGPUTextDrawData(text.ref) data := sdl_ttf.GetGPUTextDrawData(text.ref)
for data != nil { for data != nil {
for i in 0 ..< data.num_verticies { for i in 0 ..< data.num_vertices {
pos := data.vertex_positions[i] pos := data.xy[i]
uv := data.uvs[i] uv := data.uv[i]
color := text.color color := text.color
append(&vertices, TextVert{{pos.x, -pos.y, uv.x, uv.y}, color}) append(&vertices, TextVert{{pos.x, -pos.y, uv.x, uv.y}, color})
} }
@@ -345,6 +382,7 @@ draw_text :: proc(
swapchain_h: u32, swapchain_h: u32,
layer: ^Layer, layer: ^Layer,
) { ) {
using global
if layer.text_instance_len == 0 { if layer.text_instance_len == 0 {
return return
} }
@@ -377,24 +415,19 @@ draw_text :: proc(
atlas: ^sdl.GPUTexture atlas: ^sdl.GPUTexture
layer_text := tmp_text[layer.text_instance_start:layer.text_instance_start + layer_text := tmp_text[layer.text_instance_start:][:layer.text_instance_len]
layer.text_instance_len] index_offset: u32 = layer.text_index_start
index_offset: u32 = layer.text_instance_start
vertex_offset: i32 = i32(layer.text_vertex_start) vertex_offset: i32 = i32(layer.text_vertex_start)
instance_offset: u32 = layer.text_instance_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 { if scissor.text_len == 0 {
continue continue
} }
if scissor.bounds.w == 0 || scissor.bounds.h == 0 { sdl.SetGPUScissor(render_pass, scissor.bounds)
sdl.SetGPUScissor(render_pass, sdl.Rect{0, 0, i32(swapchain_w), i32(swapchain_h)})
} else {
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) data := sdl_ttf.GetGPUTextDrawData(text.ref)
for data != nil { for data != nil {
@@ -421,7 +454,7 @@ draw_text :: proc(
) )
index_offset += u32(data.num_indices) index_offset += u32(data.num_indices)
vertex_offset += data.num_verticies vertex_offset += data.num_vertices
data = data.next data = data.next
} }
@@ -434,7 +467,10 @@ draw_text :: proc(
} }
destroy_text_pipeline :: proc(device: ^sdl.GPUDevice) { destroy_text_pipeline :: proc(device: ^sdl.GPUDevice) {
using global
destroy_buffer(device, &text_pipeline.vertex_buffer) destroy_buffer(device, &text_pipeline.vertex_buffer)
destroy_buffer(device, &text_pipeline.index_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) sdl.ReleaseGPUGraphicsPipeline(device, text_pipeline.sdl_pipeline)
} }