From df3724db278d0a4f42943d0c9390cd1274398370 Mon Sep 17 00:00:00 2001 From: patrick-scho Date: Wed, 20 Aug 2025 13:59:54 +0200 Subject: [PATCH] update main.zig --- src/main.zig | 280 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 241 insertions(+), 39 deletions(-) diff --git a/src/main.zig b/src/main.zig index 92e1da5..4813e69 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,52 +1,95 @@ -const use_docking = @import("build_options").docking; -const ig = if (use_docking) @import("cimgui_docking") else @import("cimgui"); +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 shd = @import("shader/quad.glsl.zig"); +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 pass_action: sg.PassAction = .{}; - var show_first_window: bool = true; - var show_second_window: bool = true; + + 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, + .shader_pool_size = 8, + .pipeline_pool_size = 8, + .attachments_pool_size = 1, .environment = sglue.environment(), .logger = .{ .func = slog.func }, }); + sfetch.setup(.{ + .max_requests = 2, + .num_channels = 2, + .num_lanes = 1, + .logger = .{ .func = slog.func }, + }); // initialize sokol-imgui 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.pass_action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 }, + }; + + // default pass + state.bind.images[shd.IMG_tex] = sg.allocImage(); + + state.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{ + .min_filter = sg.Filter.NEAREST, + .mag_filter = sg.Filter.NEAREST, }); - // an index buffer - state.bind.index_buffer = sg.makeBuffer(.{ - .usage = .{ .index_buffer = true }, - .data = sg.asRange(&[_]u16{ 0, 1, 2, 0, 2, 3 }), + // dynamic vertex buffer for instancing data + state.bind.vertex_buffers[0] = sg.makeBuffer(.{ + .usage = .{ .stream_update = true }, + .size = state.MaxSprites * @sizeOf(Sprite), }); // a shader and pipeline state object @@ -54,41 +97,199 @@ export fn init() void { .shader = sg.makeShader(shd.quadShaderDesc(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_quad_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") }; + l.attrs[shd.ATTR_quad_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") }; + l.buffers[0].stride = @sizeOf(Sprite); break :init l; }, - .index_type = .UINT16, }); - // 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.pip_shadow = 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; + }, + }); + + // light pass + state.bind_light.vertex_buffers[0] = sg.makeBuffer(.{ + .usage = .{ .stream_update = true }, + .size = state.MaxLights * @sizeOf(Light), + }); + + state.pip_light = 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; + }, + }); + + // 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; + 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], .{ + .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; + }, + }); + } + } + } }; + _ = 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(.{ .pos = [_]f32{ 150, -300 } }); +} + +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.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(.{}); + } + + 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.igDragFloat2("pos", &light.pos); + ig.igSameLine(); + if (ig.igButton("-")) { + i -= 1; + _ = state.lights.swapRemove(i); + } + ig.igPopID(); + } + ig.igPopID(); + } + + ig.igEnd(); + } } export fn frame() void { + sfetch.dowork(); + + 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(); - } - + // 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)); - // call simgui.render() inside a sokol-gfx pass sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() }); + + // shadow pass + sg.applyPipeline(state.pip_shadow); + sg.applyBindings(state.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, + })); + + sg.draw(0, 6, @truncate(state.sprites.items.len)); + } + + // default pass sg.applyPipeline(state.pip); sg.applyBindings(state.bind); - sg.draw(0, 6, 1); + + 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)); + + // light pass + sg.applyPipeline(state.pip_light); + sg.applyBindings(state.bind_light); + + 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)); + simgui.render(); sg.endPass(); sg.commit(); @@ -96,6 +297,7 @@ export fn frame() void { export fn cleanup() void { simgui.shutdown(); + sfetch.shutdown(); sg.shutdown(); } -- 2.50.1