Backlog/engine/rend/src/sgpu/renderer.zig

472 lines
18 KiB
Zig

// 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;