diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index b573e982..4f029cf5 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -264,8 +264,9 @@ are no longer loaded. Many models of the scenery (trees, bushes, poles, fences: models without lights and animations) are drawn in batches, one batch per model and level of detail. Which of them are drawn, and at which level of detail, can be worked out on the processor (as before) or on the graphics card. On the graphics card the processor no longer goes through the models one by -one, so with dense vegetation it is much less loaded. Only the main view is drawn this way; shadows, reflections and other -views go as before. +one, so with dense vegetation it is much less loaded. The main view, the three cascades of the sun shadows and the faces of +the environment map (reflections) are drawn this way; the shadow maps of lamps and headlights, the cab and other views go as +before. | Entry | Default | Meaning | |---|---|---| @@ -274,6 +275,8 @@ views go as before. | `gfx.gpucull.occlusion.instances yes/no` | `yes` | with `gfx.gpucull.occlusion yes`: the models drawn this way hide one another too (see below) | | `gfx.depthprepass yes/no` | `yes` | with `gfx.gpucull yes`: the depth of the models is drawn first, so the colour is worked out only for what stays in view (dense foliage) | | `gfx.gpucull.multidraw yes/no` | `yes` | with `gfx.gpucull yes`: parts of models with the same material (and texture variant) in the same model are drawn with one call (multi draw), see below | +| `gfx.gpucull.shadows yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the cascades of the sun shadows too (each cascade with its own camera and the distances from the real camera, as before; models none of whose materials cast shadows are left out at once) | +| `gfx.gpucull.reflections yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the faces of the environment map too (its own range of models, as before) | | `gfx.depthprepass.scene yes/no` | `no` | the depth of the whole opaque scene of the main view (terrain, tracks, buildings, models, vehicles, the cab) is drawn before its colour, see below | With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn @@ -287,6 +290,12 @@ is not worked out with the depth prepass on). Models whose material is not one o like) are left out of the depth prepass and drawn as before; with the depth prepass on, they don't hide the others either. All of these can be changed while the simulator runs, in the **Instance culling** debug window. +The shadows and the reflections take the same tests as before (which models are in the cascade or the face, their distance +and level of detail), without the occlusion test, the depth prepass and the two phases, which belong to the main view. The +copies of the models are sent to the graphics card once and serve all of these views. In the shadows the parts with the +same texture for the alpha test (or with none) in the same model go in one call with `gfx.gpucull.multidraw yes`, whatever +their material. + With `gfx.gpucull.multidraw yes` the draws of the models drawn from the GPU are put in an order in which the parts with the same material, texture variant and model (the model keeps its geometry in a buffer of its own) follow one another, and each such run goes in one call (`glMultiDrawElementsIndirect`); the data of every part (its position in the model, colours, @@ -378,7 +387,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug made them — fewer than the draws with `gfx.gpucull.multidraw yes` — and the processor time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and `depth prepass:`; in two phases each of them adds in brackets the time of the second phase, of the second pyramid and of the depth of the models drawn first. With `compare` it shows how many models both counted and how many levels of detail - differ. + differ. With `gfx.gpucull.shadows` and `gfx.gpucull.reflections` the same rows (without the occlusion test and the + prepass) follow for each cascade, `gpu cull shadows 1:` to `3:` (1 the nearest), and for the environment map, + `gpu cull reflections, last face:` (the last face of the map drawn, as the map is drawn now and then), with the timings + `instance cull shadows 1:` … and `instance cull reflections, last face:`. - With `gfx.depthprepass.scene yes` Shift+F12 shows the `scene depth:` row (draws of the depth pass and its processor time) and the timing `scene depth:` (its GPU time). - With `gfx.contactshadows` on Shift+F12 shows the `contact shadows:` row (the level, the length and reach of the rays, or diff --git a/rendering/opengl46/opengl46instancecull.cpp b/rendering/opengl46/opengl46instancecull.cpp index 8edb106a..02172c3f 100644 --- a/rendering/opengl46/opengl46instancecull.cpp +++ b/rendering/opengl46/opengl46instancecull.cpp @@ -31,18 +31,26 @@ void reserve(gl::ssbo &Buffer, GLsizeiptr const Size) } // namespace -void opengl46_instance_cull::init() +void opengl46_instance_cull::init(opengl46_instance_cull const *Shared) { - auto const make_compute = [](std::string const &Name) { - gl::shader shader(Name); - return std::make_unique(std::vector>{shader}); - }; - m_cull_program = make_compute("instance_cull.comp"); - m_offsets_program = make_compute("instance_cull_offsets.comp"); - m_commands_program = make_compute("instance_cull_commands.comp"); + if (Shared != nullptr) + { + m_shared = Shared->m_shared; + } + else + { + auto const make_compute = [](std::string const &Name) { + gl::shader shader(Name); + return std::make_unique(std::vector>{shader}); + }; + m_shared = std::make_shared(); + m_shared->cull_program = make_compute("instance_cull.comp"); + m_shared->offsets_program = make_compute("instance_cull_offsets.comp"); + m_shared->commands_program = make_compute("instance_cull_commands.comp"); + m_shared->records = std::make_unique(65536 * RECORD_SIZE, gl::INSTANCE_RECORD_SSBO_BINDING); + } auto const readbacksize{static_cast((gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES) * sizeof(std::uint32_t))}; - m_records = std::make_unique(65536 * RECORD_SIZE, gl::INSTANCE_RECORD_SSBO_BINDING); m_jobs = std::make_unique(4096 * sizeof(gl::instance_cull_job), gl::INSTANCE_JOB_SSBO_BINDING); m_groups = std::make_unique(1024 * sizeof(gl::instance_cull_group), gl::INSTANCE_GROUP_SSBO_BINDING); m_lods = std::make_unique(4096 * sizeof(float), gl::INSTANCE_LOD_SSBO_BINDING); @@ -59,6 +67,7 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup) collect(); m_inpass = true; + m_ran = true; m_dispatched = false; m_setup = Setup; m_buckets.clear(); @@ -167,40 +176,46 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket) { + auto &shared{*m_shared}; auto const count{static_cast(Bucket.records.size())}; - auto const current{Bucket.gpugeneration == m_recordgeneration}; + auto const current{Bucket.gpugeneration == shared.recordgeneration}; if (current && !Bucket.gpustale) return; if (current && count <= Bucket.gpucapacity) { - m_records->update(Bucket.records.data(), static_cast(Bucket.gpufirst) * RECORD_SIZE, static_cast(count) * RECORD_SIZE); + shared.records->update(Bucket.records.data(), static_cast(Bucket.gpufirst) * RECORD_SIZE, static_cast(count) * RECORD_SIZE); Bucket.gpustale = false; return; } // a new place, with some room for instances the editor adds - auto const allocate = [this](scene::basic_cell::instance_bucket const &Target) { + auto const allocate = [&shared](scene::basic_cell::instance_bucket const &Target) { auto const records{static_cast(Target.records.size())}; - Target.gpufirst = m_recordfill; + Target.gpufirst = shared.recordfill; Target.gpucapacity = records + records / 4; - Target.gpugeneration = m_recordgeneration; + Target.gpugeneration = shared.recordgeneration; Target.gpustale = false; - m_recordfill += Target.gpucapacity; - m_records->update(Target.records.data(), static_cast(Target.gpufirst) * RECORD_SIZE, static_cast(records) * RECORD_SIZE); + shared.recordfill += Target.gpucapacity; + shared.records->update(Target.records.data(), static_cast(Target.gpufirst) * RECORD_SIZE, static_cast(records) * RECORD_SIZE); }; - auto const capacity{static_cast(m_records->size() / RECORD_SIZE)}; - if (m_recordfill + count + count / 4 > capacity) + auto const capacity{static_cast(shared.records->size() / RECORD_SIZE)}; + if (shared.recordfill + count + count / 4 > capacity) { // the buffer is started over, and only the buckets of this pass get places again. the places left behind by - // buckets which were made anew or are out of sight are dropped this way + // buckets which were made anew or are out of sight are dropped this way. the passes of a frame queue different + // buckets: when those of all of them don't fit in the buffer together, some pass would start it over in every frame, + // so a buffer started over again in the same or the next frame grows GLsizeiptr live{count + count / 4}; for (auto const *bucket : m_buckets) live += bucket->records.size() + bucket->records.size() / 4; - if (2 * live * RECORD_SIZE > m_records->size()) - m_records->resize(std::max(2 * live * RECORD_SIZE, 2 * m_records->size())); - ++m_recordgeneration; - m_recordfill = 0; + auto const again{shared.restarted && (m_setup.frame - shared.restartframe <= 1)}; + if ((2 * live * RECORD_SIZE > shared.records->size()) || again) + shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size())); + shared.restarted = true; + shared.restartframe = m_setup.frame; + ++shared.recordgeneration; + shared.recordfill = 0; for (std::size_t job = 0; job < m_buckets.size(); ++job) { allocate(*m_buckets[job]); @@ -244,26 +259,26 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con planes[plane] = glm::vec4{source[0], source[1], source[2], static_cast(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])}; } - glProgramUniformMatrix4fv(*m_cull_program, 0, 1, GL_FALSE, glm::value_ptr(View)); - glProgramUniform4fv(*m_cull_program, 1, 6, glm::value_ptr(planes[0])); - glProgramUniform4f(*m_cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange); - glProgramUniform4f(*m_cull_program, 8, m_setup.zoom, 0.f, 0.f, 0.f); - glProgramUniform4ui(*m_offsets_program, 0, m_entries, 0, 0, 0); + glProgramUniformMatrix4fv(*m_shared->cull_program, 0, 1, GL_FALSE, glm::value_ptr(View)); + glProgramUniform4fv(*m_shared->cull_program, 1, 6, glm::value_ptr(planes[0])); + glProgramUniform4f(*m_shared->cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange); + glProgramUniform4f(*m_shared->cull_program, 8, m_setup.zoom, 0.f, 0.f, 0.f); + glProgramUniform4ui(*m_shared->offsets_program, 0, m_entries, 0, 0, 0); // the occlusion test only when the instances are drawn from the result, the cpu doesn't make it m_occlusion = (m_setup.drawing && (Occlusion != nullptr) && (Occlusion->pyramid != 0)); if (m_occlusion) { - glProgramUniformMatrix4fv(*m_cull_program, 10, 1, GL_FALSE, glm::value_ptr(Occlusion->projection)); - glProgramUniform4i(*m_cull_program, 14, 1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y); - glProgramUniform4f(*m_cull_program, 15, Occlusion->screen.x, Occlusion->screen.y, 0.f, 0.f); + glProgramUniformMatrix4fv(*m_shared->cull_program, 10, 1, GL_FALSE, glm::value_ptr(Occlusion->projection)); + glProgramUniform4i(*m_shared->cull_program, 14, 1, Occlusion->levels, Occlusion->size.x, Occlusion->size.y); + glProgramUniform4f(*m_shared->cull_program, 15, Occlusion->screen.x, Occlusion->screen.y, 0.f, 0.f); glBindTextureUnit(0, Occlusion->pyramid); } else { - glProgramUniform4i(*m_cull_program, 14, 0, 0, 0, 0); + glProgramUniform4i(*m_shared->cull_program, 14, 0, 0, 0, 0); } m_twophase = (m_occlusion && Twophase); - glProgramUniform4ui(*m_cull_program, 16, (m_twophase ? 1u : 0u), 0, 0, 0); + glProgramUniform4ui(*m_shared->cull_program, 16, (m_twophase ? 1u : 0u), 0, 0, 0); // one invocation per record; past the count limit of one dimension the groups go into rows auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE}; @@ -271,13 +286,13 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con auto const groups_y{(groups + groups_x - 1) / groups_x}; m_timer.begin(); - glProgramUniform4ui(*m_cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 0); - m_cull_program->dispatch(groups_x, groups_y); + glProgramUniform4ui(*m_shared->cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 0); + m_shared->cull_program->dispatch(groups_x, groups_y); gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT); - m_offsets_program->dispatch(1); + m_shared->offsets_program->dispatch(1); gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT); - glProgramUniform4ui(*m_cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 1); - m_cull_program->dispatch(groups_x, groups_y); + glProgramUniform4ui(*m_shared->cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 1); + m_shared->cull_program->dispatch(groups_x, groups_y); // the output and the counts go to draws, indirect draws and the copy for the cpu gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT); m_timer.end(); @@ -293,7 +308,7 @@ void opengl46_instance_cull::dispatch(glm::mat4 const &View, occlusion_setup con void opengl46_instance_cull::bind_buffers() { - m_records->bind_base(); + m_shared->records->bind_base(); m_jobs->bind_base(); m_groups->bind_base(); m_lods->bind_base(); @@ -308,8 +323,8 @@ void opengl46_instance_cull::dispatch_second(occlusion_setup const &Occlusion) // the draws of the first phase bound buffers of their own, and other passes share the binding of the records bind_buffers(); - glProgramUniform4i(*m_cull_program, 14, 1, Occlusion.levels, Occlusion.size.x, Occlusion.size.y); - glProgramUniform4f(*m_cull_program, 15, Occlusion.screen.x, Occlusion.screen.y, 0.f, 0.f); + glProgramUniform4i(*m_shared->cull_program, 14, 1, Occlusion.levels, Occlusion.size.x, Occlusion.size.y); + glProgramUniform4f(*m_shared->cull_program, 15, Occlusion.screen.x, Occlusion.screen.y, 0.f, 0.f); glBindTextureUnit(0, Occlusion.pyramid); auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE}; @@ -317,14 +332,14 @@ void opengl46_instance_cull::dispatch_second(occlusion_setup const &Occlusion) auto const groups_y{(groups + groups_x - 1) / groups_x}; m_secondtimer.begin(); - glProgramUniform4ui(*m_cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 2); - m_cull_program->dispatch(groups_x, groups_y); + glProgramUniform4ui(*m_shared->cull_program, 9, static_cast(m_joblist.size()), m_candidates, m_entries, 2); + m_shared->cull_program->dispatch(groups_x, groups_y); gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT); if (m_commandcount > 0) { m_commands->bind_base(); - glProgramUniform4ui(*m_commands_program, 0, m_commandcount, m_entries, 1, 0); - m_commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE); + glProgramUniform4ui(*m_shared->commands_program, 0, m_commandcount, m_entries, 1, 0); + m_shared->commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE); } // the commands go to indirect draws, the matrices to the vertex shaders, the counts to the copy for the cpu gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT); @@ -346,8 +361,8 @@ void opengl46_instance_cull::fill_commands(std::vectorupdate(Commands.data(), 0, size); m_counters->bind_base(); m_commands->bind_base(); - glProgramUniform4ui(*m_commands_program, 0, m_commandcount, m_entries, 0, 0); - m_commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE); + glProgramUniform4ui(*m_shared->commands_program, 0, m_commandcount, m_entries, 0, 0); + m_shared->commands_program->dispatch((m_commandcount + gl::INSTANCE_COMMAND_GROUP_SIZE - 1) / gl::INSTANCE_COMMAND_GROUP_SIZE); // the commands go to indirect draws, the matrices to the vertex shaders gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT); } @@ -443,35 +458,38 @@ void opengl46_instance_cull::collect() ++m_mismatchedframes; } -std::string opengl46_instance_cull::info() +std::string opengl46_instance_cull::info(std::string const &Label, bool const Occlusion) { if (Global.gfx_gpucull == 0) return {}; if (Global.gfx_gpucull == 1) { if (m_last.frame == 0) - return "\ngpu cull: no result yet"; - return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances drawn" + + return "\n" + Label + ": no result yet"; + if (false == Occlusion) + return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) + + " indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms"; + return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" + (m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) + " hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " + std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms"; } if (m_comparedframes == 0) - return "\ngpu cull: no result yet"; - return "\ngpu cull: " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " + + return "\n" + Label + ": no result yet"; + return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " + std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " + std::to_string(m_comparedframes); } -std::string opengl46_instance_cull::info_times() +std::string opengl46_instance_cull::info_times(std::string const &Label) { std::string text; if (Global.gfx_gpucull == 0) return text; if (auto const time = m_timer.result()) { - text += " instance cull: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms"; + text += " " + Label + ": " + 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)"; diff --git a/rendering/opengl46/opengl46instancecull.h b/rendering/opengl46/opengl46instancecull.h index 95e6fdf9..3d8716fe 100644 --- a/rendering/opengl46/opengl46instancecull.h +++ b/rendering/opengl46/opengl46instancecull.h @@ -43,6 +43,9 @@ class TAnimModel; // of them, and the ones drawn second, each copy command_count() commands long (draw_command_offset()) // with Global.gfx_gpucull 2 the cpu still draws, and the result is only compared with its own: the counts per entry are // read back a few frames later and set against the counts Render_Instanced() reported for the same frame +// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map) has a +// culling of its own, with its own buffers, while the records and the programs are shared by all of them: the records of a +// bucket go to the gpu once and serve every pass which queues the bucket class opengl46_instance_cull { public: @@ -85,8 +88,8 @@ class opengl46_instance_cull opengl46_instance_cull(const opengl46_instance_cull &) = delete; opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete; - // creates the programs and buffers. throws gl::shader_exception - void init(); + // creates the buffers, and the programs and the record buffer, or takes the ones of Shared. throws gl::shader_exception + void init(opengl46_instance_cull const *Shared = nullptr); // starts collecting the buckets of a pass void begin_pass(pass_setup const &Setup); bool in_pass() const @@ -165,9 +168,17 @@ class opengl46_instance_cull void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector const &Bandcounts); // ends the pass: its cpu counts are kept for the comparison void end_pass(); - // lines for the debug panel - std::string info(); - std::string info_times(); + // takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins; + // for the panel also by the culls of passes which aren't made every frame + void collect(); + // a pass was culled since the start + bool ran() const + { + return m_ran; + } + // lines for the debug panel, Label naming the pass. Occlusion: the pass may test occlusion (the main view) + std::string info(std::string const &Label = "gpu cull", bool const Occlusion = true); + std::string info_times(std::string const &Label = "instance cull"); private: struct group_data @@ -198,20 +209,30 @@ class opengl46_instance_cull std::uint32_t first{0}; // drawn first, seen in the previous frame (two phases) }; + // what the culls of all passes share + struct shared_data + { + std::unique_ptr cull_program; + std::unique_ptr offsets_program; + std::unique_ptr commands_program; + std::unique_ptr records; + // record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer + std::uint32_t recordfill{0}; + unsigned int recordgeneration{1}; + // frame of the last time the buffer was started over, and whether it was + unsigned int restartframe{0}; + bool restarted{false}; + }; + // finds a place for the records of a bucket in the record buffer and sends them void place(scene::basic_cell::instance_bucket const &Bucket); // binds the buffers of the culling passes void bind_buffers(); - // takes the newest counts of the gpu which arrived and compares them with the ones of the cpu - void collect(); // entries read back for the comparison; with more of them only the totals and these are compared static constexpr std::uint32_t READBACK_ENTRIES{16384}; - std::unique_ptr m_cull_program; - std::unique_ptr m_offsets_program; - std::unique_ptr m_commands_program; - std::unique_ptr m_records; + std::shared_ptr m_shared; std::unique_ptr m_jobs; std::unique_ptr m_groups; std::unique_ptr m_lods; @@ -222,11 +243,8 @@ class opengl46_instance_cull gl::gpu_timer m_timer; gl::gpu_timer m_secondtimer; - // record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer - std::uint32_t m_recordfill{0}; - unsigned int m_recordgeneration{1}; - bool m_inpass{false}; + bool m_ran{false}; pass_setup m_setup; std::vector m_buckets; // of the jobs, in their order std::vector m_joblist; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index a67c7794..6bf212cf 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -207,6 +207,11 @@ bool opengl46_renderer::Init(GLFWwindow *Window) gl::shader prepassalphafragment("depthprepass_alpha.frag", defines); m_prepass_multidraw_shader = std::make_unique(std::vector>{prepassvertex, prepassfragment}); m_prepass_alpha_multidraw_shader = std::make_unique(std::vector>{prepassvertex, prepassalphafragment}); + gl::shader shadowvertex("simpleuv.vert", defines); + gl::shader shadowfragment("shadowmap.frag", defines); + gl::shader alphashadowfragment("alphashadowmap.frag", defines); + m_shadow_multidraw_shader = std::make_unique(std::vector>{shadowvertex, shadowfragment}); + m_alpha_shadow_multidraw_shader = std::make_unique(std::vector>{shadowvertex, alphashadowfragment}); m_drawblockstream = std::make_unique( static_cast(4096 * sizeof(gl::model_ubs)), static_cast(sizeof(gl::model_ubs)), Global.gfx_bufferstreaming); } @@ -216,6 +221,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window) m_vertex_multidraw_shader.reset(); m_prepass_multidraw_shader.reset(); m_prepass_alpha_multidraw_shader.reset(); + m_shadow_multidraw_shader.reset(); + m_alpha_shadow_multidraw_shader.reset(); m_drawblockstream.reset(); } @@ -224,6 +231,9 @@ bool opengl46_renderer::Init(GLFWwindow *Window) m_sky.init(); m_weather.init(); m_instancecull.init(); + for (auto &shadowcull : m_shadowcull) + shadowcull.init(&m_instancecull); + m_reflectioncull.init(&m_instancecull); m_hiz.init(); m_exposure.init(); m_ao.init(); @@ -769,6 +779,15 @@ void opengl46_renderer::SwapBuffers() m_debugtimestext += m_exposure.info_times(); m_debugtimestext += m_ao.info_times(); m_debugtimestext += m_instancecull.info_times(); + // the cullings of the other passes; the environment map is drawn now and then, its numbers are of its last update + if( Global.gfx_gpucull_shadows ) { + for( std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade ) { + m_debugtimestext += m_shadowcull[ cascade ].info_times( "instance cull shadows " + std::to_string( cascade + 1 ) ); + } + } + if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) { + m_debugtimestext += m_reflectioncull.info_times( "instance cull reflections, last face" ); + } if( Global.gfx_depthprepass_scene || m_contactshadowslast ) { if( auto const scenedepthtime = m_scenedepthtimer.result() ) m_debugtimestext += " scene depth: " + to_string( static_cast( *scenedepthtime ) / 1000000.0, 3 ) + " ms\n"; @@ -853,6 +872,17 @@ void opengl46_renderer::SwapBuffers() + "\n" + m_locallights.info() + m_localshadows.info() + m_instancecull.info(); + if( Global.gfx_gpucull_shadows ) { + for( std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade ) { + if( false == m_shadowcull[ cascade ].ran() ) { continue; } + m_shadowcull[ cascade ].collect(); + m_debugstatstext += m_shadowcull[ cascade ].info( "gpu cull shadows " + std::to_string( cascade + 1 ), false ); + } + } + if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) { + m_reflectioncull.collect(); + m_debugstatstext += m_reflectioncull.info( "gpu cull reflections, last face", false ); + } if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) { m_debugstatstext += "\ncluster lists: " + std::to_string( cullstats->index_count ) @@ -953,10 +983,25 @@ void opengl46_renderer::draw_debug_ui() else ImGui::TextUnformatted("(multi draws not available, see the log)"); } + if (Global.gfx_gpucull != 0) + { + ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows); + ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections); + } ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene); if (Global.gfx_contactshadows > 0) ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)"); - auto const info{m_instancecull.info()}; + auto info{m_instancecull.info()}; + if (Global.gfx_gpucull_shadows) + { + for (std::size_t cascade = 0; cascade < m_shadowcull.size(); ++cascade) + { + if (m_shadowcull[cascade].ran()) + info += m_shadowcull[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false); + } + } + if (Global.gfx_gpucull_reflections && m_reflectioncull.ran()) + info += m_reflectioncull.info("gpu cull reflections, last face", false); if (!info.empty()) ImGui::TextUnformatted(info.c_str() + 1); } @@ -1461,7 +1506,9 @@ if (!Global.gfx_skippipeline) scene_ubo->update( scene_ubs ); if( m_shadowcolor != colors::white ) { + m_shadowcascade = idx; Render( simulation::Region ); + m_shadowcascade = -1; if( idx > 0 ) { continue; } // render cab only in the closest csm stage if( !FreeFlyModeFlag && Global.render_cab ) { Render_cab( simulation::Train->Dynamic(), 0.0f, false ); @@ -2704,6 +2751,14 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel void opengl46_renderer::Bind_Material_Shadow(material_handle const Material) { + if (m_indirectrecording) + { + // the walk of Render_Instances_Indirect_Pass() only notes the material, the program and the texture are bound when the + // draw is made (Render_Instances_Indirect_Shadow()) + m_indirectmaterial = Material; + m_indirectsubmodel = nullptr; + return; + } if (Material != null_handle) { auto &material = m_materials.material(Material); @@ -2802,15 +2857,35 @@ void opengl46_renderer::Render(scene::basic_region *Region) for( auto &bucket : m_frame_instance_buckets ) { bucket.second.clear(); } - // the gpu culling runs along the first color pass of the main viewport of a frame. its result is drawn (Global.gfx_gpucull 1), - // or compared with the one of the cpu, which draws (2) + // the gpu culling runs along the first color pass of the main viewport of a frame, and the passes of the cascades of the + // sun shadows and of the faces of the environment map drawn for it, each with a culling of its own. its result is drawn + // (Global.gfx_gpucull 1), or compared with the one of the cpu, which draws (2) m_instancecullpass = false; + m_passcull = nullptr; if( ( Global.gfx_gpucull != 0 ) - && ( m_renderpass.draw_mode == rendermode::color ) && ( m_current_viewport != nullptr ) - && ( m_current_viewport->main ) - && ( m_instancecullframe != m_framestamp ) ) { - m_instancecullframe = m_framestamp; + && ( m_current_viewport->main ) ) { + if( ( m_renderpass.draw_mode == rendermode::color ) + && ( m_instancecullframe != m_framestamp ) ) { + m_instancecullframe = m_framestamp; + m_passcull = &m_instancecull; + } + else if( ( m_renderpass.draw_mode == rendermode::shadows ) + && ( Global.gfx_gpucull_shadows ) + && ( m_shadowcascade >= 0 ) + && ( m_shadowcascade < static_cast( m_shadowcull.size() ) ) + && ( false == m_cubeshadows ) ) { + // the cascades of the sun; the maps of lamps and headlights go as before + m_passcull = &m_shadowcull[ m_shadowcascade ]; + m_shadowcasters.clear(); + } + else if( ( m_renderpass.draw_mode == rendermode::reflections ) + && ( Global.gfx_gpucull_reflections ) + && ( Global.reflectiontune.fidelity >= 1 ) ) { + m_passcull = &m_reflectioncull; + } + } + if( m_passcull != nullptr ) { m_instancecullpass = true; // the same choices as in Render_Instanced() auto const reflections { m_renderpass.draw_mode == rendermode::reflections }; @@ -2825,7 +2900,7 @@ void opengl46_renderer::Render(scene::basic_region *Region) setup.drawrange = static_cast( sq( m_renderpass.draw_range + 250 ) ); setup.zoom = Global.ZoomFactor; setup.drawing = ( Global.gfx_gpucull == 1 ); - m_instancecull.begin_pass( setup ); + m_passcull->begin_pass( setup ); } // build a list of region sections to render glm::vec3 const cameraposition{m_renderpass.pass_camera.position()}; @@ -3275,6 +3350,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite indirect_counts colourcounts; if( false == m_indirectdraws.empty() ) { auto const cpustart { std::chrono::steady_clock::now() }; + m_indirectcull = &m_instancecull; m_instancecull.bind_draw(); colourcounts = Render_Instances_Indirect_Colour( m_indirectcolourset ); ::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 ); @@ -3291,26 +3367,32 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite m_renderpass.draw_stats.models += static_cast( m_instancecull.last_count() ); } if( m_instancecullpass ) { - if( m_instancecull.drawing() ) { + if( m_passcull->drawing() ) { // the buckets of the pass weren't queued for the cpu, which leaves the loop below empty - Render_Instances_Indirect(); + if( m_passcull == &m_instancecull ) { + Render_Instances_Indirect(); + } + else { + Render_Instances_Indirect_Pass(); + } } else { // the view of the pass is in place here, the one Render_Instanced() takes - m_instancecull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ) ); + m_passcull->dispatch( OpenGLMatrices.data( GL_MODELVIEW ) ); } } for( auto &bucket : m_frame_instance_buckets ) { if( bucket.second.empty() ) { continue; } Render_Instanced( bucket.first.pModel, bucket.second, ( m_instancecullpass ? &m_instance_bandcounts : nullptr ) ); if( m_instancecullpass ) { - m_instancecull.cpu_result( bucket.first, m_instance_bandcounts ); + m_passcull->cpu_result( bucket.first, m_instance_bandcounts ); } bucket.second.clear(); } if( m_instancecullpass ) { - m_instancecull.end_pass(); + m_passcull->end_pass(); m_instancecullpass = false; + m_passcull = nullptr; } } @@ -3562,10 +3644,13 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell ) Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center ); } if( m_instancecullpass ) { - // the gpu tests every instance of the bucket, the way Render_Instanced() does - m_instancecull.add_bucket( bucket.first, instancebucket, Cell->m_area ); + // the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the + // sun shadows are left out of them, as Render_Instanced() leaves them + if( ( m_renderpass.draw_mode != rendermode::shadows ) || Instances_Cast_Shadow( bucket.first, instancebucket ) ) { + m_passcull->add_bucket( bucket.first, instancebucket, Cell->m_area ); + } // and when the instances are drawn from its result, the cpu has nothing more to do with them - if( m_instancecull.drawing() ) { continue; } + if( m_passcull->drawing() ) { continue; } } // the depth pass of the whole scene culled them already, the colour pass draws what it recorded if( m_indirectcolourpending ) { continue; } @@ -3915,43 +4000,8 @@ void opengl46_renderer::Render_Instances_Indirect() ( Global.gfx_gpucull_occlusion_instances && ( false == Global.bWireFrame ) ) ); // the draws, recorded while the gpu culls - m_indirectdraws.clear(); - m_indirectcommands.clear(); - auto const &lodbounds { m_instancecull.lod_bounds() }; - m_indirectrecording = true; - for( auto const &group : m_instancecull.draw_groups() ) { - auto *model { group.key->pModel }; - if( ( model == nullptr ) || ( model->Root == nullptr ) || ( group.instance == nullptr ) ) { continue; } - group.instance->RaPrepare(); - auto const *material { group.instance->Material() }; - auto alpha { material != nullptr ? material->textures_alpha : 0x30300030 }; - alpha ^= 0x0F0F000F; - for( std::uint32_t band = 0; band <= group.lodcount; ++band ) { - // a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod - auto const bandmin { band == 0 ? 0.f : lodbounds[ group.lodfirst + band - 1 ] }; - auto const bandmax { band == group.lodcount ? std::numeric_limits::max() : lodbounds[ group.lodfirst + band ] }; - if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; } - m_indirectentry = group.entryfirst + band; - ::glPushMatrix(); - ::glLoadIdentity(); - model->Root->fSquareDist = bandmin; - model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha ); - model->Root->pRoot = model; - Render( model->Root ); - ::glPopMatrix(); - } - } - m_indirectrecording = false; + Record_Indirect_Draws( m_instancecull ); - // with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs - // of the multi draws read them from. the order goes into the commands, so it comes first - m_indirectmultidraw = ( Global.gfx_gpucull_multidraw && ( m_drawblockstream != nullptr ) ); - if( m_indirectmultidraw && ( false == m_indirectdraws.empty() ) ) { - Order_Indirect_Draws(); - Upload_Indirect_Blocks(); - } - - m_instancecull.fill_commands( m_indirectcommands ); using command_set = opengl46_instance_cull::command_set; auto const twophase { m_instancecull.two_phase() }; auto const draws { false == m_indirectdraws.empty() }; @@ -4013,6 +4063,76 @@ void opengl46_renderer::Render_Instances_Indirect() std::chrono::duration( std::chrono::steady_clock::now() - cpustart ).count() ); } +// draws the instances of a pass other than the main view from the result of its gpu culling (m_passcull): the culling of the +// pass with its own camera, no occlusion test, the draws recorded the same way, then drawn once with the programs the pass +// draws with on the cpu +void opengl46_renderer::Render_Instances_Indirect_Pass() +{ + auto const cpustart { std::chrono::steady_clock::now() }; + auto &cull { *m_passcull }; + cull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ) ); + Record_Indirect_Draws( cull ); + + using command_set = opengl46_instance_cull::command_set; + indirect_counts counts; + if( false == m_indirectdraws.empty() ) { + cull.bind_draw(); + counts = ( + m_renderpass.draw_mode == rendermode::shadows ? + Render_Instances_Indirect_Shadow( command_set::first ) : + Render_Instances_Indirect_Colour( command_set::first ) ); + ::glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0 ); + Bind_Instance_Stream(); + } + m_renderpass.draw_stats.instanced_drawcalls += static_cast( counts.calls ); + // the count of the frame the gpu reported last + m_renderpass.draw_stats.instances += static_cast( cull.last_count() ); + m_renderpass.draw_stats.models += static_cast( cull.last_count() ); + cull.draw_result( counts.draws, 0, counts.calls, 0, std::chrono::duration( std::chrono::steady_clock::now() - cpustart ).count() ); +} + +void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) +{ + m_indirectcull = &Cull; + m_indirectdraws.clear(); + m_indirectcommands.clear(); + auto const &lodbounds { Cull.lod_bounds() }; + m_indirectrecording = true; + for( auto const &group : Cull.draw_groups() ) { + auto *model { group.key->pModel }; + if( ( model == nullptr ) || ( model->Root == nullptr ) || ( group.instance == nullptr ) ) { continue; } + group.instance->RaPrepare(); + auto const *material { group.instance->Material() }; + auto alpha { material != nullptr ? material->textures_alpha : 0x30300030 }; + alpha ^= 0x0F0F000F; + for( std::uint32_t band = 0; band <= group.lodcount; ++band ) { + // a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod + auto const bandmin { band == 0 ? 0.f : lodbounds[ group.lodfirst + band - 1 ] }; + auto const bandmax { band == group.lodcount ? std::numeric_limits::max() : lodbounds[ group.lodfirst + band ] }; + if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; } + m_indirectentry = group.entryfirst + band; + ::glPushMatrix(); + ::glLoadIdentity(); + model->Root->fSquareDist = bandmin; + model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha ); + model->Root->pRoot = model; + Render( model->Root ); + ::glPopMatrix(); + } + } + m_indirectrecording = false; + + // with multi draws the draws with the same state follow one another, and their model blocks are in a buffer the programs + // of the multi draws read them from. the order goes into the commands, so it comes first + m_indirectmultidraw = ( Global.gfx_gpucull_multidraw && ( m_drawblockstream != nullptr ) ); + if( m_indirectmultidraw && ( false == m_indirectdraws.empty() ) ) { + Order_Indirect_Draws(); + Upload_Indirect_Blocks(); + } + + Cull.fill_commands( m_indirectcommands ); +} + opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer ) { if( m_scenedepthpass ) { @@ -4091,7 +4211,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ } auto const drawcount { static_cast( last - index ) }; Bind_Indirect_Blocks( index, drawcount ); - m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) ); + m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) ); counts.draws += drawcount; ++counts.calls; lastdrawn = last - 1; @@ -4104,7 +4224,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ } model_ubs = draw.ubs; model_ubo->update( model_ubs ); - m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) ); + m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) ); ++counts.draws; ++counts.calls; ++index; @@ -4137,7 +4257,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ m_bindmultidraw = false; auto const drawcount { static_cast( last - index ) }; Bind_Indirect_Blocks( index, drawcount ); - m_geometry.draw_indirect_multi( draw.geometry, m_instancecull.draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) ); + m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) ); counts.draws += drawcount; index = last; } @@ -4145,7 +4265,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ Bind_Material( draw.material, draw.submodel ); model_ubs = draw.ubs; model_ubo->update( model_ubs ); - m_geometry.draw_indirect( draw.geometry, m_instancecull.draw_command_offset( Set, index ) ); + m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) ); ++counts.draws; ++index; } @@ -4161,6 +4281,93 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ return counts; } +opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set ) +{ + indirect_counts counts; + auto const count { m_indirectdraws.size() }; + for( std::size_t index = 0; index < count; ) { + auto const &draw { m_indirectdraws[ index ] }; + auto const texture { Indirect_Shadow_Texture( draw.material ) }; + auto *multidrawprogram { + m_indirectmultidraw ? + ( texture != null_handle ? m_alpha_shadow_multidraw_shader.get() : m_shadow_multidraw_shader.get() ) : + nullptr }; + if( multidrawprogram != nullptr ) { + // the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test + auto last { index + 1 }; + while( ( last < count ) + && ( m_indirectdraws[ last ].geometry.bank == draw.geometry.bank ) + && ( m_indirectdraws[ last ].type == draw.type ) + && ( m_indirectcommands[ last ].indexed == m_indirectcommands[ index ].indexed ) + && ( Indirect_Shadow_Texture( m_indirectdraws[ last ].material ) == texture ) ) { + ++last; + } + if( texture != null_handle ) { + m_textures.bind( 0, texture ); + } + multidrawprogram->bind(); + auto const drawcount { static_cast( last - index ) }; + Bind_Indirect_Blocks( index, drawcount ); + m_geometry.draw_indirect_multi( draw.geometry, m_indirectcull->draw_command_offset( Set, index ), drawcount, sizeof( gl::instance_draw_command ) ); + counts.draws += drawcount; + index = last; + } + else { + Bind_Material_Shadow( draw.material ); + model_ubs = draw.ubs; + model_ubo->update( model_ubs ); + m_geometry.draw_indirect( draw.geometry, m_indirectcull->draw_command_offset( Set, index ) ); + ++counts.draws; + ++index; + } + ++counts.calls; + } + if( m_indirectmultidraw && ( count > 0 ) ) { + // the model block the draws made one by one leave behind + model_ubs = m_indirectdraws.back().ubs; + model_ubo->update( model_ubs ); + } + return counts; +} + +texture_handle opengl46_renderer::Indirect_Shadow_Texture( material_handle const Material ) +{ + if( Material == null_handle ) { return null_handle; } + return m_materials.material( Material ).textures[ 0 ]; +} + +bool opengl46_renderer::Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket ) +{ + auto const lookup { m_shadowcasters.find( Key ) }; + if( lookup != m_shadowcasters.end() ) { return lookup->second; } + + // the skins of the instance Render_Instanced() would take them from + TAnimModel const *instance { nullptr }; + for( auto const *candidate : Bucket.instances ) { + if( candidate != nullptr ) { + instance = candidate; + break; + } + } + auto const *skins { instance != nullptr && instance->Material() != nullptr ? instance->Material()->replacable_skins : nullptr }; + auto castsshadow { false }; + m_instance_lodpending.clear(); + m_instance_lodpending.push_back( Key.pModel != nullptr ? Key.pModel->Root : nullptr ); + while( ( false == castsshadow ) && ( false == m_instance_lodpending.empty() ) ) { + auto const *submodel = m_instance_lodpending.back(); + m_instance_lodpending.pop_back(); + if( submodel == nullptr ) { continue; } + if( submodel->eType < TP_ROTATOR ) { + auto const material { submodel->m_material >= 0 ? submodel->m_material : ( skins != nullptr ? skins[ -submodel->m_material ] : null_handle ) }; + castsshadow = ( Material( material ).shadow_rank <= Global.gfx_shadow_rank_cutoff ); + } + m_instance_lodpending.push_back( submodel->Child ); + m_instance_lodpending.push_back( submodel->Next ); + } + m_shadowcasters.emplace( Key, castsshadow ); + return castsshadow; +} + // the order of the recorded draws for multi draws. draws go in one multi draw when they follow one another with the same // state (material and its texture variant, geometry bank, primitive, blending; Indirect_Mergeable()). the draws of one entry, // the walk of one submodel tree in one lod band, keep their order: submodels of a model may lie on one another and rely on @@ -4175,8 +4382,14 @@ void opengl46_renderer::Order_Indirect_Draws() // a number for each state the draws have, by sorting the states (the buffers are kept between frames) m_indirectstatekeys.clear(); + // the sun shadows draw with the program and the texture of the alpha test, whatever the material + auto const shadows { m_renderpass.draw_mode == rendermode::shadows }; for( std::size_t index = 0; index < count; ++index ) { auto const &draw { m_indirectdraws[ index ] }; + if( shadows ) { + m_indirectstatekeys.push_back( { Indirect_Shadow_Texture( draw.material ), 0u, draw.geometry.bank, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast( index ) } ); + continue; + } auto variant { 0u }; if( draw.material != null_handle ) { auto const &material { m_materials.material( draw.material ) }; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 78d0c9af..bd8293c0 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -355,6 +355,16 @@ class opengl46_renderer : public gfx_renderer { // (Global.gfx_gpucull_occlusion_instances) the instances seen in the previous frame are drawn first (their depth, or // their colour without the prepass), the pyramid is made again and the other instances are culled against it void Render_Instances_Indirect(); + // the same for the passes culled on the gpu other than the main view (Global.gfx_gpucull_shadows, _reflections): the + // cascades of the sun shadows, drawn with the shadow programs, and the faces of the environment map, drawn with the + // programs of the materials like the main view. no occlusion test, no depth prepass, one phase + void Render_Instances_Indirect_Pass(); + // the walk of the submodel trees of the groups culled by Cull, once per lod band, which records their draws + // (m_indirectdraws, m_indirectcommands), puts them in order for multi draws and has Cull fill in their commands + void Record_Indirect_Draws( opengl46_instance_cull &Cull ); + // whether a model with its skins has any part the sun shadows draw: the same test Render_Instanced() makes. the answer + // is kept for the rest of the pass + bool Instances_Cast_Shadow( scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket ); // draws of the instances made, and the gl calls which made them struct indirect_counts { std::uint32_t draws { 0 }; @@ -371,6 +381,10 @@ class opengl46_renderer : public gfx_renderer { indirect_counts Render_Instances_Indirect_Depth( opengl46_instance_cull::command_set const Set ); indirect_counts Render_Instances_Indirect_Prepass( opengl46_instance_cull::command_set const Set, gl::gpu_timer &Timer ); indirect_counts Render_Instances_Indirect_Colour( opengl46_instance_cull::command_set const Set ); + // the recorded draws of a shadow pass with the shadow programs, the way Bind_Material_Shadow() picks them + indirect_counts Render_Instances_Indirect_Shadow( opengl46_instance_cull::command_set const Set ); + // the texture Bind_Material_Shadow() binds for the alpha test of a material, or null_handle (no test) + texture_handle Indirect_Shadow_Texture( material_handle const Material ); // multi draws of the recorded draws (Global.gfx_gpucull_multidraw): puts the draws in an order in which the ones with the // same state follow one another, the draws of one entry kept in the order of the walk, then places their model blocks in // the buffer of the frame (m_drawblockstream), in the same order @@ -555,8 +569,17 @@ class opengl46_renderer : public gfx_renderer { // when Global.gfx_gpucull is set: the instances of the pass are drawn from its result (1), or the result is compared // with the one of the cpu (2) opengl46_instance_cull m_instancecull; - bool m_instancecullpass { false }; // the current pass feeds it + bool m_instancecullpass { false }; // the current pass feeds a culling: m_passcull unsigned int m_instancecullframe { ~0u }; // frame it last ran in + // the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows, + // _reflections), with records and programs shared with m_instancecull + std::array m_shadowcull; + opengl46_instance_cull m_reflectioncull; + opengl46_instance_cull *m_passcull { nullptr }; // the one the current pass feeds + opengl46_instance_cull *m_indirectcull { &m_instancecull }; // the one the recorded draws (m_indirectdraws) were culled by + int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows + // Instances_Cast_Shadow() of the groups of the shadow pass under way + std::unordered_map m_shadowcasters; std::vector m_instance_bandcounts; // reported by Render_Instanced() for the comparison // depth pyramid for the occlusion test of the gpu culling opengl46_hiz m_hiz; @@ -616,6 +639,8 @@ class opengl46_renderer : public gfx_renderer { std::unordered_map> m_multidrawprograms; std::unique_ptr m_prepass_multidraw_shader; std::unique_ptr m_prepass_alpha_multidraw_shader; + std::unique_ptr m_shadow_multidraw_shader; // and of the sun shadows + std::unique_ptr m_alpha_shadow_multidraw_shader; bool m_bindmultidraw { false }; // Bind_Material() binds the variant of the material program for multi draws bool m_indirectrecording { false }; // draw() and Bind_Material() record instead of drawing std::uint32_t m_indirectentry { 0 }; // entry (group and lod band) recorded diff --git a/shaders/simpleuv.vert b/shaders/simpleuv.vert index 673130ae..13cd189a 100644 --- a/shaders/simpleuv.vert +++ b/shaders/simpleuv.vert @@ -9,4 +9,7 @@ void main() { gl_Position = (projection * effective_modelview()) * vec4(v_vert, 1.0f); f_coord = v_coord; +#ifdef MODEL_DRAWS + f_model_draw = uint(gl_DrawID); +#endif } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index b73630a5..f2c6a0c8 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1454,6 +1454,16 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_gpucull_multidraw; } + else if (Token == "gfx.gpucull.shadows") + { + Parser.getTokens(1); + Parser >> gfx_gpucull_shadows; + } + else if (Token == "gfx.gpucull.reflections") + { + Parser.getTokens(1); + Parser >> gfx_gpucull_reflections; + } else if (Token == "gfx.depthprepass.scene") { Parser.getTokens(1); @@ -1950,6 +1960,8 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.gpucull.occlusion.instances", gfx_gpucull_occlusion_instances ); export_as_text( Output, "gfx.depthprepass", gfx_depthprepass ); export_as_text( Output, "gfx.gpucull.multidraw", gfx_gpucull_multidraw ); + export_as_text( Output, "gfx.gpucull.shadows", gfx_gpucull_shadows ); + export_as_text( Output, "gfx.gpucull.reflections", gfx_gpucull_reflections ); export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene ); export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming ); export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled ); diff --git a/utilities/Globals.h b/utilities/Globals.h index c55d00cd..11bd1a01 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -294,6 +294,10 @@ struct global_settings { // with gfx_gpucull 1: the draws of the instances which follow one another with the same material and geometry bank are // made with one multi draw, their model blocks read from a storage buffer by the number of the draw bool gfx_gpucull_multidraw = true; + // with gfx_gpucull 1 or 2: the instances of the cascades of the sun shadows and of the faces of the environment map are + // culled in compute passes as well (drawn from their result, or compared), not only those of the main view + bool gfx_gpucull_shadows = true; + bool gfx_gpucull_reflections = true; // (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included bool gfx_depthprepass_scene = false; // (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,