treesummaryrefslogcommitdiff
diff options
context:
space:
mode:
authorPatrick2026-10-05 17:38:38 +0200
committerPatrick2026-10-05 17:38:38 +0200
commit9a7d2d44dc17603ff705be5d00416f01704459ef (patch)
tree7014ec0089e76e513249dfdba4e9a0f55163b313
parentc511ae90162f506f34ca0b98732577dc688ebcca (diff)
downloadsg3-9a7d2d44dc17603ff705be5d00416f01704459ef.tar.gz
sg3-9a7d2d44dc17603ff705be5d00416f01704459ef.zip
update
-rw-r--r--.gitignore2
-rw-r--r--build.zig26
-rw-r--r--build.zig.zon8
-rw-r--r--src/main.zig229
-rw-r--r--src/shaders/main.glsl97
-rwxr-xr-xtest.pngbin0 -> 8828 bytes
6 files changed, 326 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
index b901f11..7ce6aa6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@ zig-out/
zig-pkg/
result
.zig-cache/
+*.glsl.zig
deps/
+
diff --git a/build.zig b/build.zig
index 1cf6415..fbcd19d 100644
--- a/build.zig
+++ b/build.zig
@@ -1,4 +1,5 @@
const std = @import("std");
+const Translator = @import("translate_c").Translator;
const sokol = @import("sokol");
const cimgui = @import("cimgui");
const shdc = sokol.shdc;
@@ -7,6 +8,7 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
+ // imgui + sokol
const cimgui_conf = cimgui.getConfig(true);
const dep_sokol = b.dependency("sokol", .{
@@ -14,7 +16,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.with_sokol_imgui = true,
.with_tracing = true,
- });
+ });
const dep_cimgui = b.dependency("cimgui", .{
.target = target,
.optimize = optimize,
@@ -22,6 +24,24 @@ pub fn build(b: *std.Build) void {
dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
+ // stb
+ const translate_c = b.dependency("translate_c", .{});
+
+ const tc_stb: Translator = .init(translate_c, .{
+ .c_source_file = b.path("deps/stb/stb_image.h"),
+ .target = target,
+ .optimize = optimize,
+ .link_libc = true,
+ .default_init = false,
+ });
+
+ // zimg
+ const zigimg_dependency = b.dependency("zigimg", .{
+ .target = target,
+ .optimize = optimize,
+ });
+
+ // core
const shader = shdc.createSourceFile(b, .{
.shdc_dep = b.dependency("shdc", .{}),
.input = "src/shaders/main.glsl",
@@ -37,6 +57,7 @@ pub fn build(b: *std.Build) void {
.spirv_vk = true,
},
.reflection = true,
+ .bytecode = true,
}) catch undefined;
const exe = b.addExecutable(.{
@@ -46,6 +67,8 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
.imports = &.{
+ .{ .name = "stbi", .module = tc_stb.mod },
+ .{ .name = "zigimg", .module = zigimg_dependency.module("zigimg") },
.{ .name = "sokol", .module = dep_sokol.module("sokol") },
.{ .name = cimgui_conf.module_name, .module = dep_cimgui.module(cimgui_conf.module_name) },
},
@@ -54,6 +77,7 @@ pub fn build(b: *std.Build) void {
exe.root_module.linkLibrary(dep_sokol.artifact("sokol_clib"));
exe.root_module.link_libc = true;
+ exe.root_module.addCSourceFile(.{.file = b.path("deps/stb/stb_image.c")});
exe.step.dependOn(shader);
if (target.result.os.tag == .windows) {
diff --git a/build.zig.zon b/build.zig.zon
index 91f89d8..78eb04e 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -22,5 +22,13 @@
.url = "git+https://github.com/floooh/sokol-zig#ff40f04d4f51c73b2bf460d87fd2d7aa0dedc22f",
.hash = "sokol-0.1.0-pb1HKygbQQCINV-qSe4SFm92QWaT_95VpWTJkwYqzOrX",
},
+ .translate_c = .{
+ .url = "git+https://codeberg.org/ziglang/translate-c?ref=zig-0.16.x#6fe0ffc4549f15c5f2d9432c2b4460ba90ff85ac",
+ .hash = "translate_c-1.0.0-Q_BUWo_5BgD4flHdUhA31zOz0XvZk9k7lQv1ouzyNXj2",
+ },
+ .zigimg = .{
+ .url = "git+https://github.com/zigimg/zigimg.git#fcdd0d28d6393c747b2091874ed180856ab0ab73",
+ .hash = "zigimg-0.1.0-8_eo2llOGABcmeR44Z0MzHK5WK34KxFhrDBzqAjFpQF0",
+ },
},
}
diff --git a/src/main.zig b/src/main.zig
index 9f68540..d714e3f 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,6 +4,8 @@
// Render to an offscreen rendertarget texture, and use this texture
// for rendering to the display.
//------------------------------------------------------------------------------
+const std = @import("std");
+const stbi = @import("stbi");
const sokol = @import("sokol");
const slog = sokol.log;
const sg = sokol.gfx;
@@ -17,12 +19,32 @@ const mat4 = @import("math.zig").Mat4;
const shd = @import("shaders/main.glsl.zig");
const state = struct {
+ var alloc: std.mem.Allocator = undefined;
+ var io: std.Io = undefined;
+ const Width = 800;
+ const Height = 800;
+ const offscreen = struct {
+ var pass: sg.Pass = .{};
+ var pip: sg.Pipeline = .{};
+ var bind: sg.Bindings = .{};
+ var vs: shd.VsParams = .{
+ .position = .{ 0, 0 },
+ };
+ var fs: shd.FsParams = .{
+ .resolution = .{ Width, Height },
+ .rayCount = 16,
+ .maxSteps = 4,
+ .size = 1.0,
+ };
+
+ var pixels: [state.Width * state.Height * 4]u8 = @splat(255);
+ var pixels_updated: bool = true;
+ var pixels_img: sg.Image = undefined;
+ };
const display = struct {
var pass_action: sg.PassAction = .{};
var pip: sg.Pipeline = .{};
var bind: sg.Bindings = .{};
- var vs: shd.VsParams = .{.position = .{0,0}};
- var fs: shd.FsParams = .{.color = .{1,0,0,1}};
};
};
@@ -32,25 +54,74 @@ export fn init() void {
.logger = .{ .func = slog.func },
});
- simgui.setup(.{});
+ simgui.setup(.{
+ .logger = .{ .func = slog.func },
+ });
sappimgui.setup();
ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
- // default pass action: clear to blue-ish
+ state.offscreen.pass.action.colors[0] = .{
+ .load_action = .LOAD,
+ .store_action = .STORE,
+ };
+ state.offscreen.pass.action.depth = .{
+ .load_action = .LOAD,
+ .store_action = .STORE,
+ };
+
state.display.pass_action.colors[0] = .{
.load_action = .CLEAR,
- .clear_value = .{ .r = 1, .g = 0, .b = 0, .a = 1.0 },
};
- const vbuf = sg.makeBuffer(.{.data = sg.asRange(&[6]f32 {
+ const offscreen_width = state.Width;
+ const offscreen_height = state.Height;
+ const offscreen_sample_count = 1;
+ const color_img = sg.makeImage(.{
+ .usage = .{ .color_attachment = true },
+ .width = offscreen_width,
+ .height = offscreen_height,
+ .sample_count = offscreen_sample_count,
+ .pixel_format = .RGBA8,
+ });
+ state.offscreen.pixels_img = sg.makeImage(.{
+ .usage = .{ .dynamic_update = true },
+ .width = offscreen_width,
+ .height = offscreen_height,
+ .sample_count = offscreen_sample_count,
+ .pixel_format = .RGBA8,
+ });
+ const depth_img = sg.makeImage(.{
+ .usage = .{ .depth_stencil_attachment = true },
+ .width = offscreen_width,
+ .height = offscreen_height,
+ .sample_count = offscreen_sample_count,
+ .pixel_format = .DEPTH,
+ });
+
+ state.offscreen.pass.attachments.colors[0] = sg.makeView(.{
+ .color_attachment = .{ .image = color_img },
+ });
+ state.offscreen.pass.attachments.depth_stencil = sg.makeView(.{
+ .depth_stencil_attachment = .{ .image = depth_img },
+ });
+
+ state.offscreen.bind.views[shd.VIEW_off_tex] = sg.makeView(.{
+ .texture = .{ .image = state.offscreen.pixels_img },
+ });
+ state.display.bind.views[shd.VIEW_scr_tex] = sg.makeView(.{
+ .texture = .{ .image = color_img },
+ });
+
+ const vbuf = sg.makeBuffer(.{ .data = sg.asRange(&[6]f32{
-1.0, -1.0,
- -1.0, 3.0,
- 3.0, -1.0,
- })});
+ -1.0, 3.0,
+ 3.0, -1.0,
+ }) });
+ state.offscreen.bind.vertex_buffers[0] = vbuf;
state.display.bind.vertex_buffers[0] = vbuf;
// shader and pipeline object for the default render pass
- state.display.pip = sg.makePipeline(.{
+ state.offscreen.pip = sg.makePipeline(.{
.shader = sg.makeShader(shd.offscreenShaderDesc(sg.queryBackend())),
.layout = init: {
var l = sg.VertexLayoutState{};
@@ -58,44 +129,143 @@ export fn init() void {
break :init l;
},
.cull_mode = .BACK,
+ .sample_count = offscreen_sample_count,
+ .depth = .{
+ .pixel_format = .DEPTH,
+ .compare = .LESS_EQUAL,
+ .write_enabled = true,
+ },
+ .colors = init: {
+ var c: [sg.max_color_attachments]sg.ColorTargetState = @splat(.{});
+ c[0].pixel_format = .RGBA8;
+ break :init c;
+ },
+ });
+
+ state.display.pip = sg.makePipeline(.{
+ .shader = sg.makeShader(shd.defaultShaderDesc(sg.queryBackend())),
+ .layout = init: {
+ var l = sg.VertexLayoutState{};
+ l.attrs[shd.ATTR_default_vpos] = .{ .format = .FLOAT2 };
+ break :init l;
+ },
+ .cull_mode = .BACK,
.depth = .{
.compare = .LESS_EQUAL,
.write_enabled = true,
},
});
+
+ // a sampler object for sampling the render target texture
+ state.offscreen.bind.samplers[shd.SMP_off_smp] = sg.makeSampler(.{
+ .min_filter = .LINEAR,
+ .mag_filter = .LINEAR,
+ .wrap_u = .REPEAT,
+ .wrap_v = .REPEAT,
+ });
+ state.display.bind.samplers[shd.SMP_scr_smp] = sg.makeSampler(.{
+ .min_filter = .LINEAR,
+ .mag_filter = .LINEAR,
+ .wrap_u = .REPEAT,
+ .wrap_v = .REPEAT,
+ });
}
export fn event(ev: [*c]const sapp.Event) void {
sappimgui.trackEvent(ev.*);
- // forward input events to sokol-imgui
_ = simgui.handleEvent(ev.*);
+
+ switch (ev.*.type) {
+ .KEY_DOWN => {
+ if (ev.*.key_code == .ESCAPE) {
+ sapp.requestQuit();
+ }
+ },
+ else => {},
+ }
}
+// fn load_bmp(path: []const u8, buffer: []u8) !void {
+// var file = try std.Io.Dir.cwd().openFile(state.io, path, .{});
+// var read_buffer: [1024]u8 = undefined;
+// var reader = file.reader(state.io, &read_buffer).interface;
+// var bfType = try reader.takeInt(u16, .little);
+// var bfSize = try reader.takeInt(u32, .little);
+// var bfReserved = try reader.takeInt(u32, .little);
+// var bfOffBits = try reader.takeInt(u32, .little);
+// var biSize = try reader.takeInt(u32, .little);
+// var biWidth = try reader.takeInt(u32, .little);
+// var biHeight = try reader.takeInt(u32, .little);
+// var biPlanes = try reader.takeInt(u16, .little);
+// var biBitCount = try reader.takeInt(u16, .little);
+// var biCompression = try reader.takeInt(u32, .little);
+// var biSizeImage = try reader.takeInt(u32, .little);
+// var biXPelsPerMeter = try reader.takeInt(u32, .little);
+// var biYPelsPerMeter = try reader.takeInt(u32, .little);
+// var biClrUsed = try reader.takeInt(u32, .little);
+// var biClrImportant = try reader.takeInt(u32, .little);
+//
+// if (buffer.len < biWidth * biHeight * biBitCount / 8) {
+// return error.BufferTooSmall;
+// }
+//
+//
+// }
+
export fn frame() void {
// call simgui.newFrame() before any ImGui calls
+ sappimgui.trackFrame();
simgui.newFrame(.{
.width = sapp.width(),
.height = sapp.height(),
.delta_time = sapp.frameDuration(),
.dpi_scale = sapp.dpiScale(),
});
- sappimgui.trackFrame();
// const dt: f32 = @floatCast(sapp.frameDuration() * 60);
// const aspect = sapp.widthf() / sapp.heightf();
-
- if (ig.igBegin("abc", null, 0))
- {
- _ = ig.igDragFloat2("position", &state.display.vs.position);
- _ = ig.igColorEdit4("color", &state.display.fs.color, 0);
- ig.igEnd();
+ state.offscreen.fs.resolution[0] = sapp.widthf();
+ state.offscreen.fs.resolution[1] = sapp.heightf();
+
+ if (ig.igBegin("abc", null, 0)) {
+ // vs
+ _ = ig.igDragFloat2("position", &state.offscreen.vs.position);
+
+ // fs
+ _ = ig.igInputInt("rayCount", &state.offscreen.fs.rayCount);
+ _ = ig.igInputInt("maxSteps", &state.offscreen.fs.maxSteps);
+ _ = ig.igDragFloatEx("size", &state.offscreen.fs.size, 0.1, 0, 100, "%f", 0);
+
+ if (ig.igButton("load png")) {
+ var x: c_int = 0;
+ var y: c_int = 0;
+ var n: c_int = 0;
+ const img = stbi.stbi_load("test.png", &x, &y, &n, 0);
+ @memcpy(&state.offscreen.pixels, img);
+ stbi.stbi_image_free(img);
+ state.offscreen.pixels_updated = true;
+ }
+ }
+ ig.igEnd();
+
+ if (state.offscreen.pixels_updated) {
+ state.offscreen.pixels_updated = false;
+ var imageData: sg.ImageData = .{};
+ imageData.mip_levels[0] = sg.asRange(&state.offscreen.pixels);
+ sg.updateImage(state.offscreen.pixels_img, imageData);
}
+ sg.beginPass(state.offscreen.pass);
+ sg.applyPipeline(state.offscreen.pip);
+ sg.applyBindings(state.offscreen.bind);
+ sg.applyUniforms(shd.UB_vs_params, sg.asRange(&state.offscreen.vs));
+ sg.applyUniforms(shd.UB_fs_params, sg.asRange(&state.offscreen.fs));
+ sg.draw(0, 3, 1);
+ sg.endPass();
+
sg.beginPass(.{ .action = state.display.pass_action, .swapchain = sglue.swapchain() });
sg.applyPipeline(state.display.pip);
sg.applyBindings(state.display.bind);
- sg.applyUniforms(shd.UB_vs_params, sg.asRange(&state.display.vs));
- sg.applyUniforms(shd.UB_fs_params, sg.asRange(&state.display.fs));
sg.draw(0, 3, 1);
simgui.render();
sg.endPass();
@@ -109,27 +279,20 @@ export fn cleanup() void {
sg.shutdown();
}
-pub fn main() void {
+pub fn main(_init: std.process.Init) void {
+ state.alloc = _init.arena.allocator();
+ state.io = _init.io;
+
sapp.run(.{
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.sample_count = 4,
- .width = 800,
- .height = 600,
+ .width = state.Width,
+ .height = state.Height,
.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/shaders/main.glsl b/src/shaders/main.glsl
index 4e2d4ea..784176f 100644
--- a/src/shaders/main.glsl
+++ b/src/shaders/main.glsl
@@ -11,22 +11,115 @@ layout(binding=0) uniform vs_params {
};
in vec2 vpos;
+out vec2 fpos;
void main() {
gl_Position = vec4(vpos + position, 0, 1);
+ fpos = (vpos + 1) * 0.5;
}
@end
@fs fs_offscreen
+layout(binding=1) uniform texture2D off_tex;
+layout(binding=1) uniform sampler off_smp;
layout(binding=1) uniform fs_params {
- vec4 color;
+ vec2 resolution;
+ int rayCount;
+ int maxSteps;
+ float size;
};
+in vec2 fpos;
out vec4 frag_color;
+bool outOfBounds(vec2 uv) {
+ return uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0;
+}
+
+#define TAU 6.28318530718
+// #define RAYCOUNT 16
+// #define MAXSTEPS 8
+
+float rand(vec2 seed)
+{
+ return fract(sin(dot(seed.xy, vec2(12.9898, 78.233))) * 43758.5453);
+}
+
+vec4 raymarch() {
+ vec2 scale = resolution.xy / resolution.xy;
+
+ // sample texture at pixel
+ vec4 light = textureLod(sampler2D(off_tex, off_smp), fpos * scale, 0);
+ // if it is a light source, color that pixel in this color
+ if (light.a > 0.1)
+ {
+ return light;
+ }
+
+ float oneOverRayCount = 1.0 / float(rayCount);
+ float tauOverRayCount = TAU * oneOverRayCount;
+
+ float noise = rand(fpos);
+
+ vec4 radiance = vec4(0.0);
+
+ for (int i = 0; i < rayCount; i++)
+ {
+ float angle = tauOverRayCount * (float(i) + noise);
+ vec2 rayDirectionUv = vec2(cos(angle), -sin(angle)) / size;
+
+ vec2 sampleUv = fpos + rayDirectionUv;
+
+ for (int step = 0; step < maxSteps; step++)
+ {
+ if (outOfBounds(sampleUv))
+ break;
+
+ vec4 sampleLight = textureLod(sampler2D(off_tex, off_smp), sampleUv, 0);
+ if (sampleLight.a > 0.1)
+ {
+ radiance += sampleLight;
+ break;
+ }
+
+ sampleUv += rayDirectionUv;
+ }
+ }
+
+ return radiance * oneOverRayCount;
+}
+
void main() {
- frag_color = color;
+ frag_color = raymarch();
}
@end
@program offscreen vs_offscreen fs_offscreen
+
+
+// default-pass shaders
+@vs vs_default
+
+in vec2 vpos;
+out vec2 fpos;
+
+void main() {
+ gl_Position = vec4(vpos, 0, 1);
+ fpos = (vpos + 1) * 0.5;
+}
+@end
+
+@fs fs_default
+layout(binding=0) uniform texture2D scr_tex;
+layout(binding=0) uniform sampler scr_smp;
+
+in vec2 fpos;
+
+out vec4 frag_color;
+
+void main() {
+ frag_color = texture(sampler2D(scr_tex, scr_smp), fpos);
+}
+@end
+
+@program default vs_default fs_default
diff --git a/test.png b/test.png
new file mode 100755
index 0000000..80f2eba
--- /dev/null
+++ b/test.png
Binary files differ