This commit is contained in:
pavel 2026-05-29 00:36:55 +02:00
commit 28f0d737b1
18 changed files with 3472 additions and 493 deletions

View file

@ -1,201 +1,12 @@
package main
import "core:c"
import "core:fmt"
import "core:math"
import "core:mem"
import "core:os"
import "core:time"
import sdl "vendor:sdl3"
import "rsmview"
import "gndview"
import "shared"
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)
@ -206,7 +17,7 @@ read_file_or_fail :: proc(path: string) -> []u8 {
return file_bytes
}
create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage) -> ^sdl.GPUShader {
create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage, num_samplers: u32) -> ^sdl.GPUShader {
bytes := read_file_or_fail(path)
if len(bytes) == 0 {
return nil
@ -218,7 +29,7 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh
entrypoint = cstring("main"),
format = sdl.GPUShaderFormat{.SPIRV},
stage = stage,
num_samplers = 0,
num_samplers = num_samplers,
num_storage_textures = 0,
num_storage_buffers = 0,
num_uniform_buffers = 1,
@ -233,41 +44,23 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh
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)
copy_data_to_gpu :: proc(
device: ^sdl.GPUDevice,
cmd: ^sdl.GPUCommandBuffer,
vertices: []shared.Vertex,
vert_buf: ^sdl.GPUBuffer,
indices: []u16,
ind_buf: ^sdl.GPUBuffer
) -> bool {
return true
}
GPU_Vertex :: struct {
position: [3]f32,
uv: [2]f32,
}
main :: proc() {
if !sdl.Init(sdl.INIT_VIDEO) {
fmt.println("SDL init failed:", sdl.GetError())
@ -302,13 +95,13 @@ main :: proc() {
return
}
vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX)
vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX, 0)
if vertex_shader == nil {
return
}
defer sdl.ReleaseGPUShader(device, vertex_shader)
fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT)
fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT, 1)
if fragment_shader == nil {
return
}
@ -320,11 +113,12 @@ main :: proc() {
return
}
vb_descs := [1]sdl.GPUVertexBufferDescription{
{slot = 0, pitch = size_of(Vertex), input_rate = .VERTEX, instance_step_rate = 0},
vb_descs := []sdl.GPUVertexBufferDescription{
{slot = 0, pitch = size_of(shared.Vertex), input_rate = .VERTEX, instance_step_rate = 0},
}
vattrs := [1]sdl.GPUVertexAttribute{
{location = 0, buffer_slot = 0, format = .FLOAT3, offset = 0},
vattrs := []sdl.GPUVertexAttribute{
{location = 0, buffer_slot = 0, format = .FLOAT3, offset = u32(offset_of(shared.Vertex, pos))},
{location = 1, buffer_slot = 0, format = .FLOAT2, offset = u32(offset_of(shared.Vertex, uv))},
}
blend := sdl.GPUColorTargetBlendState{
@ -342,15 +136,15 @@ main :: proc() {
{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,
},
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 = 2,
},
primitive_type = .TRIANGLELIST,
rasterizer_state = sdl.GPURasterizerState{
fill_mode = .FILL,
@ -374,15 +168,15 @@ main :: proc() {
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,
enable_depth_test = true,
enable_depth_write = true,
enable_stencil_test = true,
},
target_info = sdl.GPUGraphicsPipelineTargetInfo{
color_target_descriptions = &color_targets[0],
num_color_targets = 1,
depth_stencil_format = .INVALID,
has_depth_stencil_target = false,
depth_stencil_format = .D32_FLOAT,
has_depth_stencil_target = true,
},
props = 0,
}
@ -394,249 +188,16 @@ main :: proc() {
}
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
pipeline_create_info := sdl.GPUGraphicsPipelineCreateInfo{
primitive_type = .TRIANGLELIST,
rasterizer_state = {
fill_mode = .LINE,
cull_mode = .FRONT,
}
}
gndview.runGame(window, device, pipeline)
_ = sdl.WaitForGPUIdle(device)
}
}