-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(.{