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:
@@ -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
|
||||
|
||||
@@ -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" +
|
||||
|
||||
12
gl/ubo.h
12
gl/ubo.h
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user