Compare commits

...

7 Commits

19 changed files with 510 additions and 365 deletions

View File

@@ -103,7 +103,7 @@ TextAlignment :: enum EnumBackingType {
}
TextElementConfig :: struct {
userData: rawptr,
userData: rawptr,
textColor: Color,
fontId: u16,
fontSize: u16,
@@ -113,9 +113,12 @@ TextElementConfig :: struct {
textAlignment: TextAlignment,
}
AspectRatioElementConfig :: struct {
aspectRatio: f32,
}
ImageElementConfig :: struct {
imageData: rawptr,
sourceDimensions: Dimensions,
}
CustomElementConfig :: struct {
@@ -135,9 +138,10 @@ BorderElementConfig :: struct {
width: BorderWidth,
}
ScrollElementConfig :: struct {
horizontal: bool,
vertical: bool,
ClipElementConfig :: struct {
horizontal: bool, // clip overflowing elements on the "X" axis
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 {
@@ -169,6 +173,11 @@ FloatingAttachToElement :: enum EnumBackingType {
Root,
}
FloatingClipToElement :: enum EnumBackingType {
None,
AttachedParent,
}
FloatingElementConfig :: struct {
offset: Vector2,
expand: Dimensions,
@@ -177,6 +186,7 @@ FloatingElementConfig :: struct {
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
clipTo: FloatingClipToElement,
}
TextRenderData :: struct {
@@ -196,7 +206,6 @@ RectangleRenderData :: struct {
ImageRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
sourceDimensions: Dimensions,
imageData: rawptr,
}
@@ -235,7 +244,7 @@ ScrollContainerData :: struct {
scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions,
config: ScrollElementConfig,
config: ClipElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool,
}
@@ -329,16 +338,17 @@ ClayArray :: struct($type: typeid) {
}
ElementDeclaration :: struct {
id: ElementId,
layout: LayoutConfig,
id: ElementId,
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
scroll: ScrollElementConfig,
border: BorderElementConfig,
userData: rawptr,
cornerRadius: CornerRadius,
aspectRatio: AspectRatioElementConfig,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
clip: ClipElementConfig,
border: BorderElementConfig,
userData: rawptr,
}
ErrorType :: enum EnumBackingType {
@@ -385,6 +395,7 @@ foreign Clay {
Hovered :: proc() -> bool ---
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
PointerOver :: proc(id: ElementId) -> bool ---
GetScrollOffset :: proc() -> Vector2 ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
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) ---
@@ -437,6 +448,14 @@ PaddingAll :: proc(allPadding: u16) -> Padding {
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 {
return CornerRadius{radius, radius, radius, radius}
}

BIN
clay/linux-arm64/clay.a Normal file

Binary file not shown.

BIN
clay/linux/clay.a Normal file

Binary file not shown.

BIN
clay/macos-arm64/clay.a Normal file

Binary file not shown.

BIN
clay/macos/clay.a Normal file

Binary file not shown.

BIN
clay/wasm/clay.o Normal file

Binary file not shown.

BIN
clay/windows/clay.lib Normal file

Binary file not shown.

View File

@@ -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
@@ -18,14 +18,12 @@ device: ^sdl.GPUDevice
debug_enabled := false
body_text := clay.TextElementConfig {
fontId = renderer.JETBRAINS_MONO_REGULAR,
fontSize = 44,
textColor = { 1.0, 1.0, 1.0, 1.0 },
fontId = renderer.JETBRAINS_MONO_REGULAR,
fontSize = 44,
textColor = {0.0, 0.0, 0.0, 255.0},
}
main :: proc() {
defer destroy()
when ODIN_DEBUG == true {
context.logger = log.create_console_logger(lowest = .Debug)
@@ -88,7 +86,7 @@ main :: proc() {
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 {
log.error("Failed to create window:", sdl.GetError())
@@ -142,7 +140,9 @@ main :: proc() {
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")
break program
}
@@ -151,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)
@@ -163,10 +166,46 @@ destroy :: proc() {
update :: proc(cmd_buffer: ^sdl.GPUCommandBuffer, delta_time: u64) -> bool {
frame_time := f32(delta_time) / 1000.0
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
}
@@ -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()
if clay.UI()(
@@ -221,14 +261,70 @@ layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
layoutDirection = .TopToBottom,
sizing = {clay.SizingGrow({}), clay.SizingGrow({})},
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.

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 ---
}

View File

@@ -13,8 +13,8 @@ fi
# Convert GLSL to SPIRV
echo "Converting GLSL shaders to SPIRV..."
mkdir -p renderer/res/shaders/compiled
cd renderer/res/shaders/raw || exit
mkdir -p res/shaders/compiled
cd res/shaders/raw || exit
glslangValidator -V quad.vert -o ../compiled/quad.vert.spv
glslangValidator -V quad.frag -o ../compiled/quad.frag.spv
glslangValidator -V text.vert -o ../compiled/text.vert.spv

View File

@@ -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)
@@ -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("ShaderType:", SHADER_TYPE)
vert_info := sdl.GPUShaderCreateInfo {
code_size = len(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 {
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)
@@ -152,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}
@@ -163,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)
@@ -193,7 +209,9 @@ draw_quads :: proc(
layer: ^Layer,
load_op: sdl.GPULoadOp,
) {
if layer.quad_len == 0 {
using global
if layer.quad_instance_len == 0 {
return
}
@@ -220,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
}
@@ -238,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)
}

View File

@@ -1,13 +1,13 @@
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_ttf "library:sdl3_ttf"
import sdl "vendor:sdl3"
import sdl_ttf "vendor:sdl3/ttf"
when ODIN_OS == .Darwin {
SHADER_TYPE :: sdl.GPUShaderFormat{.MSL}
@@ -18,24 +18,85 @@ 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,
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 {
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,
text_vertex_len: u32,
text_index_start: u32,
text_index_len: u32,
scissors: [dynamic]Scissor,
scissor_start: u32,
scissor_len: u32,
}
Scissor :: struct {
@@ -54,22 +115,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)
}
@@ -79,6 +147,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)
@@ -90,47 +159,146 @@ 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(bounds: Rectangle) -> ^Layer {
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
prepare :: proc(
device: ^sdl.GPUDevice,
window: ^sdl.Window,
cmd_buffer: ^sdl.GPUCommandBuffer,
render_commands: ^clay.ClayArray(clay.RenderCommand),
mouse_delta: [2]f32,
prepare_clay_batch :: proc(
base_layer: ^Layer,
mouse_pos: [2]f32,
mouse_flags: sdl.MouseButtonFlags,
mouse_wheel_delta: [2]f32,
frame_time: f32,
batch: ^ClayBatch,
) {
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)
using global
// 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)})
clay.SetPointerState(
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)
clear(&layers)
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{}
layer := base_layer
// 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,
}
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:
@@ -151,10 +319,13 @@ prepare :: proc(
text_pipeline.cache[render_command.id] = sdl_text
} else {
// 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)
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())
@@ -164,53 +335,61 @@ prepare :: proc(
Text{sdl_text, {bounds.x, bounds.y}, f32_color(render_data.textColor)},
)
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)
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.width * dpi_scaling),
c.int(bounds.height * dpi_scaling),
if bounds.w == 0 || bounds.h == 0 {
continue
}
new := new_scissor(&scissor)
if scissor.quad_len != 0 || scissor.text_len != 0 {
append(&layer.scissors, scissor)
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),
}
}
scissor = new
scissor.bounds = bounds
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:
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},
corner_radii = {cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft},
position_scale = {bounds.x, bounds.y, bounds.w, bounds.h},
corner_radii = {cr.bottomRight, cr.topRight, cr.bottomLeft, cr.topLeft},
color = color,
}
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:
render_data := render_command.renderData.border
cr := render_data.cornerRadius
quad := Quad {
position_scale = {bounds.x, bounds.y, bounds.width, bounds.height},
corner_radii = {cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft},
//TODO: I was using a hack here to get the underlying color of the quad in the layout and then pass it into the
// 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},
),
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),
// We only support one border width at the moment
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
// 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
layer.quad_instance_len += 1
scissors[layer.scissor_start + layer.scissor_len - 1].quad_len += 1
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
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) {
@@ -250,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,
@@ -261,7 +432,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 :)
}
}
@@ -293,15 +464,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,
}
}

View File

@@ -4,16 +4,14 @@ import "core:c"
import "core:log"
import "core:mem"
import "core:os"
import sdl_ttf "library:sdl3_ttf"
import sdl "vendor:sdl3"
import sdl_ttf "vendor:sdl3/ttf"
JETBRAINS_MONO_REGULAR: u16 : 0
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
@@ -58,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,
@@ -205,7 +240,7 @@ create_text_pipeline :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> Tex
log.error("Could not create text engine")
os.exit(1)
}
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTERCLOCKWISE)
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTER_CLOCKWISE)
// Create buffers
vertex_buffer := create_buffer(
@@ -241,18 +276,20 @@ 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 {
for i in 0 ..< data.num_verticies {
pos := data.vertex_positions[i]
uv := data.uvs[i]
for i in 0 ..< data.num_vertices {
pos := data.xy[i]
uv := data.uv[i]
color := text.color
append(&vertices, TextVert{{pos.x, -pos.y, uv.x, uv.y}, color})
}
@@ -345,6 +382,7 @@ draw_text :: proc(
swapchain_h: u32,
layer: ^Layer,
) {
using global
if layer.text_instance_len == 0 {
return
}
@@ -377,24 +415,19 @@ 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
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] {
for &text in layer_text[scissor.text_start:][:scissor.text_len] {
data := sdl_ttf.GetGPUTextDrawData(text.ref)
for data != nil {
@@ -421,7 +454,7 @@ draw_text :: proc(
)
index_offset += u32(data.num_indices)
vertex_offset += data.num_verticies
vertex_offset += data.num_vertices
data = data.next
}
@@ -434,7 +467,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)
}