17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-03 13:10:23 +01:00

Cull and draw the sleepers of tracks on the GPU with the instances

This commit is contained in:
2026-10-18 03:18:26 +02:00
parent f4b516eeb8
commit bc4df5567f
10 changed files with 293 additions and 48 deletions

View File

@@ -76,26 +76,44 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
m_drawgroups.clear();
m_lodlist.clear();
m_groupmap.clear();
m_sleepergroupmap.clear();
m_cpucounts.clear();
m_candidates = 0;
m_entries = 0;
m_sleepertracks = 0;
m_sleepers = 0;
}
void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, false);
}
void opengl46_instance_cull::add_sleepers(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area)
{
add(Key, Bucket, Area, true);
}
void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers)
{
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
return;
// range of the squared distances the instances of the bucket can be at, scaled as in the culling, with some slack for
// its single precision: the draws are made only for the lod bands within it
auto const centerdistance{glm::length(Area.center - m_setup.distancecamera)};
// its single precision: the draws are made only for the lod bands within it. sleepers go by the plain distance from the
// camera of the pass
auto const &distancecamera{Sleepers ? m_setup.camera : m_setup.distancecamera};
auto const distancescale{Sleepers ? 1.0 : static_cast<double>(m_setup.distancescale)};
auto const distanceoffset{Sleepers ? 0.0 : static_cast<double>(m_setup.distanceoffset)};
auto const centerdistance{glm::length(Area.center - distancecamera)};
auto const nearest{std::max(0.0, centerdistance - Area.radius)};
auto const farthest{centerdistance + Area.radius};
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * m_setup.distancescale + m_setup.distanceoffset))};
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * m_setup.distancescale + m_setup.distanceoffset))};
auto const distancemin{static_cast<float>(0.999 * (nearest * nearest * distancescale + distanceoffset))};
auto const distancemax{static_cast<float>(1.001 * (farthest * farthest * distancescale + distanceoffset))};
auto lookup{m_groupmap.find(Key)};
if (lookup == m_groupmap.end())
auto &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
{
// lod bounds of the model, the same set Render_Instanced() splits the distance into bands with
auto const lodfirst{static_cast<std::uint32_t>(m_lodlist.size())};
@@ -121,7 +139,7 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c
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_entries += group.entrycount;
lookup = m_groupmap.emplace(Key, group).first;
lookup = groupmap.emplace(Key, group).first;
if (m_setup.drawing)
{
// any instance of the group serves; it's only prepared and asked for its material
@@ -134,7 +152,7 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c
break;
}
}
m_drawgroups.push_back({&lookup->first, instance, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
m_drawgroups.push_back({&lookup->first, instance, Sleepers, lodfirst, lodcount, group.entryfirst, distancemin, distancemax});
}
}
else
@@ -169,9 +187,15 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c
job.count = static_cast<std::uint32_t>(Bucket.records.size());
job.group = lookup->second.index;
job.candidatestart = m_candidates;
job.flags = (Sleepers ? gl::INSTANCE_JOB_SLEEPERS : 0u);
m_joblist.push_back(job);
m_buckets.push_back(&Bucket);
m_candidates += job.count;
if (Sleepers)
{
++m_sleepertracks;
m_sleepers += job.count;
}
}
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
@@ -384,12 +408,13 @@ void opengl46_instance_cull::draw_result(std::uint32_t const Draws, std::uint32_
m_cputime = Cputime;
}
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts)
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers)
{
if (!m_dispatched)
return;
auto const lookup{m_groupmap.find(Key)};
if (lookup == m_groupmap.end())
auto const &groupmap{Sleepers ? m_sleepergroupmap : m_groupmap};
auto const lookup{groupmap.find(Key)};
if (lookup == groupmap.end())
return;
auto const &group{lookup->second};
for (std::size_t band = 0; band < std::min<std::size_t>(Bandcounts.size(), group.entrycount); ++band)
@@ -412,6 +437,8 @@ void opengl46_instance_cull::end_pass()
while (m_cpupasses.size() > 8)
m_cpupasses.pop_front();
}
m_lastsleepertracks = m_sleepertracks;
m_lastsleepers = m_sleepers;
m_inpass = false;
m_dispatched = false;
}
@@ -462,24 +489,26 @@ std::string opengl46_instance_cull::info(std::string const &Label, bool const Oc
{
if (Global.gfx_gpucull == 0)
return {};
// the sleepers are among the instances counted
auto const sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)
return "\n" + Label + ": no result yet";
return "\n" + Label + ": no result yet" + sleepers;
if (false == Occlusion)
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn, " + std::to_string(m_last.entries) + " bands; " + std::to_string(m_draws) +
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms";
" indirect draws in " + std::to_string(m_calls) + " calls, cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances drawn" +
(m_twophase ? " (" + std::to_string(m_last.first) + " of them first, seen in the last frame)" : std::string{}) + ", " + std::to_string(m_last.occluded) +
" hidden by occlusion" + (m_occlusion ? (m_twophase ? " (also by one another)" : "") : " (test off)") + ", " + std::to_string(m_last.entries) + " bands; " +
std::to_string(m_draws) + " indirect draws in " + std::to_string(m_calls) + " calls (+" + std::to_string(m_prepassdraws) + " depth prepass in " +
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms";
std::to_string(m_prepasscalls) + "), cpu " + to_string(m_cputime, 3) + " ms" + sleepers;
}
if (m_comparedframes == 0)
return "\n" + Label + ": no result yet";
return "\n" + Label + ": no result yet" + sleepers;
return "\n" + Label + ": " + std::to_string(m_last.gputotal) + " instances (cpu " + std::to_string(m_last.cputotal) + "), " + std::to_string(m_last.entries) + " bands, " +
std::to_string(m_last.mismatched) + " differ (by up to " + std::to_string(m_last.largestdifference) + "); frames differing " + std::to_string(m_mismatchedframes) + " of " +
std::to_string(m_comparedframes);
std::to_string(m_comparedframes) + sleepers;
}
std::string opengl46_instance_cull::info_times(std::string const &Label)

View File

@@ -46,6 +46,8 @@ class TAnimModel;
// each render pass culled on the gpu (the main view, the cascades of the sun shadows, the faces of the environment map) has a
// culling of its own, with its own buffers, while the records and the programs are shared by all of them: the records of a
// bucket go to the gpu once and serve every pass which queues the bucket
// the sleepers of tracks (TTrack::m_sleeper_local_transforms) are culled the same way, each track with them a bucket of its
// own, and drawn together with the instances; their groups are kept apart from those of the model instances
class opengl46_instance_cull
{
public:
@@ -76,7 +78,8 @@ class opengl46_instance_cull
struct draw_group
{
scene::basic_cell::instance_bucket_key const *key;
TAnimModel *instance; // one of the group, prepares the model and gives the material
TAnimModel *instance; // one of the group, prepares the model and gives the material. none for sleepers
bool sleepers; // the skins come from the key, as Render_Sleepers() sets them
std::uint32_t lodfirst; // in lod_bounds()
std::uint32_t lodcount;
std::uint32_t entryfirst;
@@ -103,6 +106,11 @@ class opengl46_instance_cull
// 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
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
// 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
// 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);
// runs the culling passes for the buckets added. View: rotation of the view of the pass. Occlusion: the depth pyramid
// to test the instances against, or null. Twophase: with the occlusion test, only the instances seen in the previous
// frame pass for now, dispatch_second() takes the others
@@ -164,8 +172,9 @@ class opengl46_instance_cull
{
return static_cast<std::uint32_t>(m_last.gputotal);
}
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts);
// counts per lod band of the instances Render_Instanced() drew for a model and its skins in this pass, or of the sleepers
// Render_Sleepers() drew. needs dispatch() before it
void cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts, bool const Sleepers = false);
// ends the pass: its cpu counts are kept for the comparison
void end_pass();
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu. done when a pass begins;
@@ -224,6 +233,10 @@ class opengl46_instance_cull
bool restarted{false};
};
using group_map = std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash>;
// add_bucket() and add_sleepers()
void add(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket, scene::bounding_area const &Area, bool const Sleepers);
// finds a place for the records of a bucket in the record buffer and sends them
void place(scene::basic_cell::instance_bucket const &Bucket);
// binds the buffers of the culling passes
@@ -251,10 +264,16 @@ class opengl46_instance_cull
std::vector<gl::instance_cull_group> m_grouplist;
std::vector<draw_group> m_drawgroups;
std::vector<float> m_lodlist;
std::unordered_map<scene::basic_cell::instance_bucket_key, group_data, scene::basic_cell::instance_bucket_key_hash> m_groupmap;
group_map m_groupmap;
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_entries{0};
// tracks with sleepers and their sleepers queued in the pass, and in the last pass, for the panel
std::uint32_t m_sleepertracks{0};
std::uint32_t m_sleepers{0};
std::uint32_t m_lastsleepertracks{0};
std::uint32_t m_lastsleepers{0};
bool m_dispatched{false};
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry

View File

@@ -679,6 +679,8 @@ bool opengl46_renderer::Render()
m_locallights.reset();
m_localshadows.reset_lamps();
m_localshadows.reset_headlights();
// and so may the tracks the sleeper records were made for
m_sleeperrecords.clear();
}
// generate new frame
opengl_texture::reset_unit_cache();
@@ -690,6 +692,14 @@ bool opengl46_renderer::Render()
++m_instancerecordsversion;
m_nodestateversion = scene::node_state_version;
}
if( ( m_framestamp % 256 ) == 0 ) {
// the sleeper records of tracks out of sight for a while. their places in the record buffer of the gpu culling are
// dropped when it starts over
for( auto records { m_sleeperrecords.begin() }; records != m_sleeperrecords.end(); ) {
if( m_framestamp - records->second.frame > 256 ) { records = m_sleeperrecords.erase( records ); }
else { ++records; }
}
}
// scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records
if (simulation::is_ready && (false == Global.gfx_skiprendering))
m_localshadows.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp);
@@ -987,6 +997,7 @@ void opengl46_renderer::draw_debug_ui()
{
ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows);
ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections);
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
@@ -2902,6 +2913,13 @@ void opengl46_renderer::Render(scene::basic_region *Region)
setup.drawing = ( Global.gfx_gpucull == 1 );
m_passcull->begin_pass( setup );
}
// the sleepers of tracks go with the instances of the main view
m_sleeperspass = ( ( m_passcull == &m_instancecull ) && Global.gfx_gpucull_sleepers );
if( m_sleeperspass && ( false == m_passcull->drawing() ) ) {
for( auto &counts : m_sleeperbandcounts ) {
counts.second.clear();
}
}
// build a list of region sections to render
glm::vec3 const cameraposition{m_renderpass.pass_camera.position()};
auto const camerax = static_cast<int>(std::floor(cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2));
@@ -3379,6 +3397,14 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
else {
// the view of the pass is in place here, the one Render_Instanced() takes
m_passcull->dispatch( OpenGLMatrices.data( GL_MODELVIEW ) );
// the sleepers the cpu drew along the tracks, before the culling ran
if( m_sleeperspass ) {
for( auto const &counts : m_sleeperbandcounts ) {
if( false == counts.second.empty() ) {
m_passcull->cpu_result( counts.first, counts.second, true );
}
}
}
}
}
for( auto &bucket : m_frame_instance_buckets ) {
@@ -3394,6 +3420,7 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
m_instancecullpass = false;
m_passcull = nullptr;
}
m_sleeperspass = false;
}
void opengl46_renderer::Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end)
@@ -4045,6 +4072,7 @@ void opengl46_renderer::Render_Instances_Indirect()
// the colour pass takes the draws from here, and counts them
m_indirectcolourpending = true;
m_indirectcolourset = colourset;
m_indirectcoloursleepers = m_sleeperspass;
m_renderpass.draw_stats.drawcalls += static_cast<int>( prepasscounts.calls );
colourcounts.draws = static_cast<std::uint32_t>( m_indirectdraws.size() );
}
@@ -4100,10 +4128,25 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
m_indirectrecording = true;
for( auto const &group : Cull.draw_groups() ) {
auto *model { group.key->pModel };
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( group.instance == nullptr ) ) { continue; }
group.instance->RaPrepare();
auto const *material { group.instance->Material() };
auto alpha { material != nullptr ? material->textures_alpha : 0x30300030 };
if( ( model == nullptr ) || ( model->Root == nullptr ) || ( ( group.instance == nullptr ) && ( false == group.sleepers ) ) ) { continue; }
material_handle const *skins { nullptr };
auto alpha { 0x30300030 };
if( group.sleepers ) {
// 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 {
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 ) {
// a band holds the squared distances from its lower bound up to the next one; any of them selects the same lod
@@ -4114,7 +4157,7 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
::glPushMatrix();
::glLoadIdentity();
model->Root->fSquareDist = bandmin;
model->Root->ReplacableSet( ( material != nullptr ? material->replacable_skins : nullptr ), alpha );
model->Root->ReplacableSet( skins, alpha );
model->Root->pRoot = model;
Render( model->Root );
::glPopMatrix();
@@ -4764,6 +4807,71 @@ std::vector<texture_handle> const &opengl46_renderer::Model_Textures(TModel3d *M
return textures;
}
namespace {
// the group of the sleepers of a track in the gpu culling: their model, and the skin in the slot Render_Sleepers() sets it in
scene::basic_cell::instance_bucket_key sleeper_key( TTrack const &Track ) {
scene::basic_cell::instance_bucket_key key;
key.pModel = Track.m_sleeper_model;
key.skins[ 1 ] = Track.m_sleeper_skin;
return key;
}
} // namespace
void opengl46_renderer::Queue_Sleepers( TTrack *Track )
{
// the gates of Render_Sleepers(); the range is tested for the whole track
if( ( Track->m_sleeper_model == nullptr ) || Track->m_sleeper_local_transforms.empty() || ( Global.SleeperDistance <= 0.f ) ) { return; }
auto const distsq { glm::length2( Track->location() - m_renderpass.pass_camera.position() ) };
auto const cutoffsq { static_cast<double>( Global.SleeperDistance ) * static_cast<double>( Global.SleeperDistance ) };
if( distsq > cutoffsq ) { return; }
auto &records { m_sleeperrecords[ Track ] };
auto const &transforms { Track->m_sleeper_local_transforms };
if( ( records.bucket.recordsversion != m_instancerecordsversion )
|| ( records.model != Track->m_sleeper_model )
|| ( records.skin != Track->m_sleeper_skin )
|| ( records.transforms != transforms.data() )
|| ( records.count != transforms.size() ) ) {
// each sleeper is a record placed and turned by its transform, relative to the origin of the track (the centre of its
// cell), with the sphere Render_Sleepers() tests against the frustum
auto const radius { std::max( Track->m_sleeper_model->bounding_radius(), 2.0f ) };
auto &bucket { records.bucket };
bucket.records.clear();
bucket.records.reserve( transforms.size() );
glm::vec3 low { std::numeric_limits<float>::max() };
glm::vec3 high { std::numeric_limits<float>::lowest() };
for( auto const &local : transforms ) {
scene::basic_cell::instance_record record;
glm::vec3 const position { local[ 3 ] };
record.position = packed_vec3{ position };
record.radius = radius;
record.rangesquaredmin = 0.f;
record.rangesquaredmax = std::numeric_limits<float>::max();
record.flags = scene::basic_cell::instance_visible | scene::basic_cell::instance_instanceable;
for( int column = 0; column < 3; ++column ) {
record.rotationscale[ column ] = local[ column ];
}
bucket.records.emplace_back( record );
low = glm::min( low, position );
high = glm::max( high, position );
}
bucket.recordorigin = Track->m_origin;
bucket.lodrangesquared = std::numeric_limits<float>::max();
bucket.recordsversion = m_instancerecordsversion;
bucket.gpustale = true;
records.key = sleeper_key( *Track );
records.area = scene::bounding_area{ Track->m_origin + glm::dvec3{ ( low + high ) * 0.5f }, 0.5f * glm::length( high - low ) };
records.model = Track->m_sleeper_model;
records.skin = Track->m_sleeper_skin;
records.transforms = transforms.data();
records.count = transforms.size();
}
records.frame = m_framestamp;
m_passcull->add_sleepers( records.key, records.bucket, records.area );
}
void opengl46_renderer::Render_Sleepers( TTrack *Track )
{
if( Track == nullptr ) { return; }
@@ -4931,6 +5039,25 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track )
m_renderpass.draw_stats.instances += static_cast<int>( total );
m_renderpass.draw_stats.models += static_cast<int>( total );
if( m_sleeperspass && m_instancecullpass && ( false == m_passcull->drawing() ) ) {
// the comparison with the gpu culling: the sleepers drawn by lod band, the bands as the culling makes them, split by
// the bounds of the model past 0 and short of the largest float
auto const counted = []( float const Bound ) {
return ( Bound > 0.f ) && ( Bound < std::numeric_limits<float>::max() ); };
auto &counts { m_sleeperbandcounts[ sleeper_key( *Track ) ] };
auto const bands { static_cast<std::size_t>( std::count_if( lodbounds.begin(), lodbounds.end(), counted ) ) + 1 };
if( counts.size() < bands ) {
counts.resize( bands, 0 );
}
for( auto const &survivor : survivors ) {
std::size_t band { 0 };
for( auto const bound : lodbounds ) {
if( counted( bound ) && ( bound <= survivor.first ) ) { ++band; }
}
++counts[ band ];
}
}
}
bool opengl46_renderer::Render(TDynamicObject *Dynamic)
@@ -5699,6 +5826,15 @@ void opengl46_renderer::Render(scene::basic_cell::path_sequence::const_iterator
auto *track = *first;
if( false == track->m_visible ) { continue; }
if( false == track->m_sleeper_enabled ) { continue; }
if( m_sleeperspass ) {
// culled with the instances of the pass and drawn from the result, or drawn by the cpu as well to compare
Queue_Sleepers( track );
if( m_passcull->drawing() ) { continue; }
}
else if( m_indirectcolourpending && m_indirectcoloursleepers ) {
// the depth pass of the whole scene culled and recorded them, the colour pass draws what it recorded
continue;
}
Render_Sleepers( track );
}
break;

View File

@@ -425,6 +425,9 @@ class opengl46_renderer : public gfx_renderer {
// no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond
// Global.SleeperDistance from the track origin.
void Render_Sleepers( TTrack *Track );
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
// Render_Sleepers(), making their records first when they're missing or out of date
void Queue_Sleepers( TTrack *Track );
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
bool Render_interior( bool const Alpha = false );
bool Render_lowpoly( TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false );
@@ -581,6 +584,25 @@ class opengl46_renderer : public gfx_renderer {
// Instances_Cast_Shadow() of the groups of the shadow pass under way
std::unordered_map<scene::basic_cell::instance_bucket_key, bool, scene::basic_cell::instance_bucket_key_hash> m_shadowcasters;
std::vector<std::uint32_t> m_instance_bandcounts; // reported by Render_Instanced() for the comparison
// the sleepers of tracks culled on the gpu with the instances of the main view (Global.gfx_gpucull_sleepers): records made
// from TTrack::m_sleeper_local_transforms when the track is queued first, and again when they or the instance records
// (editor, m_instancerecordsversion) went out of date. the records of tracks not queued for a while are let go
struct sleeper_records {
scene::basic_cell::instance_bucket bucket; // the records only, no instances
scene::basic_cell::instance_bucket_key key; // the model, and the skin in the slot Render_Sleepers() sets it in
scene::bounding_area area; // of the locations of the sleepers
TModel3d const *model { nullptr }; // what the records were made from
material_handle skin { null_handle };
glm::mat4 const *transforms { nullptr };
std::size_t count { 0 };
unsigned int frame { 0 }; // queued last
};
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
bool m_sleeperspass { false }; // the sleepers of the pass under way go to its gpu culling (the one of the main view)
bool m_indirectcoloursleepers { false }; // with m_indirectcolourpending: the depth pass culled and recorded the sleepers too
// counts per lod band of the sleepers Render_Sleepers() drew in the pass, by model and skin, for the comparison (they're
// drawn before the culling runs)
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
// depth pyramid for the occlusion test of the gpu culling
opengl46_hiz m_hiz;
// a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be