init
This commit is contained in:
commit
cd40b79306
9 changed files with 772 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
bin/
|
||||
*.exe
|
||||
*.dll
|
||||
*.dylib
|
||||
*.so
|
||||
60
README.md
Normal file
60
README.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Odin + SDL3 GPU cubes (Vulkan only)
|
||||
|
||||
Small starter project in Odin that opens an SDL3 window, renders several 3D cubes with the SDL3 GPU API, and uses a Vulkan-only backend with SPIR-V shaders.
|
||||
|
||||
## Controls
|
||||
|
||||
- Left mouse drag: orbit camera
|
||||
- Middle mouse drag: pan
|
||||
- Mouse wheel: zoom in/out
|
||||
- `W/A/S/D`: move camera target
|
||||
- `Esc`: quit
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Odin compiler](https://odin-lang.org/)
|
||||
- SDL3 installed on your system (for runtime linking)
|
||||
- Vulkan-capable GPU/driver with SDL3 Vulkan support
|
||||
- `glslc` (from `shaderc`) to compile GLSL to SPIR-V
|
||||
|
||||
On macOS with Homebrew:
|
||||
|
||||
```bash
|
||||
brew install odin sdl3 shaderc
|
||||
```
|
||||
|
||||
## Build and run
|
||||
|
||||
Quick start:
|
||||
|
||||
```bash
|
||||
./run.sh
|
||||
```
|
||||
|
||||
The script compiles shaders, sets `VK_ICD_FILENAMES` to MoltenVK if available,
|
||||
adds `/opt/homebrew/lib` to `DYLD_FALLBACK_LIBRARY_PATH`, and runs the app.
|
||||
|
||||
Compile shaders first:
|
||||
|
||||
```bash
|
||||
glslc -fshader-stage=vert shaders/cubes.vert.glsl -o shaders/cubes.vert.spv
|
||||
glslc -fshader-stage=frag shaders/cubes.frag.glsl -o shaders/cubes.frag.spv
|
||||
```
|
||||
|
||||
From the project root:
|
||||
|
||||
```bash
|
||||
odin run src -out:bin/cubes_gpu
|
||||
```
|
||||
|
||||
Or build only:
|
||||
|
||||
```bash
|
||||
odin build src -out:bin/cubes_gpu
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Shaders source files are `shaders/cubes.vert.glsl` and `shaders/cubes.frag.glsl`.
|
||||
- Runtime shader binaries are `shaders/cubes.vert.spv` and `shaders/cubes.frag.spv`.
|
||||
- The app forces SDL3 GPU driver `vulkan` and exits if Vulkan is unavailable.
|
||||
3
build.sh
Executable file
3
build.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
glslc -fshader-stage=vert shaders/cubes.vert.glsl -o shaders/cubes.vert.spv
|
||||
glslc -fshader-stage=frag shaders/cubes.frag.glsl -o shaders/cubes.frag.spv
|
||||
odin run src -out:bin/cubes
|
||||
38
run.sh
Executable file
38
run.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if ! command -v glslc >/dev/null 2>&1; then
|
||||
echo "error: glslc not found. Install with: brew install shaderc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v odin >/dev/null 2>&1; then
|
||||
echo "error: odin not found. Install with: brew install odin"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${VK_ICD_FILENAMES:-}" ]]; then
|
||||
if [[ -f "/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json" ]]; then
|
||||
export VK_ICD_FILENAMES="/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json"
|
||||
elif [[ -f "/opt/homebrew/Cellar/molten-vk/1.4.1/etc/vulkan/icd.d/MoltenVK_icd.json" ]]; then
|
||||
export VK_ICD_FILENAMES="/opt/homebrew/Cellar/molten-vk/1.4.1/etc/vulkan/icd.d/MoltenVK_icd.json"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${DYLD_FALLBACK_LIBRARY_PATH:-}" ]]; then
|
||||
export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib"
|
||||
elif [[ ":${DYLD_FALLBACK_LIBRARY_PATH}:" != *":/opt/homebrew/lib:"* ]]; then
|
||||
export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib:${DYLD_FALLBACK_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
mkdir -p "bin"
|
||||
|
||||
glslc -fshader-stage=vert "shaders/cubes.vert.glsl" -o "shaders/cubes.vert.spv"
|
||||
glslc -fshader-stage=frag "shaders/cubes.frag.glsl" -o "shaders/cubes.frag.spv"
|
||||
|
||||
odin build src -out:bin/cubes_vulkan
|
||||
./bin/cubes_vulkan
|
||||
12
shaders/cubes.frag.glsl
Normal file
12
shaders/cubes.frag.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 3, binding = 0) uniform FragmentUniforms {
|
||||
mat4 u_mvp;
|
||||
vec4 u_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
out_color = u_color;
|
||||
}
|
||||
BIN
shaders/cubes.frag.spv
Normal file
BIN
shaders/cubes.frag.spv
Normal file
Binary file not shown.
12
shaders/cubes.vert.glsl
Normal file
12
shaders/cubes.vert.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
|
||||
layout(set = 1, binding = 0) uniform VertexUniforms {
|
||||
mat4 u_mvp;
|
||||
vec4 u_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
||||
}
|
||||
BIN
shaders/cubes.vert.spv
Normal file
BIN
shaders/cubes.vert.spv
Normal file
Binary file not shown.
642
src/main.odin
Normal file
642
src/main.odin
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
package main
|
||||
|
||||
import "core:c"
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
import sdl "vendor:sdl3"
|
||||
|
||||
Vec3 :: [3]f32
|
||||
Mat4 :: [16]f32 // column-major
|
||||
|
||||
Camera :: struct {
|
||||
center: Vec3,
|
||||
distance: f32,
|
||||
orientation: Rotor,
|
||||
fov_deg: f32,
|
||||
}
|
||||
|
||||
Debug_State :: struct {
|
||||
enabled: bool,
|
||||
accum: f32,
|
||||
}
|
||||
|
||||
Rotor :: struct {
|
||||
s, x, y, z: f32,
|
||||
}
|
||||
|
||||
Cube_Instance :: struct {
|
||||
pos: Vec3,
|
||||
scale: f32,
|
||||
color: Vec3,
|
||||
}
|
||||
|
||||
Vertex :: struct {
|
||||
pos: Vec3,
|
||||
}
|
||||
|
||||
Push_Constants :: struct {
|
||||
mvp: Mat4,
|
||||
color: [4]f32,
|
||||
}
|
||||
|
||||
mat4_identity :: proc() -> Mat4 {
|
||||
return Mat4{1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1}
|
||||
}
|
||||
|
||||
mat4_mul :: proc(a, b: Mat4) -> Mat4 {
|
||||
r := Mat4{}
|
||||
for c in 0 ..< 4 {
|
||||
for row in 0 ..< 4 {
|
||||
r[c*4+row] =
|
||||
a[0*4+row] * b[c*4+0] +
|
||||
a[1*4+row] * b[c*4+1] +
|
||||
a[2*4+row] * b[c*4+2] +
|
||||
a[3*4+row] * b[c*4+3]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
vec3_sub :: proc(a, b: Vec3) -> Vec3 {
|
||||
return Vec3{a[0] - b[0], a[1] - b[1], a[2] - b[2]}
|
||||
}
|
||||
|
||||
vec3_add :: proc(a, b: Vec3) -> Vec3 {
|
||||
return Vec3{a[0] + b[0], a[1] + b[1], a[2] + b[2]}
|
||||
}
|
||||
|
||||
vec3_scale :: proc(v: Vec3, s: f32) -> Vec3 {
|
||||
return Vec3{v[0] * s, v[1] * s, v[2] * s}
|
||||
}
|
||||
|
||||
vec3_dot :: proc(a, b: Vec3) -> f32 {
|
||||
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
}
|
||||
|
||||
vec3_cross :: proc(a, b: Vec3) -> Vec3 {
|
||||
return Vec3{
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0],
|
||||
}
|
||||
}
|
||||
|
||||
vec3_normalize :: proc(v: Vec3) -> Vec3 {
|
||||
len2 := vec3_dot(v, v)
|
||||
if len2 <= 0.000001 {
|
||||
return Vec3{0, 0, 0}
|
||||
}
|
||||
inv := 1.0 / math.sqrt(len2)
|
||||
return vec3_scale(v, inv)
|
||||
}
|
||||
|
||||
rotor_identity :: proc() -> Rotor {
|
||||
return Rotor{1, 0, 0, 0}
|
||||
}
|
||||
|
||||
rotor_normalize :: proc(r: Rotor) -> Rotor {
|
||||
len2 := r.s*r.s + r.x*r.x + r.y*r.y + r.z*r.z
|
||||
if len2 <= 0.000001 {
|
||||
return rotor_identity()
|
||||
}
|
||||
inv := 1.0 / math.sqrt(len2)
|
||||
return Rotor{r.s * inv, r.x * inv, r.y * inv, r.z * inv}
|
||||
}
|
||||
|
||||
rotor_mul :: proc(a, b: Rotor) -> Rotor {
|
||||
return Rotor{
|
||||
a.s*b.s - a.x*b.x - a.y*b.y - a.z*b.z,
|
||||
a.s*b.x + a.x*b.s + a.y*b.z - a.z*b.y,
|
||||
a.s*b.y - a.x*b.z + a.y*b.s + a.z*b.x,
|
||||
a.s*b.z + a.x*b.y - a.y*b.x + a.z*b.s,
|
||||
}
|
||||
}
|
||||
|
||||
rotor_from_axis_angle :: proc(axis: Vec3, angle: f32) -> Rotor {
|
||||
a := vec3_normalize(axis)
|
||||
h := angle * 0.5
|
||||
c := math.cos(h)
|
||||
s := math.sin(h)
|
||||
return Rotor{c, a[0] * s, a[1] * s, a[2] * s}
|
||||
}
|
||||
|
||||
rotate_vec3 :: proc(r: Rotor, v: Vec3) -> Vec3 {
|
||||
u := Vec3{r.x, r.y, r.z}
|
||||
t := vec3_scale(vec3_cross(u, v), 2.0)
|
||||
return vec3_add(v, vec3_add(vec3_scale(t, r.s), vec3_cross(u, t)))
|
||||
}
|
||||
|
||||
mat4_translate :: proc(p: Vec3) -> Mat4 {
|
||||
m := mat4_identity()
|
||||
m[12] = p[0]
|
||||
m[13] = p[1]
|
||||
m[14] = p[2]
|
||||
return m
|
||||
}
|
||||
|
||||
mat4_scale_uniform :: proc(s: f32) -> Mat4 {
|
||||
m := mat4_identity()
|
||||
m[0] = s
|
||||
m[5] = s
|
||||
m[10] = s
|
||||
return m
|
||||
}
|
||||
|
||||
mat4_perspective :: proc(fov_deg, aspect, z_near, z_far: f32) -> Mat4 {
|
||||
f := 1.0 / math.tan((fov_deg * 0.5) * (math.PI / 180.0))
|
||||
m := Mat4{}
|
||||
m[0] = f / aspect
|
||||
m[5] = f
|
||||
m[10] = (z_far + z_near) / (z_near - z_far)
|
||||
m[11] = -1
|
||||
m[14] = (2.0 * z_far * z_near) / (z_near - z_far)
|
||||
return m
|
||||
}
|
||||
|
||||
mat4_look_at :: proc(eye, target, up: Vec3) -> Mat4 {
|
||||
fwd := vec3_normalize(vec3_sub(target, eye))
|
||||
right := vec3_normalize(vec3_cross(fwd, up))
|
||||
real_up := vec3_cross(right, fwd)
|
||||
|
||||
m := mat4_identity()
|
||||
m[0] = right[0]
|
||||
m[1] = real_up[0]
|
||||
m[2] = -fwd[0]
|
||||
m[4] = right[1]
|
||||
m[5] = real_up[1]
|
||||
m[6] = -fwd[1]
|
||||
m[8] = right[2]
|
||||
m[9] = real_up[2]
|
||||
m[10] = -fwd[2]
|
||||
m[12] = -vec3_dot(right, eye)
|
||||
m[13] = -vec3_dot(real_up, eye)
|
||||
m[14] = vec3_dot(fwd, eye)
|
||||
return m
|
||||
}
|
||||
|
||||
camera_forward :: proc(c: Camera) -> Vec3 {
|
||||
return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 0, -1}))
|
||||
}
|
||||
|
||||
camera_right :: proc(c: Camera) -> Vec3 {
|
||||
return vec3_normalize(rotate_vec3(c.orientation, Vec3{1, 0, 0}))
|
||||
}
|
||||
|
||||
camera_up :: proc(c: Camera) -> Vec3 {
|
||||
return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 1, 0}))
|
||||
}
|
||||
|
||||
camera_position :: proc(c: Camera) -> Vec3 {
|
||||
return vec3_sub(c.center, vec3_scale(camera_forward(c), c.distance))
|
||||
}
|
||||
|
||||
read_file_or_fail :: proc(path: string) -> []u8 {
|
||||
file_bytes, err := os.read_entire_file(path, context.allocator)
|
||||
if err != nil {
|
||||
fmt.println("failed to read file:", path, "error:", err)
|
||||
return nil
|
||||
}
|
||||
return file_bytes
|
||||
}
|
||||
|
||||
create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage) -> ^sdl.GPUShader {
|
||||
bytes := read_file_or_fail(path)
|
||||
if len(bytes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ci := sdl.GPUShaderCreateInfo{
|
||||
code_size = uint(len(bytes)),
|
||||
code = raw_data(bytes),
|
||||
entrypoint = cstring("main"),
|
||||
format = sdl.GPUShaderFormat{.SPIRV},
|
||||
stage = stage,
|
||||
num_samplers = 0,
|
||||
num_storage_textures = 0,
|
||||
num_storage_buffers = 0,
|
||||
num_uniform_buffers = 1,
|
||||
props = 0,
|
||||
}
|
||||
|
||||
shader := sdl.CreateGPUShader(device, ci)
|
||||
delete(bytes)
|
||||
if shader == nil {
|
||||
fmt.println("CreateGPUShader failed:", path, "error:", sdl.GetError())
|
||||
}
|
||||
return shader
|
||||
}
|
||||
|
||||
copy_vertices_to_gpu :: proc(device: ^sdl.GPUDevice, cmd: ^sdl.GPUCommandBuffer, vertices: []Vertex, dst: ^sdl.GPUBuffer) -> bool {
|
||||
byte_count := len(vertices) * size_of(Vertex)
|
||||
tb_info := sdl.GPUTransferBufferCreateInfo{
|
||||
usage = .UPLOAD,
|
||||
size = u32(byte_count),
|
||||
props = 0,
|
||||
}
|
||||
tb := sdl.CreateGPUTransferBuffer(device, tb_info)
|
||||
if tb == nil {
|
||||
fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError())
|
||||
return false
|
||||
}
|
||||
defer sdl.ReleaseGPUTransferBuffer(device, tb)
|
||||
|
||||
mapped := sdl.MapGPUTransferBuffer(device, tb, false)
|
||||
if mapped == nil {
|
||||
fmt.println("MapGPUTransferBuffer failed:", sdl.GetError())
|
||||
return false
|
||||
}
|
||||
mem.copy_non_overlapping(mapped, raw_data(vertices), byte_count)
|
||||
sdl.UnmapGPUTransferBuffer(device, tb)
|
||||
|
||||
cp := sdl.BeginGPUCopyPass(cmd)
|
||||
if cp == nil {
|
||||
fmt.println("BeginGPUCopyPass failed:", sdl.GetError())
|
||||
return false
|
||||
}
|
||||
|
||||
src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0}
|
||||
dst_region := sdl.GPUBufferRegion{buffer = dst, offset = 0, size = u32(byte_count)}
|
||||
sdl.UploadToGPUBuffer(cp, src, dst_region, false)
|
||||
sdl.EndGPUCopyPass(cp)
|
||||
return true
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
if !sdl.Init(sdl.INIT_VIDEO) {
|
||||
fmt.println("SDL init failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.Quit()
|
||||
|
||||
window := sdl.CreateWindow("Odin + SDL3 GPU Cubes", 1280, 720, sdl.WINDOW_RESIZABLE)
|
||||
if window == nil {
|
||||
fmt.println("window create failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.DestroyWindow(window)
|
||||
|
||||
device := sdl.CreateGPUDevice(sdl.GPUShaderFormat{.SPIRV}, false, cstring("vulkan"))
|
||||
if device == nil {
|
||||
fmt.println("CreateGPUDevice failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.DestroyGPUDevice(device)
|
||||
|
||||
if !sdl.ClaimWindowForGPUDevice(device, window) {
|
||||
fmt.println("ClaimWindowForGPUDevice failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.ReleaseWindowFromGPUDevice(device, window)
|
||||
|
||||
driver_name := sdl.GetGPUDeviceDriver(device)
|
||||
fmt.println("SDL GPU driver:", string(driver_name))
|
||||
if string(driver_name) != "vulkan" {
|
||||
fmt.println("expected Vulkan driver, got:", string(driver_name))
|
||||
return
|
||||
}
|
||||
|
||||
vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX)
|
||||
if vertex_shader == nil {
|
||||
return
|
||||
}
|
||||
defer sdl.ReleaseGPUShader(device, vertex_shader)
|
||||
|
||||
fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT)
|
||||
if fragment_shader == nil {
|
||||
return
|
||||
}
|
||||
defer sdl.ReleaseGPUShader(device, fragment_shader)
|
||||
|
||||
color_format := sdl.GetGPUSwapchainTextureFormat(device, window)
|
||||
if color_format == .INVALID {
|
||||
fmt.println("invalid swapchain format:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
|
||||
vb_descs := [1]sdl.GPUVertexBufferDescription{
|
||||
{slot = 0, pitch = size_of(Vertex), input_rate = .VERTEX, instance_step_rate = 0},
|
||||
}
|
||||
vattrs := [1]sdl.GPUVertexAttribute{
|
||||
{location = 0, buffer_slot = 0, format = .FLOAT3, offset = 0},
|
||||
}
|
||||
|
||||
blend := sdl.GPUColorTargetBlendState{
|
||||
src_color_blendfactor = .ONE,
|
||||
dst_color_blendfactor = .ZERO,
|
||||
color_blend_op = .ADD,
|
||||
src_alpha_blendfactor = .ONE,
|
||||
dst_alpha_blendfactor = .ZERO,
|
||||
alpha_blend_op = .ADD,
|
||||
color_write_mask = sdl.GPUColorComponentFlags{.R, .G, .B, .A},
|
||||
enable_blend = false,
|
||||
enable_color_write_mask = true,
|
||||
}
|
||||
color_targets := [1]sdl.GPUColorTargetDescription{
|
||||
{format = color_format, blend_state = blend},
|
||||
}
|
||||
|
||||
pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{
|
||||
vertex_shader = vertex_shader,
|
||||
fragment_shader = fragment_shader,
|
||||
vertex_input_state = sdl.GPUVertexInputState{
|
||||
vertex_buffer_descriptions = &vb_descs[0],
|
||||
num_vertex_buffers = 1,
|
||||
vertex_attributes = &vattrs[0],
|
||||
num_vertex_attributes = 1,
|
||||
},
|
||||
primitive_type = .TRIANGLELIST,
|
||||
rasterizer_state = sdl.GPURasterizerState{
|
||||
fill_mode = .FILL,
|
||||
cull_mode = .BACK,
|
||||
front_face = .COUNTER_CLOCKWISE,
|
||||
depth_bias_constant_factor = 0,
|
||||
depth_bias_clamp = 0,
|
||||
depth_bias_slope_factor = 0,
|
||||
enable_depth_bias = false,
|
||||
enable_depth_clip = true,
|
||||
},
|
||||
multisample_state = sdl.GPUMultisampleState{
|
||||
sample_count = ._1,
|
||||
sample_mask = 0,
|
||||
enable_mask = false,
|
||||
enable_alpha_to_coverage = false,
|
||||
},
|
||||
depth_stencil_state = sdl.GPUDepthStencilState{
|
||||
compare_op = .LESS,
|
||||
back_stencil_state = sdl.GPUStencilOpState{fail_op = .KEEP, pass_op = .KEEP, depth_fail_op = .KEEP, compare_op = .ALWAYS},
|
||||
front_stencil_state = sdl.GPUStencilOpState{fail_op = .KEEP, pass_op = .KEEP, depth_fail_op = .KEEP, compare_op = .ALWAYS},
|
||||
compare_mask = 0,
|
||||
write_mask = 0,
|
||||
enable_depth_test = false,
|
||||
enable_depth_write = false,
|
||||
enable_stencil_test = false,
|
||||
},
|
||||
target_info = sdl.GPUGraphicsPipelineTargetInfo{
|
||||
color_target_descriptions = &color_targets[0],
|
||||
num_color_targets = 1,
|
||||
depth_stencil_format = .INVALID,
|
||||
has_depth_stencil_target = false,
|
||||
},
|
||||
props = 0,
|
||||
}
|
||||
|
||||
pipeline := sdl.CreateGPUGraphicsPipeline(device, pipeline_ci)
|
||||
if pipeline == nil {
|
||||
fmt.println("CreateGPUGraphicsPipeline failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.ReleaseGPUGraphicsPipeline(device, pipeline)
|
||||
|
||||
vertices := [36]Vertex{
|
||||
{pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}}, {pos = Vec3{0.5, -0.5, -0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}},
|
||||
{pos = Vec3{-0.5, 0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{-0.5, -0.5, -0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}},
|
||||
{pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}},
|
||||
{pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, -0.5, 0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, 0.5}},
|
||||
{pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, {pos = Vec3{-0.5, -0.5, 0.5}},
|
||||
{pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, 0.5, -0.5}},
|
||||
{pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}},
|
||||
}
|
||||
|
||||
vbuf_info := sdl.GPUBufferCreateInfo{
|
||||
usage = sdl.GPUBufferUsageFlags{.VERTEX},
|
||||
size = u32(len(vertices) * size_of(Vertex)),
|
||||
props = 0,
|
||||
}
|
||||
vbuf := sdl.CreateGPUBuffer(device, vbuf_info)
|
||||
if vbuf == nil {
|
||||
fmt.println("CreateGPUBuffer failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
defer sdl.ReleaseGPUBuffer(device, vbuf)
|
||||
|
||||
{
|
||||
setup_cmd := sdl.AcquireGPUCommandBuffer(device)
|
||||
if setup_cmd == nil {
|
||||
fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
if !copy_vertices_to_gpu(device, setup_cmd, vertices[:], vbuf) {
|
||||
_ = sdl.CancelGPUCommandBuffer(setup_cmd)
|
||||
return
|
||||
}
|
||||
if !sdl.SubmitGPUCommandBuffer(setup_cmd) {
|
||||
fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
init_yaw := rotor_from_axis_angle(Vec3{0, 1, 0}, 0.9)
|
||||
init_right := rotate_vec3(init_yaw, Vec3{1, 0, 0})
|
||||
init_pitch := rotor_from_axis_angle(init_right, -0.45)
|
||||
|
||||
camera := Camera{
|
||||
center = Vec3{0, 0.5, 0},
|
||||
distance = 8,
|
||||
orientation = rotor_normalize(rotor_mul(init_pitch, init_yaw)),
|
||||
fov_deg = 60,
|
||||
}
|
||||
|
||||
cubes := []Cube_Instance{
|
||||
{pos = Vec3{0, 0, 0}, scale = 1.0, color = Vec3{0.95, 0.45, 0.20}},
|
||||
{pos = Vec3{2, 0.5, -1}, scale = 0.8, color = Vec3{0.20, 0.70, 0.95}},
|
||||
{pos = Vec3{-2, -0.2, 1.5}, scale = 1.2, color = Vec3{0.85, 0.85, 0.30}},
|
||||
{pos = Vec3{1.0, 1.4, 2.0}, scale = 0.6, color = Vec3{0.40, 0.95, 0.60}},
|
||||
}
|
||||
|
||||
running := true
|
||||
last := time.tick_now()
|
||||
left_down := false
|
||||
middle_down := false
|
||||
debug := Debug_State{}
|
||||
|
||||
for running {
|
||||
now := time.tick_now()
|
||||
dt := f32(time.duration_seconds(time.tick_diff(last, now)))
|
||||
last = now
|
||||
|
||||
event: sdl.Event
|
||||
for sdl.PollEvent(&event) {
|
||||
#partial switch event.type {
|
||||
case .QUIT:
|
||||
running = false
|
||||
case .MOUSE_BUTTON_DOWN:
|
||||
switch event.button.button {
|
||||
case sdl.BUTTON_LEFT:
|
||||
left_down = true
|
||||
case sdl.BUTTON_MIDDLE:
|
||||
middle_down = true
|
||||
}
|
||||
case .MOUSE_BUTTON_UP:
|
||||
switch event.button.button {
|
||||
case sdl.BUTTON_LEFT:
|
||||
left_down = false
|
||||
case sdl.BUTTON_MIDDLE:
|
||||
middle_down = false
|
||||
}
|
||||
case .MOUSE_WHEEL:
|
||||
camera.distance = math.clamp(camera.distance - event.wheel.y * 0.7, 1.5, 80.0)
|
||||
case .KEY_DOWN:
|
||||
if event.key.key == sdl.K_ESCAPE {
|
||||
running = false
|
||||
} else if event.key.scancode == sdl.Scancode.F1 && !event.key.repeat {
|
||||
debug.enabled = !debug.enabled
|
||||
fmt.println("debug:", debug.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x_rel: f32 = 0
|
||||
y_rel: f32 = 0
|
||||
_ = sdl.GetRelativeMouseState(&x_rel, &y_rel)
|
||||
|
||||
if left_down {
|
||||
yaw_r := rotor_from_axis_angle(Vec3{0, 1, 0}, -x_rel * 0.006)
|
||||
q1 := rotor_normalize(rotor_mul(yaw_r, camera.orientation))
|
||||
|
||||
right_axis := vec3_normalize(rotate_vec3(q1, Vec3{1, 0, 0}))
|
||||
pitch_r := rotor_from_axis_angle(right_axis, -y_rel * 0.006)
|
||||
q2 := rotor_normalize(rotor_mul(pitch_r, q1))
|
||||
|
||||
fwd2 := camera_forward(Camera{center = camera.center, distance = camera.distance, orientation = q2, fov_deg = camera.fov_deg})
|
||||
if math.abs(vec3_dot(fwd2, Vec3{0, 1, 0})) < 0.98 {
|
||||
camera.orientation = q2
|
||||
} else {
|
||||
camera.orientation = q1
|
||||
}
|
||||
}
|
||||
|
||||
if middle_down {
|
||||
right := camera_right(camera)
|
||||
up := camera_up(camera)
|
||||
pan_speed := 0.008 * camera.distance
|
||||
camera.center = vec3_add(camera.center, vec3_scale(right, -x_rel * pan_speed))
|
||||
camera.center = vec3_add(camera.center, vec3_scale(up, y_rel * pan_speed))
|
||||
}
|
||||
|
||||
keys := sdl.GetKeyboardState(nil)
|
||||
forward := camera_forward(camera)
|
||||
right := camera_right(camera)
|
||||
up := camera_up(camera)
|
||||
move_speed := camera.distance * dt * 1.4
|
||||
|
||||
if keys[sdl.Scancode.W] {
|
||||
camera.center = vec3_add(camera.center, vec3_scale(forward, move_speed))
|
||||
}
|
||||
if keys[sdl.Scancode.S] {
|
||||
camera.center = vec3_add(camera.center, vec3_scale(forward, -move_speed))
|
||||
}
|
||||
if keys[sdl.Scancode.A] {
|
||||
camera.center = vec3_add(camera.center, vec3_scale(right, -move_speed))
|
||||
}
|
||||
if keys[sdl.Scancode.D] {
|
||||
camera.center = vec3_add(camera.center, vec3_scale(right, move_speed))
|
||||
}
|
||||
|
||||
if debug.enabled {
|
||||
debug.accum += dt
|
||||
if debug.accum >= 0.2 {
|
||||
debug.accum = 0
|
||||
eye := camera_position(camera)
|
||||
fmt.println("cam eye:", eye,
|
||||
"center:", camera.center,
|
||||
"fwd:", forward,
|
||||
"right:", right,
|
||||
"up:", up)
|
||||
}
|
||||
}
|
||||
|
||||
w: c.int = 0
|
||||
h: c.int = 0
|
||||
sdl.GetWindowSize(window, &w, &h)
|
||||
if w <= 0 || h <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := sdl.AcquireGPUCommandBuffer(device)
|
||||
if cmd == nil {
|
||||
fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError())
|
||||
break
|
||||
}
|
||||
|
||||
swap_tex: ^sdl.GPUTexture
|
||||
swap_w: sdl.Uint32 = 0
|
||||
swap_h: sdl.Uint32 = 0
|
||||
if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd, window, &swap_tex, &swap_w, &swap_h) {
|
||||
fmt.println("WaitAndAcquireGPUSwapchainTexture failed:", sdl.GetError())
|
||||
_ = sdl.CancelGPUCommandBuffer(cmd)
|
||||
break
|
||||
}
|
||||
|
||||
if swap_tex == nil {
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd) {
|
||||
fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError())
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
clear := sdl.FColor{0.08, 0.09, 0.12, 1.0}
|
||||
cti := sdl.GPUColorTargetInfo{
|
||||
texture = swap_tex,
|
||||
mip_level = 0,
|
||||
layer_or_depth_plane = 0,
|
||||
clear_color = clear,
|
||||
load_op = .CLEAR,
|
||||
store_op = .STORE,
|
||||
resolve_texture = nil,
|
||||
resolve_mip_level = 0,
|
||||
resolve_layer = 0,
|
||||
cycle = false,
|
||||
cycle_resolve_texture = false,
|
||||
}
|
||||
|
||||
rp := sdl.BeginGPURenderPass(cmd, &cti, 1, nil)
|
||||
if rp == nil {
|
||||
fmt.println("BeginGPURenderPass failed:", sdl.GetError())
|
||||
_ = sdl.CancelGPUCommandBuffer(cmd)
|
||||
break
|
||||
}
|
||||
|
||||
vp := sdl.GPUViewport{x = 0, y = 0, w = f32(swap_w), h = f32(swap_h), min_depth = 0, max_depth = 1}
|
||||
sdl.SetGPUViewport(rp, vp)
|
||||
sdl.BindGPUGraphicsPipeline(rp, pipeline)
|
||||
vb_binding := sdl.GPUBufferBinding{buffer = vbuf, offset = 0}
|
||||
sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1)
|
||||
|
||||
aspect := f32(swap_w) / f32(swap_h)
|
||||
proj := mat4_perspective(camera.fov_deg, aspect, 0.1, 300.0)
|
||||
view := mat4_look_at(camera_position(camera), camera.center, camera_up(camera))
|
||||
vp_mat := mat4_mul(proj, view)
|
||||
|
||||
for cube in cubes {
|
||||
model := mat4_mul(mat4_translate(cube.pos), mat4_scale_uniform(cube.scale))
|
||||
pc := Push_Constants{}
|
||||
pc.mvp = mat4_mul(vp_mat, model)
|
||||
pc.color = [4]f32{cube.color[0], cube.color[1], cube.color[2], 1.0}
|
||||
sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants)))
|
||||
sdl.PushGPUFragmentUniformData(cmd, 0, &pc, u32(size_of(Push_Constants)))
|
||||
sdl.DrawGPUPrimitives(rp, 36, 1, 0, 0)
|
||||
}
|
||||
|
||||
sdl.EndGPURenderPass(rp)
|
||||
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd) {
|
||||
fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_ = sdl.WaitForGPUIdle(device)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue