diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..70fa75b --- /dev/null +++ b/Makefile @@ -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=. diff --git a/engine/app.odin b/engine/app.odin index d927825..f64578d 100644 --- a/engine/app.odin +++ b/engine/app.odin @@ -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,57 @@ 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 - - frag := load_spirv_shader(device, FRAG_SPV[:], .FRAGMENT, 1) - if frag == nil { - sdl.ReleaseGPUShader(device, vert) - 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 "", "" +} +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 +330,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 } diff --git a/engine/shader_backend.odin b/engine/shader_backend.odin new file mode 100644 index 0000000..e023140 --- /dev/null +++ b/engine/shader_backend.odin @@ -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 +} diff --git a/scripts/shaders_all.sh b/scripts/shaders_all.sh new file mode 100755 index 0000000..9f1e664 --- /dev/null +++ b/scripts/shaders_all.sh @@ -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 diff --git a/scripts/shaders_d3d12.sh b/scripts/shaders_d3d12.sh new file mode 100755 index 0000000..a34eb17 --- /dev/null +++ b/scripts/shaders_d3d12.sh @@ -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 diff --git a/scripts/shaders_metal.sh b/scripts/shaders_metal.sh new file mode 100755 index 0000000..ea3e651 --- /dev/null +++ b/scripts/shaders_metal.sh @@ -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 diff --git a/scripts/shaders_vulkan.sh b/scripts/shaders_vulkan.sh new file mode 100755 index 0000000..0a43199 --- /dev/null +++ b/scripts/shaders_vulkan.sh @@ -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 + diff --git a/shaders/vulkan/sprite.frag.spv b/shaders/vulkan/sprite.frag.spv new file mode 100644 index 0000000..9771d25 Binary files /dev/null and b/shaders/vulkan/sprite.frag.spv differ diff --git a/shaders/vulkan/sprite.vert.spv b/shaders/vulkan/sprite.vert.spv new file mode 100644 index 0000000..6b0906a Binary files /dev/null and b/shaders/vulkan/sprite.vert.spv differ