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, .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 }, }); ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; // 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, }); // 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 state.pip = sg.makePipeline(.{ .shader = sg.makeShader(shd.quadShaderDesc(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.buffers[0].stride = @sizeOf(Sprite); break :init l; }, }); // 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 = width, .height = height, .delta_time = sapp.frameDuration(), .dpi_scale = sapp.dpiScale(), }); 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.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.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(); } 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.*); } pub fn main() void { sapp.run(.{ .init_cb = init, .frame_cb = frame, .cleanup_cb = cleanup, .event_cb = event, .window_title = "sokol-zig + Dear Imgui", .width = 800, .height = 600, .icon = .{ .sokol_default = true }, .logger = .{ .func = slog.func }, }); }