diff --git a/model/Model3d.cpp b/model/Model3d.cpp index 80d7e577..054f9eb7 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -1616,6 +1616,7 @@ void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel) } ++iSubModelsCount; // teraz jest o 1 submodel więcej iFlags |= 0x0200; // submodele są oddzielne + m_lodboundsfound = false; // the new submodel may have its own range }; TSubModel *TModel3d::GetFromName(std::string const &Name) const @@ -1643,6 +1644,33 @@ TModel3d::instance_submodels const &TModel3d::instance_parts() return m_instancesubmodels; } +// returns the distinct bounds of the visibility ranges of the submodels, sorted, finding them on first use +std::vector const &TModel3d::lod_bounds() +{ + if (false == m_lodboundsfound) + { + m_lodbounds.clear(); + std::vector pending{Root}; + while (false == pending.empty()) + { + auto const *submodel{pending.back()}; + pending.pop_back(); + if (submodel == nullptr) + continue; + if (submodel->fSquareMinDist > 0.f) + m_lodbounds.emplace_back(submodel->fSquareMinDist); + if (submodel->fSquareMaxDist < std::numeric_limits::max()) + m_lodbounds.emplace_back(submodel->fSquareMaxDist); + pending.push_back(submodel->Child); + pending.push_back(submodel->Next); + } + std::sort(m_lodbounds.begin(), m_lodbounds.end()); + m_lodbounds.erase(std::unique(m_lodbounds.begin(), m_lodbounds.end()), m_lodbounds.end()); + m_lodboundsfound = true; + } + return m_lodbounds; +} + // locates particle source submodels and stores them on internal list nameoffset_sequence const &TModel3d::find_smoke_sources() { diff --git a/model/Model3d.h b/model/Model3d.h index 47987bf4..f3023208 100644 --- a/model/Model3d.h +++ b/model/Model3d.h @@ -292,6 +292,8 @@ private: nameoffset_sequence m_smokesources; // list of particle sources defined in the model instance_submodels m_instancesubmodels; // filled on first use, see instance_parts() bool m_instancesubmodelslocated { false }; + std::vector m_lodbounds; // filled on first use, see lod_bounds() + bool m_lodboundsfound { false }; public: TModel3d() = default; @@ -303,6 +305,9 @@ public: // returns submodels which scenery instances of the model refer to by name. the search for a name goes through the whole // submodel tree, so it's done once for the model instead of once for each of its instances instance_submodels const & instance_parts(); + // returns the distinct bounds of the visibility ranges of the submodels (fSquareMinDist and fSquareMaxDist, squared), sorted: + // they split the distance into bands in which the same submodels are drawn. found once for the model, like instance_parts() + std::vector const & lod_bounds(); TSubModel * AddToNamed(const char *Name, TSubModel *SubModel); nameoffset_sequence const & find_smoke_sources(); void AddTo(TSubModel *tmp, TSubModel *SubModel); diff --git a/rendering/opengl46/opengl46instancecull.cpp b/rendering/opengl46/opengl46instancecull.cpp index e19632cc..c4e312c2 100644 --- a/rendering/opengl46/opengl46instancecull.cpp +++ b/rendering/opengl46/opengl46instancecull.cpp @@ -125,26 +125,11 @@ void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &K 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 + // lod bounds of the model, the same set Render_Instanced() splits the distance into bands with, found once for the model auto const lodfirst{static_cast(m_lodlist.size())}; - m_pending.clear(); - m_pending.push_back(Key.pModel->Root); - while (!m_pending.empty()) - { - auto const *submodel{m_pending.back()}; - m_pending.pop_back(); - if (submodel == nullptr) - continue; - if (submodel->fSquareMinDist > 0.f) - m_lodlist.emplace_back(submodel->fSquareMinDist); - if (submodel->fSquareMaxDist < std::numeric_limits::max()) - m_lodlist.emplace_back(submodel->fSquareMaxDist); - m_pending.push_back(submodel->Child); - m_pending.push_back(submodel->Next); - } - std::sort(m_lodlist.begin() + lodfirst, m_lodlist.end()); - m_lodlist.erase(std::unique(m_lodlist.begin() + lodfirst, m_lodlist.end()), m_lodlist.end()); - auto const lodcount{static_cast(m_lodlist.size()) - lodfirst}; + auto const &lodbounds{Key.pModel->lod_bounds()}; + m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end()); + auto const lodcount{static_cast(lodbounds.size())}; group_data const group{static_cast(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax}; m_grouplist.push_back({lodfirst, lodcount, m_entries, 0}); @@ -534,7 +519,7 @@ std::string opengl46_instance_cull::info(std::string const &Label, bool const Oc // what the pass tested and sent to the gpu sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets"; if (Global.gfx_gpucull_bucketrange) - sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out beyond their range"; + sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out before the culling"; sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : ""); if (Global.gfx_gpucull == 1) { diff --git a/rendering/opengl46/opengl46instancecull.h b/rendering/opengl46/opengl46instancecull.h index 1b5f4c7d..533595af 100644 --- a/rendering/opengl46/opengl46instancecull.h +++ b/rendering/opengl46/opengl46instancecull.h @@ -107,14 +107,15 @@ class opengl46_instance_cull return m_inpass && m_setup.drawing; } // 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 + // Area: bounds which hold the locations of all its visible instances: their own (opengl46_renderer::Bucket_Area()) or of the cell + // holding the bucket 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); - // a bucket the renderer left out of the pass, as it lies wholly beyond its range (Global.gfx_gpucull_bucketrange): only counted, + // a bucket the renderer left out of the pass, as none of its instances can pass the culling (Global.gfx_gpucull_bucketrange): only counted, // for the panel void skip_bucket(scene::basic_cell::instance_bucket const &Bucket); // runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid @@ -275,7 +276,6 @@ class opengl46_instance_cull std::vector m_lodlist; 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 diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index edf314e0..e8500a9a 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -705,6 +705,7 @@ bool opengl46_renderer::Render() // and so may the tracks the sleeper records were made for, and the models drawn on their own m_sleeperrecords.clear(); m_modelrecords.clear(); + m_cascaderecords.clear(); } // generate new frame opengl_texture::reset_unit_cache(); @@ -801,7 +802,21 @@ void opengl46_renderer::SwapBuffers() Timer::subsystem.gfx_swap.stop(); m_debugtimestext.clear(); - m_debugtimestext = + // the whole frame of the main loop and its parts: the update of the simulation, Render() with the ui drawn in it, the swap, + // and what's left (input, terrain streaming, tasks, network), measured by none of them + { + auto const mainloop { Timer::subsystem.mainloop_total.average() }; + auto const simulation { Timer::subsystem.sim_total.average() }; + auto const render { Timer::subsystem.gfx_total.average() }; + auto const swap { Timer::subsystem.gfx_swap.average() }; + m_debugtimestext = + "mainloop total: " + to_string( mainloop, 2 ) + " ms\n" + + " simulation: " + to_string( simulation, 2 ) + " ms\n" + + " render: " + to_string( render, 2 ) + " ms (ui " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms)\n" + + " swap: " + to_string( swap, 2 ) + " ms\n" + + " other: " + to_string( mainloop - simulation - render - swap, 2 ) + " ms\n"; + } + m_debugtimestext += "cpu frame total: " + to_string( Timer::subsystem.gfx_color.average() + Timer::subsystem.gfx_shadows.average() + ( Global.gfx_envmap_enabled ? Timer::subsystem.gfx_reflections.average() : 0.f ) + Timer::subsystem.gfx_swap.average(), 2 ) + " ms\n" + " color: " + to_string( Timer::subsystem.gfx_color.average(), 2 ) + " ms (" + std::to_string( m_cellqueue.size() ) + " sectors)\n"; if( Global.gfx_shadowmap_enabled ) { @@ -1123,7 +1138,7 @@ void opengl46_renderer::draw_debug_ui() ImGui::Checkbox("the same for the scenery of the shadow maps of lamps, when it's drawn next (gfx.gpucull.shadows.lamps)", &Global.gfx_gpucull_shadows_lamps); ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers); ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models); - ImGui::Checkbox("leave out the models of a cell lying wholly beyond their range before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange); + ImGui::Checkbox("leave out the models of a cell lying wholly out of view, beyond their range or too small before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange); } ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene); if (Global.gfx_contactshadows > 0) @@ -4017,12 +4032,9 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell ) auto const distancelimit { slack * ( reflections ? sq( static_cast( Global.reflectiontune.range_instances ) ) : sq( static_cast( m_renderpass.draw_range ) + 250.0 ) ) }; // up close the rounding of the records is too large a part of the distance for the slack to cover it auto const distancetested { sq( 64.0 ) }; - // the nearest point of the cell, measured as the tests below and the gpu culling measure the instances: a bucket whose range - // ends before it has none of its instances drawn, and is left out of the pass before the culling. basic_cell::enclose_area - // keeps the locations of all instances of the cell within the radius of its area - auto const cellnearest { std::max( 0.0, glm::length( Cell->m_area.center - cameraposition ) - static_cast( Cell->m_area.radius ) ) }; - auto const cellscaledsquared { cellnearest * cellnearest * distancescale + distanceoffset }; - auto const bucketrange { Global.gfx_gpucull_bucketrange && userecords && ( cellnearest * cellnearest > distancetested ) }; + // a bucket whose visible instances all fail the tests of the culling by the bounds of their records (Bucket_Culled()) is left + // out of the pass before the culling. the bounds are made with the records, which the editor doesn't let go by + auto const buckettests { Global.gfx_gpucull_bucketrange && userecords }; for( auto const &bucket : Cell->m_instancebuckets_opaque ) { auto const &instancebucket { bucket.second }; @@ -4030,7 +4042,7 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell ) || ( instancebucket.records.size() != instancebucket.instances.size() ) ) { Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center ); } - if( bucketrange && ( cellscaledsquared >= slack * instancebucket.rangesquared ) ) { + if( buckettests && Bucket_Culled( instancebucket ) ) { if( m_instancecullpass ) { m_passcull->skip_bucket( instancebucket ); } @@ -4038,9 +4050,10 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell ) } if( m_instancecullpass ) { // 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 + // sun shadows are left out of them, as Render_Instanced() leaves them. the lod bands drawn go by the distances + // the locations of the instances can be at: of their own bounds, or of the cell, which holds them too if( ( m_renderpass.draw_mode != rendermode::shadows ) || Instances_Cast_Shadow( bucket.first, instancebucket ) ) { - m_passcull->add_bucket( bucket.first, instancebucket, Cell->m_area ); + m_passcull->add_bucket( bucket.first, instancebucket, ( buckettests ? Bucket_Area( instancebucket ) : Cell->m_area ) ); } // and when the instances are drawn from its result, the cpu has nothing more to do with them if( m_passcull->drawing() ) { continue; } @@ -4113,6 +4126,12 @@ bool opengl46_renderer::Queue_Model( TAnimModel *Instance, bool const Inrange ) modelkey.instance = nullptr; if( false == Instances_Cast_Shadow( modelkey, bucket ) ) { return drawing; } } + // out of the frustum of the pass, beyond its range or too small on the screen by its record: the culling would leave it out, + // and the walk of Record_Indirect_Draws() isn't made for it + if( Global.gfx_gpucull_bucketrange && ( false == EditorModeFlag ) && Bucket_Culled( bucket ) ) { + m_passcull->skip_bucket( bucket ); + return drawing; + } m_passcull->add_bucket( records.key, bucket, records.area ); if( false == drawing ) { // the cpu draws it too, and tells what it drew for the comparison @@ -4167,10 +4186,87 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b Bucket.records.emplace_back( record ); } Bucket.rangesquared = std::min( rangesquared, lodrangesquared ); + + // bounds of the visible instances, for Bucket_Culled(): the centre of the box around their locations, and the spheres + // around it which hold the locations and the instances whole. the gpu leaves out the ones which aren't visible + glm::dvec3 low { std::numeric_limits::max() }; + glm::dvec3 high { std::numeric_limits::lowest() }; + auto radiusmax { 0.f }; + for( auto const &record : Bucket.records ) { + if( ( record.flags & scene::basic_cell::instance_visible ) == 0 ) { continue; } + glm::dvec3 const position { record.position }; + low = glm::min( low, position ); + high = glm::max( high, position ); + radiusmax = std::max( radiusmax, record.radius ); + } + Bucket.recordlocationradius = 0.f; + Bucket.recordradius = -1.f; + Bucket.recordradiusmax = radiusmax; + if( low.x <= high.x ) { + auto const center { 0.5 * ( low + high ) }; + auto locationradius { 0.0 }; + auto radius { 0.0 }; + for( auto const &record : Bucket.records ) { + if( ( record.flags & scene::basic_cell::instance_visible ) == 0 ) { continue; } + auto const distance { glm::length( glm::dvec3{ record.position } - center ) }; + locationradius = std::max( locationradius, distance ); + radius = std::max( radius, distance + record.radius ); + } + Bucket.recordcenter = Origin + center; + Bucket.recordlocationradius = static_cast( locationradius ); + Bucket.recordradius = static_cast( radius ); + } + else { + Bucket.recordcenter = Origin; + } Bucket.recordsversion = m_instancerecordsversion; Bucket.gpustale = true; } +// whether none of the instances of a bucket can pass the tests of the culling of the pass (Render_Instanced(), on the gpu +// instance_cull.comp): by the bounds of their records the visible ones lie wholly out of the frustum of the pass, beyond their +// range or the draw range, or are too small on the screen. the tests are those of the culling with the nearest location and +// the largest instance of the bucket, with a margin for the single precision of the records and of the gpu, so they leave out +// only buckets which the culling would leave out whole +bool opengl46_renderer::Bucket_Culled( scene::basic_cell::instance_bucket const &Bucket ) const +{ + if( Bucket.recordradius < 0.f ) { return true; } + + auto const slack { 1.001 }; + auto const margin { 0.5 }; // metres + // the planes of the frustum in world coordinates, the ones the gpu culling moves to the camera of the pass + auto const &planes { m_renderpass.pass_camera.frustum().planes() }; + auto const ¢er { Bucket.recordcenter }; + auto const radius { slack * Bucket.recordradius + margin }; + for( auto const *plane : planes ) { + if( plane[ 0 ] * center.x + plane[ 1 ] * center.y + plane[ 2 ] * center.z + plane[ 3 ] <= -radius ) { return true; } + } + + // the same choices as in Render_Instanced(): shadows measure the distance from the real camera, reflections ignore zoom and + // distance factor, test the plain distance against their own range and add the fidelity offset + auto const reflections { m_renderpass.draw_mode == rendermode::reflections }; + auto const &distancecamera { m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera : m_renderpass.pass_camera }; + auto const nearest { std::max( 0.0, glm::length( center - distancecamera.position() ) - ( slack * Bucket.recordlocationradius + margin ) ) }; + auto const zoom { static_cast( Global.ZoomFactor ) }; + auto const scaledsquared { nearest * nearest * ( reflections ? 1.0 : 1.0 / ( zoom * zoom * static_cast( Global.fDistanceFactor ) ) ) }; + if( reflections && ( scaledsquared > slack * sq( static_cast( Global.reflectiontune.range_instances ) ) ) ) { return true; } + auto const distancesquared { scaledsquared + ( reflections ? sq( static_cast( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.0 ) }; + if( distancesquared >= slack * Bucket.rangesquared ) { return true; } + if( distancesquared > slack * sq( static_cast( m_renderpass.draw_range ) + 250.0 ) ) { return true; } + // the largest instance at the nearest location takes the largest part of the view + auto const radiusmax { static_cast( Bucket.recordradiusmax ) }; + if( radiusmax * radiusmax * zoom * slack < 0.003 * 0.003 * distancesquared ) { return true; } + + return false; +} + +// the sphere around the locations of the visible instances of a bucket, with the margin of Bucket_Culled(): their distances +// from it set the lod bands the gpu culling can put them in +scene::bounding_area opengl46_renderer::Bucket_Area( scene::basic_cell::instance_bucket const &Bucket ) +{ + return scene::bounding_area{ Bucket.recordcenter, static_cast( 1.001 * Bucket.recordlocationradius + 0.5 ) }; +} + // places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count ) { @@ -4560,39 +4656,92 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) m_indirectdraws.clear(); m_indirectcommands.clear(); auto const &lodbounds { Cull.lod_bounds() }; + // the cascades of the sun shadows make the same walks: the mode, the lod band and the state of the models are the same in all + // of them within a frame, only the instances differ, and those the gpu fills in. the walk of a group in a band is made by the + // first cascade which draws it, the others copy its draws and commands with entries of their own + auto const cascade { + ( m_renderpass.draw_mode == rendermode::shadows ) + && ( &Cull >= m_shadowcull.data() ) + && ( &Cull < m_shadowcull.data() + m_shadowcull.size() ) }; + auto constexpr nowalk { std::numeric_limits::max() }; + if( cascade && ( m_cascaderecordframe != m_framestamp ) ) { + m_cascaderecordframe = m_framestamp; + m_cascadedraws.clear(); + m_cascadecommands.clear(); + if( ( m_framestamp % 256 ) == 0 ) { + // the groups not drawn for a while + for( auto records { m_cascaderecords.begin() }; records != m_cascaderecords.end(); ) { + if( m_framestamp - records->second.frame > 256 ) { records = m_cascaderecords.erase( records ); } + else { ++records; } + } + } + } m_indirectrecording = true; for( auto const &group : Cull.draw_groups() ) { auto *model { group.key->pModel }; 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(); + auto prepared { false }; + auto const prepare = [ & ]() { + prepared = true; + 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 { + if( group.key->instance != nullptr ) { + // a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up + group.instance->RaAnimate( m_framestamp ); + } + group.instance->RaPrepare(); + auto const *material { group.instance->Material() }; + if( material != nullptr ) { + skins = material->replacable_skins; + alpha = material->textures_alpha; + } + } + alpha ^= 0x0F0F000F; + }; + // the walks of the group the cascades made in this frame. a group whose bands were all walked already isn't prepared + cascade_record *walks { nullptr }; + if( cascade ) { + walks = &m_cascaderecords[ *group.key ]; + if( ( walks->frame != m_framestamp ) || ( walks->bands.size() != group.lodcount + 1 ) ) { + walks->frame = m_framestamp; + walks->bands.assign( group.lodcount + 1, cascade_band { nowalk, 0 } ); } - TSubModel::iVariantSeed = 0; } else { - if( group.key->instance != nullptr ) { - // a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up - group.instance->RaAnimate( m_framestamp ); - } - group.instance->RaPrepare(); - auto const *material { group.instance->Material() }; - if( material != nullptr ) { - skins = material->replacable_skins; - alpha = material->textures_alpha; - } + prepare(); } - 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; + if( ( walks != nullptr ) && ( walks->bands[ band ].first != nowalk ) ) { + // walked by another cascade: its draws, for the instances of this entry + auto const &walk { walks->bands[ band ] }; + m_indirectdraws.insert( m_indirectdraws.end(), m_cascadedraws.begin() + walk.first, m_cascadedraws.begin() + walk.first + walk.count ); + for( std::uint32_t draw = 0; draw < walk.count; ++draw ) { + auto &command { m_indirectcommands.emplace_back( m_cascadecommands[ walk.first + draw ] ) }; + command.entry = m_indirectentry; + } + // as the walk would count them + m_renderpass.draw_stats.submodels += static_cast( walk.count ); + m_renderpass.draw_stats.drawcalls += static_cast( walk.count ); + continue; + } + if( false == prepared ) { + prepare(); + } + auto const walkfirst { m_indirectdraws.size() }; ::glPushMatrix(); ::glLoadIdentity(); model->Root->fSquareDist = bandmin; @@ -4600,6 +4749,13 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull ) model->Root->pRoot = model; Render( model->Root ); ::glPopMatrix(); + if( walks != nullptr ) { + walks->bands[ band ] = cascade_band { + static_cast( m_cascadedraws.size() ), + static_cast( m_indirectdraws.size() - walkfirst ) }; + m_cascadedraws.insert( m_cascadedraws.end(), m_indirectdraws.begin() + walkfirst, m_indirectdraws.end() ); + m_cascadecommands.insert( m_cascadecommands.end(), m_indirectcommands.begin() + walkfirst, m_indirectcommands.end() ); + } } } m_indirectrecording = false; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 8696b220..00843fbb 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -368,6 +368,10 @@ class opengl46_renderer : public gfx_renderer { void Queue_Instances( scene::basic_cell const *Cell ); // makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin void Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin ); + // whether none of the instances of a bucket can pass the culling of the current pass, by the bounds of their records + bool Bucket_Culled( scene::basic_cell::instance_bucket const &Bucket ) const; + // the sphere around the locations of the visible instances of a bucket, with a margin for the precision of the records + static scene::bounding_area Bucket_Area( scene::basic_cell::instance_bucket const &Bucket ); // places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them std::uint32_t Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count ); // binds the instance buffer of the frame for the vertex shaders (INSTANCE_SSBO_BINDING) @@ -732,6 +736,20 @@ class opengl46_renderer : public gfx_renderer { }; std::vector m_indirectdraws; std::vector m_indirectcommands; // in the order of m_indirectdraws + // the walks Record_Indirect_Draws() made for the cascades of the sun shadows in the frame, for the other cascades to copy: the + // draws and commands recorded for a group (model and skins) in each of its lod bands, first none where no cascade walked it + struct cascade_band { + std::uint32_t first; + std::uint32_t count; + }; + struct cascade_record { + unsigned int frame { 0 }; + std::vector bands; + }; + std::unordered_map m_cascaderecords; + std::vector m_cascadedraws; + std::vector m_cascadecommands; + unsigned int m_cascaderecordframe { 0 }; // multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where // the blocks are bool m_indirectmultidraw { false }; diff --git a/scene/scene.h b/scene/scene.h index f5b8f992..a9278b1e 100644 --- a/scene/scene.h +++ b/scene/scene.h @@ -269,6 +269,13 @@ public: mutable glm::dvec3 recordorigin { 0.0 }; // the records hold locations relative to it mutable float lodrangesquared { 0.f }; // past it no submodel of the model of the bucket is drawn mutable float rangesquared { std::numeric_limits::max() }; // past it no instance of the bucket is drawn: the largest range of its instances, within lodrangesquared + // bounds of the visible instances of the records, for the tests of a whole bucket: a sphere around their locations + // (recordcenter, recordlocationradius), the radius of the sphere which holds the instances whole (negative when none + // of them is visible), and the largest radius of an instance + mutable glm::dvec3 recordcenter { 0.0 }; + mutable float recordlocationradius { 0.f }; + mutable float recordradius { -1.f }; + mutable float recordradiusmax { 0.f }; mutable unsigned int recordsversion { 0 }; // place of the records in the record buffer of the gpu culling: first record, room, and the generation of // the buffer it is good for. gpustale: the records changed since they were sent