allocator: std.mem.Allocator, ssboScene: *gpu.GPUBuffer = undefined, ssboSceneUpload: *gpu.GPUTransferBuffer = undefined, spans: std.ArrayListUnmanaged(core.Span) = .{}, renderInfo: std.ArrayListUnmanaged(SgpuParticleRenderInfo) = .{}, lastParticleCount: u32 = 0, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.ParticleRenderer"); const SgpuParticleRenderInfo = struct { mesh: rend.IndexedMesh, texture: *rend.Texture, }; pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void { if (!first) return; self.* = .{ .allocator = allocator, }; const device = rend.context().device; const MaxParticleCount = core.configVar(u32, "rend.particles.maxCount", 200_000); self.ssboScene = device.createGPUBuffer(&.{ .usage = .{ .bufferusageGraphicsStorageRead = true, .bufferusageVertex = true, }, .size = MaxParticleCount * @sizeOf(meshes_vert.Scene), .props = 0, }); self.ssboSceneUpload = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxParticleCount * @sizeOf(meshes_vert.Scene), .props = 0, }); } pub fn uploadSsbos(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { self.spans.clearRetainingCapacity(); self.renderInfo.clearRetainingCapacity(); var offset: usize = 0; const device = rend.context().device; const uploadMapped: [*]meshes_vert.Scene = @ptrCast(@alignCast(device.mapGPUTransferBuffer(self.ssboSceneUpload, true))); defer device.unmapGPUTransferBuffer(self.ssboSceneUpload); for (rend.ParticleEmitter.BaseContainer.dense.items) |*c| { const emitter = &c.value; for (emitter.finals.items, 0..) |final, i| { const size = emitter.size.items[i]; uploadMapped[i + offset] = .{ .Model = final, .textureMode = if (emitter.billboard) 0x2 else 0x0, //0x2, // default 0x2 is fullbright .animation = -1, .float0 = size, .flags = if (emitter.billboard) 0x4 else 0, //0x4 for billboard mode }; } try self.renderInfo.append(self.allocator, .{ .texture = emitter.texture, .mesh = emitter.mesh }); try self.spans.append(self.allocator, .{ .start = @intCast(offset), .size = @intCast(emitter.finals.items.len) }); offset += emitter.finals.items.len; } self.lastParticleCount = @intCast(offset); if (self.renderInfo.items.len == 0) return; copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.ssboSceneUpload, .offset = 0 }, &.{ .buffer = self.ssboScene, .offset = 0, .size = @intCast(offset * @sizeOf(meshes_vert.Scene)), }, true); } pub fn postMesh(p: *anyopaque, cmd: *gpu.GPUCommandBuffer, renderpass: *gpu.GPURenderPass) void { const self: *@This() = @ptrCast(@alignCast(p)); _ = cmd; renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 1); renderpass.bindGPUFragmentStorageBuffers(0, &self.ssboScene, 1); const ctx = rend.context(); for (self.spans.items, 0..) |span, i| { const ri = self.renderInfo.items[i]; const mesh = ri.mesh; const _t = ri.texture; var sampler: *gpu.GPUSampler = undefined; switch (_t.samplerMode) { .blocky => { sampler = ctx.blockySampler; }, .linear => { sampler = ctx.linearSampler; }, } renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 0); renderpass.bindGPUFragmentSamplers(0, &.{ .texture = _t.texture, .sampler = sampler }, 1); renderpass.drawGPUIndexedPrimitives(mesh.index.size, span.size, mesh.index.start, @intCast(mesh.vertex.start), @intCast(span.start)); } } pub fn setup(self: *@This(), device: *gpu.GPUDevice) !void { _ = self; _ = device; } pub fn deinit(self: *@This()) void { self.spans.deinit(self.allocator); self.renderInfo.deinit(self.allocator); } const assets = @import("assets"); const core = @import("core"); const std = @import("std"); const rend = @import("../rend.zig"); const renderer = rend.renderer; const particles = rend.particles; const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const meshes_vert = @import("meshes.vert");