17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 05:56:13 +02:00

feat(gl46): leave out instance buckets beyond their range before the culling

This commit is contained in:
2026-10-20 14:57:30 +02:00
parent 07ac7406a0
commit dfbe8a6d9e
7 changed files with 70 additions and 1 deletions

View File

@@ -83,6 +83,7 @@ void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
m_sleepertracks = 0;
m_sleepers = 0;
m_models = 0;
m_counts = {};
}
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)
@@ -90,6 +91,14 @@ void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key c
add(Key, Bucket, Area, false);
}
void opengl46_instance_cull::skip_bucket(scene::basic_cell::instance_bucket const &Bucket)
{
if (!m_inpass)
return;
++m_counts.skippedbuckets;
m_counts.skippedinstances += Bucket.records.size();
}
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);
@@ -213,12 +222,13 @@ void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Buc
if (current && count <= Bucket.gpucapacity)
{
shared.records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
m_counts.sentrecords += count;
Bucket.gpustale = false;
return;
}
// a new place, with some room for instances the editor adds
auto const allocate = [&shared](scene::basic_cell::instance_bucket const &Target) {
auto const allocate = [this, &shared](scene::basic_cell::instance_bucket const &Target) {
auto const records{static_cast<std::uint32_t>(Target.records.size())};
Target.gpufirst = shared.recordfill;
Target.gpucapacity = records + records / 4;
@@ -226,6 +236,7 @@ void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Buc
Target.gpustale = false;
shared.recordfill += Target.gpucapacity;
shared.records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
m_counts.sentrecords += records;
};
auto const capacity{static_cast<std::uint32_t>(shared.records->size() / RECORD_SIZE)};
@@ -243,6 +254,7 @@ void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Buc
shared.records->resize(std::max(2 * live * RECORD_SIZE, 2 * shared.records->size()));
shared.restarted = true;
shared.restartframe = m_setup.frame;
m_counts.restarted = true;
++shared.recordgeneration;
shared.recordfill = 0;
for (std::size_t job = 0; job < m_buckets.size(); ++job)
@@ -461,6 +473,9 @@ void opengl46_instance_cull::end_pass()
m_lastsleepertracks = m_sleepertracks;
m_lastsleepers = m_sleepers;
m_lastmodels = m_models;
m_counts.jobs = static_cast<std::uint32_t>(m_joblist.size());
m_counts.candidates = m_candidates;
m_lastcounts = m_counts;
m_inpass = false;
m_dispatched = false;
}
@@ -516,6 +531,11 @@ std::string opengl46_instance_cull::info(std::string const &Label, bool const Oc
// 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";
// what the pass tested and sent to the gpu
sleepers += "\n" + Label + ": " + std::to_string(m_lastcounts.candidates) + " instances tested in " + std::to_string(m_lastcounts.jobs) + " buckets";
if (Global.gfx_gpucull_bucketrange)
sleepers += ", " + std::to_string(m_lastcounts.skippedbuckets) + " buckets (" + std::to_string(m_lastcounts.skippedinstances) + " instances) left out beyond their range";
sleepers += ", " + std::to_string(m_lastcounts.sentrecords) + " records sent" + (m_lastcounts.restarted ? ", record buffer started over" : "");
if (Global.gfx_gpucull == 1)
{
if (m_last.frame == 0)

View File

@@ -114,6 +114,9 @@ class opengl46_instance_cull
// 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);
// a bucket the renderer left out of the pass, as it lies wholly beyond its range (Global.gfx_gpucull_bucketrange): only counted,
// for the panel
void skip_bucket(scene::basic_cell::instance_bucket const &Bucket);
// 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
@@ -283,6 +286,19 @@ class opengl46_instance_cull
// 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};
// what the pass held and sent, and the same of the last pass, for the panel: jobs (buckets queued), buckets left out beyond
// their range and their instances, records sent to the gpu, the record buffer started over
struct pass_counts
{
std::uint32_t jobs{0};
std::uint32_t candidates{0};
std::uint32_t skippedbuckets{0};
std::uint64_t skippedinstances{0};
std::uint64_t sentrecords{0};
bool restarted{false};
};
pass_counts m_counts;
pass_counts m_lastcounts;
bool m_dispatched{false};
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry

View File

@@ -1103,6 +1103,7 @@ void opengl46_renderer::draw_debug_ui()
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("leave out the models of a cell lying wholly beyond their range before the culling (gfx.gpucull.bucketrange)", &Global.gfx_gpucull_bucketrange);
}
ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene);
if (Global.gfx_contactshadows > 0)
@@ -3954,6 +3955,12 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
auto const distancelimit { slack * ( reflections ? sq( static_cast<double>( Global.reflectiontune.range_instances ) ) : sq( static_cast<double>( m_renderpass.draw_range ) + 250.0 ) ) };
// up close the rounding of the records is too large a part of the distance for the slack to cover it
auto const distancetested { sq( 64.0 ) };
// the nearest point of the cell, measured as the tests below and the gpu culling measure the instances: a bucket whose range
// ends before it has none of its instances drawn, and is left out of the pass before the culling. basic_cell::enclose_area
// keeps the locations of all instances of the cell within the radius of its area
auto const cellnearest { std::max( 0.0, glm::length( Cell->m_area.center - cameraposition ) - static_cast<double>( Cell->m_area.radius ) ) };
auto const cellscaledsquared { cellnearest * cellnearest * distancescale + distanceoffset };
auto const bucketrange { Global.gfx_gpucull_bucketrange && userecords && ( cellnearest * cellnearest > distancetested ) };
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
auto const &instancebucket { bucket.second };
@@ -3961,6 +3968,12 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
}
if( bucketrange && ( cellscaledsquared >= slack * instancebucket.rangesquared ) ) {
if( m_instancecullpass ) {
m_passcull->skip_bucket( instancebucket );
}
continue;
}
if( m_instancecullpass ) {
// the gpu tests every instance of the bucket, the way Render_Instanced() does. models with nothing to draw in the
// sun shadows are left out of them, as Render_Instanced() leaves them
@@ -4070,6 +4083,8 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
Bucket.records.clear();
Bucket.records.reserve( Bucket.instances.size() );
// the range of the bucket: past it none of its instances is drawn, by its own range or by the submodels of the model
auto rangesquared { 0.f };
for( auto *instance : Bucket.instances ) {
scene::basic_cell::instance_record record;
if( instance != nullptr ) {
@@ -4077,6 +4092,7 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
record.radius = instance->radius();
record.rangesquaredmin = tofloat( instance->m_rangesquaredmin );
record.rangesquaredmax = tofloat( instance->m_rangesquaredmax );
rangesquared = std::max( rangesquared, record.rangesquaredmax );
record.flags =
( instance->visible() ? scene::basic_cell::instance_visible : 0u )
| ( instance->m_instanceable ? scene::basic_cell::instance_instanceable : 0u );
@@ -4088,6 +4104,7 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
// an empty slot keeps its record without flags, and isn't drawn
Bucket.records.emplace_back( record );
}
Bucket.rangesquared = std::min( rangesquared, lodrangesquared );
Bucket.recordsversion = m_instancerecordsversion;
Bucket.gpustale = true;
}