mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-01 05:42:44 +01:00
Cull lights into clusters in a compute pass
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "stdafx.h"
|
||||
#include "cluster_grid.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
gl::cluster_grid::cluster_grid()
|
||||
@@ -17,6 +18,17 @@ gl::cluster_grid::cluster_grid()
|
||||
m_params_buffer.update(m_params);
|
||||
}
|
||||
|
||||
gl::cluster_grid::~cluster_grid()
|
||||
{
|
||||
for (auto &slot : m_readback)
|
||||
{
|
||||
// fences first, they belong to the copies
|
||||
slot.copied.reset();
|
||||
if (slot.buffer != 0)
|
||||
glDeleteBuffers(1, &slot.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void gl::cluster_grid::bind()
|
||||
{
|
||||
m_aabbs.bind_base();
|
||||
@@ -64,3 +76,53 @@ void gl::cluster_grid::build(program &build_program)
|
||||
memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_boxes_stale = false;
|
||||
}
|
||||
|
||||
void gl::cluster_grid::cull(program &cull_program)
|
||||
{
|
||||
collect_stats();
|
||||
|
||||
bind();
|
||||
// the counters are the allocator of the index list and the count of dropped lights: every pass starts them anew
|
||||
m_counters.update(cluster_counters{});
|
||||
cull_program.dispatch((CLUSTER_COUNT + CLUSTER_CULL_GROUP_SIZE - 1) / CLUSTER_CULL_GROUP_SIZE);
|
||||
// the clusters are read by the shading, and the counters by the copy below and then by the next pass writing them
|
||||
memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
// a copy of the counters for the cpu to read when the gpu gets there; if it is far behind, this frame is not reported
|
||||
if (m_readback_used < READBACK_SLOTS)
|
||||
{
|
||||
auto &slot{m_readback[(m_readback_first + m_readback_used) % READBACK_SLOTS]};
|
||||
if (slot.buffer == 0)
|
||||
{
|
||||
glCreateBuffers(1, &slot.buffer);
|
||||
glNamedBufferStorage(slot.buffer, sizeof(cluster_counters), nullptr, GL_CLIENT_STORAGE_BIT);
|
||||
}
|
||||
glCopyNamedBufferSubData(m_counters, slot.buffer, 0, 0, sizeof(cluster_counters));
|
||||
slot.copied = std::make_unique<fence>();
|
||||
++m_readback_used;
|
||||
}
|
||||
}
|
||||
|
||||
void gl::cluster_grid::collect_stats()
|
||||
{
|
||||
// oldest first, as long as the gpu has finished the copies
|
||||
while (m_readback_used > 0)
|
||||
{
|
||||
auto &slot{m_readback[m_readback_first]};
|
||||
if (!slot.copied->is_signalled())
|
||||
break;
|
||||
|
||||
cluster_counters counters{};
|
||||
glGetNamedBufferSubData(slot.buffer, 0, sizeof(counters), &counters);
|
||||
slot.copied.reset();
|
||||
|
||||
cull_stats stats{};
|
||||
stats.index_count = counters.index_count;
|
||||
stats.overflow_count = counters.overflow_count;
|
||||
stats.overflow_peak = std::max(counters.overflow_count, m_stats ? m_stats->overflow_peak : 0U);
|
||||
m_stats = stats;
|
||||
|
||||
m_readback_first = (m_readback_first + 1) % READBACK_SLOTS;
|
||||
--m_readback_used;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "fence.h"
|
||||
#include "ssbo.h"
|
||||
#include "ubo.h"
|
||||
#include "shader.h"
|
||||
@@ -19,7 +24,21 @@ namespace gl
|
||||
class cluster_grid
|
||||
{
|
||||
public:
|
||||
// what the culling pass reports about a frame
|
||||
struct cull_stats
|
||||
{
|
||||
// entries of the shared index list in use
|
||||
uint32_t index_count = 0;
|
||||
// (cluster, light) pairs dropped because the cluster was full
|
||||
uint32_t overflow_count = 0;
|
||||
// highest overflow_count seen since the grid was created
|
||||
uint32_t overflow_peak = 0;
|
||||
};
|
||||
|
||||
cluster_grid();
|
||||
~cluster_grid();
|
||||
cluster_grid(const cluster_grid &) = delete;
|
||||
cluster_grid &operator=(const cluster_grid &) = delete;
|
||||
|
||||
void bind();
|
||||
|
||||
@@ -29,6 +48,11 @@ namespace gl
|
||||
// recomputes the boxes of the clusters in view space with the program made from cluster_build.comp.
|
||||
// binds the grid, and leaves the boxes ready to be read by the compute passes which follow
|
||||
void build(program &build_program);
|
||||
// assigns the lights of light_ssbo to the clusters with the program made from cluster_cull.comp: zeroes the
|
||||
// counters, fills the (offset, count) of every cluster and the index list, and leaves them ready to be
|
||||
// read by the shading and the debug view which follow. the boxes have to be current (update(), build()) and the
|
||||
// grid valid. also queues the counters for reading back, see stats()
|
||||
void cull(program &cull_program);
|
||||
|
||||
// false if the last update found no grid possible for the pass (orthographic projection)
|
||||
bool valid() const
|
||||
@@ -39,6 +63,12 @@ namespace gl
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
// counters of the most recent culling pass whose result has reached the cpu, which takes a few frames
|
||||
// (read back without waiting for the gpu); empty until there is one
|
||||
std::optional<cull_stats> stats() const
|
||||
{
|
||||
return m_stats;
|
||||
}
|
||||
|
||||
private:
|
||||
// cluster_aabb per cluster
|
||||
@@ -53,5 +83,19 @@ namespace gl
|
||||
|
||||
cluster_ubs m_params{};
|
||||
bool m_boxes_stale = true;
|
||||
|
||||
// counters copied for reading back: a queue of buffers, each with the fence of the copy into it
|
||||
static constexpr size_t READBACK_SLOTS = 4;
|
||||
struct readback_slot
|
||||
{
|
||||
GLuint buffer = 0;
|
||||
std::unique_ptr<fence> copied;
|
||||
};
|
||||
void collect_stats();
|
||||
std::array<readback_slot, READBACK_SLOTS> m_readback;
|
||||
// oldest slot in use and the number of slots in use
|
||||
size_t m_readback_first = 0;
|
||||
size_t m_readback_used = 0;
|
||||
std::optional<cull_stats> m_stats;
|
||||
};
|
||||
} // namespace gl
|
||||
|
||||
@@ -22,6 +22,7 @@ void gl::glsl_common_setup()
|
||||
"#define CLUSTER_COUNT " + std::to_string(CLUSTER_COUNT) + "\n" +
|
||||
"#define MAX_LIGHTS_PER_CLUSTER " + std::to_string(MAX_LIGHTS_PER_CLUSTER) + "\n" +
|
||||
"#define CLUSTER_BUILD_GROUP_SIZE " + std::to_string(CLUSTER_BUILD_GROUP_SIZE) + "\n" +
|
||||
"#define CLUSTER_CULL_GROUP_SIZE " + std::to_string(CLUSTER_CULL_GROUP_SIZE) + "\n" +
|
||||
"#define CLUSTER_UBO_BINDING " + std::to_string(CLUSTER_UBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_AABB_SSBO_BINDING " + std::to_string(CLUSTER_AABB_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_RANGE_SSBO_BINDING " + std::to_string(CLUSTER_RANGE_SSBO_BINDING) + "\n" +
|
||||
@@ -59,8 +60,11 @@ void gl::glsl_common_setup()
|
||||
layout(std430, binding = LIGHT_SSBO_BINDING) readonly buffer light_ssbo
|
||||
{
|
||||
vec3 ambient;
|
||||
// lights stored in lights[], what the cluster culling goes through
|
||||
uint lights_total;
|
||||
|
||||
vec3 fog_color;
|
||||
// lights the shading loop goes through
|
||||
uint lights_count;
|
||||
|
||||
light_s lights[];
|
||||
|
||||
18
gl/ubo.h
18
gl/ubo.h
@@ -156,22 +156,26 @@ namespace gl
|
||||
static_assert(offsetof(light_element, headlight_projection) == 64, "bad layout of light_element");
|
||||
static_assert(offsetof(light_element, headlight_weights) == 128, "bad layout of light_element");
|
||||
|
||||
// capacity of light_data.lights; slot 0 is the sun
|
||||
const size_t MAX_LIGHTS = 8;
|
||||
// capacity of light_data.lights; slot 0 is the sun. a buffer size, not a limit of the shaders: they take the
|
||||
// number of lights from the header
|
||||
const size_t MAX_LIGHTS = 256;
|
||||
|
||||
// contents of light_ssbo: global lighting header followed by the light array
|
||||
struct light_data
|
||||
{
|
||||
packed_vec3 ambient;
|
||||
UBS_PAD(4);
|
||||
// number of lights stored in lights[] (the sun included): what the light clusters are culled from
|
||||
uint32_t lights_total;
|
||||
|
||||
packed_vec3 fog_color;
|
||||
// number of lights (the sun included) the shading loop over lights[] goes through
|
||||
uint32_t lights_count;
|
||||
|
||||
light_element lights[MAX_LIGHTS];
|
||||
};
|
||||
|
||||
static_assert(offsetof(light_data, ambient) == 0, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights_total) == 12, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, fog_color) == 16, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights_count) == 28, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights) == 32, "bad layout of light_data");
|
||||
@@ -186,8 +190,9 @@ namespace gl
|
||||
const GLuint CLUSTER_COUNT = CLUSTER_GRID_X * CLUSTER_GRID_Y * CLUSTER_GRID_Z;
|
||||
// capacity of the light list of a single cluster
|
||||
const GLuint MAX_LIGHTS_PER_CLUSTER = 64;
|
||||
// work group size of cluster_build.comp, which handles one cluster per invocation
|
||||
// work group size of cluster_build.comp and cluster_cull.comp, which handle one cluster per invocation
|
||||
const GLuint CLUSTER_BUILD_GROUP_SIZE = 64;
|
||||
const GLuint CLUSTER_CULL_GROUP_SIZE = 64;
|
||||
|
||||
// binding points of the cluster buffers: layout(binding = ...) in glsl_common.
|
||||
// the uniform block numbers go together with scene (0), model (1) and instance (3) blocks
|
||||
@@ -247,8 +252,9 @@ namespace gl
|
||||
static_assert(offsetof(cluster_range, offset) == 0, "bad layout of cluster_range");
|
||||
static_assert(offsetof(cluster_range, count) == 4, "bad layout of cluster_range");
|
||||
|
||||
// layout std430: cluster_counter_ssbo. index_count is the allocator of the shared index list, overflow_count
|
||||
// the number of lights dropped from clusters which were full
|
||||
// layout std430: cluster_counter_ssbo, zeroed before every culling pass. index_count is the allocator of the shared
|
||||
// index list, overflow_count the number of (cluster, light) pairs dropped because the cluster already held
|
||||
// MAX_LIGHTS_PER_CLUSTER lights
|
||||
struct cluster_counters
|
||||
{
|
||||
uint32_t index_count;
|
||||
|
||||
Reference in New Issue
Block a user