17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 03:50:30 +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

@@ -265,8 +265,8 @@ Many models of the scenery (trees, bushes, poles, fences: models without lights
batch per model and level of detail. Which of them are drawn, and at which level of detail, can be worked out on the
processor (as before) or on the graphics card. On the graphics card the processor no longer goes through the models one by
one, so with dense vegetation it is much less loaded. The main view, the three cascades of the sun shadows and the faces of
the environment map (reflections) are drawn this way; the shadow maps of lamps and headlights, the cab and other views go as
before.
the environment map (reflections) are drawn this way, and so are the sleepers of the tracks with a `sleepermodel` in the main
view; the shadow maps of lamps and headlights, the cab and other views go as before.
| Entry | Default | Meaning |
|---|---|---|
@@ -277,6 +277,7 @@ before.
| `gfx.gpucull.multidraw yes/no` | `yes` | with `gfx.gpucull yes`: parts of models with the same material (and texture variant) in the same model are drawn with one call (multi draw), see below |
| `gfx.gpucull.shadows yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the cascades of the sun shadows too (each cascade with its own camera and the distances from the real camera, as before; models none of whose materials cast shadows are left out at once) |
| `gfx.gpucull.reflections yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the faces of the environment map too (its own range of models, as before) |
| `gfx.gpucull.sleepers yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the sleepers of the tracks (`sleepermodel`) of the main view too, each one tested against the view and given its level of detail by its distance, as before (the tracks within `sleeperdistance` are chosen as before); they also take the occlusion test, the depth prepass and the two phases of the models |
| `gfx.depthprepass.scene yes/no` | `no` | the depth of the whole opaque scene of the main view (terrain, tracks, buildings, models, vehicles, the cab) is drawn before its colour, see below |
With `gfx.gpucull.occlusion.instances yes` the test goes in two phases: the models seen in the previous frame are drawn
@@ -390,7 +391,8 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
differ. With `gfx.gpucull.shadows` and `gfx.gpucull.reflections` the same rows (without the occlusion test and the
prepass) follow for each cascade, `gpu cull shadows 1:` to `3:` (1 the nearest), and for the environment map,
`gpu cull reflections, last face:` (the last face of the map drawn, as the map is drawn now and then), with the timings
`instance cull shadows 1:` … and `instance cull reflections, last face:`.
`instance cull shadows 1:` … and `instance cull reflections, last face:`. With `gfx.gpucull.sleepers` the row
`gpu cull:` is followed by how many sleepers of how many tracks were queued (they are counted among the models).
- With `gfx.depthprepass.scene yes` Shift+F12 shows the `scene depth:` row (draws of the depth pass and its processor time)
and the timing `scene depth:` (its GPU time).
- With `gfx.contactshadows` on Shift+F12 shows the `contact shadows:` row (the level, the length and reach of the rays, or

View File

@@ -28,6 +28,7 @@ void gl::glsl_common_setup()
"#define INSTANCE_COMMAND_GROUP_SIZE " + std::to_string(INSTANCE_COMMAND_GROUP_SIZE) + "\n" +
"#define HIZ_GROUP_SIZE " + std::to_string(HIZ_GROUP_SIZE) + "\n" +
"#define INSTANCE_COUNTER_HEADER " + std::to_string(INSTANCE_COUNTER_HEADER) + "\n" +
"#define INSTANCE_JOB_SLEEPERS " + std::to_string(INSTANCE_JOB_SLEEPERS) + "U\n" +
"#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\n" +
"#define CLUSTER_GRID_X " + std::to_string(CLUSTER_GRID_X) + "\n" +
"#define CLUSTER_GRID_Y " + std::to_string(CLUSTER_GRID_Y) + "\n" +

View File

@@ -466,7 +466,7 @@ namespace gl
// culling of model instances in compute passes (instance_cull.comp, instance_cull_offsets.comp). binding points of
// the shader storage blocks, apart from the ones bound for the shading so the passes don't disturb them
const GLuint INSTANCE_RECORD_SSBO_BINDING = 9; // scene::basic_cell::instance_record of the queued buckets
const GLuint INSTANCE_JOB_SSBO_BINDING = 10; // instance_cull_job, one per queued bucket
const GLuint INSTANCE_JOB_SSBO_BINDING = 10; // instance_cull_job, one per queued bucket (or track with sleepers)
const GLuint INSTANCE_GROUP_SSBO_BINDING = 11; // instance_cull_group, one per model and skins
const GLuint INSTANCE_LOD_SSBO_BINDING = 12; // sorted lod bounds (squared distances) of the groups
const GLuint INSTANCE_COUNTER_SSBO_BINDING = 13; // header, then count[entries], cursor[entries] and first[entries]
@@ -496,7 +496,15 @@ namespace gl
uint32_t count; // records of the bucket
uint32_t group;
uint32_t candidatestart; // records of the jobs before this one
uint32_t pad[2];
uint32_t flags; // instance_cull_job_flags
uint32_t pad;
};
enum instance_cull_job_flags : uint32_t
{
// the sleepers of a track (opengl46_renderer::Render_Sleepers()): the lod goes by the plain distance from the camera of
// the pass, without the range and size tests of the model instances (the range of the whole track is tested on the cpu)
INSTANCE_JOB_SLEEPERS = 1u
};
static_assert(sizeof(instance_cull_job) == 48, "bad size of instance_cull_job");

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

View File

@@ -1,5 +1,6 @@
// culls the model instances of the buckets queued in a render pass and assigns them their lod bands, with the tests
// opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket.
// opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket. the sleepers of the
// tracks queued come as buckets too, with the tests of opengl46_renderer::Render_Sleepers().
// stage 0 counts the instances which pass the tests, per entry (lod band of a group); after instance_cull_offsets.comp
// turned the counts into the first slots of the entries, stage 1 writes the modelview matrices of the instances drawn first
// there, in no particular order, and stage 2 (two phases) those of the instances drawn second after them.
@@ -35,8 +36,8 @@ struct instance_cull_job
uint count;
uint group;
uint candidatestart;
uint pad0;
uint pad1;
uint flags;
uint pad;
};
struct instance_cull_group
@@ -162,7 +163,7 @@ bool occluded(vec3 center, float radius)
return nearest < farthest;
}
// the tests of the cpu (Render_Instanced()), all but the occlusion one: the entry of the instance, or false when it's left out.
// the tests of the cpu (Render_Instanced(), for sleepers Render_Sleepers()), all but the occlusion one: the entry of the instance, or false when it's left out.
// Location: relative to the camera of the pass
bool passes(instance_cull_job job, instance_record record, out vec3 location, out uint entry)
{
@@ -178,17 +179,26 @@ bool passes(instance_cull_job job, instance_record record, out vec3 location, ou
return false;
}
vec3 offset = job.distanceorigin + record.position;
float distancesquared = dot(offset, offset) * distance_params.x;
if (distancesquared > distance_params.z)
return false;
distancesquared += distance_params.y;
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
return false;
if (distancesquared > distance_params.w)
return false;
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
return false;
float distancesquared;
if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u)
{
// the sleepers of a track, as Render_Sleepers() takes them: the lod by the plain distance from the camera of the pass
distancesquared = dot(location, location);
}
else
{
vec3 offset = job.distanceorigin + record.position;
distancesquared = dot(offset, offset) * distance_params.x;
if (distancesquared > distance_params.z)
return false;
distancesquared += distance_params.y;
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
return false;
if (distancesquared > distance_params.w)
return false;
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
return false;
}
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
instance_cull_group group = groups[job.group];
@@ -202,9 +212,18 @@ bool passes(instance_cull_job job, instance_record record, out vec3 location, ou
return true;
}
void write(uint entry, instance_record record, vec3 location)
void write(uint entry, instance_record record, vec3 location, instance_cull_job job)
{
uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u);
if ((job.flags & INSTANCE_JOB_SLEEPERS) != 0u)
{
// in the order Render_Sleepers() multiplies them (the view moved to the origin of the track, then the transform of the
// sleeper), which gives the very same matrix: the other order rounds differently, enough to shift the edges of sleepers
// sunk in the ballast by a pixel here and there
mat4 originview = view * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(job.origin, 1.0));
modelviews[slot] = originview * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(record.position, 1.0));
return;
}
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
}
@@ -241,7 +260,7 @@ void main()
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
return;
}
write(entry, record, location);
write(entry, record, location, job);
if (phases.x != 0u)
atomicAdd(counters[COUNTER_FIRST], 1u);
return;
@@ -252,7 +271,7 @@ void main()
if (passed && (false == seen))
{
if (inview)
write(entry, record, location);
write(entry, record, location, job);
else
atomicAdd(counters[COUNTER_OCCLUDED], 1u);
}

View File

@@ -1464,6 +1464,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1);
Parser >> gfx_gpucull_reflections;
}
else if (Token == "gfx.gpucull.sleepers")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_sleepers;
}
else if (Token == "gfx.depthprepass.scene")
{
Parser.getTokens(1);
@@ -1962,6 +1967,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull.multidraw", gfx_gpucull_multidraw );
export_as_text( Output, "gfx.gpucull.shadows", gfx_gpucull_shadows );
export_as_text( Output, "gfx.gpucull.reflections", gfx_gpucull_reflections );
export_as_text( Output, "gfx.gpucull.sleepers", gfx_gpucull_sleepers );
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );

View File

@@ -298,6 +298,9 @@ struct global_settings {
// culled in compute passes as well (drawn from their result, or compared), not only those of the main view
bool gfx_gpucull_shadows = true;
bool gfx_gpucull_reflections = true;
// with gfx_gpucull 1 or 2: the sleepers of the tracks (sleepermodel) of the main view are culled with the instances as well,
// and with 1 drawn from the result
bool gfx_gpucull_sleepers = true;
// (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included
bool gfx_depthprepass_scene = false;
// (opengl 4.6) the model block of every draw and the matrices of instances are written to persistently mapped buffers,