17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-25 21:47:57 +01:00

Cull shadow and reflection passes on the GPU

This commit is contained in:
2026-10-18 01:46:45 +02:00
parent 494304903f
commit f4b516eeb8
8 changed files with 434 additions and 129 deletions

View File

@@ -31,18 +31,26 @@ void reserve(gl::ssbo &Buffer, GLsizeiptr const Size)
} // namespace
void opengl46_instance_cull::init()
void opengl46_instance_cull::init(opengl46_instance_cull const *Shared)
{
auto const make_compute = [](std::string const &Name) {
gl::shader shader(Name);
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
};
m_cull_program = make_compute("instance_cull.comp");
m_offsets_program = make_compute("instance_cull_offsets.comp");
m_commands_program = make_compute("instance_cull_commands.comp");
if (Shared != nullptr)
{
m_shared = Shared->m_shared;
}
else
{
auto const make_compute = [](std::string const &Name) {
gl::shader shader(Name);
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
};
m_shared = std::make_shared<shared_data>();
m_shared->cull_program = make_compute("instance_cull.comp");
m_shared->offsets_program = make_compute("instance_cull_offsets.comp");
m_shared->commands_program = make_compute("instance_cull_commands.comp");
m_shared->records = std::make_unique<gl::ssbo>(65536 * RECORD_SIZE, gl::INSTANCE_RECORD_SSBO_BINDING);
}
auto const readbacksize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES) * sizeof(std::uint32_t))};
m_records = std::make_unique<gl::ssbo>(65536 * RECORD_SIZE, gl::INSTANCE_RECORD_SSBO_BINDING);
m_jobs = std::make_unique<gl::ssbo>(4096 * sizeof(gl::instance_cull_job), gl::INSTANCE_JOB_SSBO_BINDING);
m_groups = std::make_unique<gl::ssbo>(1024 * sizeof(gl::instance_cull_group), gl::INSTANCE_GROUP_SSBO_BINDING);
m_lods = std::make_unique<gl::ssbo>(4096 * sizeof(float), gl::INSTANCE_LOD_SSBO_BINDING);
@@ -59,6 +67,7 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
collect();
m_inpass = true;
m_ran = true;
m_dispatched = false;
m_setup = Setup;
m_buckets.clear();
@@ -167,40 +176,46 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
{
auto &shared{*m_shared};
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
auto const current{Bucket.gpugeneration == m_recordgeneration};
auto const current{Bucket.gpugeneration == shared.recordgeneration};
if (current && !Bucket.gpustale)
return;
if (current && count <= Bucket.gpucapacity)
{
m_records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
shared.records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
Bucket.gpustale = false;
return;
}
// a new place, with some room for instances the editor adds
auto const allocate = [this](scene::basic_cell::instance_bucket const &Target) {
auto const allocate = [&shared](scene::basic_cell::instance_bucket const &Target) {
auto const records{static_cast<std::uint32_t>(Target.records.size())};
Target.gpufirst = m_recordfill;
Target.gpufirst = shared.recordfill;
Target.gpucapacity = records + records / 4;
Target.gpugeneration = m_recordgeneration;
Target.gpugeneration = shared.recordgeneration;
Target.gpustale = false;
m_recordfill += Target.gpucapacity;
m_records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
shared.recordfill += Target.gpucapacity;
shared.records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
};
auto const capacity{static_cast<std::uint32_t>(m_records->size() / RECORD_SIZE)};
if (m_recordfill + count + count / 4 > capacity)
auto const capacity{static_cast<std::uint32_t>(shared.records->size() / RECORD_SIZE)};
if (shared.recordfill + count + count / 4 > capacity)
{
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
// buckets which were made anew or are out of sight are dropped this way
// buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different
// buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame,
// so a buffer started over again in the same or the next frame grows
GLsizeiptr live{count + count / 4};
for (auto const *bucket : m_buckets)
live += bucket->records.size() + bucket->records.size() / 4;
if (2 * live * RECORD_SIZE > m_records->size())
m_records->resize(std::max(2 * live * RECORD_SIZE, 2 * m_records->size()));
++m_recordgeneration;
m_recordfill = 0;
auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)};
if ((2 * live * RECORD_SIZE > shared.records->size()) || again)
shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size()));
shared.restarted = true;
shared.restartframe = m_setup.frame;
++shared.recordgeneration;
shared.recordfill = 0;
for (std::size_t job = 0; job < m_buckets.size(); ++job)
{
allocate(*m_buckets[job]);
@@ -244,26 +259,26 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
planes[plane] = glm::vec4{source[0], source[1], source[2],
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
}
glProgramUniformMatrix4fv(*m_cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
glProgramUniform4fv(*m_cull_program, 1, 6, glm::value_ptr(planes[0]));
glProgramUniform4f(*m_cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
glProgramUniform4f(*m_cull_program, 8, m_setup.zoom, 0.f, 0.f, 0.f);
glProgramUniform4ui(*m_offsets_program, 0, m_entries, 0, 0, 0);
glProgramUniformMatrix4fv(*m_shared->cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
glProgramUniform4fv(*m_shared->cull_program, 1, 6, glm::value_ptr(planes[0]));
glProgramUniform4f(*m_shared->cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
glProgramUniform4f(*m_shared->cull_program, 8, m_setup.zoom, 0.f, 0.f, 0.f);
glProgramUniform4ui(*m_shared->offsets_program, 0, m_entries, 0, 0, 0);
// the occlusion test only when the instances are drawn from the result, the cpu doesn't make it
m_occlusion = (m_setup.drawing && (Occlusion != nullptr) && (Occlusion->pyramid != 0));
if (m_occlusion)
{
glProgramUniformMatrix4fv(*m_cull_program, 10, 1, GL_FALSE, glm::value_ptr(Occlusion->projection));
glProgramUniform4i(*m_cull_program, 14, 1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y);
glProgramUniform4f(*m_cull_program, 15, Occlusion->screen.x, Occlusion->screen.y, 0.f, 0.f);
glProgramUniformMatrix4fv(*m_shared->cull_program, 10, 1, GL_FALSE, glm::value_ptr(Occlusion->projection));
glProgramUniform4i(*m_shared->cull_program, 14, 1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y);
glProgramUniform4f(*m_shared->cull_program, 15, Occlusion->screen.x, Occlusion->screen.y, 0.f, 0.f);
glBindTextureUnit(0, Occlusion->pyramid);
}
else
{
glProgramUniform4i(*m_cull_program, 14, 0, 0, 0, 0);
glProgramUniform4i(*m_shared->cull_program, 14, 0, 0, 0, 0);
}
m_twophase = (m_occlusion && Twophase);
glProgramUniform4ui(*m_cull_program, 16, (m_twophase ? 1u : 0u), 0, 0, 0);
glProgramUniform4ui(*m_shared->cull_program, 16, (m_twophase ? 1u : 0u), 0, 0, 0);
// one invocation per record; past the count limit of one dimension the groups go into rows
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
@@ -271,13 +286,13 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
auto const groups_y{(groups + groups_x - 1) / groups_x};
m_timer.begin();
glProgramUniform4ui(*m_cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 0);
m_cull_program->dispatch(groups_x, groups_y);
glProgramUniform4ui(*m_shared->cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 0);
m_shared->cull_program->dispatch(groups_x, groups_y);
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
m_offsets_program->dispatch(1);
m_shared->offsets_program->dispatch(1);
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
glProgramUniform4ui(*m_cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 1);
m_cull_program->dispatch(groups_x, groups_y);
glProgramUniform4ui(*m_shared->cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 1);
m_shared->cull_program->dispatch(groups_x, groups_y);
// the output and the counts go to draws, indirect draws and the copy for the cpu
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
m_timer.end();
@@ -293,7 +308,7 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
void opengl46_instance_cull::bind_buffers()
{
m_records->bind_base();
m_shared->records->bind_base();
m_jobs->bind_base();
m_groups->bind_base();
m_lods->bind_base();
@@ -308,8 +323,8 @@ void opengl46_instance_cull::dispatch_second(occlusion_setup const &Occlusion)
// the draws of the first phase bound buffers of their own, and other passes share the binding of the records
bind_buffers();
glProgramUniform4i(*m_cull_program, 14, 1, Occlusion.levels, Occlusion.size.x, Occlusion.size.y);
glProgramUniform4f(*m_cull_program, 15, Occlusion.screen.x, Occlusion.screen.y, 0.f, 0.f);
glProgramUniform4i(*m_shared->cull_program, 14, 1, Occlusion.levels, Occlusion.size.x, Occlusion.size.y);
glProgramUniform4f(*m_shared->cull_program, 15, Occlusion.screen.x, Occlusion.screen.y, 0.f, 0.f);
glBindTextureUnit(0, Occlusion.pyramid);
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
@@ -317,14 +332,14 @@ void opengl46_instance_cull::dispatch_second(occlusion_setup const &Occlusion)
auto const groups_y{(groups + groups_x - 1) / groups_x};
m_secondtimer.begin();
glProgramUniform4ui(*m_cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 2);
m_cull_program->dispatch(groups_x, groups_y);
glProgramUniform4ui(*m_shared->cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 2);
m_shared->cull_program->dispatch(groups_x, groups_y);
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
if (m_commandcount > 0)
{
m_commands->bind_base();
glProgramUniform4ui(*m_commands_program, 0, m_commandcount, m_entries, 1, 0);
m_commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
glProgramUniform4ui(*m_shared->commands_program, 0, m_commandcount, m_entries, 1, 0);
m_shared->commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
}
// the commands go to indirect draws, the matrices to the vertex shaders, the counts to the copy for the cpu
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
@@ -346,8 +361,8 @@ void opengl46_instance_cull::fill_commands(std::vector<gl::instance_draw_command
m_commands->update(Commands.data(), 0, size);
m_counters->bind_base();
m_commands->bind_base();
glProgramUniform4ui(*m_commands_program, 0, m_commandcount, m_entries, 0, 0);
m_commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
glProgramUniform4ui(*m_shared->commands_program, 0, m_commandcount, m_entries, 0, 0);
m_shared->commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE);
// the commands go to indirect draws, the matrices to the vertex shaders
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
}
@@ -443,35 +458,38 @@ void opengl46_instance_cull::collect()
++m_mismatchedframes;
}
std::string opengl46_instance_cull::info()
std::string opengl46_instance_cull::info(std::string const &Label, bool const Occlusion)
{
if (Global.gfx_gpucull == 0)
return {};
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)
return "\ngpu cull: no result yet";
return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances drawn" +
return "\n" + Label + ": no result yet";
if (false == Occlusion)
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms";
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms";
}
if (m_comparedframes == 0)
return "\ngpu cull: no result yet";
return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
return "\n" + Label + ": no result yet";
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
std::to_string(m_comparedframes);
}
std::string opengl46_instance_cull::info_times()
std::string opengl46_instance_cull::info_times(std::string const &Label)
{
std::string text;
if (Global.gfx_gpucull == 0)
return text;
if (auto const time = m_timer.result())
{
text += " instance cull: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
text += " " + Label + ": " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
auto const second{m_secondtimer.result()};
if (m_twophase && second)
text += " (+" + to_string(static_cast<double>(*second) / 1000000.0, 3) + " ms second phase)";