I am just learning Odin and I successfully got the Hellope example working in SDL3. I tried to add Lua scripting by adding T as a hotkey for a Lua input string, but the Windows terminal steals focus. I tried to build it with subsystem:windows and run it from Explorer without the terminal. Hitting T has the “Lua>” prompt appear on the SDL3 window, but my typing is not echoed, and when typed blindly, it doesn’t execute a simple Lua string after hitting Enter. Should I use sokol or raylib?
Here is the code a hybrid of orignal Hellope, me, and Grok:
package sdl3_example
import sdl "vendor:sdl3"
import "core:fmt"
import "core:math"
import "core:strings"
import lua "vendor:lua/5.4"
import "base:runtime"
// Available render drivers
@require_results
get_driver_names :: proc() -> (drivers: []cstring, count: i32) {
count = sdl.GetNumRenderDrivers()
drivers = make([]cstring, count)
for d in 0 ..< count {
drivers[d] = sdl.GetRenderDriver(d)
}
return
}
// Return first driver found in priority list or empty cstring
set_driver_by_priority :: proc(priority_list: []cstring) -> (driver: cstring) {
driver_list, _ := get_driver_names()
defer delete(driver_list)
for priority in priority_list {
for d in driver_list {
if d == priority {
return priority
}
}
}
return
}
Triangle :: struct {v0, v1, v2: sdl.FPoint}
sierpinksi :: proc(r: ^sdl.Renderer, t: Triangle, depth: int) {
if depth == 0 {
t_raw := [4]sdl.FPoint{t.v0, t.v1, t.v2, t.v0}
sdl.RenderLines(r, raw_data(t_raw[:]), 4)
}
if depth > 0 {
sierpinksi(r, {t.v0, (t.v0 + t.v1) / 2, (t.v0 + t.v2) / 2}, depth - 1)
sierpinksi(r, {t.v1, (t.v1 + t.v2) / 2, (t.v0 + t.v1) / 2}, depth - 1)
sierpinksi(r, {t.v2, (t.v0 + t.v2) / 2, (t.v1 + t.v2) / 2}, depth - 1)
}
}
// Package-level variables
s_depth: int = 5
color_paused: bool = false
vsync_enabled: bool = true
fps_cap_enabled: bool = true
lua_set_depth :: proc "c" (L: ^lua.State) -> i32 {
context = runtime.default_context()
n := lua.L_checkinteger(L, 1)
if n >= 0 && n <= 9 {
s_depth = int(n)
fmt.printf("Lua: set_depth(%d)\n", s_depth)
} else {
fmt.println("Lua: depth must be 0-9")
}
return 0
}
main :: proc() {
meta_ok := sdl.SetAppMetadata("Example Renderer", "1.0", "https://forum.odin-lang.org")
sdl_ok := sdl.Init({.VIDEO})
defer sdl.Quit()
if !meta_ok || !sdl_ok {
fmt.eprintln("Failed to initialize")
return
}
driver: cstring
when ODIN_OS == .Windows {
driver = set_driver_by_priority({"direct3d12", "direct3d11", "direct3d", "gpu", "opengl", "software"})
} else {
driver = set_driver_by_priority({"gpu", "opengl", "software"})
}
window := sdl.CreateWindow("Example Renderer", 640, 480, {.RESIZABLE})
renderer := sdl.CreateRenderer(window, driver)
sdl.SetRenderLogicalPresentation(renderer, 640, 480, .LETTERBOX)
defer sdl.DestroyWindow(window)
defer sdl.DestroyRenderer(renderer)
sdl.SetRenderVSync(renderer, 1)
display_id := sdl.GetDisplayForWindow(window)
display_mode := sdl.GetCurrentDisplayMode(display_id)
refresh_rate := display_mode.refresh_rate
fps_target := 60
fps: f64
color: sdl.FColor
// Lua
L := lua.L_newstate()
lua.L_openlibs(L)
defer lua.close(L)
lua.register(L, "set_depth", lua_set_depth)
lua.L_dostring(L, `function help() print("set_depth(n) -- n=0..9") end`)
drivers, _ := get_driver_names()
defer delete(drivers)
controls := [][]cstring {
{"Quit", "Q", "ESC"},
{"Pause Color", "P", "LMB"},
{"Toggle Vsync", "V", ""},
{"Toggle FPS Cap", "F", ""},
{"Triangle Depth", "0-9", ""},
{"Lua Input", "T", ""},
}
input_active := false
input_buffer: [256]byte
input_len := 0
main_loop: for {
frame_start := sdl.GetTicksNS()
for e: sdl.Event; sdl.PollEvent(&e); {
#partial switch e.type {
case .QUIT, .WINDOW_CLOSE_REQUESTED:
break main_loop
case .KEY_UP:
switch e.key.key {
case sdl.K_0..=sdl.K_9:
s_depth = int(e.key.key - sdl.K_0)
case sdl.K_T:
input_active = !input_active
if input_active {
input_len = 0
}
case sdl.K_P:
color_paused = !color_paused
case sdl.K_ESCAPE, sdl.K_Q:
if input_active {
input_active = false
} else {
break main_loop
}
case sdl.K_V:
vsync_enabled = !vsync_enabled
sdl.SetRenderVSync(renderer, vsync_enabled ? 1 : sdl.RENDERER_VSYNC_DISABLED)
case sdl.K_F:
fps_cap_enabled = !fps_cap_enabled
}
case .TEXT_INPUT:
if input_active {
txt := e.text.text
if txt != nil {
added := strings.clone_from_cstring(txt, context.temp_allocator)
for c in added {
if input_len >= len(input_buffer)-1 { break }
input_buffer[input_len] = u8(c)
input_len += 1
}
}
}
case .MOUSE_BUTTON_UP:
if e.button.button == sdl.BUTTON_LEFT {
color_paused = !color_paused
}
}
}
if !color_paused {
now := f64(frame_start) / 1000000000.0
color.r = f32(0.5 + 0.5 * sdl.sin(now))
color.g = f32(0.5 + 0.5 * sdl.sin(now + math.PI * 2 / 3))
color.b = f32(0.5 + 0.5 * sdl.sin(now + math.PI * 4 / 3))
color.a = sdl.ALPHA_OPAQUE_FLOAT
}
sdl.SetRenderDrawColorFloat(renderer, color.r, color.g, color.b, color.a)
sdl.RenderClear(renderer)
sdl.SetRenderDrawColorFloat(renderer, 1 - color.r, 1 - color.g, 1 - color.b, 255)
t := Triangle{{366, 20}, {112, 460}, {620, 460}}
sierpinksi(renderer, t, s_depth)
r: f32
row :: proc(row: ^f32, height: f32) -> f32 { row^ += height; return row^ }
sdl.SetRenderDrawColor(renderer, 0, 0, 0, 255)
sdl.RenderDebugText(renderer, 10, row(&r, 10), "hellope world!")
sdl.RenderDebugText(renderer, 10, row(&r, 20), fmt.ctprintf("%-16s%v", "Triangle Depth:", s_depth))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%v", "Color Paused:", color_paused))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%v", "VSync Enabled:", vsync_enabled))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%v", "Refresh Rate:", refresh_rate))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%v", "FPS Capped:", fps_cap_enabled))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%i", "FPS Target:", fps_target))
sdl.RenderDebugText(renderer, 10, row(&r, 10), fmt.ctprintf("%-16s%.2f", "FPS Current:", fps))
if input_active {
input_str := fmt.ctprintf("Lua> %s", cstring(&input_buffer[0]))
sdl.RenderDebugText(renderer, 10, 380, input_str)
}
free_all(context.temp_allocator)
sdl.RenderPresent(renderer)
frame_end := sdl.GetTicksNS()
npf_target := u64(1000000000 / fps_target)
if fps_cap_enabled && (frame_end - frame_start) < npf_target {
sdl.DelayPrecise(npf_target - (frame_end - frame_start))
frame_end = sdl.GetTicksNS()
}
fps = 1000000000.0 / f64(frame_end - frame_start)
}
}