diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 4ca27a2b..21f72faf 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -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 diff --git a/gl/ubo.h b/gl/ubo.h index b2acfc88..6e950fb7 100644 --- a/gl/ubo.h +++ b/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; diff --git a/rendering/opengl46/opengl46hiz.cpp b/rendering/opengl46/opengl46hiz.cpp index 51a8c5c8..142c2286 100644 --- a/rendering/opengl46/opengl46hiz.cpp +++ b/rendering/opengl46/opengl46hiz.cpp @@ -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(*time) / 1000000.0, 3) + " ms\n"; + { + text += " occlusion depth: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms"; + auto const second{m_secondtimer.result()}; + if (Second && second) + text += " (+" + to_string(static_cast(*second) / 1000000.0, 3) + " ms with the instances drawn first)"; + text += "\n"; + } return text; } diff --git a/rendering/opengl46/opengl46hiz.h b/rendering/opengl46/opengl46hiz.h index 7e50e27a..c75d6b0b 100644 --- a/rendering/opengl46/opengl46hiz.h +++ b/rendering/opengl46/opengl46hiz.h @@ -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; }; diff --git a/rendering/opengl46/opengl46instancecull.cpp b/rendering/opengl46/opengl46instancecull.cpp index 2df29940..87033d31 100644 --- a/rendering/opengl46/opengl46instancecull.cpp +++ b/rendering/opengl46/opengl46instancecull.cpp @@ -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((gl::INSTANCE_COUNTER_HEADER + 2 * m_entries) * sizeof(std::uint32_t))}; + auto const countersize{static_cast((gl::INSTANCE_COUNTER_HEADER + 3 * m_entries) * sizeof(std::uint32_t))}; reserve(*m_jobs, static_cast(m_joblist.size() * sizeof(gl::instance_cull_job))); reserve(*m_groups, static_cast(m_grouplist.size() * sizeof(gl::instance_cull_group))); reserve(*m_lods, static_cast(std::max(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(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(groups, 65535)}; + auto const groups_y{(groups + groups_x - 1) / groups_x}; + + m_secondtimer.begin(); + glProgramUniform4ui(*m_cull_program, 9, static_cast(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 const &Commands) { m_commandcount = static_cast(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(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(*time) / 1000000.0, 3) + " ms\n"; + { + text += " instance cull: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms"; + auto const second{m_secondtimer.result()}; + if (m_twophase && second) + text += " (+" + to_string(static_cast(*second) / 1000000.0, 3) + " ms second phase)"; + text += "\n"; + } return text; } diff --git a/rendering/opengl46/opengl46instancecull.h b/rendering/opengl46/opengl46instancecull.h index 71e0e718..f51a7489 100644 --- a/rendering/opengl46/opengl46instancecull.h +++ b/rendering/opengl46/opengl46instancecull.h @@ -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 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 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((static_cast(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 m_commands; std::unique_ptr 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 }; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 32fd6437..8d65d6bb 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -749,9 +749,15 @@ void opengl46_renderer::SwapBuffers() m_debugtimestext += " contact shadows depth: " + to_string( static_cast( *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( *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( *prepasstime ) / 1000000.0, 3 ) + " ms"; + auto const firsttime { m_prepassfirsttimer.result() }; + if( twophase && firsttime ) + m_debugtimestext += " (+" + to_string( static_cast( *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( 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( 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( prepassdraws ); + colourdraws = static_cast( m_indirectdraws.size() ); } else { - m_renderpass.draw_stats.instanced_drawcalls += static_cast( m_indirectdraws.size() ); + m_renderpass.draw_stats.instanced_drawcalls += static_cast( colourdraws ); m_renderpass.draw_stats.drawcalls += static_cast( prepassdraws ); // the count of the frame the gpu reported last m_renderpass.draw_stats.instances += static_cast( m_instancecull.last_count() ); m_renderpass.draw_stats.models += static_cast( m_instancecull.last_count() ); } m_instancecull.draw_result( - static_cast( m_indirectdraws.size() ), + colourdraws, prepassdraws, std::chrono::duration( 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( 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( 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; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index c277ce3f..f6044244 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -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 m_prepass_alpha_shader; std::unordered_map 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; diff --git a/scene/scene.h b/scene/scene.h index edf5707f..a429dd37 100644 --- a/scene/scene.h +++ b/scene/scene.h @@ -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" ); diff --git a/shaders/instance_cull.comp b/shaders/instance_cull.comp index 6c925e79..12965344 100644 --- a/shaders/instance_cull.comp +++ b/shaders/instance_cull.comp @@ -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 @@ -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; } diff --git a/shaders/instance_cull_commands.comp b/shaders/instance_cull_commands.comp index 5c3204ad..627cf64a 100644 --- a/shaders/instance_cull_commands.comp +++ b/shaders/instance_cull_commands.comp @@ -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 @@ -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); } diff --git a/shaders/instance_cull_offsets.comp b/shaders/instance_cull_offsets.comp index a171dd19..c42094ef 100644 --- a/shaders/instance_cull_offsets.comp +++ b/shaders/instance_cull_offsets.comp @@ -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 @@ -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) diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index daab635b..ae3c1d73 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -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 ); diff --git a/utilities/Globals.h b/utilities/Globals.h index ca82b801..1a506ffd 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -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;