Proper multi draw indirect for all meshes!

Also refactoring for SSBO to more easily create them
This commit is contained in:
sergeypdev 2024-07-21 19:48:54 +04:00
parent a39fdad1a0
commit c27e1cecc3
4 changed files with 450 additions and 226 deletions

View File

@ -21,6 +21,27 @@ struct Light {
vec4 csm_split_points; // TODO: Maybe increase to 8, though it's probably too many
};
struct Material {
vec4 albedo;
sampler2D albedo_map;
vec2 albedo_map_uv_scale;
sampler2D normal_map;
vec2 normal_map_uv_scale;
float metallic;
sampler2D metallic_map;
vec2 metallic_map_uv_scale;
float roughness;
sampler2D roughness_map;
vec2 roughness_map_uv_scale;
vec3 emission;
sampler2D emission_map;
vec2 emission_map_uv_scale;
};
struct DrawCmdData {
mat4 transform;
};
// UBOs
layout(std140, binding = 0) uniform Matrices {
mat4 projection;
@ -32,6 +53,17 @@ layout(std430, binding = 1) readonly buffer Lights {
Light lights[];
};
layout(std430, binding = 2) readonly buffer Materials {
uint materials_count;
Material materials[];
};
layout(std430, binding = 3) readonly buffer DrawCmdDatas {
uint draws_count;
// Access by gl_DrawID
DrawCmdData draw_data[];
};
int getShadowMapIndex(int lightIdx) {
return int(lights[lightIdx].params.z);
}
@ -52,28 +84,6 @@ int getCSMSplit(int lightIdx, float depth) {
return totalSplits - 1;
}
// Uniforms
layout(location = 1) uniform mat4 model;
layout(location = 2) uniform vec4 color;
layout(location = 3, bindless_sampler) uniform sampler2D albedo_map;
layout(location = 4) uniform vec2 albedo_map_uv_scale = vec2(1);
layout(location = 5, bindless_sampler) uniform sampler2D normal_map;
layout(location = 6) uniform vec2 normal_map_uv_scale = vec2(1);
layout(location = 7) uniform float metallic;
layout(location = 8, bindless_sampler) uniform sampler2D metallic_map;
layout(location = 9) uniform vec2 metallic_map_uv_scale = vec2(1);
layout(location = 10) uniform float roughness;
layout(location = 11, bindless_sampler) uniform sampler2D roughness_map;
layout(location = 12) uniform vec2 roughness_map_uv_scale = vec2(1);
layout(location = 13) uniform vec3 emission;
layout(location = 14, bindless_sampler) uniform sampler2D emission_map;
layout(location = 15) uniform vec2 emission_map_uv_scale = vec2(1);
layout(location = 16, bindless_sampler) uniform sampler2DArrayShadow shadow_maps;
layout(location = 17, bindless_sampler) uniform samplerCubeArrayShadow cube_shadow_maps;
@ -88,6 +98,8 @@ VERTEX_EXPORT VertexData {
vec3 wNormal;
} VertexOut;
VERTEX_EXPORT flat int DrawID;
float random(vec4 seed4) {
float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
return fract(sin(dot_product) * 43758.5453);
@ -101,6 +113,8 @@ layout(location = 2) in vec2 aUV;
layout(location = 3) in vec3 aTangent;
void main() {
DrawID = gl_DrawID;
mat4 model = draw_data[DrawID].transform;
vec4 vPos = view * model * vec4(aPos.xyz, 1.0);
gl_Position = projection * vPos;
@ -121,25 +135,26 @@ void main() {
out vec4 FragColor;
struct Material {
struct EvalMaterial {
vec4 albedo;
bool metallic;
float roughness;
vec3 emission;
};
Material evalMaterial() {
Material result;
result.albedo = textureSize(albedo_map, 0) == ivec2(0) ? vec4(pow(color.rgb, vec3(2.2)), color.a) : texture(albedo_map, VertexOut.uv * albedo_map_uv_scale);
float fMetallic = textureSize(metallic_map, 0) == ivec2(0) ? metallic : texture(metallic_map, VertexOut.uv * metallic_map_uv_scale).b;
EvalMaterial evalMaterial() {
Material mat = materials[DrawID];
EvalMaterial result;
result.albedo = textureSize(mat.albedo_map, 0) == ivec2(0) ? vec4(pow(mat.albedo.rgb, vec3(2.2)), mat.albedo.a) : texture(mat.albedo_map, VertexOut.uv * mat.albedo_map_uv_scale);
float fMetallic = textureSize(mat.metallic_map, 0) == ivec2(0) ? mat.metallic : texture(mat.metallic_map, VertexOut.uv * mat.metallic_map_uv_scale).b;
result.metallic = fMetallic > 0.1;
result.roughness = max(0.01, textureSize(roughness_map, 0) == ivec2(0) ? roughness : texture(roughness_map, VertexOut.uv * roughness_map_uv_scale).g);
result.emission = textureSize(emission_map, 0) == ivec2(0) ? emission : texture(emission_map, VertexOut.uv * emission_map_uv_scale).rgb;
result.roughness = max(0.01, textureSize(mat.roughness_map, 0) == ivec2(0) ? mat.roughness : texture(mat.roughness_map, VertexOut.uv * mat.roughness_map_uv_scale).g);
result.emission = textureSize(mat.emission_map, 0) == ivec2(0) ? mat.emission : texture(mat.emission_map, VertexOut.uv * mat.emission_map_uv_scale).rgb;
return result;
}
vec3 schlickFresnel(Material mat, float LDotH) {
vec3 schlickFresnel(EvalMaterial mat, float LDotH) {
vec3 f0 = vec3(0.04); // dielectric
if (mat.metallic) {
f0 = mat.albedo.rgb;
@ -150,13 +165,13 @@ vec3 schlickFresnel(Material mat, float LDotH) {
const float eps = 0.0000001;
float geomSmith(Material mat, float DotVal) {
float geomSmith(EvalMaterial mat, float DotVal) {
float k = (mat.roughness + 1.0) * (mat.roughness + 1.0) / 8.0;
float denom = DotVal * (1 - k) + k;
return 1.0 / max(denom, eps);
}
float ggxDistribution(Material mat, float NDotH) {
float ggxDistribution(EvalMaterial mat, float NDotH) {
float alpha2 = mat.roughness * mat.roughness * mat.roughness * mat.roughness;
float d = (NDotH * NDotH) * (alpha2 - 1) + 1;
return alpha2 / max((PI * d * d), eps);
@ -191,7 +206,7 @@ float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
vec3 microfacetModel(Material mat, int light_idx, Light light, vec3 P, vec3 N) {
vec3 microfacetModel(EvalMaterial mat, int light_idx, Light light, vec3 P, vec3 N) {
int csm_split_idx = getCSMSplit(light_idx, P.z);
// Visualize CSM splits
//mat.albedo = vec4(mix(mat.albedo.rgb, csm_split_colors[csm_split_idx], 0.8), mat.albedo.a);
@ -290,9 +305,10 @@ vec3 microfacetModel(Material mat, int light_idx, Light light, vec3 P, vec3 N) {
}
void main() {
Material material = evalMaterial();
Material mat = materials[DrawID];
EvalMaterial material = evalMaterial();
vec3 N = textureSize(normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(normal_map, VertexOut.uv * normal_map_uv_scale).xy, 0);
vec3 N = textureSize(mat.normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(mat.normal_map, VertexOut.uv * mat.normal_map_uv_scale).xy, 0);
N = N * 2.0 - 1.0;
N.z = sqrt(clamp(1 - N.x * N.x - N.y * N.y, 0, 1));
N = normalize(N);

View File

@ -323,17 +323,17 @@ fn loadMeshErr(self: *AssetManager, id: AssetId) !LoadedMesh {
const vertex_offset = allocation.vertex.offset;
gl.namedBufferSubData(self.vertex_heap.vertices, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.vertices.ptr));
gl.namedBufferSubData(self.vertex_heap.vertices.buffer, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.vertices.ptr));
checkGLError();
gl.namedBufferSubData(self.vertex_heap.normals, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.normals.ptr));
gl.namedBufferSubData(self.vertex_heap.normals.buffer, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.normals.ptr));
checkGLError();
gl.namedBufferSubData(self.vertex_heap.tangents, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.tangents.ptr));
gl.namedBufferSubData(self.vertex_heap.tangents.buffer, @intCast(vertex_offset * @sizeOf(formats.Vector3)), @intCast(vertices_len * @sizeOf(formats.Vector3)), @ptrCast(mesh.tangents.ptr));
checkGLError();
gl.namedBufferSubData(self.vertex_heap.uvs, @intCast(vertex_offset * @sizeOf(formats.Vector2)), @intCast(vertices_len * @sizeOf(formats.Vector2)), @ptrCast(mesh.uvs.ptr));
gl.namedBufferSubData(self.vertex_heap.uvs.buffer, @intCast(vertex_offset * @sizeOf(formats.Vector2)), @intCast(vertices_len * @sizeOf(formats.Vector2)), @ptrCast(mesh.uvs.ptr));
checkGLError();
const index_offset = allocation.index.offset;
gl.namedBufferSubData(self.vertex_heap.indices, @intCast(index_offset * @sizeOf(formats.Index)), @intCast(mesh.indices.len * @sizeOf(formats.Index)), @ptrCast(mesh.indices.ptr));
gl.namedBufferSubData(self.vertex_heap.indices.buffer, @intCast(index_offset * @sizeOf(formats.Index)), @intCast(mesh.indices.len * @sizeOf(formats.Index)), @ptrCast(mesh.indices.ptr));
const loaded_mesh = LoadedMesh{
.aabb = .{
@ -343,27 +343,27 @@ fn loadMeshErr(self: *AssetManager, id: AssetId) !LoadedMesh {
.heap_handle = allocation,
.material = mesh.material,
.positions = .{
.buffer = self.vertex_heap.vertices,
.buffer = self.vertex_heap.vertices.buffer,
.offset = @intCast(vertex_offset * @sizeOf(formats.Vector3)),
.stride = @sizeOf(formats.Vector3),
},
.normals = .{
.buffer = self.vertex_heap.normals,
.buffer = self.vertex_heap.normals.buffer,
.offset = @intCast(vertex_offset * @sizeOf(formats.Vector3)),
.stride = @sizeOf(formats.Vector3),
},
.tangents = .{
.buffer = self.vertex_heap.tangents,
.buffer = self.vertex_heap.tangents.buffer,
.offset = @intCast(vertex_offset * @sizeOf(formats.Vector3)),
.stride = @sizeOf(formats.Vector3),
},
.uvs = .{
.buffer = self.vertex_heap.uvs,
.buffer = self.vertex_heap.uvs.buffer,
.offset = @intCast(vertex_offset * @sizeOf(formats.Vector2)),
.stride = @sizeOf(formats.Vector2),
},
.indices = .{
.buffer = self.vertex_heap.indices,
.buffer = self.vertex_heap.indices.buffer,
.offset = @intCast(index_offset * @sizeOf(formats.Index)),
.count = @intCast(mesh.indices.len),
.type = gl.UNSIGNED_INT,
@ -567,7 +567,7 @@ pub const BufferSlice = struct {
pub const IndexSlice = struct {
buffer: gl.GLuint,
offset: gl.GLuint,
count: gl.GLsizei,
count: gl.GLuint,
type: gl.GLenum,
base_vertex: gl.GLint,
@ -587,8 +587,8 @@ pub const ShaderType = enum {
};
}
const VERTEX_DEFINES = "#version 450 core\n#define VERTEX_SHADER 1\n#define VERTEX_EXPORT out\n";
const FRAGMENT_DEFINES = "#version 450 core\n#define FRAGMENT_SHADER 1\n#define VERTEX_EXPORT in\n";
const VERTEX_DEFINES = "#version 460 core\n#define VERTEX_SHADER 1\n#define VERTEX_EXPORT out\n";
const FRAGMENT_DEFINES = "#version 460 core\n#define FRAGMENT_SHADER 1\n#define VERTEX_EXPORT in\n";
pub fn getDefines(self: ShaderType) []const u8 {
return switch (self) {
.vertex => VERTEX_DEFINES,
@ -729,13 +729,29 @@ const VertexBufferHeap = struct {
index: BuddyAllocator.Alloc = .{},
};
pub const Buffer = struct {
buffer: gl.GLuint,
stride: gl.GLsizei,
pub fn init(name: gl.GLuint, stride: usize) Buffer {
return .{
.buffer = name,
.stride = @intCast(stride),
};
}
pub fn bind(self: *const Buffer, index: gl.GLuint) void {
gl.bindVertexBuffer(index, self.buffer, 0, self.stride);
}
};
vertex_buddy: BuddyAllocator,
index_buddy: BuddyAllocator,
vertices: gl.GLuint,
normals: gl.GLuint,
tangents: gl.GLuint,
uvs: gl.GLuint,
indices: gl.GLuint,
vertices: Buffer,
normals: Buffer,
tangents: Buffer,
uvs: Buffer,
indices: Buffer,
pub fn init(allocator: std.mem.Allocator) !Self {
// 256 mega vertices :)
@ -760,38 +776,38 @@ const VertexBufferHeap = struct {
}
}
const vertices = bufs[0];
const normals = bufs[1];
const tangents = bufs[2];
const uvs = bufs[3];
const indices = bufs[4];
const vertices = Buffer.init(bufs[0], @sizeOf(formats.Vector3));
const normals = Buffer.init(bufs[1], @sizeOf(formats.Vector3));
const tangents = Buffer.init(bufs[2], @sizeOf(formats.Vector3));
const uvs = Buffer.init(bufs[3], @sizeOf(formats.Vector2));
const indices = Buffer.init(bufs[4], @sizeOf(formats.Index));
gl.namedBufferStorage(
vertices,
vertices.buffer,
@intCast(vertex_buf_size * @sizeOf(formats.Vector3)),
null,
gl.DYNAMIC_STORAGE_BIT,
);
gl.namedBufferStorage(
normals,
normals.buffer,
@intCast(vertex_buf_size * @sizeOf(formats.Vector3)),
null,
gl.DYNAMIC_STORAGE_BIT,
);
gl.namedBufferStorage(
tangents,
tangents.buffer,
@intCast(vertex_buf_size * @sizeOf(formats.Vector3)),
null,
gl.DYNAMIC_STORAGE_BIT,
);
gl.namedBufferStorage(
uvs,
uvs.buffer,
@intCast(vertex_buf_size * @sizeOf(formats.Vector2)),
null,
gl.DYNAMIC_STORAGE_BIT,
);
gl.namedBufferStorage(
indices,
indices.buffer,
@intCast(index_buf_size * @sizeOf(formats.Index)),
null,
gl.DYNAMIC_STORAGE_BIT,

View File

@ -19,6 +19,7 @@ pub const MAX_FRAMES_QUEUED = 3;
pub const MAX_LIGHTS = 8;
pub const MAX_DRAW_COMMANDS = 4096;
pub const MAX_LIGHT_COMMANDS = 2048;
pub const MAX_MATERIALS = MAX_DRAW_COMMANDS;
pub const CSM_SPLITS = 4;
pub const DIRECTIONAL_SHADOW_MAP_SIZE = 2048;
// affects how cascades are split
@ -40,12 +41,16 @@ 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 = &.{},
point_lights_ssbo: gl.GLuint = 0,
point_lights: []u8 = &.{}, // TODO: remove
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,
@ -68,6 +73,8 @@ 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,
@ -154,28 +161,11 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
render.camera_matrices = camera_matrices_c[0..buf_size];
}
// Point lights ubo
// SSBOs
{
gl.createBuffers(1, &render.point_lights_ssbo);
std.debug.assert(render.camera_ubo != 0);
const buf_size = render.ssboAlign(@sizeOf(LightArraySSBO) + Light.sizeOfStd430() * MAX_LIGHTS) * MAX_FRAMES_QUEUED;
gl.namedBufferStorage(
render.point_lights_ssbo,
@intCast(buf_size),
null,
PERSISTENT_BUFFER_FLAGS,
);
const point_lights_c: [*]u8 = @ptrCast(gl.mapNamedBufferRange(
render.point_lights_ssbo,
0,
@intCast(buf_size),
PERSISTENT_BUFFER_FLAGS,
) orelse {
checkGLError();
@panic("bind point_lights_ssbo");
});
render.point_lights = point_lights_c[0..buf_size];
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()");
}
{
@ -329,6 +319,14 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
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;
}
@ -399,24 +397,13 @@ pub fn begin(self: *Render) void {
}
}
fn getLightBuffer(self: *Render) *LightArraySSBO {
return @alignCast(@ptrCast(self.point_lights[self.tripple_buffer_index * self.ssboAlign(@sizeOf(LightArraySSBO) + Light.sizeOfStd430() * MAX_LIGHTS) ..].ptr));
}
// TODO: get rid of this
pub fn flushUBOs(self: *Render) void {
const idx = self.tripple_buffer_index;
const light_array_size = self.ssboAlign(@sizeOf(LightArraySSBO) + Light.sizeOfStd430() * MAX_LIGHTS);
// gl.flushMappedNamedBufferRange(self.point_lights_ssbo, idx * @sizeOf(PointLightArray), @sizeOf(PointLightArray));
gl.bindBufferRange(
gl.SHADER_STORAGE_BUFFER,
SSBO.PointLights.value(),
self.point_lights_ssbo,
idx * light_array_size,
@intCast(light_array_size),
);
checkGLError();
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 {
@ -504,9 +491,8 @@ pub fn finish(self: *Render) void {
}.lessThan);
}
const lights_buf = self.getLightBuffer();
lights_buf.count = 0;
const lights_buf_lights = lights_buf.getLights();
const lights_buf = self.lights_ssbo.getInstance(self.tripple_buffer_index);
lights_buf.count.* = 0;
var dir_view_proj_mat: [CSM_SPLITS]Mat4 = undefined;
@ -522,11 +508,11 @@ pub fn finish(self: *Render) void {
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.shadow).program);
for (lights) |light_cmd| {
const i = lights_buf.count;
if (i == lights_buf_lights.len) break;
const i = lights_buf.count.*;
if (i == lights_buf.data.len) break;
const light = &lights_buf_lights[i];
lights_buf.count += 1;
const light = &lights_buf.data[i];
lights_buf.count.* += 1;
switch (light_cmd) {
.directional => |dir_light| {
@ -707,7 +693,7 @@ pub fn finish(self: *Render) void {
}
// Light world space to view space
for (lights_buf_lights[0..lights_buf.count]) |*light| {
for (lights_buf.data[0..lights_buf.count.*]) |*light| {
light.pos = self.camera.view_mat.mulByVec4(light.pos);
}
@ -754,101 +740,80 @@ pub fn finish(self: *Render) void {
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program);
gl.bindVertexArray(self.mesh_vao);
var switched_to_alpha_blend = false;
const switched_to_alpha_blend = false;
gl.disable(gl.BLEND);
self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value());
checkGLError();
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();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer);
checkGLError();
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);
const draw_indirect_cmds_c: [*]u8 = @ptrCast(gl.mapNamedBuffer(
self.draw_indirect_buffer,
gl.WRITE_ONLY,
) orelse {
checkGLError();
@panic("map draw indirect buffer");
});
var draw_indirect_cmds = std.mem.bytesAsSlice(DrawIndirectCmd, draw_indirect_cmds_c[0 .. @sizeOf(DrawIndirectCmd) * MAX_DRAW_COMMANDS]);
const materials = self.materials_pbr_ssbo.getInstance(self.tripple_buffer_index);
materials.count.* = 0;
const draw_cmd_data = self.draw_cmd_data_ssbo.getInstance(self.tripple_buffer_index);
var rendered_count: usize = 0;
for (self.command_buffer[0..self.command_count]) |*cmd| {
cmds: 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;
}
rendered_count += 1;
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) {
switched_to_alpha_blend = true;
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}
break :cmds;
// switched_to_alpha_blend = true;
// gl.enable(gl.BLEND);
// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}
gl.uniformMatrix4fv(Uniform.ModelMatrix.value(), 1, gl.FALSE, @ptrCast(&cmd.transform.data));
{
gl.uniform4fv(Uniform.Color.value(), 1, @ptrCast(&material.albedo.data));
draw_cmd_data.data[rendered_count] = DrawCommandData{
.transform = cmd.transform,
};
const albedo_map = self.assetman.resolveTexture(material.albedo_map);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.AlbedoMap.value(),
albedo_map.handle,
);
gl.uniform2fv(Uniform.AlbedoMapUVScale.value(), 1, @ptrCast(&albedo_map.uv_scale.data));
}
{
const normal_map = self.assetman.resolveTexture(material.normal_map);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.NormalMap.value(),
normal_map.handle,
);
gl.uniform2fv(Uniform.NormalMapUVScale.value(), 1, @ptrCast(&normal_map.uv_scale.data));
}
{
gl.uniform1fv(Uniform.Metallic.value(), 1, &material.metallic);
materials.data[rendered_count] = MaterialPBR.fromMaterial(self.assetman, &material);
materials.count.* += 1;
const metallic_map = self.assetman.resolveTexture(material.metallic_map);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.MetallicMap.value(),
metallic_map.handle,
);
gl.uniform2fv(Uniform.MetallicMapUVScale.value(), 1, @ptrCast(&metallic_map.uv_scale.data));
}
{
gl.uniform1fv(Uniform.Roughness.value(), 1, &material.roughness);
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 = 0,
.transform = cmd.transform,
};
const roughness_map = self.assetman.resolveTexture(material.roughness_map);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.RoughnessMap.value(),
roughness_map.handle,
);
gl.uniform2fv(Uniform.RoughnessMapUVScale.value(), 1, @ptrCast(&roughness_map.uv_scale.data));
rendered_count += 1;
}
{
gl.uniform3fv(Uniform.Emission.value(), 1, @ptrCast(&material.emission.data));
const emission_map = self.assetman.resolveTexture(material.emission_map);
gl.GL_ARB_bindless_texture.uniformHandleui64ARB(
Uniform.EmissionMap.value(),
emission_map.handle,
);
gl.uniform2fv(Uniform.EmissionMapUVScale.value(), 1, @ptrCast(&emission_map.uv_scale.data));
}
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);
_ = gl.unmapNamedBuffer(self.draw_indirect_buffer);
mesh.positions.bind(Render.Attrib.Position.value());
checkGLError();
mesh.normals.bind(Render.Attrib.Normal.value());
checkGLError();
mesh.tangents.bind(Render.Attrib.Tangent.value());
checkGLError();
mesh.uvs.bind(Render.Attrib.UV.value());
checkGLError();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices.buffer);
checkGLError();
gl.drawElementsBaseVertex(
gl.TRIANGLES,
mesh.indices.count,
mesh.indices.type,
@ptrFromInt(mesh.indices.offset),
mesh.indices.base_vertex,
);
checkGLError();
}
gl.bindBuffer(gl.DRAW_INDIRECT_BUFFER, self.draw_indirect_buffer);
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_count), @sizeOf(DrawIndirectCmd));
gl.disable(gl.BLEND);
@ -882,7 +847,7 @@ pub fn finish(self: *Render) void {
gl.uniformMatrix4fv(Uniform.ModelMatrix.value(), 1, gl.FALSE, @ptrCast(&frustum_model_mat.data));
gl.drawElementsBaseVertex(
gl.TRIANGLES,
mesh.indices.count,
@intCast(mesh.indices.count),
mesh.indices.type,
@ptrFromInt(mesh.indices.offset),
mesh.indices.base_vertex,
@ -901,7 +866,7 @@ pub fn finish(self: *Render) void {
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, mesh.indices.count, mesh.indices.type, @ptrFromInt(mesh.indices.offset), mesh.indices.base_vertex);
gl.drawElementsBaseVertex(gl.TRIANGLES, @intCast(mesh.indices.count), mesh.indices.type, @ptrFromInt(mesh.indices.offset), mesh.indices.base_vertex);
}
}
}
@ -941,7 +906,7 @@ pub fn finish(self: *Render) void {
gl.drawElementsBaseVertex(
gl.TRIANGLES,
quad.indices.count,
@intCast(quad.indices.count),
quad.indices.type,
@ptrFromInt(quad.indices.offset),
quad.indices.base_vertex,
@ -968,7 +933,7 @@ pub fn finish(self: *Render) void {
gl.drawElementsBaseVertex(
gl.TRIANGLES,
quad.indices.count,
@intCast(quad.indices.count),
quad.indices.type,
@ptrFromInt(quad.indices.offset),
quad.indices.base_vertex,
@ -988,7 +953,7 @@ pub fn finish(self: *Render) void {
gl.bindTextureUnit(0, self.screen_color_texture);
defer gl.bindTextureUnit(0, 0);
gl.drawElementsBaseVertex(gl.TRIANGLES, quad.indices.count, quad.indices.type, @ptrFromInt(quad.indices.offset), quad.indices.base_vertex);
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);
@ -1058,7 +1023,7 @@ fn renderShadow(self: *Render, frustum: *const math.Frustum) void {
gl.drawElementsBaseVertex(
gl.TRIANGLES,
mesh.indices.count,
@intCast(mesh.indices.count),
mesh.indices.type,
@ptrFromInt(mesh.indices.offset),
mesh.indices.base_vertex,
@ -1116,12 +1081,59 @@ pub const UBO = enum(gl.GLuint) {
};
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,
@ -1194,25 +1206,205 @@ pub const Light = extern struct {
/// Alignment of this struct if it was in a std430 array
pub fn alignStd430() usize {
return 16;
return @alignOf(Light);
}
/// Aligned size of this struct if it was in a std430 array
pub fn sizeOfStd430() usize {
return std.mem.alignForward(usize, @sizeOf(Light), Light.alignStd430());
return @sizeOf(Light);
// return std.mem.alignForward(usize, @sizeOf(Light), Light.alignStd430());
}
};
// TODO: rename
pub const LightArraySSBO = extern struct {
count: c_uint,
// Zero sized field that just has the right alignment
_lights_start: [0]Light align(Light.alignStd430()),
const LightSSBO = BufferSSBO(Light);
pub fn getLights(self: *LightArraySSBO) []align(Light.alignStd430()) Light {
var lights_c: [*]align(Light.alignStd430()) Light = @ptrFromInt(@intFromPtr(self) + @offsetOf(LightArraySSBO, "_lights_start"));
return lights_c[0..MAX_LIGHTS];
// 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(BufferInstance) + @sizeOf(T) * max_count, ssbo_align);
}
pub fn getData(self: *BufferLayout, len: usize) []T {
var data_c: [*]T = @ptrFromInt(@intFromPtr(self) + @offsetOf(BufferLayout, "_start"));
return 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,
};
const DrawCommandDataSSBO = BufferSSBOAlign(DrawCommandData, 16);
const DrawIndirectCmd = extern struct {
count: gl.GLuint,
instance_count: gl.GLuint,
first_index: gl.GLuint,
base_vertex: gl.GLint,
base_instance: gl.GLuint,
transform: Mat4,
};
fn uboAlignedSizeOf(self: *const Render, comptime T: type) usize {

View File

@ -193,35 +193,35 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
.rotate = .{ .axis = Vec3.up(), .rate = -10 },
});
// const light_root = globals.g_mem.world.addEntity(.{
// .flags = .{ .rotate = true },
// .transform = .{ .pos = Vec3.new(0, 0.1, 0) },
// .rotate = .{ .axis = Vec3.up(), .rate = 60 },
// });
const light_root = globals.g_mem.world.addEntity(.{
.flags = .{ .rotate = true },
.transform = .{ .pos = Vec3.new(0, 0.1, 0) },
.rotate = .{ .axis = Vec3.up(), .rate = 60 },
});
// const light1 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1.8, 1, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0) },
// .point_light = .{ .radius = 0.1 },
// .rotate = .{ .axis = Vec3.up(), .rate = -40 },
// });
// light1.ptr.setParent(light_root.handle);
const light1 = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(1.8, 1, 0) },
.flags = .{ .point_light = true, .rotate = true },
.light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0) },
.point_light = .{ .radius = 0.1 },
.rotate = .{ .axis = Vec3.up(), .rate = -40 },
});
light1.ptr.setParent(light_root.handle);
// const light2 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(-2, 0, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0) },
// .point_light = .{ .radius = 0.1 },
// });
// light2.ptr.setParent(light1.handle);
const light2 = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(-2, 0, 0) },
.flags = .{ .point_light = true, .rotate = true },
.light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0) },
.point_light = .{ .radius = 0.1 },
});
light2.ptr.setParent(light1.handle);
// _ = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1, 0.5, 4) },
// .flags = .{ .point_light = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 10.0) },
// .point_light = .{ .radius = 1 },
// });
_ = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(1, 0.5, 4) },
.flags = .{ .point_light = true },
.light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 10.0) },
.point_light = .{ .radius = 1 },
});
// Plane
_ = globals.g_mem.world.addEntity(.{