17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 09:09:19 +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

@@ -264,8 +264,9 @@ are no longer loaded.
Many models of the scenery (trees, bushes, poles, fences: models without lights and animations) are drawn in batches, one
batch per model and level of detail. Which of them are drawn, and at which level of detail, can be worked out on the
processor (as before) or on the graphics card. On the graphics card the processor no longer goes through the models one by
one, so with dense vegetation it is much less loaded. Only the main view is drawn this way; shadows, reflections and other
views go as before.
one, so with dense vegetation it is much less loaded. The main view, the three cascades of the sun shadows and the faces of
the environment map (reflections) are drawn this way; the shadow maps of lamps and headlights, the cab and other views go as
before.
| Entry | Default | Meaning |
|---|---|---|
@@ -274,6 +275,8 @@ views go as before.
| `gfx.gpucull.occlusion.instances yes/no` | `yes` | with `gfx.gpucull.occlusion yes`: the models drawn this way hide one another too (see below) |
| `gfx.depthprepass yes/no` | `yes` | with `gfx.gpucull yes`: the depth of the models is drawn first, so the colour is worked out only for what stays in view (dense foliage) |
| `gfx.gpucull.multidraw yes/no` | `yes` | with `gfx.gpucull yes`: parts of models with the same material (and texture variant) in the same model are drawn with one call (multi draw), see below |
| `gfx.gpucull.shadows yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the cascades of the sun shadows too (each cascade with its own camera and the distances from the real camera, as before; models none of whose materials cast shadows are left out at once) |
| `gfx.gpucull.reflections yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the faces of the environment map too (its own range of models, as before) |
| `gfx.depthprepass.scene yes/no` | `no` | the depth of the whole opaque scene of the main view (terrain, tracks, buildings, models, vehicles, the cab) is drawn before its colour, see below |
With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn
@@ -287,6 +290,12 @@ is not worked out with the depth prepass on). Models whose material is not one o
like) are left out of the depth prepass and drawn as before; with the depth prepass on, they don't hide the others either.
All of these can be changed while the simulator runs, in the **Instance culling** debug window.
The shadows and the reflections take the same tests as before (which models are in the cascade or the face, their distance
and level of detail), without the occlusion test, the depth prepass and the two phases, which belong to the main view. The
copies of the models are sent to the graphics card once and serve all of these views. In the shadows the parts with the
same texture for the alpha test (or with none) in the same model go in one call with `gfx.gpucull.multidraw yes`, whatever
their material.
With `gfx.gpucull.multidraw yes` the draws of the models drawn from the GPU are put in an order in which the parts with the
same material, texture variant and model (the model keeps its geometry in a buffer of its own) follow one another, and each
such run goes in one call (`glMultiDrawElementsIndirect`); the data of every part (its position in the model, colours,
@@ -378,7 +387,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
made them — fewer than the draws with `gfx.gpucull.multidraw yes` — and the processor time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and
`depth prepass:`; in two phases each of them adds in brackets the time of the second phase, of the second pyramid and of
the depth of the models drawn first. With `compare` it shows how many models both counted and how many levels of detail
differ.
differ. With `gfx.gpucull.shadows` and `gfx.gpucull.reflections` the same rows (without the occlusion test and the
prepass) follow for each cascade, `gpu cull shadows 1:` to `3:` (1 the nearest), and for the environment map,
`gpu cull reflections, last face:` (the last face of the map drawn, as the map is drawn now and then), with the timings
`instance cull shadows 1:` … and `instance cull reflections, last face:`.
- With `gfx.depthprepass.scene yes` Shift+F12 shows the `scene depth:` row (draws of the depth pass and its processor time)
and the timing `scene depth:` (its GPU time).
- With `gfx.contactshadows` on Shift+F12 shows the `contact shadows:` row (the level, the length and reach of the rays, or

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

View File

@@ -9,4 +9,7 @@ void main()
{
gl_Position = (projection * effective_modelview()) * vec4(v_vert, 1.0f);
f_coord = v_coord;
#ifdef MODEL_DRAWS
f_model_draw = uint(gl_DrawID);
#endif
}

View File

@@ -1454,6 +1454,16 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1);
Parser >> gfx_gpucull_multidraw;
}
else if (Token == "gfx.gpucull.shadows")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_shadows;
}
else if (Token == "gfx.gpucull.reflections")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_reflections;
}
else if (Token == "gfx.depthprepass.scene")
{
Parser.getTokens(1);
@@ -1950,6 +1960,8 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull.occlusion.instances", gfx_gpucull_occlusion_instances );
export_as_text( Output, "gfx.depthprepass", gfx_depthprepass );
export_as_text( Output, "gfx.gpucull.multidraw", gfx_gpucull_multidraw );
export_as_text( Output, "gfx.gpucull.shadows", gfx_gpucull_shadows );
export_as_text( Output, "gfx.gpucull.reflections", gfx_gpucull_reflections );
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );

View File

@@ -294,6 +294,10 @@ struct global_settings {
// with gfx_gpucull 1: the draws of the instances which follow one another with the same material and geometry bank are
// made with one multi draw, their model blocks read from a storage buffer by the number of the draw
bool gfx_gpucull_multidraw = true;
// with gfx_gpucull 1 or 2: the instances of the cascades of the sun shadows and of the faces of the environment map are
// culled in compute passes as well (drawn from their result, or compared), not only those of the main view
bool gfx_gpucull_shadows = true;
bool gfx_gpucull_reflections = true;
// (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included
bool gfx_depthprepass_scene = false;
// (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,