diff options
| -rw-r--r-- | .gitignore | 7 | ||||
| -rw-r--r-- | build.zig | 65 | ||||
| -rw-r--r-- | build.zig.zon | 25 | ||||
| -rw-r--r-- | flake.lock | 45 | ||||
| -rw-r--r-- | flake.nix | 38 | ||||
| -rw-r--r-- | src/main.zig | 107 | ||||
| -rw-r--r-- | src/math.zig | 290 | ||||
| -rw-r--r-- | src/shaders/main.glsl | 33 |
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 |
