// 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 = undefined, // .{ .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 }) = .{}, postMesh: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCommandBuffer, *gpu.GPURenderPass) void }) = .{}, postRenders: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCommandBuffer) void }) = .{}, destroys: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, preDraws: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque, *gpu.GPUCommandBuffer) void }) = .{}, depthTexture: *gpu.GPUTexture = undefined, depthFormat: gpu.GPUTextureFormat = .textureformatD16Unorm, textureList: *TextureList = undefined, shadowMapProjection: core.Transform = core.zm.identity(), blockySampler: *gpu.GPUSampler = undefined, cubeSampler: *gpu.GPUSampler = undefined, defaultTexture: *rend.Texture = undefined, lightPosition: core.Vectorf = .{}, debugDrawSys: *DebugDrawSystem = undefined, shadowOrthoNear: f32 = -150, shadowOrthoFar: f32 = 150, directionalLightYaw: f32 = 0.0, directionalLightColor: core.colors.Color = .{ .r = 0.8, .g = 0.8, .b = 0.9 }, directionalLightDir: core.Vectorf = core.Vectorf.new(0.5, -0.5, 0.0).normalize(), shadowDepthFormat: gpu.GPUTextureFormat = .textureformatD16Unorm, shadowDepthTexture: *gpu.GPUTexture = undefined, shadowDepthDebugOutput: *gpu.GPUTexture = undefined, shadowCastingPipeline: *gpu.GPUGraphicsPipeline = undefined, shadowDepthTextureSlot: u32 = 3, skyboxSystem: *SkyboxSystem = undefined, // transients DO NOT TOUCH NORMALLY, just a way to let me make the renderer more modular and flexible for exploring // only valid when rendering state: RendererState = .{}, // swapchainTexture: ?*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 startRenderer(self: *@This()) !void { self.device = gpu.createGPUDevice(.{ .shaderformatSpirv = true, .shaderformatDxil = true, .shaderformatMsl = true, }, true, null); self.window = platform.getInstance().window; self.scissor = .{ .x = 0, .y = 0, .w = platform.getInstance().windowExtent.x, .h = platform.getInstance().windowExtent.y }; if (!self.device.claimWindowForGPUDevice(self.window)) return error.UnableToClaimGpu; try self.discoverFormats(); core.engine_log("sgpu device created, using shader format: {s}", .{self.shaderSuffix}); core.engine_log("using renderer... scientist", .{}); try self.createMeshPipeline(); try self.createBuffers(); try self.createDepthTexture(); self.meshPool = try self.createRendererObject(MeshPool); self.textureList = try self.createRendererEngineObject(TextureList); try self.createSamplers(); // todo... make these embedded try assets.load( assets.MakeImportRefOptions( "Texture", "t_default", .{ .path = "textures/texture_sample.png" }, ), ); try assets.load( assets.MakeImportRefOptions( "Mesh", "m_default_cube", .{ .path = "meshes/primitive_box.obj" }, ), ); var defaultTextureName = core.MakeName("t_default"); self.defaultTexture = getTexture(&defaultTextureName).?; self.debugDrawSys = try self.createRendererEngineObject(DebugDrawSystem); try self.createDirectionalShadowsPipeline(); self.skyboxSystem = try self.createRendererEngineObject(SkyboxSystem); _ = try core.fs().installFileBytesMount("embedded:plane.obj", @constCast(&plane_obj), true); try assets.load( assets.MakeImportRefOptions( "Mesh", "m_plane", .{ .path = "embedded:plane.obj" }, ), ); } pub fn createDirectionalShadowsPipeline(self: *@This()) !void { try self.createShadowDepthTexture(); try self.createShadowCastingPipeline(); } pub fn createSamplers(self: *@This()) !void { core.engine_log("creating blocky sampler", .{}); self.blockySampler = self.device.createGPUSampler(&std.mem.zeroInit(gpu.GPUSamplerCreateInfo, .{ .min_filter = .filterNearest, .mag_filter = .filterNearest, .mipmap_mode = .samplermipmapmodeNearest, .address_mode_u = .sampleraddressmodeRepeat, .address_mode_v = .sampleraddressmodeRepeat, .address_mode_w = .sampleraddressmodeRepeat, })); } // registers a pre-existing render object, does not add to destroys pub fn registerRendererObject(self: *@This(), T: type, object: *anyopaque) !void { if (@hasDecl(T, "onUpload")) { try self.uploads.append(self.allocator, .{ .ptr = object, .func = T.onUpload }); } if (@hasDecl(T, "onUploadCleanup")) { try self.uploadCleanup.append(self.allocator, .{ .ptr = object, .func = T.onUploadCleanup }); } if (@hasDecl(T, "postRender")) { try self.postRenders.append(self.allocator, .{ .ptr = object, .func = T.postRender }); } if (@hasDecl(T, "postMesh")) { try self.postMesh.append(self.allocator, .{ .ptr = object, .func = T.postMesh }); } if (@hasDecl(T, "onPreDraw")) { try self.preDraws.append(self.allocator, .{ .ptr = object, .func = T.onPreDraw }); } } pub fn createRendererEngineObject(self: *@This(), T: type) !*T { const object = try core.createObject(T, .{}); try object.setup(self.device); try self.registerRendererObject(T, object); return object; } pub fn createRendererObject(self: *@This(), T: type) !*T { const object = try T.create(self.device, self.allocator, .{}); try self.registerRendererObject(T, object); try self.destroys.append(self.allocator, .{ .ptr = object, .func = T.destroy }); return object; } fn createShadowDepthTexture(self: *@This()) !void { var gci = std.mem.zeroes(gpu.GPUTextureCreateInfo); const shadowMapRes = core.configVar(u32, "renderer.shadowmap.resolution", 2048); gci.type = .texturetype2d; gci.format = self.shadowDepthFormat; gci.width = shadowMapRes; gci.height = shadowMapRes; gci.layer_count_or_depth = 1; gci.num_levels = 1; gci.sample_count = .samplecount1; gci.usage = .{ .textureusageDepthStencilTarget = true, .textureusageSampler = true }; self.shadowDepthTexture = self.device.createGPUTexture(&gci); } fn createDepthTexture(self: *@This()) !void { var gci = std.mem.zeroes(gpu.GPUTextureCreateInfo); gci.type = .texturetype2d; gci.format = self.depthFormat; 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 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 generateVertexAttributeList(self: *@This()) !std.ArrayList(gpu.GPUVertexAttribute) { var list = std.ArrayList(gpu.GPUVertexAttribute).init(self.allocator); var offset: u32 = 0; { try addAttribute(&list, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(&list, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(&list, &offset, @sizeOf(f32) * 4, .vertexelementformatFloat4); try addAttribute(&list, &offset, @sizeOf(f32) * 2, .vertexelementformatFloat2); try addAttribute(&list, &offset, @sizeOf(u32), .vertexelementformatUint); } return list; } pub fn createShadowCastingPipeline(self: *@This()) !void { const vertex = try self.loadShader("meshes.vert", meshes_vert.LoadArgs); const fragment = try self.loadShader("depthOnly.frag", depthOnly.LoadArgs); var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo); pci.vertex_shader = vertex; pci.fragment_shader = fragment; var attributes = try self.generateVertexAttributeList(); defer attributes.deinit(); 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 = self.shadowDepthFormat; pci.target_info.num_color_targets = 0; // no color targets pci.rasterizer_state.cull_mode = .cullmodeNone; pci.rasterizer_state.fill_mode = .fillmodeFill; pci.rasterizer_state.enable_depth_bias = true; self.shadowCastingPipeline = self.device.createGPUGraphicsPipeline(&pci); } 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 = try self.generateVertexAttributeList(); defer attributes.deinit(); 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 = self.depthFormat; 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); } pub fn createBuffers(self: *@This()) !void { // 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 container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); { const uploadMapped: [*]meshes_vert.Scene = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(self.ssboSceneUpload, true))); defer self.device.unmapGPUTransferBuffer(self.ssboSceneUpload); // iterate over MeshComponents 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); uploadMapped[i].textureMode = @bitCast(object.textureMode); } } 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 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.ShadowMapProjection = @bitCast(self.shadowMapProjection); // ptr.ViewProjection = @bitCast(self.shadowMapProjection); ptr.time = @floatCast(self.totalTime); cmd.pushGPUVertexUniformData(0, &data, @sizeOf(meshes_vert.Uniforms)); } { var data = std.mem.zeroes([@sizeOf(lit_mesh_frag.Uniforms) / 8 + 1]usize); const ptr: *lit_mesh_frag.Uniforms = @ptrCast(@alignCast(&data)); ptr.time = @floatCast(self.totalTime); var position: core.Vectorf = .{}; if (self.activeCamera) |cam| { position = cam.finalPos; } const resolved = core.Rotation.eulerY(core.radians(self.directionalLightYaw)).rotateVector(self.directionalLightDir.fmul(-1)); ptr.screenSize = @bitCast(platform.context().extent); ptr.viewPos = @bitCast(position.toZm()); ptr.lightPosition = @bitCast(self.lightPosition.toZm()); ptr.directionalLight = @bitCast(resolved.toZm()); ptr.directionalLightColor = @bitCast(self.directionalLightColor); cmd.pushGPUFragmentUniformData(0, &data, @sizeOf(lit_mesh_frag.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; self.state.cmd = self.device.acquireGPUCommandBuffer(); self.frameUploads(); // mesh pre-iteration const container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); for (0..uploadCount) |i| { const component = &container.dense.items[i].value; if (component.mesh == null) { component.updateMesh(); } if (component.texture == null) { component.updateTexture(); } } for (self.preDraws.items) |interface| { interface.func(interface.ptr, self.state.cmd.?); } self.draw(); } // orthographic view projection from the sun towards the center of the thing pub fn shadowMapOrtho(self: *@This()) core.Transform { const ortho = core.zm.orthographicLh(200, 200, self.shadowOrthoNear, self.shadowOrthoFar); var rotation = core.zm.lookAtLh( .{ 0, 0, 0, 1 }, self.directionalLightDir.toZm(), .{ 0, 1, 0, 1 }, ); rotation = core.zm.mul(core.zm.rotationY(core.radians(self.directionalLightYaw)), rotation); return core.zm.mul(rotation, ortho); } pub fn drawDirectionalShadowMap(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)); self.shadowMapProjection = self.shadowMapOrtho(); ptr.ViewProjection = @bitCast(self.shadowMapProjection); cmd.pushGPUVertexUniformData(0, &data, @sizeOf(meshes_vert.Uniforms)); } // can cache this const depthTarget = std.mem.zeroInit(gpu.GPUDepthStencilTargetInfo, .{ .texture = self.shadowDepthTexture, .load_op = .loadopClear, .store_op = .storeopStore, .stencil_load_op = .loadopDontCare, .stencil_store_op = .storeopDontCare, .clear_depth = 1.0, .clear_stencil = 0, }); const renderpass = cmd.beginGPURenderPass(null, 0, &depthTarget); renderpass.bindGPUGraphicsPipeline(self.shadowCastingPipeline); 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); const container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); for (0..uploadCount) |i| { const component = &container.dense.items[i].value; if (component.mesh) |mesh| { renderpass.drawGPUIndexedPrimitives(mesh.index.size, 1, mesh.index.start, @intCast(mesh.vertex.start), @intCast(i)); } } renderpass.endGPURenderPass(); } pub fn draw(self: *@This()) void { const cmd = self.state.cmd.?; if (cmd.waitAndAcquireGPUSwapchainTexture(self.window, @ptrCast(&self.state.swapchainTexture), null, null)) { if (self.state.swapchainTexture == null) { _ = cmd.submitGPUCommandBuffer(); return; } // preMeshPasses self.drawDirectionalShadowMap(cmd); // // draw skybox { self.skyboxSystem.render(cmd); } // can cache this const targetInfo: gpu.GPUColorTargetInfo = std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.swapchainTexture.?, .load_op = .loadopLoad, .store_op = .storeopStore, }); const depthTarget = std.mem.zeroInit(gpu.GPUDepthStencilTargetInfo, .{ .texture = self.depthTexture, .load_op = .loadopClear, .store_op = .storeopDontCare, .stencil_load_op = .loadopDontCare, .stencil_store_op = .storeopDontCare, .clear_depth = 1.0, .clear_stencil = 0, }); self.state.pass = cmd.beginGPURenderPass(&targetInfo, 1, &depthTarget); const renderpass = self.state.pass.?; //renderpass.bindGPUGraphicsPipeline(self.testPipeline); renderpass.bindGPUFragmentSamplers(1, &.{ .sampler = self.blockySampler, .texture = self.defaultTexture.texture }, 1); renderpass.bindGPUFragmentSamplers(2, &.{ .sampler = self.blockySampler, .texture = self.defaultTexture.texture }, 1); renderpass.bindGPUVertexStorageBuffers(0, &self.ssboScene, 1); renderpass.bindGPUFragmentStorageBuffers(0, &self.ssboScene, 1); renderpass.bindGPUVertexBuffers(0, &.{ .buffer = self.meshPool.vertexBuffer, .offset = 0 }, 1); renderpass.bindGPUIndexBuffer(&.{ .buffer = self.meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); // render the meshes renderpass.bindGPUGraphicsPipeline(self.meshPipe); renderpass.bindGPUFragmentSamplers(self.shadowDepthTextureSlot, &.{ .texture = self.shadowDepthTexture, .sampler = self.blockySampler }, 1); // todo move into materials system renderpass.setGPUScissor(&self.scissor); try self.uploadUniforms(cmd); //rendering each mesh { // placeholder textures const container = rend.MeshComponent.BaseContainer; const uploadCount: usize = @min(container.dense.items.len, MaxObjectCount); for (0..uploadCount) |i| { const component = &container.dense.items[i].value; var t = component.texture; if (t == null) { t = self.defaultTexture; } if (component.mesh) |mesh| { renderpass.bindGPUFragmentSamplers(0, &.{ .texture = t.?.texture, .sampler = self.blockySampler }, 1); if (component.cmrf) |cmrf| { cmrf(component.cmrf_ctx); } renderpass.drawGPUIndexedPrimitives(mesh.index.size, 1, mesh.index.start, @intCast(mesh.vertex.start), @intCast(i)); } else {} } } for (self.postMesh.items) |interface| { interface.func(interface.ptr, cmd, renderpass); } renderpass.endGPURenderPass(); for (self.postRenders.items) |interface| { interface.func(interface.ptr, cmd); } // renderpass.drawGPUPrimitives(3, 1, 0, 0); } _ = cmd.submitGPUCommandBuffer(); } pub fn deinit(self: *@This()) void { // self.device.releaseGPUGraphicsPipeline(self.testPipeline); for (self.destroys.items) |d| { d.func(d.ptr); } self.postMesh.deinit(self.allocator); self.uploadCleanup.deinit(self.allocator); self.preDraws.deinit(self.allocator); self.postRenders.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 assets = @import("assets"); const core = @import("core"); const platform = @import("platform"); const sdl3 = @import("sdl3"); pub const gpu = sdl3.gpu; const meshes_vert = @import("meshes.vert"); const lit_mesh_frag = @import("lit_mesh.frag"); const depthOnly = @import("depthOnly.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; pub const TextureList = @import("TextureList.zig"); const DebugDrawSystem = @import("DebugDrawSystem.zig"); const SkyboxSystem = @import("SkyboxSystem.zig"); // 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 fn createRendererObject(comptime T: type) !*T { return try gRenderer.createRendererObject(T); } pub fn registerRendererObject(comptime T: type, object: *anyopaque) !void { return try gRenderer.registerRendererObject(T, object); } pub fn setSkyboxTexture(name: []const u8) void { gRenderer.skyboxSystem.skyboxTextureName = core.MakeName(name); } pub const getMesh = mesh_pool.getMesh; pub const getMeshByName = mesh_pool.getMeshByName; pub const pushMeshUpdate = mesh_pool.pushMeshUpdate; pub fn getTexture(name: *core.Name) ?*rend.Texture { return gRenderer.textureList.map.get(name.handle()); } pub const RendererState = struct { copyPass: ?*gpu.GPURenderPass = null, pass: ?*gpu.GPURenderPass = null, cmd: ?*gpu.GPUCommandBuffer = null, swapchainTexture: ?*gpu.GPUTexture = null, }; pub const CustomMeshRenderFunc = *const fn (?*anyopaque) void; pub const GPUTextureType = gpu.GPUTexture; const plane_obj align(8) = @embedFile("embedded/plane.obj").*;