mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 22:25:45 +01: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` | `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 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 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 |
|
| `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
|
With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn
|
||||||
drawn (its colour is not worked out with the depth prepass on). Models whose material is not one of the usual ones (parallax,
|
first (their depth, or their colour without the depth prepass), the depth they leave is added to what the others are tested
|
||||||
water and the like) are left out of the depth prepass and drawn as before. All three can be changed while the simulator
|
against, and only then are the others tested and drawn; the graphics card also notes which models it saw, for the next frame.
|
||||||
runs, in the **Instance culling** debug window.
|
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
|
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
|
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
|
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
|
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:`.
|
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
|
- **Instance culling** window: the mode of `gfx.gpucull` and the 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
|
`gpu cull:` (models drawn, in two phases how many of them first, and hidden by the occlusion test, as counted on the
|
||||||
of detail, the draw calls with those of the depth prepass, and the processor time of the whole step) and the timings
|
graphics card a few frames before, the levels of detail, the draw calls with those of the depth prepass, and the processor
|
||||||
`instance cull:`, `occlusion depth:` (the depth pyramid of the test) and `depth prepass:`. With `compare` it shows how many
|
time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and
|
||||||
models both counted and how many levels of detail differ.
|
`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)
|
- 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).
|
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
|
- 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_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_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_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_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_COMMAND_SSBO_BINDING = 15; // indirect draw commands of the entries (instance_cull_commands.comp)
|
||||||
const GLuint INSTANCE_CULL_GROUP_SIZE = 64;
|
const GLuint INSTANCE_CULL_GROUP_SIZE = 64;
|
||||||
const GLuint INSTANCE_OFFSETS_GROUP_SIZE = 1024;
|
const GLuint INSTANCE_OFFSETS_GROUP_SIZE = 1024;
|
||||||
const GLuint INSTANCE_COMMAND_GROUP_SIZE = 64;
|
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
|
// uints of the header of the counter buffer: frame number, survivors of the tests but the occlusion one in total,
|
||||||
// behind the depth already drawn (occlusion test).
|
// 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
|
// 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
|
// work group of the passes which make the depth pyramid of the occlusion test (hiz_build.comp), 8x8
|
||||||
const GLuint HIZ_GROUP_SIZE = 8;
|
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
|
// 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
|
// 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
|
// 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
|
struct instance_draw_command
|
||||||
{
|
{
|
||||||
uint32_t count;
|
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);
|
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))
|
if ((Width <= 0) || (Height <= 0))
|
||||||
return;
|
return;
|
||||||
if (m_sourcesize != glm::ivec2{Width, Height})
|
if (m_sourcesize != glm::ivec2{Width, Height})
|
||||||
allocate(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
|
// resolve of the multisampled depth; the scissor test would limit the blit
|
||||||
auto const scissor{glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
|
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);
|
glBindTextureUnit(0, 0);
|
||||||
opengl_texture::reset_unit_cache();
|
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;
|
std::string text;
|
||||||
if (auto const time = m_timer.result())
|
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;
|
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
|
// 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.
|
// 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
|
// 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
|
class opengl46_hiz
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -31,8 +32,9 @@ class opengl46_hiz
|
|||||||
// creates the program. throws gl::shader_exception
|
// creates the program. throws gl::shader_exception
|
||||||
void init();
|
void init();
|
||||||
// makes the pyramid from the depth attachment of Source, a framebuffer of Width x Height pixels.
|
// 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)
|
// 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);
|
// 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()
|
// the pyramid, its first level size and level count; valid after build()
|
||||||
GLuint texture() const
|
GLuint texture() const
|
||||||
{
|
{
|
||||||
@@ -50,7 +52,8 @@ class opengl46_hiz
|
|||||||
{
|
{
|
||||||
return m_sourcesize;
|
return m_sourcesize;
|
||||||
}
|
}
|
||||||
std::string info_times();
|
// Second: with the time of the second pyramid
|
||||||
|
std::string info_times(bool const Second);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// (re)creates the textures for a view of the given size
|
// (re)creates the textures for a view of the given size
|
||||||
@@ -65,4 +68,5 @@ class opengl46_hiz
|
|||||||
glm::ivec2 m_size{0};
|
glm::ivec2 m_size{0};
|
||||||
int m_levels{0};
|
int m_levels{0};
|
||||||
gl::gpu_timer m_timer;
|
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);
|
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_occlusion = false;
|
||||||
|
m_twophase = false;
|
||||||
if (!m_inpass || m_joblist.empty())
|
if (!m_inpass || m_joblist.empty())
|
||||||
return;
|
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_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_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)));
|
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)));
|
m_lods->update(m_lodlist.data(), 0, static_cast<GLsizeiptr>(m_lodlist.size() * sizeof(float)));
|
||||||
// counts and cursors start at zero
|
// counts and cursors start at zero
|
||||||
glClearNamedBufferSubData(*m_counters, GL_R32UI, 0, countersize, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
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_counters->update(header, 0, sizeof(header));
|
||||||
|
|
||||||
m_records->bind_base();
|
bind_buffers();
|
||||||
m_jobs->bind_base();
|
|
||||||
m_groups->bind_base();
|
|
||||||
m_lods->bind_base();
|
|
||||||
m_counters->bind_base();
|
|
||||||
m_output->bind_base();
|
|
||||||
|
|
||||||
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
||||||
auto const &frustumplanes{m_setup.frustum->planes()};
|
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);
|
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
|
// 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};
|
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();
|
opengl_texture::reset_unit_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_readback->push(*m_counters);
|
|
||||||
m_cpucounts.assign(m_entries, 0);
|
m_cpucounts.assign(m_entries, 0);
|
||||||
m_dispatched = true;
|
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)
|
void opengl46_instance_cull::fill_commands(std::vector<gl::instance_draw_command> const &Commands)
|
||||||
{
|
{
|
||||||
m_commandcount = static_cast<std::uint32_t>(Commands.size());
|
m_commandcount = static_cast<std::uint32_t>(Commands.size());
|
||||||
if (!m_dispatched || Commands.empty())
|
if (!m_dispatched || Commands.empty())
|
||||||
return;
|
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))};
|
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_commands->update(Commands.data(), 0, size);
|
||||||
m_counters->bind_base();
|
m_counters->bind_base();
|
||||||
m_commands->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()
|
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
|
// the cpu counts are there to compare with only when the cpu drew
|
||||||
if (m_dispatched && !m_setup.drawing)
|
if (m_dispatched && !m_setup.drawing)
|
||||||
{
|
{
|
||||||
@@ -363,9 +407,10 @@ void opengl46_instance_cull::collect()
|
|||||||
auto const frame{m_readbackdata[0]};
|
auto const frame{m_readbackdata[0]};
|
||||||
comparison result;
|
comparison result;
|
||||||
result.frame = frame;
|
result.frame = frame;
|
||||||
result.gputotal = m_readbackdata[1];
|
|
||||||
result.entries = m_readbackdata[2];
|
|
||||||
result.occluded = m_readbackdata[3];
|
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)
|
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||||
m_cpupasses.pop_front();
|
m_cpupasses.pop_front();
|
||||||
if (m_cpupasses.empty())
|
if (m_cpupasses.empty())
|
||||||
@@ -404,8 +449,10 @@ std::string opengl46_instance_cull::info()
|
|||||||
{
|
{
|
||||||
if (m_last.frame == 0)
|
if (m_last.frame == 0)
|
||||||
return "\ngpu cull: no result yet";
|
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" +
|
return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances drawn" +
|
||||||
(m_occlusion ? "" : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) + " indirect draws (+" +
|
(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";
|
std::to_string(m_prepassdraws) + " depth prepass), cpu " + to_string(m_cputime, 3) + " ms";
|
||||||
}
|
}
|
||||||
if (m_comparedframes == 0)
|
if (m_comparedframes == 0)
|
||||||
@@ -421,6 +468,12 @@ std::string opengl46_instance_cull::info_times()
|
|||||||
if (Global.gfx_gpucull == 0)
|
if (Global.gfx_gpucull == 0)
|
||||||
return text;
|
return text;
|
||||||
if (auto const time = m_timer.result())
|
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;
|
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
|
// 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
|
// 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_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
|
// 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
|
// read back a few frames later and set against the counts Render_Instanced() reported for the same frame
|
||||||
class opengl46_instance_cull
|
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
|
// 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);
|
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
|
// 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
|
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
|
||||||
void dispatch(glm::mat4 const &View, occlusion_setup const *Occlusion = nullptr);
|
// 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()
|
// 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
|
std::vector<draw_group> const &draw_groups() const
|
||||||
{
|
{
|
||||||
@@ -109,9 +122,22 @@ class opengl46_instance_cull
|
|||||||
{
|
{
|
||||||
return m_lodlist;
|
return m_lodlist;
|
||||||
}
|
}
|
||||||
// sends the indirect draw commands made for the entries of the pass and fills in their instances. after this the
|
// sends the indirect draw commands made for the entries of the pass and fills in their instances (of the instances drawn
|
||||||
// commands are in the buffer bound by bind_draw(), the modelview matrices of the instances in output()
|
// 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);
|
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
|
// binds the commands to GL_DRAW_INDIRECT_BUFFER and the output in place of the instance buffer of the frame
|
||||||
void bind_draw();
|
void bind_draw();
|
||||||
// counts of the last result which came back from the gpu, with the draws of the pass, for the debug panel
|
// 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 mismatched{0};
|
||||||
std::uint32_t largestdifference{0};
|
std::uint32_t largestdifference{0};
|
||||||
std::uint32_t occluded{0}; // left out by the occlusion test
|
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
|
// 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);
|
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
|
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu
|
||||||
void collect();
|
void collect();
|
||||||
|
|
||||||
@@ -186,6 +215,7 @@ class opengl46_instance_cull
|
|||||||
std::unique_ptr<gl::ssbo> m_commands;
|
std::unique_ptr<gl::ssbo> m_commands;
|
||||||
std::unique_ptr<gl::readback> m_readback;
|
std::unique_ptr<gl::readback> m_readback;
|
||||||
gl::gpu_timer m_timer;
|
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
|
// 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};
|
std::uint32_t m_recordfill{0};
|
||||||
@@ -216,4 +246,5 @@ class opengl46_instance_cull
|
|||||||
std::uint32_t m_commandcount{0};
|
std::uint32_t m_commandcount{0};
|
||||||
double m_cputime{0.0};
|
double m_cputime{0.0};
|
||||||
bool m_occlusion{false}; // the last dispatch tested occlusion
|
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";
|
m_debugtimestext += " contact shadows depth: " + to_string( static_cast<double>( *contacttime ) / 1000000.0, 3 ) + " ms\n";
|
||||||
}
|
}
|
||||||
if( Global.gfx_gpucull == 1 ) {
|
if( Global.gfx_gpucull == 1 ) {
|
||||||
m_debugtimestext += m_hiz.info_times();
|
auto const twophase { m_instancecull.two_phase() };
|
||||||
if( auto const prepasstime = m_prepasstimer.result() )
|
m_debugtimestext += m_hiz.info_times( twophase );
|
||||||
m_debugtimestext += " depth prepass: " + to_string( static_cast<double>( *prepasstime ) / 1000000.0, 3 ) + " ms\n";
|
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 += m_localshadows.info_times();
|
||||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
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)
|
if (Global.gfx_gpucull == 1)
|
||||||
{
|
{
|
||||||
ImGui::Checkbox("leave out instances hidden behind the depth drawn before them (gfx.gpucull.occlusion)", &Global.gfx_gpucull_occlusion);
|
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 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);
|
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() ) {
|
if( false == m_indirectdraws.empty() ) {
|
||||||
auto const cpustart { std::chrono::steady_clock::now() };
|
auto const cpustart { std::chrono::steady_clock::now() };
|
||||||
m_instancecull.bind_draw();
|
m_instancecull.bind_draw();
|
||||||
Render_Instances_Indirect_Colour();
|
Render_Instances_Indirect_Colour( m_indirectcolourset );
|
||||||
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
|
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
|
||||||
Bind_Instance_Stream();
|
Bind_Instance_Stream();
|
||||||
m_instancecull.draw_result(
|
m_instancecull.draw_result(
|
||||||
@@ -3810,7 +3818,12 @@ void opengl46_renderer::Render_Instances_Indirect()
|
|||||||
occlusion.screen = glm::vec2{ m_hiz.source_size() };
|
occlusion.screen = glm::vec2{ m_hiz.source_size() };
|
||||||
occlusion.projection = OpenGLMatrices.data( GL_PROJECTION );
|
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
|
// the draws, recorded while the gpu culls
|
||||||
m_indirectdraws.clear();
|
m_indirectdraws.clear();
|
||||||
@@ -3842,29 +3855,43 @@ void opengl46_renderer::Render_Instances_Indirect()
|
|||||||
m_indirectrecording = false;
|
m_indirectrecording = false;
|
||||||
|
|
||||||
m_instancecull.fill_commands( m_indirectcommands );
|
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 };
|
std::uint32_t prepassdraws { 0 };
|
||||||
if( false == m_indirectdraws.empty() ) {
|
std::uint32_t colourdraws { 0 };
|
||||||
m_instancecull.bind_draw();
|
auto colourset { command_set::first };
|
||||||
if( m_scenedepthpass ) {
|
if( twophase ) {
|
||||||
// the depth pass of the whole scene, which has its state set: only the depth here, the colour pass draws the
|
if( draws ) {
|
||||||
// colour of the same draws (m_indirectcolourpending)
|
// the instances seen in the previous frame, then the pyramid again, with them in it
|
||||||
prepassdraws = Render_Instances_Indirect_Depth();
|
m_instancecull.bind_draw();
|
||||||
}
|
if( depthfirst ) {
|
||||||
else {
|
prepassdraws += Render_Instances_Indirect_Prepass( command_set::first, m_prepassfirsttimer );
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
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 );
|
::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 );
|
||||||
Bind_Instance_Stream();
|
Bind_Instance_Stream();
|
||||||
@@ -3872,25 +3899,42 @@ void opengl46_renderer::Render_Instances_Indirect()
|
|||||||
if( m_scenedepthpass ) {
|
if( m_scenedepthpass ) {
|
||||||
// the colour pass takes the draws from here, and counts them
|
// the colour pass takes the draws from here, and counts them
|
||||||
m_indirectcolourpending = true;
|
m_indirectcolourpending = true;
|
||||||
|
m_indirectcolourset = colourset;
|
||||||
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
|
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
|
||||||
|
colourdraws = static_cast<std::uint32_t>( m_indirectdraws.size() );
|
||||||
}
|
}
|
||||||
else {
|
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 );
|
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepassdraws );
|
||||||
// the count of the frame the gpu reported last
|
// 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.instances += static_cast<int>( m_instancecull.last_count() );
|
||||||
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
|
m_renderpass.draw_stats.models += static_cast<int>( m_instancecull.last_count() );
|
||||||
}
|
}
|
||||||
m_instancecull.draw_result(
|
m_instancecull.draw_result(
|
||||||
static_cast<std::uint32_t>( m_indirectdraws.size() ),
|
colourdraws,
|
||||||
prepassdraws,
|
prepassdraws,
|
||||||
std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - cpustart ).count() );
|
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 };
|
std::uint32_t draws { 0 };
|
||||||
// the draws without the alpha test first, then the ones with it, one program each
|
// the draws without the alpha test first, then the ones with it, one program each
|
||||||
for( auto prepassclass = 1; prepassclass <= 2; ++prepassclass ) {
|
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_ubs = draw.ubs;
|
||||||
model_ubo->update( model_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;
|
++draws;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 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 ) {
|
for( std::size_t index = 0; index < m_indirectdraws.size(); ++index ) {
|
||||||
auto const &draw { m_indirectdraws[ index ] };
|
auto const &draw { m_indirectdraws[ index ] };
|
||||||
if( draw.blending ) { setup_drawing( true ); }
|
if( draw.blending ) { setup_drawing( true ); }
|
||||||
Bind_Material( draw.material, draw.submodel );
|
Bind_Material( draw.material, draw.submodel );
|
||||||
model_ubs = draw.ubs;
|
model_ubs = draw.ubs;
|
||||||
model_ubo->update( model_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 ); }
|
if( draw.blending ) { setup_drawing( false ); }
|
||||||
}
|
}
|
||||||
model_ubs.emission = 0.0f;
|
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
|
// 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,
|
// 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
|
// 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();
|
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
|
// the recorded draws of the instances, with the commands of the given set: their depth with the programs of the depth
|
||||||
// already set (returns the number of draws), and their colour. the command buffer and the instances are bound by the caller
|
// prepass, with the state of the prepass already set (returns the number of draws), the same with the state set for it
|
||||||
std::uint32_t Render_Instances_Indirect_Depth();
|
// and the time measured outside the depth pass of the whole scene, and their colour. the command buffer and the
|
||||||
void Render_Instances_Indirect_Colour();
|
// 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
|
// 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 );
|
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
|
// 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::unique_ptr<gl::program> m_prepass_alpha_shader;
|
||||||
std::unordered_map<gl::program const *, int> m_prepassclasses;
|
std::unordered_map<gl::program const *, int> m_prepassclasses;
|
||||||
gl::gpu_timer m_prepasstimer;
|
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())
|
// 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_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_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_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
|
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
|
debug_stats m_scenedepthstats; // of its last run
|
||||||
double m_scenedepthcputime { 0.0 }; // ms
|
double m_scenedepthcputime { 0.0 }; // ms
|
||||||
gl::gpu_timer m_scenedepthtimer;
|
gl::gpu_timer m_scenedepthtimer;
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ public:
|
|||||||
float rangesquaredmin { 0.f }; // visibility range of the instance
|
float rangesquaredmin { 0.f }; // visibility range of the instance
|
||||||
float rangesquaredmax { 0.f };
|
float rangesquaredmax { 0.f };
|
||||||
std::uint32_t flags { 0 }; // instance_record_flags
|
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
|
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" );
|
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
|
// 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.
|
// 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
|
// stage 0 counts the instances which pass the tests, per entry (lod band of a group); after instance_cull_offsets.comp
|
||||||
// counts into the first slots of the entries, stage 1 writes their modelview matrices there, in no particular order.
|
// 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
|
// 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>
|
#include <common>
|
||||||
|
|
||||||
@@ -17,7 +23,7 @@ struct instance_record
|
|||||||
float rangesquaredmin;
|
float rangesquaredmin;
|
||||||
float rangesquaredmax;
|
float rangesquaredmax;
|
||||||
uint flags;
|
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];
|
vec4 rotationscale[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +47,7 @@ struct instance_cull_group
|
|||||||
uint pad;
|
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[];
|
instance_record records[];
|
||||||
};
|
};
|
||||||
@@ -82,6 +88,8 @@ layout(location = 10) uniform mat4 hiz_projection;
|
|||||||
layout(location = 14) uniform ivec4 hiz_params;
|
layout(location = 14) uniform ivec4 hiz_params;
|
||||||
// xy: size of the depth the pyramid was made from, pixels
|
// xy: size of the depth the pyramid was made from, pixels
|
||||||
layout(location = 15) uniform vec4 hiz_screen;
|
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
|
// depth pyramid, farthest (smallest, reversed) depth per texel
|
||||||
layout(binding = 0) uniform sampler2D hiz;
|
layout(binding = 0) uniform sampler2D hiz;
|
||||||
@@ -90,6 +98,9 @@ const uint INSTANCE_VISIBLE = 1u;
|
|||||||
const uint INSTANCE_INSTANCEABLE = 2u;
|
const uint INSTANCE_INSTANCEABLE = 2u;
|
||||||
// smallest part of the view an instance has to take
|
// smallest part of the view an instance has to take
|
||||||
const float MIN_SCREEN_SIZE = 0.003;
|
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
|
// the job which holds the given record: the last one starting at it or before it
|
||||||
uint find_job(uint candidate)
|
uint find_job(uint candidate)
|
||||||
@@ -151,43 +162,33 @@ bool occluded(vec3 center, float radius)
|
|||||||
return nearest < farthest;
|
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
|
location = job.origin + record.position;
|
||||||
uint candidate = (gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x) * INSTANCE_CULL_GROUP_SIZE + gl_LocalInvocationIndex;
|
entry = 0u;
|
||||||
if (candidate >= counts.y)
|
|
||||||
return;
|
|
||||||
|
|
||||||
instance_cull_job job = jobs[find_job(candidate)];
|
|
||||||
instance_record record = records[job.first + (candidate - job.candidatestart)];
|
|
||||||
// instances hidden, or drawn on their own after getting animations or skins from events
|
// 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))
|
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)
|
for (int plane = 0; plane < 6; ++plane)
|
||||||
{
|
{
|
||||||
if (dot(planes[plane].xyz, location) + planes[plane].w <= -record.radius)
|
if (dot(planes[plane].xyz, location) + planes[plane].w <= -record.radius)
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 offset = job.distanceorigin + record.position;
|
vec3 offset = job.distanceorigin + record.position;
|
||||||
float distancesquared = dot(offset, offset) * distance_params.x;
|
float distancesquared = dot(offset, offset) * distance_params.x;
|
||||||
if (distancesquared > distance_params.z)
|
if (distancesquared > distance_params.z)
|
||||||
return;
|
return false;
|
||||||
distancesquared += distance_params.y;
|
distancesquared += distance_params.y;
|
||||||
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
|
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
|
||||||
return;
|
return false;
|
||||||
if (distancesquared > distance_params.w)
|
if (distancesquared > distance_params.w)
|
||||||
return;
|
return false;
|
||||||
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
|
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
|
||||||
return;
|
return false;
|
||||||
if ((hiz_params.x != 0) && occluded((view * vec4(location, 1.0)).xyz, record.radius))
|
|
||||||
{
|
|
||||||
if (counts.w == 0u)
|
|
||||||
atomicAdd(counters[3], 1u);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
|
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
|
||||||
instance_cull_group group = groups[job.group];
|
instance_cull_group group = groups[job.group];
|
||||||
@@ -197,15 +198,65 @@ void main()
|
|||||||
if (lodbounds[group.lodfirst + bound] <= distancesquared)
|
if (lodbounds[group.lodfirst + bound] <= distancesquared)
|
||||||
band = bound + 1u;
|
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)
|
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);
|
// the ones seen in the previous frame first, the others wait for stage 2
|
||||||
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
|
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
|
// fills in the instances of the indirect draw commands of the entries, after instance_cull.comp wrote the modelview
|
||||||
// modelview matrices of the instances which passed. one invocation per command: the instance count is the count of its
|
// matrices of the instances drawn. one invocation per command. the commands are kept three times over: the first copy
|
||||||
// entry, the base instance the first slot of the entry in the output (its cursor, advanced by stage 1, less the count)
|
// 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>
|
#include <common>
|
||||||
|
|
||||||
@@ -18,9 +21,18 @@ layout(std430, binding = INSTANCE_COMMAND_SSBO_BINDING) buffer instance_command_
|
|||||||
uint commands[];
|
uint commands[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// x: commands, y: entries
|
// x: commands (of one copy), y: entries, z: stage
|
||||||
layout(location = 0) uniform uvec4 counts;
|
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()
|
void main()
|
||||||
{
|
{
|
||||||
uint command = gl_GlobalInvocationID.x;
|
uint command = gl_GlobalInvocationID.x;
|
||||||
@@ -29,11 +41,22 @@ void main()
|
|||||||
|
|
||||||
uint field = command * 8u;
|
uint field = command * 8u;
|
||||||
uint entry = commands[field + 5u];
|
uint entry = commands[field + 5u];
|
||||||
uint count = counters[INSTANCE_COUNTER_HEADER + entry];
|
uint first = counters[INSTANCE_COUNTER_HEADER + 2u * counts.y + entry];
|
||||||
uint base = counters[INSTANCE_COUNTER_HEADER + counts.y + entry] - count;
|
uint count = counters[INSTANCE_COUNTER_HEADER + counts.y + entry] - first;
|
||||||
commands[field + 1u] = count;
|
if (counts.z == 0u)
|
||||||
if (commands[field + 6u] != 0u)
|
{
|
||||||
commands[field + 4u] = base;
|
set_instances(field, count, first);
|
||||||
else
|
return;
|
||||||
commands[field + 3u] = base;
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
// a single work group: each invocation sums a run of the entries, then the sums are scanned in shared memory
|
||||||
|
|
||||||
#include <common>
|
#include <common>
|
||||||
@@ -46,6 +47,7 @@ void main()
|
|||||||
for (uint entry = begin; entry < end; ++entry)
|
for (uint entry = begin; entry < end; ++entry)
|
||||||
{
|
{
|
||||||
counters[INSTANCE_COUNTER_HEADER + entries + entry] = offset;
|
counters[INSTANCE_COUNTER_HEADER + entries + entry] = offset;
|
||||||
|
counters[INSTANCE_COUNTER_HEADER + 2u * entries + entry] = offset;
|
||||||
offset += counters[INSTANCE_COUNTER_HEADER + entry];
|
offset += counters[INSTANCE_COUNTER_HEADER + entry];
|
||||||
}
|
}
|
||||||
if (invocation == INSTANCE_OFFSETS_GROUP_SIZE - 1u)
|
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.getTokens(1);
|
||||||
Parser >> gfx_gpucull_occlusion;
|
Parser >> gfx_gpucull_occlusion;
|
||||||
}
|
}
|
||||||
|
else if (Token == "gfx.gpucull.occlusion.instances")
|
||||||
|
{
|
||||||
|
Parser.getTokens(1);
|
||||||
|
Parser >> gfx_gpucull_occlusion_instances;
|
||||||
|
}
|
||||||
else if (Token == "gfx.depthprepass")
|
else if (Token == "gfx.depthprepass")
|
||||||
{
|
{
|
||||||
Parser.getTokens(1);
|
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.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", 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", 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", gfx_depthprepass );
|
||||||
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
|
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
|
||||||
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
|
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
|
// 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;
|
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 = 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
|
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
|
// (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;
|
bool gfx_depthprepass_scene = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user