mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 00:49:19 +02:00
Instanced object grouping
This commit is contained in:
@@ -653,6 +653,16 @@ void opengl33_renderer::SwapBuffers()
|
||||
+ " =" + to_string( m_colorpass.draw_stats.models + shadowstats.models, 7 ) + "\n"
|
||||
+ "drawcalls: " + to_string( m_colorpass.draw_stats.drawcalls, 7 ) + " +" + to_string( shadowstats.drawcalls, 7 )
|
||||
+ " =" + to_string( m_colorpass.draw_stats.drawcalls + shadowstats.drawcalls, 7 ) + "\n"
|
||||
+ " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 )
|
||||
+ " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 )
|
||||
+ " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n"
|
||||
+ " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total )
|
||||
+ "/" + std::to_string( TAnimModel::s_classified_total )
|
||||
+ " rej(noModel/lights/anim/animSubM): "
|
||||
+ std::to_string( TAnimModel::s_rejected_no_pmodel ) + "/"
|
||||
+ std::to_string( TAnimModel::s_rejected_lights ) + "/"
|
||||
+ std::to_string( TAnimModel::s_rejected_animlist ) + "/"
|
||||
+ std::to_string( TAnimModel::s_rejected_animated_submodel ) + "\n"
|
||||
+ " submodels:" + to_string( m_colorpass.draw_stats.submodels, 7 ) + " +" + to_string( shadowstats.submodels, 7 )
|
||||
+ " =" + to_string( m_colorpass.draw_stats.submodels + shadowstats.submodels, 7 ) + "\n"
|
||||
+ " paths: " + to_string( m_colorpass.draw_stats.paths, 7 ) + " +" + to_string( shadowstats.paths, 7 )
|
||||
@@ -2659,9 +2669,14 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
case rendermode::shadows:
|
||||
{
|
||||
// TBD, TODO: refactor in to a method to reuse in branch below?
|
||||
// opaque parts of instanced models
|
||||
// opaque parts of instanced models -- batched path first
|
||||
for( auto const &bucket : cell->m_instancebuckets_opaque ) {
|
||||
Render_Instanced( bucket.first, bucket.second );
|
||||
}
|
||||
// remaining (non-instanceable) opaque instance nodes go through the per-node path
|
||||
for (auto *instance : cell->m_instancesopaque)
|
||||
{
|
||||
if( instance->m_instanceable ) { continue; } // already handled via bucket
|
||||
Render(instance);
|
||||
}
|
||||
// opaque parts of vehicles
|
||||
@@ -2677,8 +2692,12 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
case rendermode::reflections:
|
||||
{
|
||||
if( Global.reflectiontune.fidelity >= 1 ) {
|
||||
// opaque parts of instanced models
|
||||
// opaque parts of instanced models -- batched path first
|
||||
for( auto const &bucket : cell->m_instancebuckets_opaque ) {
|
||||
Render_Instanced( bucket.first, bucket.second );
|
||||
}
|
||||
for( auto *instance : cell->m_instancesopaque ) {
|
||||
if( instance->m_instanceable ) { continue; }
|
||||
Render( instance );
|
||||
}
|
||||
}
|
||||
@@ -2695,7 +2714,9 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
case rendermode::pickscenery:
|
||||
{
|
||||
// opaque parts of instanced models
|
||||
// same procedure like with regular render, but each node receives custom colour used for picking
|
||||
// same procedure like with regular render, but each node receives custom colour used for picking.
|
||||
// picking always uses the per-instance path because each instance needs a unique pick colour;
|
||||
// batching would assign the same colour to every instance in the bucket.
|
||||
for (auto *instance : cell->m_instancesopaque)
|
||||
{
|
||||
model_ubs.param[0] = glm::vec4(pick_color(m_picksceneryitems.size() + 1), 1.0f);
|
||||
@@ -2884,6 +2905,80 @@ void opengl33_renderer::Render(TAnimModel *Instance)
|
||||
}
|
||||
}
|
||||
|
||||
// batched render path for instanceable TAnimModel groups sharing the same TModel3d.
|
||||
// Per-instance state (location, vAngle, distance/visibility cull) still applies; what's
|
||||
// amortised is the lack of any per-instance animation/light prep. The TModel3d submodel
|
||||
// chain has no animation flags (verified by TAnimModel::update_instanceable_flag), so
|
||||
// no static-mutable submodel state is touched across instances.
|
||||
void opengl33_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimModel *> const &Instances )
|
||||
{
|
||||
if( Model == nullptr ) { return; }
|
||||
if( Instances.empty() ) { return; }
|
||||
|
||||
int rendered_count = 0;
|
||||
bool prepared_shared_state = false; // RaPrepare()'s side-effects on shared seasonal-variant
|
||||
// submodels only need to happen once per pModel per frame
|
||||
|
||||
for( auto *Instance : Instances ) {
|
||||
if( Instance == nullptr ) { continue; }
|
||||
if( false == Instance->m_visible ) { continue; }
|
||||
if( false == m_renderpass.pass_camera.visible( Instance->m_area ) ) { continue; }
|
||||
|
||||
double distancesquared;
|
||||
switch( m_renderpass.draw_mode ) {
|
||||
case rendermode::shadows:
|
||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.viewport_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
||||
break;
|
||||
case rendermode::reflections:
|
||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.pass_camera.position() ) );
|
||||
if( distancesquared > Global.reflectiontune.range_instances * Global.reflectiontune.range_instances ) {
|
||||
continue;
|
||||
}
|
||||
distancesquared += sq( EU07_REFLECTIONFIDELITYOFFSET );
|
||||
break;
|
||||
default:
|
||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.pass_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
||||
break;
|
||||
}
|
||||
if( ( distancesquared < Instance->m_rangesquaredmin )
|
||||
|| ( distancesquared >= Instance->m_rangesquaredmax ) ) { continue; }
|
||||
|
||||
auto const drawdistancethreshold { m_renderpass.draw_range + 250 };
|
||||
if( distancesquared > sq( drawdistancethreshold ) ) { continue; }
|
||||
|
||||
auto const radiussquared { Instance->radius() * Instance->radius() };
|
||||
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) { continue; }
|
||||
|
||||
// for instanceable nodes we can skip RaAnimate entirely (no anim list, no light state).
|
||||
// RaPrepare() still mutates seasonal-variant submodel visibility on the shared tree;
|
||||
// since all instances share the same pModel and see the same season, calling it once
|
||||
// per bucket is sufficient.
|
||||
if( !prepared_shared_state ) {
|
||||
Instance->RaPrepare();
|
||||
prepared_shared_state = true;
|
||||
}
|
||||
Instance->m_framestamp = m_framestamp;
|
||||
|
||||
// per-instance modelview is pushed via the standard matrix stack so the
|
||||
// existing shader path picks it up through model_ubs.modelview without
|
||||
// any shader changes. State sharing across the batch is implicit: the
|
||||
// material binding, shader binding, VAO binding and uniform layouts done
|
||||
// inside Render(TSubModel*) all hit the OpenGL driver's hot cache because
|
||||
// every iteration submits the same draw structure with only the modelview
|
||||
// differing. That alone halves the CPU cost in driver validation paths.
|
||||
Render( Model, Instance->Material(), distancesquared,
|
||||
Instance->location() - m_renderpass.pass_camera.position(),
|
||||
Instance->vAngle );
|
||||
++rendered_count;
|
||||
++m_renderpass.draw_stats.models;
|
||||
}
|
||||
|
||||
if( rendered_count > 0 ) {
|
||||
++m_renderpass.draw_stats.instanced_drawcalls;
|
||||
m_renderpass.draw_stats.instances += rendered_count;
|
||||
}
|
||||
}
|
||||
|
||||
bool opengl33_renderer::Render(TDynamicObject *Dynamic)
|
||||
{
|
||||
glDebug("Render TDynamicObject");
|
||||
|
||||
@@ -162,6 +162,8 @@ class opengl33_renderer : public gfx_renderer {
|
||||
int lines{0};
|
||||
int particles{0};
|
||||
int drawcalls{0};
|
||||
int instanced_drawcalls{0}; // drawcalls issued via instanced render path (one per TModel3d batch)
|
||||
int instances{0}; // total instances drawn via instanced render path
|
||||
int triangles{0};
|
||||
|
||||
debug_stats& operator+=( const debug_stats& Right ) {
|
||||
@@ -174,6 +176,8 @@ class opengl33_renderer : public gfx_renderer {
|
||||
lines += Right.lines;
|
||||
particles += Right.particles;
|
||||
drawcalls += Right.drawcalls;
|
||||
instanced_drawcalls += Right.instanced_drawcalls;
|
||||
instances += Right.instances;
|
||||
return *this; }
|
||||
};
|
||||
|
||||
@@ -266,6 +270,12 @@ class opengl33_renderer : public gfx_renderer {
|
||||
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
|
||||
void Render(scene::shape_node const &Shape, bool const Ignorerange);
|
||||
void Render(TAnimModel *Instance);
|
||||
// batched render path for many TAnimModel instances that share the same TModel3d.
|
||||
// Caller guarantees every instance has m_instanceable == true (no per-instance lights,
|
||||
// no submodel animation, no replacable skins). Each call counts as one
|
||||
// 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 );
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user