17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 05:09:20 +02:00

perf(instancing): instances of scene cells culled by copies of their bounds kept with the buckets, before the instances themselves are fetched; instances past the range of the lod stages of their model left out; identity of the instance buffer restored once per bucket instead of after each batch

This commit is contained in:
2026-10-07 16:29:15 +02:00
parent bc99b80191
commit 398fdac6a1
4 changed files with 128 additions and 21 deletions

View File

@@ -577,6 +577,10 @@ bool opengl33_renderer::Render()
}
// generate new frame
opengl_texture::reset_unit_cache();
if( EditorModeFlag ) {
// the editor moves and resizes model instances in place, which leaves the copies of their bounds out of date
++m_instanceboundsversion;
}
m_renderpass.draw_mode = rendermode::none; // force setup anew
m_renderpass.draw_stats = debug_stats();
@@ -2765,10 +2769,7 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
/ ( static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.ZoomFactor ) )
/ static_cast<double>( Global.fDistanceFactor ) };
if( cellnearestdistancesquared <= sq( static_cast<double>( m_renderpass.draw_range ) + 250.0 ) ) {
for( auto const &bucket : cell->m_instancebuckets_opaque ) {
auto &dest = m_frame_instance_buckets[ bucket.first ];
dest.insert( dest.end(), bucket.second.begin(), bucket.second.end() );
}
Queue_Instances( cell );
}
}
// remaining (non-instanceable) opaque instance nodes go through the per-node path
@@ -2798,10 +2799,7 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
&& ( cellnearestdistance <= Global.reflectiontune.range_instances ) ) {
// opaque parts of instanced models -- accumulate into the
// frame-level map; flushed once per unique model after the loop.
for( auto const &bucket : cell->m_instancebuckets_opaque ) {
auto &dest = m_frame_instance_buckets[ bucket.first ];
dest.insert( dest.end(), bucket.second.begin(), bucket.second.end() );
}
Queue_Instances( cell );
for( auto *instance : cell->m_instancesopaque ) {
if( instance->m_instanceable ) { continue; }
Render( instance );
@@ -3054,6 +3052,88 @@ void opengl33_renderer::Render(TAnimModel *Instance)
}
}
// adds the bucketed instances of a scene cell to the frame-level buckets, for Render_Instanced() to draw.
// Render_Instanced() culls each instance it's given. the tests it makes are repeated here on the copies of instance
// bounds kept by the buckets, with some slack for the single precision of the copies, and the instances which fail are
// left out: the instances themselves are fetched only when there's a chance for them to be drawn
void opengl33_renderer::Queue_Instances( scene::basic_cell const *Cell )
{
// the editor moves and resizes the instances in place, the copies can't be relied on while it's active
auto const usebounds { false == EditorModeFlag };
auto const reflections { m_renderpass.draw_mode == rendermode::reflections };
// 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 &distancecamera { m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera : m_renderpass.pass_camera };
glm::dvec3 const cameraposition { distancecamera.position() };
auto const distancescale { reflections ? 1.0 : 1.0 / ( static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.fDistanceFactor ) ) };
auto const distanceoffset { reflections ? sq( static_cast<double>( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.0 };
auto const slack { 1.001 };
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 ) };
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
auto const &instances { bucket.second.instances };
if( false == usebounds ) {
auto &dest { m_frame_instance_buckets[ bucket.first ] };
dest.insert( dest.end(), instances.begin(), instances.end() );
continue;
}
if( ( bucket.second.boundsversion != m_instanceboundsversion )
|| ( bucket.second.bounds.size() != instances.size() ) ) {
Update_Instance_Bounds( bucket.first.pModel, bucket.second );
}
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 ) {
auto const &instancebounds { bounds[ idx ] };
auto const center { glm::dvec3{ instancebounds.center } };
auto const distancesquared { glm::length2( center - cameraposition ) };
if( distancesquared > distancetested ) {
auto const scaleddistancesquared { distancesquared * distancescale };
if( scaleddistancesquared > distancelimit ) { continue; }
if( scaleddistancesquared + distanceoffset >= slack * instancebounds.rangesquaredmax ) { continue; }
}
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ center, instancebounds.radius } ) ) { continue; }
if( dest == nullptr ) {
dest = &m_frame_instance_buckets[ bucket.first ];
}
dest->emplace_back( instances[ idx ] );
}
}
}
// 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 )
{
// a submodel is drawn only closer than its fSquareMaxDist, see Render( TSubModel * ). past the largest of these
// there's nothing of the model left to draw, which makes it another limit of the range of its instances
auto lodrangesquared { ( Model != nullptr && Model->Root != nullptr ) ? 0.f : std::numeric_limits<float>::max() };
m_instance_lodpending.clear();
m_instance_lodpending.push_back( Model != nullptr ? Model->Root : nullptr );
while( false == m_instance_lodpending.empty() ) {
auto const *submodel = m_instance_lodpending.back();
m_instance_lodpending.pop_back();
if( submodel == nullptr ) { continue; }
lodrangesquared = std::max( lodrangesquared, submodel->fSquareMaxDist );
m_instance_lodpending.push_back( submodel->Child );
m_instance_lodpending.push_back( submodel->Next );
}
Bucket.bounds.clear();
Bucket.bounds.reserve( Bucket.instances.size() );
for( auto const *instance : Bucket.instances ) {
scene::basic_cell::instance_bounds instancebounds;
if( instance != nullptr ) {
instancebounds.center = glm::vec3{ instance->m_area.center };
instancebounds.radius = instance->m_area.radius;
instancebounds.rangesquaredmax = std::min( lodrangesquared, static_cast<float>( std::min( instance->m_rangesquaredmax, static_cast<double>( std::numeric_limits<float>::max() ) ) ) );
}
Bucket.bounds.emplace_back( instancebounds );
}
Bucket.boundsversion = m_instanceboundsversion;
}
// True GPU-instanced render path for a group of TAnimModel instances sharing the same
// TModel3d. The submodel tree is walked ONCE per batch; at every submodel that draws
// geometry we issue a single glDrawElementsInstancedBaseVertex(N) covering all visible
@@ -3239,17 +3319,17 @@ void opengl33_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
::glPopMatrix();
// 2d. Restore instance_modelview[0] to identity so subsequent
// non-instanced draws continue to compute identity * modelview.
{
glm::mat4 const identity( 1.0f );
instance_ubo->update( reinterpret_cast<uint8_t const *>( &identity ), 0, sizeof( identity ) );
}
offset_idx += this_batch;
++m_renderpass.draw_stats.instanced_drawcalls;
}
}
// 3. Restore instance_modelview[0] to identity so subsequent
// non-instanced draws continue to compute identity * modelview.
// done once the last batch is out, as everything drawn up to that point is instanced
{
glm::mat4 const identity( 1.0f );
instance_ubo->update( reinterpret_cast<uint8_t const *>( &identity ), 0, sizeof( identity ) );
}
m_renderpass.draw_stats.instances += static_cast<int>( m_instance_survivors.size() );
m_renderpass.draw_stats.models += static_cast<int>( m_instance_survivors.size() );