diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 4f029cf5..c7b22543 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -265,8 +265,8 @@ Many models of the scenery (trees, bushes, poles, fences: models without lights 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. 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. +the environment map (reflections) are drawn this way, and so are the sleepers of the tracks with a `sleepermodel` in the main +view; the shadow maps of lamps and headlights, the cab and other views go as before. | Entry | Default | Meaning | |---|---|---| @@ -277,6 +277,7 @@ before. | `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.gpucull.sleepers yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the sleepers of the tracks (`sleepermodel`) of the main view too, each one tested against the view and given its level of detail by its distance, as before (the tracks within `sleeperdistance` are chosen as before); they also take the occlusion test, the depth prepass and the two phases of the models | | `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 @@ -390,7 +391,8 @@ editor goes through the first `dynamiclights` headlights, the reflections throug 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:`. + `instance cull shadows 1:` … and `instance cull reflections, last face:`. With `gfx.gpucull.sleepers` the row + `gpu cull:` is followed by how many sleepers of how many tracks were queued (they are counted among the models). - 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/glsl_common.cpp b/gl/glsl_common.cpp index 2df97c0b..22a7bafd 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -28,6 +28,7 @@ void gl::glsl_common_setup() "#define INSTANCE_COMMAND_GROUP_SIZE " + std::to_string(INSTANCE_COMMAND_GROUP_SIZE) + "\n" + "#define HIZ_GROUP_SIZE " + std::to_string(HIZ_GROUP_SIZE) + "\n" + "#define INSTANCE_COUNTER_HEADER " + std::to_string(INSTANCE_COUNTER_HEADER) + "\n" + + "#define INSTANCE_JOB_SLEEPERS " + std::to_string(INSTANCE_JOB_SLEEPERS) + "U\n" + "#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\n" + "#define CLUSTER_GRID_X " + std::to_string(CLUSTER_GRID_X) + "\n" + "#define CLUSTER_GRID_Y " + std::to_string(CLUSTER_GRID_Y) + "\n" + diff --git a/gl/ubo.h b/gl/ubo.h index e33a353b..163b691b 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -466,7 +466,7 @@ namespace gl // culling of model instances in compute passes (instance_cull.comp, instance_cull_offsets.comp). binding points of // the shader storage blocks, apart from the ones bound for the shading so the passes don't disturb them const GLuint INSTANCE_RECORD_SSBO_BINDING = 9; // scene::basic_cell::instance_record of the queued buckets - const GLuint INSTANCE_JOB_SSBO_BINDING = 10; // instance_cull_job, one per queued bucket + const GLuint INSTANCE_JOB_SSBO_BINDING = 10; // instance_cull_job, one per queued bucket (or track with sleepers) 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], cursor[entries] and first[entries] @@ -496,7 +496,15 @@ namespace gl uint32_t count; // records of the bucket uint32_t group; uint32_t candidatestart; // records of the jobs before this one - uint32_t pad[2]; + uint32_t flags; // instance_cull_job_flags + uint32_t pad; + }; + + enum instance_cull_job_flags : uint32_t + { + // the sleepers of a track (opengl46_renderer::Render_Sleepers()): the lod goes by the plain distance from the camera of + // the pass, without the range and size tests of the model instances (the range of the whole track is tested on the cpu) + INSTANCE_JOB_SLEEPERS = 1u }; static_assert(sizeof(instance_cull_job) == 48, "bad size of instance_cull_job"); diff --git a/rendering/opengl46/opengl46instancecull.cpp b/rendering/opengl46/opengl46instancecull.cpp index 02172c3f..4b62bb4e 100644 --- a/rendering/opengl46/opengl46instancecull.cpp +++ b/rendering/opengl46/opengl46instancecull.cpp @@ -76,26 +76,44 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup) m_drawgroups.clear(); m_lodlist.clear(); m_groupmap.clear(); + m_sleepergroupmap.clear(); m_cpucounts.clear(); m_candidates = 0; m_entries = 0; + m_sleepertracks = 0; + m_sleepers = 0; } void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area) +{ + add(Key, Bucket, Area, false); +} + +void opengl46_instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area) +{ + add(Key, Bucket, Area, true); +} + +void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers) { if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr) return; // range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for - // its single precision: the draws are made only for the lod bands within it - auto const centerdistance{glm::length(Area.center - m_setup.distancecamera)}; + // its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the + // camera of the pass + auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera}; + auto const distancescale{Sleepers ? 1.0 : static_cast(m_setup.distancescale)}; + auto const distanceoffset{Sleepers ? 0.0 : static_cast(m_setup.distanceoffset)}; + auto const centerdistance{glm::length(Area.center - distancecamera)}; auto const nearest{std::max(0.0, centerdistance - Area.radius)}; auto const farthest{centerdistance + Area.radius}; - auto const distancemin{static_cast(0.999 * (nearest * nearest * m_setup.distancescale + m_setup.distanceoffset))}; - auto const distancemax{static_cast(1.001 * (farthest * farthest * m_setup.distancescale + m_setup.distanceoffset))}; + auto const distancemin{static_cast(0.999 * (nearest * nearest * distancescale + distanceoffset))}; + auto const distancemax{static_cast(1.001 * (farthest * farthest * distancescale + distanceoffset))}; - auto lookup{m_groupmap.find(Key)}; - if (lookup == m_groupmap.end()) + auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap}; + auto lookup{groupmap.find(Key)}; + if (lookup == groupmap.end()) { // lod bounds of the model, the same set Render_Instanced() splits the distance into bands with auto const lodfirst{static_cast(m_lodlist.size())}; @@ -121,7 +139,7 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c group_data const group{static_cast(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax}; m_grouplist.push_back({lodfirst, lodcount, m_entries, 0}); m_entries += group.entrycount; - lookup = m_groupmap.emplace(Key, group).first; + lookup = groupmap.emplace(Key, group).first; if (m_setup.drawing) { // any instance of the group serves; it's only prepared and asked for its material @@ -134,7 +152,7 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c break; } } - m_drawgroups.push_back({&lookup->first, instance, lodfirst, lodcount, group.entryfirst, distancemin, distancemax}); + m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax}); } } else @@ -169,9 +187,15 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c job.count = static_cast(Bucket.records.size()); job.group = lookup->second.index; job.candidatestart = m_candidates; + job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u); m_joblist.push_back(job); m_buckets.push_back(&Bucket); m_candidates += job.count; + if (Sleepers) + { + ++m_sleepertracks; + m_sleepers += job.count; + } } void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket) @@ -384,12 +408,13 @@ void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_ m_cputime = Cputime; } -void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector const &Bandcounts) +void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector const &Bandcounts, bool const Sleepers) { if (!m_dispatched) return; - auto const lookup{m_groupmap.find(Key)}; - if (lookup == m_groupmap.end()) + auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap}; + auto const lookup{groupmap.find(Key)}; + if (lookup == groupmap.end()) return; auto const &group{lookup->second}; for (std::size_t band = 0; band < std::min(Bandcounts.size(), group.entrycount); ++band) @@ -412,6 +437,8 @@ void opengl46_instance_cull::end_pass() while (m_cpupasses.size() > 8) m_cpupasses.pop_front(); } + m_lastsleepertracks = m_sleepertracks; + m_lastsleepers = m_sleepers; m_inpass = false; m_dispatched = false; } @@ -462,24 +489,26 @@ std::string opengl46_instance_cull::info(std::string const &Label, bool const Oc { if (Global.gfx_gpucull == 0) return {}; + // the sleepers are among the instances counted + auto const sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}}; if (Global.gfx_gpucull == 1) { if (m_last.frame == 0) - return "\n" + Label + ": no result yet"; + return "\n" + Label + ": no result yet" + sleepers; 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"; + " indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers; 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"; + std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers; } if (m_comparedframes == 0) - return "\n" + Label + ": no result yet"; + return "\n" + Label + ": no result yet" + sleepers; 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::to_string(m_comparedframes) + sleepers; } std::string opengl46_instance_cull::info_times(std::string const &Label) diff --git a/rendering/opengl46/opengl46instancecull.h b/rendering/opengl46/opengl46instancecull.h index 3d8716fe..f069eae7 100644 --- a/rendering/opengl46/opengl46instancecull.h +++ b/rendering/opengl46/opengl46instancecull.h @@ -46,6 +46,8 @@ class TAnimModel; // 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 +// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its +// own, and drawn together with the instances; their groups are kept apart from those of the model instances class opengl46_instance_cull { public: @@ -76,7 +78,8 @@ class opengl46_instance_cull struct draw_group { scene::basic_cell::instance_bucket_key const *key; - TAnimModel *instance; // one of the group, prepares the model and gives the material + TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers + bool sleepers; // the skins come from the key, as Render_Sleepers() sets them std::uint32_t lodfirst; // in lod_bounds() std::uint32_t lodcount; std::uint32_t entryfirst; @@ -103,6 +106,11 @@ class opengl46_instance_cull // adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed. // 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); + // adds the sleepers of a track, their records made already as a bucket with no instances (only the records and the place + // in the record buffer count). Area: bounds of the locations of the sleepers. the range of the whole track is tested by + // the caller, the culling tests each sleeper against the frustum and gives it the lod band of its plain distance from the + // camera of the pass (Render_Sleepers()) + void add_sleepers(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. Twophase: with the occlusion test, only the instances seen in the previous // frame pass for now, dispatch_second() takes the others @@ -164,8 +172,9 @@ class opengl46_instance_cull { return static_cast(m_last.gputotal); } - // counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass - void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector const &Bandcounts); + // counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers + // Render_Sleepers() drew. needs dispatch() before it + void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector const &Bandcounts, bool const Sleepers = false); // ends the pass: its cpu counts are kept for the comparison void end_pass(); // takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins; @@ -224,6 +233,10 @@ class opengl46_instance_cull bool restarted{false}; }; + using group_map = std::unordered_map; + + // add_bucket() and add_sleepers() + void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers); // 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 @@ -251,10 +264,16 @@ class opengl46_instance_cull std::vector m_grouplist; std::vector m_drawgroups; std::vector m_lodlist; - std::unordered_map m_groupmap; + group_map m_groupmap; + group_map m_sleepergroupmap; // groups of the sleepers std::vector m_pending; // walk of the submodel tree std::uint32_t m_candidates{0}; std::uint32_t m_entries{0}; + // tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel + std::uint32_t m_sleepertracks{0}; + std::uint32_t m_sleepers{0}; + std::uint32_t m_lastsleepertracks{0}; + std::uint32_t m_lastsleepers{0}; bool m_dispatched{false}; std::vector m_cpucounts; // of the current pass, by entry diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 6bf212cf..c0acd626 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -679,6 +679,8 @@ bool opengl46_renderer::Render() m_locallights.reset(); m_localshadows.reset_lamps(); m_localshadows.reset_headlights(); + // and so may the tracks the sleeper records were made for + m_sleeperrecords.clear(); } // generate new frame opengl_texture::reset_unit_cache(); @@ -690,6 +692,14 @@ bool opengl46_renderer::Render() ++m_instancerecordsversion; m_nodestateversion = scene::node_state_version; } + if( ( m_framestamp % 256 ) == 0 ) { + // the sleeper records of tracks out of sight for a while. their places in the record buffer of the gpu culling are + // dropped when it starts over + for( auto records { m_sleeperrecords.begin() }; records != m_sleeperrecords.end(); ) { + if( m_framestamp - records->second.frame > 256 ) { records = m_sleeperrecords.erase( records ); } + else { ++records; } + } + } // scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records if (simulation::is_ready && (false == Global.gfx_skiprendering)) m_localshadows.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp); @@ -987,6 +997,7 @@ void opengl46_renderer::draw_debug_ui() { 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("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers); } ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene); if (Global.gfx_contactshadows > 0) @@ -2902,6 +2913,13 @@ void opengl46_renderer::Render(scene::basic_region *Region) setup.drawing = ( Global.gfx_gpucull == 1 ); m_passcull->begin_pass( setup ); } + // the sleepers of tracks go with the instances of the main view + m_sleeperspass = ( ( m_passcull == &m_instancecull ) && Global.gfx_gpucull_sleepers ); + if( m_sleeperspass && ( false == m_passcull->drawing() ) ) { + for( auto &counts : m_sleeperbandcounts ) { + counts.second.clear(); + } + } // build a list of region sections to render glm::vec3 const cameraposition{m_renderpass.pass_camera.position()}; auto const camerax = static_cast(std::floor(cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2)); @@ -3379,6 +3397,14 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite else { // the view of the pass is in place here, the one Render_Instanced() takes m_passcull->dispatch( OpenGLMatrices.data( GL_MODELVIEW ) ); + // the sleepers the cpu drew along the tracks, before the culling ran + if( m_sleeperspass ) { + for( auto const &counts : m_sleeperbandcounts ) { + if( false == counts.second.empty() ) { + m_passcull->cpu_result( counts.first, counts.second, true ); + } + } + } } } for( auto &bucket : m_frame_instance_buckets ) { @@ -3394,6 +3420,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite m_instancecullpass = false; m_passcull = nullptr; } + m_sleeperspass = false; } void opengl46_renderer::Draw_Geometry(std::vector::iterator begin, std::vector::iterator end) @@ -4045,6 +4072,7 @@ void opengl46_renderer::Render_Instances_Indirect() // the colour pass takes the draws from here, and counts them m_indirectcolourpending = true; m_indirectcolourset = colourset; + m_indirectcoloursleepers = m_sleeperspass; m_renderpass.draw_stats.drawcalls += static_cast( prepasscounts.calls ); colourcounts.draws = static_cast( m_indirectdraws.size() ); } @@ -4100,10 +4128,25 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) 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 }; + if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; } + material_handle const *skins { nullptr }; + auto alpha { 0x30300030 }; + if( group.sleepers ) { + // as Render_Sleepers() sets them up: the skin of the track, if any. like scenery models they pick no texture + // variants of their own + if( group.key->skins[ 1 ] != null_handle ) { + skins = group.key->skins.data(); + } + TSubModel::iVariantSeed = 0; + } + else { + group.instance->RaPrepare(); + auto const *material { group.instance->Material() }; + if( material != nullptr ) { + skins = material->replacable_skins; + alpha = material->textures_alpha; + } + } 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 @@ -4114,7 +4157,7 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) ::glPushMatrix(); ::glLoadIdentity(); model->Root->fSquareDist = bandmin; - model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha ); + model->Root->ReplacableSet( skins, alpha ); model->Root->pRoot = model; Render( model->Root ); ::glPopMatrix(); @@ -4764,6 +4807,71 @@ std::vector const &opengl46_renderer::Model_Textures(TModel3d *M return textures; } +namespace { + +// the group of the sleepers of a track in the gpu culling: their model, and the skin in the slot Render_Sleepers() sets it in +scene::basic_cell::instance_bucket_key sleeper_key( TTrack const &Track ) { + scene::basic_cell::instance_bucket_key key; + key.pModel = Track.m_sleeper_model; + key.skins[ 1 ] = Track.m_sleeper_skin; + return key; +} + +} // namespace + +void opengl46_renderer::Queue_Sleepers( TTrack *Track ) +{ + // the gates of Render_Sleepers(); the range is tested for the whole track + if( ( Track->m_sleeper_model == nullptr ) || Track->m_sleeper_local_transforms.empty() || ( Global.SleeperDistance <= 0.f ) ) { return; } + auto const distsq { glm::length2( Track->location() - m_renderpass.pass_camera.position() ) }; + auto const cutoffsq { static_cast( Global.SleeperDistance ) * static_cast( Global.SleeperDistance ) }; + if( distsq > cutoffsq ) { return; } + + auto &records { m_sleeperrecords[ Track ] }; + auto const &transforms { Track->m_sleeper_local_transforms }; + if( ( records.bucket.recordsversion != m_instancerecordsversion ) + || ( records.model != Track->m_sleeper_model ) + || ( records.skin != Track->m_sleeper_skin ) + || ( records.transforms != transforms.data() ) + || ( records.count != transforms.size() ) ) { + // each sleeper is a record placed and turned by its transform, relative to the origin of the track (the centre of its + // cell), with the sphere Render_Sleepers() tests against the frustum + auto const radius { std::max( Track->m_sleeper_model->bounding_radius(), 2.0f ) }; + auto &bucket { records.bucket }; + bucket.records.clear(); + bucket.records.reserve( transforms.size() ); + glm::vec3 low { std::numeric_limits::max() }; + glm::vec3 high { std::numeric_limits::lowest() }; + for( auto const &local : transforms ) { + scene::basic_cell::instance_record record; + glm::vec3 const position { local[ 3 ] }; + record.position = packed_vec3{ position }; + record.radius = radius; + record.rangesquaredmin = 0.f; + record.rangesquaredmax = std::numeric_limits::max(); + record.flags = scene::basic_cell::instance_visible | scene::basic_cell::instance_instanceable; + for( int column = 0; column < 3; ++column ) { + record.rotationscale[ column ] = local[ column ]; + } + bucket.records.emplace_back( record ); + low = glm::min( low, position ); + high = glm::max( high, position ); + } + bucket.recordorigin = Track->m_origin; + bucket.lodrangesquared = std::numeric_limits::max(); + bucket.recordsversion = m_instancerecordsversion; + bucket.gpustale = true; + records.key = sleeper_key( *Track ); + records.area = scene::bounding_area{ Track->m_origin + glm::dvec3{ ( low + high ) * 0.5f }, 0.5f * glm::length( high - low ) }; + records.model = Track->m_sleeper_model; + records.skin = Track->m_sleeper_skin; + records.transforms = transforms.data(); + records.count = transforms.size(); + } + records.frame = m_framestamp; + m_passcull->add_sleepers( records.key, records.bucket, records.area ); +} + void opengl46_renderer::Render_Sleepers( TTrack *Track ) { if( Track == nullptr ) { return; } @@ -4931,6 +5039,25 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track ) m_renderpass.draw_stats.instances += static_cast( total ); m_renderpass.draw_stats.models += static_cast( total ); + + if( m_sleeperspass && m_instancecullpass && ( false == m_passcull->drawing() ) ) { + // the comparison with the gpu culling: the sleepers drawn by lod band, the bands as the culling makes them, split by + // the bounds of the model past 0 and short of the largest float + auto const counted = []( float const Bound ) { + return ( Bound > 0.f ) && ( Bound < std::numeric_limits::max() ); }; + auto &counts { m_sleeperbandcounts[ sleeper_key( *Track ) ] }; + auto const bands { static_cast( std::count_if( lodbounds.begin(), lodbounds.end(), counted ) ) + 1 }; + if( counts.size() < bands ) { + counts.resize( bands, 0 ); + } + for( auto const &survivor : survivors ) { + std::size_t band { 0 }; + for( auto const bound : lodbounds ) { + if( counted( bound ) && ( bound <= survivor.first ) ) { ++band; } + } + ++counts[ band ]; + } + } } bool opengl46_renderer::Render(TDynamicObject *Dynamic) @@ -5699,6 +5826,15 @@ void opengl46_renderer::Render(scene::basic_cell::path_sequence::const_iterator auto *track = *first; if( false == track->m_visible ) { continue; } if( false == track->m_sleeper_enabled ) { continue; } + if( m_sleeperspass ) { + // culled with the instances of the pass and drawn from the result, or drawn by the cpu as well to compare + Queue_Sleepers( track ); + if( m_passcull->drawing() ) { continue; } + } + else if( m_indirectcolourpending && m_indirectcoloursleepers ) { + // the depth pass of the whole scene culled and recorded them, the colour pass draws what it recorded + continue; + } Render_Sleepers( track ); } break; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index bd8293c0..5f504c2b 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -425,6 +425,9 @@ class opengl46_renderer : public gfx_renderer { // no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond // Global.SleeperDistance from the track origin. void Render_Sleepers( TTrack *Track ); + // queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of + // Render_Sleepers(), making their records first when they're missing or out of date + void Queue_Sleepers( TTrack *Track ); bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false); bool Render_interior( bool const Alpha = false ); bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false ); @@ -581,6 +584,25 @@ class opengl46_renderer : public gfx_renderer { // 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 + // the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made + // from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records + // (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go + struct sleeper_records { + scene::basic_cell::instance_bucket bucket; // the records only, no instances + scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in + scene::bounding_area area; // of the locations of the sleepers + TModel3d const *model { nullptr }; // what the records were made from + material_handle skin { null_handle }; + glm::mat4 const *transforms { nullptr }; + std::size_t count { 0 }; + unsigned int frame { 0 }; // queued last + }; + std::unordered_map m_sleeperrecords; + bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view) + bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too + // counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're + // drawn before the culling runs) + std::unordered_map, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts; // depth pyramid for the occlusion test of the gpu culling opengl46_hiz m_hiz; // a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be diff --git a/shaders/instance_cull.comp b/shaders/instance_cull.comp index 12965344..87a8641a 100644 --- a/shaders/instance_cull.comp +++ b/shaders/instance_cull.comp @@ -1,5 +1,6 @@ // culls the model instances of the buckets queued in a render pass and assigns them their lod bands, with the tests -// opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket. +// opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket. the sleepers of the +// tracks queued come as buckets too, with the tests of opengl46_renderer::Render_Sleepers(). // 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. @@ -35,8 +36,8 @@ struct instance_cull_job uint count; uint group; uint candidatestart; - uint pad0; - uint pad1; + uint flags; + uint pad; }; struct instance_cull_group @@ -162,7 +163,7 @@ bool occluded(vec3 center, float radius) return nearest < farthest; } -// the tests of the cpu (Render_Instanced()), all but the occlusion one: the entry of the instance, or false when it's left out. +// the tests of the cpu (Render_Instanced(), for sleepers Render_Sleepers()), 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) { @@ -178,17 +179,26 @@ bool passes(instance_cull_job job, instance_record record, out vec3 location, ou return false; } - vec3 offset = job.distanceorigin + record.position; - float distancesquared = dot(offset, offset) * distance_params.x; - if (distancesquared > distance_params.z) - return false; - distancesquared += distance_params.y; - if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax)) - return false; - if (distancesquared > distance_params.w) - return false; - if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE) - return false; + float distancesquared; + if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u) + { + // the sleepers of a track, as Render_Sleepers() takes them: the lod by the plain distance from the camera of the pass + distancesquared = dot(location, location); + } + else + { + vec3 offset = job.distanceorigin + record.position; + distancesquared = dot(offset, offset) * distance_params.x; + if (distancesquared > distance_params.z) + return false; + distancesquared += distance_params.y; + if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax)) + return false; + if (distancesquared > distance_params.w) + return false; + if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE) + 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]; @@ -202,9 +212,18 @@ bool passes(instance_cull_job job, instance_record record, out vec3 location, ou return true; } -void write(uint entry, instance_record record, vec3 location) +void write(uint entry, instance_record record, vec3 location, instance_cull_job job) { uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u); + if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u) + { + // in the order Render_Sleepers() multiplies them (the view moved to the origin of the track, then the transform of the + // sleeper), which gives the very same matrix: the other order rounds differently, enough to shift the edges of sleepers + // sunk in the ballast by a pixel here and there + mat4 originview = view * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(job.origin, 1.0)); + modelviews[slot] = originview * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(record.position, 1.0)); + return; + } modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0)); } @@ -241,7 +260,7 @@ void main() atomicAdd(counters[COUNTER_OCCLUDED], 1u); return; } - write(entry, record, location); + write(entry, record, location, job); if (phases.x != 0u) atomicAdd(counters[COUNTER_FIRST], 1u); return; @@ -252,7 +271,7 @@ void main() if (passed && (false == seen)) { if (inview) - write(entry, record, location); + write(entry, record, location, job); else atomicAdd(counters[COUNTER_OCCLUDED], 1u); } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index f2c6a0c8..93cf05c4 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1464,6 +1464,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_gpucull_reflections; } + else if (Token == "gfx.gpucull.sleepers") + { + Parser.getTokens(1); + Parser >> gfx_gpucull_sleepers; + } else if (Token == "gfx.depthprepass.scene") { Parser.getTokens(1); @@ -1962,6 +1967,7 @@ global_settings::export_as_text( std::ostream &Output ) const { 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.gpucull.sleepers", gfx_gpucull_sleepers ); 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 11bd1a01..9b9f950d 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -298,6 +298,9 @@ struct global_settings { // 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; + // with gfx_gpucull 1 or 2: the sleepers of the tracks (sleepermodel) of the main view are culled with the instances as well, + // and with 1 drawn from the result + bool gfx_gpucull_sleepers = 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,