]> gitweb.ps.run Git - sporegirl/blob - src/main.zig
4813e6909aee5ec0e9a913d2d3a6b794ce8b7f24
[sporegirl] / src / main.zig
1 const std = @import("std");
2
3 const ig = @import("cimgui_docking");
4 const sokol = @import("sokol");
5 const slog = sokol.log;
6 const sg = sokol.gfx;
7 const sapp = sokol.app;
8 const sglue = sokol.glue;
9 const sfetch = sokol.fetch;
10 const simgui = sokol.imgui;
11
12 const stbi = @cImport({
13     @cInclude("stb_image.h");
14 });
15
16 const shd = @import("shd/quad.glsl.zig");
17
18 const state = struct {
19     const MaxSprites = 100;
20     const MaxLights = 100;
21
22     var pass_action: sg.PassAction = .{};
23     var bind: sg.Bindings = .{};
24     var pip: sg.Pipeline = .{};
25
26     var pip_shadow: sg.Pipeline = .{};
27     
28     var bind_light: sg.Bindings = .{};
29     var pip_light: sg.Pipeline = .{}; 
30
31     var img_buf: [1024 * 1024]u8 = undefined;
32
33     var cam = [2]f32{ -300, -300 };
34
35     var sprites_buf: [MaxSprites]Sprite = undefined;
36     var sprites = std.ArrayListUnmanaged(Sprite).initBuffer(&state.sprites_buf);
37     var lights_buf: [MaxLights]Light = undefined;
38     var lights = std.ArrayListUnmanaged(Light).initBuffer(&state.lights_buf);
39 };
40
41 const Sprite = struct {
42     texid: usize = shd.IMG_tex,
43     pos: [2]f32 = [_]f32{ 0, 0 },
44     size: [2]i32 = [_]i32{ 300, 300 },
45 };
46
47 const Light = struct {
48     pos: [2]f32 = [_]f32{ 0, 0 },
49 };
50
51 export fn init() void {
52     // initialize sokol-gfx
53     sg.setup(.{
54         .buffer_pool_size = 8,
55         .image_pool_size = 4,
56         .shader_pool_size = 8,
57         .pipeline_pool_size = 8,
58         .attachments_pool_size = 1,
59         .environment = sglue.environment(),
60         .logger = .{ .func = slog.func },
61     });
62     sfetch.setup(.{
63         .max_requests = 2,
64         .num_channels = 2,
65         .num_lanes = 1,
66         .logger = .{ .func = slog.func },
67     });
68
69     // initialize sokol-imgui
70     simgui.setup(.{
71         .logger = .{ .func = slog.func },
72     });
73     ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
74
75     // initial clear color
76     state.pass_action.colors[0] = .{
77         .load_action = .CLEAR,
78         .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 },
79     };
80
81     // default pass
82     state.bind.images[shd.IMG_tex] = sg.allocImage();
83
84     state.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{
85         .min_filter = sg.Filter.NEAREST,
86         .mag_filter = sg.Filter.NEAREST,
87     });
88
89     // dynamic vertex buffer for instancing data
90     state.bind.vertex_buffers[0] = sg.makeBuffer(.{
91         .usage = .{ .stream_update = true },
92         .size = state.MaxSprites * @sizeOf(Sprite),
93     });
94
95     // a shader and pipeline state object
96     state.pip = sg.makePipeline(.{
97         .shader = sg.makeShader(shd.quadShaderDesc(sg.queryBackend())),
98         .layout = init: {
99             var l = sg.VertexLayoutState{};
100             l.buffers[0].step_func = .PER_INSTANCE;
101             l.attrs[shd.ATTR_quad_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") };
102             l.attrs[shd.ATTR_quad_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") };
103             l.buffers[0].stride = @sizeOf(Sprite);
104             break :init l;
105         },
106     });
107
108     // shadow pass
109     state.pip_shadow = sg.makePipeline(.{
110         .shader = sg.makeShader(shd.shadowShaderDesc(sg.queryBackend())),
111         .layout = init: {
112             var l = sg.VertexLayoutState{};
113             l.buffers[0].step_func = .PER_INSTANCE;
114             l.attrs[shd.ATTR_shadow_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") };
115             l.attrs[shd.ATTR_shadow_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") };
116             l.buffers[0].stride = @sizeOf(Sprite);
117             break :init l;
118         },
119     });
120
121     // light pass
122     state.bind_light.vertex_buffers[0] = sg.makeBuffer(.{
123         .usage = .{ .stream_update = true },
124         .size = state.MaxLights * @sizeOf(Light),
125     });
126
127     state.pip_light = sg.makePipeline(.{
128         .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())),
129         .layout = init: {
130             var l = sg.VertexLayoutState{};
131             l.buffers[0].step_func = .PER_INSTANCE;
132             l.attrs[shd.ATTR_light_light_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Light, "pos") };
133             l.buffers[0].stride = @sizeOf(Light);
134             break :init l;
135         },
136     });
137
138     // fetch resources
139     const FileCb = struct {
140         fn img(res: [*c]const sfetch.Response) callconv(.c) void {
141             if (res.*.finished) {
142                 var w: c_int = 0;
143                 var h: c_int = 0;
144                 var n: c_int = 0;
145                 const pixels = stbi.stbi_load_from_memory(@ptrCast(res.*.data.ptr.?), @intCast(res.*.data.size), &w, &h, &n, 4);
146                 if (pixels != null) {
147                     sg.initImage(state.bind.images[shd.IMG_tex], .{
148                         .width = w,
149                         .height = h,
150                         .pixel_format = sg.PixelFormat.RGBA8,
151                         .data = init: {
152                             var i = sg.ImageData{};
153                             i.subimage[0][0] = .{
154                                 .ptr = pixels,
155                                 .size = @intCast(w * h * 4),
156                             };
157                             break :init i;
158                         },
159                     });
160                 }
161             }
162         }
163     };
164     _ = sfetch.send(.{
165         .path = "img/test.png",
166         .callback = FileCb.img,
167         .buffer = .{ .ptr = &state.img_buf, .size = state.img_buf.len },
168     });
169
170     state.sprites.appendAssumeCapacity(.{});
171     state.lights.appendAssumeCapacity(.{ .pos = [_]f32{ 150, -300 } });
172 }
173
174 fn ui() void {
175     if (ig.igBegin("Hello Dear ImGui!", null, ig.ImGuiWindowFlags_None)) {
176         _ = ig.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None);
177         _ = ig.igDragFloat2("cam", &state.cam);
178
179         {
180             ig.igPushIDPtr(state.sprites.items.ptr);
181             if (ig.igButton("+")) {
182                 _ = state.sprites.appendAssumeCapacity(.{});
183             }
184
185             var i: u16 = 0;
186             while (i < state.sprites.items.len) {
187                 var sprite = &state.sprites.items[i];
188                 i += 1;
189                 ig.igPushIDInt(i);
190                 ig.igPushItemWidth(100);
191                 _ = ig.igDragFloat2("pos", &sprite.pos);
192                 ig.igSameLine();
193                 _ = ig.igDragInt2("size", &sprite.size);
194                 ig.igSameLine();
195                 if (ig.igButton("-")) {
196                     i -= 1;
197                     _ = state.sprites.swapRemove(i);
198                 }
199                 ig.igPopID();
200             }
201             ig.igPopID();
202         }
203
204         {
205             ig.igPushIDPtr(state.lights.items.ptr);
206             if (ig.igButton("+")) {
207                 _ = state.lights.appendAssumeCapacity(.{});
208             }
209
210             var i: u16 = 0;
211             while (i < state.lights.items.len) {
212                 var light = &state.lights.items[i];
213                 i += 1;
214                 ig.igPushIDInt(i);
215                 ig.igPushItemWidth(100);
216                 _ = ig.igDragFloat2("pos", &light.pos);
217                 ig.igSameLine();
218                 if (ig.igButton("-")) {
219                     i -= 1;
220                     _ = state.lights.swapRemove(i);
221                 }
222                 ig.igPopID();
223             }
224             ig.igPopID();
225         }
226
227         ig.igEnd();
228     }
229 }
230
231 export fn frame() void {
232     sfetch.dowork();
233
234     const width = sapp.width();
235     const height = sapp.height();
236     const widthf = sapp.widthf();
237     const heightf = sapp.heightf();
238
239     // call simgui.newFrame() before any ImGui calls
240     simgui.newFrame(.{
241         .width = width,
242         .height = height,
243         .delta_time = sapp.frameDuration(),
244         .dpi_scale = sapp.dpiScale(),
245     });
246     ui();
247
248     // update instance vertex buffer
249     sg.updateBuffer(state.bind.vertex_buffers[0], sg.asRange(state.sprites.items));
250     sg.updateBuffer(state.bind_light.vertex_buffers[0], sg.asRange(state.lights.items));
251
252     sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() });
253
254     // shadow pass
255     sg.applyPipeline(state.pip_shadow);
256     sg.applyBindings(state.bind);
257
258     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
259         .screen = [_]f32{ widthf, heightf },
260         .cam = state.cam,
261     }));
262
263     for (state.lights.items) |light| {
264         sg.applyUniforms(shd.UB_Light, sg.asRange(&shd.Light{
265             .light_pos = light.pos,
266         }));
267
268         sg.draw(0, 6, @truncate(state.sprites.items.len));
269     }
270
271     // default pass
272     sg.applyPipeline(state.pip);
273     sg.applyBindings(state.bind);
274
275     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
276         .screen = [_]f32{ widthf, heightf },
277         .cam = state.cam,
278     }));
279
280     sg.draw(0, 6, @truncate(state.sprites.items.len));
281
282     // light pass
283     sg.applyPipeline(state.pip_light);
284     sg.applyBindings(state.bind_light);
285
286     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
287         .screen = [_]f32{ widthf, heightf },
288         .cam = state.cam,
289     }));
290
291     sg.draw(0, 6, @truncate(state.lights.items.len));
292
293     simgui.render();
294     sg.endPass();
295     sg.commit();
296 }
297
298 export fn cleanup() void {
299     simgui.shutdown();
300     sfetch.shutdown();
301     sg.shutdown();
302 }
303
304 export fn event(ev: [*c]const sapp.Event) void {
305     // forward input events to sokol-imgui
306     _ = simgui.handleEvent(ev.*);
307 }
308
309 pub fn main() void {
310     sapp.run(.{
311         .init_cb = init,
312         .frame_cb = frame,
313         .cleanup_cb = cleanup,
314         .event_cb = event,
315         .window_title = "sokol-zig + Dear Imgui",
316         .width = 800,
317         .height = 600,
318         .icon = .{ .sokol_default = true },
319         .logger = .{ .func = slog.func },
320     });
321 }