// 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, opaquePipe: *gpu.GPUGraphicsPipeline = undefined, transparentPipe: *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, ssboAnimation: *gpu.GPUBuffer = undefined, ssboAnimationUpload: *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 }) = .{}, shaderReloads: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, shaderReloadFinished: std.ArrayListUnmanaged(struct { ptr: *anyopaque, func: *const fn (*anyopaque) void }) = .{}, depthTexture: *gpu.GPUTexture = undefined, depthFormat: gpu.GPUTextureFormat = .textureformatD32Float, transparentDepthTexture: *gpu.GPUTexture = undefined, textureList: *TextureList = undefined, shadowMapProjection: core.Transform = core.zm.identity(), blockySampler: *gpu.GPUSampler = undefined, linearSampler: *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 = .textureformatD32Float, 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, swapchainTargetFormat: gpu.GPUTextureFormat = undefined, hdrTextureFormat: gpu.GPUTextureFormat = undefined, postProcessingPipeline: *gpu.GPUGraphicsPipeline = undefined, screenPlane: ?rend.IndexedMesh = null, lightPower: f32 = 4.0, particleRenderer: *ParticleRenderer = undefined, ssaoSystem: *SsaoSystem = undefined, positionTargetFormat: gpu.GPUTextureFormat = .textureformatR16g16b16a16Float, normalTargetFormat: gpu.GPUTextureFormat = .textureformatR16g16b16a16Float, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "rend.Renderer"); pub const MaxObjectCount = 50000; pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, }; self.hdrTextureFormat = .textureformatR16g16b16a16Float; return self; } pub fn imageExtents(self: @This()) core.Vectorf { _ = self; return .{ .x = @floatFromInt(platform.getInstance().windowExtent.x), .y = @floatFromInt(platform.getInstance().windowExtent.y), }; } pub fn reloadPluginShaders(self: *@This()) void { for (self.shaderReloads.items) |interface| { interface.func(interface.ptr); } } pub fn reloadPluginShadersFinished(self: *@This()) void { for (self.shaderReloadFinished.items) |interface| { interface.func(interface.ptr); } } pub fn createRenderTargets(self: *@This()) !void { var gci = std.mem.zeroes(gpu.GPUTextureCreateInfo); gci.type = .texturetype2d; gci.format = self.hdrTextureFormat; gci.width = @intCast(platform.getInstance().windowExtent.x); gci.height = @intCast(platform.getInstance().windowExtent.y); gci.layer_count_or_depth = 1; gci.num_levels = 1; gci.sample_count = .samplecount1; gci.usage = .{ .textureusageSampler = true, .textureusageColorTarget = true, }; self.state.targetTexture = self.device.createGPUTexture(&gci); self.state.emissiveTarget = self.device.createGPUTexture(&gci); gci.format = self.positionTargetFormat; self.state.positionTarget = self.device.createGPUTexture(&gci); gci.format = self.normalTargetFormat; self.state.normalTarget = self.device.createGPUTexture(&gci); self.swapchainTargetFormat = self.device.getGPUSwapchainTextureFormat(self.window); core.graphics_log("swapchain format: {any}", .{self.swapchainTargetFormat}); } pub fn startRenderer(self: *@This()) !void { self.device = gpu.createGPUDevice(.{ .shaderformatSpirv = true, .shaderformatDxil = true, .shaderformatMsl = true, }, builtin.mode == .Debug, 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.createOpaquePipeline(); try self.createTransparentsPipeline(); try self.createBuffers(); try self.createDepthTexture(); try self.createRenderTargets(); try self.createPostProcessingPipeline(); self.meshPool = try self.createRendererObject(MeshPool); self.textureList = try self.createRendererEngineObject(TextureList); try self.createSamplers(); _ = try core.fs().installFileBytesMount("embedded:texture_sample.png", @constCast(&texture_sample_png), true); _ = try core.fs().installFileBytesMount("embedded:texture_white.png", @constCast(&texture_white_png), true); _ = try core.fs().installFileBytesMount("embedded:primitive_box.obj", @constCast(&primitive_box_obj), true); // todo... make these embedded try assets.load( assets.MakeImportRefOptions( "Texture", "t_default", .{ .path = "embedded:texture_sample.png" }, ), ); try assets.load( assets.MakeImportRefOptions( "Texture", "t_white", .{ .path = "embedded:texture_white.png" }, ), ); try assets.load( assets.MakeImportRefOptions( "Mesh", "m_default_cube", .{ .path = "embedded:primitive_box.obj" }, ), ); var defaultTextureName = core.MakeName("t_default"); self.defaultTexture = getTexture(&defaultTextureName).?; try self.createDirectionalShadowsPipeline(); self.skyboxSystem = try self.createRendererEngineObject(SkyboxSystem); self.particleRenderer = try self.createRendererEngineObject(ParticleRenderer); self.debugDrawSys = try self.createRendererEngineObject(DebugDrawSystem); _ = try core.fs().installFileBytesMount("embedded:plane.obj", @constCast(&plane_obj), true); _ = try core.fs().installFileBytesMount("embedded:screenPlane.obj", @constCast(&screenPlane_obj), true); try assets.load( assets.MakeImportRefOptions( "Mesh", "m_plane", .{ .path = "embedded:plane.obj" }, ), ); try assets.load( assets.MakeImportRefOptions( "Mesh", "m_screenPlane", .{ .path = "embedded:screenPlane.obj" }, ), ); self.ssaoSystem = try self.createRendererEngineObject(SsaoSystem); } pub fn addVertexAttributes(self: *@This(), pci: *gpu.GPUGraphicsPipelineCreateInfo) !std.ArrayList(gpu.GPUVertexAttribute) { _ = self; const attributes = try addVertexAttributesFromStruct(rend.MeshVertex, pci); 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), }; return attributes; } pub fn createPostProcessingPipeline(self: *@This()) !void { const vertex = try self.loadShader("postProc.vert", postProcVert.LoadArgs); const fragment = try self.loadShader("postProc.frag", postProcFrag.LoadArgs); var pci = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo); pci.vertex_shader = vertex; pci.fragment_shader = fragment; //return rend.renderer.addVertexAttributesFromStruct(TextMeshVertex, pci); //var attributes = try self.generateVertexAttributeList(); var attributes = try addVertexAttributesFromStruct(rend.MeshVertex, &pci); defer attributes.deinit(self.allocator); 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), }; core.graphics_log("hdr texture format format: {any}", .{self.hdrTextureFormat}); pci.target_info.num_color_targets = 1; pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{ .{ .format = self.swapchainTargetFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, }; pci.rasterizer_state.cull_mode = .cullmodeNone; pci.rasterizer_state.fill_mode = .fillmodeFill; self.postProcessingPipeline = self.device.createGPUGraphicsPipeline(&pci); } 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, })); self.linearSampler = self.device.createGPUSampler(&std.mem.zeroInit(gpu.GPUSamplerCreateInfo, .{ .min_filter = .filterLinear, .mag_filter = .filterLinear, .mipmap_mode = .samplermipmapmodeNearest, .compare_op = .compareopNever, // .address_mode_u = .sampleraddressmodeMirroredRepeat, // .address_mode_v = .sampleraddressmodeMirroredRepeat, // .address_mode_w = .sampleraddressmodeMirroredRepeat, // .address_mode_u = .sampleraddressmodeRepeat, // .address_mode_v = .sampleraddressmodeRepeat, // .address_mode_w = .sampleraddressmodeRepeat, // .mip_lod_bias = 0.0, .min_lod = 0, // Clamps the minimum of the computed LOD value. .max_lod = 10, // Clamps the maximum of the computed LOD value. .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 }); } if (@hasDecl(T, "onShaderReload")) { try self.shaderReloads.append(self.allocator, .{ .ptr = object, .func = T.onShaderReload }); try self.shaderReloadFinished.append(self.allocator, .{ .ptr = object, .func = T.onShaderReloadFinished }); } } pub fn createRendererEngineObject(self: *@This(), T: type) !*T { const object = try core.createObject(T, .{}); if (@hasDecl(T, "setup")) { 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); self.transparentDepthTexture = 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 }; } } pub 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 generateVertexAttributeList_deprecated(self: *@This()) !std.ArrayList(gpu.GPUVertexAttribute) { var list = std.ArrayList(gpu.GPUVertexAttribute){}; var offset: u32 = 0; { try addAttribute(self.allocator, &list, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(self.allocator, &list, &offset, @sizeOf(f32) * 3, .vertexelementformatFloat3); try addAttribute(self.allocator, &list, &offset, @sizeOf(f32) * 4, .vertexelementformatFloat4); try addAttribute(self.allocator, &list, &offset, @sizeOf(f32) * 2, .vertexelementformatFloat2); try addAttribute(self.allocator, &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(); var attributes = try self.addVertexAttributes(&pci); defer attributes.deinit(self.allocator); 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); } const CreatePipelineInfo = struct { allocator: std.mem.Allocator, attributes: ?std.ArrayList(gpu.GPUVertexAttribute) = null, pci: gpu.GPUGraphicsPipelineCreateInfo = std.mem.zeroes(gpu.GPUGraphicsPipelineCreateInfo), vertexBuffers: std.ArrayList(gpu.GPUVertexBufferDescription) = .{}, colorTargets: std.ArrayList(gpu.GPUColorTargetDescription) = .{}, pub fn init(a: std.mem.Allocator) @This() { return .{ .allocator = a }; } pub fn addMeshPci(self: *@This(), vertexShader: []const u8, fragmentShader: []const u8) !void { const ctx = context(); const pci = &self.pci; const vertex = try ctx.loadShader(vertexShader, meshes_vert.LoadArgs); const fragment = try ctx.loadShader(fragmentShader, lit_mesh_frag.LoadArgs); pci.vertex_shader = vertex; pci.fragment_shader = fragment; self.attributes = try addVertexAttributesFromStruct(rend.MeshVertex, pci); try self.vertexBuffers.appendSlice(ctx.allocator, &[_]gpu.GPUVertexBufferDescription{ .{ .slot = 0, .pitch = @sizeOf(rend.MeshVertex), .input_rate = .vertexinputrateVertex, .instance_step_rate = 0, }, }); pci.vertex_input_state = .{ .num_vertex_buffers = 1, .vertex_buffer_descriptions = self.vertexBuffers.items.ptr, .num_vertex_attributes = @intCast(self.attributes.?.items.len), .vertex_attributes = @ptrCast(self.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 = ctx.depthFormat; pci.target_info.num_color_targets = 4; try self.colorTargets.appendSlice(ctx.allocator, &[4]gpu.GPUColorTargetDescription{ .{ .format = ctx.hdrTextureFormat, //.blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), .blend_state = std.mem.zeroInit(gpu.GPUColorTargetBlendState, .{ .alpha_blend_op = .blendopAdd, .color_blend_op = .blendopAdd, .src_color_blendfactor = .blendfactorSrcAlpha, .dst_color_blendfactor = .blendfactorOneMinusSrcAlpha, .src_alpha_blendfactor = .blendfactorSrcAlpha, .dst_alpha_blendfactor = .blendfactorOneMinusSrcAlpha, .enable_blend = true, }), }, .{ .format = ctx.hdrTextureFormat, // .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), .blend_state = std.mem.zeroInit(gpu.GPUColorTargetBlendState, .{ .alpha_blend_op = .blendopAdd, .color_blend_op = .blendopAdd, .src_color_blendfactor = .blendfactorSrcAlpha, .dst_color_blendfactor = .blendfactorOneMinusSrcAlpha, .src_alpha_blendfactor = .blendfactorSrcAlpha, .dst_alpha_blendfactor = .blendfactorOneMinusSrcAlpha, .enable_blend = true, }), }, .{ .format = ctx.positionTargetFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, .{ .format = ctx.normalTargetFormat, .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), }, }); pci.target_info.color_target_descriptions = self.colorTargets.items.ptr; pci.rasterizer_state.fill_mode = .fillmodeFill; pci.rasterizer_state.cull_mode = .cullmodeBack; pci.rasterizer_state.front_face = .frontfaceClockwise; } pub fn deinit(self: *@This()) void { if (self.attributes) |*a| { a.deinit(self.allocator); } self.vertexBuffers.deinit(context().allocator); self.colorTargets.deinit(context().allocator); } }; pub fn createTransparentsPipeline(self: *@This()) !void { var createInfo = CreatePipelineInfo.init(self.allocator); try createInfo.addMeshPci("meshes.vert", "lit_mesh.frag"); defer createInfo.deinit(); createInfo.pci.depth_stencil_state.enable_depth_write = false; self.transparentPipe = self.device.createGPUGraphicsPipeline(&createInfo.pci); } pub fn createOpaquePipeline(self: *@This()) !void { var createInfo = CreatePipelineInfo.init(self.allocator); try createInfo.addMeshPci("meshes.vert", "lit_mesh.frag"); defer createInfo.deinit(); self.opaquePipe = self.device.createGPUGraphicsPipeline(&createInfo.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 = createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(meshes_vert.Scene), .props = 0, }); self.ssboAnimation = self.device.createGPUBuffer(&.{ .usage = .{ .bufferusageGraphicsStorageRead = true, .bufferusageVertex = true, }, .size = MaxObjectCount * @sizeOf(meshes_vert.BoneTransform), .props = 0, }); self.ssboAnimationUpload = createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(meshes_vert.BoneTransform), .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 (container.dense.items[i].value.animator) |animator| { uploadMapped[i].animation = @intCast(animator.finalsSpan.start); } else { uploadMapped[i].animation = -1; } } } var animationHighest: u32 = 0; // upload animation buffers { const animationMapped: [*]meshes_vert.BoneTransform = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(self.ssboAnimationUpload, true))); defer self.device.unmapGPUTransferBuffer(self.ssboAnimationUpload); // const as = animationSystem.getAnimationSystem(); for (animationSystem.Animator.BaseContainer.list.items) |animator| { const span = animator.finalsSpan; for (0..span.size) |i| { animationMapped[span.start + i].final = animator.finals.items[i]; } if (span.start + span.size > animationHighest) animationHighest = span.start + span.size; } } self.particleRenderer.uploadSsbos(copyPass) catch unreachable; 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); if (animationHighest == 0) return; copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.ssboAnimationUpload, .offset = 0 }, &.{ .buffer = self.ssboAnimation, .offset = 0, .size = @intCast(animationHighest * @sizeOf(meshes_vert.BoneTransform)), }, 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); // fuck... the mapping bytes really needs some work... const sci = gpu.GPUShaderCreateInfo{ .code = @ptrCast(mapping.bytesNoEnd().ptr), .entrypoint = @ptrCast(self.entrypoint.ptr), .format = self.shaderformat, .code_size = mapping.bytesNoEnd().len, .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.ViewTransform = @bitCast(camera.transform); ptr.ViewProjection = @bitCast(camera.final); ptr.NoTranslateView = @bitCast(camera.noTranslate); ptr.cameraPosition = @bitCast(camera.finalPos.toZm()); } 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); ptr.lightPower = self.lightPower; 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); 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 { var z = tracy.ZoneN(@src(), "renderer tick"); defer z.End(); self.totalTime += dt; var z2 = tracy.ZoneN(@src(), "Uploads and Updates"); 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 or self.meshPool.isMeshInvalidated(&component.meshName)) { component.updateMesh(); } if (component.texture == null) { component.updateTexture(); } } self.meshPool.clearInvalidations(); for (self.preDraws.items) |interface| { interface.func(interface.ptr, self.state.cmd.?); } z2.End(); var z1 = tracy.ZoneN(@src(), "draw"); self.draw(); z1.End(); } // 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 z = tracy.ZoneN(@src(), "drawDirectionalShadowMap"); defer z.End(); { 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.bindGPUVertexStorageBuffers(1, &self.ssboAnimation, 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(); } var meshRenderPassTargetInfo: ?[4]gpu.GPUColorTargetInfo = null; pub fn beginMeshRenderPass(self: *@This(), cmd: *gpu.GPUCommandBuffer, pipeline: *gpu.GPUGraphicsPipeline, clearDepth: bool) *gpu.GPURenderPass { if (meshRenderPassTargetInfo == null) { meshRenderPassTargetInfo = .{ std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.targetTexture, .load_op = .loadopLoad, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.emissiveTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.positionTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.normalTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), }; } const targetInfo = meshRenderPassTargetInfo.?; var depthTarget = std.mem.zeroInit(gpu.GPUDepthStencilTargetInfo, .{ .texture = self.depthTexture, .load_op = .loadopLoad, .store_op = .storeopStore, .stencil_load_op = .loadopDontCare, .stencil_store_op = .storeopDontCare, .clear_depth = 1.0, .clear_stencil = 0, }); if (clearDepth) { depthTarget.load_op = .loadopClear; } self.state.pass = cmd.beginGPURenderPass(&targetInfo, 4, &depthTarget); const renderpass = self.state.pass.?; renderpass.setGPUScissor(&self.scissor); //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.bindGPUVertexStorageBuffers(1, &self.ssboAnimation, 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); renderpass.bindGPUGraphicsPipeline(pipeline); renderpass.bindGPUFragmentSamplers(self.shadowDepthTextureSlot, &.{ .texture = self.shadowDepthTexture, .sampler = self.blockySampler }, 1); return renderpass; } pub fn drawOpaques(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { const renderpass = self.beginMeshRenderPass(cmd, self.opaquePipe, true); //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| { // TODO put the sampler onto the texture itself if (t) |_t| { var sampler: *gpu.GPUSampler = undefined; switch (_t.samplerMode) { .blocky => { sampler = self.blockySampler; }, .linear => { sampler = self.linearSampler; }, } renderpass.bindGPUFragmentSamplers(0, &.{ .texture = _t.texture, .sampler = sampler }, 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 {} } } renderpass.endGPURenderPass(); self.state.pass = null; } pub fn drawTransparents(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { const renderpass = self.beginMeshRenderPass(cmd, self.transparentPipe, false); for (self.postMesh.items) |interface| { interface.func(interface.ptr, cmd, renderpass); } renderpass.endGPURenderPass(); self.state.pass = null; } pub fn drawMeshes(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { // can cache this try self.uploadUniforms(cmd); self.drawOpaques(cmd); self.drawTransparents(cmd); } pub fn renderTransparents(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { const targetInfo: [4]gpu.GPUColorTargetInfo = .{ std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.targetTexture, .load_op = .loadopLoad, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.emissiveTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.positionTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.normalTarget, .load_op = .loadopClear, .store_op = .storeopStore, }), }; const depthTarget = std.mem.zeroInit(gpu.GPUDepthStencilTargetInfo, .{ .texture = self.transparentDepthTexture, .load_op = .loadopDontCare, .store_op = .storeopDontCare, .stencil_load_op = .loadopDontCare, .stencil_store_op = .storeopDontCare, .clear_depth = 1.0, .clear_stencil = 0, }); const renderpass = cmd.beginGPURenderPass(&targetInfo, 4, &depthTarget); renderpass.endGPURenderPass(); } pub fn drawPostProcess(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { const postProcTargetInfo: gpu.GPUColorTargetInfo = std.mem.zeroInit(gpu.GPUColorTargetInfo, .{ .texture = self.state.swapchainTargetTexture.?, .load_op = .loadopClear, .store_op = .storeopStore, }); const postProcPass = cmd.beginGPURenderPass(&postProcTargetInfo, 1, null); postProcPass.bindGPUGraphicsPipeline(self.postProcessingPipeline); postProcPass.bindGPUFragmentSamplers(0, &[3]gpu.GPUTextureSamplerBinding{ .{ .sampler = self.blockySampler, .texture = self.state.targetTexture }, .{ .sampler = self.linearSampler, .texture = self.state.emissiveTarget }, .{ .sampler = self.linearSampler, .texture = self.ssaoSystem.ssaoTexture }, }, 3); postProcPass.bindGPUVertexBuffers(0, &.{ .buffer = self.meshPool.vertexBuffer, .offset = 0 }, 1); postProcPass.bindGPUIndexBuffer(&.{ .buffer = self.meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); // postProcPass.bindGPUFragmentSamplers(1, &.{ .sampler = self.blockySampler, .texture = self.state.emissiveTarget }, 1); // post processing + hdr etc... if (self.screenPlane == null) { self.screenPlane = getMesh("m_screenPlane"); } if (self.screenPlane) |mesh| { postProcPass.drawGPUIndexedPrimitives(mesh.index.size, 1, mesh.index.start, @intCast(mesh.vertex.start), 0); } postProcPass.endGPURenderPass(); } pub fn draw(self: *@This()) void { const cmd = self.state.cmd.?; var z = tracy.ZoneN(@src(), "Acquiring Next frame"); const shouldDraw = cmd.waitAndAcquireGPUSwapchainTexture(self.window, @ptrCast(&self.state.swapchainTargetTexture), null, null); z.End(); if (shouldDraw) { if (self.state.swapchainTargetTexture == null) { _ = cmd.submitGPUCommandBuffer(); return; } // preMeshPasses self.drawDirectionalShadowMap(cmd); self.skyboxSystem.render(cmd); self.drawMeshes(cmd); self.ssaoSystem.render(cmd); self.drawPostProcess(cmd); for (self.postRenders.items) |interface| { interface.func(interface.ptr, cmd); } } _ = 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.shaderReloads.deinit(self.allocator); self.shaderReloadFinished.deinit(self.allocator); self.uploads.deinit(self.allocator); self.destroys.deinit(self.allocator); self.allocator.destroy(self); } }; // pub var gRenderer: *Renderer = undefined; pub const context = core.EngineObject(Renderer).get; 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 postProcVert = @import("postProc.vert"); const postProcFrag = @import("postProc.frag"); 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 { if (core.BuildOption("static_build")) { core.graphics_logs("skipping shader hotreload, not available in static build"); return; } // _ = core.shell.runCmd(context().allocator, &.{ "python", "../tools/scripts/cookShaders.py" }, ".") catch { // core.graphics_logs("shader cook script failed"); // return; // }; try sys.start_CompileShaders(null, reloadShadersFinished); context().reloadPluginShaders(); } pub fn reloadShadersFinished(ctx: ?*anyopaque) void { _ = ctx; context().createOpaquePipeline() catch { core.engine_errs("unable to rebuild pipeline"); }; context().createTransparentsPipeline() catch { core.engine_errs("unable to rebuild pipeline"); }; context().createPostProcessingPipeline() catch { core.engine_errs("unable to rebuild pipeline"); }; context().ssaoSystem.createPipeline() catch { core.engine_errs("unable to rebuild pipeline"); }; context().reloadPluginShadersFinished(); } // ====== renderer API ======= pub fn createInstance() !void { _ = try core.createObject(Renderer, .{ .can_tick = true, .isCore = true }); } pub fn start() !void { try context().startRenderer(); } pub fn shutdown() void {} pub fn setActiveCamera(camera: ?*rend.CameraComponent) void { context().activeCamera = camera; } pub fn createRendererObject(comptime T: type) !*T { return try context().createRendererObject(T); } pub fn registerRendererObject(comptime T: type, object: *anyopaque) !void { return try context().registerRendererObject(T, object); } pub fn setSkyboxTexture(name: []const u8) void { context().skyboxSystem.skyboxTextureName = core.MakeName(name); context().skyboxSystem.skyboxTexture = null; } 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 context().textureList.map.get(name.handle()); } pub fn createGPUTransferBuffer(createInfo: *const gpu.GPUTransferBufferCreateInfo) *gpu.GPUTransferBuffer { core.MemoryTracker.MTAddUntrackedAllocation(@intCast(createInfo.size)); return context().device.createGPUTransferBuffer(createInfo); } pub fn releaseGPUTransferBuffer(transferBuffer: *gpu.GPUTransferBuffer, size: usize) void { core.MemoryTracker.MTRemoveAllocation(size); context().device.releaseGPUTransferBuffer(transferBuffer); } pub const RendererState = struct { copyPass: ?*gpu.GPURenderPass = null, pass: ?*gpu.GPURenderPass = null, cmd: ?*gpu.GPUCommandBuffer = null, swapchainTargetTexture: ?*gpu.GPUTexture = null, targetTexture: *gpu.GPUTexture = undefined, emissiveTarget: *gpu.GPUTexture = undefined, positionTarget: *gpu.GPUTexture = undefined, normalTarget: *gpu.GPUTexture = undefined, }; pub fn textureExists(name: []const u8) bool { var n = core.MakeName(name); return context().textureList.requestMap.contains(n.handle()); } pub const addVertexAttributesFromStruct = @import("vertexAttributes.zig").addVertexAttributesFromStruct; pub const CustomMeshRenderFunc = *const fn (?*anyopaque) void; pub const GPUTextureType = gpu.GPUTexture; const plane_obj align(8) = @embedFile("embedded/plane.obj").*; const screenPlane_obj align(8) = @embedFile("embedded/screenPlane.obj").*; const texture_sample_png align(8) = @embedFile("embedded/texture_sample.png").*; const texture_white_png align(8) = @embedFile("embedded/texture_white.png").*; const primitive_box_obj align(8) = @embedFile("embedded/primitive_box.obj").*; const tracy = core.tracy; const animationSystem = rend.animationSystem; const sys = @import("sys"); const builtin = @import("builtin"); pub const SsaoSystem = @import("ssao.zig"); pub const ParticleRenderer = @import("ParticleRenderer.zig");