From 3f41dbdfc27ad9d42ce254618b7bafc58797f8b2 Mon Sep 17 00:00:00 2001 From: patrick-scho Date: Mon, 1 Sep 2025 15:07:41 +0200 Subject: [PATCH] changezzz --- build.zig | 24 ++- build.zig.zon | 12 +- shd/main.glsl | 301 ++++++++++++++++++++++++++++++++++ shd/quad.glsl | 153 ------------------ src/main.zig | 441 ++++++++++++++++++++++++++++++++++++++------------ 5 files changed, 662 insertions(+), 269 deletions(-) create mode 100644 shd/main.glsl delete mode 100644 shd/quad.glsl diff --git a/build.zig b/build.zig index 0fe3fbf..8742838 100644 --- a/build.zig +++ b/build.zig @@ -5,6 +5,7 @@ const ResolvedTarget = Build.ResolvedTarget; const Dependency = Build.Dependency; const sokol = @import("sokol"); const cimgui = @import("cimgui"); +const shdc = @import("shdc"); pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); @@ -30,11 +31,11 @@ pub fn build(b: *Build) !void { // shaders dep_sokol.artifact("sokol_clib").addIncludePath(b.path("ext/cimgui")); - const dep_shdc = dep_sokol.builder.dependency("shdc", .{}); - const shdc_step = try sokol.shdc.createSourceFile(b, .{ + const dep_shdc = b.dependency("shdc", .{}); + const shdc_step = try shdc.createSourceFile(b, .{ .shdc_dep = dep_shdc, - .input = "shd/quad.glsl", - .output = "src/shd/quad.glsl.zig", + .input = "shd/main.glsl", + .output = "src/shd/main.glsl.zig", .slang = .{ .glsl430 = true }, }); @@ -64,16 +65,27 @@ pub fn build(b: *Build) !void { } else { const exe = try buildNative(b, mod_main); exe.step.dependOn(shdc_step); + + exe.root_module.link_libc = true; + exe.root_module.linkSystemLibrary("X11", .{ .needed = true }); + exe.root_module.linkSystemLibrary("Xcursor", .{ .needed = true }); + + const exe_check = b.addExecutable(.{ + .name = "sporegirl", + .root_module = mod_main, + }); + const check = b.step("check", "Check"); + check.dependOn(&exe_check.step); } } fn buildNative(b: *Build, mod: *Build.Module) !*Build.Step.Compile { const exe = b.addExecutable(.{ - .name = "demo", + .name = "sporegirl", .root_module = mod, }); b.installArtifact(exe); - b.step("run", "Run demo").dependOn(&b.addRunArtifact(exe).step); + b.step("run", "Run Sporegirl").dependOn(&b.addRunArtifact(exe).step); return exe; } diff --git a/build.zig.zon b/build.zig.zon index bc7ef94..74704ce 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -9,12 +9,16 @@ .dependencies = .{ .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#zig-0.14.1", - .hash = "sokol-0.1.0-pb1HK26VLQC1XOkHDW-5TglgwypAKIHGR2HPOTP6limn", + .url = "git+https://github.com/floooh/sokol-zig.git#0831d1f15d2e1bc2d26a4556dba15b8962c29f00", + .hash = "sokol-0.1.0-pb1HKwHSLQAjEIRTGaVbHdtf6zt9VhNsOHuoVfTc2Z1i", }, .cimgui = .{ - .url = "git+https://github.com/floooh/dcimgui#3b98e0a57fc17cc72fdda6934bd932426778a16e", - .hash = "cimgui-0.1.0-44ClkczdkgCE9Z_0ehliUmEJjg-bjQVy1r55RQPw9N10", + .url = "git+https://github.com/floooh/dcimgui#de39f1d7106d448909d9776eb0049fbfed24d056", + .hash = "cimgui-0.1.0-44ClkQ_ekgDNAf1N4Fl1mJnVDyO83xbcqQElNUtURXcA", + }, + .shdc = .{ + .url = "git+https://github.com/floooh/sokol-tools-bin#d1a6a719f24852f104a7f5481beb31e42ebf5f8a", + .hash = "sokolshdc-0.1.0-r2KZDtYXlQS2C2CNekXgsRqhyKk5JtGzBlFBTMX27o0f", }, }, diff --git a/shd/main.glsl b/shd/main.glsl new file mode 100644 index 0000000..c6ec364 --- /dev/null +++ b/shd/main.glsl @@ -0,0 +1,301 @@ +// === combine === +@vs vs_combine +const vec2 positions[4] = { vec2(-1, 1), vec2(1, 1), vec2(1, -1), vec2(-1, -1) }; +const vec2 uvs[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) }; +const int indices[6] = { 0, 1, 2, 0, 2, 3 }; + +layout(binding=0) uniform Game +{ + vec2 screen; + vec2 cam; +}; + +out vec2 uv; + +void main() { + int idx = indices[gl_VertexIndex]; + vec2 pos = positions[idx]; + uv = uvs[idx]; + + gl_Position = vec4(pos, 1, 1); +} +@end + +@fs fs_combine +layout(binding=0) uniform texture2D tex_tex; +layout(binding=1) uniform texture2D tex_shadow; +layout(binding=2) uniform texture2D tex_light; +layout(binding=0) uniform sampler smp; + +in vec2 uv; +out vec4 frag_color; + + +void main() { + vec4 tb = vec4(0.8,0.3,0.3,1); + if (mod(floor(uv.x*10), 2) != mod(floor(uv.y*10),2)) { + tb = vec4(0.3,0.3,0.8,1); + } + vec4 tt = texture(sampler2D(tex_tex, smp), uv); + vec4 ts = texture(sampler2D(tex_shadow, smp), uv); + vec4 tl = texture(sampler2D(tex_light, smp), uv); + + vec3 l = tt.a * (tt.rgb * tl.rgb) + (1-tt.a) * (tl.rgb * tl.a); + l = ts.a * (l * ts.rgb) + (1-ts.a) * (l); + frag_color = vec4(l, 1); + + vec3 a = (tb.rgb + tl.rgb) * tt.rgb * ts.rgb; // + tl.rgb * tl.a; + frag_color = vec4(a, 1); + + + vec3 bg = tb.rgb * tl.rgb; + vec3 bg_s = bg * (1-ts.a) + bg * ts.rgb * 0.5 * ts.a; + vec3 t = tt.rgb * tl.rgb; + vec3 t_s = t * (1-ts.a) + t * ts.rgb * 0.5 * ts.a; + vec3 bg_s_t_s = bg_s * (1-tt.a) + t_s * tt.a; + frag_color = vec4(bg_s_t_s, 1); + + // frag_color = vec4(tl); +} +@end + +@program combine vs_combine fs_combine + + +// === tex === +@vs vs_tex +const vec2 positions[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) }; +const vec2 uvs[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; +const int indices[6] = { 0, 1, 2, 0, 2, 3 }; + +layout(binding=0) uniform Game +{ + vec2 screen; + vec2 cam; +}; + +in vec2 inst_pos; +in ivec2 inst_size; +out vec2 uv; + +void main() { + int idx = indices[gl_VertexIndex]; + vec2 pos = positions[idx]; + uv = uvs[idx]; + + vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2; + gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); +} +@end + +@fs fs_tex +layout(binding=0) uniform texture2D tex; +layout(binding=0) uniform sampler smp; + +in vec2 uv; +out vec4 frag_color; + +void main() { + vec4 tex_color = texture(sampler2D(tex, smp), uv); + if (tex_color.a < 1) + discard; + frag_color = tex_color; +} +@end + +@program tex vs_tex fs_tex + + +// === shadows === +@vs vs_shadow +const vec2 positions[3] = { vec2(0, 1), vec2(1, 1), vec2(0.5, 0) }; +const vec2 uvs[3] = { vec2(0, 0), vec2(1, 0), vec2(0.5, 1) }; + +layout(binding=0) uniform Game +{ + vec2 screen; + vec2 cam; +}; + +layout(binding=1) uniform Light +{ + vec3 color; + vec2 pos; + float reach; + float radius_glow; + float intensity; + float shadow_brightness; +} light; + +in vec2 inst_pos; +in ivec2 inst_size; +out vec2 uv; +out vec3 light_color; +out float shadow_brightness; + +// https://gist.github.com/yiwenl/3f804e80d0930e34a0b33359259b556c +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, s, -s, c); + return m * v; +} + +void main() { + int idx = gl_VertexIndex; + vec2 pos = positions[idx]; + uv = uvs[idx]; + light_color = light.color; + shadow_brightness = light.shadow_brightness; + + if (idx == 2) { // keep pos for base + vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2; + gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); + } + else { + vec2 anchor = vec2(0.5, 0); + vec2 anchor_abs = inst_pos + inst_size * anchor - cam - screen / 2; + vec2 light_pos_abs = light.pos - cam - screen / 2; + vec2 dir = anchor_abs - light_pos_abs; + + vec2 left_right[] = { + rotate(dir, 1.5708), + rotate(dir, -1.5708), + }; + + float light_radius_factor = 1.5 / log(light.reach); + + vec2 inst_pos_abs = anchor_abs + dir + left_right[idx] * light_radius_factor; + gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); + } +} +@end + +@fs fs_shadow +layout(binding=0) uniform texture2D tex; +layout(binding=0) uniform sampler smp; + +in vec2 uv; +in vec3 light_color; +in float shadow_brightness; +out vec4 frag_color; + +vec3 brighten(vec3 c, float f) { + vec3 d = vec3(1) - c; + return c + d * f; +} + +vec4 blur(float radius, float resolution) { + float blur = radius/resolution; + + float hstep = 1; + float vstep = 1; + + vec4 sum = vec4(0.0); + + sum += texture(sampler2D(tex, smp), vec2(uv.x - 4.0*blur*hstep, uv.y - 4.0*blur*vstep)) * 0.0162162162; + sum += texture(sampler2D(tex, smp), vec2(uv.x - 3.0*blur*hstep, uv.y - 3.0*blur*vstep)) * 0.0540540541; + sum += texture(sampler2D(tex, smp), vec2(uv.x - 2.0*blur*hstep, uv.y - 2.0*blur*vstep)) * 0.1216216216; + sum += texture(sampler2D(tex, smp), vec2(uv.x - 1.0*blur*hstep, uv.y - 1.0*blur*vstep)) * 0.1945945946; + + sum += texture(sampler2D(tex, smp), vec2(uv.x, uv.y)) * 0.2270270270; + + sum += texture(sampler2D(tex, smp), vec2(uv.x + 1.0*blur*hstep, uv.y + 1.0*blur*vstep)) * 0.1945945946; + sum += texture(sampler2D(tex, smp), vec2(uv.x + 2.0*blur*hstep, uv.y + 2.0*blur*vstep)) * 0.1216216216; + sum += texture(sampler2D(tex, smp), vec2(uv.x + 3.0*blur*hstep, uv.y + 3.0*blur*vstep)) * 0.0540540541; + sum += texture(sampler2D(tex, smp), vec2(uv.x + 4.0*blur*hstep, uv.y + 4.0*blur*vstep)) * 0.0162162162; + + return sum; +} + +void main() { + vec4 tex_color = blur(10, 1); + tex_color = texture(sampler2D(tex, smp), uv); + + vec3 brighter = brighten(light_color, shadow_brightness); + frag_color = vec4(brighter * tex_color.a + vec3(1) * (1-tex_color.a), tex_color.a); +} +@end + +@program shadow vs_shadow fs_shadow + + +// === lights === +@vs vs_light +const vec2 positions[4] = { vec2(-0.5, 0.5), vec2(0.5, 0.5), vec2(0.5, -0.5), vec2(-0.5, -0.5) }; +const int indices[6] = { 0, 1, 2, 0, 2, 3 }; + +layout(binding=0) uniform Game +{ + vec2 screen; + vec2 cam; +}; + +layout(binding=1) uniform Light +{ + vec3 color; + vec2 pos; + float reach; + float radius_glow; + float intensity; + float shadow_brightness; +} light; + +out vec3 light_col; +out vec2 light_pos; +out float light_rea; +out float light_glo; +out float light_intensity; +out vec2 fragpos; + +void main() { + int idx = indices[gl_VertexIndex]; + vec2 pos = positions[idx]; + + vec2 inst_pos_ap = light.pos + light.reach * 2 * pos; + vec2 inst_pos_rp = inst_pos_ap - cam - screen / 2; + vec2 inst_pos_rw = inst_pos_rp / screen * 2; + gl_Position = vec4(inst_pos_rw, 1, 1); + + light_col = light.color; + light_pos = light.pos; + light_rea = light.reach; + light_glo = light.radius_glow; + light_intensity = light.intensity; + fragpos = inst_pos_ap; +} +@end + +@fs fs_light +layout(binding=0) uniform Game +{ + vec2 screen; + vec2 cam; +}; + +in vec3 light_col; +in vec2 light_pos; +in float light_rea; +in float light_glo; +in float light_intensity; +in vec2 fragpos; +out vec4 frag_color; + +float dist(vec2 a, vec2 b) { + float d1 = a.x - b.x; + float d2 = a.y - b.y; + return sqrt(d1*d1 + d2*d2); +} + +void main() { + // vec2 frag_coord_abs = gl_FragCoord.xy * v_screen + v_cam + v_screen / 2; + // float factor = dist(light_pos, frag_coord_abs) / light_rad; + float factor_rea = 1 - (dist(light_pos, fragpos) / light_rea); + float factor_glo = 1 - (dist(light_pos, fragpos) / light_glo); + if (factor_rea > 1.0) discard; + frag_color = vec4(light_col.rgb * factor_rea, factor_glo * light_intensity); + // frag_color = vec4(vec3(mod(factor, 1.0)) * light_col, factor); +} +@end + +@program light vs_light fs_light diff --git a/shd/quad.glsl b/shd/quad.glsl deleted file mode 100644 index 7f073c4..0000000 --- a/shd/quad.glsl +++ /dev/null @@ -1,153 +0,0 @@ -/* quad vertex shader */ -@vs vs -const vec2 positions[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) }; -const vec2 uvs[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; -const int indices[6] = { 0, 1, 2, 0, 2, 3 }; - -layout(binding=0) uniform Game -{ - vec2 screen; - vec2 cam; -}; - -in vec2 inst_pos; -in ivec2 inst_size; -out vec2 uv; - -void main() { - int idx = indices[gl_VertexIndex]; - vec2 pos = positions[idx]; - uv = uvs[idx]; - - vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2; - gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); -} -@end - -/* quad fragment shader */ -@fs fs -layout(binding=0) uniform texture2D tex; -layout(binding=0) uniform sampler smp; - -in vec2 uv; -out vec4 frag_color; - -void main() { - vec4 tex_color = texture(sampler2D(tex, smp), uv); - if (tex_color.a < 1) - discard; - frag_color = tex_color; -} -@end - -/* quad shader program */ -@program quad vs fs - - - - - -@vs vs_shadow -const vec2 positions[4] = { vec2(0, 1), vec2(1, 1), vec2(1, 0), vec2(0, 0) }; -const vec2 uvs[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; -const int indices[6] = { 0, 1, 2, 0, 2, 3 }; - -layout(binding=0) uniform Game -{ - vec2 screen; - vec2 cam; -}; - -layout(binding=1) uniform Light -{ - vec2 light_pos; -}; - -in vec2 inst_pos; -in ivec2 inst_size; -out vec2 uv; - -vec2 rotate(vec2 v, float a) { - float s = sin(a); - float c = cos(a); - mat2 m = mat2(c, s, -s, c); - return m * v; -} - -void main() { - int idx = indices[gl_VertexIndex]; - vec2 pos = positions[idx]; - uv = uvs[idx]; - - if (idx == 2 || idx == 3) { // keep pos for lower vertices (base) - vec2 inst_pos_abs = inst_pos + inst_size * pos - cam - screen / 2; - gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); - } - else { - vec2 anchor = vec2(0.5, 0); - vec2 anchor_abs = inst_pos + inst_size * anchor - cam - screen / 2; - vec2 light_pos_abs = light_pos - cam - screen / 2; - vec2 dir = anchor_abs - light_pos_abs; - - vec2 left_right[] = { - rotate(dir, 1.5708), - rotate(dir, -1.5708), - }; - - vec2 inst_pos_abs = anchor_abs + dir + left_right[idx] / 2; - gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); - } -} -@end - -@fs fs_shadow -layout(binding=0) uniform texture2D tex; -layout(binding=0) uniform sampler smp; - -in vec2 uv; -out vec4 frag_color; - -void main() { - vec4 tex_color = texture(sampler2D(tex, smp), uv); - // if (tex_color.a < 1) - // discard; - frag_color = vec4(0, 0, 0, 1); -} -@end - -@program shadow vs_shadow fs_shadow - - - -@vs vs_light -const vec2 positions[4] = { vec2(-0.5, 0.5), vec2(0.5, 0.5), vec2(0.5, -0.5), vec2(-0.5, -0.5) }; -const int indices[6] = { 0, 1, 2, 0, 2, 3 }; - -layout(binding=0) uniform Game -{ - vec2 screen; - vec2 cam; -}; - -in vec2 light_pos; - -void main() { - int idx = indices[gl_VertexIndex]; - vec2 pos = positions[idx]; - - vec2 light_size = vec2(10, 10); - - vec2 inst_pos_abs = light_pos + light_size * pos - cam - screen / 2; - gl_Position = vec4(inst_pos_abs / screen * 2, 1, 1); -} -@end - -@fs fs_light -out vec4 frag_color; - -void main() { - frag_color = vec4(1, 0, 0, 1); -} -@end - -@program light vs_light fs_light diff --git a/src/main.zig b/src/main.zig index 4813e69..bf77621 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,61 +1,11 @@ -const std = @import("std"); - -const ig = @import("cimgui_docking"); -const sokol = @import("sokol"); -const slog = sokol.log; -const sg = sokol.gfx; -const sapp = sokol.app; -const sglue = sokol.glue; -const sfetch = sokol.fetch; -const simgui = sokol.imgui; - -const stbi = @cImport({ - @cInclude("stb_image.h"); -}); - -const shd = @import("shd/quad.glsl.zig"); - -const state = struct { - const MaxSprites = 100; - const MaxLights = 100; - - var pass_action: sg.PassAction = .{}; - var bind: sg.Bindings = .{}; - var pip: sg.Pipeline = .{}; - - var pip_shadow: sg.Pipeline = .{}; - - var bind_light: sg.Bindings = .{}; - var pip_light: sg.Pipeline = .{}; - - var img_buf: [1024 * 1024]u8 = undefined; - - var cam = [2]f32{ -300, -300 }; - - var sprites_buf: [MaxSprites]Sprite = undefined; - var sprites = std.ArrayListUnmanaged(Sprite).initBuffer(&state.sprites_buf); - var lights_buf: [MaxLights]Light = undefined; - var lights = std.ArrayListUnmanaged(Light).initBuffer(&state.lights_buf); -}; - -const Sprite = struct { - texid: usize = shd.IMG_tex, - pos: [2]f32 = [_]f32{ 0, 0 }, - size: [2]i32 = [_]i32{ 300, 300 }, -}; - -const Light = struct { - pos: [2]f32 = [_]f32{ 0, 0 }, -}; - export fn init() void { // initialize sokol-gfx sg.setup(.{ .buffer_pool_size = 8, - .image_pool_size = 4, + .image_pool_size = 16, .shader_pool_size = 8, .pipeline_pool_size = 8, - .attachments_pool_size = 1, + .view_pool_size = 16, .environment = sglue.environment(), .logger = .{ .func = slog.func }, }); @@ -65,48 +15,142 @@ export fn init() void { .num_lanes = 1, .logger = .{ .func = slog.func }, }); - - // initialize sokol-imgui + stime.setup(); simgui.setup(.{ .logger = .{ .func = slog.func }, }); ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; // initial clear color - state.pass_action.colors[0] = .{ + state.shaders.tex.pass.action.colors[0] = sg.ColorAttachmentAction{ + .load_action = .CLEAR, + .store_action = .STORE, + .clear_value = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }, + }; + state.shaders.shadow.pass.action.colors[0] = sg.ColorAttachmentAction{ .load_action = .CLEAR, - .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 }, + .store_action = .STORE, + .clear_value = .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 0.0 }, }; + state.shaders.light.pass.action.colors[0] = sg.ColorAttachmentAction{ + .load_action = .CLEAR, + .store_action = .STORE, + .clear_value = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }, + }; + + // combining pass + state.shaders.tex.img = sg.makeImage(.{ + .usage = .{ .color_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .RGBA8, + }); + state.shaders.tex.depth = sg.makeImage(.{ + .usage = .{ .depth_stencil_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .DEPTH, + }); + state.shaders.shadow.img = sg.makeImage(.{ + .usage = .{ .color_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .RGBA8, + }); + state.shaders.shadow.depth = sg.makeImage(.{ + .usage = .{ .depth_stencil_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .DEPTH, + }); + state.shaders.light.img = sg.makeImage(.{ + .usage = .{ .color_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .RGBA8, + }); + state.shaders.light.depth = sg.makeImage(.{ + .usage = .{ .depth_stencil_attachment = true }, + .width = state.OffscreenWidth, + .height = state.OffscreenHeight, + .sample_count = state.OffscreenSampleCount, + .pixel_format = .DEPTH, + }); + + state.shaders.combine.bind.views[shd.VIEW_tex_tex] = sg.makeView(.{ + .texture = .{ .image = state.shaders.tex.img }, + }); + state.shaders.combine.bind.views[shd.VIEW_tex_shadow] = sg.makeView(.{ + .texture = .{ .image = state.shaders.shadow.img }, + }); + state.shaders.combine.bind.views[shd.VIEW_tex_light] = sg.makeView(.{ + .texture = .{ .image = state.shaders.light.img }, + }); + state.shaders.combine.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{ + .min_filter = sg.Filter.LINEAR, + .mag_filter = sg.Filter.LINEAR, + }); - // default pass - state.bind.images[shd.IMG_tex] = sg.allocImage(); + state.shaders.combine.pip = sg.makePipeline(.{ + .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())), + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, + }); - state.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{ + // texture pass + state.img = sg.allocImage(); + state.shaders.tex.pass.attachments.colors[0] = sg.makeView(.{ + .color_attachment = .{ .image = state.shaders.tex.img }, + }); + state.shaders.tex.pass.attachments.depth_stencil = sg.makeView(.{ + .depth_stencil_attachment = .{ .image = state.shaders.tex.depth }, + }); + state.shaders.tex.bind.views[shd.VIEW_tex] = sg.allocView(); + state.shaders.tex.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{ .min_filter = sg.Filter.NEAREST, .mag_filter = sg.Filter.NEAREST, + .wrap_u = sg.Wrap.CLAMP_TO_EDGE, + .wrap_v = sg.Wrap.CLAMP_TO_EDGE, }); // dynamic vertex buffer for instancing data - state.bind.vertex_buffers[0] = sg.makeBuffer(.{ + state.shaders.tex.bind.vertex_buffers[0] = sg.makeBuffer(.{ .usage = .{ .stream_update = true }, .size = state.MaxSprites * @sizeOf(Sprite), }); // a shader and pipeline state object - state.pip = sg.makePipeline(.{ - .shader = sg.makeShader(shd.quadShaderDesc(sg.queryBackend())), + state.shaders.tex.pip = sg.makePipeline(.{ + .shader = sg.makeShader(shd.texShaderDesc(sg.queryBackend())), .layout = init: { var l = sg.VertexLayoutState{}; l.buffers[0].step_func = .PER_INSTANCE; - l.attrs[shd.ATTR_quad_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") }; - l.attrs[shd.ATTR_quad_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") }; + l.attrs[shd.ATTR_tex_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") }; + l.attrs[shd.ATTR_tex_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") }; l.buffers[0].stride = @sizeOf(Sprite); break :init l; }, + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, }); // shadow pass - state.pip_shadow = sg.makePipeline(.{ + state.shaders.shadow.pass.attachments.colors[0] = sg.makeView(.{ + .color_attachment = .{ .image = state.shaders.shadow.img }, + }); + state.shaders.shadow.pass.attachments.depth_stencil = sg.makeView(.{ + .depth_stencil_attachment = .{ .image = state.shaders.shadow.depth }, + }); + state.shaders.shadow.pip = sg.makePipeline(.{ .shader = sg.makeShader(shd.shadowShaderDesc(sg.queryBackend())), .layout = init: { var l = sg.VertexLayoutState{}; @@ -116,22 +160,58 @@ export fn init() void { l.buffers[0].stride = @sizeOf(Sprite); break :init l; }, + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, + .colors = init: { + var c: [4]sg.ColorTargetState = @splat(.{}); + c[0] = .{ + .blend = .{ + .enabled = true, + .src_factor_rgb = .DST_COLOR, + .dst_factor_rgb = .ZERO, + .op_rgb = .ADD, + .src_factor_alpha = .ONE, + .dst_factor_alpha = .ONE, + .op_alpha = .ADD, + }, + }; + break :init c; + }, }); // light pass - state.bind_light.vertex_buffers[0] = sg.makeBuffer(.{ + state.shaders.light.pass.attachments.colors[0] = sg.makeView(.{ + .color_attachment = .{ .image = state.shaders.light.img }, + }); + state.shaders.light.pass.attachments.depth_stencil = sg.makeView(.{ + .depth_stencil_attachment = .{ .image = state.shaders.light.depth }, + }); + state.shaders.light.bind.vertex_buffers[0] = sg.makeBuffer(.{ .usage = .{ .stream_update = true }, - .size = state.MaxLights * @sizeOf(Light), + .size = state.MaxLights * @sizeOf(shd.Light), }); - state.pip_light = sg.makePipeline(.{ + state.shaders.light.pip = sg.makePipeline(.{ .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())), - .layout = init: { - var l = sg.VertexLayoutState{}; - l.buffers[0].step_func = .PER_INSTANCE; - l.attrs[shd.ATTR_light_light_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Light, "pos") }; - l.buffers[0].stride = @sizeOf(Light); - break :init l; + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, + .blend_color = sg.Color{ .r=0.5,.g=0.5,.b=0.5,.a = 0.5 }, + .colors = init: { + var c: [4]sg.ColorTargetState = @splat(.{}); + c[0] = .{ + .blend = .{ + .enabled = true, + .src_factor_rgb = .SRC_COLOR, + .dst_factor_rgb = .ONE_MINUS_SRC_COLOR, + .src_factor_alpha = .ONE, + .dst_factor_alpha = .ONE, + }, + }; + break :init c; }, }); @@ -142,9 +222,10 @@ export fn init() void { var w: c_int = 0; var h: c_int = 0; var n: c_int = 0; + // TODO: free memory? const pixels = stbi.stbi_load_from_memory(@ptrCast(res.*.data.ptr.?), @intCast(res.*.data.size), &w, &h, &n, 4); if (pixels != null) { - sg.initImage(state.bind.images[shd.IMG_tex], .{ + sg.initImage(state.img, .{ .width = w, .height = h, .pixel_format = sg.PixelFormat.RGBA8, @@ -157,6 +238,9 @@ export fn init() void { break :init i; }, }); + sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{ + .texture = .{ .image = state.img }, + }); } } } @@ -168,12 +252,33 @@ export fn init() void { }); state.sprites.appendAssumeCapacity(.{}); - state.lights.appendAssumeCapacity(.{ .pos = [_]f32{ 150, -300 } }); + state.lights.appendAssumeCapacity(DefaultLight); + state.lights.appendAssumeCapacity(DefaultLight); + state.lights.items[0].color = [_]f32{ 0.8, 1, 0.8 }; +} + +export fn cleanup() void { + simgui.shutdown(); + sfetch.shutdown(); + sg.shutdown(); +} + +export fn event(ev: [*c]const sapp.Event) void { + // forward input events to sokol-imgui + _ = simgui.handleEvent(ev.*); + + switch (ev.*.type) { + .MOUSE_MOVE => { + state.lights.items[0].pos[0] = ev.*.mouse_x + state.cam[0]; + state.lights.items[0].pos[1] = (sapp.heightf() - ev.*.mouse_y) + state.cam[1]; + }, + else => {}, + } } fn ui() void { if (ig.igBegin("Hello Dear ImGui!", null, ig.ImGuiWindowFlags_None)) { - _ = ig.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None); + ig.igLabelText("fps", "%d", state.fps); _ = ig.igDragFloat2("cam", &state.cam); { @@ -204,7 +309,7 @@ fn ui() void { { ig.igPushIDPtr(state.lights.items.ptr); if (ig.igButton("+")) { - _ = state.lights.appendAssumeCapacity(.{}); + _ = state.lights.appendAssumeCapacity(DefaultLight); } var i: u16 = 0; @@ -213,8 +318,18 @@ fn ui() void { i += 1; ig.igPushIDInt(i); ig.igPushItemWidth(100); + _ = ig.igColorEdit3("color", &light.color, ig.ImGuiColorEditFlags_None); + ig.igSameLine(); _ = ig.igDragFloat2("pos", &light.pos); ig.igSameLine(); + _ = ig.igDragFloat("reach", &light.reach); + ig.igSameLine(); + _ = ig.igDragFloat("radius_glow", &light.radius_glow); + ig.igSameLine(); + _ = ig.igDragFloatEx("intensity", &light.intensity, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None); + ig.igSameLine(); + _ = ig.igDragFloatEx("shadow_brightness", &light.shadow_brightness, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None); + ig.igSameLine(); if (ig.igButton("-")) { i -= 1; _ = state.lights.swapRemove(i); @@ -229,8 +344,18 @@ fn ui() void { } export fn frame() void { + // sfetch sfetch.dowork(); + // fps + const now = stime.now(); + if (stime.ms(stime.diff(now, state.time)) >= 1000) { + state.fps = state.frames; + state.frames = 0; + state.time = now; + } + state.frames += 1; + const width = sapp.width(); const height = sapp.height(); const widthf = sapp.widthf(); @@ -245,32 +370,32 @@ export fn frame() void { }); ui(); - // update instance vertex buffer - sg.updateBuffer(state.bind.vertex_buffers[0], sg.asRange(state.sprites.items)); - sg.updateBuffer(state.bind_light.vertex_buffers[0], sg.asRange(state.lights.items)); - - sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() }); - // shadow pass - sg.applyPipeline(state.pip_shadow); - sg.applyBindings(state.bind); + sg.beginPass(state.shaders.shadow.pass); + + sg.applyPipeline(state.shaders.shadow.pip); + sg.applyBindings(state.shaders.tex.bind); sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{ .screen = [_]f32{ widthf, heightf }, .cam = state.cam, })); - for (state.lights.items) |light| { - sg.applyUniforms(shd.UB_Light, sg.asRange(&shd.Light{ - .light_pos = light.pos, - })); + for (state.lights.items) |*light| { + sg.applyUniforms(shd.UB_Light, sg.asRange(light)); - sg.draw(0, 6, @truncate(state.sprites.items.len)); + sg.draw(0, 3, @truncate(state.sprites.items.len)); } - // default pass - sg.applyPipeline(state.pip); - sg.applyBindings(state.bind); + sg.endPass(); + + // texture pass + sg.beginPass(state.shaders.tex.pass); + + sg.updateBuffer(state.shaders.tex.bind.vertex_buffers[0], sg.asRange(state.sprites.items)); + + sg.applyPipeline(state.shaders.tex.pip); + sg.applyBindings(state.shaders.tex.bind); sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{ .screen = [_]f32{ widthf, heightf }, @@ -279,32 +404,136 @@ export fn frame() void { sg.draw(0, 6, @truncate(state.sprites.items.len)); + sg.endPass(); + // light pass - sg.applyPipeline(state.pip_light); - sg.applyBindings(state.bind_light); + sg.beginPass(state.shaders.light.pass); + + sg.updateBuffer(state.shaders.light.bind.vertex_buffers[0], sg.asRange(state.lights.items)); + + sg.applyPipeline(state.shaders.light.pip); + sg.applyBindings(state.shaders.light.bind); + + sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{ + .screen = [_]f32{ widthf, heightf }, + .cam = state.cam, + })); + + for (state.lights.items) |*light| { + sg.applyUniforms(shd.UB_Light, sg.asRange(light)); + + sg.draw(0, 6, 1); + } + + sg.endPass(); + + // combine pass + sg.beginPass(.{ .action = state.shaders.combine.pass_action, .swapchain = sglue.swapchain() }); + + sg.applyPipeline(state.shaders.combine.pip); + sg.applyBindings(state.shaders.combine.bind); sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{ .screen = [_]f32{ widthf, heightf }, .cam = state.cam, })); - sg.draw(0, 6, @truncate(state.lights.items.len)); + sg.draw(0, 6, 1); simgui.render(); sg.endPass(); sg.commit(); } -export fn cleanup() void { - simgui.shutdown(); - sfetch.shutdown(); - sg.shutdown(); -} +const state = struct { + var fps: u64 = 0; + var frames: u64 = 0; + var time: u64 = 0; -export fn event(ev: [*c]const sapp.Event) void { - // forward input events to sokol-imgui - _ = simgui.handleEvent(ev.*); -} + const OffscreenWidth = 1920; + const OffscreenHeight = 1080; + const OffscreenSampleCount = 1; + const MaxSprites = 100; + const MaxLights = 100; + + const shaders = struct { + const combine = struct { + var pass_action: sg.PassAction = .{}; + var bind: sg.Bindings = .{}; + var pip: sg.Pipeline = .{}; + }; + const tex = struct { + var pass: sg.Pass = .{}; + var bind: sg.Bindings = .{}; + var pip: sg.Pipeline = .{}; + var img: sg.Image = .{}; + var depth: sg.Image = .{}; + }; + const shadow = struct { + var pass: sg.Pass = .{}; + // var bind: sg.Bindings = .{}; + var pip: sg.Pipeline = .{}; + var img: sg.Image = .{}; + var depth: sg.Image = .{}; + }; + const light = struct { + var pass: sg.Pass = .{}; + var bind: sg.Bindings = .{}; + var pip: sg.Pipeline = .{}; + var img: sg.Image = .{}; + var depth: sg.Image = .{}; + }; + }; + + var img_buf: [1024 * 1024]u8 = undefined; + var img: sg.Image = undefined; + + var cam = [2]f32{ -300, -300 }; + + var sprites_buf: [MaxSprites]Sprite = undefined; + var sprites = std.ArrayListUnmanaged(Sprite).initBuffer(&state.sprites_buf); + var lights_buf: [MaxLights]shd.Light = undefined; + var lights = std.ArrayListUnmanaged(shd.Light).initBuffer(&state.lights_buf); +}; + +const DefaultLight = shd.Light{ + .pos = [_]f32{ 150, -300 }, + .color = [_]f32{ 1, 0.8, 0 }, + .reach = 500, + .radius_glow = 200, + .intensity = 1.0, + .shadow_brightness = 0.5, +}; + +const Sprite = struct { + texid: usize = shd.VIEW_tex, + pos: [2]f32 = [_]f32{ 0, 0 }, + size: [2]i32 = [_]i32{ 300, 300 }, +}; + +// const Light = struct { +// color: [3]f32 = [_]f32{ 1, 0, 0 }, +// pos: [2]f32 = [_]f32{ 0, 0 }, +// radius: f32 = 100, +// }; + +const std = @import("std"); + +const ig = @import("cimgui_docking"); +const sokol = @import("sokol"); +const slog = sokol.log; +const sg = sokol.gfx; +const sapp = sokol.app; +const stime = sokol.time; +const sglue = sokol.glue; +const sfetch = sokol.fetch; +const simgui = sokol.imgui; + +const stbi = @cImport({ + @cInclude("stb_image.h"); +}); + +const shd = @import("shd/main.glsl.zig"); pub fn main() void { sapp.run(.{ -- 2.50.1