17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-19 22:47:29 +02:00

Cull the scenery models drawn on their own on the GPU with the instances

This commit is contained in:
2026-10-19 10:54:04 +02:00
parent ffe77be754
commit b1f880b5a7
10 changed files with 185 additions and 15 deletions

View File

@@ -281,6 +281,7 @@ cab and other views go as before.
| `gfx.gpucull.shadows.headlights yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the shadow maps of headlights too (each map tested against the beam it is drawn with, to its reach, the distances from the real camera, as before) |
| `gfx.gpucull.shadows.lamps yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the scenery part of the shadow maps of scenery lamps too (each map tested against the reach of its lamp, as before); the part with the vehicles has no models of the scenery |
| `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.gpucull.models yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models drawn on their own too (scenery models with lights, animations, or skins and animations given by events, which the instanced draws leave out), in every pass the instances are culled in: each such model is tested on its own like the others (visibility, the view, its range, the draw range, its size on the screen) and given the level of detail its distance picks from the `mindistance`/`maxdistance` of its submodels; the processor animates and prepares the model only to record what it draws. Models with billboards turned to the camera stay on the processor, and so do vehicles |
| `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 |
| `gfx.depthprepass.replay yes/no` | `yes` | the scene is gone through once for its depth and its colour: what the colour pass draws is recorded and both are drawn from the record, see below. `no`: twice, as before |
@@ -413,7 +414,9 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
slot of the map), and for the scenery of the maps of lamps, `gpu cull lamp shadows, scenery 1:` and `2:` (the first and
the second drawn in a frame), each with the numbers of its last drawing and the timings `instance cull headlight shadows 1:`
… and `instance cull lamp shadows, scenery 1:` …. 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).
`gpu cull:` is followed by how many sleepers of how many tracks were queued (they are counted among the models). With
`gfx.gpucull.models` each `gpu cull` row is followed by how many models drawn on their own were queued (also 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.depthprepass.replay yes` the row tells how many of the recorded
parts (of models, of the scenery; neighbouring tracks count one by one) had their depth drawn, in how many calls, how many

View File

@@ -511,6 +511,16 @@ bool submodel_tree_blocks_instancing( TSubModel const *Sub ) {
if( submodel_tree_blocks_instancing( Sub->Next ) ) { return true; }
return false;
}
// returns true if any submodel of the tree turns to the camera: a billboard is placed from the modelview of the drawn
// instance, which a walk started from the origin of the instance doesn't have
bool submodel_tree_faces_camera( TSubModel const *Sub ) {
if( Sub == nullptr ) { return false; }
if( ( Sub->b_Anim == TAnimType::at_Billboard ) || ( Sub->b_aAnim == TAnimType::at_Billboard ) ) { return true; }
if( submodel_tree_faces_camera( Sub->Child ) ) { return true; }
if( submodel_tree_faces_camera( Sub->Next ) ) { return true; }
return false;
}
} // anonymous namespace
int TAnimModel::s_instanceable_total = 0;
@@ -530,6 +540,7 @@ void TAnimModel::update_instanceable_flag() {
// refs are intentionally allowed — they're either passed per-instance via
// Material() or share global state (season) across all instances.
m_instanceable = false;
m_cameraindependent = ( pModel != nullptr ) && ( false == submodel_tree_faces_camera( pModel->Root ) );
++s_classified_total;
if( pModel == nullptr ) { ++s_rejected_no_pmodel; return; }
if( iNumLights != 0 ) { ++s_rejected_lights; return; }

View File

@@ -228,6 +228,10 @@ public:
// no lights, no submodel animations, no replacable skins, no seasonal variants.
// computed once at end of Load() so the renderer can simply test the flag.
bool m_instanceable { false };
// true if the walk of the submodel tree gives the same submodel-local transforms whatever modelview it starts from
// (no billboards turned to the camera): an instance drawn on its own can then be culled on the gpu like the instanced
// ones, its tree walked from the origin of the instance, which the culling places in view
bool m_cameraindependent { false };
// helper: evaluates current state and updates m_instanceable accordingly.
void update_instanceable_flag();
// helper: assigns the model and the replacable skin to the instance, and binds light and variant submodels of the model

View File

@@ -82,6 +82,7 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
m_entries = 0;
m_sleepertracks = 0;
m_sleepers = 0;
m_models = 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)
@@ -196,6 +197,10 @@ void opengl46_instance_cull::add(scene::basic_cell::instance_bucket_key const &K
++m_sleepertracks;
m_sleepers += job.count;
}
else if (Key.instance != nullptr)
{
++m_models;
}
}
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
@@ -421,6 +426,22 @@ void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key c
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
}
void opengl46_instance_cull::cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared)
{
if (!m_dispatched)
return;
auto const lookup{m_groupmap.find(Key)};
if (lookup == m_groupmap.end())
return;
auto const &group{lookup->second};
auto const &lods{m_grouplist[group.index]};
// the band the culling gives the distance: the number of bounds it reached
auto const first{m_lodlist.begin() + lods.lodfirst};
auto const band{static_cast<std::uint32_t>(std::upper_bound(first, first + lods.lodcount, Distancesquared) - first)};
if (band < group.entrycount)
++m_cpucounts[group.entryfirst + band];
}
void opengl46_instance_cull::end_pass()
{
// the counts of both phases, for the panel and the comparison
@@ -439,6 +460,7 @@ void opengl46_instance_cull::end_pass()
}
m_lastsleepertracks = m_sleepertracks;
m_lastsleepers = m_sleepers;
m_lastmodels = m_models;
m_inpass = false;
m_dispatched = false;
}
@@ -490,7 +512,10 @@ 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{}};
auto sleepers{m_lastsleepertracks > 0 ? "\n" + Label + ": " + std::to_string(m_lastsleepers) + " sleepers of " + std::to_string(m_lastsleepertracks) + " tracks queued" : std::string{}};
// and so are the models drawn on their own
if (m_lastmodels > 0)
sleepers += "\n" + Label + ": " + std::to_string(m_lastmodels) + " models drawn on their own queued";
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)

View File

@@ -49,6 +49,8 @@ class TAnimModel;
// 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
// so are the model instances drawn on their own (not instanceable): each one a bucket and a group of its own (the instance in
// its key), whose submodel tree the renderer animates and walks for it alone
class opengl46_instance_cull
{
public:
@@ -176,6 +178,9 @@ class opengl46_instance_cull
// 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);
// the cpu drew the instance of a group of its own (Key with the instance) at a squared distance scaled as the culling
// scales it: counted in the lod band the distance falls in. needs dispatch() before it
void cpu_instance(scene::basic_cell::instance_bucket_key const &Key, float const Distancesquared);
// 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;
@@ -275,6 +280,9 @@ class opengl46_instance_cull
std::uint32_t m_sleepers{0};
std::uint32_t m_lastsleepertracks{0};
std::uint32_t m_lastsleepers{0};
// model instances drawn on their own queued in the pass, and in the last pass, for the panel
std::uint32_t m_models{0};
std::uint32_t m_lastmodels{0};
bool m_dispatched{false};
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry

View File

@@ -685,8 +685,9 @@ 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
// and so may the tracks the sleeper records were made for, and the models drawn on their own
m_sleeperrecords.clear();
m_modelrecords.clear();
}
// generate new frame
opengl_texture::reset_unit_cache();
@@ -705,6 +706,11 @@ bool opengl46_renderer::Render()
if( m_framestamp - records->second.frame > 256 ) { records = m_sleeperrecords.erase( records ); }
else { ++records; }
}
// and so are the records of the models drawn on their own
for( auto records { m_modelrecords.begin() }; records != m_modelrecords.end(); ) {
if( m_framestamp - records->second.frame > 256 ) { records = m_modelrecords.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))
@@ -1025,6 +1031,7 @@ void opengl46_renderer::draw_debug_ui()
ImGui::Checkbox("the same for the shadow maps of headlights (gfx.gpucull.shadows.headlights)", &Global.gfx_gpucull_shadows_headlights);
ImGui::Checkbox("the same for the scenery of the shadow maps of lamps, when it's drawn next (gfx.gpucull.shadows.lamps)", &Global.gfx_gpucull_shadows_lamps);
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
ImGui::Checkbox("the same for the models drawn on their own, with lights, animations or skins of their own (gfx.gpucull.models)", &Global.gfx_gpucull_models);
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
@@ -3013,6 +3020,9 @@ void opengl46_renderer::Render(scene::basic_region *Region)
counts.second.clear();
}
}
// the models drawn on their own go with the instances of every pass culled on the gpu
m_modelspass = ( m_instancecullpass && Global.gfx_gpucull_models );
m_modelcpudistances.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));
@@ -3361,8 +3371,9 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
// cell that still holds a drawable instance. Shadows measure from the
// real (viewport) camera, matching Render_Instanced()'s shadow branch;
// every other gated mode measures from the pass camera. Non-instanced
// scenery and vehicles below keep their own per-node culling and are
// intentionally left untouched.
// scenery drawn by the cpu and vehicles below keep their own per-node
// culling and are intentionally left untouched; the non-instanced models
// culled on the gpu (Queue_Model()) are left out with the cell the same way.
{
auto const &distancecamera = (
m_renderpass.draw_mode == rendermode::shadows
@@ -3374,17 +3385,21 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
( cellnearestdistance * cellnearestdistance )
/ ( 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 ) ) {
auto const cellinrange { cellnearestdistancesquared <= sq( static_cast<double>( m_renderpass.draw_range ) + 250.0 ) };
if( cellinrange ) {
Queue_Instances( cell );
}
}
// 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
// the model of a lamp would shadow everything in the map of the lamp
if( instance == m_cubeskipinstance ) { continue; }
Render(instance);
// remaining (non-instanceable) opaque instance nodes go through the per-node path, or with the instances
// to the gpu culling, each on its own
for (auto *instance : cell->m_instancesopaque)
{
if( instance->m_instanceable ) { continue; } // already handled via bucket
// the model of a lamp would shadow everything in the map of the lamp
if( instance == m_cubeskipinstance ) { continue; }
if( Queue_Model( instance, cellinrange ) ) { continue; }
Render(instance);
m_modelcomparekey = nullptr;
}
}
// opaque parts of vehicles. the kept scenery part of the map of a lamp is drawn without them
if( false == m_cubescenery ) {
@@ -3415,7 +3430,9 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
Queue_Instances( cell );
for( auto *instance : cell->m_instancesopaque ) {
if( instance->m_instanceable ) { continue; }
if( Queue_Model( instance, true ) ) { continue; }
Render( instance );
m_modelcomparekey = nullptr;
}
}
if( Global.reflectiontune.fidelity >= 2 ) {
@@ -3485,6 +3502,10 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
}
}
}
// and the models drawn on their own
for( auto const &model : m_modelcpudistances ) {
m_passcull->cpu_instance( model.first, model.second );
}
}
}
for( auto &bucket : m_frame_instance_buckets ) {
@@ -3501,6 +3522,8 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
m_passcull = nullptr;
}
m_sleeperspass = false;
m_modelspass = false;
m_modelcpudistances.clear();
}
void opengl46_renderer::Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end)
@@ -3685,6 +3708,10 @@ void opengl46_renderer::Render(TAnimModel *Instance)
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) {
return;
}
if( m_modelcomparekey != nullptr ) {
// queued in the gpu culling as well, to compare with (Queue_Model())
m_modelcpudistances.emplace_back( *m_modelcomparekey, static_cast<float>( distancesquared ) );
}
switch (m_renderpass.draw_mode)
{
@@ -3794,6 +3821,58 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
}
}
// queues a model instance drawn on its own in the gpu culling of the pass, as a bucket and a group of its own. the culling
// makes the tests of Render( TAnimModel * ): visibility, frustum, the range of the instance, the draw range, the size on the
// screen, and gives it the lod band of the bounds of its submodels (mindistance/maxdistance) its distance falls in, then
// Record_Indirect_Draws() animates, prepares and walks its submodel tree from its origin, like it does for a group of
// instanced ones
bool opengl46_renderer::Queue_Model( TAnimModel *Instance, bool const Inrange )
{
// a billboard is turned to the camera from the modelview of the instance, which a walk from its origin doesn't have
if( ( false == Instance->m_cameraindependent ) || ( Instance->Model() == nullptr ) ) { return false; }
if( false == m_modelspass ) {
// the depth pass of the whole scene culled and recorded it, the colour pass draws what it recorded
return ( m_indirectcolourpending && m_indirectcolourmodels );
}
auto const drawing { m_passcull->drawing() };
// a model hidden by an event, or in a cell wholly out of the range of the pass, would be left out by the culling
if( ( false == Instance->visible() ) || ( false == Inrange ) ) { return drawing; }
auto &records { m_modelrecords[ Instance ] };
auto &bucket { records.bucket };
if( ( bucket.recordsversion != m_instancerecordsversion ) || ( bucket.records.size() != 1 ) ) {
// the key, the instance and the record anew: the editor may have given the instance another model or skins, or
// another instance may have taken the place of a deleted one
auto &key { records.key };
key = {};
key.pModel = Instance->Model();
if( auto const *material { Instance->Material() } ) {
for( int skin = 0; skin < 5; ++skin ) { key.skins[ skin ] = material->replacable_skins[ skin ]; }
}
key.instance = Instance;
bucket.instances.assign( 1, Instance );
// the location of the instance as the origin of the record, which keeps the precision
Update_Instance_Records( key.pModel, bucket, Instance->location() );
// drawn by its bucket, though it isn't instanceable
bucket.records.front().flags |= scene::basic_cell::instance_instanceable;
records.area = scene::bounding_area{ Instance->location(), 0.f };
}
records.frame = m_framestamp;
if( m_renderpass.draw_mode == rendermode::shadows ) {
// a model with nothing to draw in the shadows is left out of them, like the instances. the answer depends on the
// model and the skins only, and is shared by the instances with them
auto modelkey { records.key };
modelkey.instance = nullptr;
if( false == Instances_Cast_Shadow( modelkey, bucket ) ) { return drawing; }
}
m_passcull->add_bucket( records.key, bucket, records.area );
if( false == drawing ) {
// the cpu draws it too, and tells what it drew for the comparison
m_modelcomparekey = &records.key;
}
return drawing;
}
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin )
{
@@ -4169,6 +4248,7 @@ void opengl46_renderer::Render_Instances_Indirect()
m_indirectcolourpending = true;
m_indirectcolourset = colourset;
m_indirectcoloursleepers = m_sleeperspass;
m_indirectcolourmodels = m_modelspass;
if( m_scenerecording ) {
// where the colour pass, which draws the record, draws them, in the light the walk had here
auto &marker { m_scenedraws.emplace_back() };
@@ -4242,6 +4322,10 @@ void opengl46_renderer::Record_Indirect_Draws( opengl46_instance_cull &Cull )
TSubModel::iVariantSeed = 0;
}
else {
if( group.key->instance != nullptr ) {
// a model drawn on its own: its lights and animations, the way Render( TAnimModel * ) sets them up
group.instance->RaAnimate( m_framestamp );
}
group.instance->RaPrepare();
auto const *material { group.instance->Material() };
if( material != nullptr ) {

View File

@@ -449,6 +449,11 @@ class opengl46_renderer : public gfx_renderer {
// 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 );
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
// culling, or it was culled and recorded by the depth pass of the whole scene, or it lies out of range
bool Queue_Model( TAnimModel *Instance, bool const Inrange );
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 );
@@ -625,6 +630,22 @@ class opengl46_renderer : public gfx_renderer {
unsigned int frame { 0 }; // queued last
};
std::unordered_map<TTrack const *, sleeper_records> m_sleeperrecords;
// the model instances drawn on their own culled on the gpu (Global.gfx_gpucull_models), each one a bucket of its own with
// a group of its own: the record made when the instance is queued first, and again when the instance records went out of
// date (editor, events: m_instancerecordsversion). the records of instances not queued for a while are let go
struct model_records {
scene::basic_cell::instance_bucket bucket; // the instance alone
scene::basic_cell::instance_bucket_key key; // the model, the skins and the instance
scene::bounding_area area; // the location of the instance
unsigned int frame { 0 }; // queued last
};
std::unordered_map<TAnimModel const *, model_records> m_modelrecords;
bool m_modelspass { false }; // the models drawn on their own of the pass under way go to its gpu culling
bool m_indirectcolourmodels { false }; // with m_indirectcolourpending: the depth pass culled and recorded them too
// comparison (Global.gfx_gpucull 2): the key of the model Render( TAnimModel * ) is called for, and the models it drew in
// the pass with their scaled squared distances, reported once the culling ran
scene::basic_cell::instance_bucket_key const *m_modelcomparekey { nullptr };
std::vector<std::pair<scene::basic_cell::instance_bucket_key, float>> m_modelcpudistances;
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

View File

@@ -221,9 +221,12 @@ public:
struct instance_bucket_key {
TModel3d *pModel { nullptr };
std::array<material_handle, 5> skins {};
// an instance drawn on its own (not instanceable) culled on the gpu as a bucket of its own: it isn't drawn
// together with other instances of the model, as its submodel tree is prepared and walked for it alone
TAnimModel const *instance { nullptr };
bool operator==( instance_bucket_key const &other ) const {
return pModel == other.pModel && skins == other.skins;
return pModel == other.pModel && skins == other.skins && instance == other.instance;
}
};
struct instance_bucket_key_hash {
@@ -233,6 +236,7 @@ public:
// boost-style hash combine
h ^= std::hash<int>()( s ) + 0x9e3779b9 + ( h << 6 ) + ( h >> 2 );
}
h ^= std::hash<TAnimModel const *>()( k.instance ) + 0x9e3779b9 + ( h << 6 ) + ( h >> 2 );
return h;
}
};

View File

@@ -1479,6 +1479,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
Parser.getTokens(1);
Parser >> gfx_gpucull_sleepers;
}
else if (Token == "gfx.gpucull.models")
{
Parser.getTokens(1);
Parser >> gfx_gpucull_models;
}
else if (Token == "gfx.depthprepass.scene")
{
Parser.getTokens(1);
@@ -1985,6 +1990,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.gpucull.shadows.headlights", gfx_gpucull_shadows_headlights );
export_as_text( Output, "gfx.gpucull.shadows.lamps", gfx_gpucull_shadows_lamps );
export_as_text( Output, "gfx.gpucull.sleepers", gfx_gpucull_sleepers );
export_as_text( Output, "gfx.gpucull.models", gfx_gpucull_models );
export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene );
export_as_text( Output, "gfx.depthprepass.replay", gfx_depthprepass_replay );
export_as_text( Output, "gfx.bufferstreaming", gfx_bufferstreaming );

View File

@@ -305,6 +305,10 @@ struct global_settings {
// 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;
// with gfx_gpucull 1 or 2: the model instances drawn on their own (with lights, animations or skins of their own, which
// keep them out of the instanced draws) are culled with the instances as well, each one a group of its own, in every pass
// culled on the gpu, and with 1 drawn from the result. models with billboards stay on the cpu
bool gfx_gpucull_models = 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 depth pass of the scene (gfx_depthprepass_scene, or the one of the contact shadows) walks the scene for