// sdl3_gpu is like 8 characterso // so instead of refering to it by it's full name every time // // we will just call it sgpu. pub const Renderer = struct { allocator: std.mem.Allocator, totalTime: f64 = 0, device: *gpu.GPUDevice = undefined, shaderType: []const u8 = undefined, shaderSuffix: []const u8 = undefined, shaderformat: gpu.GPUShaderFormat = undefined, entrypoint: []const u8 = "main", testPipeline: *gpu.GPUGraphicsPipeline = undefined, meshPipe: *gpu.GPUGraphicsPipeline = undefined, scissor: gpu.Rect = .{ .x = 0, .y = 0, .w = 1600, .h = 900 }, colorBuffer: *gpu.GPUBuffer = undefined, colorBufferTransfer: *gpu.GPUTransferBuffer = undefined, window: *sdl3.Window = undefined, activeCamera: ?*rend.CameraComponent = null, ssboScene: *gpu.GPUBuffer = undefined, ssboSceneUpload: *gpu.GPUTransferBuffer = undefined, meshPool: *MeshPool = undefined, uploads: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCopyPass) void }) = .{}, uploadCleanup: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, destroys: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, depthTexture: *gpu.GPUTexture = undefined, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This()); pub const MaxObjectCount = 50000; pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, }; try core.defineComponent(rend.MeshComponent, allocator); try core.defineComponent(rend.CameraComponent, allocator); return self; } pub fn registerRendererObject(self: *@This(), T: type) !*T { const object = try T.create(self.device, self.allocator, .{}); try self.uploads.append(self.allocator, .{ .ptr = object, .func = T.onUpload }); try self.uploadCleanup.append(self.allocator, .{ .ptr = object, .func = T.onUploadCleanup }); try self.destroys.append(self.allocator, .{ .ptr = object, .func = T.destroy }); return object; } fn createDepthTexture(self: *@This()) !void { var gci = std.mem.zeroes(gpu.GPUTextureCreateInfo); gci.type = .texturetype2d; gci.format = .textureformatD32Float; gci.width = @intCast(self.scissor.w); gci.height = @intCast(self.scissor.h); gci.layer_count_or_depth = 1; gci.num_levels = 1; gci.usage = .{ .textureusageDepthStencilTarget = true }; self.depthTexture = self.device.createGPUTexture(&gci); } pub fn startRenderer(self: *@This()) !void { self.device = gpu.createGPUDevice(.{ .shaderformatSpirv = true, .shaderformatDxil = true, .shaderformatMsl = true, }, true, null); self.window = platform.getInstance().window; if (!self.device.claimWindowForGPUDevice(self.window)) return error.UnableToClaimGpu; try self.discoverFormats(); core.engine_log("sgpu device created, using shader format: {s}", .{self.shaderSuffix}); // try self.createPipeline(); try self.createMeshPipeline(); try self.createDepthTexture(); self.meshPool = try self.registerRendererObject(MeshPool); } pub fn discoverFormats(self: *@This()) !void { const formats = self.device.getGPUShaderFormats(); if (formats.shaderformatSpirv) { self.shaderType = "spv"; // shaderformatSpirv self.shaderSuffix = ".spv"; self.shaderformat = .{ .shaderformatSpirv = true }; } else if (formats.shaderformatMsl) { self.shaderType = "msl"; // shaderformatSpirv self.shaderSuffix = ".msl"; self.entrypoint = "main0"; self.shaderformat = .{ .shaderformatMsl = true }; } else if (formats.shaderformatDxil) { self.shaderType = "dxil"; // shaderformatSpirv self.shaderSuffix = ".dxil"; self.shaderformat = .{ .shaderformatDxil = true }; } } fn addAttribute(list: *std.ArrayList(gpu.GPUVertexAttribute), offset: *u32, size: u32, format: gpu.GPUVertexElementFormat) !void { try list.append(.{ .location = @intCast(list.items.len), .offset = offset.*, .format = format, .buffer_slot = 0 }); offset.* = offset.* + size; } pub fn createMeshPipeline(self: *@This()) !void { const vertex = try self.loadShader("meshes.vert", meshes_vert.LoadArgs); const fragment = try self.loadShader("lit_mesh.frag", lit_mesh_frag.LoadArgs); var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo); pci.vertex_shader = vertex; pci.fragment_shader = fragment; var attributes = std.ArrayList(gpu.GPUVertexAttribute).init(self.allocator); defer attributes.deinit(); var offset: u32 = 0; { try addAttribute(&attributes, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(&attributes, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(&attributes, &offset, @sizeOf(f32) * 4, .vertexelementformatFloat4); try addAttribute(&attributes, &offset, @sizeOf(f32) * 2, .vertexelementformatFloat2); try addAttribute(&attributes, &offset, @sizeOf(u32), .vertexelementformatUint); } 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 = .textureformatD32Float; pci.target_info.num_color_targets = 1; pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{ .{ .format = self.device.getGPUSwapchainTextureFormat(self.window), .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, }; pci.rasterizer_state.fill_mode = .fillmodeFill; self.meshPipe = self.device.createGPUGraphicsPipeline(&pci); // ssbo buffer // todo.. sparse uploads self.ssboScene = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageGraphicsStorageRead = true, .bufferusageVertex = true }, .size = MaxObjectCount * @sizeOf(meshes_vert.Scene), .props = 0, }); self.ssboSceneUpload = self.device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(meshes_vert.Scene), .props = 0, }); } pub fn uploadSSBOs(self: *@This(), copyPass: *gpu.GPUCopyPass) !void { const uploadMapped: [*]meshes_vert.Scene = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(self.ssboSceneUpload, true))); self.device.unmapGPUTransferBuffer(self.ssboSceneUpload); const container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); for (0..uploadCount) |i| { // const object = &container.dense.items[i].value; const objectId = container.dense.items[i].sparseIndex; var transform = core.zm.identity(); if (core.Scene.SceneObjectContainer.get(objectId, ._repr)) |repr| { transform = repr.transform; } uploadMapped[i].Model = @bitCast(transform); } if (uploadCount == 0) return; copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.ssboSceneUpload, .offset = 0 }, &.{ .buffer = self.ssboScene, .offset = 0, .size = @intCast(uploadCount * @sizeOf(meshes_vert.Scene)), }, true); } pub fn createPipeline(self: *@This()) !void { const vertex = try self.loadShader("sample.vert", sample_vert.LoadArgs); const fragment = try self.loadShader("sample.frag", .{}); var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo); pci.fragment_shader = fragment; pci.vertex_shader = vertex; pci.target_info.num_color_targets = 1; pci.target_info.has_depth_stencil_target = true; pci.target_info.depth_stencil_format = .textureformatD32Float; pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{ .{ .format = self.device.getGPUSwapchainTextureFormat(self.window), .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, }; pci.rasterizer_state.fill_mode = .fillmodeFill; self.testPipeline = self.device.createGPUGraphicsPipeline(&pci); self.colorBuffer = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageVertex = true, .bufferusageGraphicsStorageRead = true }, .size = 8192 * 2, .props = 0, }); self.colorBufferTransfer = self.device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = 8192 * 2, .props = 0, }); } pub fn loadShader( self: *@This(), shaderName: []const u8, loadArgs: ShaderLoadArgs, ) !*gpu.GPUShader { const contentPath = try std.fmt.allocPrint(self.allocator, "_shaders/{s}/{s}{s}", .{ self.shaderType, shaderName, self.shaderSuffix }); defer self.allocator.free(contentPath); var stage: gpu.GPUShaderStage = undefined; if (std.mem.endsWith(u8, shaderName, ".vert")) { stage = .shaderstageVertex; } else if (std.mem.endsWith(u8, shaderName, ".frag")) { stage = .shaderstageFragment; } else { return error.NotImplemented; } core.engine_log("creating shader {s} => {s} {any} args: {any}", .{ shaderName, contentPath, stage, loadArgs }); const mapping = try core.fs().loadFile(contentPath); defer core.fs().unmap(mapping); const sci = gpu.GPUShaderCreateInfo{ .code = @ptrCast(mapping.bytes.ptr), .entrypoint = @ptrCast(self.entrypoint.ptr), .format = self.shaderformat, .code_size = mapping.bytes.len - 1, .stage = stage, .num_samplers = loadArgs.num_samplers, .num_storage_textures = loadArgs.num_storage_textures, // The number of storage textures defined in the shader. .num_storage_buffers = loadArgs.num_storage_buffers, // The number of storage buffers defined in the shader. .num_uniform_buffers = loadArgs.num_uniform_buffers, // The number of uniform buffers defined in the shader. .props = 0, }; const rv = self.device.createGPUShader(&sci); return rv; } pub fn uploadUniforms(self: *@This(), cmd: *gpu.GPUCommandBuffer) !void { var data = std.mem.zeroes([@sizeOf(meshes_vert.Uniforms) / 8 + 1]usize); const ptr: *meshes_vert.Uniforms = @ptrCast(@alignCast(&data)); if (self.activeCamera) |camera| { ptr.ViewProjection = @bitCast(camera.final); } ptr.time = @floatCast(self.totalTime); cmd.pushGPUVertexUniformData(0, &data, @sizeOf(meshes_vert.Uniforms)); } pub fn frameUploads(self: *@This()) void { const cmd = self.device.acquireGPUCommandBuffer(); // const buffer = self.device.mapGPUTransferBuffer(self.colorBufferTransfer, true); // var b: [*][4]f32 = @ptrCast(@alignCast(buffer)); // b[0] = .{ @floatCast(std.math.sin(self.totalTime * 3 * 2 + 0.8) * 0.2 + 0.8), 0.4, 0.4, 1.0 }; // b[1] = .{ 0.4, @floatCast(std.math.sin(self.totalTime * 2 * 2 + 0.3) * 0.2 + 0.8), 0.4, 1.0 }; // b[2] = .{ 0.4, 0.4, @floatCast(std.math.sin(self.totalTime * 4 * 2) * 0.2 + 0.8), 1.0 }; // self.device.unmapGPUTransferBuffer(self.colorBufferTransfer); if (self.activeCamera) |camera| { camera.resolve(); } const copyPass = cmd.beginGPUCopyPass(); // copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.colorBufferTransfer, .offset = 0 }, &.{ // .buffer = self.colorBuffer, // .offset = 0, // .size = 4 * 12, // }, true); try self.uploadSSBOs(copyPass); for (self.uploads.items) |upload| { upload.func(upload.ptr, copyPass); } copyPass.endGPUCopyPass(); if (!cmd.submitGPUCommandBuffer()) { core.graphics_log("submit gpu command buffer SDL ERROR: {s}", .{sdl3.getError()}); } for (self.uploadCleanup.items) |uploadCleanup| { uploadCleanup.func(uploadCleanup.ptr); } } pub fn tick(self: *@This(), dt: f64) void { self.totalTime += dt; const cmd = self.device.acquireGPUCommandBuffer(); self.frameUploads(); try self.uploadUniforms(cmd); var swapchainTexture: *gpu.GPUTexture = undefined; if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, &swapchainTexture, null, null)) { var targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroes(gpu.GPUColorTargetInfo); targetInfo.texture = swapchainTexture; targetInfo.clear_color = .{ .r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0 }; targetInfo.load_op = .loadopClear; targetInfo.store_op = .storeopStore; var depthTarget = std.mem.zeroes(gpu.GPUDepthStencilTargetInfo); depthTarget.texture = self.depthTexture; depthTarget.load_op = .loadopClear; depthTarget.store_op = .storeopDontCare; depthTarget.stencil_load_op = .loadopDontCare; depthTarget.stencil_store_op = .storeopDontCare; depthTarget.clear_depth = 1.0; depthTarget.clear_stencil = 0; const renderpass = cmd.beginGPURenderPass(&targetInfo, 1, &depthTarget); //renderpass.bindGPUGraphicsPipeline(self.testPipeline); renderpass.bindGPUGraphicsPipeline(self.meshPipe); renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 1); renderpass.bindGPUVertexBuffers(0, &.{ .buffer = self.meshPool.vertexBuffer, .offset = 0 }, 1); renderpass.bindGPUIndexBuffer(&.{ .buffer = self.meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); renderpass.setGPUScissor(&self.scissor); //rendering each mesh { const container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); for (0..uploadCount) |i| { // core.engine_log("pushing draw {d}", .{i}); const component = &container.dense.items[i].value; if (component.mesh == null) { component.updateMesh(); } if (component.mesh) |mesh| { // core.engine_log("pushing draw for instance {d}", .{i}); renderpass.drawGPUIndexedPrimitives(mesh.index.size, 1, mesh.index.start, @intCast(mesh.vertex.start), @intCast(i)); } } } // renderpass.drawGPUPrimitives(3, 1, 0, 0); renderpass.endGPURenderPass(); } _ = cmd.submitGPUCommandBuffer(); } pub fn deinit(self: *@This()) void { // self.device.releaseGPUGraphicsPipeline(self.testPipeline); for (self.destroys.items) |d| { d.func(d.ptr); } self.uploadCleanup.deinit(self.allocator); self.uploads.deinit(self.allocator); self.destroys.deinit(self.allocator); self.allocator.destroy(self); } }; pub var gRenderer: *Renderer = undefined; pub var gAllocator: std.mem.Allocator = undefined; const rend = @import("../rend.zig"); const std = @import("std"); const core = @import("core"); const platform = @import("platform"); const sdl3 = @import("sdl3"); const gpu = sdl3.gpu; const meshes_vert = @import("meshes.vert"); const lit_mesh_frag = @import("lit_mesh.frag"); const sample_vert = @import("sample.vert"); const MeshVertices = meshes_vert.Scene; const MeshUniforms = meshes_vert.Uniforms; const ShaderLoadArgs = sdl3.shaderTypes.ShaderLoadArgs; pub const mesh_pool = @import("mesh-pool.zig"); pub const MeshPool = mesh_pool.MeshPool; // debug api pub fn reloadShaders() !void { _ = core.shell.runCmd(gRenderer.allocator, &.{ "python", "../tools/scripts/cookShaders.py" }, ".") catch { core.graphics_logs("shader cook script failed"); return; }; try gRenderer.createMeshPipeline(); } // ====== renderer API ======= pub fn createInstance() !void { gRenderer = try core.createObject(Renderer, .{ .can_tick = true, .isCore = true }); gAllocator = gRenderer.allocator; } pub fn start() !void { try gRenderer.startRenderer(); } pub fn shutdown() void {} pub fn context() *Renderer { return gRenderer; } pub fn setActiveCamera(camera: ?*rend.CameraComponent) void { gRenderer.activeCamera = camera; } pub const getMesh = mesh_pool.getMesh; pub const getMeshByName = mesh_pool.getMeshByName; pub const pushMeshUpdate = mesh_pool.pushMeshUpdate;