960 lines
36 KiB
Zig
960 lines
36 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 = 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,
|
|
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 = .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,
|
|
|
|
swapchainTargetFormat: gpu.GPUTextureFormat = undefined,
|
|
hdrTextureFormat: gpu.GPUTextureFormat = undefined,
|
|
postProcessingPipeline: *gpu.GPUGraphicsPipeline = undefined,
|
|
|
|
screenPlane: ?rend.IndexedMesh = null,
|
|
|
|
lightPower: f32 = 4.0,
|
|
|
|
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);
|
|
|
|
self.hdrTextureFormat = .textureformatR16g16b16a16Float;
|
|
return self;
|
|
}
|
|
|
|
pub fn createHDRTexture(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);
|
|
}
|
|
|
|
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();
|
|
try self.createHDRTexture();
|
|
try self.createPostProcessingPipeline();
|
|
|
|
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 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" },
|
|
),
|
|
);
|
|
}
|
|
|
|
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;
|
|
|
|
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),
|
|
};
|
|
|
|
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,
|
|
// .address_mode_u = .sampleraddressmodeMirroredRepeat,
|
|
// .address_mode_v = .sampleraddressmodeMirroredRepeat,
|
|
// .address_mode_w = .sampleraddressmodeMirroredRepeat,
|
|
// .address_mode_u = .sampleraddressmodeRepeat,
|
|
// .address_mode_v = .sampleraddressmodeRepeat,
|
|
// .address_mode_w = .sampleraddressmodeRepeat,
|
|
.address_mode_u = .sampleraddressmodeClampToEdge,
|
|
.address_mode_v = .sampleraddressmodeClampToEdge,
|
|
.address_mode_w = .sampleraddressmodeClampToEdge,
|
|
}));
|
|
}
|
|
|
|
// 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;
|
|
|
|
self.swapchainTargetFormat = self.device.getGPUSwapchainTextureFormat(self.window);
|
|
core.graphics_log("swapchain format: {any}", .{self.swapchainTargetFormat});
|
|
pci.target_info.has_depth_stencil_target = true;
|
|
pci.target_info.depth_stencil_format = self.depthFormat;
|
|
pci.target_info.num_color_targets = 2;
|
|
pci.target_info.color_target_descriptions = &[2]gpu.GPUColorTargetDescription{
|
|
.{
|
|
.format = self.hdrTextureFormat,
|
|
.blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState),
|
|
},
|
|
.{
|
|
.format = self.hdrTextureFormat,
|
|
.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);
|
|
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);
|
|
|
|
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 {
|
|
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) {
|
|
component.updateMesh();
|
|
}
|
|
|
|
if (component.texture == null) {
|
|
component.updateTexture();
|
|
}
|
|
}
|
|
|
|
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.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 drawMeshes(self: *@This(), cmd: *gpu.GPUCommandBuffer) void {
|
|
// can cache this
|
|
const targetInfo: [2]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,
|
|
}),
|
|
};
|
|
|
|
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, 2, &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.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(self.meshPipe);
|
|
renderpass.bindGPUFragmentSamplers(self.shadowDepthTextureSlot, &.{ .texture = self.shadowDepthTexture, .sampler = self.blockySampler }, 1);
|
|
|
|
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();
|
|
}
|
|
|
|
pub fn drawPostProcess(self: *@This(), cmd: *gpu.GPUCommandBuffer) void {
|
|
const postProcTargetInfo: gpu.GPUColorTargetInfo = std.mem.zeroInit(gpu.GPUColorTargetInfo, .{
|
|
.texture = self.state.swapchainTargetTexture.?,
|
|
.load_op = .loadopDontCare,
|
|
.store_op = .storeopStore,
|
|
});
|
|
|
|
const postProcPass = cmd.beginGPURenderPass(&postProcTargetInfo, 1, null);
|
|
|
|
postProcPass.bindGPUGraphicsPipeline(self.postProcessingPipeline);
|
|
postProcPass.bindGPUFragmentSamplers(0, &[2]gpu.GPUTextureSamplerBinding{
|
|
.{ .sampler = self.blockySampler, .texture = self.state.targetTexture },
|
|
.{ .sampler = self.linearSampler, .texture = self.state.emissiveTarget },
|
|
}, 2);
|
|
// 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.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.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 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 {
|
|
_ = core.shell.runCmd(gRenderer.allocator, &.{ "python", "../tools/scripts/cookShaders.py" }, ".") catch {
|
|
core.graphics_logs("shader cook script failed");
|
|
return;
|
|
};
|
|
try gRenderer.createMeshPipeline();
|
|
try gRenderer.createPostProcessingPipeline();
|
|
}
|
|
|
|
// ====== 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,
|
|
swapchainTargetTexture: ?*gpu.GPUTexture = null,
|
|
targetTexture: *gpu.GPUTexture = undefined,
|
|
emissiveTarget: *gpu.GPUTexture = undefined,
|
|
};
|
|
|
|
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 tracy = core.tracy;
|