1602 lines
59 KiB
Zig
1602 lines
59 KiB
Zig
const std = @import("std");
|
|
const gl = @import("gl.zig");
|
|
const c = @import("sdl.zig");
|
|
const AssetManager = @import("AssetManager.zig");
|
|
const a = @import("asset_manifest");
|
|
const globals = @import("globals.zig");
|
|
pub const Material = @import("formats.zig").Material;
|
|
const math = @import("math.zig");
|
|
const formats = @import("formats.zig");
|
|
const tracy = @import("tracy");
|
|
|
|
const za = @import("zalgebra");
|
|
const Vec2 = za.Vec2;
|
|
const Vec3 = za.Vec3;
|
|
const Vec4 = za.Vec4;
|
|
const Mat4 = za.Mat4;
|
|
const Quat = za.Quat;
|
|
const Vec2_i32 = za.Vec2_i32;
|
|
|
|
pub const MAX_FRAMES_QUEUED = 3;
|
|
pub const MAX_LIGHTS = 8;
|
|
pub const MAX_DRAW_COMMANDS = 1024 * 16;
|
|
pub const MAX_LIGHT_COMMANDS = 2048;
|
|
pub const MAX_MATERIALS = MAX_DRAW_COMMANDS;
|
|
pub const CSM_SPLITS = 4;
|
|
pub const DIRECTIONAL_SHADOW_MAP_SIZE = 4096;
|
|
// affects how cascades are split
|
|
// 0 - uniform
|
|
// 1 - exponential
|
|
// 0.5 - mix between the two
|
|
pub const CSM_EXPO_UNIFORM_FACTOR = 0.8;
|
|
|
|
pub const Render = @This();
|
|
|
|
var default_camera: Camera = .{};
|
|
|
|
allocator: std.mem.Allocator,
|
|
frame_arena: std.mem.Allocator,
|
|
assetman: *AssetManager,
|
|
camera: *Camera = &default_camera,
|
|
mesh_vao: gl.GLuint = 0,
|
|
tripple_buffer_index: usize = MAX_FRAMES_QUEUED - 1,
|
|
gl_fences: [MAX_FRAMES_QUEUED]?gl.GLsync = [_]?gl.GLsync{null} ** MAX_FRAMES_QUEUED,
|
|
camera_ubo: gl.GLuint = 0,
|
|
camera_matrices: []u8 = &.{},
|
|
lights: [MAX_LIGHT_COMMANDS]LightCommand = undefined,
|
|
light_count: usize = 0,
|
|
|
|
lights_ssbo: LightSSBO = .{},
|
|
materials_pbr_ssbo: MaterialPBRSSBO = .{},
|
|
draw_cmd_data_ssbo: DrawCommandDataSSBO = .{},
|
|
|
|
command_buffer: [MAX_DRAW_COMMANDS]DrawCommand = undefined,
|
|
command_count: usize = 0,
|
|
|
|
ubo_align: usize = 0,
|
|
ssbo_align: usize = 0,
|
|
shadow_vao: gl.GLuint = 0,
|
|
shadow_texture_array: gl.GLuint = 0,
|
|
shadow_texture_handle: gl.GLuint64 = 0,
|
|
shadow_framebuffer: gl.GLuint = 0,
|
|
shadow_matrices_buffer: gl.GLuint = 0,
|
|
shadow_matrices: CameraMatrices = .{},
|
|
cube_shadow_texture_array: gl.GLuint = 0,
|
|
cube_shadow_texture_handle: gl.GLuint64 = 0,
|
|
cube_shadow_framebuffer: gl.GLuint = 0,
|
|
|
|
// Destination for all 3d rendering
|
|
screen_color_texture: gl.GLuint = 0,
|
|
screen_depth_texture: gl.GLuint = 0,
|
|
screen_fbo: gl.GLuint = 0,
|
|
screen_tex_size: Vec2_i32 = Vec2_i32.zero(),
|
|
screen_mip_count: usize = 1,
|
|
|
|
// VAO for post processing shaders
|
|
post_process_vao: gl.GLuint = 0,
|
|
|
|
draw_indirect_buffer: gl.GLuint = 0,
|
|
|
|
// Bloom
|
|
screen_bloom_sampler: gl.GLuint = 0,
|
|
|
|
update_view_frustum: bool = true,
|
|
camera_view_proj: Mat4 = Mat4.identity(),
|
|
world_camera_frustum: math.Frustum = .{},
|
|
world_view_frustum_corners: [CSM_SPLITS][8]Vec3 = undefined,
|
|
|
|
pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetman: *AssetManager) Render {
|
|
var render = Render{
|
|
.allocator = allocator,
|
|
.frame_arena = frame_arena,
|
|
.assetman = assetman,
|
|
};
|
|
|
|
gl.clipControl(gl.LOWER_LEFT, gl.ZERO_TO_ONE); // use [0, 1] depth in NDC
|
|
|
|
{
|
|
var buffer_align_int: gl.GLint = 0;
|
|
gl.getIntegerv(gl.UNIFORM_BUFFER_OFFSET_ALIGNMENT, &buffer_align_int);
|
|
if (buffer_align_int == 0) @panic("Failed to query GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT");
|
|
render.ubo_align = @intCast(buffer_align_int);
|
|
}
|
|
|
|
{
|
|
var buffer_align_int: gl.GLint = 0;
|
|
gl.getIntegerv(gl.SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &buffer_align_int);
|
|
if (buffer_align_int == 0) @panic("Failed to query GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT");
|
|
render.ssbo_align = @intCast(buffer_align_int);
|
|
}
|
|
|
|
{
|
|
// MESH VAO
|
|
var vao: gl.GLuint = 0;
|
|
gl.createVertexArrays(1, &vao);
|
|
std.debug.assert(vao != 0);
|
|
render.mesh_vao = vao;
|
|
|
|
// positions
|
|
// gl.vertexArrayVertexBuffer(vao, 0, vertices, 0, @sizeOf(formats.Vector3));
|
|
gl.enableVertexArrayAttrib(vao, Attrib.Position.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
|
|
|
// normals
|
|
gl.enableVertexArrayAttrib(vao, Attrib.Normal.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.Normal.value(), 1);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.Normal.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
|
|
|
// tangents
|
|
gl.enableVertexArrayAttrib(vao, Attrib.Tangent.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.Tangent.value(), 3);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.Tangent.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
|
|
|
// uvs
|
|
gl.enableVertexArrayAttrib(vao, Attrib.UV.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.UV.value(), 2);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.UV.value(), 2, gl.FLOAT, gl.FALSE, 0);
|
|
}
|
|
|
|
const PERSISTENT_BUFFER_FLAGS: gl.GLbitfield = gl.MAP_PERSISTENT_BIT | gl.MAP_WRITE_BIT | gl.MAP_COHERENT_BIT;
|
|
|
|
// Camera matrices ubo
|
|
{
|
|
gl.createBuffers(1, &render.camera_ubo);
|
|
std.debug.assert(render.camera_ubo != 0);
|
|
|
|
const buf_size = render.uboAlignedSizeOf(CameraMatrices) * MAX_FRAMES_QUEUED;
|
|
gl.namedBufferStorage(
|
|
render.camera_ubo,
|
|
@intCast(buf_size),
|
|
null,
|
|
PERSISTENT_BUFFER_FLAGS,
|
|
);
|
|
const camera_matrices_c: [*]u8 = @ptrCast(gl.mapNamedBufferRange(
|
|
render.camera_ubo,
|
|
0,
|
|
@intCast(buf_size),
|
|
PERSISTENT_BUFFER_FLAGS,
|
|
) orelse {
|
|
checkGLError();
|
|
@panic("bind camera_ubo");
|
|
});
|
|
render.camera_matrices = camera_matrices_c[0..buf_size];
|
|
}
|
|
|
|
// SSBOs
|
|
{
|
|
render.lights_ssbo = LightSSBO.init(render.ssbo_align, MAX_LIGHTS, MAX_FRAMES_QUEUED) catch @panic("LightSSBO.init()");
|
|
render.materials_pbr_ssbo = MaterialPBRSSBO.init(render.ssbo_align, MAX_MATERIALS, MAX_FRAMES_QUEUED) catch @panic("MaterialPBRSSBO.init()");
|
|
render.draw_cmd_data_ssbo = DrawCommandDataSSBO.init(render.ssbo_align, MAX_DRAW_COMMANDS, MAX_FRAMES_QUEUED) catch @panic("DrawCommandDataSSBO.init()");
|
|
}
|
|
|
|
{
|
|
// 2D Shadow texture array
|
|
{
|
|
gl.createTextures(gl.TEXTURE_2D_ARRAY, 1, &render.shadow_texture_array);
|
|
checkGLError();
|
|
std.debug.assert(render.shadow_texture_array != 0);
|
|
|
|
gl.textureStorage3D(render.shadow_texture_array, 1, gl.DEPTH_COMPONENT16, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE, CSM_SPLITS);
|
|
checkGLError();
|
|
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS);
|
|
|
|
var border = [_]f32{1} ** 4;
|
|
gl.textureParameterfv(render.shadow_texture_array, gl.TEXTURE_BORDER_COLOR, &border);
|
|
checkGLError();
|
|
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER);
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER);
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
}
|
|
|
|
// First shadow texture handle
|
|
{
|
|
render.shadow_texture_handle = gl.GL_ARB_bindless_texture.getTextureHandleARB(render.shadow_texture_array);
|
|
checkGLError();
|
|
std.debug.assert(render.shadow_texture_handle != 0);
|
|
gl.GL_ARB_bindless_texture.makeTextureHandleResidentARB(render.shadow_texture_handle);
|
|
checkGLError();
|
|
}
|
|
|
|
// Cube Shadow texture array
|
|
{
|
|
gl.createTextures(gl.TEXTURE_CUBE_MAP_ARRAY, 1, &render.cube_shadow_texture_array);
|
|
checkGLError();
|
|
std.debug.assert(render.cube_shadow_texture_array != 0);
|
|
|
|
gl.textureStorage3D(render.cube_shadow_texture_array, 1, gl.DEPTH_COMPONENT16, 512, 512, MAX_LIGHTS * 6);
|
|
checkGLError();
|
|
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
|
|
}
|
|
|
|
// Cube Shadow array handle
|
|
{
|
|
render.cube_shadow_texture_handle = gl.GL_ARB_bindless_texture.getTextureHandleARB(render.cube_shadow_texture_array);
|
|
checkGLError();
|
|
std.debug.assert(render.cube_shadow_texture_handle != 0);
|
|
gl.GL_ARB_bindless_texture.makeTextureHandleResidentARB(render.cube_shadow_texture_handle);
|
|
checkGLError();
|
|
}
|
|
|
|
// Shadow FBO
|
|
{
|
|
gl.createFramebuffers(1, &render.shadow_framebuffer);
|
|
checkGLError();
|
|
std.debug.assert(render.shadow_framebuffer != 0);
|
|
gl.namedFramebufferDrawBuffer(render.shadow_framebuffer, gl.NONE);
|
|
gl.namedFramebufferReadBuffer(render.shadow_framebuffer, gl.NONE);
|
|
}
|
|
|
|
gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.shadow_texture_array, 0, 0);
|
|
const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER);
|
|
if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) {
|
|
std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status});
|
|
}
|
|
|
|
gl.createBuffers(1, &render.shadow_matrices_buffer);
|
|
|
|
gl.namedBufferStorage(
|
|
render.shadow_matrices_buffer,
|
|
@sizeOf(CameraMatrices),
|
|
null,
|
|
gl.DYNAMIC_STORAGE_BIT,
|
|
);
|
|
|
|
// SHADOW VAO
|
|
var vao: gl.GLuint = 0;
|
|
gl.createVertexArrays(1, &vao);
|
|
std.debug.assert(vao != 0);
|
|
render.shadow_vao = vao;
|
|
|
|
// positions
|
|
// gl.vertexArrayVertexBuffer(vao, 0, vertices, 0, @sizeOf(formats.Vector3));
|
|
gl.enableVertexArrayAttrib(vao, Attrib.Position.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
|
}
|
|
|
|
// Screen HDR FBO
|
|
{
|
|
gl.createFramebuffers(1, &render.screen_fbo);
|
|
std.debug.assert(render.screen_fbo != 0);
|
|
|
|
var width: c_int = 0;
|
|
var height: c_int = 0;
|
|
c.SDL_GL_GetDrawableSize(globals.g_init.window, &width, &height);
|
|
|
|
var textures = [2]gl.GLuint{ 0, 0 };
|
|
gl.createTextures(gl.TEXTURE_2D, textures.len, &textures);
|
|
render.screen_color_texture = textures[0];
|
|
render.screen_depth_texture = textures[1];
|
|
|
|
std.debug.assert(render.screen_color_texture != 0);
|
|
std.debug.assert(render.screen_depth_texture != 0);
|
|
|
|
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.textureParameteri(render.screen_color_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
|
|
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.textureParameteri(render.screen_depth_texture, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
|
|
render.updateScreenBufferSize(width, height);
|
|
}
|
|
|
|
// Bloom screen sampler
|
|
{
|
|
var sampler: gl.GLuint = 0;
|
|
gl.createSamplers(1, &sampler);
|
|
std.debug.assert(sampler != 0);
|
|
render.screen_bloom_sampler = sampler;
|
|
|
|
gl.samplerParameteri(sampler, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
|
|
gl.samplerParameteri(sampler, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
}
|
|
|
|
// Post process VAO
|
|
{
|
|
gl.createVertexArrays(1, &render.post_process_vao);
|
|
std.debug.assert(render.post_process_vao != 0);
|
|
const vao = render.post_process_vao;
|
|
|
|
// positions
|
|
gl.enableVertexArrayAttrib(vao, Attrib.Position.value());
|
|
gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0);
|
|
gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0);
|
|
}
|
|
|
|
// Draw indirect buffer
|
|
{
|
|
gl.createBuffers(1, &render.draw_indirect_buffer);
|
|
std.debug.assert(render.draw_indirect_buffer != 0);
|
|
|
|
gl.namedBufferStorage(render.draw_indirect_buffer, @sizeOf(DrawIndirectCmd) * MAX_DRAW_COMMANDS, null, gl.MAP_WRITE_BIT);
|
|
}
|
|
|
|
return render;
|
|
}
|
|
|
|
fn getMipSize(width: i32, height: i32, mip_level: usize) Vec2_i32 {
|
|
if (mip_level == 0) return Vec2_i32.new(width, height);
|
|
const denom = std.math.pow(f32, 2, @floatFromInt(mip_level));
|
|
var mip_width: c_int = @intFromFloat(@as(f32, @floatFromInt(width)) / denom);
|
|
var mip_height: c_int = @intFromFloat(@as(f32, @floatFromInt(height)) / denom);
|
|
mip_width = @max(mip_width, 1);
|
|
mip_height = @max(mip_height, 1);
|
|
|
|
return Vec2_i32.new(mip_width, mip_height);
|
|
}
|
|
|
|
fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void {
|
|
const mip_count = 1 + @as(
|
|
u32,
|
|
@intFromFloat(@log2(@as(f32, @floatFromInt(@max(width, height))))),
|
|
);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, self.screen_color_texture);
|
|
for (0..mip_count) |mip_level| {
|
|
const size = getMipSize(width, height, mip_level);
|
|
std.log.debug("screen_color mip {} size {}x{}\n", .{ mip_level, size.x(), size.y() });
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D, @intCast(mip_level), gl.RGB16F, size.x(), size.y(), 0, gl.RGB, gl.HALF_FLOAT, null);
|
|
checkGLError();
|
|
}
|
|
|
|
// Depth doesn't need any mips cause it's not filterable anyway
|
|
gl.bindTexture(gl.TEXTURE_2D, self.screen_depth_texture);
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, width, height, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null);
|
|
checkGLError();
|
|
|
|
self.screen_tex_size = Vec2_i32.new(width, height);
|
|
self.screen_mip_count = mip_count;
|
|
}
|
|
|
|
pub fn begin(self: *Render) void {
|
|
self.command_count = 0;
|
|
self.light_count = 0;
|
|
self.tripple_buffer_index = (self.tripple_buffer_index + 1) % MAX_FRAMES_QUEUED;
|
|
|
|
gl.enable(gl.CULL_FACE);
|
|
gl.enable(gl.DEPTH_TEST);
|
|
if (self.gl_fences[self.tripple_buffer_index]) |fence| {
|
|
const syncResult = gl.clientWaitSync(fence, gl.SYNC_FLUSH_COMMANDS_BIT, 9999999999);
|
|
|
|
switch (syncResult) {
|
|
gl.ALREADY_SIGNALED => {
|
|
// awesome
|
|
},
|
|
gl.TIMEOUT_EXPIRED => {
|
|
// oh no, driver will crash soon :(
|
|
std.log.err("OpenGL clientWaitSync timeout expired D:\n", .{});
|
|
checkGLError();
|
|
},
|
|
gl.CONDITION_SATISFIED => {
|
|
// awesome
|
|
},
|
|
gl.WAIT_FAILED => {
|
|
checkGLError();
|
|
},
|
|
else => unreachable,
|
|
}
|
|
gl.deleteSync(fence);
|
|
self.gl_fences[self.tripple_buffer_index] = null;
|
|
}
|
|
}
|
|
|
|
// TODO: get rid of this
|
|
pub fn flushUBOs(self: *Render) void {
|
|
const idx = self.tripple_buffer_index;
|
|
|
|
self.lights_ssbo.bind(idx, SSBO.PointLights);
|
|
self.materials_pbr_ssbo.bind(idx, SSBO.Materials);
|
|
self.draw_cmd_data_ssbo.bind(idx, SSBO.DrawCommandData);
|
|
}
|
|
|
|
pub const LightKind = enum {
|
|
directional,
|
|
point,
|
|
// Spot, // TODO
|
|
};
|
|
|
|
pub const PointLight = struct {
|
|
color: Vec3,
|
|
pos: Vec3,
|
|
radius: f32,
|
|
};
|
|
|
|
pub const LightCommand = union(LightKind) {
|
|
directional: struct {
|
|
color: Vec3,
|
|
dir: Vec3,
|
|
},
|
|
point: PointLight,
|
|
};
|
|
|
|
const DrawCommandKey = packed struct {
|
|
mesh: u16 = 0,
|
|
distance: u15 = 0,
|
|
transparent: u1 = 0,
|
|
};
|
|
|
|
pub fn drawLight(self: *Render, cmd: LightCommand) void {
|
|
self.lights[self.light_count] = cmd;
|
|
self.light_count += 1;
|
|
}
|
|
|
|
pub fn draw(self: *Render, cmd: DrawCommand) void {
|
|
self.command_buffer[self.command_count] = cmd;
|
|
// TODO: don't load the whole mesh here
|
|
const mesh = self.assetman.resolveMesh(cmd.mesh);
|
|
const material: Material = if (cmd.material_override) |mat| mat else mesh.material;
|
|
const view_origin = self.camera.view_mat.extractTranslation();
|
|
const max_value = @as(f32, @floatFromInt(std.math.maxInt(u15)));
|
|
const dist: u15 = @intFromFloat(std.math.clamp(view_origin.distance(cmd.transform.extractTranslation()) / max_value, 0.0, max_value));
|
|
const key = DrawCommandKey{
|
|
.transparent = if (material.blend_mode == .AlphaBlend) 1 else 0,
|
|
.distance = if (material.blend_mode == .AlphaBlend) dist else dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front
|
|
.mesh = @intCast(cmd.mesh.id % std.math.maxInt(u16)),
|
|
};
|
|
self.command_buffer[self.command_count].key = key;
|
|
self.command_count += 1;
|
|
}
|
|
|
|
// Multipass radix sort for u32
|
|
fn sortCommands(self: *Render, in_cmds: []DrawCommand) void {
|
|
var cmds = in_cmds;
|
|
var aux = self.frame_arena.alloc(DrawCommand, cmds.len) catch @panic("OOM");
|
|
|
|
var cnt1: [256]usize = std.mem.zeroes([256]usize);
|
|
var cnt2: [256]usize = std.mem.zeroes([256]usize);
|
|
var cnt3: [256]usize = std.mem.zeroes([256]usize);
|
|
var cnt4: [256]usize = std.mem.zeroes([256]usize);
|
|
|
|
// Find counts
|
|
for (cmds) |*cmd| {
|
|
const key: u32 = @bitCast(cmd.key);
|
|
|
|
cnt1[(key >> 0) & 0xFF] += 1;
|
|
cnt2[(key >> 8) & 0xFF] += 1;
|
|
cnt3[(key >> 16) & 0xFF] += 1;
|
|
cnt4[(key >> 24) & 0xFF] += 1;
|
|
}
|
|
|
|
var a1: usize = 0;
|
|
var a2: usize = 0;
|
|
var a3: usize = 0;
|
|
var a4: usize = 0;
|
|
for (0..256) |i| {
|
|
const b1 = cnt1[i];
|
|
const b2 = cnt2[i];
|
|
const b3 = cnt3[i];
|
|
const b4 = cnt4[i];
|
|
|
|
cnt1[i] = a1;
|
|
cnt2[i] = a2;
|
|
cnt3[i] = a3;
|
|
cnt4[i] = a4;
|
|
|
|
a1 += b1;
|
|
a2 += b2;
|
|
a3 += b3;
|
|
a4 += b4;
|
|
}
|
|
|
|
for (0..cmds.len) |i| {
|
|
const key: u32 = @bitCast(cmds[i].key);
|
|
const k = (key >> 0) & 0xFF;
|
|
const dst = cnt1[k];
|
|
cnt1[k] += 1;
|
|
aux[dst] = cmds[i];
|
|
}
|
|
std.mem.swap([]DrawCommand, &cmds, &aux);
|
|
|
|
for (0..cmds.len) |i| {
|
|
const key: u32 = @bitCast(cmds[i].key);
|
|
const k = (key >> 8) & 0xFF;
|
|
const dst = cnt2[k];
|
|
cnt2[k] += 1;
|
|
aux[dst] = cmds[i];
|
|
}
|
|
std.mem.swap([]DrawCommand, &cmds, &aux);
|
|
|
|
for (0..cmds.len) |i| {
|
|
const key: u32 = @bitCast(cmds[i].key);
|
|
const k = (key >> 16) & 0xFF;
|
|
const dst = cnt3[k];
|
|
cnt3[k] += 1;
|
|
aux[dst] = cmds[i];
|
|
}
|
|
std.mem.swap([]DrawCommand, &cmds, &aux);
|
|
|
|
for (0..cmds.len) |i| {
|
|
const key: u32 = @bitCast(cmds[i].key);
|
|
const k = (key >> 24) & 0xFF;
|
|
const dst = cnt4[k];
|
|
cnt4[k] += 1;
|
|
aux[dst] = cmds[i];
|
|
}
|
|
std.mem.swap([]DrawCommand, &cmds, &aux);
|
|
}
|
|
|
|
pub fn finish(self: *Render) void {
|
|
const zone = tracy.initZone(@src(), .{ .name = "Render.finish" });
|
|
defer zone.deinit();
|
|
|
|
const camera_projection = self.camera.projection();
|
|
const view_proj = camera_projection.mul(self.camera.view_mat);
|
|
|
|
// Sort draw calls: opaque -> blended
|
|
{
|
|
const zoneSort = tracy.initZone(@src(), .{ .name = "Render.finish_sortDraws" });
|
|
defer zoneSort.deinit();
|
|
|
|
self.sortCommands(self.command_buffer[0..self.command_count]);
|
|
|
|
// Sorting validation
|
|
if (false) {
|
|
var alpha = false;
|
|
for (self.command_buffer[0..self.command_count]) |cmd| {
|
|
if (!alpha and cmd.key.transparent == 1) {
|
|
alpha = true;
|
|
}
|
|
|
|
if (alpha and cmd.key.transparent == 0) {
|
|
std.log.err("WRONG SORTING!\n", .{});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.update_view_frustum) {
|
|
self.camera_view_proj = view_proj;
|
|
self.world_camera_frustum = math.Frustum.new(view_proj);
|
|
}
|
|
|
|
const lights = self.lights[0..self.light_count];
|
|
|
|
// Sort lights: directional first
|
|
{
|
|
std.mem.sortUnstable(LightCommand, lights, {}, struct {
|
|
pub fn lessThan(_: void, lhs: LightCommand, rhs: LightCommand) bool {
|
|
_ = rhs; // autofix
|
|
return switch (lhs) {
|
|
.directional => true,
|
|
.point => false,
|
|
};
|
|
}
|
|
}.lessThan);
|
|
}
|
|
|
|
const lights_buf = self.lights_ssbo.getInstance(self.tripple_buffer_index);
|
|
lights_buf.count.* = 0;
|
|
|
|
var dir_view_proj_mat: [CSM_SPLITS]Mat4 = undefined;
|
|
|
|
// Light shadow maps
|
|
{
|
|
const zoneShadowmaps = tracy.initZone(@src(), .{ .name = "Render.finish_shadowmaps" });
|
|
defer zoneShadowmaps.deinit();
|
|
|
|
gl.enable(gl.DEPTH_CLAMP);
|
|
defer gl.disable(gl.DEPTH_CLAMP);
|
|
|
|
gl.bindVertexArray(self.shadow_vao);
|
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, self.shadow_framebuffer);
|
|
|
|
var finished_dir_lights = false;
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.shadow).program);
|
|
|
|
for (lights) |light_cmd| {
|
|
const i = lights_buf.count.*;
|
|
if (i == lights_buf.data.len) break;
|
|
|
|
const light = &lights_buf.data[i];
|
|
lights_buf.count.* += 1;
|
|
|
|
switch (light_cmd) {
|
|
.directional => |dir_light| {
|
|
light.pos = dir_light.dir.toVec4(0);
|
|
light.color_radius = dir_light.color.toVec4(0);
|
|
gl.viewport(0, 0, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE);
|
|
|
|
const camera_matrix = &self.shadow_matrices;
|
|
|
|
const view = Mat4.lookAt(
|
|
dir_light.dir.scale(-1),
|
|
Vec3.zero(),
|
|
Vec3.up(),
|
|
);
|
|
|
|
const shadow_map_idx = 0;
|
|
|
|
light.view_mat = view;
|
|
light.params.shadow_map_idx = shadow_map_idx;
|
|
light.params.csm_split_count = @floatFromInt(CSM_SPLITS);
|
|
const shadow_far = self.camera.far / 2;
|
|
|
|
var splits: [CSM_SPLITS + 1]f32 = undefined;
|
|
|
|
const splits_count_f: f32 = @floatFromInt(CSM_SPLITS);
|
|
for (0..CSM_SPLITS + 1) |split_idx| {
|
|
const split_idx_f: f32 = @floatFromInt(split_idx);
|
|
const split_i_over_n = split_idx_f / splits_count_f;
|
|
const expo_split = self.camera.near * std.math.pow(f32, shadow_far / self.camera.near, split_i_over_n);
|
|
const uniform_split = self.camera.near + split_i_over_n * (shadow_far - self.camera.near);
|
|
const split = CSM_EXPO_UNIFORM_FACTOR * expo_split + (1.0 - CSM_EXPO_UNIFORM_FACTOR) * uniform_split;
|
|
splits[split_idx] = split;
|
|
}
|
|
|
|
for (0..CSM_SPLITS) |split_idx| {
|
|
const split_near = splits[split_idx];
|
|
const split_far = splits[split_idx + 1];
|
|
|
|
gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.shadow_texture_array, 0, @intCast(shadow_map_idx * CSM_SPLITS + split_idx));
|
|
const check_fbo_status = gl.checkNamedFramebufferStatus(self.shadow_framebuffer, gl.DRAW_FRAMEBUFFER);
|
|
if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) {
|
|
std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status});
|
|
}
|
|
|
|
var projection: Mat4 = undefined;
|
|
|
|
{
|
|
var camera = self.camera.*;
|
|
if (self.update_view_frustum) {
|
|
camera.near = split_near;
|
|
camera.far = split_far;
|
|
const inv_csm_proj = camera.projection().mul(camera.view_mat).inv();
|
|
|
|
for (math.ndc_box_corners, 0..) |corner, corner_idx| {
|
|
const pos4 = inv_csm_proj.mulByVec4(corner.toVec4(1));
|
|
self.world_view_frustum_corners[split_idx][corner_idx] = pos4.toVec3().scale(1.0 / pos4.w());
|
|
}
|
|
}
|
|
|
|
// Find minimal bounding sphere for a frustum
|
|
// Taken from:
|
|
// https://lxjk.github.io/2017/04/15/Calculate-Minimal-Bounding-Sphere-of-Frustum.html
|
|
const inv_aspect_sqr = (camera.aspect) * (camera.aspect);
|
|
const k = @sqrt(1 + inv_aspect_sqr) * @tan(za.toRadians(camera.fovy) / 2);
|
|
|
|
var center = Vec3.zero();
|
|
var radius: f32 = 0;
|
|
if (k * k >= (camera.far - camera.near) / (camera.far + camera.near)) {
|
|
center = Vec3.new(0, 0, -camera.far);
|
|
radius = camera.far * k;
|
|
} else {
|
|
center = Vec3.new(0, 0, -0.5 * (camera.far + camera.near) * (1 + k * k));
|
|
radius = 0.5 * @sqrt((camera.far - camera.near) * (camera.far - camera.near) + 2 * (camera.far * camera.far + camera.near * camera.near) * k * k + (camera.far + camera.near) * (camera.far + camera.near) * k * k * k * k);
|
|
}
|
|
|
|
center = camera.view_mat.inv().mulByVec4(center.toVec4(1)).toVec3();
|
|
center = view.mulByVec4(center.toVec4(1)).toVec3();
|
|
|
|
// NOTE: Use bounding sphere instead of AABB to prevent split size changing with rotation
|
|
projection = math.orthographic(
|
|
center.x() - radius - 0.0001,
|
|
center.x() + radius,
|
|
center.y() - radius,
|
|
center.y() + radius,
|
|
-center.z() - radius,
|
|
-center.z() + radius,
|
|
);
|
|
}
|
|
|
|
var shadow_view_proj = projection.mul(view);
|
|
|
|
// Snap to texels
|
|
{
|
|
var shadow_origin = shadow_view_proj.mulByVec4(Vec4.new(0, 0, 0, 1));
|
|
shadow_origin = shadow_origin.scale(1.0 / shadow_origin.w());
|
|
shadow_origin = shadow_origin.scale(DIRECTIONAL_SHADOW_MAP_SIZE / 2);
|
|
var rounded_origin: Vec4 = undefined;
|
|
rounded_origin.data = @round(shadow_origin.data);
|
|
var offset = rounded_origin.sub(shadow_origin).toVec2().toVec3(0);
|
|
offset = offset.scale(2.0 / @as(f32, DIRECTIONAL_SHADOW_MAP_SIZE));
|
|
projection = projection.translate(offset);
|
|
shadow_view_proj = projection.mul(view);
|
|
}
|
|
|
|
camera_matrix.* = .{
|
|
.view = view,
|
|
.projection = projection,
|
|
};
|
|
dir_view_proj_mat[split_idx] = shadow_view_proj;
|
|
const light_frustum = math.Frustum.new(shadow_view_proj);
|
|
|
|
light.view_proj_mats[split_idx] = shadow_view_proj;
|
|
light.csm_split_points[split_idx] = -split_far;
|
|
|
|
gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices));
|
|
checkGLError();
|
|
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer);
|
|
|
|
self.renderShadow(&light_frustum);
|
|
}
|
|
},
|
|
.point => |point_light| {
|
|
if (!finished_dir_lights) {
|
|
finished_dir_lights = true;
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.cube_shadow).program);
|
|
}
|
|
|
|
const pos = point_light.pos;
|
|
light.pos = pos.toVec4(1);
|
|
light.color_radius = point_light.color.toVec4(point_light.radius);
|
|
|
|
const range = pointLightRange(&point_light);
|
|
const near_far = Vec2.new(0.1, range);
|
|
|
|
light.view_mat = Mat4.fromTranslate(pos.negate());
|
|
light.params.near = near_far.x();
|
|
light.params.far = near_far.y();
|
|
|
|
const shadow_map_idx = i;
|
|
light.params.shadow_map_idx = @floatFromInt(shadow_map_idx);
|
|
|
|
// For each cube face
|
|
for (cube_camera_dirs, 0..) |cam_dir, face| {
|
|
gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.cube_shadow_texture_array, 0, @intCast(shadow_map_idx * 6 + face));
|
|
const check_fbo_status = gl.checkNamedFramebufferStatus(self.shadow_framebuffer, gl.DRAW_FRAMEBUFFER);
|
|
if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) {
|
|
std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status});
|
|
}
|
|
|
|
gl.viewport(0, 0, 512, 512);
|
|
|
|
const camera_matrix = &self.shadow_matrices;
|
|
camera_matrix.* = .{
|
|
.projection = math.perspective(90, 1, near_far.x(), near_far.y()),
|
|
.view = Mat4.lookAt(
|
|
pos,
|
|
pos.add(cam_dir.target),
|
|
cam_dir.up,
|
|
),
|
|
};
|
|
|
|
const shadow_view_proj = camera_matrix.projection.mul(camera_matrix.view);
|
|
const light_frustum = math.Frustum.new(shadow_view_proj);
|
|
gl.uniform2f(Uniform.NearFarPlanes.value(), near_far.x(), near_far.y());
|
|
|
|
gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices));
|
|
checkGLError();
|
|
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer);
|
|
|
|
self.renderShadow(&light_frustum);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Light world space to view space
|
|
for (lights_buf.data[0..lights_buf.count.*]) |*light| {
|
|
light.pos = self.camera.view_mat.mulByVec4(light.pos);
|
|
}
|
|
|
|
var width: c_int = 0;
|
|
var height: c_int = 0;
|
|
c.SDL_GL_GetDrawableSize(globals.g_init.window, &width, &height);
|
|
|
|
if (width != self.screen_tex_size.x() or height != self.screen_tex_size.y()) {
|
|
self.updateScreenBufferSize(width, height);
|
|
}
|
|
|
|
gl.namedFramebufferTexture(self.screen_fbo, gl.COLOR_ATTACHMENT0, self.screen_color_texture, 0);
|
|
gl.namedFramebufferTexture(self.screen_fbo, gl.DEPTH_ATTACHMENT, self.screen_depth_texture, 0);
|
|
|
|
if (gl.checkNamedFramebufferStatus(self.screen_fbo, gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
|
|
checkGLError();
|
|
@panic("Framebuffer incomplete");
|
|
}
|
|
|
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, self.screen_fbo);
|
|
|
|
gl.viewport(0, 0, width, height);
|
|
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
|
|
var switched_to_alpha_blend = false;
|
|
|
|
var draw_indirect_cmds = self.frame_arena.alloc(DrawIndirectCmd, MAX_DRAW_COMMANDS) catch @panic("OOM");
|
|
var draw_cmd_data = self.frame_arena.alloc(DrawCommandData, MAX_DRAW_COMMANDS) catch @panic("OOM");
|
|
|
|
var draw_indirect_buf: gl.GLuint = 0;
|
|
gl.createBuffers(1, &draw_indirect_buf);
|
|
checkGLError();
|
|
defer gl.deleteBuffers(1, &draw_indirect_buf);
|
|
|
|
var draw_cmd_data_buf: gl.GLuint = 0;
|
|
gl.createBuffers(1, &draw_cmd_data_buf);
|
|
checkGLError();
|
|
defer gl.deleteBuffers(1, &draw_cmd_data_buf);
|
|
|
|
var rendered_count: usize = 0;
|
|
var rendered_opaque_count: usize = 0;
|
|
|
|
// Prepare indirect draw commands
|
|
{
|
|
const zonePrepareInidirectDraws = tracy.initZone(@src(), .{ .name = "Render.finish_PrepareInidirectDraws" });
|
|
defer zonePrepareInidirectDraws.deinit();
|
|
|
|
const materials = self.materials_pbr_ssbo.getInstance(self.tripple_buffer_index);
|
|
materials.count.* = 0;
|
|
|
|
var material_map = std.StringHashMap(i32).init(self.frame_arena);
|
|
|
|
var materials_count: usize = 0;
|
|
for (self.command_buffer[0..self.command_count]) |*cmd| {
|
|
const mesh = self.assetman.resolveMesh(cmd.mesh);
|
|
// const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max);
|
|
|
|
// if (!self.world_camera_frustum.intersectAABB(aabb.transform(cmd.transform))) {
|
|
// continue;
|
|
// }
|
|
|
|
const material: Material = if (cmd.material_override) |mat| mat else mesh.material;
|
|
|
|
// Opaque objects are drawn, start rendering alpha blended objects
|
|
if (material.blend_mode == .AlphaBlend and !switched_to_alpha_blend) {
|
|
rendered_opaque_count = rendered_count;
|
|
std.log.debug("opaque: {}\n", .{rendered_opaque_count});
|
|
switched_to_alpha_blend = true;
|
|
}
|
|
|
|
const material_bytes = std.mem.asBytes(&material);
|
|
const material_copy = self.frame_arena.alloc(u8, material_bytes.len) catch @panic("OOM");
|
|
@memcpy(material_copy, material_bytes);
|
|
const gop = material_map.getOrPut(material_copy) catch @panic("OOM");
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = @intCast(materials_count);
|
|
materials.data[materials_count] = MaterialPBR.fromMaterial(self.assetman, &material);
|
|
materials_count += 1;
|
|
}
|
|
|
|
draw_cmd_data[rendered_count] = DrawCommandData{
|
|
.transform = cmd.transform,
|
|
.material_index = gop.value_ptr.*,
|
|
};
|
|
|
|
draw_indirect_cmds[rendered_count] = DrawIndirectCmd{
|
|
.count = mesh.indices.count,
|
|
.instance_count = 1,
|
|
.first_index = mesh.indices.offset / 4,
|
|
.base_vertex = mesh.indices.base_vertex,
|
|
.base_instance = @intCast(rendered_count),
|
|
};
|
|
|
|
rendered_count += 1;
|
|
}
|
|
}
|
|
|
|
if (rendered_opaque_count == 0) {
|
|
rendered_opaque_count = rendered_count;
|
|
}
|
|
|
|
{
|
|
const camera_matrix: *CameraMatrices = @alignCast(@ptrCast(self.camera_matrices[self.tripple_buffer_index * self.uboAlignedSizeOf(CameraMatrices) ..].ptr));
|
|
camera_matrix.* = .{
|
|
.projection = camera_projection,
|
|
.view = self.camera.view_mat,
|
|
};
|
|
|
|
//gl.flushMappedNamedBufferRange(self.camera_ubo, idx * @sizeOf(CameraMatrices), @sizeOf(CameraMatrices));
|
|
gl.bindBufferRange(
|
|
gl.UNIFORM_BUFFER,
|
|
UBO.CameraMatrices.value(),
|
|
self.camera_ubo,
|
|
self.tripple_buffer_index * self.uboAlignedSizeOf(CameraMatrices),
|
|
@intCast(self.uboAlignedSizeOf(CameraMatrices)),
|
|
);
|
|
checkGLError();
|
|
}
|
|
|
|
gl.namedBufferStorage(draw_indirect_buf, @intCast(@sizeOf(DrawIndirectCmd) * rendered_count), draw_indirect_cmds.ptr, 0);
|
|
gl.bindBuffer(gl.DRAW_INDIRECT_BUFFER, draw_indirect_buf);
|
|
|
|
gl.namedBufferStorage(draw_cmd_data_buf, @intCast(@sizeOf(DrawCommandData) * rendered_count), draw_cmd_data.ptr, 0);
|
|
gl.bindBufferBase(gl.SHADER_STORAGE_BUFFER, SSBO.DrawCommandData.value(), draw_cmd_data_buf);
|
|
|
|
// Z Prepass
|
|
{
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program);
|
|
gl.bindVertexArray(self.shadow_vao);
|
|
gl.depthFunc(gl.LESS);
|
|
|
|
self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value());
|
|
checkGLError();
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer);
|
|
checkGLError();
|
|
|
|
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd));
|
|
checkGLError();
|
|
}
|
|
|
|
// Main pass
|
|
{
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program);
|
|
gl.bindVertexArray(self.mesh_vao);
|
|
gl.depthFunc(gl.EQUAL);
|
|
|
|
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.EnvBRDF.value(), self.assetman.resolveTexture(a.Textures.@"ibl_brdf_lut.norm").handle);
|
|
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.ShadowMap2D.value(), self.shadow_texture_handle);
|
|
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.ShadowMapCube.value(), self.cube_shadow_texture_handle);
|
|
|
|
self.assetman.vertex_heap.normals.bind(Render.Attrib.Normal.value());
|
|
checkGLError();
|
|
self.assetman.vertex_heap.tangents.bind(Render.Attrib.Tangent.value());
|
|
checkGLError();
|
|
self.assetman.vertex_heap.uvs.bind(Render.Attrib.UV.value());
|
|
checkGLError();
|
|
self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value());
|
|
checkGLError();
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer);
|
|
checkGLError();
|
|
|
|
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd));
|
|
checkGLError();
|
|
}
|
|
|
|
// Alpha Pass
|
|
const blended_draws_count = rendered_count - rendered_opaque_count;
|
|
if (blended_draws_count > 0) {
|
|
std.log.debug("blended: {}\n", .{blended_draws_count});
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
gl.depthFunc(gl.LEQUAL);
|
|
gl.depthMask(gl.FALSE);
|
|
|
|
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, @ptrFromInt(@sizeOf(DrawIndirectCmd) * rendered_opaque_count), @intCast(blended_draws_count), @sizeOf(DrawIndirectCmd));
|
|
|
|
gl.disable(gl.BLEND);
|
|
gl.depthFunc(gl.LEQUAL);
|
|
gl.depthMask(gl.TRUE);
|
|
}
|
|
|
|
// Debug stuff
|
|
{
|
|
gl.polygonMode(gl.FRONT_AND_BACK, gl.LINE);
|
|
defer gl.polygonMode(gl.FRONT_AND_BACK, gl.FILL);
|
|
gl.lineWidth(4);
|
|
|
|
// Frustum debug stuff, drawn only when view frustum is fixed
|
|
if (!self.update_view_frustum) {
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.unlit).program);
|
|
|
|
// Draw wire frustum cubes
|
|
{
|
|
const mesh = self.assetman.resolveMesh(a.Meshes.cube.Cube);
|
|
mesh.positions.bind(Render.Attrib.Position.value());
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices.buffer);
|
|
gl.uniform3fv(Uniform.Color.value(), 1, @ptrCast(&Vec3.one().data));
|
|
|
|
const model = Mat4.fromTranslate(Vec3.new(0, 0, 0.5)).mul(Mat4.fromScale(Vec3.new(1, 1, 0.5)));
|
|
|
|
var view_proj_matrices: [1 + CSM_SPLITS]Mat4 = undefined;
|
|
for (0..CSM_SPLITS) |split_idx| {
|
|
view_proj_matrices[split_idx] = dir_view_proj_mat[split_idx];
|
|
}
|
|
view_proj_matrices[CSM_SPLITS] = self.camera_view_proj;
|
|
|
|
for (view_proj_matrices) |frustum_view_proj| {
|
|
const frustum_model_mat = frustum_view_proj.inv().mul(model);
|
|
gl.uniformMatrix4fv(Uniform.ModelMatrix.value(), 1, gl.FALSE, @ptrCast(&frustum_model_mat.data));
|
|
gl.drawElementsBaseVertex(
|
|
gl.TRIANGLES,
|
|
@intCast(mesh.indices.count),
|
|
mesh.indices.type,
|
|
@ptrFromInt(mesh.indices.offset),
|
|
mesh.indices.base_vertex,
|
|
);
|
|
}
|
|
}
|
|
// Draw corner positions of view frustum
|
|
{
|
|
const mesh = self.assetman.resolveMesh(a.Meshes.sphere.Icosphere);
|
|
mesh.positions.bind(Attrib.Position.value());
|
|
mesh.indices.bind();
|
|
|
|
gl.uniform3fv(Uniform.Color.value(), 1, @ptrCast(&Vec3.new(1, 0, 0).data));
|
|
|
|
for (0..CSM_SPLITS) |split_idx| {
|
|
for (self.world_view_frustum_corners[split_idx]) |corner| {
|
|
const model = Mat4.fromTranslate(corner);
|
|
gl.uniformMatrix4fv(Uniform.ModelMatrix.value(), 1, gl.FALSE, @ptrCast(&model.data));
|
|
gl.drawElementsBaseVertex(gl.TRIANGLES, @intCast(mesh.indices.count), mesh.indices.type, @ptrFromInt(mesh.indices.offset), mesh.indices.base_vertex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//std.log.debug("Total draws {}, frustum culled draws {}\n", .{ self.command_count, rendered_count });
|
|
|
|
gl.disable(gl.DEPTH_TEST);
|
|
gl.bindVertexArray(self.post_process_vao); // shared for all post process shaders
|
|
|
|
const quad = self.assetman.resolveMesh(a.Meshes.quad.Plane);
|
|
// Bind quad
|
|
{
|
|
quad.positions.bind(Render.Attrib.Position.value());
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, quad.indices.buffer);
|
|
}
|
|
|
|
// Bloom pass
|
|
{
|
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, self.screen_fbo);
|
|
|
|
gl.bindTextureUnit(0, self.screen_color_texture);
|
|
gl.bindSampler(0, self.screen_bloom_sampler);
|
|
defer gl.bindSampler(0, 0);
|
|
|
|
// Downsample and filter
|
|
{
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.bloom_downsample).program);
|
|
|
|
for (1..self.screen_mip_count) |dst_mip_level| {
|
|
const src_mip_level = dst_mip_level - 1;
|
|
gl.namedFramebufferTexture(self.screen_fbo, gl.COLOR_ATTACHMENT0, self.screen_color_texture, @intCast(dst_mip_level));
|
|
const size = getMipSize(self.screen_tex_size.x(), self.screen_tex_size.y(), dst_mip_level);
|
|
gl.viewport(0, 0, size.x(), size.y());
|
|
gl.uniform1i(Uniform.SRCMipLevel.value(), @intCast(src_mip_level));
|
|
|
|
gl.drawElementsBaseVertex(
|
|
gl.TRIANGLES,
|
|
@intCast(quad.indices.count),
|
|
quad.indices.type,
|
|
@ptrFromInt(quad.indices.offset),
|
|
quad.indices.base_vertex,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Upsample
|
|
{
|
|
gl.enable(gl.BLEND);
|
|
defer gl.disable(gl.BLEND);
|
|
gl.blendFunc(gl.ONE, gl.ONE);
|
|
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.bloom_upsample).program);
|
|
|
|
var src_mip_level = self.screen_mip_count - 1;
|
|
while (src_mip_level > 0) : (src_mip_level -= 1) {
|
|
const dst_mip_level = src_mip_level - 1;
|
|
gl.namedFramebufferTexture(self.screen_fbo, gl.COLOR_ATTACHMENT0, self.screen_color_texture, @intCast(dst_mip_level));
|
|
const size = getMipSize(self.screen_tex_size.x(), self.screen_tex_size.y(), dst_mip_level);
|
|
gl.viewport(0, 0, size.x(), size.y());
|
|
gl.uniform1i(Uniform.SRCMipLevel.value(), @intCast(src_mip_level));
|
|
gl.uniform1f(Uniform.BloomStrength.value(), if (dst_mip_level == 0) 0.04 else 1);
|
|
|
|
gl.drawElementsBaseVertex(
|
|
gl.TRIANGLES,
|
|
@intCast(quad.indices.count),
|
|
quad.indices.type,
|
|
@ptrFromInt(quad.indices.offset),
|
|
quad.indices.base_vertex,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Final post processing pass
|
|
{
|
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, 0);
|
|
//gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
|
|
gl.viewport(0, 0, width, height);
|
|
|
|
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.post_process).program);
|
|
|
|
gl.bindTextureUnit(0, self.screen_color_texture);
|
|
defer gl.bindTextureUnit(0, 0);
|
|
|
|
gl.drawElementsBaseVertex(gl.TRIANGLES, @intCast(quad.indices.count), quad.indices.type, @ptrFromInt(quad.indices.offset), quad.indices.base_vertex);
|
|
}
|
|
|
|
self.gl_fences[self.tripple_buffer_index] = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
}
|
|
|
|
pub fn pointLightRange(self: *const PointLight) f32 {
|
|
const color = self.color;
|
|
const light_intensity = @max(color.x(), color.y(), color.z());
|
|
|
|
const cutoff = 0.005;
|
|
return self.radius * (@sqrt(light_intensity / cutoff) - 1);
|
|
}
|
|
|
|
const CubeCameraDir = struct {
|
|
face: gl.GLenum,
|
|
target: Vec3,
|
|
up: Vec3,
|
|
};
|
|
|
|
const cube_camera_dirs = [6]CubeCameraDir{
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_POSITIVE_X,
|
|
.target = Vec3.right(),
|
|
.up = Vec3.down(),
|
|
},
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
|
|
.target = Vec3.left(),
|
|
.up = Vec3.down(),
|
|
},
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
|
|
.target = Vec3.up(),
|
|
.up = Vec3.forward(),
|
|
},
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
|
|
.target = Vec3.down(),
|
|
.up = Vec3.back(),
|
|
},
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
|
|
.target = Vec3.forward(),
|
|
.up = Vec3.down(),
|
|
},
|
|
.{
|
|
.face = gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
|
|
.target = Vec3.back(),
|
|
.up = Vec3.down(),
|
|
},
|
|
};
|
|
|
|
fn renderShadow(self: *Render, frustum: *const math.Frustum) void {
|
|
const zone = tracy.initZone(@src(), .{ .name = "Render.renderShadow" });
|
|
defer zone.deinit();
|
|
_ = frustum; // autofix
|
|
self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value());
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer);
|
|
|
|
// TODO: this wastes space in temp allocator
|
|
var draw_indirect_cmds = std.ArrayList(DrawIndirectCmd).init(self.frame_arena);
|
|
var transforms = std.ArrayList(Mat4).init(self.frame_arena);
|
|
|
|
for (self.command_buffer[0..self.command_count]) |*cmd| {
|
|
const mesh = self.assetman.resolveMesh(cmd.mesh);
|
|
// const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max);
|
|
|
|
// if (!frustum.intersectAABBSkipNear(aabb.transform(cmd.transform))) {
|
|
// continue;
|
|
// }
|
|
|
|
const draw_indirect_cmd = draw_indirect_cmds.addOne() catch @panic("OOM");
|
|
const transform = transforms.addOne() catch @panic("OOM");
|
|
|
|
draw_indirect_cmd.* = .{
|
|
.count = mesh.indices.count,
|
|
.instance_count = 1,
|
|
.first_index = mesh.indices.offset / 4,
|
|
.base_vertex = mesh.indices.base_vertex,
|
|
.base_instance = 0,
|
|
};
|
|
|
|
transform.* = cmd.transform;
|
|
}
|
|
|
|
var bufs = [2]gl.GLuint{ 0, 0 };
|
|
gl.createBuffers(bufs.len, &bufs);
|
|
checkGLError();
|
|
defer _ = gl.deleteBuffers(bufs.len, &bufs);
|
|
|
|
gl.namedBufferStorage(bufs[0], @intCast(@sizeOf(DrawIndirectCmd) * draw_indirect_cmds.items.len), draw_indirect_cmds.items.ptr, 0);
|
|
gl.namedBufferStorage(bufs[1], @intCast(@sizeOf(Mat4) * transforms.items.len), transforms.items.ptr, 0);
|
|
|
|
gl.bindBuffer(gl.DRAW_INDIRECT_BUFFER, bufs[0]);
|
|
gl.bindBufferBase(gl.SHADER_STORAGE_BUFFER, SSBO.DrawCommandData.value(), bufs[1]);
|
|
|
|
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(draw_indirect_cmds.items.len), 0);
|
|
}
|
|
|
|
pub fn checkGLError() void {
|
|
var err = gl.getError();
|
|
if (err == gl.NO_ERROR) return;
|
|
|
|
while (err != gl.NO_ERROR) : (err = gl.getError()) {
|
|
const name = switch (err) {
|
|
gl.INVALID_ENUM => "invalid enum",
|
|
gl.INVALID_VALUE => "invalid value",
|
|
gl.INVALID_OPERATION => "invalid operation",
|
|
gl.STACK_OVERFLOW => "stack overflow",
|
|
gl.STACK_UNDERFLOW => "stack underflow",
|
|
gl.OUT_OF_MEMORY => "out of memory",
|
|
gl.INVALID_FRAMEBUFFER_OPERATION => "invalid framebuffer operation",
|
|
// binding.INVALID_FRAMEBUFFER_OPERATION_EXT => Error.InvalidFramebufferOperation,
|
|
// binding.INVALID_FRAMEBUFFER_OPERATION_OES => Error.InvalidFramebufferOperation,
|
|
//binding.TABLE_TOO_LARGE => "Table too large",
|
|
// binding.TABLE_TOO_LARGE_EXT => Error.TableTooLarge,
|
|
//binding.TEXTURE_TOO_LARGE_EXT => "Texture too large",
|
|
else => "unknown error",
|
|
};
|
|
|
|
std.log.scoped(.OpenGL).err("OpenGL Failure: {s}\n", .{name});
|
|
}
|
|
}
|
|
|
|
pub const DrawCommand = struct {
|
|
key: DrawCommandKey = .{},
|
|
mesh: AssetManager.Handle.Mesh,
|
|
material_override: ?Material,
|
|
transform: Mat4,
|
|
};
|
|
|
|
pub const Attrib = enum(gl.GLuint) {
|
|
Position = 0,
|
|
Normal = 1,
|
|
UV = 2,
|
|
Tangent = 3,
|
|
|
|
pub inline fn value(self: Attrib) gl.GLuint {
|
|
return @intFromEnum(self);
|
|
}
|
|
};
|
|
pub const UBO = enum(gl.GLuint) {
|
|
CameraMatrices = 0,
|
|
|
|
pub inline fn value(self: UBO) gl.GLuint {
|
|
return @intFromEnum(self);
|
|
}
|
|
};
|
|
pub const SSBO = enum(gl.GLuint) {
|
|
PointLights = 1,
|
|
Materials = 2,
|
|
DrawCommandData = 3,
|
|
|
|
pub inline fn value(self: SSBO) gl.GLuint {
|
|
return @intFromEnum(self);
|
|
}
|
|
};
|
|
|
|
pub fn getStd430Align(comptime T: type) usize {
|
|
switch (T) {
|
|
Vec2, Vec2_i32 => {
|
|
return 8;
|
|
},
|
|
Vec3, Vec4 => {
|
|
return 16;
|
|
},
|
|
Mat4 => {
|
|
return 16;
|
|
},
|
|
}
|
|
|
|
const info = @typeInfo(T);
|
|
switch (info) {
|
|
.Int => |int| {
|
|
if (int.bits & (int.bits - 1) != 0) {
|
|
@compileError("Non power of two bit size of int");
|
|
}
|
|
const byte_size = int.bits / 8;
|
|
|
|
return @intCast(byte_size);
|
|
},
|
|
.Float => |float| {
|
|
if (float.bits & (float.bits - 1) != 0) {
|
|
@compileError("Non power of two bit size of float");
|
|
}
|
|
const byte_size = float.bits / 8;
|
|
|
|
return @intCast(byte_size);
|
|
},
|
|
.Struct => |str| {
|
|
if (str.layout != .@"extern") {
|
|
@compileError("Structs should be extern for std430");
|
|
}
|
|
|
|
// inline for (str.fields) |field| {
|
|
// field.
|
|
// }
|
|
return 0;
|
|
},
|
|
_ => @compileError("Unknown type for std430 " ++ @typeName(T)),
|
|
}
|
|
}
|
|
|
|
pub const Uniform = enum(gl.GLint) {
|
|
ModelMatrix = 1,
|
|
Color = 2,
|
|
AlbedoMap = 3,
|
|
AlbedoMapUVScale = 4,
|
|
NormalMap = 5,
|
|
NormalMapUVScale = 6,
|
|
Metallic = 7,
|
|
MetallicMap = 8,
|
|
MetallicMapUVScale = 9,
|
|
Roughness = 10,
|
|
RoughnessMap = 11,
|
|
RoughnessMapUVScale = 12,
|
|
Emission = 13,
|
|
EmissionMap = 14,
|
|
EmissionMapUVScale = 15,
|
|
|
|
ShadowMap2D = 16,
|
|
ShadowMapCube = 17,
|
|
|
|
NearFarPlanes = 18, // vec2 stores near and far planes for perspective projection
|
|
|
|
// Bloom
|
|
SRCMipLevel = 19,
|
|
BloomStrength = 20,
|
|
LightsCount = 21,
|
|
EnvBRDF = 22,
|
|
|
|
pub inline fn value(self: Uniform) gl.GLint {
|
|
return @intFromEnum(self);
|
|
}
|
|
};
|
|
|
|
// TODO: support ortho
|
|
pub const Camera = struct {
|
|
pos: Vec3 = Vec3.zero(),
|
|
|
|
fovy: f32 = 60,
|
|
aspect: f32 = 1,
|
|
near: f32 = 0.1,
|
|
far: f32 = 10,
|
|
|
|
view_mat: Mat4 = Mat4.identity(),
|
|
|
|
pub fn projection(self: *const Camera) Mat4 {
|
|
return math.perspective(self.fovy, self.aspect, self.near, self.far);
|
|
}
|
|
};
|
|
|
|
// Should be std140
|
|
const CameraMatrices = extern struct {
|
|
projection: Mat4 = Mat4.identity(),
|
|
view: Mat4 = Mat4.identity(),
|
|
};
|
|
pub const Light = extern struct {
|
|
pos: Vec4, // x, y, z, w - vPos
|
|
color_radius: Vec4, // x, y, z - color, w - radius
|
|
view_mat: Mat4 = Mat4.identity(),
|
|
|
|
// for directional lights contains view projection matrices for each split
|
|
// TODO: comprejk ss this somehow
|
|
view_proj_mats: [4]Mat4 = undefined,
|
|
|
|
// Usese floats because it's a vec4 on the other end
|
|
params: extern struct {
|
|
near: f32,
|
|
far: f32,
|
|
shadow_map_idx: f32,
|
|
csm_split_count: f32,
|
|
},
|
|
csm_split_points: [4]f32 = undefined,
|
|
|
|
/// Alignment of this struct if it was in a std430 array
|
|
pub fn alignStd430() usize {
|
|
return @alignOf(Light);
|
|
}
|
|
|
|
/// Aligned size of this struct if it was in a std430 array
|
|
pub fn sizeOfStd430() usize {
|
|
return @sizeOf(Light);
|
|
// return std.mem.alignForward(usize, @sizeOf(Light), Light.alignStd430());
|
|
}
|
|
};
|
|
|
|
const LightSSBO = BufferSSBOAlign(Light, 16);
|
|
|
|
// Shader struct for material data
|
|
pub const MaterialPBR = extern struct {
|
|
albedo: Vec4,
|
|
albedo_map: gl.GLuint64,
|
|
albedo_map_uv_scale: Vec2,
|
|
normal_map: gl.GLuint64,
|
|
normal_map_uv_scale: Vec2,
|
|
metallic: f32,
|
|
metallic_map: gl.GLuint64,
|
|
metallic_map_uv_scale: Vec2,
|
|
roughness: f32,
|
|
roughness_map: gl.GLuint64,
|
|
roughness_map_uv_scale: Vec2,
|
|
emission: Vec3 align(16),
|
|
emission_map: gl.GLuint64,
|
|
emission_map_uv_scale: Vec2,
|
|
|
|
pub fn fromMaterial(assetman: *AssetManager, mat: *const Material) MaterialPBR {
|
|
const albedo_map = assetman.resolveTexture(mat.albedo_map);
|
|
const normal_map = assetman.resolveTexture(mat.normal_map);
|
|
const metallic_map = assetman.resolveTexture(mat.metallic_map);
|
|
const roughness_map = assetman.resolveTexture(mat.roughness_map);
|
|
const emission_map = assetman.resolveTexture(mat.emission_map);
|
|
return .{
|
|
.albedo = mat.albedo,
|
|
.albedo_map = albedo_map.handle,
|
|
.albedo_map_uv_scale = albedo_map.uv_scale,
|
|
.normal_map = normal_map.handle,
|
|
.normal_map_uv_scale = normal_map.uv_scale,
|
|
.metallic = mat.metallic,
|
|
.metallic_map = metallic_map.handle,
|
|
.metallic_map_uv_scale = metallic_map.uv_scale,
|
|
.roughness = mat.roughness,
|
|
.roughness_map = roughness_map.handle,
|
|
.roughness_map_uv_scale = roughness_map.uv_scale,
|
|
.emission = mat.emission,
|
|
.emission_map = emission_map.handle,
|
|
.emission_map_uv_scale = emission_map.uv_scale,
|
|
};
|
|
}
|
|
|
|
/// Alignment of this struct if it was in a std430 array
|
|
pub fn alignStd430() usize {
|
|
return @alignOf(MaterialPBR);
|
|
}
|
|
|
|
/// Aligned size of this struct if it was in a std430 array
|
|
pub fn sizeOfStd430() usize {
|
|
return @sizeOf(MaterialPBR);
|
|
//return std.mem.alignForward(usize, @sizeOf(MaterialPBR), MaterialPBR.alignStd430());
|
|
}
|
|
};
|
|
|
|
pub fn BufferSSBO(comptime T: type) type {
|
|
return BufferSSBOAlign(T, @alignOf(T));
|
|
}
|
|
|
|
// Helper struct for using ssbo arrays with count
|
|
// It provides a coherent always mapped buffer
|
|
pub fn BufferSSBOAlign(comptime T: type, comptime alignment: usize) type {
|
|
switch (@typeInfo(T)) {
|
|
.Struct => |str| {
|
|
if (str.layout != .@"extern") {
|
|
@compileError("Use extern layout for SSBO structs");
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
return struct {
|
|
pub const BufferInstance = struct {
|
|
count: *c_uint,
|
|
data: []T align(alignment),
|
|
};
|
|
|
|
// Helper struct to calculate buffer sizes
|
|
// not actually used
|
|
const BufferLayout = extern struct {
|
|
count: c_uint,
|
|
_start: [0]T align(alignment),
|
|
|
|
pub fn calculateBufSize(max_count: usize, ssbo_align: usize) usize {
|
|
return std.mem.alignForward(usize, @sizeOf(BufferLayout) + std.mem.alignForward(usize, @sizeOf(T), alignment) * max_count, ssbo_align);
|
|
}
|
|
|
|
pub fn getData(self: *BufferLayout, len: usize) ([]align(alignment) T) {
|
|
var data_c: [*]align(alignment) T = @ptrFromInt(@intFromPtr(self) + @offsetOf(BufferLayout, "_start"));
|
|
|
|
return @alignCast(data_c[0..len]);
|
|
}
|
|
};
|
|
|
|
const Self = @This();
|
|
|
|
len: usize = 0,
|
|
/// How many buffer instances of length `len` are in a single GL buffer
|
|
len_buffers: usize = 0,
|
|
buffer: gl.GLuint = 0,
|
|
data: []u8 = &.{},
|
|
|
|
// Don't like duplicating it here, but don't have a better idea
|
|
ssbo_align: usize = 0,
|
|
|
|
pub fn init(ssbo_align: usize, len: usize, num_buffers: usize) !Self {
|
|
var result = Self{
|
|
.len = len,
|
|
.len_buffers = num_buffers,
|
|
.ssbo_align = ssbo_align,
|
|
};
|
|
|
|
gl.createBuffers(1, &result.buffer);
|
|
if (result.buffer == 0) {
|
|
checkGLError();
|
|
return error.CreateBuffers;
|
|
}
|
|
|
|
const PERSISTENT_BUFFER_FLAGS: gl.GLbitfield = gl.MAP_PERSISTENT_BIT | gl.MAP_WRITE_BIT | gl.MAP_COHERENT_BIT;
|
|
|
|
const buf_size = BufferLayout.calculateBufSize(len, ssbo_align) * num_buffers;
|
|
gl.namedBufferStorage(
|
|
result.buffer,
|
|
@intCast(buf_size),
|
|
null,
|
|
PERSISTENT_BUFFER_FLAGS,
|
|
);
|
|
const data_c: [*]u8 = @ptrCast(gl.mapNamedBufferRange(
|
|
result.buffer,
|
|
0,
|
|
@intCast(buf_size),
|
|
PERSISTENT_BUFFER_FLAGS,
|
|
) orelse {
|
|
checkGLError();
|
|
@panic("bind point_lights_ssbo");
|
|
});
|
|
|
|
result.data = data_c[0..buf_size];
|
|
|
|
return result;
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
gl.deleteBuffers(1, &self.buffer);
|
|
self.buffer = 0;
|
|
self.data = &.{};
|
|
}
|
|
|
|
pub fn getInstance(self: *Self, index: usize) BufferInstance {
|
|
std.debug.assert(index < self.len_buffers);
|
|
|
|
const layout: *BufferLayout = @alignCast(@ptrCast(self.data[index * BufferLayout.calculateBufSize(self.len, self.ssbo_align) ..].ptr));
|
|
|
|
return BufferInstance{
|
|
.count = &layout.count,
|
|
.data = layout.getData(self.len),
|
|
};
|
|
}
|
|
|
|
pub fn bind(self: *const Self, idx: usize, binding: SSBO) void {
|
|
std.debug.assert(idx < self.len_buffers);
|
|
|
|
const size = BufferLayout.calculateBufSize(self.len, self.ssbo_align);
|
|
gl.bindBufferRange(
|
|
gl.SHADER_STORAGE_BUFFER,
|
|
binding.value(),
|
|
self.buffer,
|
|
idx * size,
|
|
@intCast(size),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
const MaterialPBRSSBO = BufferSSBO(MaterialPBR);
|
|
|
|
const DrawCommandData = extern struct {
|
|
transform: Mat4,
|
|
material_index: c_int,
|
|
_pad: [0]void align(16) = std.mem.zeroes([0]void),
|
|
};
|
|
|
|
const DrawCommandDataSSBO = BufferSSBO(DrawCommandData);
|
|
|
|
const DrawIndirectCmd = extern struct {
|
|
count: gl.GLuint,
|
|
instance_count: gl.GLuint,
|
|
first_index: gl.GLuint,
|
|
base_vertex: gl.GLint,
|
|
base_instance: gl.GLuint,
|
|
};
|
|
|
|
fn uboAlignedSizeOf(self: *const Render, comptime T: type) usize {
|
|
return std.mem.alignForward(usize, @sizeOf(T), self.ubo_align);
|
|
}
|
|
|
|
fn ssboAlign(self: *const Render, size: usize) usize {
|
|
return std.mem.alignForward(usize, size, self.ssbo_align);
|
|
}
|
|
|
|
fn ssboAlignedSizeOf(self: *const Render, comptime T: type) usize {
|
|
return self.ssboAlign(@sizeOf(T));
|
|
}
|