-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
.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();
export fn cleanup() void {
simgui.shutdown();
+ sfetch.shutdown();
sg.shutdown();
}