treesummaryrefslogcommitdiff
diff options
context:
space:
mode:
authorPatrick2026-09-26 12:48:51 +0200
committerPatrick2026-09-26 12:48:51 +0200
commita58717ae1a2451635c7dee0970e6ee82b9cb4ac2 (patch)
tree65ba99ba7dd471433ae30a415ae50fa88023c1db
downloadsg3-a58717ae1a2451635c7dee0970e6ee82b9cb4ac2.tar.gz
sg3-a58717ae1a2451635c7dee0970e6ee82b9cb4ac2.zip
add all
-rw-r--r--.gitignore7
-rw-r--r--build.zig65
-rw-r--r--build.zig.zon25
-rw-r--r--flake.lock45
-rw-r--r--flake.nix38
-rw-r--r--src/main.zig107
-rw-r--r--src/math.zig290
-rw-r--r--src/shaders/main.glsl33
8 files changed, 610 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b901f11
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.zig-cache/
+zig-deps/
+zig-out/
+zig-pkg/
+result
+.zig-cache/
+deps/
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..1cf6415
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,65 @@
+const std = @import("std");
+const sokol = @import("sokol");
+const cimgui = @import("cimgui");
+const shdc = sokol.shdc;
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const cimgui_conf = cimgui.getConfig(true);
+
+ const dep_sokol = b.dependency("sokol", .{
+ .target = target,
+ .optimize = optimize,
+ .with_sokol_imgui = true,
+ .with_tracing = true,
+ });
+ const dep_cimgui = b.dependency("cimgui", .{
+ .target = target,
+ .optimize = optimize,
+ });
+
+ dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
+
+ const shader = shdc.createSourceFile(b, .{
+ .shdc_dep = b.dependency("shdc", .{}),
+ .input = "src/shaders/main.glsl",
+ .output = "src/shaders/main.glsl.zig",
+ .slang = .{
+ .glsl430 = true,
+ .glsl410 = false,
+ .glsl310es = true,
+ .glsl300es = false,
+ .metal_macos = true,
+ .hlsl5 = true,
+ .wgsl = true,
+ .spirv_vk = true,
+ },
+ .reflection = true,
+ }) catch undefined;
+
+ const exe = b.addExecutable(.{
+ .name = "rc2d",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "sokol", .module = dep_sokol.module("sokol") },
+ .{ .name = cimgui_conf.module_name, .module = dep_cimgui.module(cimgui_conf.module_name) },
+ },
+ }),
+ });
+
+ exe.root_module.linkLibrary(dep_sokol.artifact("sokol_clib"));
+ exe.root_module.link_libc = true;
+ exe.step.dependOn(shader);
+
+ if (target.result.os.tag == .windows) {
+ exe.root_module.linkSystemLibrary("user32", .{});
+ exe.root_module.linkSystemLibrary("gdi32", .{});
+ }
+
+ b.installArtifact(exe);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..f7cdaf4
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,25 @@
+.{
+ .name = .sporegirl3,
+ .version = "0.0.1",
+ .minimum_zig_version = "0.16.0-dev.1484+d0ba6642b",
+ .paths = .{ "src/main.zig", "build.zig", "build.zig.zon" },
+ .fingerprint = 0x688392934479299d,
+ .dependencies = .{
+ .emsdk = .{
+ .url = "git+https://github.com/emscripten-core/emsdk?ref=6.0.0#d223ae73c6998296e3ab27cf81dc2c2c9fd383de",
+ .hash = "N-V-__8AAFzfCQCASN6D5b3L8-QbCR9pCMDgNoKp69omdc4V",
+ },
+ .shdc = .{
+ .url = "git+https://github.com/floooh/sokol-tools-bin#6801e61ab7ea64dd9369ae9ff2f46d20c61fc655",
+ .hash = "sokolshdc-0.1.0-r2KZDj2ESgPeSsOrxWqONyemz4-250cFO8ZhXkEs4DrZ",
+ },
+ .sokol = .{
+ .url = "git+https://github.com/floooh/sokol-zig?ref=zig-0.16.0#54776d69dac1650221a84191a07269fc4e1e82cd",
+ .hash = "sokol-0.1.0-pb1HKxTaNgA8rXBh41wxEucgmR4YPuoOXGeXbFyGPvTK",
+ },
+ .cimgui = .{
+ .url = "git+https://github.com/floooh/dcimgui#e2f0e0d93adec02743c55940be23ffe286e857f7",
+ .hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
+ },
+ },
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..a0204e5
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,45 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1790238896,
+ "narHash": "sha256-nuO5fxDxKTeYxSEfD4mVB+XwzOy5qj9T2LxVc5vk4Bc=",
+ "rev": "34ca302a9572963c02e385c056be37c85ff51b77",
+ "type": "tarball",
+ "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1078969.34ca302a9572/nixexprs.tar.zst"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs",
+ "zig": "zig"
+ }
+ },
+ "zig": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1790271080,
+ "narHash": "sha256-jYnu8aqa8fv533z5FcrPQXngw+G9gWZd0VmudoOegA0=",
+ "owner": "silversquirl",
+ "repo": "zig-flake",
+ "rev": "c7ff2435b157d0807bd9715e5aed62046f23dfef",
+ "type": "github"
+ },
+ "original": {
+ "owner": "silversquirl",
+ "repo": "zig-flake",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..5cd834c
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,38 @@
+{
+ inputs = {
+ nixpkgs.url = "nixpkgs";
+ zig.url = "github:silversquirl/zig-flake";
+ zig.inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ outputs = {
+ nixpkgs,
+ zig,
+ ...
+ }: let
+ forAllSystems = f:
+ builtins.mapAttrs
+ (system: pkgs: f pkgs zig.packages.${system}.zig_0_16_0)
+ nixpkgs.legacyPackages;
+ in {
+ devShells = forAllSystems (pkgs: zig: {
+ default = pkgs.mkShellNoCC {
+ packages = [pkgs.bash pkgs.tmux zig zig.zls];
+ shellHook = ''
+ tmux set-option -g default-shell '${pkgs.bashInteractive}/bin/bash' ';' new-session -s sporegirl
+ exit
+ '';
+ };
+ });
+
+ packages = forAllSystems (pkgs: zig: {
+ default = zig.makePackage {
+ pname = "zig-flake-template";
+ version = "0.0.0";
+ src = ./.;
+ zigReleaseMode = "fast";
+ # depsHash = "<replace this with the hash Nix provides in its error message>"
+ };
+ });
+ };
+}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..4c6d98a
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,107 @@
+//------------------------------------------------------------------------------
+// offscreen.zig
+//
+// Render to an offscreen rendertarget texture, and use this texture
+// for rendering to the display.
+//------------------------------------------------------------------------------
+const sokol = @import("sokol");
+const slog = sokol.log;
+const sg = sokol.gfx;
+const sapp = sokol.app;
+const sglue = sokol.glue;
+const imgui = sokol.imgui;
+const appimgui = sokol.appimgui;
+const vec3 = @import("math.zig").Vec3;
+const mat4 = @import("math.zig").Mat4;
+const shd = @import("shaders/main.glsl.zig");
+
+const state = struct {
+ const display = struct {
+ var pass_action: sg.PassAction = .{};
+ var pip: sg.Pipeline = .{};
+ var bind: sg.Bindings = .{};
+ };
+};
+
+export fn init() void {
+ sg.setup(.{
+ .environment = sglue.environment(),
+ .logger = .{ .func = slog.func },
+ });
+
+ imgui.setup(.{});
+ appimgui.setup();
+
+ // default pass action: clear to blue-ish
+ state.display.pass_action.colors[0] = .{
+ .load_action = .CLEAR,
+ .clear_value = .{ .r = 0.25, .g = 0.45, .b = 0.65, .a = 1.0 },
+ };
+
+ const vbuf = sg.makeBuffer(.{});
+ state.display.bind.vertex_buffers[0] = vbuf;
+
+ // shader and pipeline object for the default render pass
+ state.display.pip = sg.makePipeline(.{
+ .shader = sg.makeShader(shd.offscreenShaderDesc(sg.queryBackend())),
+ .layout = init: {
+ var l = sg.VertexLayoutState{};
+ l.attrs[shd.ATTR_offscreen_position] = .{};
+ break :init l;
+ },
+ .index_type = .UINT16,
+ .cull_mode = .BACK,
+ .depth = .{
+ .compare = .LESS_EQUAL,
+ .write_enabled = true,
+ },
+ });
+}
+
+export fn event(ev: [*c]const sapp.Event) void {
+ appimgui.trackEvent(ev.*);
+}
+
+export fn frame() void {
+ // const dt: f32 = @floatCast(sapp.frameDuration() * 60);
+ // const aspect = sapp.widthf() / sapp.heightf();
+
+ sg.beginPass(.{ .action = state.display.pass_action, .swapchain = sglue.swapchain() });
+ sg.applyPipeline(state.display.pip);
+ sg.applyBindings(state.display.bind);
+ sg.draw(0, 3, 1);
+ sg.endPass();
+
+ sg.commit();
+}
+
+export fn cleanup() void {
+ appimgui.shutdown();
+ imgui.shutdown();
+ sg.shutdown();
+}
+
+pub fn main() void {
+ sapp.run(.{
+ .init_cb = init,
+ .frame_cb = frame,
+ .event_cb = event,
+ .cleanup_cb = cleanup,
+ .sample_count = 4,
+ .width = 800,
+ .height = 600,
+ .icon = .{ .sokol_default = true },
+ .window_title = "offscreen.zig",
+ .logger = .{ .func = slog.func },
+ });
+}
+
+fn computeVsParams(rx: f32, ry: f32, aspect: f32, eye_dist: f32) shd.VsParams {
+ const proj = mat4.persp(45, aspect, 0.01, 10);
+ const view = mat4.lookat(.{ .x = 0, .y = 0, .z = eye_dist }, vec3.zero(), vec3.up());
+ const view_proj = mat4.mul(proj, view);
+ const rxm = mat4.rotate(rx, .{ .x = 1, .y = 0, .z = 0 });
+ const rym = mat4.rotate(ry, .{ .x = 0, .y = 1, .z = 0 });
+ const model = mat4.mul(rxm, rym);
+ return shd.VsParams{ .mvp = mat4.mul(view_proj, model) };
+}
diff --git a/src/math.zig b/src/math.zig
new file mode 100644
index 0000000..6ca7c37
--- /dev/null
+++ b/src/math.zig
@@ -0,0 +1,290 @@
+//------------------------------------------------------------------------------
+// math.zig
+//
+// minimal vector math helper functions, just the stuff needed for
+// the sokol-samples
+//
+// Ported from HandmadeMath.h
+//------------------------------------------------------------------------------
+const assert = @import("std").debug.assert;
+const math = @import("std").math;
+
+fn radians(deg: f32) f32 {
+ return deg * (math.pi / 180.0);
+}
+
+pub const Vec2 = extern struct {
+ x: f32,
+ y: f32,
+
+ pub fn zero() Vec2 {
+ return Vec2{ .x = 0.0, .y = 0.0 };
+ }
+
+ pub fn new(x: f32, y: f32) Vec2 {
+ return Vec2{ .x = x, .y = y };
+ }
+};
+
+pub const Vec3 = extern struct {
+ x: f32,
+ y: f32,
+ z: f32,
+
+ pub fn zero() Vec3 {
+ return Vec3{ .x = 0.0, .y = 0.0, .z = 0.0 };
+ }
+
+ pub fn new(x: f32, y: f32, z: f32) Vec3 {
+ return Vec3{ .x = x, .y = y, .z = z };
+ }
+
+ pub fn up() Vec3 {
+ return Vec3{ .x = 0.0, .y = 1.0, .z = 0.0 };
+ }
+
+ pub fn len(v: Vec3) f32 {
+ return math.sqrt(Vec3.dot(v, v));
+ }
+
+ pub fn add(left: Vec3, right: Vec3) Vec3 {
+ return Vec3{ .x = left.x + right.x, .y = left.y + right.y, .z = left.z + right.z };
+ }
+
+ pub fn sub(left: Vec3, right: Vec3) Vec3 {
+ return Vec3{ .x = left.x - right.x, .y = left.y - right.y, .z = left.z - right.z };
+ }
+
+ pub fn mul(v: Vec3, s: f32) Vec3 {
+ return Vec3{ .x = v.x * s, .y = v.y * s, .z = v.z * s };
+ }
+
+ pub fn norm(v: Vec3) Vec3 {
+ const l = Vec3.len(v);
+ if (l != 0.0) {
+ return Vec3{ .x = v.x / l, .y = v.y / l, .z = v.z / l };
+ } else {
+ return Vec3.zero();
+ }
+ }
+
+ pub fn cross(v0: Vec3, v1: Vec3) Vec3 {
+ return Vec3{ .x = (v0.y * v1.z) - (v0.z * v1.y), .y = (v0.z * v1.x) - (v0.x * v1.z), .z = (v0.x * v1.y) - (v0.y * v1.x) };
+ }
+
+ pub fn dot(v0: Vec3, v1: Vec3) f32 {
+ return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z;
+ }
+};
+
+pub const Mat4 = extern struct {
+ m: [4][4]f32,
+
+ pub fn identity() Mat4 {
+ return Mat4{
+ .m = [_][4]f32{ .{ 1.0, 0.0, 0.0, 0.0 }, .{ 0.0, 1.0, 0.0, 0.0 }, .{ 0.0, 0.0, 1.0, 0.0 }, .{ 0.0, 0.0, 0.0, 1.0 } },
+ };
+ }
+
+ pub fn zero() Mat4 {
+ return Mat4{
+ .m = [_][4]f32{ .{ 0.0, 0.0, 0.0, 0.0 }, .{ 0.0, 0.0, 0.0, 0.0 }, .{ 0.0, 0.0, 0.0, 0.0 }, .{ 0.0, 0.0, 0.0, 0.0 } },
+ };
+ }
+
+ pub fn mul(left: Mat4, right: Mat4) Mat4 {
+ var res = Mat4.zero();
+ for (0..4) |col| {
+ for (0..4) |row| {
+ res.m[col][row] = left.m[0][row] * right.m[col][0] +
+ left.m[1][row] * right.m[col][1] +
+ left.m[2][row] * right.m[col][2] +
+ left.m[3][row] * right.m[col][3];
+ }
+ }
+ return res;
+ }
+
+ pub fn persp(fov: f32, aspect: f32, near: f32, far: f32) Mat4 {
+ var res = Mat4.identity();
+ const t = math.tan(fov * (math.pi / 360.0));
+ res.m[0][0] = 1.0 / t;
+ res.m[1][1] = aspect / t;
+ res.m[2][3] = -1.0;
+ res.m[2][2] = (near + far) / (near - far);
+ res.m[3][2] = (2.0 * near * far) / (near - far);
+ res.m[3][3] = 0.0;
+ return res;
+ }
+
+ pub fn lookat(eye: Vec3, center: Vec3, up: Vec3) Mat4 {
+ var res = Mat4.zero();
+
+ const f = Vec3.norm(Vec3.sub(center, eye));
+ const s = Vec3.norm(Vec3.cross(f, up));
+ const u = Vec3.cross(s, f);
+
+ res.m[0][0] = s.x;
+ res.m[0][1] = u.x;
+ res.m[0][2] = -f.x;
+
+ res.m[1][0] = s.y;
+ res.m[1][1] = u.y;
+ res.m[1][2] = -f.y;
+
+ res.m[2][0] = s.z;
+ res.m[2][1] = u.z;
+ res.m[2][2] = -f.z;
+
+ res.m[3][0] = -Vec3.dot(s, eye);
+ res.m[3][1] = -Vec3.dot(u, eye);
+ res.m[3][2] = Vec3.dot(f, eye);
+ res.m[3][3] = 1.0;
+
+ return res;
+ }
+
+ pub fn rotate(angle: f32, axis_unorm: Vec3) Mat4 {
+ var res = Mat4.identity();
+
+ const axis = Vec3.norm(axis_unorm);
+ const sin_theta = math.sin(radians(angle));
+ const cos_theta = math.cos(radians(angle));
+ const cos_value = 1.0 - cos_theta;
+
+ res.m[0][0] = (axis.x * axis.x * cos_value) + cos_theta;
+ res.m[0][1] = (axis.x * axis.y * cos_value) + (axis.z * sin_theta);
+ res.m[0][2] = (axis.x * axis.z * cos_value) - (axis.y * sin_theta);
+ res.m[1][0] = (axis.y * axis.x * cos_value) - (axis.z * sin_theta);
+ res.m[1][1] = (axis.y * axis.y * cos_value) + cos_theta;
+ res.m[1][2] = (axis.y * axis.z * cos_value) + (axis.x * sin_theta);
+ res.m[2][0] = (axis.z * axis.x * cos_value) + (axis.y * sin_theta);
+ res.m[2][1] = (axis.z * axis.y * cos_value) - (axis.x * sin_theta);
+ res.m[2][2] = (axis.z * axis.z * cos_value) + cos_theta;
+
+ return res;
+ }
+
+ pub fn translate(translation: Vec3) Mat4 {
+ var res = Mat4.identity();
+ res.m[3][0] = translation.x;
+ res.m[3][1] = translation.y;
+ res.m[3][2] = translation.z;
+ return res;
+ }
+};
+
+test "Vec3.zero" {
+ const v = Vec3.zero();
+ assert(v.x == 0.0 and v.y == 0.0 and v.z == 0.0);
+}
+
+test "Vec3.new" {
+ const v = Vec3.new(1.0, 2.0, 3.0);
+ assert(v.x == 1.0 and v.y == 2.0 and v.z == 3.0);
+}
+
+test "Mat4.ident" {
+ const m = Mat4.identity();
+ for (m.m, 0..) |row, y| {
+ for (row, 0..) |val, x| {
+ if (x == y) {
+ assert(val == 1.0);
+ } else {
+ assert(val == 0.0);
+ }
+ }
+ }
+}
+
+test "Mat4.mul" {
+ const l = Mat4.identity();
+ const r = Mat4.identity();
+ const m = Mat4.mul(l, r);
+ for (m.m, 0..) |row, y| {
+ for (row, 0..) |val, x| {
+ if (x == y) {
+ assert(val == 1.0);
+ } else {
+ assert(val == 0.0);
+ }
+ }
+ }
+}
+
+fn eq(val: f32, cmp: f32) bool {
+ const delta: f32 = 0.00001;
+ return (val > (cmp - delta)) and (val < (cmp + delta));
+}
+
+test "Mat4.persp" {
+ const m = Mat4.persp(60.0, 1.33333337, 0.01, 10.0);
+
+ assert(eq(m.m[0][0], 1.73205));
+ assert(eq(m.m[0][1], 0.0));
+ assert(eq(m.m[0][2], 0.0));
+ assert(eq(m.m[0][3], 0.0));
+
+ assert(eq(m.m[1][0], 0.0));
+ assert(eq(m.m[1][1], 2.30940));
+ assert(eq(m.m[1][2], 0.0));
+ assert(eq(m.m[1][3], 0.0));
+
+ assert(eq(m.m[2][0], 0.0));
+ assert(eq(m.m[2][1], 0.0));
+ assert(eq(m.m[2][2], -1.00200));
+ assert(eq(m.m[2][3], -1.0));
+
+ assert(eq(m.m[3][0], 0.0));
+ assert(eq(m.m[3][1], 0.0));
+ assert(eq(m.m[3][2], -0.02002));
+ assert(eq(m.m[3][3], 0.0));
+}
+
+test "Mat4.lookat" {
+ const m = Mat4.lookat(.{ .x = 0.0, .y = 1.5, .z = 6.0 }, Vec3.zero(), Vec3.up());
+
+ assert(eq(m.m[0][0], 1.0));
+ assert(eq(m.m[0][1], 0.0));
+ assert(eq(m.m[0][2], 0.0));
+ assert(eq(m.m[0][3], 0.0));
+
+ assert(eq(m.m[1][0], 0.0));
+ assert(eq(m.m[1][1], 0.97014));
+ assert(eq(m.m[1][2], 0.24253));
+ assert(eq(m.m[1][3], 0.0));
+
+ assert(eq(m.m[2][0], 0.0));
+ assert(eq(m.m[2][1], -0.24253));
+ assert(eq(m.m[2][2], 0.97014));
+ assert(eq(m.m[2][3], 0.0));
+
+ assert(eq(m.m[3][0], 0.0));
+ assert(eq(m.m[3][1], 0.0));
+ assert(eq(m.m[3][2], -6.18465));
+ assert(eq(m.m[3][3], 1.0));
+}
+
+test "Mat4.rotate" {
+ const m = Mat4.rotate(2.0, .{ .x = 0.0, .y = 1.0, .z = 0.0 });
+
+ assert(eq(m.m[0][0], 0.99939));
+ assert(eq(m.m[0][1], 0.0));
+ assert(eq(m.m[0][2], -0.03489));
+ assert(eq(m.m[0][3], 0.0));
+
+ assert(eq(m.m[1][0], 0.0));
+ assert(eq(m.m[1][1], 1.0));
+ assert(eq(m.m[1][2], 0.0));
+ assert(eq(m.m[1][3], 0.0));
+
+ assert(eq(m.m[2][0], 0.03489));
+ assert(eq(m.m[2][1], 0.0));
+ assert(eq(m.m[2][2], 0.99939));
+ assert(eq(m.m[2][3], 0.0));
+
+ assert(eq(m.m[3][0], 0.0));
+ assert(eq(m.m[3][1], 0.0));
+ assert(eq(m.m[3][2], 0.0));
+ assert(eq(m.m[3][3], 1.0));
+}
diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl
new file mode 100644
index 0000000..48de990
--- /dev/null
+++ b/src/shaders/main.glsl
@@ -0,0 +1,33 @@
+//------------------------------------------------------------------------------
+// shaders for offscreen-sapp sample
+//------------------------------------------------------------------------------
+@header const m = @import("../math.zig")
+@ctype mat4 m.Mat4
+
+// shared code for all shaders
+@block uniforms
+layout(binding=0) uniform vs_params {
+ mat4 mvp;
+};
+@end
+
+// offscreen rendering shaders
+@vs vs_offscreen
+@include_block uniforms
+
+in vec2 position;
+
+void main() {
+ gl_Position = vec4(position, 0, 1);
+}
+@end
+
+@fs fs_offscreen
+out vec4 frag_color;
+
+void main() {
+ frag_color = vec4(1.0, 0.5, 1.0, 1.0);
+}
+@end
+
+@program offscreen vs_offscreen fs_offscreen