-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 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 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,
+ .image_pool_size = 16,
.shader_pool_size = 8,
.pipeline_pool_size = 8,
- .attachments_pool_size = 1,
+ .view_pool_size = 16,
.environment = sglue.environment(),
.logger = .{ .func = slog.func },
});
.num_lanes = 1,
.logger = .{ .func = slog.func },
});
-
- // initialize sokol-imgui
+ stime.setup();
simgui.setup(.{
.logger = .{ .func = slog.func },
});
ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
// initial clear color
- state.pass_action.colors[0] = .{
+ 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,
- .clear_value = .{ .r = 0.0, .g = 0.5, .b = 1.0, .a = 1.0 },
+ .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,
+ });
+
+ 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,
+ });
- // default pass
- state.bind.images[shd.IMG_tex] = sg.allocImage();
+ state.shaders.combine.pip = sg.makePipeline(.{
+ .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())),
+ .depth = .{
+ .compare = .LESS_EQUAL,
+ .write_enabled = true,
+ },
+ });
- state.bind.samplers[shd.SMP_smp] = sg.makeSampler(.{
+ // 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.bind.vertex_buffers[0] = sg.makeBuffer(.{
+ 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.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.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;
},
+ .depth = .{
+ .compare = .LESS_EQUAL,
+ .write_enabled = true,
+ },
});
// shadow pass
- state.pip_shadow = sg.makePipeline(.{
+ 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].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.bind_light.vertex_buffers[0] = sg.makeBuffer(.{
+ 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(Light),
+ .size = state.MaxLights * @sizeOf(shd.Light),
});
- state.pip_light = sg.makePipeline(.{
+ state.shaders.light.pip = 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;
+ .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;
},
});
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.bind.images[shd.IMG_tex], .{
+ sg.initImage(state.img, .{
.width = w,
.height = h,
.pixel_format = sg.PixelFormat.RGBA8,
break :init i;
},
});
+ sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{
+ .texture = .{ .image = state.img },
+ });
}
}
}
});
state.sprites.appendAssumeCapacity(.{});
- state.lights.appendAssumeCapacity(.{ .pos = [_]f32{ 150, -300 } });
+ 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.igColorEdit3("Background", &state.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None);
+ ig.igLabelText("fps", "%d", state.fps);
_ = ig.igDragFloat2("cam", &state.cam);
{
{
ig.igPushIDPtr(state.lights.items.ptr);
if (ig.igButton("+")) {
- _ = state.lights.appendAssumeCapacity(.{});
+ _ = state.lights.appendAssumeCapacity(DefaultLight);
}
var i: u16 = 0;
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);
}
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();
});
ui();
- // 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));
-
- sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() });
-
// shadow pass
- sg.applyPipeline(state.pip_shadow);
- sg.applyBindings(state.bind);
+ 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(&shd.Light{
- .light_pos = light.pos,
- }));
+ for (state.lights.items) |*light| {
+ sg.applyUniforms(shd.UB_Light, sg.asRange(light));
- sg.draw(0, 6, @truncate(state.sprites.items.len));
+ sg.draw(0, 3, @truncate(state.sprites.items.len));
}
- // default pass
- sg.applyPipeline(state.pip);
- sg.applyBindings(state.bind);
+ 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 },
sg.draw(0, 6, @truncate(state.sprites.items.len));
+ sg.endPass();
+
// light pass
- sg.applyPipeline(state.pip_light);
- sg.applyBindings(state.bind_light);
+ 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,
}));
- sg.draw(0, 6, @truncate(state.lights.items.len));
+ sg.draw(0, 6, 1);
simgui.render();
sg.endPass();
sg.commit();
}
-export fn cleanup() void {
- simgui.shutdown();
- sfetch.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(.{