Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be516d39a3 | ||
|
|
e7cc935035 | ||
|
|
416b42c27e | ||
|
|
2b8488a1e2 | ||
|
|
7740632541 | ||
|
|
5ef1dafed2 | ||
|
|
86ee8083b5 | ||
|
|
f1028acb10 | ||
|
|
90602ff1ff |
@@ -0,0 +1,52 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$PROJECT = "sindri"
|
||||||
|
$COMMIT = git rev-parse --short HEAD
|
||||||
|
$VERSION = "dev-$(Get-Date -Format 'yyyy-MM-dd')-$COMMIT"
|
||||||
|
|
||||||
|
$OPTION = $args[0]
|
||||||
|
if ($args) { $args = $args[1..($args.Length - 1)] } else { $args = @() }
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Game compile
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path build | Out-Null
|
||||||
|
|
||||||
|
# Game.dll
|
||||||
|
odin build game/ -show-timings `
|
||||||
|
-collection:sindri=src `
|
||||||
|
-build-mode:dynamic `
|
||||||
|
-out:build/game_tmp -microarch:native "-define:VERSION=$VERSION-debug" -debug @args
|
||||||
|
|
||||||
|
# Need to use a temp file on Windows because it first writes an empty file,
|
||||||
|
# which the engine will load before it is actually fully written.
|
||||||
|
if (-not (Test-Path "build/game_tmp.dll")) {
|
||||||
|
Write-Error "error: build produced no game lib.dll"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Move-Item -Force "build/game_tmp.dll" "build/game.dll"
|
||||||
|
|
||||||
|
# ------------------------------------------
|
||||||
|
# Engine compile
|
||||||
|
|
||||||
|
# If the executable is already running, then don't try to build and start it.
|
||||||
|
$proc = Get-Process -Name $PROJECT -ErrorAction SilentlyContinue
|
||||||
|
if ($proc) {
|
||||||
|
Write-Output "Hot reloading..."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($OPTION -eq "debug") {
|
||||||
|
odin build src/ -show-timings `
|
||||||
|
-collection:sindri=src `
|
||||||
|
-collection:gram=vendor/gram/src `
|
||||||
|
-collection:wgpu=vendor `
|
||||||
|
-out:build/$PROJECT -microarch:native -use-separate-modules "-define:VERSION=$VERSION-debug" -debug @args
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
odin build src/ -show-timings `
|
||||||
|
-collection:sindri=src `
|
||||||
|
-collection:gram=vendor/gram/src `
|
||||||
|
-collection:wgpu=vendor `
|
||||||
|
-out:build/$PROJECT -microarch:native -o:speed "-define:VERSION=$VERSION" @args
|
||||||
@@ -5,6 +5,9 @@ set -eu
|
|||||||
PROJECT="sindri"
|
PROJECT="sindri"
|
||||||
VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)"
|
VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
OPTION="${1:-}"
|
||||||
|
shift
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
# Setup compiled wgpu binary
|
# Setup compiled wgpu binary
|
||||||
@@ -39,9 +42,7 @@ if pgrep -x $PROJECT >/dev/null; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" = "debug" ]; then
|
if [ "$OPTION" = "debug" ]; then
|
||||||
shift
|
|
||||||
|
|
||||||
odin build src/ -show-timings \
|
odin build src/ -show-timings \
|
||||||
-collection:sindri=src \
|
-collection:sindri=src \
|
||||||
-collection:gram=vendor/gram/src \
|
-collection:gram=vendor/gram/src \
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Odin
|
||||||
|
|
||||||
|
This document contains specifics about the workings of the Odin programming
|
||||||
|
language, dynamic linking and state management design patterns. These are
|
||||||
|
tested and validated findings, for continued reference by me.
|
||||||
|
|
||||||
|
## Dynamic Library Memory
|
||||||
|
|
||||||
|
When a program loads in a dynamic library, what happens with package globals?
|
||||||
|
|
||||||
|
Findings (macOS):
|
||||||
|
- The package globals are shared between the host and the lib
|
||||||
|
- After hot reloading the lib, the memory of the packge is not refreshed,
|
||||||
|
it keeps pointing to the package global of the host
|
||||||
|
|
||||||
|
The clanker states this is due to "flat-namespace symbol interposition".
|
||||||
|
|
||||||
|
Linux "ELF has symbol preemption: when resolving references, ld.so searches
|
||||||
|
globals scope in order — executable first, then loaded shared objects".
|
||||||
|
|
||||||
|
Windows PE has no interposition at all, every module (exe and each DLL) has its
|
||||||
|
own symbol table.
|
||||||
+20
-6
@@ -1,13 +1,16 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
|
import "sindri:input"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
|
||||||
import "sindri:core"
|
import "sindri:core"
|
||||||
|
import "sindri:test"
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
Game_Memory :: struct {
|
Game_Memory :: struct {
|
||||||
|
should_close: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
g: ^Game_Memory
|
g: ^Game_Memory
|
||||||
@@ -52,6 +55,8 @@ settings :: proc() -> core.Settings {
|
|||||||
@(export)
|
@(export)
|
||||||
init_once :: proc() {
|
init_once :: proc() {
|
||||||
fmt.println("init once")
|
fmt.println("init once")
|
||||||
|
|
||||||
|
test.test_proc = proc() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@(export)
|
@(export)
|
||||||
@@ -59,11 +64,22 @@ init :: proc() {
|
|||||||
fmt.println("hello from .dll!")
|
fmt.println("hello from .dll!")
|
||||||
|
|
||||||
g = new(Game_Memory)
|
g = new(Game_Memory)
|
||||||
|
memory_set(g)
|
||||||
|
|
||||||
|
test.test_proc = proc() { asd := 2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@(export)
|
@(export)
|
||||||
update :: proc(dt: f32) {
|
update :: proc(dt: f32) {
|
||||||
fmt.println("dt:", dt)
|
fmt.println("dt:", dt)
|
||||||
|
|
||||||
|
if input.key_state(.Key_Escape) == .Press {
|
||||||
|
g.should_close = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.println("GAME:", test.test)
|
||||||
|
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
|
||||||
|
test.test += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@(export)
|
@(export)
|
||||||
@@ -77,19 +93,17 @@ start := time.tick_now()
|
|||||||
should_close :: proc() -> bool {
|
should_close :: proc() -> bool {
|
||||||
elapsed := time.tick_since(start)
|
elapsed := time.tick_since(start)
|
||||||
seconds := time.duration_seconds(elapsed)
|
seconds := time.duration_seconds(elapsed)
|
||||||
if seconds > 4 do return true
|
// if seconds > 4 do return true
|
||||||
|
|
||||||
return false
|
return g.should_close
|
||||||
}
|
}
|
||||||
|
|
||||||
@(export)
|
@(export)
|
||||||
force_reload :: proc() -> bool {
|
force_reload :: proc() -> bool {
|
||||||
// TODO: read keypress
|
return input.key_state(.Key_F5) == .Press
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(export)
|
@(export)
|
||||||
force_restart :: proc() -> bool {
|
force_restart :: proc() -> bool {
|
||||||
// TODO: read keypress
|
return input.key_state(.Key_F6) == .Press
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Settings :: struct {
|
|||||||
height: u16,
|
height: u16,
|
||||||
title: string,
|
title: string,
|
||||||
mode: WindowMode,
|
mode: WindowMode,
|
||||||
refresh: u16,
|
refresh: u16, // 0 = unlimited
|
||||||
vsync: bool,
|
vsync: bool,
|
||||||
// TODO:
|
// TODO:
|
||||||
// windowed resizable y/n
|
// windowed resizable y/n
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
import "sindri:input"
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Types
|
||||||
|
|
||||||
|
// event category, bitfield (?)
|
||||||
|
|
||||||
|
// Event_Data :: union {
|
||||||
|
Event :: union {
|
||||||
|
Window_Close_Event,
|
||||||
|
Window_Resize_Event,
|
||||||
|
Joystick_Connect_Event,
|
||||||
|
Joystick_Disconnect_Event,
|
||||||
|
Key_Press_Event,
|
||||||
|
Key_Release_Event,
|
||||||
|
Key_Repeat_Event,
|
||||||
|
Mouse_Button_Press_Event,
|
||||||
|
Mouse_Button_Release_Event,
|
||||||
|
Mouse_Position_Event,
|
||||||
|
Mouse_Scroll_Event,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event :: struct {
|
||||||
|
// data: Event_Data,
|
||||||
|
// handled: bool,
|
||||||
|
// }
|
||||||
|
|
||||||
|
Window_Close_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
Window_Resize_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Joystick_Connect_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Joystick_Disconnect_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Key_Press_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
key: input.Key,
|
||||||
|
mods: input.Mod_Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
Key_Release_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
key: input.Key,
|
||||||
|
mods: input.Mod_Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
Key_Repeat_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
key: input.Key,
|
||||||
|
mods: input.Mod_Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse_Button_Press_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
button: input.Mouse_Button,
|
||||||
|
mods: input.Mod_Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse_Button_Release_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
button: input.Mouse_Button,
|
||||||
|
mods: input.Mod_Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse_Position_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
x_pos: f32,
|
||||||
|
y_pos: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse_Scroll_Event :: struct {
|
||||||
|
handled: bool,
|
||||||
|
x_offset: f32,
|
||||||
|
y_offset: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
// I want to have a list of Listeners per Event type, but this is state
|
||||||
|
// listeners: map[typeid][dynamic]Listener
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
// Dispatcher(Event) -> forwards to the right function
|
||||||
|
on_event :: proc(event: Event) {
|
||||||
|
fmt.println("toby!")
|
||||||
|
|
||||||
|
#partial switch e in event {
|
||||||
|
case Window_Close_Event:
|
||||||
|
fmt.println("Close")
|
||||||
|
case Window_Resize_Event:
|
||||||
|
fmt.println("Resize:", e.width, e.height)
|
||||||
|
|
||||||
|
case Key_Press_Event:
|
||||||
|
fmt.println("Key press", e.key, e.mods)
|
||||||
|
case Key_Release_Event:
|
||||||
|
fmt.println("Key release", e.key, e.mods)
|
||||||
|
case Key_Repeat_Event:
|
||||||
|
fmt.println("Key repeat", e.key, e.mods)
|
||||||
|
|
||||||
|
case Mouse_Button_Press_Event:
|
||||||
|
fmt.println("Mouse press", e.button, e.mods)
|
||||||
|
case Mouse_Button_Release_Event:
|
||||||
|
fmt.println("Mouse release", e.button, e.mods)
|
||||||
|
case Mouse_Position_Event:
|
||||||
|
fmt.printfln("Mouse pos: {}x{}", e.x_pos, e.y_pos)
|
||||||
|
|
||||||
|
case Joystick_Connect_Event:
|
||||||
|
fmt.println("Joy con:", e.id)
|
||||||
|
case Joystick_Disconnect_Event:
|
||||||
|
fmt.println("Joy dis:", e.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
Have the events queued up into a frame buffer that gets drained at a specific time.
|
||||||
|
*/
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
package sindri
|
|
||||||
|
|
||||||
import "core:strings"
|
|
||||||
import "vendor:glfw"
|
|
||||||
|
|
||||||
import "wgpu:wgpu"
|
|
||||||
import "wgpu:wgpu/glfwglue"
|
|
||||||
|
|
||||||
import "sindri:core"
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
OS :: struct {
|
|
||||||
window: glfw.WindowHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
os_init :: proc(settings: core.Settings) {
|
|
||||||
if !glfw.Init() {
|
|
||||||
panic("[glfw] init failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
|
|
||||||
state.os.window = glfw.CreateWindow(
|
|
||||||
i32(settings.width),
|
|
||||||
i32(settings.height),
|
|
||||||
strings.unsafe_string_to_cstring(settings.title),
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
glfw.SetFramebufferSizeCallback(state.os.window, size_callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_set_monitor :: proc(settings: core.Settings) {
|
|
||||||
monitor := glfw.GetPrimaryMonitor()
|
|
||||||
x_pos: i32
|
|
||||||
y_pos: i32
|
|
||||||
width := i32(settings.width)
|
|
||||||
height := i32(settings.height)
|
|
||||||
target_monitor: glfw.MonitorHandle
|
|
||||||
|
|
||||||
mode := glfw.GetVideoMode(monitor)
|
|
||||||
|
|
||||||
switch settings.mode {
|
|
||||||
case .Fullscreen:
|
|
||||||
target_monitor = monitor
|
|
||||||
case .Borderless:
|
|
||||||
// FIXME: On macOS, borderless also fills the notch area
|
|
||||||
|
|
||||||
// Monitor origin on the virtual desktop
|
|
||||||
x_pos, y_pos = glfw.GetMonitorPos(monitor)
|
|
||||||
|
|
||||||
width = mode.width
|
|
||||||
height = mode.height
|
|
||||||
|
|
||||||
glfw.SetWindowAttrib(
|
|
||||||
state.os.window,
|
|
||||||
glfw.DECORATED,
|
|
||||||
i32(glfw.FALSE),
|
|
||||||
)
|
|
||||||
case .Windowed:
|
|
||||||
// Put window in the center of the monitor
|
|
||||||
x_pos = (mode.width - width) / 2
|
|
||||||
y_pos = (mode.height - height) / 2
|
|
||||||
|
|
||||||
glfw.SetWindowAttrib(
|
|
||||||
state.os.window,
|
|
||||||
glfw.DECORATED,
|
|
||||||
i32(glfw.TRUE),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh := settings.vsync ? mode.refresh_rate : i32(settings.refresh)
|
|
||||||
if refresh == 0 do refresh = glfw.DONT_CARE
|
|
||||||
|
|
||||||
glfw.SetWindowMonitor(
|
|
||||||
state.os.window,
|
|
||||||
target_monitor,
|
|
||||||
x_pos,
|
|
||||||
y_pos,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
refresh,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_should_close :: proc() -> bool {
|
|
||||||
return bool(glfw.WindowShouldClose(state.os.window))
|
|
||||||
}
|
|
||||||
|
|
||||||
os_set_should_close :: proc(val: bool) {
|
|
||||||
glfw.SetWindowShouldClose(state.os.window, b32(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
os_poll_events :: proc() {
|
|
||||||
glfw.PollEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
os_destroy :: proc() {
|
|
||||||
glfw.DestroyWindow(state.os.window)
|
|
||||||
glfw.Terminate()
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_framebuffer_size :: proc() -> (width, height: u32) {
|
|
||||||
iw, ih := glfw.GetFramebufferSize(state.os.window)
|
|
||||||
return u32(iw), u32(ih)
|
|
||||||
}
|
|
||||||
|
|
||||||
os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
|
|
||||||
return glfwglue.GetSurface(instance, state.os.window)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private = "file")
|
|
||||||
size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package input
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
// Returns the state of the keyboard key
|
||||||
|
// implementation in platform package, to prevent cyclic dependency
|
||||||
|
key_state: proc(key: Key) -> Action
|
||||||
|
|
||||||
|
// Returns the state of the mouse button
|
||||||
|
// implementation in platform package, to prevent cyclic dependency
|
||||||
|
mouse_button_state: proc(button: Mouse_Button) -> Action
|
||||||
|
|
||||||
|
// Returns the position of the mouse cursor
|
||||||
|
// implementation in platform package, to prevent cyclic dependency
|
||||||
|
mouse_position: proc() -> (x_pos: f32, y_pos: f32)
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Private functions
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package input
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
// Button/Key states
|
||||||
|
Action :: enum i8 {
|
||||||
|
None = -1,
|
||||||
|
Release = 0,
|
||||||
|
Press = 1,
|
||||||
|
Repeat = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
Key :: enum i16 {
|
||||||
|
// The unknown key
|
||||||
|
Key_Unknown = -1,
|
||||||
|
|
||||||
|
// --- Printable keys ---
|
||||||
|
|
||||||
|
// Named printable keys
|
||||||
|
Key_Space = 32,
|
||||||
|
Key_Apostrophe = 39, // '
|
||||||
|
Key_Comma = 44, // ,
|
||||||
|
Key_Minus = 45, // -
|
||||||
|
Key_Period = 46, // .
|
||||||
|
Key_Slash = 47, // /
|
||||||
|
Key_Semicolon = 59, // ;
|
||||||
|
Key_Equal = 61, // =
|
||||||
|
Key_Left_Bracket = 91, // [
|
||||||
|
Key_Backslash = 92, // \
|
||||||
|
Key_Right_Bracket = 93, // ]
|
||||||
|
Key_Grave_Accent = 96, // `
|
||||||
|
Key_World_1 = 161, // non-US #1
|
||||||
|
Key_World_2 = 162, // non-US #2
|
||||||
|
|
||||||
|
// Alphanumeric characters
|
||||||
|
Key_0 = 48,
|
||||||
|
Key_1 = 49,
|
||||||
|
Key_2 = 50,
|
||||||
|
Key_3 = 51,
|
||||||
|
Key_4 = 52,
|
||||||
|
Key_5 = 53,
|
||||||
|
Key_6 = 54,
|
||||||
|
Key_7 = 55,
|
||||||
|
Key_8 = 56,
|
||||||
|
Key_9 = 57,
|
||||||
|
Key_A = 65,
|
||||||
|
Key_B = 66,
|
||||||
|
Key_C = 67,
|
||||||
|
Key_D = 68,
|
||||||
|
Key_E = 69,
|
||||||
|
Key_F = 70,
|
||||||
|
Key_G = 71,
|
||||||
|
Key_H = 72,
|
||||||
|
Key_I = 73,
|
||||||
|
Key_J = 74,
|
||||||
|
Key_K = 75,
|
||||||
|
Key_L = 76,
|
||||||
|
Key_M = 77,
|
||||||
|
Key_N = 78,
|
||||||
|
Key_O = 79,
|
||||||
|
Key_P = 80,
|
||||||
|
Key_Q = 81,
|
||||||
|
Key_R = 82,
|
||||||
|
Key_S = 83,
|
||||||
|
Key_T = 84,
|
||||||
|
Key_U = 85,
|
||||||
|
Key_V = 86,
|
||||||
|
Key_W = 87,
|
||||||
|
Key_X = 88,
|
||||||
|
Key_Y = 89,
|
||||||
|
Key_Z = 90,
|
||||||
|
|
||||||
|
// --- Function keys ---
|
||||||
|
|
||||||
|
// Named non-printable keys
|
||||||
|
Key_Escape = 256,
|
||||||
|
Key_Enter = 257,
|
||||||
|
Key_Tab = 258,
|
||||||
|
Key_Backspace = 259,
|
||||||
|
Key_Insert = 260,
|
||||||
|
Key_Delete = 261,
|
||||||
|
Key_Right = 262,
|
||||||
|
Key_Left = 263,
|
||||||
|
Key_Down = 264,
|
||||||
|
Key_Up = 265,
|
||||||
|
Key_Page_Up = 266,
|
||||||
|
Key_Page_Down = 267,
|
||||||
|
Key_Home = 268,
|
||||||
|
Key_End = 269,
|
||||||
|
Key_Caps_Lock = 280,
|
||||||
|
Key_Scroll_Lock = 281,
|
||||||
|
Key_Num_Lock = 282,
|
||||||
|
Key_Print_Screen = 283,
|
||||||
|
Key_Pause = 284,
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
Key_F1 = 290,
|
||||||
|
Key_F2 = 291,
|
||||||
|
Key_F3 = 292,
|
||||||
|
Key_F4 = 293,
|
||||||
|
Key_F5 = 294,
|
||||||
|
Key_F6 = 295,
|
||||||
|
Key_F7 = 296,
|
||||||
|
Key_F8 = 297,
|
||||||
|
Key_F9 = 298,
|
||||||
|
Key_F10 = 299,
|
||||||
|
Key_F11 = 300,
|
||||||
|
Key_F12 = 301,
|
||||||
|
Key_F13 = 302,
|
||||||
|
Key_F14 = 303,
|
||||||
|
Key_F15 = 304,
|
||||||
|
Key_F16 = 305,
|
||||||
|
Key_F17 = 306,
|
||||||
|
Key_F18 = 307,
|
||||||
|
Key_F19 = 308,
|
||||||
|
Key_F20 = 309,
|
||||||
|
Key_F21 = 310,
|
||||||
|
Key_F22 = 311,
|
||||||
|
Key_F23 = 312,
|
||||||
|
Key_F24 = 313,
|
||||||
|
Key_F25 = 314,
|
||||||
|
|
||||||
|
// Keypad numbers
|
||||||
|
Key_KP_0 = 320,
|
||||||
|
Key_KP_1 = 321,
|
||||||
|
Key_KP_2 = 322,
|
||||||
|
Key_KP_3 = 323,
|
||||||
|
Key_KP_4 = 324,
|
||||||
|
Key_KP_5 = 325,
|
||||||
|
Key_KP_6 = 326,
|
||||||
|
Key_KP_7 = 327,
|
||||||
|
Key_KP_8 = 328,
|
||||||
|
Key_KP_9 = 329,
|
||||||
|
|
||||||
|
// Keypad named function keys
|
||||||
|
Key_KP_Decimal = 330,
|
||||||
|
Key_KP_Divide = 331,
|
||||||
|
Key_KP_Multiply = 332,
|
||||||
|
Key_KP_Subtract = 333,
|
||||||
|
Key_KP_Add = 334,
|
||||||
|
Key_KP_Enter = 335,
|
||||||
|
Key_KP_Equal = 336,
|
||||||
|
|
||||||
|
// Modifier keys
|
||||||
|
Key_Left_Shift = 340,
|
||||||
|
Key_Left_Control = 341,
|
||||||
|
Key_Left_Alt = 342,
|
||||||
|
Key_Left_Super = 343,
|
||||||
|
Key_Right_Shift = 344,
|
||||||
|
Key_Right_Control = 345,
|
||||||
|
Key_Right_Alt = 346,
|
||||||
|
Key_Right_Super = 347,
|
||||||
|
Key_Menu = 348,
|
||||||
|
Key_Last = Key_Menu,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitmask for modifier keys
|
||||||
|
Mod :: enum i8 {
|
||||||
|
Shift = 0,
|
||||||
|
Control = 1,
|
||||||
|
Alt = 2,
|
||||||
|
Super = 3,
|
||||||
|
Caps_Lock = 4,
|
||||||
|
Num_Lock = 5,
|
||||||
|
}
|
||||||
|
Mod_Set :: bit_set[Mod]
|
||||||
|
|
||||||
|
// Mouse buttons
|
||||||
|
Mouse_Button :: enum i8 {
|
||||||
|
Button_1 = 0,
|
||||||
|
Button_2 = 1,
|
||||||
|
Button_3 = 2,
|
||||||
|
Button_4 = 3,
|
||||||
|
Button_5 = 4,
|
||||||
|
Button_6 = 5,
|
||||||
|
Button_7 = 6,
|
||||||
|
Button_8 = 7,
|
||||||
|
// Alias names
|
||||||
|
Last = Button_8,
|
||||||
|
Left = Button_1,
|
||||||
|
Right = Button_2,
|
||||||
|
Middle = Button_3,
|
||||||
|
}
|
||||||
+13
-8
@@ -2,8 +2,10 @@ package sindri
|
|||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
|
import "sindri:test"
|
||||||
|
|
||||||
import "sindri:hot_reload"
|
import "sindri:hot_reload"
|
||||||
|
import "sindri:platform"
|
||||||
|
|
||||||
VERSION :: #config(VERSION, "dev")
|
VERSION :: #config(VERSION, "dev")
|
||||||
|
|
||||||
@@ -20,13 +22,13 @@ main :: proc() {
|
|||||||
settings := api.settings()
|
settings := api.settings()
|
||||||
|
|
||||||
// Initialize Window
|
// Initialize Window
|
||||||
os_init(settings)
|
platform.os_init(settings)
|
||||||
os_set_monitor(settings)
|
platform.os_set_monitor(settings)
|
||||||
defer os_destroy()
|
defer platform.os_destroy()
|
||||||
|
|
||||||
// Initialize GPU resources
|
// Initialize GPU resources
|
||||||
instance_init()
|
platform.instance_init(settings)
|
||||||
defer instance_destroy()
|
defer platform.instance_destroy()
|
||||||
|
|
||||||
api.init_once()
|
api.init_once()
|
||||||
api.init()
|
api.init()
|
||||||
@@ -34,13 +36,16 @@ main :: proc() {
|
|||||||
gt: f64 = 0
|
gt: f64 = 0
|
||||||
dt: f32
|
dt: f32
|
||||||
|
|
||||||
for !os_should_close() && !hot_reload.should_close(&hr) {
|
for !platform.os_should_close() && !hot_reload.should_close(&hr) {
|
||||||
start := time.tick_now()
|
start := time.tick_now()
|
||||||
|
|
||||||
os_poll_events()
|
platform.os_poll_events()
|
||||||
api.update(dt)
|
api.update(dt)
|
||||||
|
|
||||||
frame(dt)
|
fmt.println("MAIN:", test.test)
|
||||||
|
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
|
||||||
|
|
||||||
|
platform.frame(dt)
|
||||||
|
|
||||||
dt = f32(time.duration_seconds(time.tick_since(start)))
|
dt = f32(time.duration_seconds(time.tick_since(start)))
|
||||||
gt += f64(dt)
|
gt += f64(dt)
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
package platform
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
import "vendor:glfw"
|
||||||
|
|
||||||
|
import "wgpu:wgpu"
|
||||||
|
import "wgpu:wgpu/glfwglue"
|
||||||
|
|
||||||
|
import "sindri:core"
|
||||||
|
import "sindri:event"
|
||||||
|
import "sindri:input"
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Types
|
||||||
|
|
||||||
|
OS :: struct {
|
||||||
|
window: glfw.WindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Constructor / destructor
|
||||||
|
|
||||||
|
os_init :: proc(settings: core.Settings) {
|
||||||
|
if !glfw.Init() {
|
||||||
|
panic("[glfw] init failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set window properties
|
||||||
|
glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API) // do not create OpenGL context
|
||||||
|
glfw.WindowHint(glfw.RESIZABLE, glfw.FALSE)
|
||||||
|
|
||||||
|
// Create GLFW window
|
||||||
|
state.os.window = glfw.CreateWindow(
|
||||||
|
i32(settings.width),
|
||||||
|
i32(settings.height),
|
||||||
|
strings.unsafe_string_to_cstring(settings.title),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
glfw.SetErrorCallback(error_callback)
|
||||||
|
glfw.SetWindowCloseCallback(state.os.window, window_close_callback)
|
||||||
|
// NOTE: SetFramebufferSize over SetWindowSize, to handle different DPIs
|
||||||
|
glfw.SetFramebufferSizeCallback(state.os.window, size_callback)
|
||||||
|
glfw.SetKeyCallback(state.os.window, key_callback)
|
||||||
|
glfw.SetMouseButtonCallback(state.os.window, mouse_button_callback)
|
||||||
|
glfw.SetCursorPosCallback(state.os.window, cursor_pos_callback)
|
||||||
|
glfw.SetScrollCallback(state.os.window, scroll_callback)
|
||||||
|
glfw.SetJoystickCallback(joystick_callback)
|
||||||
|
|
||||||
|
// Register input functions
|
||||||
|
input.key_state = key_state
|
||||||
|
input.mouse_button_state = mouse_button_state
|
||||||
|
input.mouse_position = mouse_position
|
||||||
|
}
|
||||||
|
|
||||||
|
os_destroy :: proc() {
|
||||||
|
glfw.DestroyWindow(state.os.window)
|
||||||
|
glfw.Terminate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
os_set_monitor :: proc(settings: core.Settings) {
|
||||||
|
monitor := glfw.GetPrimaryMonitor()
|
||||||
|
x_pos: i32
|
||||||
|
y_pos: i32
|
||||||
|
width := i32(settings.width)
|
||||||
|
height := i32(settings.height)
|
||||||
|
target_monitor: glfw.MonitorHandle
|
||||||
|
|
||||||
|
mode := glfw.GetVideoMode(monitor)
|
||||||
|
|
||||||
|
switch settings.mode {
|
||||||
|
case .Fullscreen:
|
||||||
|
target_monitor = monitor
|
||||||
|
case .Borderless:
|
||||||
|
// FIXME: On macOS, borderless also fills the notch area
|
||||||
|
|
||||||
|
// Monitor origin on the virtual desktop
|
||||||
|
x_pos, y_pos = glfw.GetMonitorPos(monitor)
|
||||||
|
|
||||||
|
width = mode.width
|
||||||
|
height = mode.height
|
||||||
|
|
||||||
|
glfw.SetWindowAttrib(
|
||||||
|
state.os.window,
|
||||||
|
glfw.DECORATED,
|
||||||
|
i32(glfw.FALSE),
|
||||||
|
)
|
||||||
|
case .Windowed:
|
||||||
|
// Put window in the center of the monitor
|
||||||
|
x_pos = (mode.width - width) / 2
|
||||||
|
y_pos = (mode.height - height) / 2
|
||||||
|
|
||||||
|
glfw.SetWindowAttrib(
|
||||||
|
state.os.window,
|
||||||
|
glfw.DECORATED,
|
||||||
|
i32(glfw.TRUE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh := settings.refresh == 0 ? glfw.DONT_CARE : i32(settings.refresh)
|
||||||
|
|
||||||
|
glfw.SetWindowMonitor(
|
||||||
|
state.os.window,
|
||||||
|
target_monitor,
|
||||||
|
x_pos,
|
||||||
|
y_pos,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
refresh,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
os_set_callback_proc :: proc(window: rawptr) {
|
||||||
|
glfw.SetWindowUserPointer(state.os.window, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
os_should_close :: proc() -> bool {
|
||||||
|
return bool(glfw.WindowShouldClose(state.os.window))
|
||||||
|
}
|
||||||
|
|
||||||
|
os_set_should_close :: proc(val: bool) {
|
||||||
|
glfw.SetWindowShouldClose(state.os.window, b32(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
os_poll_events :: proc() {
|
||||||
|
glfw.PollEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
os_get_framebuffer_size :: proc() -> (width, height: u32) {
|
||||||
|
iw, ih := glfw.GetFramebufferSize(state.os.window)
|
||||||
|
return u32(iw), u32(ih)
|
||||||
|
}
|
||||||
|
|
||||||
|
os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
|
||||||
|
return glfwglue.GetSurface(instance, state.os.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Private functions
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
key_state :: proc(key: input.Key) -> input.Action {
|
||||||
|
return input_action(glfw.GetKey(state.os.window, i32(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
mouse_button_state :: proc(button: input.Mouse_Button) -> input.Action {
|
||||||
|
return input_action(glfw.GetMouseButton(state.os.window, i32(button)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
mouse_position :: proc() -> (x_pos: f32, y_pos: f32) {
|
||||||
|
x_pos_f64, y_pos_64 := glfw.GetCursorPos(state.os.window)
|
||||||
|
return f32(x_pos_f64), f32(y_pos_64)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
input_action :: proc(action: i32) -> input.Action {
|
||||||
|
if action == glfw.RELEASE do return input.Action.Release
|
||||||
|
else if action == glfw.PRESS do return input.Action.Press
|
||||||
|
else if action == glfw.REPEAT do return input.Action.Repeat
|
||||||
|
when ODIN_DEBUG do panic("[glfw] unknown action")
|
||||||
|
return .None
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
input_key :: proc(key: i32) -> input.Key {
|
||||||
|
return input.Key(key) // values match
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
input_mod_set :: proc(mods: i32) -> (set: input.Mod_Set) {
|
||||||
|
return transmute(input.Mod_Set)i8(mods & 0x3f) // values match bit position
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
input_mouse_button :: proc(button: i32) -> input.Mouse_Button {
|
||||||
|
return input.Mouse_Button(button) // values match
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error callback
|
||||||
|
@(private = "file")
|
||||||
|
error_callback :: proc "c" (error: i32, description: cstring) {
|
||||||
|
context = state.ctx
|
||||||
|
fmt.eprintfln("error: GLFW {}: {}", error, description)
|
||||||
|
} // GLFWerrorfun
|
||||||
|
|
||||||
|
// Window close callback
|
||||||
|
@(private = "file")
|
||||||
|
window_close_callback :: proc "c" (window: glfw.WindowHandle) {
|
||||||
|
context = state.ctx
|
||||||
|
event.on_event(event.Window_Close_Event{})
|
||||||
|
} // GLFWwindowclosefun
|
||||||
|
|
||||||
|
// Window resize callback
|
||||||
|
@(private = "file")
|
||||||
|
size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
|
||||||
|
resize()
|
||||||
|
} // GLFWframebuffersizefun
|
||||||
|
|
||||||
|
// Keyboard callback
|
||||||
|
@(private = "file")
|
||||||
|
key_callback :: proc "c" (
|
||||||
|
window: glfw.WindowHandle,
|
||||||
|
key, scancode, action, mods: i32,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
if action == glfw.PRESS {
|
||||||
|
event.on_event(
|
||||||
|
event.Key_Press_Event {
|
||||||
|
key = input_key(key),
|
||||||
|
mods = input_mod_set(mods),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if action == glfw.RELEASE {
|
||||||
|
event.on_event(
|
||||||
|
event.Key_Release_Event {
|
||||||
|
key = input_key(key),
|
||||||
|
mods = input_mod_set(mods),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if action == glfw.REPEAT {
|
||||||
|
event.on_event(
|
||||||
|
event.Key_Repeat_Event {
|
||||||
|
key = input_key(key),
|
||||||
|
mods = input_mod_set(mods),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} // GLFWkeyfun
|
||||||
|
|
||||||
|
// Mouse button callback
|
||||||
|
@(private = "file")
|
||||||
|
mouse_button_callback :: proc "c" (
|
||||||
|
window: glfw.WindowHandle,
|
||||||
|
button, action, mods: i32,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
if action == glfw.PRESS {
|
||||||
|
event.on_event(
|
||||||
|
event.Mouse_Button_Press_Event {
|
||||||
|
button = input_mouse_button(button),
|
||||||
|
mods = input_mod_set(mods),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if action == glfw.RELEASE {
|
||||||
|
event.on_event(
|
||||||
|
event.Mouse_Button_Release_Event {
|
||||||
|
button = input_mouse_button(button),
|
||||||
|
mods = input_mod_set(mods),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} // GLFWmousebuttonfun
|
||||||
|
|
||||||
|
// Mouse position callback
|
||||||
|
@(private = "file")
|
||||||
|
cursor_pos_callback :: proc "c" (
|
||||||
|
window: glfw.WindowHandle,
|
||||||
|
x_pos, y_pos: f64,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
event.on_event(
|
||||||
|
event.Mouse_Position_Event{x_pos = f32(x_pos), y_pos = f32(y_pos)},
|
||||||
|
)
|
||||||
|
} // GLFWcursorposfun
|
||||||
|
|
||||||
|
// Mouse scroll callback
|
||||||
|
@(private = "file")
|
||||||
|
scroll_callback :: proc "c" (
|
||||||
|
window: glfw.WindowHandle,
|
||||||
|
x_offset, y_offset: f64,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
event.on_event(
|
||||||
|
event.Mouse_Scroll_Event {
|
||||||
|
x_offset = f32(x_offset),
|
||||||
|
y_offset = f32(y_offset),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} // GLFWscrollfun
|
||||||
|
|
||||||
|
// Joystick connected / disconnected callback
|
||||||
|
@(private = "file")
|
||||||
|
joystick_callback :: proc "c" (id, connected: i32) {
|
||||||
|
context = state.ctx
|
||||||
|
if connected == glfw.CONNECTED {
|
||||||
|
event.on_event(event.Joystick_Connect_Event{id = id})
|
||||||
|
} else {
|
||||||
|
event.on_event(event.Joystick_Disconnect_Event{id = id})
|
||||||
|
}
|
||||||
|
} // GLFWjoystickfun
|
||||||
|
|
||||||
|
// References:
|
||||||
|
// - https://www.glfw.org/docs/latest/group__init.html
|
||||||
|
// - https://www.glfw.org/docs/latest/group__window.html
|
||||||
|
// - https://www.glfw.org/docs/latest/group__input.html
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
package platform
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:slice"
|
||||||
|
|
||||||
|
import "wgpu:wgpu"
|
||||||
|
|
||||||
|
import "sindri:core"
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
// Instance -> Surface -> Adapter -> Device -> Queue
|
||||||
|
state: struct {
|
||||||
|
ctx: runtime.Context,
|
||||||
|
os: OS,
|
||||||
|
// ----------------------------------------
|
||||||
|
instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface
|
||||||
|
surface: wgpu.Surface, // handle to a presentable surface (e.g. a window)
|
||||||
|
adapter: wgpu.Adapter, // handle to physical GPU
|
||||||
|
device: wgpu.Device, // open connection to a GPU
|
||||||
|
queue: wgpu.Queue, // handle to command queue on a Device
|
||||||
|
config: wgpu.SurfaceConfiguration,
|
||||||
|
module: wgpu.ShaderModule,
|
||||||
|
pipeline_layout: wgpu.PipelineLayout,
|
||||||
|
pipeline: wgpu.RenderPipeline,
|
||||||
|
// ----------------------------------------
|
||||||
|
capabilities: wgpu.SurfaceCapabilities,
|
||||||
|
vsync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Constructor/destructor
|
||||||
|
|
||||||
|
instance_init :: proc(settings: core.Settings) {
|
||||||
|
state.ctx = context
|
||||||
|
|
||||||
|
wgpu.SetLogCallback(log_callback, nil)
|
||||||
|
wgpu.SetLogLevel(.Warn)
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
state.instance = wgpu.CreateInstance(nil)
|
||||||
|
if state.instance == nil {
|
||||||
|
panic("[wgpu] WebGPU is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface
|
||||||
|
state.surface = os_get_surface(state.instance)
|
||||||
|
|
||||||
|
// Adapter
|
||||||
|
wgpu.InstanceRequestAdapter(
|
||||||
|
state.instance,
|
||||||
|
&{compatibleSurface = state.surface},
|
||||||
|
{callback = on_adapter},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Present modes
|
||||||
|
get_present_modes()
|
||||||
|
state.vsync = settings.vsync
|
||||||
|
|
||||||
|
// Device
|
||||||
|
wgpu.AdapterRequestDevice(state.adapter, nil, {callback = on_device})
|
||||||
|
}
|
||||||
|
|
||||||
|
instance_destroy :: proc() {
|
||||||
|
wgpu.RenderPipelineRelease(state.pipeline)
|
||||||
|
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
||||||
|
wgpu.ShaderModuleRelease(state.module)
|
||||||
|
wgpu.QueueRelease(state.queue)
|
||||||
|
wgpu.DeviceRelease(state.device)
|
||||||
|
wgpu.SurfaceCapabilitiesFreeMembers(state.capabilities)
|
||||||
|
wgpu.AdapterRelease(state.adapter)
|
||||||
|
wgpu.SurfaceRelease(state.surface)
|
||||||
|
wgpu.InstanceRelease(state.instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Public functions
|
||||||
|
|
||||||
|
log_callback :: proc "c" (
|
||||||
|
level: wgpu.LogLevel,
|
||||||
|
message: string,
|
||||||
|
userdata: rawptr,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
fmt.eprintfln("[wgpu:%v] %v", level, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
resize :: proc "c" () {
|
||||||
|
context = state.ctx
|
||||||
|
|
||||||
|
if state.surface == nil || state.device == nil do return
|
||||||
|
|
||||||
|
state.config.width, state.config.height = os_get_framebuffer_size()
|
||||||
|
wgpu.SurfaceConfigure(state.surface, &state.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame :: proc "c" (dt: f32) {
|
||||||
|
context = state.ctx
|
||||||
|
|
||||||
|
surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
|
||||||
|
switch surface_texture.status {
|
||||||
|
case .SuccessOptimal, .SuccessSuboptimal:
|
||||||
|
// All good, could handle suboptimal here.
|
||||||
|
case .Timeout, .Outdated, .Lost:
|
||||||
|
// Skip this frame, and re-configure surface.
|
||||||
|
if surface_texture.texture != nil {
|
||||||
|
wgpu.TextureRelease(surface_texture.texture)
|
||||||
|
}
|
||||||
|
resize()
|
||||||
|
return
|
||||||
|
case .Occluded:
|
||||||
|
// Window is occluded (e.g. minimized), skip this frame.
|
||||||
|
return
|
||||||
|
case .Error:
|
||||||
|
// Fatal error
|
||||||
|
fmt.panicf(
|
||||||
|
"[wgpu] triangle get_current_texture status=%v",
|
||||||
|
surface_texture.status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer wgpu.TextureRelease(surface_texture.texture)
|
||||||
|
|
||||||
|
frame := wgpu.TextureCreateView(surface_texture.texture, nil)
|
||||||
|
defer wgpu.TextureViewRelease(frame)
|
||||||
|
|
||||||
|
command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
|
||||||
|
defer wgpu.CommandEncoderRelease(command_encoder)
|
||||||
|
|
||||||
|
render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
|
||||||
|
command_encoder,
|
||||||
|
&{
|
||||||
|
colorAttachmentCount = 1,
|
||||||
|
colorAttachments = &wgpu.RenderPassColorAttachment {
|
||||||
|
view = frame,
|
||||||
|
loadOp = .Clear,
|
||||||
|
storeOp = .Store,
|
||||||
|
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
|
||||||
|
clearValue = {0, 1, 0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
||||||
|
wgpu.RenderPassEncoderDraw(
|
||||||
|
render_pass_encoder,
|
||||||
|
vertexCount = 3,
|
||||||
|
instanceCount = 1,
|
||||||
|
firstVertex = 0,
|
||||||
|
firstInstance = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
||||||
|
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
||||||
|
|
||||||
|
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
|
||||||
|
defer wgpu.CommandBufferRelease(command_buffer)
|
||||||
|
|
||||||
|
wgpu.QueueSubmit(state.queue, {command_buffer})
|
||||||
|
wgpu.SurfacePresent(state.surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Private functions
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
on_adapter :: proc "c" (
|
||||||
|
status: wgpu.RequestAdapterStatus,
|
||||||
|
adapter: wgpu.Adapter,
|
||||||
|
message: string,
|
||||||
|
userdata1: rawptr,
|
||||||
|
userdata2: rawptr,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
if status != .Success || adapter == nil {
|
||||||
|
fmt.panicf("[wgpu] request adapter failure: [%v] %s", status, message)
|
||||||
|
}
|
||||||
|
state.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
on_device :: proc "c" (
|
||||||
|
status: wgpu.RequestDeviceStatus,
|
||||||
|
device: wgpu.Device,
|
||||||
|
message: string,
|
||||||
|
userdata1: rawptr,
|
||||||
|
userdata2: rawptr,
|
||||||
|
) {
|
||||||
|
context = state.ctx
|
||||||
|
if status != .Success || device == nil {
|
||||||
|
fmt.panicf("[wgpu] request device failure: [%v] %s", status, message)
|
||||||
|
}
|
||||||
|
state.device = device
|
||||||
|
|
||||||
|
state.queue = wgpu.DeviceGetQueue(state.device)
|
||||||
|
|
||||||
|
width, height := os_get_framebuffer_size()
|
||||||
|
|
||||||
|
state.config = wgpu.SurfaceConfiguration {
|
||||||
|
device = state.device,
|
||||||
|
usage = {.RenderAttachment},
|
||||||
|
format = .BGRA8Unorm,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
presentMode = pick_present_mode(state.vsync),
|
||||||
|
alphaMode = .Opaque,
|
||||||
|
}
|
||||||
|
wgpu.SurfaceConfigure(state.surface, &state.config)
|
||||||
|
|
||||||
|
shader :: `
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||||
|
let x = f32(i32(in_vertex_index) - 1);
|
||||||
|
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||||
|
return vec4<f32>(x, y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}`
|
||||||
|
|
||||||
|
state.module = wgpu.DeviceCreateShaderModule(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
nextInChain = &wgpu.ShaderSourceWGSL {
|
||||||
|
sType = .ShaderSourceWGSL,
|
||||||
|
code = shader,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{})
|
||||||
|
state.pipeline = wgpu.DeviceCreateRenderPipeline(
|
||||||
|
state.device,
|
||||||
|
&{
|
||||||
|
layout = state.pipeline_layout,
|
||||||
|
vertex = {module = state.module, entryPoint = "vs_main"},
|
||||||
|
fragment = &{
|
||||||
|
module = state.module,
|
||||||
|
entryPoint = "fs_main",
|
||||||
|
targetCount = 1,
|
||||||
|
targets = &wgpu.ColorTargetState {
|
||||||
|
format = .BGRA8Unorm,
|
||||||
|
writeMask = wgpu.ColorWriteMaskFlags_All,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primitive = {topology = .TriangleList},
|
||||||
|
multisample = {count = 1, mask = 0xFFFFFFFF},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
get_present_modes :: proc() {
|
||||||
|
capabilities, status := wgpu.SurfaceGetCapabilities(
|
||||||
|
state.surface,
|
||||||
|
state.adapter,
|
||||||
|
)
|
||||||
|
if status != .Success {
|
||||||
|
fmt.panicf("[wgpu] surface capabilities failure: [%v]", status)
|
||||||
|
}
|
||||||
|
state.capabilities = capabilities
|
||||||
|
|
||||||
|
present_modes := capabilities.presentModes[:capabilities.presentModeCount]
|
||||||
|
fmt.println("[wgpu] supported present modes", present_modes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private = "file")
|
||||||
|
pick_present_mode :: proc(vsync: bool) -> wgpu.PresentMode {
|
||||||
|
present_modes := state.capabilities.presentModes[:state.capabilities.presentModeCount]
|
||||||
|
|
||||||
|
if len(present_modes) == 0 do return .Fifo
|
||||||
|
|
||||||
|
// Mimmick wgpu auto Vsync behavior
|
||||||
|
// https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html
|
||||||
|
if vsync {
|
||||||
|
if slice.contains(present_modes, wgpu.PresentMode.FifoRelaxed) {
|
||||||
|
return .FifoRelaxed // Adaptive Vsync
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if slice.contains(present_modes, wgpu.PresentMode.Immediate) {
|
||||||
|
return .Immediate // Vsync Off
|
||||||
|
}
|
||||||
|
if slice.contains(present_modes, wgpu.PresentMode.Mailbox) {
|
||||||
|
return .Mailbox // Fast Vsync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Fifo // Vsync On
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
test: int = 0
|
||||||
|
|
||||||
|
test_proc: proc()
|
||||||
-219
@@ -1,219 +0,0 @@
|
|||||||
package sindri
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
import "core:fmt"
|
|
||||||
|
|
||||||
import "wgpu:wgpu"
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
// Instance -> Surface -> Adapter -> Device -> Queue
|
|
||||||
state: struct {
|
|
||||||
ctx: runtime.Context,
|
|
||||||
os: OS,
|
|
||||||
instance: wgpu.Instance, // entry point: creates Adapter, Device and Surface
|
|
||||||
surface: wgpu.Surface, // handle to a presentable surface (e.g. a window)
|
|
||||||
adapter: wgpu.Adapter, // handle to physical GPU
|
|
||||||
device: wgpu.Device, // open connection to a GPU
|
|
||||||
queue: wgpu.Queue, // handle to command queue on a Device
|
|
||||||
config: wgpu.SurfaceConfiguration,
|
|
||||||
module: wgpu.ShaderModule,
|
|
||||||
pipeline_layout: wgpu.PipelineLayout,
|
|
||||||
pipeline: wgpu.RenderPipeline,
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
instance_init :: proc() {
|
|
||||||
state.ctx = context
|
|
||||||
|
|
||||||
state.instance = wgpu.CreateInstance(nil)
|
|
||||||
if state.instance == nil {
|
|
||||||
panic("WebGPU is not supported")
|
|
||||||
}
|
|
||||||
state.surface = os_get_surface(state.instance)
|
|
||||||
|
|
||||||
wgpu.InstanceRequestAdapter(
|
|
||||||
state.instance,
|
|
||||||
&{compatibleSurface = state.surface},
|
|
||||||
{callback = on_adapter},
|
|
||||||
)
|
|
||||||
|
|
||||||
on_adapter :: proc "c" (
|
|
||||||
status: wgpu.RequestAdapterStatus,
|
|
||||||
adapter: wgpu.Adapter,
|
|
||||||
message: string,
|
|
||||||
userdata1: rawptr,
|
|
||||||
userdata2: rawptr,
|
|
||||||
) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || adapter == nil {
|
|
||||||
fmt.panicf("request adapter failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.adapter = adapter
|
|
||||||
wgpu.AdapterRequestDevice(adapter, nil, {callback = on_device})
|
|
||||||
}
|
|
||||||
|
|
||||||
on_device :: proc "c" (
|
|
||||||
status: wgpu.RequestDeviceStatus,
|
|
||||||
device: wgpu.Device,
|
|
||||||
message: string,
|
|
||||||
userdata1: rawptr,
|
|
||||||
userdata2: rawptr,
|
|
||||||
) {
|
|
||||||
context = state.ctx
|
|
||||||
if status != .Success || device == nil {
|
|
||||||
fmt.panicf("request device failure: [%v] %s", status, message)
|
|
||||||
}
|
|
||||||
state.device = device
|
|
||||||
|
|
||||||
state.queue = wgpu.DeviceGetQueue(state.device)
|
|
||||||
|
|
||||||
width, height := os_get_framebuffer_size()
|
|
||||||
|
|
||||||
state.config = wgpu.SurfaceConfiguration {
|
|
||||||
device = state.device,
|
|
||||||
usage = {.RenderAttachment},
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
width = width,
|
|
||||||
height = height,
|
|
||||||
// https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html
|
|
||||||
presentMode = .Fifo, // .Fifo is essentially VSync
|
|
||||||
alphaMode = .Opaque,
|
|
||||||
}
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
|
|
||||||
shader :: `
|
|
||||||
@vertex
|
|
||||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
|
||||||
let x = f32(i32(in_vertex_index) - 1);
|
|
||||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
|
||||||
return vec4<f32>(x, y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}`
|
|
||||||
|
|
||||||
state.module = wgpu.DeviceCreateShaderModule(
|
|
||||||
state.device,
|
|
||||||
&{
|
|
||||||
nextInChain = &wgpu.ShaderSourceWGSL {
|
|
||||||
sType = .ShaderSourceWGSL,
|
|
||||||
code = shader,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(
|
|
||||||
state.device,
|
|
||||||
&{},
|
|
||||||
)
|
|
||||||
state.pipeline = wgpu.DeviceCreateRenderPipeline(
|
|
||||||
state.device,
|
|
||||||
&{
|
|
||||||
layout = state.pipeline_layout,
|
|
||||||
vertex = {module = state.module, entryPoint = "vs_main"},
|
|
||||||
fragment = &{
|
|
||||||
module = state.module,
|
|
||||||
entryPoint = "fs_main",
|
|
||||||
targetCount = 1,
|
|
||||||
targets = &wgpu.ColorTargetState {
|
|
||||||
format = .BGRA8Unorm,
|
|
||||||
writeMask = wgpu.ColorWriteMaskFlags_All,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
primitive = {topology = .TriangleList},
|
|
||||||
multisample = {count = 1, mask = 0xFFFFFFFF},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
instance_destroy :: proc() {
|
|
||||||
wgpu.RenderPipelineRelease(state.pipeline)
|
|
||||||
wgpu.PipelineLayoutRelease(state.pipeline_layout)
|
|
||||||
wgpu.ShaderModuleRelease(state.module)
|
|
||||||
wgpu.QueueRelease(state.queue)
|
|
||||||
wgpu.DeviceRelease(state.device)
|
|
||||||
wgpu.AdapterRelease(state.adapter)
|
|
||||||
wgpu.SurfaceRelease(state.surface)
|
|
||||||
wgpu.InstanceRelease(state.instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
resize :: proc "c" () {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
if state.surface == nil || state.device == nil do return
|
|
||||||
|
|
||||||
state.config.width, state.config.height = os_get_framebuffer_size()
|
|
||||||
wgpu.SurfaceConfigure(state.surface, &state.config)
|
|
||||||
}
|
|
||||||
|
|
||||||
frame :: proc "c" (dt: f32) {
|
|
||||||
context = state.ctx
|
|
||||||
|
|
||||||
surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
|
|
||||||
switch surface_texture.status {
|
|
||||||
case .SuccessOptimal, .SuccessSuboptimal:
|
|
||||||
// All good, could handle suboptimal here.
|
|
||||||
case .Timeout, .Outdated, .Lost:
|
|
||||||
// Skip this frame, and re-configure surface.
|
|
||||||
if surface_texture.texture != nil {
|
|
||||||
wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
}
|
|
||||||
resize()
|
|
||||||
return
|
|
||||||
case .Occluded:
|
|
||||||
// Window is occluded (e.g. minimized), skip this frame.
|
|
||||||
return
|
|
||||||
case .Error:
|
|
||||||
// Fatal error
|
|
||||||
fmt.panicf(
|
|
||||||
"[triangle] get_current_texture status=%v",
|
|
||||||
surface_texture.status,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
defer wgpu.TextureRelease(surface_texture.texture)
|
|
||||||
|
|
||||||
frame := wgpu.TextureCreateView(surface_texture.texture, nil)
|
|
||||||
defer wgpu.TextureViewRelease(frame)
|
|
||||||
|
|
||||||
command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
|
|
||||||
defer wgpu.CommandEncoderRelease(command_encoder)
|
|
||||||
|
|
||||||
render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
|
|
||||||
command_encoder,
|
|
||||||
&{
|
|
||||||
colorAttachmentCount = 1,
|
|
||||||
colorAttachments = &wgpu.RenderPassColorAttachment {
|
|
||||||
view = frame,
|
|
||||||
loadOp = .Clear,
|
|
||||||
storeOp = .Store,
|
|
||||||
depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
|
|
||||||
clearValue = {0, 1, 0, 1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
|
|
||||||
wgpu.RenderPassEncoderDraw(
|
|
||||||
render_pass_encoder,
|
|
||||||
vertexCount = 3,
|
|
||||||
instanceCount = 1,
|
|
||||||
firstVertex = 0,
|
|
||||||
firstInstance = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
wgpu.RenderPassEncoderEnd(render_pass_encoder)
|
|
||||||
wgpu.RenderPassEncoderRelease(render_pass_encoder)
|
|
||||||
|
|
||||||
command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
|
|
||||||
defer wgpu.CommandBufferRelease(command_buffer)
|
|
||||||
|
|
||||||
wgpu.QueueSubmit(state.queue, {command_buffer})
|
|
||||||
wgpu.SurfacePresent(state.surface)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user