]> gitweb.ps.run Git - sporegirl/blob - src/main.zig
changezzz
[sporegirl] / src / main.zig
1 export fn init() void {
2     // initialize sokol-gfx
3     sg.setup(.{
4         .buffer_pool_size = 8,
5         .image_pool_size = 16,
6         .shader_pool_size = 8,
7         .pipeline_pool_size = 8,
8         .view_pool_size = 16,
9         .environment = sglue.environment(),
10         .logger = .{ .func = slog.func },
11     });
12     sfetch.setup(.{
13         .max_requests = 2,
14         .num_channels = 2,
15         .num_lanes = 1,
16         .logger = .{ .func = slog.func },
17     });
18     stime.setup();
19     simgui.setup(.{
20         .logger = .{ .func = slog.func },
21     });
22     ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
23
24     // initial clear color
25     state.shaders.tex.pass.action.colors[0] = sg.ColorAttachmentAction{
26         .load_action = .CLEAR,
27         .store_action = .STORE,
28         .clear_value = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 },
29     };
30     state.shaders.shadow.pass.action.colors[0] = sg.ColorAttachmentAction{
31         .load_action = .CLEAR,
32         .store_action = .STORE,
33         .clear_value = .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 0.0 },
34     };
35     state.shaders.light.pass.action.colors[0] = sg.ColorAttachmentAction{
36         .load_action = .CLEAR,
37         .store_action = .STORE,
38         .clear_value = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 },
39     };
40
41     // combining pass
42     state.shaders.tex.img = sg.makeImage(.{
43         .usage = .{ .color_attachment = true },
44         .width = state.OffscreenWidth,
45         .height = state.OffscreenHeight,
46         .sample_count = state.OffscreenSampleCount,
47         .pixel_format = .RGBA8,
48     });
49     state.shaders.tex.depth = sg.makeImage(.{
50         .usage = .{ .depth_stencil_attachment = true },
51         .width = state.OffscreenWidth,
52         .height = state.OffscreenHeight,
53         .sample_count = state.OffscreenSampleCount,
54         .pixel_format = .DEPTH,
55     });
56     state.shaders.shadow.img = sg.makeImage(.{
57         .usage = .{ .color_attachment = true },
58         .width = state.OffscreenWidth,
59         .height = state.OffscreenHeight,
60         .sample_count = state.OffscreenSampleCount,
61         .pixel_format = .RGBA8,
62     });
63     state.shaders.shadow.depth = sg.makeImage(.{
64         .usage = .{ .depth_stencil_attachment = true },
65         .width = state.OffscreenWidth,
66         .height = state.OffscreenHeight,
67         .sample_count = state.OffscreenSampleCount,
68         .pixel_format = .DEPTH,
69     });
70     state.shaders.light.img = sg.makeImage(.{
71         .usage = .{ .color_attachment = true },
72         .width = state.OffscreenWidth,
73         .height = state.OffscreenHeight,
74         .sample_count = state.OffscreenSampleCount,
75         .pixel_format = .RGBA8,
76     });
77     state.shaders.light.depth = sg.makeImage(.{
78         .usage = .{ .depth_stencil_attachment = true },
79         .width = state.OffscreenWidth,
80         .height = state.OffscreenHeight,
81         .sample_count = state.OffscreenSampleCount,
82         .pixel_format = .DEPTH,
83     });
84
85     state.shaders.combine.bind.views[shd.VIEW_tex_tex] = sg.makeView(.{
86         .texture = .{ .image = state.shaders.tex.img },
87     });
88     state.shaders.combine.bind.views[shd.VIEW_tex_shadow] = sg.makeView(.{
89         .texture = .{ .image = state.shaders.shadow.img },
90     });
91     state.shaders.combine.bind.views[shd.VIEW_tex_light] = sg.makeView(.{
92         .texture = .{ .image = state.shaders.light.img },
93     });
94     state.shaders.combine.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{
95         .min_filter = sg.Filter.LINEAR,
96         .mag_filter = sg.Filter.LINEAR,
97     });
98
99     state.shaders.combine.pip = sg.makePipeline(.{
100         .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())),
101         .depth = .{
102             .compare = .LESS_EQUAL,
103             .write_enabled = true,
104         },
105     });
106
107     // texture pass
108     state.img = sg.allocImage();
109     state.shaders.tex.pass.attachments.colors[0] = sg.makeView(.{
110         .color_attachment = .{ .image = state.shaders.tex.img },
111     });
112     state.shaders.tex.pass.attachments.depth_stencil = sg.makeView(.{
113         .depth_stencil_attachment = .{ .image = state.shaders.tex.depth },
114     });
115     state.shaders.tex.bind.views[shd.VIEW_tex] = sg.allocView();
116     state.shaders.tex.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{
117         .min_filter = sg.Filter.NEAREST,
118         .mag_filter = sg.Filter.NEAREST,
119         .wrap_u = sg.Wrap.CLAMP_TO_EDGE,
120         .wrap_v = sg.Wrap.CLAMP_TO_EDGE,
121     });
122
123     // dynamic vertex buffer for instancing data
124     state.shaders.tex.bind.vertex_buffers[0] = sg.makeBuffer(.{
125         .usage = .{ .stream_update = true },
126         .size = state.MaxSprites * @sizeOf(Sprite),
127     });
128
129     // a shader and pipeline state object
130     state.shaders.tex.pip = sg.makePipeline(.{
131         .shader = sg.makeShader(shd.texShaderDesc(sg.queryBackend())),
132         .layout = init: {
133             var l = sg.VertexLayoutState{};
134             l.buffers[0].step_func = .PER_INSTANCE;
135             l.attrs[shd.ATTR_tex_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") };
136             l.attrs[shd.ATTR_tex_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") };
137             l.buffers[0].stride = @sizeOf(Sprite);
138             break :init l;
139         },
140         .depth = .{
141             .compare = .LESS_EQUAL,
142             .write_enabled = true,
143         },
144     });
145
146     // shadow pass
147     state.shaders.shadow.pass.attachments.colors[0] = sg.makeView(.{
148         .color_attachment = .{ .image = state.shaders.shadow.img },
149     });
150     state.shaders.shadow.pass.attachments.depth_stencil = sg.makeView(.{
151         .depth_stencil_attachment = .{ .image = state.shaders.shadow.depth },
152     });
153     state.shaders.shadow.pip = sg.makePipeline(.{
154         .shader = sg.makeShader(shd.shadowShaderDesc(sg.queryBackend())),
155         .layout = init: {
156             var l = sg.VertexLayoutState{};
157             l.buffers[0].step_func = .PER_INSTANCE;
158             l.attrs[shd.ATTR_shadow_inst_pos] = .{ .format = .FLOAT2, .offset = @offsetOf(Sprite, "pos") };
159             l.attrs[shd.ATTR_shadow_inst_size] = .{ .format = .INT2, .offset = @offsetOf(Sprite, "size") };
160             l.buffers[0].stride = @sizeOf(Sprite);
161             break :init l;
162         },
163         .depth = .{
164             .compare = .LESS_EQUAL,
165             .write_enabled = true,
166         },
167         .colors = init: {
168             var c: [4]sg.ColorTargetState = @splat(.{});
169             c[0] = .{
170                 .blend = .{
171                     .enabled = true,
172                     .src_factor_rgb = .DST_COLOR,
173                     .dst_factor_rgb = .ZERO,
174                     .op_rgb = .ADD,
175                     .src_factor_alpha = .ONE,
176                     .dst_factor_alpha = .ONE,
177                     .op_alpha = .ADD,
178                 },
179             };
180             break :init c;
181         },
182     });
183
184     // light pass
185     state.shaders.light.pass.attachments.colors[0] = sg.makeView(.{
186         .color_attachment = .{ .image = state.shaders.light.img },
187     });
188     state.shaders.light.pass.attachments.depth_stencil = sg.makeView(.{
189         .depth_stencil_attachment = .{ .image = state.shaders.light.depth },
190     });
191     state.shaders.light.bind.vertex_buffers[0] = sg.makeBuffer(.{
192         .usage = .{ .stream_update = true },
193         .size = state.MaxLights * @sizeOf(shd.Light),
194     });
195
196     state.shaders.light.pip = sg.makePipeline(.{
197         .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())),
198         .depth = .{
199             .compare = .LESS_EQUAL,
200             .write_enabled = true,
201         },
202         .blend_color = sg.Color{ .r=0.5,.g=0.5,.b=0.5,.a = 0.5 },
203         .colors = init: {
204             var c: [4]sg.ColorTargetState = @splat(.{});
205             c[0] = .{
206                 .blend = .{
207                     .enabled = true,
208                     .src_factor_rgb = .SRC_COLOR,
209                     .dst_factor_rgb = .ONE_MINUS_SRC_COLOR,
210                     .src_factor_alpha = .ONE,
211                     .dst_factor_alpha = .ONE,
212                 },
213             };
214             break :init c;
215         },
216     });
217
218     // fetch resources
219     const FileCb = struct {
220         fn img(res: [*c]const sfetch.Response) callconv(.c) void {
221             if (res.*.finished) {
222                 var w: c_int = 0;
223                 var h: c_int = 0;
224                 var n: c_int = 0;
225                 // TODO: free memory?
226                 const pixels = stbi.stbi_load_from_memory(@ptrCast(res.*.data.ptr.?), @intCast(res.*.data.size), &w, &h, &n, 4);
227                 if (pixels != null) {
228                     sg.initImage(state.img, .{
229                         .width = w,
230                         .height = h,
231                         .pixel_format = sg.PixelFormat.RGBA8,
232                         .data = init: {
233                             var i = sg.ImageData{};
234                             i.subimage[0][0] = .{
235                                 .ptr = pixels,
236                                 .size = @intCast(w * h * 4),
237                             };
238                             break :init i;
239                         },
240                     });
241                     sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{
242                         .texture = .{ .image = state.img },
243                     });
244                 }
245             }
246         }
247     };
248     _ = sfetch.send(.{
249         .path = "img/test.png",
250         .callback = FileCb.img,
251         .buffer = .{ .ptr = &state.img_buf, .size = state.img_buf.len },
252     });
253
254     state.sprites.appendAssumeCapacity(.{});
255     state.lights.appendAssumeCapacity(DefaultLight);
256     state.lights.appendAssumeCapacity(DefaultLight);
257     state.lights.items[0].color = [_]f32{ 0.8, 1, 0.8 };
258 }
259
260 export fn cleanup() void {
261     simgui.shutdown();
262     sfetch.shutdown();
263     sg.shutdown();
264 }
265
266 export fn event(ev: [*c]const sapp.Event) void {
267     // forward input events to sokol-imgui
268     _ = simgui.handleEvent(ev.*);
269
270     switch (ev.*.type) {
271         .MOUSE_MOVE => {
272             state.lights.items[0].pos[0] = ev.*.mouse_x + state.cam[0];
273             state.lights.items[0].pos[1] = (sapp.heightf() - ev.*.mouse_y) + state.cam[1];
274         },
275         else => {},
276     }
277 }
278
279 fn ui() void {
280     if (ig.igBegin("Hello Dear ImGui!", null, ig.ImGuiWindowFlags_None)) {
281         ig.igLabelText("fps", "%d", state.fps);
282         _ = ig.igDragFloat2("cam", &state.cam);
283
284         {
285             ig.igPushIDPtr(state.sprites.items.ptr);
286             if (ig.igButton("+")) {
287                 _ = state.sprites.appendAssumeCapacity(.{});
288             }
289
290             var i: u16 = 0;
291             while (i < state.sprites.items.len) {
292                 var sprite = &state.sprites.items[i];
293                 i += 1;
294                 ig.igPushIDInt(i);
295                 ig.igPushItemWidth(100);
296                 _ = ig.igDragFloat2("pos", &sprite.pos);
297                 ig.igSameLine();
298                 _ = ig.igDragInt2("size", &sprite.size);
299                 ig.igSameLine();
300                 if (ig.igButton("-")) {
301                     i -= 1;
302                     _ = state.sprites.swapRemove(i);
303                 }
304                 ig.igPopID();
305             }
306             ig.igPopID();
307         }
308
309         {
310             ig.igPushIDPtr(state.lights.items.ptr);
311             if (ig.igButton("+")) {
312                 _ = state.lights.appendAssumeCapacity(DefaultLight);
313             }
314
315             var i: u16 = 0;
316             while (i < state.lights.items.len) {
317                 var light = &state.lights.items[i];
318                 i += 1;
319                 ig.igPushIDInt(i);
320                 ig.igPushItemWidth(100);
321                 _ = ig.igColorEdit3("color", &light.color, ig.ImGuiColorEditFlags_None);
322                 ig.igSameLine();
323                 _ = ig.igDragFloat2("pos", &light.pos);
324                 ig.igSameLine();
325                 _ = ig.igDragFloat("reach", &light.reach);
326                 ig.igSameLine();
327                 _ = ig.igDragFloat("radius_glow", &light.radius_glow);
328                 ig.igSameLine();
329                 _ = ig.igDragFloatEx("intensity", &light.intensity, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None);
330                 ig.igSameLine();
331                 _ = ig.igDragFloatEx("shadow_brightness", &light.shadow_brightness, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None);
332                 ig.igSameLine();
333                 if (ig.igButton("-")) {
334                     i -= 1;
335                     _ = state.lights.swapRemove(i);
336                 }
337                 ig.igPopID();
338             }
339             ig.igPopID();
340         }
341
342         ig.igEnd();
343     }
344 }
345
346 export fn frame() void {
347     // sfetch
348     sfetch.dowork();
349
350     // fps
351     const now = stime.now();
352     if (stime.ms(stime.diff(now, state.time)) >= 1000) {
353         state.fps = state.frames;
354         state.frames = 0;
355         state.time = now;
356     }
357     state.frames += 1;
358
359     const width = sapp.width();
360     const height = sapp.height();
361     const widthf = sapp.widthf();
362     const heightf = sapp.heightf();
363
364     // call simgui.newFrame() before any ImGui calls
365     simgui.newFrame(.{
366         .width = width,
367         .height = height,
368         .delta_time = sapp.frameDuration(),
369         .dpi_scale = sapp.dpiScale(),
370     });
371     ui();
372
373     // shadow pass
374     sg.beginPass(state.shaders.shadow.pass);
375
376     sg.applyPipeline(state.shaders.shadow.pip);
377     sg.applyBindings(state.shaders.tex.bind);
378
379     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
380         .screen = [_]f32{ widthf, heightf },
381         .cam = state.cam,
382     }));
383
384     for (state.lights.items) |*light| {
385         sg.applyUniforms(shd.UB_Light, sg.asRange(light));
386
387         sg.draw(0, 3, @truncate(state.sprites.items.len));
388     }
389
390     sg.endPass();
391
392     // texture pass
393     sg.beginPass(state.shaders.tex.pass);
394
395     sg.updateBuffer(state.shaders.tex.bind.vertex_buffers[0], sg.asRange(state.sprites.items));
396
397     sg.applyPipeline(state.shaders.tex.pip);
398     sg.applyBindings(state.shaders.tex.bind);
399
400     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
401         .screen = [_]f32{ widthf, heightf },
402         .cam = state.cam,
403     }));
404
405     sg.draw(0, 6, @truncate(state.sprites.items.len));
406
407     sg.endPass();
408
409     // light pass
410     sg.beginPass(state.shaders.light.pass);
411
412     sg.updateBuffer(state.shaders.light.bind.vertex_buffers[0], sg.asRange(state.lights.items));
413
414     sg.applyPipeline(state.shaders.light.pip);
415     sg.applyBindings(state.shaders.light.bind);
416
417     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
418         .screen = [_]f32{ widthf, heightf },
419         .cam = state.cam,
420     }));
421
422     for (state.lights.items) |*light| {
423         sg.applyUniforms(shd.UB_Light, sg.asRange(light));
424
425         sg.draw(0, 6, 1);
426     }
427
428     sg.endPass();
429
430     // combine pass
431     sg.beginPass(.{ .action = state.shaders.combine.pass_action, .swapchain = sglue.swapchain() });
432
433     sg.applyPipeline(state.shaders.combine.pip);
434     sg.applyBindings(state.shaders.combine.bind);
435
436     sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
437         .screen = [_]f32{ widthf, heightf },
438         .cam = state.cam,
439     }));
440
441     sg.draw(0, 6, 1);
442
443     simgui.render();
444     sg.endPass();
445     sg.commit();
446 }
447
448 const state = struct {
449     var fps: u64 = 0;
450     var frames: u64 = 0;
451     var time: u64 = 0;
452
453     const OffscreenWidth = 1920;
454     const OffscreenHeight = 1080;
455     const OffscreenSampleCount = 1;
456     const MaxSprites = 100;
457     const MaxLights = 100;
458
459     const shaders = struct {
460         const combine = struct {
461             var pass_action: sg.PassAction = .{};
462             var bind: sg.Bindings = .{};
463             var pip: sg.Pipeline = .{};
464         };
465         const tex = struct {
466             var pass: sg.Pass = .{};
467             var bind: sg.Bindings = .{};
468             var pip: sg.Pipeline = .{};
469             var img: sg.Image = .{};
470             var depth: sg.Image = .{};
471         };
472         const shadow = struct {
473             var pass: sg.Pass = .{};
474             // var bind: sg.Bindings = .{};
475             var pip: sg.Pipeline = .{};
476             var img: sg.Image = .{};
477             var depth: sg.Image = .{};
478         };
479         const light = struct {
480             var pass: sg.Pass = .{};
481             var bind: sg.Bindings = .{};
482             var pip: sg.Pipeline = .{};
483             var img: sg.Image = .{};
484             var depth: sg.Image = .{};
485         };
486     };
487
488     var img_buf: [1024 * 1024]u8 = undefined;
489     var img: sg.Image = undefined;
490
491     var cam = [2]f32{ -300, -300 };
492
493     var sprites_buf: [MaxSprites]Sprite = undefined;
494     var sprites = std.ArrayListUnmanaged(Sprite).initBuffer(&state.sprites_buf);
495     var lights_buf: [MaxLights]shd.Light = undefined;
496     var lights = std.ArrayListUnmanaged(shd.Light).initBuffer(&state.lights_buf);
497 };
498
499 const DefaultLight = shd.Light{
500     .pos = [_]f32{ 150, -300 },
501     .color = [_]f32{ 1, 0.8, 0 },
502     .reach = 500,
503     .radius_glow = 200,
504     .intensity = 1.0,
505     .shadow_brightness = 0.5,
506 };
507
508 const Sprite = struct {
509     texid: usize = shd.VIEW_tex,
510     pos: [2]f32 = [_]f32{ 0, 0 },
511     size: [2]i32 = [_]i32{ 300, 300 },
512 };
513
514 // const Light = struct {
515 //     color: [3]f32 = [_]f32{ 1, 0, 0 },
516 //     pos: [2]f32 = [_]f32{ 0, 0 },
517 //     radius: f32 = 100,
518 // };
519
520 const std = @import("std");
521
522 const ig = @import("cimgui_docking");
523 const sokol = @import("sokol");
524 const slog = sokol.log;
525 const sg = sokol.gfx;
526 const sapp = sokol.app;
527 const stime = sokol.time;
528 const sglue = sokol.glue;
529 const sfetch = sokol.fetch;
530 const simgui = sokol.imgui;
531
532 const stbi = @cImport({
533     @cInclude("stb_image.h");
534 });
535
536 const shd = @import("shd/main.glsl.zig");
537
538 pub fn main() void {
539     sapp.run(.{
540         .init_cb = init,
541         .frame_cb = frame,
542         .cleanup_cb = cleanup,
543         .event_cb = event,
544         .window_title = "sokol-zig + Dear Imgui",
545         .width = 800,
546         .height = 600,
547         .icon = .{ .sokol_default = true },
548         .logger = .{ .func = slog.func },
549     });
550 }