]> gitweb.ps.run Git - sporegirl/blobdiff - src/main.zig
changezzz
[sporegirl] / src / main.zig
index 4813e6909aee5ec0e9a913d2d3a6b794ce8b7f24..bf77621e3e15044d31c4c1fd1eb434f579d8c34c 100644 (file)
@@ -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(.{