pub const MeshPool = struct { allocator: std.mem.Allocator, meshMap: std.AutoHashMapUnmanaged(u32, rend.IndexedMesh) = .{}, indexSpans: core.MergedSpans, vertexSpans: core.MergedSpans, indexBuffer: *gpu.GPUBuffer = undefined, vertexBuffer: *gpu.GPUBuffer = undefined, meshUpdates: core.RingQueue(rend.MeshUpdate), device: *gpu.GPUDevice, destroyList: std.ArrayList(*gpu.GPUTransferBuffer), installedMeshes: std.AutoHashMapUnmanaged(u32, meshes.IndexedMesh) = .{}, pub fn create(device: *gpu.GPUDevice, allocator: std.mem.Allocator, settings: MeshPoolCreationSettings) !*@This() { const self = try allocator.create(@This()); gMeshPool = self; self.* = .{ .allocator = allocator, .indexSpans = try core.MergedSpans.init(allocator, settings.indexCount), .vertexSpans = try core.MergedSpans.init(allocator, settings.vertexCount), .meshUpdates = try core.RingQueue(rend.MeshUpdate).init(allocator, 128), .destroyList = std.ArrayList(*gpu.GPUTransferBuffer).init(allocator), .device = device, }; self.vertexBuffer = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageVertex = true }, .size = settings.vertexCount * @sizeOf(rend.MeshVertex), .props = 0, }); self.indexBuffer = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageIndex = true }, .size = settings.indexCount * @sizeOf(u32), .props = 0, }); core.graphics_log("mesh pool created {d}k vertices, {d}k indices", .{ settings.vertexCount / 1000, settings.indexCount / 1000 }); try assets.gAssetSys.registerLoader(try core.createObject(MeshAssetLoader, .{})); return self; } pub fn onUploadCleanup(p: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); for (self.destroyList.items) |transferBuffer| { self.device.releaseGPUTransferBuffer(transferBuffer); } self.destroyList.clearRetainingCapacity(); } pub fn onUploadInner(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { self.meshUpdates.lock(); defer self.meshUpdates.unlock(); self.destroyList = std.ArrayList(*gpu.GPUTransferBuffer).init(self.allocator); while (self.meshUpdates.popFromUnlocked()) |u| { switch (u) { .new => |new| { const indexSpan = try self.addTransfer(copyPass, self.indexBuffer, u32, &self.indexSpans, new.indices); const vertexSpan = try self.addTransfer(copyPass, self.vertexBuffer, meshes.MeshVertex, &self.vertexSpans, new.vertices); var name = new.name; core.graphics_log("uploading {d} vertices and {d} indices", .{ vertexSpan.size, indexSpan.size }); try self.installedMeshes.put(self.allocator, name.handle(), .{ .vertex = vertexSpan, .index = indexSpan, .name = new.name, .jointRemap = null, // joint remaps... parse the installed skeletal meshes to generate this remap }); }, .free => |free| { var name = free.name; if (!self.installedMeshes.remove(name.handle())) { core.engine_log("unable to find handle from mesh pool.. {s}", .{name.utf8()}); } self.vertexSpans.removeSpan(free.vertices); self.indexSpans.removeSpan(free.indices); }, } u.deinit(self.allocator); } } pub fn onUpload(p: *anyopaque, copyPass: *gpu.GPUCopyPass) void { const self: *@This() = @ptrCast(@alignCast(p)); if (self.meshUpdates.count() <= 0) return; self.onUploadInner(copyPass) catch unreachable; } pub fn addTransfer( self: *@This(), copyPass: *gpu.GPUCopyPass, destBuffer: *gpu.GPUBuffer, comptime T: type, mergedSpans: *core.MergedSpans, uploadSlice: []const T, ) !core.Span { const newSpan = try mergedSpans.allocate(@intCast(uploadSlice.len)); const upload = self.device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = @sizeOf(T) * newSpan.size, .props = 0 }); try self.destroyList.append(upload); var mappedSlice: []T = undefined; mappedSlice.ptr = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(upload, false))); defer self.device.unmapGPUTransferBuffer(upload); mappedSlice.len = uploadSlice.len; std.mem.copyForwards(T, mappedSlice, uploadSlice); //copyPass.uploadToGPUBuffer(source: [*c]const GPUTransferBufferLocation, destination: [*c]const GPUBufferRegion, cycle: bool) copyPass.uploadToGPUBuffer( &.{ .transfer_buffer = upload, .offset = 0 }, &.{ .buffer = destBuffer, .offset = newSpan.start, .size = newSpan.size }, false, ); return newSpan; } // pushes the meshUpdate into the pool, either an insert or a free. pub fn pushMeshUpdate(self: *@This(), meshUpdate: rend.MeshUpdate) !void { try self.meshUpdates.pushLocked(meshUpdate); } pub fn destroy(p: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); self.device.releaseGPUBuffer(self.indexBuffer); self.device.releaseGPUBuffer(self.vertexBuffer); self.installedMeshes.deinit(self.allocator); self.destroyList.deinit(); self.indexSpans.deinit(); self.meshUpdates.deinit(); self.vertexSpans.deinit(); self.allocator.destroy(self); } pub const MeshPoolCreationSettings = struct { vertexCount: u32 = 4_000_000, indexCount: u32 = 16_000_000, }; }; /// === renderer interface implementation === var gMeshPool: *MeshPool = undefined; pub fn getMesh(name: []const u8) ?rend.IndexedMesh { var n = core.MakeName(name); return getMeshByName(&n); } pub fn getMeshByName(name: *core.Name) ?rend.IndexedMesh { return gMeshPool.installedMeshes.get(name.handle()); } pub fn pushMeshUpdate(meshUpdate: rend.MeshUpdate) !void { try gMeshPool.pushMeshUpdate(meshUpdate); } const MeshAssetLoader = @import("MeshAssetLoader.zig"); const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const rend = @import("../rend.zig"); const std = @import("std"); const core = @import("core"); const assets = @import("assets"); const meshes = rend.meshes;