17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 00:06:13 +02: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)";

View File

@@ -43,6 +43,9 @@ class TAnimModel;
// of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset())
// with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map) has a
// culling of its own, with its own buffers, while the records and the programs are shared by all of them: the records of a
// bucket go to the gpu once and serve every pass which queues the bucket
class opengl46_instance_cull
{
public:
@@ -85,8 +88,8 @@ class opengl46_instance_cull
opengl46_instance_cull(const opengl46_instance_cull &) = delete;
opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete;
// creates the programs and buffers. throws gl::shader_exception
void init();
// creates the buffers, and the programs and the record buffer, or takes the ones of Shared. throws gl::shader_exception
void init(opengl46_instance_cull const *Shared = nullptr);
// starts collecting the buckets of a pass
void begin_pass(pass_setup const &Setup);
bool in_pass() const
@@ -165,9 +168,17 @@ class opengl46_instance_cull
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts);
// ends the pass: its cpu counts are kept for the comparison
void end_pass();
// lines for the debug panel
std::string info();
std::string info_times();
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
// for the panel also by the culls of passes which aren't made every frame
void collect();
// a pass was culled since the start
bool ran() const
{
return m_ran;
}
// lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view)
std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true);
std::string info_times(std::string const &Label = "instance cull");
private:
struct group_data
@@ -198,20 +209,30 @@ class opengl46_instance_cull
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
};
// what the culls of all passes share
struct shared_data
{
std::unique_ptr<gl::program> cull_program;
std::unique_ptr<gl::program> offsets_program;
std::unique_ptr<gl::program> commands_program;
std::unique_ptr<gl::ssbo> records;
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
std::uint32_t recordfill{0};
unsigned int recordgeneration{1};
// frame of the last time the buffer was started over, and whether it was
unsigned int restartframe{0};
bool restarted{false};
};
// finds a place for the records of a bucket in the record buffer and sends them
void place(scene::basic_cell::instance_bucket const &Bucket);
// binds the buffers of the culling passes
void bind_buffers();
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu
void collect();
// entries read back for the comparison; with more of them only the totals and these are compared
static constexpr std::uint32_t READBACK_ENTRIES{16384};
std::unique_ptr<gl::program> m_cull_program;
std::unique_ptr<gl::program> m_offsets_program;
std::unique_ptr<gl::program> m_commands_program;
std::unique_ptr<gl::ssbo> m_records;
std::shared_ptr<shared_data> m_shared;
std::unique_ptr<gl::ssbo> m_jobs;
std::unique_ptr<gl::ssbo> m_groups;
std::unique_ptr<gl::ssbo> m_lods;
@@ -222,11 +243,8 @@ class opengl46_instance_cull
gl::gpu_timer m_timer;
gl::gpu_timer m_secondtimer;
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
std::uint32_t m_recordfill{0};
unsigned int m_recordgeneration{1};
bool m_inpass{false};
bool m_ran{false};
pass_setup m_setup;
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
std::vector<gl::instance_cull_job> m_joblist;

View File

@@ -207,6 +207,11 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
gl::shader prepassalphafragment("depthprepass_alpha.frag", defines);
m_prepass_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{prepassvertex, prepassfragment});
m_prepass_alpha_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{prepassvertex, prepassalphafragment});
gl::shader shadowvertex("simpleuv.vert", defines);
gl::shader shadowfragment("shadowmap.frag", defines);
gl::shader alphashadowfragment("alphashadowmap.frag", defines);
m_shadow_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shadowvertex, shadowfragment});
m_alpha_shadow_multidraw_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shadowvertex, alphashadowfragment});
m_drawblockstream = std::make_unique<gl::frame_stream>(
static_cast<GLsizeiptr>(4096 * sizeof(gl::model_ubs)), static_cast<GLsizeiptr>(sizeof(gl::model_ubs)), Global.gfx_bufferstreaming);
}
@@ -216,6 +221,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_vertex_multidraw_shader.reset();
m_prepass_multidraw_shader.reset();
m_prepass_alpha_multidraw_shader.reset();
m_shadow_multidraw_shader.reset();
m_alpha_shadow_multidraw_shader.reset();
m_drawblockstream.reset();
}
@@ -224,6 +231,9 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_sky.init();
m_weather.init();
m_instancecull.init();
for (auto &shadowcull : m_shadowcull)
shadowcull.init(&m_instancecull);
m_reflectioncull.init(&m_instancecull);
m_hiz.init();
m_exposure.init();
m_ao.init();
@@ -769,6 +779,15 @@ void opengl46_renderer::SwapBuffers()
m_debugtimestext += m_exposure.info_times();
m_debugtimestext += m_ao.info_times();
m_debugtimestext += m_instancecull.info_times();
// the cullings of the other passes; the environment map is drawn now and then, its numbers are of its last update
if( Global.gfx_gpucull_shadows ) {
for( std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade ) {
m_debugtimestext += m_shadowcull[ cascade ].info_times( "instance cull shadows " + std::to_string( cascade + 1 ) );
}
}
if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) {
m_debugtimestext += m_reflectioncull.info_times( "instance cull reflections, last face" );
}
if( Global.gfx_depthprepass_scene || m_contactshadowslast ) {
if( auto const scenedepthtime = m_scenedepthtimer.result() )
m_debugtimestext += " scene depth: " + to_string( static_cast<double>( *scenedepthtime ) / 1000000.0, 3 ) + " ms\n";
@@ -853,6 +872,17 @@ void opengl46_renderer::SwapBuffers()
+ "\n" + m_locallights.info()
+ m_localshadows.info()
+ m_instancecull.info();
if( Global.gfx_gpucull_shadows ) {
for( std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade ) {
if( false == m_shadowcull[ cascade ].ran() ) { continue; }
m_shadowcull[ cascade ].collect();
m_debugstatstext += m_shadowcull[ cascade ].info( "gpu cull shadows " + std::to_string( cascade + 1 ), false );
}
}
if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) {
m_reflectioncull.collect();
m_debugstatstext += m_reflectioncull.info( "gpu cull reflections, last face", false );
}
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
m_debugstatstext +=
"\ncluster lists: " + std::to_string( cullstats->index_count )
@@ -953,10 +983,25 @@ void opengl46_renderer::draw_debug_ui()
else
ImGui::TextUnformatted("(multi draws not available, see the log)");
}
if (Global.gfx_gpucull != 0)
{
ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows);
ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections);
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)");
auto const info{m_instancecull.info()};
auto info{m_instancecull.info()};
if (Global.gfx_gpucull_shadows)
{
for (std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade)
{
if (m_shadowcull[cascade].ran())
info += m_shadowcull[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false);
}
}
if (Global.gfx_gpucull_reflections && m_reflectioncull.ran())
info += m_reflectioncull.info("gpu cull reflections, last face", false);
if (!info.empty())
ImGui::TextUnformatted(info.c_str() + 1);
}
@@ -1461,7 +1506,9 @@ if (!Global.gfx_skippipeline)
scene_ubo->update( scene_ubs );
if( m_shadowcolor != colors::white ) {
m_shadowcascade = idx;
Render( simulation::Region );
m_shadowcascade = -1;
if( idx > 0 ) { continue; } // render cab only in the closest csm stage
if( !FreeFlyModeFlag && Global.render_cab ) {
Render_cab( simulation::Train->Dynamic(), 0.0f, false );
@@ -2704,6 +2751,14 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
void opengl46_renderer::Bind_Material_Shadow(material_handle const Material)
{
if (m_indirectrecording)
{
// the walk of Render_Instances_Indirect_Pass() only notes the material, the program and the texture are bound when the
// draw is made (Render_Instances_Indirect_Shadow())
m_indirectmaterial = Material;
m_indirectsubmodel = nullptr;
return;
}
if (Material != null_handle)
{
auto &material = m_materials.material(Material);
@@ -2802,15 +2857,35 @@ void opengl46_renderer::Render(scene::basic_region *Region)
for( auto &bucket : m_frame_instance_buckets ) {
bucket.second.clear();
}
// the gpu culling runs along the first color pass of the main viewport of a frame. its result is drawn (Global.gfx_gpucull 1),
// or compared with the one of the cpu, which draws (2)
// the gpu culling runs along the first color pass of the main viewport of a frame, and the passes of the cascades of the
// sun shadows and of the faces of the environment map drawn for it, each with a culling of its own. its result is drawn
// (Global.gfx_gpucull 1), or compared with the one of the cpu, which draws (2)
m_instancecullpass = false;
m_passcull = nullptr;
if( ( Global.gfx_gpucull != 0 )
&& ( m_renderpass.draw_mode == rendermode::color )
&& ( m_current_viewport != nullptr )
&& ( m_current_viewport->main )
&& ( m_instancecullframe != m_framestamp ) ) {
m_instancecullframe = m_framestamp;
&& ( m_current_viewport->main ) ) {
if( ( m_renderpass.draw_mode == rendermode::color )
&& ( m_instancecullframe != m_framestamp ) ) {
m_instancecullframe = m_framestamp;
m_passcull = &m_instancecull;
}
else if( ( m_renderpass.draw_mode == rendermode::shadows )
&& ( Global.gfx_gpucull_shadows )
&& ( m_shadowcascade >= 0 )
&& ( m_shadowcascade < static_cast<int>( m_shadowcull.size() ) )
&& ( false == m_cubeshadows ) ) {
// the cascades of the sun; the maps of lamps and headlights go as before
m_passcull = &m_shadowcull[ m_shadowcascade ];
m_shadowcasters.clear();
}
else if( ( m_renderpass.draw_mode == rendermode::reflections )
&& ( Global.gfx_gpucull_reflections )
&& ( Global.reflectiontune.fidelity >= 1 ) ) {
m_passcull = &m_reflectioncull;
}
}
if( m_passcull != nullptr ) {
m_instancecullpass = true;
// the same choices as in Render_Instanced()
auto const reflections { m_renderpass.draw_mode == rendermode::reflections };
@@ -2825,7 +2900,7 @@ void opengl46_renderer::Render(scene::basic_region *Region)
setup.drawrange = static_cast<float>( sq( m_renderpass.draw_range + 250 ) );
setup.zoom = Global.ZoomFactor;
setup.drawing = ( Global.gfx_gpucull == 1 );
m_instancecull.begin_pass( setup );
m_passcull->begin_pass( setup );
}
// build a list of region sections to render
glm::vec3 const cameraposition{m_renderpass.pass_camera.position()};
@@ -3275,6 +3350,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
indirect_counts colourcounts;
if( false == m_indirectdraws.empty() ) {
auto const cpustart { std::chrono::steady_clock::now() };
m_indirectcull = &m_instancecull;
m_instancecull.bind_draw();
colourcounts = Render_Instances_Indirect_Colour( m_indirectcolourset );
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
@@ -3291,26 +3367,32 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
}
if( m_instancecullpass ) {
if( m_instancecull.drawing() ) {
if( m_passcull->drawing() ) {
// the buckets of the pass weren't queued for the cpu, which leaves the loop below empty
Render_Instances_Indirect();
if( m_passcull == &m_instancecull ) {
Render_Instances_Indirect();
}
else {
Render_Instances_Indirect_Pass();
}
}
else {
// the view of the pass is in place here, the one Render_Instanced() takes
m_instancecull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ) );
m_passcull->dispatch( OpenGLMatrices.data( GL_MODELVIEW ) );
}
}
for( auto &bucket : m_frame_instance_buckets ) {
if( bucket.second.empty() ) { continue; }
Render_Instanced( bucket.first.pModel, bucket.second, ( m_instancecullpass ? &m_instance_bandcounts : nullptr ) );
if( m_instancecullpass ) {
m_instancecull.cpu_result( bucket.first, m_instance_bandcounts );
m_passcull->cpu_result( bucket.first, m_instance_bandcounts );
}
bucket.second.clear();
}
if( m_instancecullpass ) {
m_instancecull.end_pass();
m_passcull->end_pass();
m_instancecullpass = false;
m_passcull = nullptr;
}
}
@@ -3562,10 +3644,13 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
}
if( m_instancecullpass ) {
// the gpu tests every instance of the bucket, the way Render_Instanced() does
m_instancecull.add_bucket( bucket.first, instancebucket, Cell->m_area );
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
// sun shadows are left out of them, as Render_Instanced() leaves them
if( ( m_renderpass.draw_mode != rendermode::shadows ) || Instances_Cast_Shadow( bucket.first, instancebucket ) ) {
m_passcull->add_bucket( bucket.first, instancebucket, Cell->m_area );
}
// and when the instances are drawn from its result, the cpu has nothing more to do with them
if( m_instancecull.drawing() ) { continue; }
if( m_passcull->drawing() ) { continue; }
}
// the depth pass of the whole scene culled them already, the colour pass draws what it recorded
if( m_indirectcolourpending ) { continue; }
@@ -3915,43 +4000,8 @@ void opengl46_renderer::Render_Instances_Indirect()
( Global.gfx_gpucull_occlusion_instances && ( false == Global.bWireFrame ) ) );
// the draws, recorded while the gpu culls
m_indirectdraws.clear();
m_indirectcommands.clear();
auto const &lodbounds { m_instancecull.lod_bounds() };
m_indirectrecording = true;
for( auto const &group : m_instancecull.draw_groups() ) {
auto *model { group.key->pModel };
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( group.instance == nullptr ) ) { continue; }
group.instance->RaPrepare();
auto const *material { group.instance->Material() };
auto alpha { material != nullptr ? material->textures_alpha : 0x30300030 };
alpha ^= 0x0F0F000F;
for( std::uint32_t band = 0; band <= group.lodcount; ++band ) {
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
auto const bandmin { band == 0 ? 0.f : lodbounds[ group.lodfirst + band - 1 ] };
auto const bandmax { band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[ group.lodfirst + band ] };
if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; }
m_indirectentry = group.entryfirst + band;
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha );
model->Root->pRoot = model;
Render( model->Root );
::glPopMatrix();
}
}
m_indirectrecording = false;
Record_Indirect_Draws( m_instancecull );
// with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs
// of the multi draws read them from. the order goes into the commands, so it comes first
m_indirectmultidraw = ( Global.gfx_gpucull_multidraw && ( m_drawblockstream != nullptr ) );
if( m_indirectmultidraw && ( false == m_indirectdraws.empty() ) ) {
Order_Indirect_Draws();
Upload_Indirect_Blocks();
}
m_instancecull.fill_commands( m_indirectcommands );
using command_set = opengl46_instance_cull::command_set;
auto const twophase { m_instancecull.two_phase() };
auto const draws { false == m_indirectdraws.empty() };
@@ -4013,6 +4063,76 @@ void opengl46_renderer::Render_Instances_Indirect()
std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
}
// draws the instances of a pass other than the main view from the result of its gpu culling (m_passcull): the culling of the
// pass with its own camera, no occlusion test, the draws recorded the same way, then drawn once with the programs the pass
// draws with on the cpu
void opengl46_renderer::Render_Instances_Indirect_Pass()
{
auto const cpustart { std::chrono::steady_clock::now() };
auto &cull { *m_passcull };
cull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ) );
Record_Indirect_Draws( cull );
using command_set = opengl46_instance_cull::command_set;
indirect_counts counts;
if( false == m_indirectdraws.empty() ) {
cull.bind_draw();
counts = (
m_renderpass.draw_mode == rendermode::shadows ?
Render_Instances_Indirect_Shadow( command_set::first ) :
Render_Instances_Indirect_Colour( command_set::first ) );
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
Bind_Instance_Stream();
}
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( counts.calls );
// the count of the frame the gpu reported last
m_renderpass.draw_stats.instances += static_cast<int>( cull.last_count() );
m_renderpass.draw_stats.models += static_cast<int>( cull.last_count() );
cull.draw_result( counts.draws, 0, counts.calls, 0, std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
}
void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
{
m_indirectcull = &Cull;
m_indirectdraws.clear();
m_indirectcommands.clear();
auto const &lodbounds { Cull.lod_bounds() };
m_indirectrecording = true;
for( auto const &group : Cull.draw_groups() ) {
auto *model { group.key->pModel };
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( group.instance == nullptr ) ) { continue; }
group.instance->RaPrepare();
auto const *material { group.instance->Material() };
auto alpha { material != nullptr ? material->textures_alpha : 0x30300030 };
alpha ^= 0x0F0F000F;
for( std::uint32_t band = 0; band <= group.lodcount; ++band ) {
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
auto const bandmin { band == 0 ? 0.f : lodbounds[ group.lodfirst + band - 1 ] };
auto const bandmax { band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[ group.lodfirst + band ] };
if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; }
m_indirectentry = group.entryfirst + band;
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha );
model->Root->pRoot = model;
Render( model->Root );
::glPopMatrix();
}
}
m_indirectrecording = false;
// with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs
// of the multi draws read them from. the order goes into the commands, so it comes first
m_indirectmultidraw = ( Global.gfx_gpucull_multidraw && ( m_drawblockstream != nullptr ) );
if( m_indirectmultidraw && ( false == m_indirectdraws.empty() ) ) {
Order_Indirect_Draws();
Upload_Indirect_Blocks();
}
Cull.fill_commands( m_indirectcommands );
}
opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer )
{
if( m_scenedepthpass ) {
@@ -4091,7 +4211,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_
}
auto const drawcount { static_cast<std::uint32_t>( last - index ) };
Bind_Indirect_Blocks( index, drawcount );
m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
counts.draws += drawcount;
++counts.calls;
lastdrawn = last - 1;
@@ -4104,7 +4224,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_
}
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) );
++counts.draws;
++counts.calls;
++index;
@@ -4137,7 +4257,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_
m_bindmultidraw = false;
auto const drawcount { static_cast<std::uint32_t>( last - index ) };
Bind_Indirect_Blocks( index, drawcount );
m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
counts.draws += drawcount;
index = last;
}
@@ -4145,7 +4265,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_
Bind_Material( draw.material, draw.submodel );
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) );
++counts.draws;
++index;
}
@@ -4161,6 +4281,93 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_
return counts;
}
opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set )
{
indirect_counts counts;
auto const count { m_indirectdraws.size() };
for( std::size_t index = 0; index < count; ) {
auto const &draw { m_indirectdraws[ index ] };
auto const texture { Indirect_Shadow_Texture( draw.material ) };
auto *multidrawprogram {
m_indirectmultidraw ?
( texture != null_handle ? m_alpha_shadow_multidraw_shader.get() : m_shadow_multidraw_shader.get() ) :
nullptr };
if( multidrawprogram != nullptr ) {
// the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test
auto last { index + 1 };
while( ( last < count )
&& ( m_indirectdraws[ last ].geometry.bank == draw.geometry.bank )
&& ( m_indirectdraws[ last ].type == draw.type )
&& ( m_indirectcommands[ last ].indexed == m_indirectcommands[ index ].indexed )
&& ( Indirect_Shadow_Texture( m_indirectdraws[ last ].material ) == texture ) ) {
++last;
}
if( texture != null_handle ) {
m_textures.bind( 0, texture );
}
multidrawprogram->bind();
auto const drawcount { static_cast<std::uint32_t>( last - index ) };
Bind_Indirect_Blocks( index, drawcount );
m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) );
counts.draws += drawcount;
index = last;
}
else {
Bind_Material_Shadow( draw.material );
model_ubs = draw.ubs;
model_ubo->update( model_ubs );
m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) );
++counts.draws;
++index;
}
++counts.calls;
}
if( m_indirectmultidraw && ( count > 0 ) ) {
// the model block the draws made one by one leave behind
model_ubs = m_indirectdraws.back().ubs;
model_ubo->update( model_ubs );
}
return counts;
}
texture_handle opengl46_renderer::Indirect_Shadow_Texture( material_handle const Material )
{
if( Material == null_handle ) { return null_handle; }
return m_materials.material( Material ).textures[ 0 ];
}
bool opengl46_renderer::Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket )
{
auto const lookup { m_shadowcasters.find( Key ) };
if( lookup != m_shadowcasters.end() ) { return lookup->second; }
// the skins of the instance Render_Instanced() would take them from
TAnimModel const *instance { nullptr };
for( auto const *candidate : Bucket.instances ) {
if( candidate != nullptr ) {
instance = candidate;
break;
}
}
auto const *skins { instance != nullptr && instance->Material() != nullptr ? instance->Material()->replacable_skins : nullptr };
auto castsshadow { false };
m_instance_lodpending.clear();
m_instance_lodpending.push_back( Key.pModel != nullptr ? Key.pModel->Root : nullptr );
while( ( false == castsshadow ) && ( false == m_instance_lodpending.empty() ) ) {
auto const *submodel = m_instance_lodpending.back();
m_instance_lodpending.pop_back();
if( submodel == nullptr ) { continue; }
if( submodel->eType < TP_ROTATOR ) {
auto const material { submodel->m_material >= 0 ? submodel->m_material : ( skins != nullptr ? skins[ -submodel->m_material ] : null_handle ) };
castsshadow = ( Material( material ).shadow_rank <= Global.gfx_shadow_rank_cutoff );
}
m_instance_lodpending.push_back( submodel->Child );
m_instance_lodpending.push_back( submodel->Next );
}
m_shadowcasters.emplace( Key, castsshadow );
return castsshadow;
}
// the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same
// state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry,
// the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on
@@ -4175,8 +4382,14 @@ void opengl46_renderer::Order_Indirect_Draws()
// a number for each state the draws have, by sorting the states (the buffers are kept between frames)
m_indirectstatekeys.clear();
// the sun shadows draw with the program and the texture of the alpha test, whatever the material
auto const shadows { m_renderpass.draw_mode == rendermode::shadows };
for( std::size_t index = 0; index < count; ++index ) {
auto const &draw { m_indirectdraws[ index ] };
if( shadows ) {
m_indirectstatekeys.push_back( { Indirect_Shadow_Texture( draw.material ), 0u, draw.geometry.bank, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast<std::uint32_t>( index ) } );
continue;
}
auto variant { 0u };
if( draw.material != null_handle ) {
auto const &material { m_materials.material( draw.material ) };

View File

@@ -355,6 +355,16 @@ class opengl46_renderer : public gfx_renderer {
// (Global.gfx_gpucull_occlusion_instances) the instances seen in the previous frame are drawn first (their depth, or
// their colour without the prepass), the pyramid is made again and the other instances are culled against it
void Render_Instances_Indirect();
// the same for the passes culled on the gpu other than the main view (Global.gfx_gpucull_shadows, _reflections): the
// cascades of the sun shadows, drawn with the shadow programs, and the faces of the environment map, drawn with the
// programs of the materials like the main view. no occlusion test, no depth prepass, one phase
void Render_Instances_Indirect_Pass();
// the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws
// (m_indirectdraws, m_indirectcommands), puts them in order for multi draws and has Cull fill in their commands
void Record_Indirect_Draws( opengl46_instance_cull &Cull );
// whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer
// is kept for the rest of the pass
bool Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket );
// draws of the instances made, and the gl calls which made them
struct indirect_counts {
std::uint32_t draws { 0 };
@@ -371,6 +381,10 @@ class opengl46_renderer : public gfx_renderer {
indirect_counts Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set );
indirect_counts Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer );
indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
// the recorded draws of a shadow pass with the shadow programs, the way Bind_Material_Shadow() picks them
indirect_counts Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set );
// the texture Bind_Material_Shadow() binds for the alpha test of a material, or null_handle (no test)
texture_handle Indirect_Shadow_Texture( material_handle const Material );
// multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the
// same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in
// the buffer of the frame (m_drawblockstream), in the same order
@@ -555,8 +569,17 @@ class opengl46_renderer : public gfx_renderer {
// when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared
// with the one of the cpu (2)
opengl46_instance_cull m_instancecull;
bool m_instancecullpass { false }; // the current pass feeds it
bool m_instancecullpass { false }; // the current pass feeds a culling: m_passcull
unsigned int m_instancecullframe { ~0u }; // frame it last ran in
// the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows,
// _reflections), with records and programs shared with m_instancecull
std::array<opengl46_instance_cull, 3> m_shadowcull;
opengl46_instance_cull m_reflectioncull;
opengl46_instance_cull *m_passcull { nullptr }; // the one the current pass feeds
opengl46_instance_cull *m_indirectcull { &m_instancecull }; // the one the recorded draws (m_indirectdraws) were culled by
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
// Instances_Cast_Shadow() of the groups of the shadow pass under way
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
std::vector<std::uint32_t> m_instance_bandcounts; // reported by Render_Instanced() for the comparison
// depth pyramid for the occlusion test of the gpu culling
opengl46_hiz m_hiz;
@@ -616,6 +639,8 @@ class opengl46_renderer : public gfx_renderer {
std::unordered_map<gl::program const *, std::unique_ptr<gl::program>> m_multidrawprograms;
std::unique_ptr<gl::program> m_prepass_multidraw_shader;
std::unique_ptr<gl::program> m_prepass_alpha_multidraw_shader;
std::unique_ptr<gl::program> m_shadow_multidraw_shader; // and of the sun shadows
std::unique_ptr<gl::program> m_alpha_shadow_multidraw_shader;
bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws
bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing
std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded