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 }, }); sfetch.setup(.{ .max_requests = 2, .num_channels = 2, .num_lanes = 1, .logger = .{ .func = slog.func }, }); stime.setup(); simgui.setup(.{ .logger = .{ .func = slog.func }, }); ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; // 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, }); 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.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_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.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 = width, .height = height, .delta_time = sapp.frameDuration(), .dpi_scale = sapp.dpiScale(), }); ui(); // 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, })); sg.draw(0, 6, 1); simgui.render(); sg.endPass(); sg.commit(); } const state = struct { var fps: u64 = 0; var frames: u64 = 0; var time: u64 = 0; 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(.{ .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 }, }); }