17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-09 00:17:39 +01:00

Instancing: skip the buckets of a cell lying wholly beyond the range of their instances (#225)

* perf(instancing): skip a cell's buckets lying wholly beyond the range of their instances

* perf(instancing): go only through the instances drawn one by one in the per-node pass

* perf(instancing): leave out of the color pass the cells holding only instances too far to be drawn

* refactor(instancing): keep the caches of the instances of a cell without mutable fields, set by the renderer when the cell is queued
This commit is contained in:
pawelw
2026-11-07 12:35:55 +01:00
committed by GitHub
parent 3a29a165a9
commit 55ba7a9f41
6 changed files with 119 additions and 2 deletions

View File

@@ -2666,6 +2666,12 @@ void opengl33_renderer::Render(section_sequence::iterator First, section_sequenc
{
if ((true == cell.m_active) && (m_renderpass.pass_camera.visible(cell.m_area)))
{
// a cell with nothing but instances too far to be drawn is left out of the color pass altogether
if( ( true == Prepare_Instances( cell ) )
&& ( m_renderpass.draw_mode == rendermode::color )
&& ( true == Cell_Out_Of_Range( cell ) ) ) {
continue;
}
// store visible cells with content as well as their current distance, for sorting later
m_cellqueue.emplace_back(glm::length2(m_renderpass.pass_camera.position() - cell.m_area.center), &cell);
}
@@ -2680,6 +2686,7 @@ void opengl33_renderer::Render(section_sequence::iterator First, section_sequenc
{
if ((true == cell.m_active) && (m_renderpass.pass_camera.visible(cell.m_area)))
{
Prepare_Instances( cell );
// store visible cells with content as well as their current distance, for sorting later
m_cellqueue.emplace_back(glm::length2(m_renderpass.pass_camera.position() - cell.m_area.center), &cell);
}
@@ -2871,8 +2878,10 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
Queue_Instances( cell );
}
}
// remaining (non-instanceable) opaque instance nodes go through the per-node path
for (auto *instance : cell->m_instancesopaque)
// remaining (non-instanceable) opaque instance nodes go through the per-node path. the list of them kept for
// the cell spares going through every instance of it only to skip the instanceable ones - with many of these
// (vegetation) that took more time than drawing everything else
for (auto *instance : ( Instance_Cache_Valid( *cell ) ? cell->m_instancecache.single : cell->m_instancesopaque ))
{
if( instance->m_instanceable ) { continue; } // already handled via bucket
Render(instance);
@@ -3178,6 +3187,11 @@ void opengl33_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 ) ) };
// up close the rounding of the copies 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 bounding sphere of the cell, which holds the locations of all its instances. no instance
// of the cell is closer, so where this distance fails the range test below for the largest range in a bucket,
// it fails it for each instance of that bucket, and the bucket can be skipped without going through them
auto const cellnearestdistance { std::max( 0.0, glm::length( Cell->m_area.center - cameraposition ) - Cell->m_area.radius ) };
auto const cellnearestdistancesquared { cellnearestdistance * cellnearestdistance };
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
auto const &instances { bucket.second.instances };
@@ -3190,6 +3204,11 @@ void opengl33_renderer::Queue_Instances( scene::basic_cell const *Cell )
|| ( bucket.second.bounds.size() != instances.size() ) ) {
Update_Instance_Bounds( bucket.first.pModel, bucket.second );
}
else if( ( bucket.second.rangeversion == m_instanceboundsversion )
&& ( cellnearestdistancesquared > distancetested )
&& ( cellnearestdistancesquared * distancescale + distanceoffset >= slack * bucket.second.rangesquaredmax ) ) {
continue;
}
auto const &bounds { bucket.second.bounds };
std::vector<TAnimModel *> *dest { nullptr }; // looked up when there's something to put in it
for( std::size_t idx = 0; idx < bounds.size(); ++idx ) {
@@ -3210,6 +3229,76 @@ void opengl33_renderer::Queue_Instances( scene::basic_cell const *Cell )
}
}
// brings up to date what the renderer keeps of the instances of the cell: the copies of the bounds of the instances
// and the largest range in each bucket, the largest range in the cell, and the list of its instances drawn one by one.
// rebuilt after the content of the cell changed, or any instance lost its instanceable status (an event added an
// animation container, or changed a skin). returns: true if the cache can be used; it can't in the editor, which
// moves and resizes the instances in place, as with the copies of their bounds
bool opengl33_renderer::Prepare_Instances( scene::basic_cell &Cell )
{
if( EditorModeFlag ) { return false; }
if( Instance_Cache_Valid( Cell ) ) { return true; }
auto &cache { Cell.m_instancecache };
cache.rangesquaredmax = 0.f;
for( auto &[ key, bucket ] : Cell.m_instancebuckets_opaque ) {
if( ( bucket.boundsversion != m_instanceboundsversion )
|| ( bucket.bounds.size() != bucket.instances.size() ) ) {
Update_Instance_Bounds( key.pModel, bucket );
}
auto rangesquaredmax { 0.f };
for( auto const &instancebounds : bucket.bounds ) {
rangesquaredmax = std::max( rangesquaredmax, instancebounds.rangesquaredmax );
}
bucket.rangesquaredmax = rangesquaredmax;
bucket.rangeversion = m_instanceboundsversion;
cache.rangesquaredmax = std::max( cache.rangesquaredmax, rangesquaredmax );
}
cache.single.clear();
for( auto *instance : Cell.m_instancesopaque ) {
if( false == instance->m_instanceable ) {
cache.single.emplace_back( instance );
}
}
cache.instancedonly =
( false == Cell.m_instancebuckets_opaque.empty() )
&& Cell.m_paths.empty()
&& Cell.m_shapesopaque.empty()
&& Cell.m_shapestranslucent.empty()
&& Cell.m_lines.empty()
&& Cell.m_traction.empty()
&& Cell.m_instancetranslucent.empty()
&& cache.single.empty();
cache.boundsversion = m_instanceboundsversion;
cache.revoked = TAnimModel::s_instanceable_revoked;
return true;
}
bool opengl33_renderer::Instance_Cache_Valid( scene::basic_cell const &Cell ) const
{
return ( false == EditorModeFlag )
&& ( Cell.m_instancecache.boundsversion == m_instanceboundsversion )
&& ( Cell.m_instancecache.revoked == TAnimModel::s_instanceable_revoked );
}
// true if nothing of the cell would be drawn by the color pass: it holds only instances drawn with the instanced path,
// and the nearest point of the cell is past the largest range of them. the test is the one made for each bucket in
// Queue_Instances(), done before the cell is put in the cell queue: a scenery with many small instances of a short
// range has them in nearly every cell up to the draw range, and queueing, sorting and visiting these cells only
// to skip all their buckets took a good part of the frame
bool opengl33_renderer::Cell_Out_Of_Range( scene::basic_cell const &Cell ) const
{
if( ( false == Instance_Cache_Valid( Cell ) ) || ( false == Cell.m_instancecache.instancedonly ) ) { return false; }
// same measures as in Queue_Instances() for the color pass
auto const distancescale { 1.0 / ( static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.fDistanceFactor ) ) };
auto const slack { 1.001 };
auto const distancetested { sq( 64.0 ) };
auto const cellnearestdistance { std::max( 0.0, glm::length( Cell.m_area.center - m_renderpass.pass_camera.position() ) - Cell.m_area.radius ) };
auto const cellnearestdistancesquared { cellnearestdistance * cellnearestdistance };
return ( cellnearestdistancesquared > distancetested )
&& ( cellnearestdistancesquared * distancescale >= slack * Cell.m_instancecache.rangesquaredmax );
}
// makes the copies of the bounds of the instances held by a bucket of a scene cell
void opengl33_renderer::Update_Instance_Bounds( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket )
{