add shader backend + support for multiple shader formats

This commit is contained in:
2026-08-01 00:41:50 -07:00
parent 1a7b777b0f
commit fee46730b1
9 changed files with 195 additions and 23 deletions
+27
View File
@@ -0,0 +1,27 @@
.PHONY: shaders-vulkan shaders-d3d12 shaders-metal shaders-all toad check help
help:
@echo "Targets:"
@echo " shaders-vulkan Compile SPIR-V into shaders/vulkan/"
@echo " shaders-d3d12 Compile DXIL into shaders/d3d12/ (needs shadercross)"
@echo " shaders-metal Compile MSL into shaders/metal/ (needs shadercross)"
@echo " shaders-all Build all shader backends"
@echo " check Typecheck examples/toad"
@echo " toad Run the toad example"
shaders-vulkan:
./scripts/shaders_vulkan.sh
shaders-d3d12:
./scripts/shaders_d3d12.sh
shaders-metal:
./scripts/shaders_metal.sh
shaders-all: shaders-vulkan shaders-d3d12 shaders-metal
check:
odin check examples/toad -collection:pkg=.
toad:
odin run examples/toad -collection:pkg=.
+79 -23
View File
@@ -1,6 +1,7 @@
package engine package engine
import "core:fmt" import "core:fmt"
import "core:path/filepath"
import sdl "vendor:sdl3" import sdl "vendor:sdl3"
Vertex :: struct { Vertex :: struct {
@@ -11,12 +12,10 @@ Vertex :: struct {
SPRITE_VERT_COUNT :: 6 SPRITE_VERT_COUNT :: 6
VERTEX_BUFFER_SIZE :: SPRITE_VERT_COUNT * size_of(Vertex) VERTEX_BUFFER_SIZE :: SPRITE_VERT_COUNT * size_of(Vertex)
VERT_SPV := #load("../shaders/sprite.vert.spv")
FRAG_SPV := #load("../shaders/sprite.frag.spv")
App :: struct { App :: struct {
window: ^sdl.Window, window: ^sdl.Window,
device: ^sdl.GPUDevice, device: ^sdl.GPUDevice,
shader: Shader_Runtime,
pipeline: ^sdl.GPUGraphicsPipeline, pipeline: ^sdl.GPUGraphicsPipeline,
sampler: ^sdl.GPUSampler, sampler: ^sdl.GPUSampler,
cmd: ^sdl.GPUCommandBuffer, cmd: ^sdl.GPUCommandBuffer,
@@ -28,6 +27,19 @@ App :: struct {
transfer_buffer: ^sdl.GPUTransferBuffer, transfer_buffer: ^sdl.GPUTransferBuffer,
} }
Shader_Backend :: enum {
Vulkan_SPIRV,
DSD12_DXIL,
Metal_MSL,
}
Shader_Runtime :: struct {
backend: Shader_Backend,
format: sdl.GPUShaderFormat,
shader_dir: string,
entrypoint: cstring,
}
init :: proc(app: ^App, title: cstring, width, height: i32) -> bool { init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
if !sdl.Init({.VIDEO}) { if !sdl.Init({.VIDEO}) {
fmt.eprintfln("SDL_Init failed: %s", sdl.GetError()) fmt.eprintfln("SDL_Init failed: %s", sdl.GetError())
@@ -41,7 +53,8 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
return false return false
} }
app.device = sdl.CreateGPUDevice({.SPIRV}, true, nil) requested: sdl.GPUShaderFormat = {.SPIRV, .DXIL, .MSL}
app.device = sdl.CreateGPUDevice(requested, true, nil)
if app.device == nil { if app.device == nil {
fmt.eprintfln("CreateGPUDevice failed: %s", sdl.GetError()) fmt.eprintfln("CreateGPUDevice failed: %s", sdl.GetError())
return false return false
@@ -52,7 +65,11 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
return false return false
} }
app.pipeline = create_sprite_pipeline(app.device, app.window) ok: bool
app.shader, ok = choose_shader_runtime(app.device)
if !ok do return false
app.pipeline = create_sprite_pipeline(app)
if app.pipeline == nil { if app.pipeline == nil {
fmt.eprintfln("create_sprite_pipeline failed: %s", sdl.GetError()) fmt.eprintfln("create_sprite_pipeline failed: %s", sdl.GetError())
return false return false
@@ -196,10 +213,12 @@ end_frame :: proc(app: ^App) {
app.swapchain_texture = nil app.swapchain_texture = nil
} }
load_spirv_shader :: proc( load_gpu_shader :: proc(
device: ^sdl.GPUDevice, device: ^sdl.GPUDevice,
code: []u8, code: []u8,
stage: sdl.GPUShaderStage, stage: sdl.GPUShaderStage,
format: sdl.GPUShaderFormat,
entrypoint: cstring,
num_samplers: u32, num_samplers: u32,
) -> ^sdl.GPUShader { ) -> ^sdl.GPUShader {
return sdl.CreateGPUShader( return sdl.CreateGPUShader(
@@ -207,8 +226,8 @@ load_spirv_shader :: proc(
{ {
code_size = len(code), code_size = len(code),
code = raw_data(code), code = raw_data(code),
entrypoint = "main", entrypoint = entrypoint,
format = {.SPIRV}, format = format,
stage = stage, stage = stage,
num_samplers = num_samplers, num_samplers = num_samplers,
num_storage_textures = 0, num_storage_textures = 0,
@@ -218,20 +237,57 @@ load_spirv_shader :: proc(
) )
} }
create_sprite_pipeline :: proc( shader_filenames :: proc(backend: Shader_Backend) -> (vert_name, frag_name: string) {
device: ^sdl.GPUDevice, switch backend {
window: ^sdl.Window, case .Vulkan_SPIRV:
) -> ^sdl.GPUGraphicsPipeline { return "sprite.vert.spv", "sprite.frag.spv"
vert := load_spirv_shader(device, VERT_SPV[:], .VERTEX, 0) case .DSD12_DXIL:
if vert == nil do return nil return "sprite.vert.dxil", "sprite.frag.dxil"
case .Metal_MSL:
frag := load_spirv_shader(device, FRAG_SPV[:], .FRAGMENT, 1) return "sprite.vert.msl", "sprite.frag.msl"
if frag == nil { }
sdl.ReleaseGPUShader(device, vert) return "", ""
return nil }
create_sprite_pipeline :: proc(app: ^App) -> ^sdl.GPUGraphicsPipeline {
vert_name, frag_name := shader_filenames(app.shader.backend)
vert_path, _ := filepath.join({app.shader.shader_dir, vert_name})
frag_path, _ := filepath.join({app.shader.shader_dir, frag_name})
defer {
delete(vert_path)
delete(frag_path)
} }
swap_format := sdl.GetGPUSwapchainTextureFormat(device, window) vert_code, vok := load_shader_blob(vert_path)
if !vok do return nil
defer delete(vert_code)
frag_code, fok := load_shader_blob(frag_path)
if !fok do return nil
defer delete(frag_code)
vert := load_gpu_shader(
app.device,
vert_code,
.VERTEX,
app.shader.format,
app.shader.entrypoint,
0,
)
if vert == nil do return nil
frag := load_gpu_shader(
app.device,
frag_code,
.FRAGMENT,
app.shader.format,
app.shader.entrypoint,
1,
)
if frag == nil {
sdl.ReleaseGPUShader(app.device, vert)
return nil
}
swap_format := sdl.GetGPUSwapchainTextureFormat(app.device, app.window)
blend := sdl.GPUColorTargetBlendState { blend := sdl.GPUColorTargetBlendState {
@@ -274,10 +330,10 @@ create_sprite_pipeline :: proc(
target_info = {color_target_descriptions = &color_target, num_color_targets = 1}, target_info = {color_target_descriptions = &color_target, num_color_targets = 1},
} }
pipeline := sdl.CreateGPUGraphicsPipeline(device, pipeline_info) pipeline := sdl.CreateGPUGraphicsPipeline(app.device, pipeline_info)
sdl.ReleaseGPUShader(device, vert) sdl.ReleaseGPUShader(app.device, vert)
sdl.ReleaseGPUShader(device, frag) sdl.ReleaseGPUShader(app.device, frag)
return pipeline return pipeline
} }
+52
View File
@@ -0,0 +1,52 @@
package engine
import "core:fmt"
import "core:os"
import sdl "vendor:sdl3"
choose_shader_runtime :: proc(device: ^sdl.GPUDevice) -> (Shader_Runtime, bool) {
supported := sdl.GetGPUShaderFormats(device)
if .MSL in supported {
return {
backend = .Metal_MSL,
format = {.MSL},
shader_dir = "shaders/metal",
entrypoint = "main",
},
true
}
if .DXIL in supported {
return {
backend = .DSD12_DXIL,
format = {.DXIL},
shader_dir = "shaders/d3d12",
entrypoint = "main",
},
true
}
if .SPIRV in supported {
return {
backend = .Vulkan_SPIRV,
format = {.SPIRV},
shader_dir = "shaders/vulkan",
entrypoint = "main",
},
true
}
fmt.eprintfln("No supported shader format from device {got %v}", supported)
return {}, false
}
load_shader_blob :: proc(path: string) -> ([]u8, bool) {
data, err := os.read_entire_file(path, context.allocator)
if err != nil {
fmt.eprintfln("failed to read shader %s: %v", path, err)
return nil, false
}
return data, true
}
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
./scripts/shaders_vulkan.sh
./scripts/shaders_d3d12.sh
./scripts/shaders_metal.sh
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
if ! command -v shadercross >/dev/null 2>&1; then
echo "shadercross not found (SDL_shadercross). Install it to build D3D12 DXIL shaders." >&2
exit 1
fi
mkdir -p shaders/d3d12
shadercross shaders/sprite.vert.glsl -o shaders/d3d12/sprite.vert.dxil -s vertex -e main
shadercross shaders/sprite.frag.glsl -o shaders/d3d12/sprite.frag.dxil -s fragment -e main
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
if ! command -v shadercross >/dev/null 2>&1; then
echo "shadercross not found (SDL_shadercross). Install it to build Metal MSL shaders." >&2
exit 1
fi
mkdir -p shaders/metal
shadercross shaders/sprite.vert.glsl -o shaders/metal/sprite.vert.msl -s vertex -e main
shadercross shaders/sprite.frag.glsl -o shaders/metal/sprite.frag.msl -s fragment -e main
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
mkdir -p shaders/vulkan
glslangValidator -V shaders/sprite.vert.glsl -o shaders/vulkan/sprite.vert.spv
glslangValidator -V shaders/sprite.frag.glsl -o shaders/vulkan/sprite.frag.spv
Binary file not shown.
Binary file not shown.