mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-18 00:59:18 +02:00
Let the instances drawn from the GPU hide one another, in two phases
This commit is contained in:
@@ -268,13 +268,20 @@ views go as before.
|
||||
|---|---|---|
|
||||
| `gfx.gpucull` | `no` | `no`: on the processor, as before. `yes`: on the graphics card, and the models are drawn from its result. `compare`: on the processor, which draws, and on the graphics card too, for the results to be compared (debugging) |
|
||||
| `gfx.gpucull.occlusion yes/no` | `yes` | with `gfx.gpucull yes`: models hidden whole behind what was drawn before them (the cab, the terrain, buildings and other models drawn on their own, vehicles) are left out |
|
||||
| `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.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 |
|
||||
|
||||
The occlusion test does not take models drawn this way as covering each other: a tree hidden behind another tree is still
|
||||
drawn (its colour is not worked out with the depth prepass on). Models whose material is not one of the usual ones (parallax,
|
||||
water and the like) are left out of the depth prepass and drawn as before. All three can be changed while the simulator
|
||||
runs, in the **Instance culling** debug window.
|
||||
With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn
|
||||
first (their depth, or their colour without the depth prepass), the depth they leave is added to what the others are tested
|
||||
against, and only then are the others tested and drawn; the graphics card also notes which models it saw, for the next frame.
|
||||
So a tree hidden behind other trees, or behind a hill of models, is left out as well. Whatever is left out is hidden behind
|
||||
something drawn, so the picture is the same either way; a model coming into view is drawn in the frame it appears in. It
|
||||
costs the depth pyramid a second time and the processor one more pass over the draws of the models (hundreds of draw calls,
|
||||
not the scene). With `no` the models do not cover each other: a tree hidden behind another tree is still drawn (its colour
|
||||
is not worked out with the depth prepass on). Models whose material is not one of the usual ones (parallax, water and the
|
||||
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.
|
||||
|
||||
With `gfx.depthprepass.scene yes` the scene is gone through twice: first only its depth is drawn, then its colour, worked
|
||||
out once per pixel for the surface which stays in view. That costs the processor a second walk through the scene (fewer
|
||||
@@ -351,11 +358,13 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
|
||||
of it, which can be edited and applied with a time of change, and the layers of clouds, the fog, the precipitation and
|
||||
the wind, which change at once. These changes are of this computer only. Shift+F12 shows the GPU time of `clouds:` (the
|
||||
main view), `clouds panorama:` (a band of the panorama), `cloud shadows:` and `fog volume:`.
|
||||
- **Instance culling** window: the mode of `gfx.gpucull` and the two switches above. With `gfx.gpucull yes` Shift+F12 shows
|
||||
`gpu cull:` (models drawn and hidden by the occlusion test, as counted on the graphics card a few frames before, the levels
|
||||
of detail, the draw calls with those of the depth prepass, and the processor time of the whole step) and the timings
|
||||
`instance cull:`, `occlusion depth:` (the depth pyramid of the test) and `depth prepass:`. With `compare` it shows how many
|
||||
models both counted and how many levels of detail differ.
|
||||
- **Instance culling** window: the mode of `gfx.gpucull` and the switches above. With `gfx.gpucull yes` Shift+F12 shows
|
||||
`gpu cull:` (models drawn, in two phases how many of them first, and hidden by the occlusion test, as counted on the
|
||||
graphics card a few frames before, the levels of detail, the draw calls with those of the depth prepass, 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.
|
||||
- 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
|
||||
|
||||
12
gl/ubo.h
12
gl/ubo.h
@@ -469,16 +469,17 @@ namespace gl
|
||||
const GLuint INSTANCE_JOB_SSBO_BINDING = 10; // instance_cull_job, one per queued bucket
|
||||
const GLuint INSTANCE_GROUP_SSBO_BINDING = 11; // instance_cull_group, one per model and skins
|
||||
const GLuint INSTANCE_LOD_SSBO_BINDING = 12; // sorted lod bounds (squared distances) of the groups
|
||||
const GLuint INSTANCE_COUNTER_SSBO_BINDING = 13; // header, then count[entries] and cursor[entries]
|
||||
const GLuint INSTANCE_COUNTER_SSBO_BINDING = 13; // header, then count[entries], cursor[entries] and first[entries]
|
||||
const GLuint INSTANCE_OUTPUT_SSBO_BINDING = 14; // modelview matrices of the instances which passed, by entry
|
||||
const GLuint INSTANCE_COMMAND_SSBO_BINDING = 15; // indirect draw commands of the entries (instance_cull_commands.comp)
|
||||
const GLuint INSTANCE_CULL_GROUP_SIZE = 64;
|
||||
const GLuint INSTANCE_OFFSETS_GROUP_SIZE = 1024;
|
||||
const GLuint INSTANCE_COMMAND_GROUP_SIZE = 64;
|
||||
// uints of the header of the counter buffer: frame number, survivors in total, entries, instances left out as hidden
|
||||
// behind the depth already drawn (occlusion test).
|
||||
// uints of the header of the counter buffer: frame number, survivors of the tests but the occlusion one in total,
|
||||
// entries, instances left out as hidden behind the depth already drawn (occlusion test), instances drawn first in two
|
||||
// phases (seen in the previous frame), unused.
|
||||
// an entry is a lod band of a group: survivors of group g in band b are counted at entry entryfirst(g) + b
|
||||
const GLuint INSTANCE_COUNTER_HEADER = 4;
|
||||
const GLuint INSTANCE_COUNTER_HEADER = 8;
|
||||
// work group of the passes which make the depth pyramid of the occlusion test (hiz_build.comp), 8x8
|
||||
const GLuint HIZ_GROUP_SIZE = 8;
|
||||
|
||||
@@ -510,7 +511,8 @@ namespace gl
|
||||
// layout std430: an indirect draw of the submodel of a group in one of its lod bands. the first five fields are the
|
||||
// command of glDrawElementsIndirect (count, instance count, first index, base vertex, base instance), or with indexed 0
|
||||
// the one of glDrawArraysIndirect (count, instance count, first vertex, base instance). the cpu fills in what it knows
|
||||
// of the geometry, instance_cull_commands.comp the instances, from the counts of the entry
|
||||
// of the geometry, instance_cull_commands.comp the instances, from the counts of the entry. the command buffer holds the
|
||||
// commands of a pass three times over (instances drawn first, all, drawn second), see opengl46_instance_cull
|
||||
struct instance_draw_command
|
||||
{
|
||||
uint32_t count;
|
||||
|
||||
@@ -70,14 +70,15 @@ void opengl46_hiz::allocate(int const Width, int const Height)
|
||||
glTextureParameteri(m_pyramid, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
void opengl46_hiz::build(GLuint const Source, int const Width, int const Height)
|
||||
void opengl46_hiz::build(GLuint const Source, int const Width, int const Height, bool const Second)
|
||||
{
|
||||
if ((Width <= 0) || (Height <= 0))
|
||||
return;
|
||||
if (m_sourcesize != glm::ivec2{Width, Height})
|
||||
allocate(Width, Height);
|
||||
|
||||
m_timer.begin();
|
||||
auto &timer{Second ? m_secondtimer : m_timer};
|
||||
timer.begin();
|
||||
|
||||
// resolve of the multisampled depth; the scissor test would limit the blit
|
||||
auto const scissor{glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
|
||||
@@ -107,13 +108,19 @@ void opengl46_hiz::build(GLuint const Source, int const Width, int const Height)
|
||||
glBindTextureUnit(0, 0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_timer.end();
|
||||
timer.end();
|
||||
}
|
||||
|
||||
std::string opengl46_hiz::info_times()
|
||||
std::string opengl46_hiz::info_times(bool const Second)
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_timer.result())
|
||||
text += " occlusion depth: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
{
|
||||
text += " occlusion depth: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms";
|
||||
auto const second{m_secondtimer.result()};
|
||||
if (Second && second)
|
||||
text += " (+" + to_string(static_cast<double>(*second) / 1000000.0, 3) + " ms with the instances drawn first)";
|
||||
text += "\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
// pass of the main view, from the depth drawn up to then (cab, terrain, shapes, tracks, vehicles and the models drawn on
|
||||
// their own), so it needs no reprojection: the instances are tested against what is already in the image.
|
||||
// the multisampled depth is resolved by a blit, then each level of the pyramid (hiz_build.comp) keeps the farthest
|
||||
// depth of the 2x2 texels below it; the first level is half the size of the view
|
||||
// depth of the 2x2 texels below it; the first level is half the size of the view.
|
||||
// in two phases the pyramid is made a second time in the pass, with the instances drawn first in it
|
||||
class opengl46_hiz
|
||||
{
|
||||
public:
|
||||
@@ -31,8 +32,9 @@ class opengl46_hiz
|
||||
// creates the program. throws gl::shader_exception
|
||||
void init();
|
||||
// makes the pyramid from the depth attachment of Source, a framebuffer of Width x Height pixels.
|
||||
// leaves the framebuffer bindings alone, texture unit 0 and image unit 0 are used (the unit cache is reset)
|
||||
void build(GLuint const Source, int const Width, int const Height);
|
||||
// leaves the framebuffer bindings alone, texture unit 0 and image unit 0 are used (the unit cache is reset).
|
||||
// Second: the second pyramid of the pass, timed on its own
|
||||
void build(GLuint const Source, int const Width, int const Height, bool const Second = false);
|
||||
// the pyramid, its first level size and level count; valid after build()
|
||||
GLuint texture() const
|
||||
{
|
||||
@@ -50,7 +52,8 @@ class opengl46_hiz
|
||||
{
|
||||
return m_sourcesize;
|
||||
}
|
||||
std::string info_times();
|
||||
// Second: with the time of the second pyramid
|
||||
std::string info_times(bool const Second);
|
||||
|
||||
private:
|
||||
// (re)creates the textures for a view of the given size
|
||||
@@ -65,4 +68,5 @@ class opengl46_hiz
|
||||
glm::ivec2 m_size{0};
|
||||
int m_levels{0};
|
||||
gl::gpu_timer m_timer;
|
||||
gl::gpu_timer m_secondtimer;
|
||||
};
|
||||
|
||||
@@ -210,13 +210,14 @@ void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Buc
|
||||
allocate(Bucket);
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion)
|
||||
void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion, bool const Twophase)
|
||||
{
|
||||
m_occlusion = false;
|
||||
m_twophase = false;
|
||||
if (!m_inpass || m_joblist.empty())
|
||||
return;
|
||||
|
||||
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 2 * m_entries) * sizeof(std::uint32_t))};
|
||||
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t))};
|
||||
reserve(*m_jobs, static_cast<GLsizeiptr>(m_joblist.size() * sizeof(gl::instance_cull_job)));
|
||||
reserve(*m_groups, static_cast<GLsizeiptr>(m_grouplist.size() * sizeof(gl::instance_cull_group)));
|
||||
reserve(*m_lods, static_cast<GLsizeiptr>(std::max<std::size_t>(m_lodlist.size(), 1) * sizeof(float)));
|
||||
@@ -229,15 +230,10 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
m_lods->update(m_lodlist.data(), 0, static_cast<GLsizeiptr>(m_lodlist.size() * sizeof(float)));
|
||||
// counts and cursors start at zero
|
||||
glClearNamedBufferSubData(*m_counters, GL_R32UI, 0, countersize, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||
std::uint32_t const header[gl::INSTANCE_COUNTER_HEADER]{m_setup.frame, 0, m_entries, 0};
|
||||
std::uint32_t const header[gl::INSTANCE_COUNTER_HEADER]{m_setup.frame, 0, m_entries, 0, 0, 0, 0, 0};
|
||||
m_counters->update(header, 0, sizeof(header));
|
||||
|
||||
m_records->bind_base();
|
||||
m_jobs->bind_base();
|
||||
m_groups->bind_base();
|
||||
m_lods->bind_base();
|
||||
m_counters->bind_base();
|
||||
m_output->bind_base();
|
||||
bind_buffers();
|
||||
|
||||
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
||||
auto const &frustumplanes{m_setup.frustum->planes()};
|
||||
@@ -266,6 +262,8 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
{
|
||||
glProgramUniform4i(*m_cull_program, 14, 0, 0, 0, 0);
|
||||
}
|
||||
m_twophase = (m_occlusion && Twophase);
|
||||
glProgramUniform4ui(*m_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};
|
||||
@@ -289,19 +287,62 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
m_readback->push(*m_counters);
|
||||
m_cpucounts.assign(m_entries, 0);
|
||||
m_dispatched = true;
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::bind_buffers()
|
||||
{
|
||||
m_records->bind_base();
|
||||
m_jobs->bind_base();
|
||||
m_groups->bind_base();
|
||||
m_lods->bind_base();
|
||||
m_counters->bind_base();
|
||||
m_output->bind_base();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::dispatch_second(occlusion_setup const &Occlusion)
|
||||
{
|
||||
if (!m_dispatched || !m_twophase)
|
||||
return;
|
||||
|
||||
// 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);
|
||||
glBindTextureUnit(0, Occlusion.pyramid);
|
||||
|
||||
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
|
||||
auto const groups_x{std::min<GLuint>(groups, 65535)};
|
||||
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);
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
m_secondtimer.end();
|
||||
|
||||
glBindTextureUnit(0, 0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
void opengl46_instance_cull::fill_commands(std::vector<gl::instance_draw_command> const &Commands)
|
||||
{
|
||||
m_commandcount = static_cast<std::uint32_t>(Commands.size());
|
||||
if (!m_dispatched || Commands.empty())
|
||||
return;
|
||||
|
||||
// the first copy is sent, the others are made from it on the gpu
|
||||
auto const size{static_cast<GLsizeiptr>(Commands.size() * sizeof(gl::instance_draw_command))};
|
||||
reserve(*m_commands, size);
|
||||
reserve(*m_commands, 3 * size);
|
||||
m_commands->update(Commands.data(), 0, size);
|
||||
m_counters->bind_base();
|
||||
m_commands->bind_base();
|
||||
@@ -340,6 +381,9 @@ void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key c
|
||||
|
||||
void opengl46_instance_cull::end_pass()
|
||||
{
|
||||
// the counts of both phases, for the panel and the comparison
|
||||
if (m_dispatched)
|
||||
m_readback->push(*m_counters);
|
||||
// the cpu counts are there to compare with only when the cpu drew
|
||||
if (m_dispatched && !m_setup.drawing)
|
||||
{
|
||||
@@ -363,9 +407,10 @@ void opengl46_instance_cull::collect()
|
||||
auto const frame{m_readbackdata[0]};
|
||||
comparison result;
|
||||
result.frame = frame;
|
||||
result.gputotal = m_readbackdata[1];
|
||||
result.entries = m_readbackdata[2];
|
||||
result.occluded = m_readbackdata[3];
|
||||
result.gputotal = m_readbackdata[1] - std::min(m_readbackdata[1], result.occluded);
|
||||
result.entries = m_readbackdata[2];
|
||||
result.first = m_readbackdata[4];
|
||||
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||
m_cpupasses.pop_front();
|
||||
if (m_cpupasses.empty())
|
||||
@@ -404,8 +449,10 @@ std::string opengl46_instance_cull::info()
|
||||
{
|
||||
if (m_last.frame == 0)
|
||||
return "\ngpu cull: no result yet";
|
||||
return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.occluded) + " hidden by occlusion" +
|
||||
(m_occlusion ? "" : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) + " indirect draws (+" +
|
||||
return "\ngpu cull: " + 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 (+" +
|
||||
std::to_string(m_prepassdraws) + " depth prepass), cpu " + to_string(m_cputime, 3) + " ms";
|
||||
}
|
||||
if (m_comparedframes == 0)
|
||||
@@ -421,6 +468,12 @@ std::string opengl46_instance_cull::info_times()
|
||||
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\n";
|
||||
{
|
||||
text += " instance cull: " + 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)";
|
||||
text += "\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@ class TAnimModel;
|
||||
// list of indirect draw commands (one per submodel drawn in the band), whose instances a fourth pass fills in from the
|
||||
// counts (instance_cull_commands.comp), and draws them with the output bound as the instance buffer
|
||||
// (opengl46_renderer::Render_Instances_Indirect()). an occlusion test against a depth pyramid of what the pass drew before
|
||||
// (opengl46_hiz) can leave out more instances.
|
||||
// (opengl46_hiz) can leave out more instances. in two phases the instances seen in the previous frame are culled and drawn
|
||||
// first, then the pyramid is made again with them in it (dispatch_second()) and the others are tested against it, so the
|
||||
// instances hide one another too. the command buffer holds the commands three times over: the instances drawn first, all
|
||||
// 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
|
||||
class opengl46_instance_cull
|
||||
@@ -98,8 +101,18 @@ class opengl46_instance_cull
|
||||
// Area: bounds of the cell holding the bucket, which hold the locations of all its instances
|
||||
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area);
|
||||
// runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid
|
||||
// to test the instances against, or null
|
||||
void dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion = nullptr);
|
||||
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
|
||||
// frame pass for now, dispatch_second() takes the others
|
||||
void dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion = nullptr, bool const Twophase = false);
|
||||
// the second phase, after the instances which passed dispatch() were drawn and the pyramid was made again with them:
|
||||
// the others are tested against it, then the commands of all instances and of the ones of this phase are filled in.
|
||||
// needs fill_commands() before it
|
||||
void dispatch_second(occlusion_setup const &Occlusion);
|
||||
// the last dispatch() works in two phases
|
||||
bool two_phase() const
|
||||
{
|
||||
return m_twophase;
|
||||
}
|
||||
// groups of the pass and the lod bounds they refer to, valid from dispatch() to end_pass()
|
||||
std::vector<draw_group> const &draw_groups() const
|
||||
{
|
||||
@@ -109,9 +122,22 @@ class opengl46_instance_cull
|
||||
{
|
||||
return m_lodlist;
|
||||
}
|
||||
// sends the indirect draw commands made for the entries of the pass and fills in their instances. after this the
|
||||
// commands are in the buffer bound by bind_draw(), the modelview matrices of the instances in output()
|
||||
// sends the indirect draw commands made for the entries of the pass and fills in their instances (of the instances drawn
|
||||
// first, in two phases). after this the commands are in the buffer bound by bind_draw(), the modelview matrices of the
|
||||
// instances in the output
|
||||
void fill_commands(std::vector<gl::instance_draw_command> const &Commands);
|
||||
// copies of the commands in the command buffer
|
||||
enum class command_set : std::uint32_t
|
||||
{
|
||||
first = 0, // instances which passed dispatch(): in one phase all of them
|
||||
all = 1, // after dispatch_second()
|
||||
second = 2 // the ones which passed dispatch_second()
|
||||
};
|
||||
// offset of command Index of a copy in the command buffer
|
||||
std::intptr_t draw_command_offset(command_set const Set, std::size_t const Index) const
|
||||
{
|
||||
return static_cast<std::intptr_t>((static_cast<std::size_t>(Set) * m_commandcount + Index) * sizeof(gl::instance_draw_command));
|
||||
}
|
||||
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
|
||||
void bind_draw();
|
||||
// counts of the last result which came back from the gpu, with the draws of the pass, for the debug panel
|
||||
@@ -164,10 +190,13 @@ class opengl46_instance_cull
|
||||
std::uint32_t mismatched{0};
|
||||
std::uint32_t largestdifference{0};
|
||||
std::uint32_t occluded{0}; // left out by the occlusion test
|
||||
std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases)
|
||||
};
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -186,6 +215,7 @@ class opengl46_instance_cull
|
||||
std::unique_ptr<gl::ssbo> m_commands;
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
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};
|
||||
@@ -216,4 +246,5 @@ class opengl46_instance_cull
|
||||
std::uint32_t m_commandcount{0};
|
||||
double m_cputime{0.0};
|
||||
bool m_occlusion{false}; // the last dispatch tested occlusion
|
||||
bool m_twophase{false}; // in two phases
|
||||
};
|
||||
|
||||
@@ -749,9 +749,15 @@ void opengl46_renderer::SwapBuffers()
|
||||
m_debugtimestext += " contact shadows depth: " + to_string( static_cast<double>( *contacttime ) / 1000000.0, 3 ) + " ms\n";
|
||||
}
|
||||
if( Global.gfx_gpucull == 1 ) {
|
||||
m_debugtimestext += m_hiz.info_times();
|
||||
if( auto const prepasstime = m_prepasstimer.result() )
|
||||
m_debugtimestext += " depth prepass: " + to_string( static_cast<double>( *prepasstime ) / 1000000.0, 3 ) + " ms\n";
|
||||
auto const twophase { m_instancecull.two_phase() };
|
||||
m_debugtimestext += m_hiz.info_times( twophase );
|
||||
if( auto const prepasstime = m_prepasstimer.result() ) {
|
||||
m_debugtimestext += " depth prepass: " + to_string( static_cast<double>( *prepasstime ) / 1000000.0, 3 ) + " ms";
|
||||
auto const firsttime { m_prepassfirsttimer.result() };
|
||||
if( twophase && firsttime )
|
||||
m_debugtimestext += " (+" + to_string( static_cast<double>( *firsttime ) / 1000000.0, 3 ) + " ms of the instances drawn first)";
|
||||
m_debugtimestext += "\n";
|
||||
}
|
||||
}
|
||||
m_debugtimestext += m_localshadows.info_times();
|
||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||
@@ -894,6 +900,8 @@ void opengl46_renderer::draw_debug_ui()
|
||||
if (Global.gfx_gpucull == 1)
|
||||
{
|
||||
ImGui::Checkbox("leave out instances hidden behind the depth drawn before them (gfx.gpucull.occlusion)", &Global.gfx_gpucull_occlusion);
|
||||
if (Global.gfx_gpucull_occlusion)
|
||||
ImGui::Checkbox("draw the instances seen in the last frame first, to hide the others (gfx.gpucull.occlusion.instances)", &Global.gfx_gpucull_occlusion_instances);
|
||||
ImGui::Checkbox("draw the depth of the instances before their colour (gfx.depthprepass)", &Global.gfx_depthprepass);
|
||||
}
|
||||
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
|
||||
@@ -3181,7 +3189,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
if( false == m_indirectdraws.empty() ) {
|
||||
auto const cpustart { std::chrono::steady_clock::now() };
|
||||
m_instancecull.bind_draw();
|
||||
Render_Instances_Indirect_Colour();
|
||||
Render_Instances_Indirect_Colour( m_indirectcolourset );
|
||||
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
|
||||
Bind_Instance_Stream();
|
||||
m_instancecull.draw_result(
|
||||
@@ -3810,7 +3818,12 @@ void opengl46_renderer::Render_Instances_Indirect()
|
||||
occlusion.screen = glm::vec2{ m_hiz.source_size() };
|
||||
occlusion.projection = OpenGLMatrices.data( GL_PROJECTION );
|
||||
}
|
||||
m_instancecull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ), ( useocclusion ? &occlusion : nullptr ) );
|
||||
// in two phases the instances seen in the previous frame are drawn first, and the others are tested against a pyramid
|
||||
// made again with their depth in it (not in wireframe, which doesn't draw surfaces)
|
||||
m_instancecull.dispatch(
|
||||
OpenGLMatrices.data( GL_MODELVIEW ),
|
||||
( useocclusion ? &occlusion : nullptr ),
|
||||
( Global.gfx_gpucull_occlusion_instances && ( false == Global.bWireFrame ) ) );
|
||||
|
||||
// the draws, recorded while the gpu culls
|
||||
m_indirectdraws.clear();
|
||||
@@ -3842,29 +3855,43 @@ void opengl46_renderer::Render_Instances_Indirect()
|
||||
m_indirectrecording = false;
|
||||
|
||||
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() };
|
||||
// the depth comes first in the depth pass of the whole scene, which has its state set and leaves the colour to the colour
|
||||
// pass (m_indirectcolourpending), and with the depth prepass of the instances: the colour pass shades only what stays in
|
||||
// view. the depth goes a little farther than the surface, so the colour pass, which tests and writes its depth as usual,
|
||||
// is sure to pass where it's the same
|
||||
auto const depthfirst { m_scenedepthpass || ( Global.gfx_depthprepass && ( false == Global.bWireFrame ) ) };
|
||||
std::uint32_t prepassdraws { 0 };
|
||||
if( false == m_indirectdraws.empty() ) {
|
||||
m_instancecull.bind_draw();
|
||||
if( m_scenedepthpass ) {
|
||||
// the depth pass of the whole scene, which has its state set: only the depth here, the colour pass draws the
|
||||
// colour of the same draws (m_indirectcolourpending)
|
||||
prepassdraws = Render_Instances_Indirect_Depth();
|
||||
}
|
||||
else {
|
||||
if( Global.gfx_depthprepass && ( false == Global.bWireFrame ) ) {
|
||||
// the depth first: the colour pass shades only what stays in view. the depth goes a little farther than the
|
||||
// surface, so the colour pass, which tests and writes its depth as usual, is sure to pass where it's the same
|
||||
m_prepasstimer.begin();
|
||||
setup_drawing( false );
|
||||
::glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
|
||||
::glEnable( GL_POLYGON_OFFSET_FILL );
|
||||
::glPolygonOffset( -1.f, -1.f );
|
||||
prepassdraws = Render_Instances_Indirect_Depth();
|
||||
::glDisable( GL_POLYGON_OFFSET_FILL );
|
||||
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
|
||||
m_prepasstimer.end();
|
||||
std::uint32_t colourdraws { 0 };
|
||||
auto colourset { command_set::first };
|
||||
if( twophase ) {
|
||||
if( draws ) {
|
||||
// the instances seen in the previous frame, then the pyramid again, with them in it
|
||||
m_instancecull.bind_draw();
|
||||
if( depthfirst ) {
|
||||
prepassdraws += Render_Instances_Indirect_Prepass( command_set::first, m_prepassfirsttimer );
|
||||
}
|
||||
Render_Instances_Indirect_Colour();
|
||||
else {
|
||||
Render_Instances_Indirect_Colour( command_set::first );
|
||||
colourdraws += static_cast<std::uint32_t>( m_indirectdraws.size() );
|
||||
}
|
||||
m_hiz.build( *viewport->msaa_fb, viewport->width, viewport->height, true );
|
||||
occlusion.pyramid = m_hiz.texture();
|
||||
}
|
||||
// the others are tested against it; with no draws the instances are only marked for the next frame
|
||||
m_instancecull.dispatch_second( occlusion );
|
||||
colourset = ( depthfirst ? command_set::all : command_set::second );
|
||||
}
|
||||
if( draws ) {
|
||||
m_instancecull.bind_draw();
|
||||
if( depthfirst ) {
|
||||
prepassdraws += Render_Instances_Indirect_Prepass( ( twophase ? command_set::second : command_set::first ), m_prepasstimer );
|
||||
}
|
||||
if( false == m_scenedepthpass ) {
|
||||
Render_Instances_Indirect_Colour( colourset );
|
||||
colourdraws += static_cast<std::uint32_t>( m_indirectdraws.size() );
|
||||
}
|
||||
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
|
||||
Bind_Instance_Stream();
|
||||
@@ -3872,25 +3899,42 @@ void opengl46_renderer::Render_Instances_Indirect()
|
||||
if( m_scenedepthpass ) {
|
||||
// the colour pass takes the draws from here, and counts them
|
||||
m_indirectcolourpending = true;
|
||||
m_indirectcolourset = colourset;
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
|
||||
colourdraws = static_cast<std::uint32_t>( m_indirectdraws.size() );
|
||||
}
|
||||
else {
|
||||
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( m_indirectdraws.size() );
|
||||
m_renderpass.draw_stats.instanced_drawcalls += static_cast<int>( colourdraws );
|
||||
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
|
||||
// the count of the frame the gpu reported last
|
||||
m_renderpass.draw_stats.instances += static_cast<int>( m_instancecull.last_count() );
|
||||
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
|
||||
}
|
||||
m_instancecull.draw_result(
|
||||
static_cast<std::uint32_t>( m_indirectdraws.size() ),
|
||||
colourdraws,
|
||||
prepassdraws,
|
||||
std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
|
||||
}
|
||||
|
||||
std::uint32_t opengl46_renderer::Render_Instances_Indirect_Depth()
|
||||
std::uint32_t opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer )
|
||||
{
|
||||
if( m_scenedepthpass ) {
|
||||
return Render_Instances_Indirect_Depth( Set );
|
||||
}
|
||||
Timer.begin();
|
||||
setup_drawing( false );
|
||||
::glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
|
||||
::glEnable( GL_POLYGON_OFFSET_FILL );
|
||||
::glPolygonOffset( -1.f, -1.f );
|
||||
auto const draws { Render_Instances_Indirect_Depth( Set ) };
|
||||
::glDisable( GL_POLYGON_OFFSET_FILL );
|
||||
::glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
|
||||
Timer.end();
|
||||
return draws;
|
||||
}
|
||||
|
||||
std::uint32_t opengl46_renderer::Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set )
|
||||
{
|
||||
auto const commandoffset = []( std::size_t const Index ) {
|
||||
return static_cast<std::intptr_t>( Index * sizeof( gl::instance_draw_command ) ); };
|
||||
std::uint32_t draws { 0 };
|
||||
// the draws without the alpha test first, then the ones with it, one program each
|
||||
for( auto prepassclass = 1; prepassclass <= 2; ++prepassclass ) {
|
||||
@@ -3917,24 +3961,22 @@ std::uint32_t opengl46_renderer::Render_Instances_Indirect_Depth()
|
||||
}
|
||||
model_ubs = draw.ubs;
|
||||
model_ubo->update( model_ubs );
|
||||
m_geometry.draw_indirect( draw.geometry, commandoffset( index ) );
|
||||
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
|
||||
++draws;
|
||||
}
|
||||
}
|
||||
return draws;
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_Instances_Indirect_Colour()
|
||||
void opengl46_renderer::Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set )
|
||||
{
|
||||
auto const commandoffset = []( std::size_t const Index ) {
|
||||
return static_cast<std::intptr_t>( Index * sizeof( gl::instance_draw_command ) ); };
|
||||
for( std::size_t index = 0; index < m_indirectdraws.size(); ++index ) {
|
||||
auto const &draw { m_indirectdraws[ index ] };
|
||||
if( draw.blending ) { setup_drawing( true ); }
|
||||
Bind_Material( draw.material, draw.submodel );
|
||||
model_ubs = draw.ubs;
|
||||
model_ubo->update( model_ubs );
|
||||
m_geometry.draw_indirect( draw.geometry, commandoffset( index ) );
|
||||
m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) );
|
||||
if( draw.blending ) { setup_drawing( false ); }
|
||||
}
|
||||
model_ubs.emission = 0.0f;
|
||||
|
||||
@@ -351,12 +351,17 @@ class opengl46_renderer : public gfx_renderer {
|
||||
// pyramid of the occlusion test, runs the culling, walks the submodel trees of the groups once per lod band to record
|
||||
// the draws (m_indirectdraws, m_indirectcommands), has their instances filled in, then draws them with indirect draws,
|
||||
// their depth first (Global.gfx_depthprepass). inside the depth pass of the whole scene only their depth is drawn, their
|
||||
// colour waits for the colour pass (Render_Instances_Indirect_Colour())
|
||||
// colour waits for the colour pass (Render_Instances_Indirect_Colour()). in two phases
|
||||
// (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 recorded draws of the instances: their depth with the programs of the depth prepass, with the state of the prepass
|
||||
// already set (returns the number of draws), and their colour. the command buffer and the instances are bound by the caller
|
||||
std::uint32_t Render_Instances_Indirect_Depth();
|
||||
void Render_Instances_Indirect_Colour();
|
||||
// the recorded draws of the instances, with the commands of the given set: their depth with the programs of the depth
|
||||
// prepass, with the state of the prepass already set (returns the number of draws), the same with the state set for it
|
||||
// and the time measured outside the depth pass of the whole scene, and their colour. the command buffer and the
|
||||
// instances are bound by the caller
|
||||
std::uint32_t Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set );
|
||||
std::uint32_t Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer );
|
||||
void Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set );
|
||||
// how the depth prepass treats the material: 0 left out, 1 drawn without alpha test, 2 with the alpha test of the diffuse texture
|
||||
int Prepass_Class( material_handle const Material );
|
||||
// depth of the whole opaque scene of the colour pass, before its colour (Global.gfx_depthprepass_scene): the colour pass
|
||||
@@ -552,11 +557,13 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unique_ptr<gl::program> m_prepass_alpha_shader;
|
||||
std::unordered_map<gl::program const *, int> m_prepassclasses;
|
||||
gl::gpu_timer m_prepasstimer;
|
||||
gl::gpu_timer m_prepassfirsttimer; // of the instances drawn first, in two phases
|
||||
// depth pass of the whole scene (Render_Scene_Depth())
|
||||
bool m_scenedepthpass { false }; // inside it: Bind_Material() and draw() work for the depth
|
||||
bool m_scenedepthskip { true }; // the material bound last isn't drawn in it
|
||||
bool m_scenedepthdrawn { false }; // it was drawn for the colour pass under way, which skips what was done in it already
|
||||
bool m_indirectcolourpending { false }; // the instances were culled in it, the colour pass draws them from the recorded draws
|
||||
opengl46_instance_cull::command_set m_indirectcolourset { opengl46_instance_cull::command_set::first }; // with these commands
|
||||
debug_stats m_scenedepthstats; // of its last run
|
||||
double m_scenedepthcputime { 0.0 }; // ms
|
||||
gl::gpu_timer m_scenedepthtimer;
|
||||
|
||||
@@ -247,7 +247,7 @@ public:
|
||||
float rangesquaredmin { 0.f }; // visibility range of the instance
|
||||
float rangesquaredmax { 0.f };
|
||||
std::uint32_t flags { 0 }; // instance_record_flags
|
||||
std::uint32_t pad { 0 };
|
||||
std::uint32_t pad { 0 }; // the copy of the gpu culling keeps there whether the instance was seen in the last frame
|
||||
glm::vec4 rotationscale[ 3 ] {}; // columns of rotation and scale of the instance, w unused
|
||||
};
|
||||
static_assert( sizeof( instance_record ) == 80, "instance_record has to keep its std430 layout" );
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// culls the model instances of the buckets queued in a render pass and assigns them their lod bands, with the tests
|
||||
// opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket.
|
||||
// stage 0 counts the instances which pass, per entry (lod band of a group); after instance_cull_offsets.comp turned the
|
||||
// counts into the first slots of the entries, stage 1 writes their modelview matrices there, in no particular order.
|
||||
// stage 0 counts the instances which pass the tests, per entry (lod band of a group); after instance_cull_offsets.comp
|
||||
// turned the counts into the first slots of the entries, stage 1 writes the modelview matrices of the instances drawn first
|
||||
// there, in no particular order, and stage 2 (two phases) those of the instances drawn second after them.
|
||||
// with the occlusion test on, an instance whose bounding sphere lies whole behind the depth already drawn in the pass
|
||||
// (the farthest depth of the pyramid over the part of the view it takes, see hiz_build.comp) is left out as well
|
||||
// (the farthest depth of the pyramid over the part of the view it takes, see hiz_build.comp) is left out as well.
|
||||
// in two phases (opengl46_instance_cull::dispatch_second()) stage 1 takes only the instances seen in the previous frame,
|
||||
// which are drawn, the pyramid is made again with them in it, and stage 2 tests the others against it; it also keeps for
|
||||
// the next frame which instances it found in view. an instance seen before but hidden behind the depth of the first
|
||||
// pyramid is hidden behind the second one as well (it only holds nearer depth), so each instance is drawn at most once,
|
||||
// and everything left out is hidden behind something drawn
|
||||
|
||||
#include <common>
|
||||
|
||||
@@ -17,7 +23,7 @@ struct instance_record
|
||||
float rangesquaredmin;
|
||||
float rangesquaredmax;
|
||||
uint flags;
|
||||
uint pad;
|
||||
uint visible; // the instance was seen in the previous frame (written by stage 2, 0 when the cpu sends the record)
|
||||
vec4 rotationscale[3];
|
||||
};
|
||||
|
||||
@@ -41,7 +47,7 @@ struct instance_cull_group
|
||||
uint pad;
|
||||
};
|
||||
|
||||
layout(std430, binding = INSTANCE_RECORD_SSBO_BINDING) readonly buffer instance_record_ssbo
|
||||
layout(std430, binding = INSTANCE_RECORD_SSBO_BINDING) buffer instance_record_ssbo
|
||||
{
|
||||
instance_record records[];
|
||||
};
|
||||
@@ -82,6 +88,8 @@ layout(location = 10) uniform mat4 hiz_projection;
|
||||
layout(location = 14) uniform ivec4 hiz_params;
|
||||
// xy: size of the depth the pyramid was made from, pixels
|
||||
layout(location = 15) uniform vec4 hiz_screen;
|
||||
// x: two phases, stage 1 takes only the instances seen in the previous frame
|
||||
layout(location = 16) uniform uvec4 phases;
|
||||
|
||||
// depth pyramid, farthest (smallest, reversed) depth per texel
|
||||
layout(binding = 0) uniform sampler2D hiz;
|
||||
@@ -90,6 +98,9 @@ const uint INSTANCE_VISIBLE = 1u;
|
||||
const uint INSTANCE_INSTANCEABLE = 2u;
|
||||
// smallest part of the view an instance has to take
|
||||
const float MIN_SCREEN_SIZE = 0.003;
|
||||
// header of the counters, see gl::INSTANCE_COUNTER_HEADER
|
||||
const uint COUNTER_OCCLUDED = 3u;
|
||||
const uint COUNTER_FIRST = 4u;
|
||||
|
||||
// the job which holds the given record: the last one starting at it or before it
|
||||
uint find_job(uint candidate)
|
||||
@@ -151,43 +162,33 @@ bool occluded(vec3 center, float radius)
|
||||
return nearest < farthest;
|
||||
}
|
||||
|
||||
void main()
|
||||
// the tests of the cpu (Render_Instanced()), all but the occlusion one: the entry of the instance, or false when it's left out.
|
||||
// Location: relative to the camera of the pass
|
||||
bool passes(instance_cull_job job, instance_record record, out vec3 location, out uint entry)
|
||||
{
|
||||
// the dispatch can be two dimensional when the records don't fit in the work group count limit of one
|
||||
uint candidate = (gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x) * INSTANCE_CULL_GROUP_SIZE + gl_LocalInvocationIndex;
|
||||
if (candidate >= counts.y)
|
||||
return;
|
||||
|
||||
instance_cull_job job = jobs[find_job(candidate)];
|
||||
instance_record record = records[job.first + (candidate - job.candidatestart)];
|
||||
location = job.origin + record.position;
|
||||
entry = 0u;
|
||||
// instances hidden, or drawn on their own after getting animations or skins from events
|
||||
if ((record.flags & (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE)) != (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE))
|
||||
return;
|
||||
return false;
|
||||
|
||||
vec3 location = job.origin + record.position;
|
||||
for (int plane = 0; plane < 6; ++plane)
|
||||
{
|
||||
if (dot(planes[plane].xyz, location) + planes[plane].w <= -record.radius)
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
vec3 offset = job.distanceorigin + record.position;
|
||||
float distancesquared = dot(offset, offset) * distance_params.x;
|
||||
if (distancesquared > distance_params.z)
|
||||
return;
|
||||
return false;
|
||||
distancesquared += distance_params.y;
|
||||
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
|
||||
return;
|
||||
return false;
|
||||
if (distancesquared > distance_params.w)
|
||||
return;
|
||||
return false;
|
||||
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
|
||||
return;
|
||||
if ((hiz_params.x != 0) && occluded((view * vec4(location, 1.0)).xyz, record.radius))
|
||||
{
|
||||
if (counts.w == 0u)
|
||||
atomicAdd(counters[3], 1u);
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
|
||||
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
|
||||
instance_cull_group group = groups[job.group];
|
||||
@@ -197,15 +198,65 @@ void main()
|
||||
if (lodbounds[group.lodfirst + bound] <= distancesquared)
|
||||
band = bound + 1u;
|
||||
}
|
||||
uint entry = group.entryfirst + band;
|
||||
entry = group.entryfirst + band;
|
||||
return true;
|
||||
}
|
||||
|
||||
void write(uint entry, instance_record record, vec3 location)
|
||||
{
|
||||
uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u);
|
||||
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// the dispatch can be two dimensional when the records don't fit in the work group count limit of one
|
||||
uint candidate = (gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x) * INSTANCE_CULL_GROUP_SIZE + gl_LocalInvocationIndex;
|
||||
if (candidate >= counts.y)
|
||||
return;
|
||||
|
||||
instance_cull_job job = jobs[find_job(candidate)];
|
||||
uint index = job.first + (candidate - job.candidatestart);
|
||||
instance_record record = records[index];
|
||||
vec3 location;
|
||||
uint entry;
|
||||
bool passed = passes(job, record, location, entry);
|
||||
|
||||
if (counts.w == 0u)
|
||||
{
|
||||
atomicAdd(counters[INSTANCE_COUNTER_HEADER + entry], 1u);
|
||||
// room in the output for every instance which passes, whatever the occlusion test says
|
||||
if (passed)
|
||||
atomicAdd(counters[INSTANCE_COUNTER_HEADER + entry], 1u);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
bool seen = (record.visible != 0u);
|
||||
if (counts.w == 1u)
|
||||
{
|
||||
uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u);
|
||||
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
|
||||
// the ones seen in the previous frame first, the others wait for stage 2
|
||||
if ((false == passed) || ((phases.x != 0u) && (false == seen)))
|
||||
return;
|
||||
if ((hiz_params.x != 0) && occluded((view * vec4(location, 1.0)).xyz, record.radius))
|
||||
{
|
||||
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
|
||||
return;
|
||||
}
|
||||
write(entry, record, location);
|
||||
if (phases.x != 0u)
|
||||
atomicAdd(counters[COUNTER_FIRST], 1u);
|
||||
return;
|
||||
}
|
||||
|
||||
// stage 2, against the pyramid with the instances drawn first in it
|
||||
bool inview = passed && (false == occluded((view * vec4(location, 1.0)).xyz, record.radius));
|
||||
if (passed && (false == seen))
|
||||
{
|
||||
if (inview)
|
||||
write(entry, record, location);
|
||||
else
|
||||
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
|
||||
}
|
||||
uint visible = (inview ? 1u : 0u);
|
||||
if (visible != record.visible)
|
||||
records[index].visible = visible;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// fills in the instances of the indirect draw commands of the entries, after stage 1 of instance_cull.comp wrote the
|
||||
// modelview matrices of the instances which passed. one invocation per command: the instance count is the count of its
|
||||
// entry, the base instance the first slot of the entry in the output (its cursor, advanced by stage 1, less the count)
|
||||
// fills in the instances of the indirect draw commands of the entries, after instance_cull.comp wrote the modelview
|
||||
// matrices of the instances drawn. one invocation per command. the commands are kept three times over: the first copy
|
||||
// draws the instances of stage 1, the second all of them, the third those of stage 2 (two phases), each from its first
|
||||
// slot in the output on. stage 0 (after stage 1): the instance count of the first copy is how far stage 1 advanced the
|
||||
// cursor of its entry, the base instance the first slot of the entry. stage 1 (after stage 2): the other two copies take
|
||||
// the command of the first one, the second with the instances of both stages, the third with the ones past the first copy
|
||||
|
||||
#include <common>
|
||||
|
||||
@@ -18,9 +21,18 @@ layout(std430, binding = INSTANCE_COMMAND_SSBO_BINDING) buffer instance_command_
|
||||
uint commands[];
|
||||
};
|
||||
|
||||
// x: commands, y: entries
|
||||
// x: commands (of one copy), y: entries, z: stage
|
||||
layout(location = 0) uniform uvec4 counts;
|
||||
|
||||
void set_instances(uint field, uint count, uint base)
|
||||
{
|
||||
commands[field + 1u] = count;
|
||||
if (commands[field + 6u] != 0u)
|
||||
commands[field + 4u] = base;
|
||||
else
|
||||
commands[field + 3u] = base;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint command = gl_GlobalInvocationID.x;
|
||||
@@ -29,11 +41,22 @@ void main()
|
||||
|
||||
uint field = command * 8u;
|
||||
uint entry = commands[field + 5u];
|
||||
uint count = counters[INSTANCE_COUNTER_HEADER + entry];
|
||||
uint base = counters[INSTANCE_COUNTER_HEADER + counts.y + entry] - count;
|
||||
commands[field + 1u] = count;
|
||||
if (commands[field + 6u] != 0u)
|
||||
commands[field + 4u] = base;
|
||||
else
|
||||
commands[field + 3u] = base;
|
||||
uint first = counters[INSTANCE_COUNTER_HEADER + 2u * counts.y + entry];
|
||||
uint count = counters[INSTANCE_COUNTER_HEADER + counts.y + entry] - first;
|
||||
if (counts.z == 0u)
|
||||
{
|
||||
set_instances(field, count, first);
|
||||
return;
|
||||
}
|
||||
|
||||
uint drawnfirst = commands[field + 1u];
|
||||
uint all = (counts.x + command) * 8u;
|
||||
uint second = (2u * counts.x + command) * 8u;
|
||||
for (uint value = 0u; value < 8u; ++value)
|
||||
{
|
||||
commands[all + value] = commands[field + value];
|
||||
commands[second + value] = commands[field + value];
|
||||
}
|
||||
set_instances(all, count, first);
|
||||
set_instances(second, count - drawnfirst, first + drawnfirst);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// turns the counts of instances of the entries, written by stage 0 of instance_cull.comp, into the first slots of the
|
||||
// entries in the output (cursors, which stage 1 advances as it writes), and puts the total in the header.
|
||||
// entries in the output (cursors, which stages 1 and 2 advance as they write, and the same first slots kept for
|
||||
// instance_cull_commands.comp), and puts the total in the header.
|
||||
// a single work group: each invocation sums a run of the entries, then the sums are scanned in shared memory
|
||||
|
||||
#include <common>
|
||||
@@ -46,6 +47,7 @@ void main()
|
||||
for (uint entry = begin; entry < end; ++entry)
|
||||
{
|
||||
counters[INSTANCE_COUNTER_HEADER + entries + entry] = offset;
|
||||
counters[INSTANCE_COUNTER_HEADER + 2u * entries + entry] = offset;
|
||||
offset += counters[INSTANCE_COUNTER_HEADER + entry];
|
||||
}
|
||||
if (invocation == INSTANCE_OFFSETS_GROUP_SIZE - 1u)
|
||||
|
||||
@@ -1439,6 +1439,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_gpucull_occlusion;
|
||||
}
|
||||
else if (Token == "gfx.gpucull.occlusion.instances")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_gpucull_occlusion_instances;
|
||||
}
|
||||
else if (Token == "gfx.depthprepass")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
@@ -1934,6 +1939,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "gfx.lights.clusterheatmap", gfx_lights_clusterheatmap );
|
||||
export_as_text( Output, "gfx.gpucull", std::string{ gfx_gpucull == 1 ? "yes" : gfx_gpucull == 2 ? "compare" : "no" } );
|
||||
export_as_text( Output, "gfx.gpucull.occlusion", gfx_gpucull_occlusion );
|
||||
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.depthprepass.scene", gfx_depthprepass_scene );
|
||||
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
|
||||
|
||||
@@ -289,6 +289,7 @@ struct global_settings {
|
||||
// drawn from with indirect draws, 2 on the cpu, which draws, and in a compute pass too, for the results to be compared
|
||||
int gfx_gpucull = 0;
|
||||
bool gfx_gpucull_occlusion = true; // with gfx_gpucull 1: instances hidden behind the depth drawn before them are left out
|
||||
bool gfx_gpucull_occlusion_instances = true; // with gfx_gpucull_occlusion: in two phases, so that instances hide one another too
|
||||
bool gfx_depthprepass = true; // with gfx_gpucull 1: the depth of the instances is drawn before their colour
|
||||
// (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;
|
||||
|
||||
Reference in New Issue
Block a user