mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-18 06:49:18 +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:
@@ -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() );
|
||||
|
||||
@@ -284,6 +284,11 @@ class opengl33_renderer : public gfx_renderer {
|
||||
// instanced_drawcall in draw_stats and contributes Instances.size() to the
|
||||
// instances counter. Instances are still individually frustum/distance culled.
|
||||
void Render_Instanced( TModel3d *Model, std::vector<TAnimModel *> const &Instances );
|
||||
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
|
||||
// less the ones the copies of their bounds show to be out of sight in the current render pass
|
||||
void Queue_Instances( scene::basic_cell const *Cell );
|
||||
// makes the copies of the bounds of the instances held by a bucket of a scene cell
|
||||
void Update_Instance_Bounds( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket );
|
||||
bool Render(TDynamicObject *Dynamic);
|
||||
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
|
||||
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
|
||||
@@ -383,6 +388,9 @@ class opengl33_renderer : public gfx_renderer {
|
||||
// of once per cell -- collapsing many tiny instanced draws into a few large
|
||||
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
|
||||
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
|
||||
// copies of instance bounds kept by the buckets of scene cells are good as long as they carry this number.
|
||||
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place
|
||||
unsigned int m_instanceboundsversion { 1 };
|
||||
renderpass_config m_colorpass; // parametrs of most recent color pass
|
||||
std::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
|
||||
std::vector<TSubModel const *> m_pickcontrolsitems;
|
||||
|
||||
@@ -379,7 +379,9 @@ basic_cell::insert( TAnimModel *Instance ) {
|
||||
if( mat != nullptr ) {
|
||||
for( int i = 0; i < 5; ++i ) { key.skins[i] = mat->replacable_skins[i]; }
|
||||
}
|
||||
m_instancebuckets_opaque[ key ].emplace_back( Instance );
|
||||
auto &bucket { m_instancebuckets_opaque[ key ] };
|
||||
bucket.instances.emplace_back( Instance );
|
||||
bucket.bounds.clear();
|
||||
}
|
||||
}
|
||||
// re-calculate cell bounding area, in case model extends outside the cell's boundaries
|
||||
@@ -478,10 +480,12 @@ basic_cell::erase( TAnimModel *Instance ) {
|
||||
// NOTE: searched by pointer rather than by key: the instance may have lost its instanceable
|
||||
// status or changed its skins since it was inserted, which would leave a dangling pointer behind
|
||||
for( auto bucket = m_instancebuckets_opaque.begin(); bucket != m_instancebuckets_opaque.end(); ) {
|
||||
bucket->second.erase(
|
||||
std::remove( std::begin( bucket->second ), std::end( bucket->second ), Instance ),
|
||||
std::end( bucket->second ) );
|
||||
if( bucket->second.empty() ) {
|
||||
auto &instances { bucket->second.instances };
|
||||
instances.erase(
|
||||
std::remove( std::begin( instances ), std::end( instances ), Instance ),
|
||||
std::end( instances ) );
|
||||
bucket->second.bounds.clear();
|
||||
if( instances.empty() ) {
|
||||
bucket = m_instancebuckets_opaque.erase( bucket );
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -239,6 +239,21 @@ public:
|
||||
}
|
||||
};
|
||||
using instance_bucket_map = std::unordered_map< instance_bucket_key, std::vector<TAnimModel *>, instance_bucket_key_hash >;
|
||||
// what it takes to tell that an instance is out of sight, copied from the instance.
|
||||
// a cell can hold thousands of instances, each of them large and placed wherever the memory was free, and a render
|
||||
// pass rejects most of them. the renderer makes the copies and goes by them, to fetch only the instances it may draw
|
||||
struct instance_bounds {
|
||||
glm::vec3 center { 0.f }; // location of the instance, in the precision the frustum test takes it
|
||||
float radius { 0.f };
|
||||
float rangesquaredmax { 0.f }; // past it the instance isn't drawn: by its own range, or the lod stages of its model
|
||||
};
|
||||
struct instance_bucket {
|
||||
std::vector<TAnimModel *> instances;
|
||||
// caches owned by the renderer; emptied whenever the content of the bucket changes
|
||||
mutable std::vector<instance_bounds> bounds; // copies for the instances, in the same order
|
||||
mutable unsigned int boundsversion { 0 };
|
||||
};
|
||||
using instance_bucketdata_map = std::unordered_map< instance_bucket_key, instance_bucket, instance_bucket_key_hash >;
|
||||
using sound_sequence = std::vector<sound_source *>;
|
||||
using eventlauncher_sequence = std::vector<TEventLauncher *>;
|
||||
using memorycell_sequence = std::vector<TMemCell *>;
|
||||
@@ -259,7 +274,7 @@ public:
|
||||
// batched instance buckets keyed by shared TModel3d*; populated alongside
|
||||
// m_instancesopaque for nodes whose TAnimModel::m_instanceable == true.
|
||||
// The renderer uses these to amortise per-model state setup across many instances.
|
||||
instance_bucket_map m_instancebuckets_opaque;
|
||||
instance_bucketdata_map m_instancebuckets_opaque;
|
||||
traction_sequence m_traction;
|
||||
sound_sequence m_sounds;
|
||||
eventlauncher_sequence m_eventlaunchers;
|
||||
|
||||
Reference in New Issue
Block a user