Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2cf967e
Shadercross WIP
Gota7 Aug 23, 2025
65fdb0a
Finish Wrapping SDL Shadercross
Gota7 Aug 24, 2025
84b1085
GPU Examples WIP
Gota7 Aug 31, 2025
082447e
Clear Screen GPU Example
Gota7 Aug 31, 2025
31a597c
Add Multi Window Example
Gota7 Aug 31, 2025
e6d5b64
Basic Triangle WIP
Gota7 Sep 1, 2025
482a585
Temporarily Remove Reflect Call
Gota7 Sep 1, 2025
826a962
Zig & GLSL Shaders Work
Gota7 Sep 2, 2025
6a30857
Remove I64 From SPIRV
Gota7 Sep 3, 2025
05c9727
Finish Basic Triangle
Gota7 Sep 5, 2025
7781483
Finish Basic Vertex Buffer Example
Gota7 Sep 5, 2025
00f7995
Finish Cull Mode Example
Gota7 Sep 5, 2025
4b2db3a
Add GPU Examples README
Gota7 Sep 5, 2025
3c45c30
Basic Stencil Example Finished
Gota7 Sep 6, 2025
b6ae536
Add Instanced Index Example
Gota7 Sep 6, 2025
72dd431
Add Textured Quad Example
Gota7 Sep 6, 2025
04291e8
Add Animated Textured Quad Example
Gota7 Sep 7, 2025
f20394e
Add Clear 3d Slice Example
Gota7 Sep 7, 2025
00e3cd8
Add Basic Compute Example
Gota7 Sep 7, 2025
8f27693
Compute Uniform WIP
Gota7 Sep 9, 2025
bca172c
Compute Uniforms Example
Gota7 Sep 10, 2025
ff9ca57
simplify matrix vector multiplication
Traxar Sep 14, 2025
c76ef26
gpu_examples: simplifications to `Mat4` (cpu side)
Traxar Sep 18, 2025
f396f83
Tone Mapping WIP
Gota7 Sep 24, 2025
8d24974
Draw Indirect Example
Gota7 Sep 24, 2025
b48a905
Add Compute Sampler Example
Gota7 Sep 24, 2025
f03ebe9
Add Copy And Readback Example
Gota7 Sep 27, 2025
33af0b7
Add Copy Consistency Example
Gota7 Sep 27, 2025
f420318
Add Texture 2d Array Example
Gota7 Sep 27, 2025
f0557ec
Add Triangle MSAA Example
Gota7 Sep 27, 2025
b5096d3
Add Cubemap Example
Gota7 Sep 28, 2025
52bba37
Add Window Resize Example
Gota7 Sep 28, 2025
e103e34
Add Blit 2d Array & Blit Cube Examples
Gota7 Sep 28, 2025
ebe14a1
Add Blit Mirror Example
Gota7 Sep 28, 2025
b10487b
Add Generate Mipmaps Example
Gota7 Sep 28, 2025
c456e5d
Add Latency Example
Gota7 Sep 29, 2025
a029d30
Merge branch 'Gota7:shadercross' into shadercross
Traxar Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ jobs:
run: zig build examples
- name: Build Examples Windows
run: zig build examples -Dtarget=x86_64-windows
gpu_examples:
name: Build GPU Examples
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: ${{ env.ZIG_VERSION }}
- name: Build GPU Examples Linux
run: cd gpu_examples && zig build examples
- name: Build GPU Examples Windows
run: cd gpu_examples && zig build examples -Dtarget=x86_64-windows
template:
name: Build Template
runs-on: ubuntu-latest
Expand All @@ -45,9 +57,9 @@ jobs:
with:
version: ${{ env.ZIG_VERSION }}
- name: Build Template Linux
run: cd template && zig build
run: cd template && zig build -Dext_shadercross=true
- name: Build Template Windows
run: cd template && zig build -Dtarget=x86_64-windows
run: cd template && zig build -Dext_shadercross=true -Dtarget=x86_64-windows
test:
name: Testing
runs-on: ubuntu-latest
Expand Down
50 changes: 50 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub fn build(b: *std.Build) !void {
extension_options.addOption(bool, "main", sdl3_main);
const ext_image = b.option(bool, "ext_image", "Enable SDL_image extension") orelse false;
extension_options.addOption(bool, "image", ext_image);
const ext_shadercross = b.option(bool, "ext_shadercross", "Enable SDL_shadercross extension") orelse false;
extension_options.addOption(bool, "shadercross", ext_shadercross);

const c_source_code = b.fmt(
\\#include <SDL3/SDL.h>
Expand All @@ -78,9 +80,11 @@ pub fn build(b: *std.Build) !void {
\\#include <SDL3/SDL_vulkan.h>
\\
\\{s}
\\{s}
, .{
if (!sdl3_main) "#define SDL_MAIN_NOIMPL\n" else "",
if (ext_image) "#include <SDL3_image/SDL_image.h>\n" else "",
if (ext_shadercross) "#include <SDL3_shadercross/SDL_shadercross.h>\n" else "",
});
const c_source_file_step = b.addWriteFiles();
const c_source_path = c_source_file_step.add("c.c", c_source_code);
Expand Down Expand Up @@ -108,6 +112,9 @@ pub fn build(b: *std.Build) !void {
if (ext_image) {
setupSdlImage(b, sdl3, translate_c, sdl_dep_lib, c_sdl_preferred_linkage, cfg);
}
if (ext_shadercross) {
setupSdlShadercross(b, sdl3, translate_c, sdl_dep_lib, c_sdl_preferred_linkage, cfg);
}

_ = setupDocs(b, sdl3);
_ = setupTest(b, cfg, extension_options, c_module);
Expand Down Expand Up @@ -210,6 +217,49 @@ pub fn setupSdlImage(b: *std.Build, sdl3: *std.Build.Module, translate_c: *std.B
sdl3.linkLibrary(lib);
}

// Most of this is copied from https://github.com/Beyley/SDL_shadercross_zig/blob/master/build.zig.
pub fn setupSdlShadercross(b: *std.Build, sdl3: *std.Build.Module, translate_c: *std.Build.Step.TranslateC, sdl_dep_lib: *std.Build.Step.Compile, linkage: std.builtin.LinkMode, cfg: Config) void {
const upstream = b.lazyDependency("sdl_shadercross", .{}) orelse return;

const target = cfg.target;
const lib = b.addLibrary(.{
.name = "SDL3_shadercross",
.version = .{ .major = 3, .minor = 0, .patch = 0 },
.linkage = linkage,
.root_module = b.createModule(.{
.target = target,
.optimize = cfg.optimize,
.link_libc = true,
}),
});
lib.root_module.linkLibrary(sdl_dep_lib);

translate_c.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(upstream.path("src"));

lib.root_module.addCSourceFiles(.{
.root = upstream.path("src"),
.files = &.{
"SDL_shadercross.c",
},
});

lib.installHeadersDirectory(upstream.path("include"), "", .{});

const spirv_headers = b.dependency("spirv_headers", .{});
const spirv_cross = b.dependency("spirv_cross", .{
.target = target,
.optimize = .ReleaseFast, // There is a C bug in spirv-cross upstream! Ignore undefined behavior for now.
.spv_cross_reflect = true,
.spv_cross_cpp = false,
});
lib.linkLibrary(spirv_cross.artifact("spirv-cross-c"));
lib.addIncludePath(spirv_headers.path("include/spirv/1.2/"));

sdl3.linkLibrary(lib);
}

pub fn setupDocs(b: *std.Build, sdl3: *std.Build.Module) *std.Build.Step {
const sdl3_lib = b.addLibrary(.{
.root_module = sdl3,
Expand Down
12 changes: 12 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
.hash = "N-V-__8AAP6WIgI4b7FYvyJ6F7gg38lmxqvHSQPGyTlGuFuT",
.lazy = true,
},
.sdl_shadercross = .{
.url = "git+https://github.com/libsdl-org/SDL_shadercross#392d12afc1ef084c5cd656307180027399b7a54e",
.hash = "N-V-__8AAH9GBgCySY9rt5VJj3A-OGl0Z05BPZ9PgS5Pt1zm",
},
.spirv_cross = .{
.url = "git+https://github.com/Gota7/spirv_cross-zig#75cfd40f81f63a678d70d7e3de021edd944eb553",
.hash = "spirv_cross-0.0.0-bzNVZC4YAADQdsxyfpxC9Iw8xxietH7xoEQxbHY43bmo",
},
.spirv_headers = .{
.url = "git+https://github.com/KhronosGroup/SPIRV-Headers#1bfd27101e4578d0284061bdf8f09fb4755c7c2d",
.hash = "N-V-__8AADq1MQCCoUh-qOkYXGvqKFClPzdiedYrsGFYzTV9",
},
},
.paths = .{
"build.zig",
Expand Down
40 changes: 40 additions & 0 deletions gpu_examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SDL3 GPU Examples
Zig examples of using SDL3's GPU subsystem along with shadercross to run all provided shader formats on the target. SDL shadercross bridges the platform shader format gap such that the shaders "just work" everywhere.

## Build System
You can use `zig build examples` to build all of the example executables, and `zig build run -Dexample=example-zig-file-here` (Ex: `-Dexample=basic-triangle`) to run a particular example. You may also specify the shader format to use with `-Dshader_format`. Note that the default format is `zig`. You are free to use the build system as a reference for building your own project's, taking the shader format you like most.

Zig shaders use `spirv-opt` on the system to optimize the result of SPIR-V as zig does not optimize SPIR-V iirc? Also, you may not compile zig shaders on a Windows system at the moment [due to this issue](https://github.com/ziglang/zig/issues/23883). There is nothing stopping you from cross-compiling for a Windows system, but doing dev from a Windows environment would be unfun.

GLSL shaders use `glslang` on the system in order to compile GLSL into SPIR-V binaries.

HLSL shaders are compiled at runtime using SDL shadercross. Note that nothing is stopping you from pre-compiling HLSL into SPIR-V using `glslang` if runtime flexibility is not needed.

## Shader Formats
Each shader format has its own ups and downs.

### Zig
Pros:
* It's zig
* Tooling already installed with the compiler

Cons:
* Inline assembly required for more complex tasks
* External tool required at compile time to maximize performance
* No runtime recompilation support

### Compiled GLSL/HLSL
Pros:
* Normal shading language

Cons:
* Requires `glslang` or other shader compiler at build time
* No runtime recompilation support

### Runtime HLSL
Pros:
* Runtime recompilation support
* Normal shading language

Cons:
* Requires additional less-flexible dependencies for SDL shadercross
165 changes: 165 additions & 0 deletions gpu_examples/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
const std = @import("std");

const ShaderFormat = enum {
glsl,
hlsl,
zig,
hlsl_runtime,
};

fn setupShader(
b: *std.Build,
module: *std.Build.Module,
name: []const u8,
format: ShaderFormat,
) !void {
// Compute shaders not possible for zig atm. See `shaders/basic-compute.zig` for more info.
const suffix = name[std.mem.lastIndexOf(u8, name, ".").? + 1 ..];
const actual_format = if (format == .zig and std.mem.eql(u8, suffix, "comp")) .hlsl else format;
switch (actual_format) {
.glsl, .hlsl => {
const glslang = b.findProgram(&.{"glslang"}, &.{}) catch @panic("glslang not found, can not compile GLSL shaders");
const glslang_cmd = b.addSystemCommand(&.{ glslang, "-V100", "-e", "main", "-S" });
glslang_cmd.addArg(suffix);
if (actual_format == .hlsl)
glslang_cmd.addArg("-D");
glslang_cmd.addFileArg(b.path(try std.fmt.allocPrint(b.allocator, "shaders/{s}.{s}", .{ name, if (actual_format == .glsl) "glsl" else "hlsl" })));
glslang_cmd.addArg("-o");
const glslang_cmd_out = glslang_cmd.addOutputFileArg(try std.fmt.allocPrint(b.allocator, "{s}.spv", .{name}));

module.addAnonymousImport(name, .{ .root_source_file = glslang_cmd_out });
},
.hlsl_runtime => module.addAnonymousImport(name, .{ .root_source_file = b.path(try std.fmt.allocPrint(b.allocator, "shaders/{s}.hlsl", .{name})) }),
.zig => {
const obj = b.addObject(.{
.name = name,
.root_module = b.addModule(name, .{
.root_source_file = b.path(try std.fmt.allocPrint(b.allocator, "shaders/{s}.zig", .{name})),
.target = b.resolveTargetQuery(.{
.cpu_arch = .spirv64,
.cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 },
.cpu_features_add = std.Target.spirv.featureSet(&.{}),
.os_tag = .vulkan,
.ofmt = .spirv,
}),
}),
.use_llvm = false,
.use_lld = false,
});
var shader_out = obj.getEmittedBin();

if (b.findProgram(&.{"spirv-opt"}, &.{})) |spirv_opt| {
const spirv_opt_cmd = b.addSystemCommand(&.{ spirv_opt, "-O" });
spirv_opt_cmd.addFileArg(obj.getEmittedBin());
spirv_opt_cmd.addArg("-o");
shader_out = spirv_opt_cmd.addOutputFileArg(try std.fmt.allocPrint(b.allocator, "{s}-opt.spv", .{name}));
} else |err| switch (err) {
error.FileNotFound => std.debug.print("spirv-opt not found, shader output will be unoptimized!\n", .{}),
}

module.addAnonymousImport(name, .{ .root_source_file = shader_out });
},
}
}

fn buildExample(
b: *std.Build,
sdl3: *std.Build.Module,
format: ShaderFormat,
options: *std.Build.Step.Options,
name: []const u8,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) !*std.Build.Step.Compile {
const exe_mod = b.createModule(.{
.root_source_file = b.path(try std.fmt.allocPrint(b.allocator, "src/{s}.zig", .{name})),
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = name,
.root_module = exe_mod,
});

var dir = (try std.fs.openDirAbsolute(b.path("shaders").getPath(b), .{ .iterate = true }));
defer dir.close();
var dir_iterator = try dir.walk(b.allocator);
defer dir_iterator.deinit();
while (try dir_iterator.next()) |file| {
if (file.kind == .file) {
const extension = switch (format) {
.glsl => ".glsl",
.hlsl => ".hlsl",
.zig => ".zig",
.hlsl_runtime => ".hlsl",
};
if (!std.mem.endsWith(u8, file.basename, extension))
continue;
try setupShader(b, exe.root_module, file.basename[0..(file.basename.len - extension.len)], format);
}
}

exe.root_module.addImport("sdl3", sdl3);
exe.root_module.addOptions("options", options);
b.installArtifact(exe);
return exe;
}

pub fn runExample(
b: *std.Build,
sdl3: *std.Build.Module,
format: ShaderFormat,
options: *std.Build.Step.Options,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) !void {
const run_example: ?[]const u8 = b.option([]const u8, "example", "The example name for running an example") orelse null;
const run = b.step("run", "Run an example with -Dexample=<example_name> option");
if (run_example) |example| {
const run_art = b.addRunArtifact(try buildExample(b, sdl3, format, options, example, target, optimize));
run_art.step.dependOn(b.getInstallStep());
run.dependOn(&run_art.step);
}
}

pub fn setupExamples(
b: *std.Build,
sdl3: *std.Build.Module,
format: ShaderFormat,
options: *std.Build.Step.Options,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) !void {
const exp = b.step("examples", "Build all examples");
const examples_dir = b.path("src");
var dir = (try std.fs.openDirAbsolute(examples_dir.getPath(b), .{ .iterate = true }));
defer dir.close();
var dir_iterator = try dir.walk(b.allocator);
defer dir_iterator.deinit();
while (try dir_iterator.next()) |file| {
if (file.kind == .file and std.mem.endsWith(u8, file.basename, ".zig")) {
_ = try buildExample(b, sdl3, format, options, file.basename[0 .. file.basename.len - 4], target, optimize);
}
}
exp.dependOn(b.getInstallStep());
}

pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const sdl3 = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.callbacks = true,
.ext_shadercross = true,
});
const sdl3_mod = sdl3.module("sdl3");
const options = b.addOptions();

const format = b.option(ShaderFormat, "shader_format", "Shader format to use") orelse .zig;
options.addOption(bool, "spirv", format != .hlsl_runtime);
options.addOption(bool, "gpu_debug", b.option(bool, "gpu_debug", "Enable GPU debugging functionality") orelse false);
try setupExamples(b, sdl3_mod, format, options, target, optimize);
try runExample(b, sdl3_mod, format, options, target, optimize);
}
16 changes: 16 additions & 0 deletions gpu_examples/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.{
.name = .gpu_examples,
.version = "0.0.1",
.minimum_zig_version = "0.15.1",
.dependencies = .{
.sdl3 = .{
.path = "../",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
.fingerprint = 0x7a44a34cd5e71f29,
}
17 changes: 17 additions & 0 deletions gpu_examples/shaders/customSampling.frag.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#version 450

layout(set = 2, binding = 0, rgba8) uniform image2D tex0;
layout(set = 3, binding = 0, std140) uniform uniforms {
int custom_sampler;
};

layout(location = 0) in vec2 tex_coord_in;

layout(location = 0) out vec4 color_out;

void main() {
ivec2 size = imageSize(tex0);
ivec2 texel_pos = ivec2(vec2(size) * tex_coord_in);
vec4 texel = imageLoad(tex0, texel_pos);
color_out = texel;
}
26 changes: 26 additions & 0 deletions gpu_examples/shaders/customSampling.frag.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
cbuffer UBO : register(b0, space3)
{
int mode : packoffset(c0);
};

Texture2D<float4> Texture : register(t0, space2);

float4 main(float2 TexCoord : TEXCOORD0) : SV_Target0
{
float w, h;
Texture.GetDimensions(w, h);
int2 texelPos = int2(float2(w, h) * TexCoord);
float4 mainTexel = Texture[texelPos];
if (mode == 0)
{
return mainTexel;
}
else
{
float4 bottomTexel = Texture[texelPos + int2(0, 1)];
float4 leftTexel = Texture[texelPos + int2(-1, 0)];
float4 topTexel = Texture[texelPos + int2(0, -1)];
float4 rightTexel = Texture[texelPos + int2(1, 0)];
return ((((mainTexel * 0.2f) + (bottomTexel * 0.2f)) + (leftTexel * 0.20000000298023223876953125f)) + (topTexel * 0.20000000298023223876953125f)) + (rightTexel * 0.2f);
}
}
Loading