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:
@@ -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
|
||||
|
||||
@@ -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)";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user