467 lines
18 KiB
Zig
467 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,
|
|
};
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn prepare(self: *@This()) core.EngineDataEventError!void {
|
|
core.engine_log("starting up renderer interface", .{});
|
|
self.startRenderer() catch return error.BadInit;
|
|
}
|
|
|
|
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 {
|
|
_ = try core.shell.runCmd(gRenderer.allocator, &.{ "python", "../tools/scripts/cookShaders.py" }, ".");
|
|
try gRenderer.createMeshPipeline();
|
|
}
|
|
|
|
// ====== renderer API =======
|
|
pub fn start() !void {
|
|
gRenderer = try core.createObject(Renderer, .{ .can_tick = true, .isCore = true });
|
|
gAllocator = gRenderer.allocator;
|
|
}
|
|
|
|
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;
|