X-Git-Url: https://gitweb.ps.run/sporegirl/blobdiff_plain/f23222b4a847768225c50a37c9659a58865796c1..3f41dbdfc27ad9d42ce254618b7bafc58797f8b2:/src/main.zig diff --git a/src/main.zig b/src/main.zig index 92e1da5..bf77621 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,108 +1,539 @@ -const use_docking = @import("build_options").docking; -const ig = if (use_docking) @import("cimgui_docking") else @import("cimgui"); -const sokol = @import("sokol"); -const slog = sokol.log; -const sg = sokol.gfx; -const sapp = sokol.app; -const sglue = sokol.glue; -const simgui = sokol.imgui; - -const shd = @import("shader/quad.glsl.zig"); - -const state = struct { - 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; -}; - export fn init() void { // initialize sokol-gfx sg.setup(.{ + .buffer_pool_size = 8, + .image_pool_size = 16, + .shader_pool_size = 8, + .pipeline_pool_size = 8, + .view_pool_size = 16, .environment = sglue.environment(), .logger = .{ .func = slog.func }, }); - - // initialize sokol-imgui + sfetch.setup(.{ + .max_requests = 2, + .num_channels = 2, + .num_lanes = 1, + .logger = .{ .func = slog.func }, + }); + stime.setup(); 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.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, + .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, }); - // an index buffer - state.bind.index_buffer = sg.makeBuffer(.{ - .usage = .{ .index_buffer = true }, - .data = sg.asRange(&[_]u16{ 0, 1, 2, 0, 2, 3 }), + 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, + }); + + state.shaders.combine.pip = sg.makePipeline(.{ + .shader = sg.makeShader(shd.combineShaderDesc(sg.queryBackend())), + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, + }); + + // 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.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.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_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; }, - .index_type = .UINT16, + .depth = .{ + .compare = .LESS_EQUAL, + .write_enabled = true, + }, }); - // 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.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].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; + }, + .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.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(shd.Light), + }); + + state.shaders.light.pip = sg.makePipeline(.{ + .shader = sg.makeShader(shd.lightShaderDesc(sg.queryBackend())), + .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; + }, + }); + + // 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; + // 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.img, .{ + .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; + }, + }); + sg.initView(state.shaders.tex.bind.views[shd.VIEW_tex], .{ + .texture = .{ .image = state.img }, + }); + } + } + } }; + _ = 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(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.igLabelText("fps", "%d", state.fps); + _ = 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(DefaultLight); + } + + 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.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); + } + ig.igPopID(); + } + ig.igPopID(); + } + + ig.igEnd(); + } } 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(); + 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(); + // shadow pass + 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(light)); + + sg.draw(0, 3, @truncate(state.sprites.items.len)); + } + + 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 }, + .cam = state.cam, + })); + + sg.draw(0, 6, @truncate(state.sprites.items.len)); + + sg.endPass(); + + // light pass + 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, + })); - // call simgui.render() inside a sokol-gfx pass - sg.beginPass(.{ .action = state.pass_action, .swapchain = sglue.swapchain() }); - sg.applyPipeline(state.pip); - sg.applyBindings(state.bind); sg.draw(0, 6, 1); + simgui.render(); sg.endPass(); sg.commit(); } -export fn cleanup() void { - simgui.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(.{