Add shader backend + support for multiple shader formats- #2
Adds a runtime GPU shader-backend selection layer so the engine is no longer hardcoded to #load SPIR-V only.
- Introduce Shader_Runtime / choose_shader_runtime (engine/shader_backend.odin)
- Create the GPU device with {.SPIRV, .DXIL, .MSL}, then pick a supported format + shaders/<backend>/ directory
- Load shader blobs from disk and build the sprite pipeline with the chosen format/entrypoint
- Add shader build scripts + Makefile targets (shaders-vulkan / d3d12 / metal / all)
This commit is contained in:
@@ -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
-22
@@ -1,6 +1,7 @@
|
||||
package engine
|
||||
|
||||
import "core:fmt"
|
||||
import "core:path/filepath"
|
||||
import sdl "vendor:sdl3"
|
||||
|
||||
Vertex :: struct {
|
||||
@@ -11,12 +12,10 @@ Vertex :: struct {
|
||||
SPRITE_VERT_COUNT :: 6
|
||||
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 {
|
||||
window: ^sdl.Window,
|
||||
device: ^sdl.GPUDevice,
|
||||
shader: Shader_Runtime,
|
||||
pipeline: ^sdl.GPUGraphicsPipeline,
|
||||
sampler: ^sdl.GPUSampler,
|
||||
cmd: ^sdl.GPUCommandBuffer,
|
||||
@@ -28,6 +27,19 @@ App :: struct {
|
||||
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 {
|
||||
if !sdl.Init({.VIDEO}) {
|
||||
fmt.eprintfln("SDL_Init failed: %s", sdl.GetError())
|
||||
@@ -41,7 +53,8 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
|
||||
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 {
|
||||
fmt.eprintfln("CreateGPUDevice failed: %s", sdl.GetError())
|
||||
return false
|
||||
@@ -52,7 +65,11 @@ init :: proc(app: ^App, title: cstring, width, height: i32) -> bool {
|
||||
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 {
|
||||
fmt.eprintfln("create_sprite_pipeline failed: %s", sdl.GetError())
|
||||
return false
|
||||
@@ -196,10 +213,12 @@ end_frame :: proc(app: ^App) {
|
||||
app.swapchain_texture = nil
|
||||
}
|
||||
|
||||
load_spirv_shader :: proc(
|
||||
load_gpu_shader :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
code: []u8,
|
||||
stage: sdl.GPUShaderStage,
|
||||
format: sdl.GPUShaderFormat,
|
||||
entrypoint: cstring,
|
||||
num_samplers: u32,
|
||||
) -> ^sdl.GPUShader {
|
||||
return sdl.CreateGPUShader(
|
||||
@@ -207,8 +226,8 @@ load_spirv_shader :: proc(
|
||||
{
|
||||
code_size = len(code),
|
||||
code = raw_data(code),
|
||||
entrypoint = "main",
|
||||
format = {.SPIRV},
|
||||
entrypoint = entrypoint,
|
||||
format = format,
|
||||
stage = stage,
|
||||
num_samplers = num_samplers,
|
||||
num_storage_textures = 0,
|
||||
@@ -218,20 +237,58 @@ load_spirv_shader :: proc(
|
||||
)
|
||||
}
|
||||
|
||||
create_sprite_pipeline :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
window: ^sdl.Window,
|
||||
) -> ^sdl.GPUGraphicsPipeline {
|
||||
vert := load_spirv_shader(device, VERT_SPV[:], .VERTEX, 0)
|
||||
if vert == nil do return nil
|
||||
shader_filenames :: proc(backend: Shader_Backend) -> (vert_name, frag_name: string) {
|
||||
switch backend {
|
||||
case .Vulkan_SPIRV:
|
||||
return "sprite.vert.spv", "sprite.frag.spv"
|
||||
case .DSD12_DXIL:
|
||||
return "sprite.vert.dxil", "sprite.frag.dxil"
|
||||
case .Metal_MSL:
|
||||
return "sprite.vert.msl", "sprite.frag.msl"
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
frag := load_spirv_shader(device, FRAG_SPV[:], .FRAGMENT, 1)
|
||||
if frag == nil {
|
||||
sdl.ReleaseGPUShader(device, vert)
|
||||
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 {
|
||||
@@ -274,10 +331,10 @@ create_sprite_pipeline :: proc(
|
||||
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(device, frag)
|
||||
sdl.ReleaseGPUShader(app.device, vert)
|
||||
sdl.ReleaseGPUShader(app.device, frag)
|
||||
|
||||
return pipeline
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Executable
+9
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+6
@@ -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.
Reference in New Issue
Block a user