]> gitweb.ps.run Git - sporegirl/blobdiff - src/main.zig
changezzz
[sporegirl] / src / main.zig
index 92e1da531ff832ec4541134d446a04a153e1c1d5..bf77621e3e15044d31c4c1fd1eb434f579d8c34c 100644 (file)
-const use_docking = @import("build_options").docking;
-const ig = if (use_docking) @import("cimgui_docking") else @import("cimgui");
-const sokol = @import("sokol");
-const slog = sokol.log;
-const sg = sokol.gfx;
-const sapp = sokol.app;
-const sglue = sokol.glue;
-const simgui = sokol.imgui;
-
-const shd = @import("shader/quad.glsl.zig");
-
-const state = struct {
-    var bind: sg.Bindings = .{};
-    var pip: sg.Pipeline = .{};
-    var pass_action: sg.PassAction = .{};
-    var show_first_window: bool = true;
-    var show_second_window: bool = true;
-};
-
 export fn init() void {
     // initialize sokol-gfx
     sg.setup(.{
+        .buffer_pool_size = 8,
+        .image_pool_size = 16,
+        .shader_pool_size = 8,
+        .pipeline_pool_size = 8,
+        .view_pool_size = 16,
         .environment = sglue.environment(),
         .logger = .{ .func = slog.func },
     });
-
-    // initialize sokol-imgui
+    sfetch.setup(.{
+        .max_requests = 2,
+        .num_channels = 2,
+        .num_lanes = 1,
+        .logger = .{ .func = slog.func },
+    });
+    stime.setup();
     simgui.setup(.{
         .logger = .{ .func = slog.func },
     });
-    if (use_docking) {
-        ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
-    }
+    ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
 
-    // a vertex buffer
-    state.bind.vertex_buffers[0] = sg.makeBuffer(.{
-        .data = sg.asRange(&[_]f32{
-            // positions      colors
-            -0.5, 0.5,  0.5, 1.0, 0.0, 0.0, 1.0,
-            0.5,  0.5,  0.5, 0.0, 1.0, 0.0, 1.0,
-            0.5,  -0.5, 0.5, 0.0, 0.0, 1.0, 1.0,
-            -0.5, -0.5, 0.5, 1.0, 1.0, 0.0, 1.0,
-        }),
+    // initial clear color
+    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,
+        .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,
     });
 
-    // an index buffer
-    state.bind.index_buffer = sg.makeBuffer(.{
-        .usage = .{ .index_buffer = true },
-        .data = sg.asRange(&[_]u16{ 0, 1, 2, 0, 2, 3 }),
+    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,
+    });
+
+    state.shaders.combine.pip = sg.makePipeline(.{
+        .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())),
+        .depth = .{
+            .compare = .LESS_EQUAL,
+            .write_enabled = true,
+        },
+    });
+
+    // 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.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.attrs[shd.ATTR_quad_position].format = .FLOAT3;
-            l.attrs[shd.ATTR_quad_color0].format = .FLOAT4;
+            l.buffers[0].step_func = .PER_INSTANCE;
+            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;
         },
-        .index_type = .UINT16,
+        .depth = .{
+            .compare = .LESS_EQUAL,
+            .write_enabled = true,
+        },
     });
 
-    // initial clear color
-    state.pass_action.colors[0] = .{
-        .load_action = .CLEAR,
-        .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 },
+    // shadow pass
+    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{};
+            l.buffers[0].step_func = .PER_INSTANCE;
+            l.attrs[shd.ATTR_shadow_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") };
+            l.attrs[shd.ATTR_shadow_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") };
+            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.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(shd.Light),
+    });
+
+    state.shaders.light.pip = sg.makePipeline(.{
+        .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())),
+        .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;
+        },
+    });
+
+    // fetch resources
+    const FileCb = struct {
+        fn img(res: [*c]const sfetch.Response) callconv(.c) void {
+            if (res.*.finished) {
+                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.img, .{
+                        .width = w,
+                        .height = h,
+                        .pixel_format = sg.PixelFormat.RGBA8,
+                        .data = init: {
+                            var i = sg.ImageData{};
+                            i.subimage[0][0] = .{
+                                .ptr = pixels,
+                                .size = @intCast(w * h * 4),
+                            };
+                            break :init i;
+                        },
+                    });
+                    sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{
+                        .texture = .{ .image = state.img },
+                    });
+                }
+            }
+        }
     };
+    _ = sfetch.send(.{
+        .path = "img/test.png",
+        .callback = FileCb.img,
+        .buffer = .{ .ptr = &state.img_buf, .size = state.img_buf.len },
+    });
+
+    state.sprites.appendAssumeCapacity(.{});
+    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.igLabelText("fps", "%d", state.fps);
+        _ = ig.igDragFloat2("cam", &state.cam);
+
+        {
+            ig.igPushIDPtr(state.sprites.items.ptr);
+            if (ig.igButton("+")) {
+                _ = state.sprites.appendAssumeCapacity(.{});
+            }
+
+            var i: u16 = 0;
+            while (i < state.sprites.items.len) {
+                var sprite = &state.sprites.items[i];
+                i += 1;
+                ig.igPushIDInt(i);
+                ig.igPushItemWidth(100);
+                _ = ig.igDragFloat2("pos", &sprite.pos);
+                ig.igSameLine();
+                _ = ig.igDragInt2("size", &sprite.size);
+                ig.igSameLine();
+                if (ig.igButton("-")) {
+                    i -= 1;
+                    _ = state.sprites.swapRemove(i);
+                }
+                ig.igPopID();
+            }
+            ig.igPopID();
+        }
+
+        {
+            ig.igPushIDPtr(state.lights.items.ptr);
+            if (ig.igButton("+")) {
+                _ = state.lights.appendAssumeCapacity(DefaultLight);
+            }
+
+            var i: u16 = 0;
+            while (i < state.lights.items.len) {
+                var light = &state.lights.items[i];
+                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);
+                }
+                ig.igPopID();
+            }
+            ig.igPopID();
+        }
+
+        ig.igEnd();
+    }
 }
 
 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();
+    const heightf = sapp.heightf();
+
     // call simgui.newFrame() before any ImGui calls
     simgui.newFrame(.{
-        .width = sapp.width(),
-        .height = sapp.height(),
+        .width = width,
+        .height = height,
         .delta_time = sapp.frameDuration(),
         .dpi_scale = sapp.dpiScale(),
     });
+    ui();
 
-    if (ig.igBegin("Hello Dear ImGui!", &state.show_first_window, ig.ImGuiWindowFlags_None)) {
-        _ = ig.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None);
-        _ = ig.igText("Dear ImGui Version: %s", ig.IMGUI_VERSION);
-        ig.igEnd();
+    // shadow pass
+    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(light));
+
+        sg.draw(0, 3, @truncate(state.sprites.items.len));
+    }
+
+    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 },
+        .cam = state.cam,
+    }));
+
+    sg.draw(0, 6, @truncate(state.sprites.items.len));
+
+    sg.endPass();
+
+    // light pass
+    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,
+    }));
 
-    // call simgui.render() inside a sokol-gfx pass
-    sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() });
-    sg.applyPipeline(state.pip);
-    sg.applyBindings(state.bind);
     sg.draw(0, 6, 1);
+
     simgui.render();
     sg.endPass();
     sg.commit();
 }
 
-export fn cleanup() void {
-    simgui.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(.{