Initial draw package

This commit is contained in:
Zachary Levy
2026-04-14 16:16:50 -07:00
parent 59c600d630
commit c786147720
26 changed files with 4371 additions and 1 deletions

140
draw/text.odin Normal file
View File

@@ -0,0 +1,140 @@
package draw
import "core:log"
import sdl "vendor:sdl3"
import sdl_ttf "vendor:sdl3/ttf"
Font_Id :: u16
Font_Key :: struct {
id: Font_Id,
size: u16,
}
Text_Cache :: struct {
engine: ^sdl_ttf.TextEngine,
font_bytes: [dynamic][]u8,
sdl_fonts: map[Font_Key]^sdl_ttf.Font,
cache: map[u32]^sdl_ttf.Text,
}
// Internal for fetching SDL TTF font pointer for rendering
get_font :: proc(id: Font_Id, size: u16) -> ^sdl_ttf.Font {
assert(int(id) < len(GLOB.text_cache.font_bytes), "Invalid font ID.")
key := Font_Key{id, size}
font := GLOB.text_cache.sdl_fonts[key]
if font == nil {
log.debug("Font with id:", id, "and size:", size, "not found. Adding..")
font_bytes := GLOB.text_cache.font_bytes[id]
if font_bytes == nil {
log.panicf("Font must first be registered with register_font before using (id=%d)", id)
}
font_io := sdl.IOFromConstMem(raw_data(font_bytes[:]), len(font_bytes))
if font_io == nil {
log.panicf("Failed to create IOStream for font id=%d: %s", id, sdl.GetError())
}
sdl_font := sdl_ttf.OpenFontIO(font_io, true, f32(size))
if sdl_font == nil {
log.panicf("Failed to create SDL font for font id=%d size=%d: %s", id, size, sdl.GetError())
}
if !sdl_ttf.SetFontSizeDPI(sdl_font, f32(size), 72 * i32(GLOB.dpi_scaling), 72 * i32(GLOB.dpi_scaling)) {
log.panicf("Failed to set font DPI for font id=%d size=%d: %s", id, size, sdl.GetError())
}
GLOB.text_cache.sdl_fonts[key] = sdl_font
return sdl_font
} else {
return font
}
}
// Returns `false` if there are more than max(u16) fonts
register_font :: proc(bytes: []u8) -> (id: Font_Id, ok: bool) #optional_ok {
if GLOB.text_cache.engine == nil {
log.panicf("Cannot register font: text system not initialized. Call init() first.")
}
if len(GLOB.text_cache.font_bytes) > int(max(Font_Id)) do return 0, false
log.debug("Registering font...")
append(&GLOB.text_cache.font_bytes, bytes)
return Font_Id(len(GLOB.text_cache.font_bytes) - 1), true
}
Text :: struct {
ref: ^sdl_ttf.Text,
position: [2]f32,
color: Color,
}
text :: proc(
id: u32,
txt: cstring,
pos: [2]f32,
font_id: Font_Id,
font_size: u16 = 44,
color: Color = {0, 0, 0, 255},
) -> Text {
sdl_text := GLOB.text_cache.cache[id]
if sdl_text == nil {
sdl_text = sdl_ttf.CreateText(GLOB.text_cache.engine, get_font(font_id, font_size), txt, 0)
if sdl_text == nil {
log.panicf("Failed to create SDL text: %s", sdl.GetError())
}
GLOB.text_cache.cache[id] = sdl_text
} else {
//TODO if IDs are always unique and never change the underlying text
// can get rid of this
if !sdl_ttf.SetTextString(sdl_text, txt, 0) {
log.panicf("Failed to update SDL text string: %s", sdl.GetError())
}
}
return Text{sdl_text, pos, color}
}
@(private, require_results)
init_text_cache :: proc(
device: ^sdl.GPUDevice,
allocator := context.allocator,
) -> (
text_cache: Text_Cache,
ok: bool,
) {
log.debug("Initializing text state")
if !sdl_ttf.Init() {
log.errorf("Failed to initialize SDL_ttf: %s", sdl.GetError())
return text_cache, false
}
engine := sdl_ttf.CreateGPUTextEngine(device)
if engine == nil {
log.errorf("Failed to create GPU text engine: %s", sdl.GetError())
sdl_ttf.Quit()
return text_cache, false
}
sdl_ttf.SetGPUTextEngineWinding(engine, .COUNTER_CLOCKWISE)
text_cache = Text_Cache {
engine = engine,
cache = make(map[u32]^sdl_ttf.Text, allocator = allocator),
}
log.debug("Done initializing text cache")
return text_cache, true
}
destroy_text_cache :: proc() {
for _, font in GLOB.text_cache.sdl_fonts {
sdl_ttf.CloseFont(font)
}
delete(GLOB.text_cache.sdl_fonts)
delete(GLOB.text_cache.font_bytes)
delete(GLOB.text_cache.cache)
sdl_ttf.DestroyGPUTextEngine(GLOB.text_cache.engine)
sdl_ttf.Quit()
}