1 export fn init() void {
2 // initialize sokol-gfx
7 .pipeline_pool_size = 8,
9 .environment = sglue.environment(),
10 .logger = .{ .func = slog.func },
16 .logger = .{ .func = slog.func },
20 .logger = .{ .func = slog.func },
22 ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
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 },
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 },
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 },
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,
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,
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,
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,
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,
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,
85 state.shaders.combine.bind.views[shd.VIEW_tex_tex] = sg.makeView(.{
86 .texture = .{ .image = state.shaders.tex.img },
88 state.shaders.combine.bind.views[shd.VIEW_tex_shadow] = sg.makeView(.{
89 .texture = .{ .image = state.shaders.shadow.img },
91 state.shaders.combine.bind.views[shd.VIEW_tex_light] = sg.makeView(.{
92 .texture = .{ .image = state.shaders.light.img },
94 state.shaders.combine.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{
95 .min_filter = sg.Filter.LINEAR,
96 .mag_filter = sg.Filter.LINEAR,
99 state.shaders.combine.pip = sg.makePipeline(.{
100 .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())),
102 .compare = .LESS_EQUAL,
103 .write_enabled = true,
108 state.img = sg.allocImage();
109 state.shaders.tex.pass.attachments.colors[0] = sg.makeView(.{
110 .color_attachment = .{ .image = state.shaders.tex.img },
112 state.shaders.tex.pass.attachments.depth_stencil = sg.makeView(.{
113 .depth_stencil_attachment = .{ .image = state.shaders.tex.depth },
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,
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),
129 // a shader and pipeline state object
130 state.shaders.tex.pip = sg.makePipeline(.{
131 .shader = sg.makeShader(shd.texShaderDesc(sg.queryBackend())),
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);
141 .compare = .LESS_EQUAL,
142 .write_enabled = true,
147 state.shaders.shadow.pass.attachments.colors[0] = sg.makeView(.{
148 .color_attachment = .{ .image = state.shaders.shadow.img },
150 state.shaders.shadow.pass.attachments.depth_stencil = sg.makeView(.{
151 .depth_stencil_attachment = .{ .image = state.shaders.shadow.depth },
153 state.shaders.shadow.pip = sg.makePipeline(.{
154 .shader = sg.makeShader(shd.shadowShaderDesc(sg.queryBackend())),
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);
164 .compare = .LESS_EQUAL,
165 .write_enabled = true,
168 var c: [4]sg.ColorTargetState = @splat(.{});
172 .src_factor_rgb = .DST_COLOR,
173 .dst_factor_rgb = .ZERO,
175 .src_factor_alpha = .ONE,
176 .dst_factor_alpha = .ONE,
185 state.shaders.light.pass.attachments.colors[0] = sg.makeView(.{
186 .color_attachment = .{ .image = state.shaders.light.img },
188 state.shaders.light.pass.attachments.depth_stencil = sg.makeView(.{
189 .depth_stencil_attachment = .{ .image = state.shaders.light.depth },
191 state.shaders.light.bind.vertex_buffers[0] = sg.makeBuffer(.{
192 .usage = .{ .stream_update = true },
193 .size = state.MaxLights * @sizeOf(shd.Light),
196 state.shaders.light.pip = sg.makePipeline(.{
197 .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())),
199 .compare = .LESS_EQUAL,
200 .write_enabled = true,
202 .blend_color = sg.Color{ .r=0.5,.g=0.5,.b=0.5,.a = 0.5 },
204 var c: [4]sg.ColorTargetState = @splat(.{});
208 .src_factor_rgb = .SRC_COLOR,
209 .dst_factor_rgb = .ONE_MINUS_SRC_COLOR,
210 .src_factor_alpha = .ONE,
211 .dst_factor_alpha = .ONE,
219 const FileCb = struct {
220 fn img(res: [*c]const sfetch.Response) callconv(.c) void {
221 if (res.*.finished) {
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, .{
231 .pixel_format = sg.PixelFormat.RGBA8,
233 var i = sg.ImageData{};
234 i.subimage[0][0] = .{
236 .size = @intCast(w * h * 4),
241 sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{
242 .texture = .{ .image = state.img },
249 .path = "img/test.png",
250 .callback = FileCb.img,
251 .buffer = .{ .ptr = &state.img_buf, .size = state.img_buf.len },
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 };
260 export fn cleanup() void {
266 export fn event(ev: [*c]const sapp.Event) void {
267 // forward input events to sokol-imgui
268 _ = simgui.handleEvent(ev.*);
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];
280 if (ig.igBegin("Hello Dear ImGui!", null, ig.ImGuiWindowFlags_None)) {
281 ig.igLabelText("fps", "%d", state.fps);
282 _ = ig.igDragFloat2("cam", &state.cam);
285 ig.igPushIDPtr(state.sprites.items.ptr);
286 if (ig.igButton("+")) {
287 _ = state.sprites.appendAssumeCapacity(.{});
291 while (i < state.sprites.items.len) {
292 var sprite = &state.sprites.items[i];
295 ig.igPushItemWidth(100);
296 _ = ig.igDragFloat2("pos", &sprite.pos);
298 _ = ig.igDragInt2("size", &sprite.size);
300 if (ig.igButton("-")) {
302 _ = state.sprites.swapRemove(i);
310 ig.igPushIDPtr(state.lights.items.ptr);
311 if (ig.igButton("+")) {
312 _ = state.lights.appendAssumeCapacity(DefaultLight);
316 while (i < state.lights.items.len) {
317 var light = &state.lights.items[i];
320 ig.igPushItemWidth(100);
321 _ = ig.igColorEdit3("color", &light.color, ig.ImGuiColorEditFlags_None);
323 _ = ig.igDragFloat2("pos", &light.pos);
325 _ = ig.igDragFloat("reach", &light.reach);
327 _ = ig.igDragFloat("radius_glow", &light.radius_glow);
329 _ = ig.igDragFloatEx("intensity", &light.intensity, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None);
331 _ = ig.igDragFloatEx("shadow_brightness", &light.shadow_brightness, 0.01, 0, 1, "%.3f", ig.ImGuiSliderFlags_None);
333 if (ig.igButton("-")) {
335 _ = state.lights.swapRemove(i);
346 export fn frame() void {
351 const now = stime.now();
352 if (stime.ms(stime.diff(now, state.time)) >= 1000) {
353 state.fps = state.frames;
359 const width = sapp.width();
360 const height = sapp.height();
361 const widthf = sapp.widthf();
362 const heightf = sapp.heightf();
364 // call simgui.newFrame() before any ImGui calls
368 .delta_time = sapp.frameDuration(),
369 .dpi_scale = sapp.dpiScale(),
374 sg.beginPass(state.shaders.shadow.pass);
376 sg.applyPipeline(state.shaders.shadow.pip);
377 sg.applyBindings(state.shaders.tex.bind);
379 sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
380 .screen = [_]f32{ widthf, heightf },
384 for (state.lights.items) |*light| {
385 sg.applyUniforms(shd.UB_Light, sg.asRange(light));
387 sg.draw(0, 3, @truncate(state.sprites.items.len));
393 sg.beginPass(state.shaders.tex.pass);
395 sg.updateBuffer(state.shaders.tex.bind.vertex_buffers[0], sg.asRange(state.sprites.items));
397 sg.applyPipeline(state.shaders.tex.pip);
398 sg.applyBindings(state.shaders.tex.bind);
400 sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
401 .screen = [_]f32{ widthf, heightf },
405 sg.draw(0, 6, @truncate(state.sprites.items.len));
410 sg.beginPass(state.shaders.light.pass);
412 sg.updateBuffer(state.shaders.light.bind.vertex_buffers[0], sg.asRange(state.lights.items));
414 sg.applyPipeline(state.shaders.light.pip);
415 sg.applyBindings(state.shaders.light.bind);
417 sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
418 .screen = [_]f32{ widthf, heightf },
422 for (state.lights.items) |*light| {
423 sg.applyUniforms(shd.UB_Light, sg.asRange(light));
431 sg.beginPass(.{ .action = state.shaders.combine.pass_action, .swapchain = sglue.swapchain() });
433 sg.applyPipeline(state.shaders.combine.pip);
434 sg.applyBindings(state.shaders.combine.bind);
436 sg.applyUniforms(shd.UB_Game, sg.asRange(&shd.Game{
437 .screen = [_]f32{ widthf, heightf },
448 const state = struct {
453 const OffscreenWidth = 1920;
454 const OffscreenHeight = 1080;
455 const OffscreenSampleCount = 1;
456 const MaxSprites = 100;
457 const MaxLights = 100;
459 const shaders = struct {
460 const combine = struct {
461 var pass_action: sg.PassAction = .{};
462 var bind: sg.Bindings = .{};
463 var pip: sg.Pipeline = .{};
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 = .{};
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 = .{};
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 = .{};
488 var img_buf: [1024 * 1024]u8 = undefined;
489 var img: sg.Image = undefined;
491 var cam = [2]f32{ -300, -300 };
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);
499 const DefaultLight = shd.Light{
500 .pos = [_]f32{ 150, -300 },
501 .color = [_]f32{ 1, 0.8, 0 },
505 .shadow_brightness = 0.5,
508 const Sprite = struct {
509 texid: usize = shd.VIEW_tex,
510 pos: [2]f32 = [_]f32{ 0, 0 },
511 size: [2]i32 = [_]i32{ 300, 300 },
514 // const Light = struct {
515 // color: [3]f32 = [_]f32{ 1, 0, 0 },
516 // pos: [2]f32 = [_]f32{ 0, 0 },
517 // radius: f32 = 100,
520 const std = @import("std");
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;
532 const stbi = @cImport({
533 @cInclude("stb_image.h");
536 const shd = @import("shd/main.glsl.zig");
542 .cleanup_cb = cleanup,
544 .window_title = "sokol-zig + Dear Imgui",
547 .icon = .{ .sokol_default = true },
548 .logger = .{ .func = slog.func },