pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.DebugDrawSystem"); allocator: std.mem.Allocator, debugDraws: core.RingQueueU(DebugPrimitive), device: *gpu.GPUDevice = undefined, renderer: *Renderer = undefined, pipeline: *gpu.GPUGraphicsPipeline = undefined, ssbo: *gpu.GPUBuffer = undefined, ssobUpload: *gpu.GPUTransferBuffer = undefined, meshes: [@as(usize, @intCast(@intFromEnum(DebugPrimitiveType.box) + 1))]?rend.IndexedMesh = .{ null, null, null }, deltaTime: f64 = 0, drawsThisFrame: std.ArrayListUnmanaged(DebugPrimitive) = .{}, pub const MaxObjectCount = 4096; const assetReferences = [_]assets.AssetImportReference{ assets.MakeImportRefOptions( "Mesh", "m_debug_box", .{ .path = "embedded:debug_box.obj" }, ), assets.MakeImportRefOptions( "Mesh", "m_debug_line", .{ .path = "embedded:debug_line.obj" }, ), assets.MakeImportRefOptions( "Mesh", "m_debug_sphere", .{ .path = "embedded:debug_sphere.obj" }, ), }; pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void { if (!first) return; self.* = .{ .debugDraws = try core.RingQueueU(DebugPrimitive).init(allocator, MaxObjectCount), .allocator = allocator, }; core.engine_logs("[DebugDrawSystem] starting up..."); _ = try core.fs().installFileBytesMount("embedded:debug_box.obj", @constCast(&primitive_box), true); _ = try core.fs().installFileBytesMount("embedded:debug_line.obj", @constCast(&primitive_line), true); _ = try core.fs().installFileBytesMount("embedded:debug_sphere.obj", @constCast(&primitive_sphere), true); try assets.loadList(assetReferences); gDebugDrawSys = self; } pub fn updateMeshes(self: *@This()) void { // line = 0, // sphere = 1, // box = 2, const list: [3][]const u8 = .{ "m_debug_line", // 0 "m_debug_sphere", // 1 "m_debug_box", // 2 }; for (list, 0..) |path, i| { self.meshes[i] = rend.getMesh(path); } } pub fn tick(self: *@This(), dt: f64) void { self.deltaTime = dt; if (self.meshes[0] == null) { self.updateMeshes(); } } // ========= Rendering section ========= pub fn onUpload(p: *anyopaque, copyPass: *gpu.GPUCopyPass) void { const self: *@This() = @ptrCast(@alignCast(p)); const count = self.debugDraws.count(); { const uploadMapped: [*]debug_vert.Scene = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(self.ssobUpload, true))); defer self.device.unmapGPUTransferBuffer(self.ssobUpload); var offset: usize = 0; self.drawsThisFrame.clearRetainingCapacity(); while (offset < count) { var primitive: DebugPrimitive = self.debugDraws.pop().?; uploadMapped[offset] = .{ .Model = primitive.resolve(), .Color = @bitCast(primitive.color.toZm()), }; self.drawsThisFrame.append(self.allocator, primitive) catch unreachable; primitive.duration -= @as(f32, @floatCast(self.deltaTime)); offset += 1; if (primitive.duration >= 0) { // push this primitive so that it goes to the next frame self.debugDraws.push(primitive) catch continue; } } } if (count > 0) { copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.ssobUpload, .offset = 0 }, &.{ .buffer = self.ssbo, .offset = 0, .size = @intCast(count * @sizeOf(debug_vert.Scene)), }, false); } } pub fn postMesh(p: *anyopaque, cmd: *gpu.GPUCommandBuffer, renderpass: *gpu.GPURenderPass) void { const self: *@This() = @ptrCast(@alignCast(p)); if (self.meshes[0] == null) { return; } renderpass.bindGPUGraphicsPipeline(self.pipeline); renderpass.bindGPUVertexStorageBuffers(0, &self.ssbo, 1); for (self.drawsThisFrame.items, 0..) |draw, i| { var mesh: rend.IndexedMesh = undefined; switch (draw.primitive) { .box => { mesh = self.meshes[2].?; }, .sphere => { mesh = self.meshes[1].?; }, .line => { mesh = self.meshes[0].?; }, } renderpass.drawGPUIndexedPrimitives(mesh.index.size, 1, mesh.index.start, @intCast(mesh.vertex.start), @intCast(i)); } _ = cmd; } // ========= End of Rendering section ========= pub fn createBuffers(self: *@This()) !void { self.ssbo = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageGraphicsStorageRead = true, .bufferusageVertex = true }, .size = MaxObjectCount * @sizeOf(debug_vert.Scene), .props = 0, }); self.ssobUpload = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(debug_vert.Scene), .props = 0, }); } pub fn createPipeline(self: *@This()) !*gpu.GPUGraphicsPipeline { const vertex = try rend.context().loadShader("debug.vert", debug_vert.LoadArgs); const fragment = try rend.context().loadShader("debug.frag", debug_frag.LoadArgs); var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo); pci.vertex_shader = vertex; pci.fragment_shader = fragment; var attributes = std.ArrayList(gpu.GPUVertexAttribute){}; defer attributes.deinit(self.allocator); var offset: u32 = 0; { try addAttribute(self.allocator, &attributes, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); } pci.vertex_input_state = .{ .num_vertex_buffers = 1, .vertex_buffer_descriptions = &[_]gpu.GPUVertexBufferDescription{ .{ .slot = 0, .pitch = @sizeOf(rend.MeshVertex), .input_rate = .vertexinputrateVertex, .instance_step_rate = 0 }, }, .num_vertex_attributes = @intCast(attributes.items.len), .vertex_attributes = @ptrCast(attributes.items.ptr), }; pci.depth_stencil_state.compare_op = .compareopLess; pci.depth_stencil_state.enable_depth_test = true; pci.depth_stencil_state.enable_depth_write = true; pci.depth_stencil_state.write_mask = 0xff; pci.target_info.has_depth_stencil_target = true; pci.target_info.depth_stencil_format = rend.context().depthFormat; pci.target_info.num_color_targets = 4; pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{ .{ .format = rend.context().hdrTextureFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, .{ .format = rend.context().hdrTextureFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, .{ .format = rend.context().positionTargetFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, .{ .format = rend.context().normalTargetFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, }; pci.rasterizer_state.fill_mode = .fillmodeLine; const pipe = self.device.createGPUGraphicsPipeline(&pci); return pipe; } fn addAttribute(allocator: std.mem.Allocator, list: *std.ArrayList(gpu.GPUVertexAttribute), offset: *u32, size: u32, format: gpu.GPUVertexElementFormat) !void { try list.append(allocator, .{ .location = @intCast(list.items.len), .offset = offset.*, .format = format, .buffer_slot = 0 }); offset.* = offset.* + size; } pub fn setup(self: *@This(), device: *gpu.GPUDevice) !void { core.engine_logs("[DebugDrawSystem] registering to renderer..."); self.device = device; self.renderer = rend.context(); self.pipeline = try self.createPipeline(); try self.createBuffers(); //core.installDebugDrawInterface(self.allocator, newInterface: DebugDrawInterface); try core.installDebugDrawInterface(self.allocator, .{ .debugSphereFn = debugSphere, // : *const fn (pos: core.Vectorf, radius: f32, DebugDrawParams) void, .debugBoxFn = debugBox, // : *const fn (pos: core.Vectorf, extents: core.Vectorf, DebugDrawParams) void, .debugLineFn = debugLine, // : *const fn (start: core.Vectorf, end: core.Vectorf, DebugDrawParams) void, }); } pub fn destroy(self: *@This()) void { self.debugDraws.deinit(self.allocator); self.drawsThisFrame.deinit(self.allocator); } // ==================== Primitives =================== pub const DebugLine = struct { start: core.Vectorf, end: core.Vectorf, pub fn resolve(self: @This(), _: anytype) core.Transform { var delta = self.start.sub(self.end); const d = delta.normalize(); const axz = std.math.atan2(-d.z, d.x) + core.radians(180.0); const ay = -std.math.asin(d.y); const mat1 = core.zm.matFromRollPitchYaw(0, 0, ay); const mat2 = core.zm.rotationY(axz); const len = delta.length(); return core.zm.mul(core.zm.mul( core.zm.mul(mat1, mat2), core.zm.scaling(len, len, len), ), core.zm.translationV(self.start.toZm())); } }; pub const DebugSphere = struct { position: core.Vectorf, radius: f32, rotation: core.Quat, pub fn resolve(self: @This(), _: anytype) core.Transform { return core.zm.mul(core.zm.mul( core.zm.matFromQuat(self.rotation), core.zm.scaling(self.radius, self.radius, self.radius), ), core.zm.translationV(self.position.toZm())); } }; pub const DebugBox = struct { position: core.Vectorf, extents: core.Vectorf, rotation: core.Quat, pub fn resolve(self: @This(), _: anytype) core.Transform { return core.zm.mul(core.zm.mul( core.zm.matFromQuat(self.rotation), core.zm.scalingV(self.extents.toZm()), ), core.zm.translationV(self.position.toZm())); } }; const DebugPrimitiveType = enum(u8) { line = 0, sphere = 1, box = 2, }; pub const DebugPrimitive = struct { primitive: union(DebugPrimitiveType) { line: DebugLine, sphere: DebugSphere, box: DebugBox, }, color: core.Vectorf = .{ .x = 0.0, .y = 1.0, .z = 0.0 }, duration: f32 = 0.0, pub fn resolve(self: @This()) core.Transform { // comptime core.asserts(@sizeOf(DebugPrimitiveGpu) == DebugPrimitiveGpu.TargetSize, ""); switch (self.primitive) { .line => |inner| { return inner.resolve(.{}); }, .sphere => |inner| { return inner.resolve(.{}); }, .box => |inner| { return inner.resolve(.{}); }, } unreachable; } }; const DebugPrimitiveGpu = struct { const UnpaddedSize = @sizeOf(core.Transform) + @sizeOf(core.Vectorf); const TargetSize = 80; model: core.Transform, color: core.Vectorf, pad: [TargetSize - UnpaddedSize]u8 = std.mem.zeroes([TargetSize - UnpaddedSize]u8), }; const DebugDrawSharedInstance = struct {}; const DebugSharedData = struct { drawsThisFrame: std.ArrayListUnmanaged(DebugPrimitive) = .{}, lock: std.Thread.Mutex = .{}, pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { self.drawsThisFrame.deinit(allocator); } }; const objectCount = 2048; // ==================== End of primitives ============ var gDebugDrawSys: *@This() = undefined; pub fn debugSphere(position: core.Vectorf, radius: f32, params: DebugDrawParams) void { gDebugDrawSys.debugDraws.push(.{ .primitive = .{ .sphere = .{ .position = position, .radius = radius, .rotation = params.rotation } }, .color = params.color, .duration = params.duration, }) catch return; } pub fn debugLine(start: core.Vectorf, end: core.Vectorf, params: DebugDrawParams) void { gDebugDrawSys.debugDraws.push(.{ .primitive = .{ .line = .{ .start = start, .end = end, } }, .color = params.color, .duration = params.duration, }) catch return; } pub fn debugBox(position: core.Vectorf, extents: core.Vectorf, params: DebugDrawParams) void { gDebugDrawSys.debugDraws.push(.{ .primitive = .{ .box = .{ .position = position, .extents = extents, .rotation = params.rotation }, }, .color = params.color, .duration = params.duration, }) catch return; } const DebugDrawParams = core.DebugDrawParams; const core = @import("core"); const rend = @import("../rend.zig"); const assets = @import("assets"); const texture = rend.texture; const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const std = @import("std"); const debug_frag = @import("debug.frag"); const debug_vert = @import("debug.vert"); const Renderer = @import("renderer.zig").Renderer; const primitive_box align(8) = @embedFile("embedded/primitive_box.obj").*; const primitive_line align(8) = @embedFile("embedded/primitive_line.obj").*; const primitive_sphere align(8) = @embedFile("embedded/primitive_sphere.obj").*;