mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-05 20:39:09 +01:00
perf(gl46): leave out instance buckets before the culling, lod bounds once per model, shared walks of the shadow cascades
This commit is contained in:
@@ -1616,6 +1616,7 @@ void TModel3d::AddTo(TSubModel *tmp, TSubModel *SubModel)
|
|||||||
}
|
}
|
||||||
++iSubModelsCount; // teraz jest o 1 submodel więcej
|
++iSubModelsCount; // teraz jest o 1 submodel więcej
|
||||||
iFlags |= 0x0200; // submodele są oddzielne
|
iFlags |= 0x0200; // submodele są oddzielne
|
||||||
|
m_lodboundsfound = false; // the new submodel may have its own range
|
||||||
};
|
};
|
||||||
|
|
||||||
TSubModel *TModel3d::GetFromName(std::string const &Name) const
|
TSubModel *TModel3d::GetFromName(std::string const &Name) const
|
||||||
@@ -1643,6 +1644,33 @@ TModel3d::instance_submodels const &TModel3d::instance_parts()
|
|||||||
return m_instancesubmodels;
|
return m_instancesubmodels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns the distinct bounds of the visibility ranges of the submodels, sorted, finding them on first use
|
||||||
|
std::vector<float> const &TModel3d::lod_bounds()
|
||||||
|
{
|
||||||
|
if (false == m_lodboundsfound)
|
||||||
|
{
|
||||||
|
m_lodbounds.clear();
|
||||||
|
std::vector<TSubModel const *> 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<float>::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
|
// locates particle source submodels and stores them on internal list
|
||||||
nameoffset_sequence const &TModel3d::find_smoke_sources()
|
nameoffset_sequence const &TModel3d::find_smoke_sources()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -292,6 +292,8 @@ private:
|
|||||||
nameoffset_sequence m_smokesources; // list of particle sources defined in the model
|
nameoffset_sequence m_smokesources; // list of particle sources defined in the model
|
||||||
instance_submodels m_instancesubmodels; // filled on first use, see instance_parts()
|
instance_submodels m_instancesubmodels; // filled on first use, see instance_parts()
|
||||||
bool m_instancesubmodelslocated { false };
|
bool m_instancesubmodelslocated { false };
|
||||||
|
std::vector<float> m_lodbounds; // filled on first use, see lod_bounds()
|
||||||
|
bool m_lodboundsfound { false };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TModel3d() = default;
|
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
|
// 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
|
// submodel tree, so it's done once for the model instead of once for each of its instances
|
||||||
instance_submodels const & instance_parts();
|
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<float> const & lod_bounds();
|
||||||
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
|
TSubModel * AddToNamed(const char *Name, TSubModel *SubModel);
|
||||||
nameoffset_sequence const & find_smoke_sources();
|
nameoffset_sequence const & find_smoke_sources();
|
||||||
void AddTo(TSubModel *tmp, TSubModel *SubModel);
|
void AddTo(TSubModel *tmp, TSubModel *SubModel);
|
||||||
|
|||||||
@@ -125,26 +125,11 @@ void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &K
|
|||||||
auto lookup{groupmap.find(Key)};
|
auto lookup{groupmap.find(Key)};
|
||||||
if (lookup == groupmap.end())
|
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<std::uint32_t>(m_lodlist.size())};
|
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
|
||||||
m_pending.clear();
|
auto const &lodbounds{Key.pModel->lod_bounds()};
|
||||||
m_pending.push_back(Key.pModel->Root);
|
m_lodlist.insert(m_lodlist.end(), lodbounds.begin(), lodbounds.end());
|
||||||
while (!m_pending.empty())
|
auto const lodcount{static_cast<std::uint32_t>(lodbounds.size())};
|
||||||
{
|
|
||||||
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<float>::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<std::uint32_t>(m_lodlist.size()) - lodfirst};
|
|
||||||
|
|
||||||
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1, distancemin, distancemax};
|
||||||
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
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
|
// 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";
|
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
|
||||||
if (Global.gfx_gpucull_bucketrange)
|
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" : "");
|
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
|
||||||
if (Global.gfx_gpucull == 1)
|
if (Global.gfx_gpucull == 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,14 +107,15 @@ class opengl46_instance_cull
|
|||||||
return m_inpass && m_setup.drawing;
|
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.
|
// 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);
|
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
|
// 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
|
// 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
|
// 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())
|
// 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);
|
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
|
// for the panel
|
||||||
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
|
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
|
// 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<float> m_lodlist;
|
std::vector<float> m_lodlist;
|
||||||
group_map m_groupmap;
|
group_map m_groupmap;
|
||||||
group_map m_sleepergroupmap; // groups of the sleepers
|
group_map m_sleepergroupmap; // groups of the sleepers
|
||||||
std::vector<TSubModel const *> m_pending; // walk of the submodel tree
|
|
||||||
std::uint32_t m_candidates{0};
|
std::uint32_t m_candidates{0};
|
||||||
std::uint32_t m_entries{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
|
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
|
||||||
|
|||||||
@@ -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
|
// and so may the tracks the sleeper records were made for, and the models drawn on their own
|
||||||
m_sleeperrecords.clear();
|
m_sleeperrecords.clear();
|
||||||
m_modelrecords.clear();
|
m_modelrecords.clear();
|
||||||
|
m_cascaderecords.clear();
|
||||||
}
|
}
|
||||||
// generate new frame
|
// generate new frame
|
||||||
opengl_texture::reset_unit_cache();
|
opengl_texture::reset_unit_cache();
|
||||||
@@ -801,7 +802,21 @@ void opengl46_renderer::SwapBuffers()
|
|||||||
Timer::subsystem.gfx_swap.stop();
|
Timer::subsystem.gfx_swap.stop();
|
||||||
|
|
||||||
m_debugtimestext.clear();
|
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"
|
"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";
|
+ " color: " + to_string( Timer::subsystem.gfx_color.average(), 2 ) + " ms (" + std::to_string( m_cellqueue.size() ) + " sectors)\n";
|
||||||
if( Global.gfx_shadowmap_enabled ) {
|
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 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 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("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);
|
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
|
||||||
if (Global.gfx_contactshadows > 0)
|
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<double>( Global.reflectiontune.range_instances ) ) : sq( static_cast<double>( m_renderpass.draw_range ) + 250.0 ) ) };
|
auto const distancelimit { slack * ( reflections ? sq( static_cast<double>( Global.reflectiontune.range_instances ) ) : sq( static_cast<double>( 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
|
// 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 ) };
|
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
|
// a bucket whose visible instances all fail the tests of the culling by the bounds of their records (Bucket_Culled()) is left
|
||||||
// ends before it has none of its instances drawn, and is left out of the pass before the culling. basic_cell::enclose_area
|
// out of the pass before the culling. the bounds are made with the records, which the editor doesn't let go by
|
||||||
// keeps the locations of all instances of the cell within the radius of its area
|
auto const buckettests { Global.gfx_gpucull_bucketrange && userecords };
|
||||||
auto const cellnearest { std::max( 0.0, glm::length( Cell->m_area.center - cameraposition ) - static_cast<double>( Cell->m_area.radius ) ) };
|
|
||||||
auto const cellscaledsquared { cellnearest * cellnearest * distancescale + distanceoffset };
|
|
||||||
auto const bucketrange { Global.gfx_gpucull_bucketrange && userecords && ( cellnearest * cellnearest > distancetested ) };
|
|
||||||
|
|
||||||
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
|
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
|
||||||
auto const &instancebucket { bucket.second };
|
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() ) ) {
|
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
|
||||||
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
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 ) {
|
if( m_instancecullpass ) {
|
||||||
m_passcull->skip_bucket( instancebucket );
|
m_passcull->skip_bucket( instancebucket );
|
||||||
}
|
}
|
||||||
@@ -4038,9 +4050,10 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|
|||||||
}
|
}
|
||||||
if( m_instancecullpass ) {
|
if( m_instancecullpass ) {
|
||||||
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
|
// 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 ) ) {
|
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
|
// and when the instances are drawn from its result, the cpu has nothing more to do with them
|
||||||
if( m_passcull->drawing() ) { continue; }
|
if( m_passcull->drawing() ) { continue; }
|
||||||
@@ -4113,6 +4126,12 @@ bool opengl46_renderer::Queue_Model( TAnimModel *Instance, bool const Inrange )
|
|||||||
modelkey.instance = nullptr;
|
modelkey.instance = nullptr;
|
||||||
if( false == Instances_Cast_Shadow( modelkey, bucket ) ) { return drawing; }
|
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 );
|
m_passcull->add_bucket( records.key, bucket, records.area );
|
||||||
if( false == drawing ) {
|
if( false == drawing ) {
|
||||||
// the cpu draws it too, and tells what it drew for the comparison
|
// 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.records.emplace_back( record );
|
||||||
}
|
}
|
||||||
Bucket.rangesquared = std::min( rangesquared, lodrangesquared );
|
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<double>::max() };
|
||||||
|
glm::dvec3 high { std::numeric_limits<double>::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<float>( locationradius );
|
||||||
|
Bucket.recordradius = static_cast<float>( radius );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Bucket.recordcenter = Origin;
|
||||||
|
}
|
||||||
Bucket.recordsversion = m_instancerecordsversion;
|
Bucket.recordsversion = m_instancerecordsversion;
|
||||||
Bucket.gpustale = true;
|
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<double>( Global.ZoomFactor ) };
|
||||||
|
auto const scaledsquared { nearest * nearest * ( reflections ? 1.0 : 1.0 / ( zoom * zoom * static_cast<double>( Global.fDistanceFactor ) ) ) };
|
||||||
|
if( reflections && ( scaledsquared > slack * sq( static_cast<double>( Global.reflectiontune.range_instances ) ) ) ) { return true; }
|
||||||
|
auto const distancesquared { scaledsquared + ( reflections ? sq( static_cast<double>( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.0 ) };
|
||||||
|
if( distancesquared >= slack * Bucket.rangesquared ) { return true; }
|
||||||
|
if( distancesquared > slack * sq( static_cast<double>( 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<double>( 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<float>( 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
|
// 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 )
|
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_indirectdraws.clear();
|
||||||
m_indirectcommands.clear();
|
m_indirectcommands.clear();
|
||||||
auto const &lodbounds { Cull.lod_bounds() };
|
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<std::uint32_t>::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;
|
m_indirectrecording = true;
|
||||||
for( auto const &group : Cull.draw_groups() ) {
|
for( auto const &group : Cull.draw_groups() ) {
|
||||||
auto *model { group.key->pModel };
|
auto *model { group.key->pModel };
|
||||||
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; }
|
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; }
|
||||||
material_handle const *skins { nullptr };
|
material_handle const *skins { nullptr };
|
||||||
auto alpha { 0x30300030 };
|
auto alpha { 0x30300030 };
|
||||||
if( group.sleepers ) {
|
auto prepared { false };
|
||||||
// as Render_Sleepers() sets them up: the skin of the track, if any. like scenery models they pick no texture
|
auto const prepare = [ & ]() {
|
||||||
// variants of their own
|
prepared = true;
|
||||||
if( group.key->skins[ 1 ] != null_handle ) {
|
if( group.sleepers ) {
|
||||||
skins = group.key->skins.data();
|
// 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 {
|
else {
|
||||||
if( group.key->instance != nullptr ) {
|
prepare();
|
||||||
// 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;
|
|
||||||
for( std::uint32_t band = 0; band <= group.lodcount; ++band ) {
|
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
|
// 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 bandmin { band == 0 ? 0.f : lodbounds[ group.lodfirst + band - 1 ] };
|
||||||
auto const bandmax { band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[ group.lodfirst + band ] };
|
auto const bandmax { band == group.lodcount ? std::numeric_limits<float>::max() : lodbounds[ group.lodfirst + band ] };
|
||||||
if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; }
|
if( ( bandmin > group.distancemax ) || ( bandmax <= group.distancemin ) ) { continue; }
|
||||||
m_indirectentry = group.entryfirst + band;
|
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<int>( walk.count );
|
||||||
|
m_renderpass.draw_stats.drawcalls += static_cast<int>( walk.count );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( false == prepared ) {
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
auto const walkfirst { m_indirectdraws.size() };
|
||||||
::glPushMatrix();
|
::glPushMatrix();
|
||||||
::glLoadIdentity();
|
::glLoadIdentity();
|
||||||
model->Root->fSquareDist = bandmin;
|
model->Root->fSquareDist = bandmin;
|
||||||
@@ -4600,6 +4749,13 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
|
|||||||
model->Root->pRoot = model;
|
model->Root->pRoot = model;
|
||||||
Render( model->Root );
|
Render( model->Root );
|
||||||
::glPopMatrix();
|
::glPopMatrix();
|
||||||
|
if( walks != nullptr ) {
|
||||||
|
walks->bands[ band ] = cascade_band {
|
||||||
|
static_cast<std::uint32_t>( m_cascadedraws.size() ),
|
||||||
|
static_cast<std::uint32_t>( 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;
|
m_indirectrecording = false;
|
||||||
|
|||||||
@@ -368,6 +368,10 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
void Queue_Instances( scene::basic_cell const *Cell );
|
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
|
// 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 );
|
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
|
// 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 );
|
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)
|
// 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<indirect_draw> m_indirectdraws;
|
std::vector<indirect_draw> m_indirectdraws;
|
||||||
std::vector<gl::instance_draw_command> m_indirectcommands; // in the order of m_indirectdraws
|
std::vector<gl::instance_draw_command> 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<cascade_band> bands;
|
||||||
|
};
|
||||||
|
std::unordered_map<scene::basic_cell::instance_bucket_key, cascade_record, scene::basic_cell::instance_bucket_key_hash> m_cascaderecords;
|
||||||
|
std::vector<indirect_draw> m_cascadedraws;
|
||||||
|
std::vector<gl::instance_draw_command> 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
|
// multi draws of the recorded draws: whether they were recorded for them (ordered, their model blocks uploaded), and where
|
||||||
// the blocks are
|
// the blocks are
|
||||||
bool m_indirectmultidraw { false };
|
bool m_indirectmultidraw { false };
|
||||||
|
|||||||
@@ -269,6 +269,13 @@ public:
|
|||||||
mutable glm::dvec3 recordorigin { 0.0 }; // the records hold locations relative to it
|
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 lodrangesquared { 0.f }; // past it no submodel of the model of the bucket is drawn
|
||||||
mutable float rangesquared { std::numeric_limits<float>::max() }; // past it no instance of the bucket is drawn: the largest range of its instances, within lodrangesquared
|
mutable float rangesquared { std::numeric_limits<float>::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 };
|
mutable unsigned int recordsversion { 0 };
|
||||||
// place of the records in the record buffer of the gpu culling: first record, room, and the generation of
|
// 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
|
// the buffer it is good for. gpustale: the records changed since they were sent
|
||||||
|
|||||||
Reference in New Issue
Block a user