mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-03 22:22:04 +01:00
Cull and assign LOD to instances in a compute pass
This commit is contained in:
@@ -338,6 +338,7 @@ if (WITH_OPENGL_MODERN)
|
|||||||
"rendering/opengl46/opengl46exposure.cpp"
|
"rendering/opengl46/opengl46exposure.cpp"
|
||||||
"rendering/opengl46/opengl46ao.cpp"
|
"rendering/opengl46/opengl46ao.cpp"
|
||||||
"rendering/opengl46/opengl46locallights.cpp"
|
"rendering/opengl46/opengl46locallights.cpp"
|
||||||
|
"rendering/opengl46/opengl46instancecull.cpp"
|
||||||
"rendering/opengl46/opengl46precipitation.cpp"
|
"rendering/opengl46/opengl46precipitation.cpp"
|
||||||
"rendering/opengl46/opengl46particles.cpp"
|
"rendering/opengl46/opengl46particles.cpp"
|
||||||
"rendering/opengl46/opengl46terrain.cpp"
|
"rendering/opengl46/opengl46terrain.cpp"
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ void gl::glsl_common_setup()
|
|||||||
"#define MAX_CASCADES " + std::to_string(MAX_CASCADES) + "U\n" +
|
"#define MAX_CASCADES " + std::to_string(MAX_CASCADES) + "U\n" +
|
||||||
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
|
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
|
||||||
"#define INSTANCE_SSBO_BINDING " + std::to_string(INSTANCE_SSBO_BINDING) + "\n" +
|
"#define INSTANCE_SSBO_BINDING " + std::to_string(INSTANCE_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_RECORD_SSBO_BINDING " + std::to_string(INSTANCE_RECORD_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_JOB_SSBO_BINDING " + std::to_string(INSTANCE_JOB_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_GROUP_SSBO_BINDING " + std::to_string(INSTANCE_GROUP_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_LOD_SSBO_BINDING " + std::to_string(INSTANCE_LOD_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_COUNTER_SSBO_BINDING " + std::to_string(INSTANCE_COUNTER_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_OUTPUT_SSBO_BINDING " + std::to_string(INSTANCE_OUTPUT_SSBO_BINDING) + "\n" +
|
||||||
|
"#define INSTANCE_CULL_GROUP_SIZE " + std::to_string(INSTANCE_CULL_GROUP_SIZE) + "\n" +
|
||||||
|
"#define INSTANCE_OFFSETS_GROUP_SIZE " + std::to_string(INSTANCE_OFFSETS_GROUP_SIZE) + "\n" +
|
||||||
|
"#define INSTANCE_COUNTER_HEADER " + std::to_string(INSTANCE_COUNTER_HEADER) + "\n" +
|
||||||
"#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\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_X " + std::to_string(CLUSTER_GRID_X) + "\n" +
|
||||||
"#define CLUSTER_GRID_Y " + std::to_string(CLUSTER_GRID_Y) + "\n" +
|
"#define CLUSTER_GRID_Y " + std::to_string(CLUSTER_GRID_Y) + "\n" +
|
||||||
|
|||||||
39
gl/ubo.h
39
gl/ubo.h
@@ -374,5 +374,44 @@ namespace gl
|
|||||||
|
|
||||||
static_assert(sizeof(atmosphere_result) == 16 * (SKY_SH_COEFFICIENTS + 4), "bad size of atmosphere_result");
|
static_assert(sizeof(atmosphere_result) == 16 * (SKY_SH_COEFFICIENTS + 4), "bad size of atmosphere_result");
|
||||||
|
|
||||||
|
// 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_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] and cursor[entries]
|
||||||
|
const GLuint INSTANCE_OUTPUT_SSBO_BINDING = 14; // modelview matrices of the instances which passed, by entry
|
||||||
|
const GLuint INSTANCE_CULL_GROUP_SIZE = 64;
|
||||||
|
const GLuint INSTANCE_OFFSETS_GROUP_SIZE = 1024;
|
||||||
|
// uints of the header of the counter buffer: frame number, survivors in total, entries, unused.
|
||||||
|
// an entry is a lod band of a group: survivors of group g in band b are counted at entry entryfirst(g) + b
|
||||||
|
const GLuint INSTANCE_COUNTER_HEADER = 4;
|
||||||
|
|
||||||
|
// layout std430: a bucket of a scene cell queued in the pass (GLSL: instance_cull_job)
|
||||||
|
struct instance_cull_job
|
||||||
|
{
|
||||||
|
packed_vec3 origin; // origin of the records of the bucket, relative to the camera of the pass
|
||||||
|
uint32_t first; // first record of the bucket in the record buffer
|
||||||
|
packed_vec3 distanceorigin; // the same origin, relative to the camera the distance is measured from
|
||||||
|
uint32_t count; // records of the bucket
|
||||||
|
uint32_t group;
|
||||||
|
uint32_t candidatestart; // records of the jobs before this one
|
||||||
|
uint32_t pad[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(instance_cull_job) == 48, "bad size of instance_cull_job");
|
||||||
|
|
||||||
|
// layout std430: instances of one model with one set of skins, the unit drawn together (GLSL: instance_cull_group)
|
||||||
|
struct instance_cull_group
|
||||||
|
{
|
||||||
|
uint32_t lodfirst; // first bound in the lod buffer
|
||||||
|
uint32_t lodcount; // bounds; the group has lodcount + 1 bands
|
||||||
|
uint32_t entryfirst; // first entry of the counters
|
||||||
|
uint32_t pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(instance_cull_group) == 16, "bad size of instance_cull_group");
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ public:
|
|||||||
cube_inside( float3 const &Center, float const Size ) const { return cube_inside( Center.x, Center.y, Center.z, Size ); }
|
cube_inside( float3 const &Center, float const Size ) const { return cube_inside( Center.x, Center.y, Center.z, Size ); }
|
||||||
bool
|
bool
|
||||||
cube_inside( float const X, float const Y, float const Z, float const Size ) const;
|
cube_inside( float const X, float const Y, float const Z, float const Size ) const;
|
||||||
|
// A, B, C and D of the planes, in world coordinates
|
||||||
|
inline
|
||||||
|
auto const &
|
||||||
|
planes() const { return m_frustum; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// types:
|
// types:
|
||||||
|
|||||||
312
rendering/opengl46/opengl46instancecull.cpp
Normal file
312
rendering/opengl46/opengl46instancecull.cpp
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "rendering/opengl46/opengl46instancecull.h"
|
||||||
|
|
||||||
|
#include "model/Model3d.h"
|
||||||
|
#include "rendering/frustum.h"
|
||||||
|
#include "utilities/Globals.h"
|
||||||
|
#include "utilities/utilities.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
constexpr GLsizeiptr RECORD_SIZE{sizeof(scene::basic_cell::instance_record)};
|
||||||
|
|
||||||
|
// grows a buffer to hold at least Size bytes, at least doubling it. a new buffer drops the contents and the binding
|
||||||
|
void reserve(gl::ssbo &Buffer, GLsizeiptr const Size)
|
||||||
|
{
|
||||||
|
if (Size > Buffer.size())
|
||||||
|
Buffer.resize(std::max(Size, 2 * Buffer.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void opengl46_instance_cull::init()
|
||||||
|
{
|
||||||
|
auto const make_compute = [](std::string const &Name) {
|
||||||
|
gl::shader shader(Name);
|
||||||
|
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||||
|
};
|
||||||
|
m_cull_program = make_compute("instance_cull.comp");
|
||||||
|
m_offsets_program = make_compute("instance_cull_offsets.comp");
|
||||||
|
|
||||||
|
auto const readbacksize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES) * sizeof(std::uint32_t))};
|
||||||
|
m_records = std::make_unique<gl::ssbo>(65536 * RECORD_SIZE, gl::INSTANCE_RECORD_SSBO_BINDING);
|
||||||
|
m_jobs = std::make_unique<gl::ssbo>(4096 * sizeof(gl::instance_cull_job), gl::INSTANCE_JOB_SSBO_BINDING);
|
||||||
|
m_groups = std::make_unique<gl::ssbo>(1024 * sizeof(gl::instance_cull_group), gl::INSTANCE_GROUP_SSBO_BINDING);
|
||||||
|
m_lods = std::make_unique<gl::ssbo>(4096 * sizeof(float), gl::INSTANCE_LOD_SSBO_BINDING);
|
||||||
|
// the readback copies a fixed size from it
|
||||||
|
m_counters = std::make_unique<gl::ssbo>(readbacksize, gl::INSTANCE_COUNTER_SSBO_BINDING);
|
||||||
|
m_output = std::make_unique<gl::ssbo>(65536 * sizeof(glm::mat4), gl::INSTANCE_OUTPUT_SSBO_BINDING);
|
||||||
|
m_readback = std::make_unique<gl::readback>(readbacksize);
|
||||||
|
m_readbackdata.resize(gl::INSTANCE_COUNTER_HEADER + READBACK_ENTRIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::begin_pass(pass_setup const &Setup)
|
||||||
|
{
|
||||||
|
collect();
|
||||||
|
|
||||||
|
m_inpass = true;
|
||||||
|
m_dispatched = false;
|
||||||
|
m_setup = Setup;
|
||||||
|
m_buckets.clear();
|
||||||
|
m_joblist.clear();
|
||||||
|
m_grouplist.clear();
|
||||||
|
m_lodlist.clear();
|
||||||
|
m_groupmap.clear();
|
||||||
|
m_cpucounts.clear();
|
||||||
|
m_candidates = 0;
|
||||||
|
m_entries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket)
|
||||||
|
{
|
||||||
|
if (!m_inpass || Bucket.records.empty() || Key.pModel == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto lookup{m_groupmap.find(Key)};
|
||||||
|
if (lookup == m_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())};
|
||||||
|
m_pending.clear();
|
||||||
|
m_pending.push_back(Key.pModel->Root);
|
||||||
|
while (!m_pending.empty())
|
||||||
|
{
|
||||||
|
auto const *submodel{m_pending.back()};
|
||||||
|
m_pending.pop_back();
|
||||||
|
if (submodel == nullptr)
|
||||||
|
continue;
|
||||||
|
if (submodel->fSquareMinDist > 0.f)
|
||||||
|
m_lodlist.emplace_back(submodel->fSquareMinDist);
|
||||||
|
if (submodel->fSquareMaxDist < std::numeric_limits<float>::max())
|
||||||
|
m_lodlist.emplace_back(submodel->fSquareMaxDist);
|
||||||
|
m_pending.push_back(submodel->Child);
|
||||||
|
m_pending.push_back(submodel->Next);
|
||||||
|
}
|
||||||
|
std::sort(m_lodlist.begin() + lodfirst, m_lodlist.end());
|
||||||
|
m_lodlist.erase(std::unique(m_lodlist.begin() + lodfirst, m_lodlist.end()), m_lodlist.end());
|
||||||
|
auto const lodcount{static_cast<std::uint32_t>(m_lodlist.size()) - lodfirst};
|
||||||
|
|
||||||
|
group_data const group{static_cast<std::uint32_t>(m_grouplist.size()), m_entries, lodcount + 1};
|
||||||
|
m_grouplist.push_back({lodfirst, lodcount, m_entries, 0});
|
||||||
|
m_entries += group.entrycount;
|
||||||
|
lookup = m_groupmap.emplace(Key, group).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
place(Bucket);
|
||||||
|
|
||||||
|
gl::instance_cull_job job{};
|
||||||
|
job.origin = packed_vec3{Bucket.recordorigin - m_setup.camera};
|
||||||
|
job.first = Bucket.gpufirst;
|
||||||
|
job.distanceorigin = packed_vec3{Bucket.recordorigin - m_setup.distancecamera};
|
||||||
|
job.count = static_cast<std::uint32_t>(Bucket.records.size());
|
||||||
|
job.group = lookup->second.index;
|
||||||
|
job.candidatestart = m_candidates;
|
||||||
|
m_joblist.push_back(job);
|
||||||
|
m_buckets.push_back(&Bucket);
|
||||||
|
m_candidates += job.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::place(scene::basic_cell::instance_bucket const &Bucket)
|
||||||
|
{
|
||||||
|
auto const count{static_cast<std::uint32_t>(Bucket.records.size())};
|
||||||
|
auto const current{Bucket.gpugeneration == m_recordgeneration};
|
||||||
|
if (current && !Bucket.gpustale)
|
||||||
|
return;
|
||||||
|
if (current && count <= Bucket.gpucapacity)
|
||||||
|
{
|
||||||
|
m_records->update(Bucket.records.data(), static_cast<GLintptr>(Bucket.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(count) * RECORD_SIZE);
|
||||||
|
Bucket.gpustale = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a new place, with some room for instances the editor adds
|
||||||
|
auto const allocate = [this](scene::basic_cell::instance_bucket const &Target) {
|
||||||
|
auto const records{static_cast<std::uint32_t>(Target.records.size())};
|
||||||
|
Target.gpufirst = m_recordfill;
|
||||||
|
Target.gpucapacity = records + records / 4;
|
||||||
|
Target.gpugeneration = m_recordgeneration;
|
||||||
|
Target.gpustale = false;
|
||||||
|
m_recordfill += Target.gpucapacity;
|
||||||
|
m_records->update(Target.records.data(), static_cast<GLintptr>(Target.gpufirst) * RECORD_SIZE, static_cast<GLsizeiptr>(records) * RECORD_SIZE);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const capacity{static_cast<std::uint32_t>(m_records->size() / RECORD_SIZE)};
|
||||||
|
if (m_recordfill + count + count / 4 > capacity)
|
||||||
|
{
|
||||||
|
// the buffer is started over, and only the buckets of this pass get places again. the places left behind by
|
||||||
|
// buckets which were made anew or are out of sight are dropped this way
|
||||||
|
GLsizeiptr live{count + count / 4};
|
||||||
|
for (auto const *bucket : m_buckets)
|
||||||
|
live += bucket->records.size() + bucket->records.size() / 4;
|
||||||
|
if (2 * live * RECORD_SIZE > m_records->size())
|
||||||
|
m_records->resize(std::max(2 * live * RECORD_SIZE, 2 * m_records->size()));
|
||||||
|
++m_recordgeneration;
|
||||||
|
m_recordfill = 0;
|
||||||
|
for (std::size_t job = 0; job < m_buckets.size(); ++job)
|
||||||
|
{
|
||||||
|
allocate(*m_buckets[job]);
|
||||||
|
m_joblist[job].first = m_buckets[job]->gpufirst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allocate(Bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::dispatch(glm::mat4 const &View)
|
||||||
|
{
|
||||||
|
if (!m_inpass || m_joblist.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const countersize{static_cast<GLsizeiptr>((gl::INSTANCE_COUNTER_HEADER + 2 * m_entries) * sizeof(std::uint32_t))};
|
||||||
|
reserve(*m_jobs, static_cast<GLsizeiptr>(m_joblist.size() * sizeof(gl::instance_cull_job)));
|
||||||
|
reserve(*m_groups, static_cast<GLsizeiptr>(m_grouplist.size() * sizeof(gl::instance_cull_group)));
|
||||||
|
reserve(*m_lods, static_cast<GLsizeiptr>(std::max<std::size_t>(m_lodlist.size(), 1) * sizeof(float)));
|
||||||
|
reserve(*m_counters, countersize);
|
||||||
|
reserve(*m_output, static_cast<GLsizeiptr>(m_candidates) * static_cast<GLsizeiptr>(sizeof(glm::mat4)));
|
||||||
|
|
||||||
|
m_jobs->update(m_joblist.data(), 0, static_cast<GLsizeiptr>(m_joblist.size() * sizeof(gl::instance_cull_job)));
|
||||||
|
m_groups->update(m_grouplist.data(), 0, static_cast<GLsizeiptr>(m_grouplist.size() * sizeof(gl::instance_cull_group)));
|
||||||
|
if (!m_lodlist.empty())
|
||||||
|
m_lods->update(m_lodlist.data(), 0, static_cast<GLsizeiptr>(m_lodlist.size() * sizeof(float)));
|
||||||
|
// counts and cursors start at zero
|
||||||
|
glClearNamedBufferSubData(*m_counters, GL_R32UI, 0, countersize, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
|
||||||
|
std::uint32_t const header[gl::INSTANCE_COUNTER_HEADER]{m_setup.frame, 0, m_entries, 0};
|
||||||
|
m_counters->update(header, 0, sizeof(header));
|
||||||
|
|
||||||
|
m_records->bind_base();
|
||||||
|
m_jobs->bind_base();
|
||||||
|
m_groups->bind_base();
|
||||||
|
m_lods->bind_base();
|
||||||
|
m_counters->bind_base();
|
||||||
|
m_output->bind_base();
|
||||||
|
|
||||||
|
// planes moved to the camera of the pass, in double precision: the cpu tests world coordinates
|
||||||
|
auto const &frustumplanes{m_setup.frustum->planes()};
|
||||||
|
glm::vec4 planes[6];
|
||||||
|
for (int plane = 0; plane < 6; ++plane)
|
||||||
|
{
|
||||||
|
auto const *source{frustumplanes[plane]};
|
||||||
|
planes[plane] = glm::vec4{source[0], source[1], source[2],
|
||||||
|
static_cast<float>(source[0] * m_setup.camera.x + source[1] * m_setup.camera.y + source[2] * m_setup.camera.z + source[3])};
|
||||||
|
}
|
||||||
|
glProgramUniformMatrix4fv(*m_cull_program, 0, 1, GL_FALSE, glm::value_ptr(View));
|
||||||
|
glProgramUniform4fv(*m_cull_program, 1, 6, glm::value_ptr(planes[0]));
|
||||||
|
glProgramUniform4f(*m_cull_program, 7, m_setup.distancescale, m_setup.distanceoffset, m_setup.distancerange, m_setup.drawrange);
|
||||||
|
glProgramUniform4f(*m_cull_program, 8, m_setup.zoom, 0.f, 0.f, 0.f);
|
||||||
|
glProgramUniform4ui(*m_offsets_program, 0, m_entries, 0, 0, 0);
|
||||||
|
|
||||||
|
// one invocation per record; past the count limit of one dimension the groups go into rows
|
||||||
|
auto const groups{(m_candidates + gl::INSTANCE_CULL_GROUP_SIZE - 1) / gl::INSTANCE_CULL_GROUP_SIZE};
|
||||||
|
auto const groups_x{std::min<GLuint>(groups, 65535)};
|
||||||
|
auto const groups_y{(groups + groups_x - 1) / groups_x};
|
||||||
|
|
||||||
|
m_timer.begin();
|
||||||
|
glProgramUniform4ui(*m_cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 0);
|
||||||
|
m_cull_program->dispatch(groups_x, groups_y);
|
||||||
|
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||||
|
m_offsets_program->dispatch(1);
|
||||||
|
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||||
|
glProgramUniform4ui(*m_cull_program, 9, static_cast<GLuint>(m_joblist.size()), m_candidates, m_entries, 1);
|
||||||
|
m_cull_program->dispatch(groups_x, groups_y);
|
||||||
|
// the output and the counts go to draws, indirect draws and the copy for the cpu
|
||||||
|
gl::memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
|
||||||
|
m_timer.end();
|
||||||
|
|
||||||
|
m_readback->push(*m_counters);
|
||||||
|
m_cpucounts.assign(m_entries, 0);
|
||||||
|
m_dispatched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::cpu_result(scene::basic_cell::instance_bucket_key const &Key, std::vector<std::uint32_t> const &Bandcounts)
|
||||||
|
{
|
||||||
|
if (!m_dispatched)
|
||||||
|
return;
|
||||||
|
auto const lookup{m_groupmap.find(Key)};
|
||||||
|
if (lookup == m_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)
|
||||||
|
m_cpucounts[group.entryfirst + band] += Bandcounts[band];
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::end_pass()
|
||||||
|
{
|
||||||
|
if (m_dispatched)
|
||||||
|
{
|
||||||
|
std::uint64_t total{0};
|
||||||
|
for (auto const count : m_cpucounts)
|
||||||
|
total += count;
|
||||||
|
m_cpupasses.push_back({m_setup.frame, m_cpucounts, total});
|
||||||
|
// the gpu is never this far behind
|
||||||
|
while (m_cpupasses.size() > 8)
|
||||||
|
m_cpupasses.pop_front();
|
||||||
|
}
|
||||||
|
m_inpass = false;
|
||||||
|
m_dispatched = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opengl46_instance_cull::collect()
|
||||||
|
{
|
||||||
|
if (!m_readback || !m_readback->pop(m_readbackdata.data()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const frame{m_readbackdata[0]};
|
||||||
|
while (!m_cpupasses.empty() && m_cpupasses.front().frame != frame)
|
||||||
|
m_cpupasses.pop_front();
|
||||||
|
if (m_cpupasses.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const &cpu{m_cpupasses.front()};
|
||||||
|
comparison result;
|
||||||
|
result.frame = frame;
|
||||||
|
result.gputotal = m_readbackdata[1];
|
||||||
|
result.cputotal = cpu.total;
|
||||||
|
result.entries = m_readbackdata[2];
|
||||||
|
result.compared = std::min<std::uint32_t>({result.entries, READBACK_ENTRIES, static_cast<std::uint32_t>(cpu.counts.size())});
|
||||||
|
for (std::uint32_t entry = 0; entry < result.compared; ++entry)
|
||||||
|
{
|
||||||
|
auto const gpu{m_readbackdata[gl::INSTANCE_COUNTER_HEADER + entry]};
|
||||||
|
auto const difference{gpu > cpu.counts[entry] ? gpu - cpu.counts[entry] : cpu.counts[entry] - gpu};
|
||||||
|
if (difference > 0)
|
||||||
|
{
|
||||||
|
++result.mismatched;
|
||||||
|
result.largestdifference = std::max(result.largestdifference, difference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_cpupasses.pop_front();
|
||||||
|
|
||||||
|
m_last = result;
|
||||||
|
++m_comparedframes;
|
||||||
|
if ((result.mismatched > 0) || (result.gputotal != result.cputotal) || (result.entries != cpu.counts.size()))
|
||||||
|
++m_mismatchedframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string opengl46_instance_cull::info()
|
||||||
|
{
|
||||||
|
if (!Global.gfx_gpucull)
|
||||||
|
return {};
|
||||||
|
if (m_comparedframes == 0)
|
||||||
|
return "\ngpu cull: no result yet";
|
||||||
|
return "\ngpu cull: " + 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::string opengl46_instance_cull::info_times()
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
if (!Global.gfx_gpucull)
|
||||||
|
return text;
|
||||||
|
if (auto const time = m_timer.result())
|
||||||
|
text += " instance cull: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||||
|
return text;
|
||||||
|
}
|
||||||
144
rendering/opengl46/opengl46instancecull.h
Normal file
144
rendering/opengl46/opengl46instancecull.h
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gl/shader.h"
|
||||||
|
#include "gl/ssbo.h"
|
||||||
|
#include "gl/readback.h"
|
||||||
|
#include "gl/query.h"
|
||||||
|
#include "gl/ubo.h"
|
||||||
|
#include "scene/scene.h"
|
||||||
|
|
||||||
|
class cFrustum;
|
||||||
|
|
||||||
|
// culling of the model instances of a render pass in compute passes (instance_cull.comp, instance_cull_offsets.comp),
|
||||||
|
// the gpu side of opengl46_renderer::Queue_Instances() and Render_Instanced():
|
||||||
|
// - the records of the instances (scene::basic_cell::instance_record) are kept in one buffer, each bucket of a scene cell
|
||||||
|
// in its own place, and sent again only when the bucket made them anew
|
||||||
|
// - every bucket queued in the pass becomes a job; the buckets of one model and one set of skins make a group, whose
|
||||||
|
// lod bands are its entries
|
||||||
|
// - the passes count the instances which pass per entry, turn the counts into places in the output and write the
|
||||||
|
// modelview matrices of the instances there
|
||||||
|
// so far the result is only compared with the one of the cpu, which still draws: the counts per entry are read back a few
|
||||||
|
// frames later and set against the counts Render_Instanced() reported for the same frame (Global.gfx_gpucull)
|
||||||
|
class opengl46_instance_cull
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// what a pass needs for the tests, the way Render_Instanced() takes it
|
||||||
|
struct pass_setup
|
||||||
|
{
|
||||||
|
unsigned int frame{0};
|
||||||
|
glm::dvec3 camera{0.0}; // camera of the pass: frustum and placement
|
||||||
|
glm::dvec3 distancecamera{0.0}; // the distance is measured from it
|
||||||
|
cFrustum const *frustum{nullptr}; // of the pass camera, in world coordinates
|
||||||
|
float distancescale{1.f}; // of the squared distance
|
||||||
|
float distanceoffset{0.f}; // added to the scaled squared distance past the range test
|
||||||
|
float distancerange{0.f}; // squared range of the scaled distance before the offset
|
||||||
|
float drawrange{0.f}; // squared draw range
|
||||||
|
float zoom{1.f};
|
||||||
|
};
|
||||||
|
|
||||||
|
opengl46_instance_cull() = default;
|
||||||
|
opengl46_instance_cull(const opengl46_instance_cull &) = delete;
|
||||||
|
opengl46_instance_cull &operator=(const opengl46_instance_cull &) = delete;
|
||||||
|
|
||||||
|
// creates the programs and buffers. throws gl::shader_exception
|
||||||
|
void init();
|
||||||
|
// starts collecting the buckets of a pass
|
||||||
|
void begin_pass(pass_setup const &Setup);
|
||||||
|
bool in_pass() const
|
||||||
|
{
|
||||||
|
return m_inpass;
|
||||||
|
}
|
||||||
|
// adds the records of a bucket (made already) to the pass, sending them to the gpu when they changed
|
||||||
|
void add_bucket(scene::basic_cell::instance_bucket_key const &Key, scene::basic_cell::instance_bucket const &Bucket);
|
||||||
|
// runs the culling passes for the buckets added. View: rotation of the view of the pass
|
||||||
|
void dispatch(glm::mat4 const &View);
|
||||||
|
// 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);
|
||||||
|
// ends the pass: its cpu counts are kept for the comparison
|
||||||
|
void end_pass();
|
||||||
|
// lines for the debug panel
|
||||||
|
std::string info();
|
||||||
|
std::string info_times();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct group_data
|
||||||
|
{
|
||||||
|
std::uint32_t index;
|
||||||
|
std::uint32_t entryfirst;
|
||||||
|
std::uint32_t entrycount;
|
||||||
|
};
|
||||||
|
// counts of the cpu of a pass, waiting for those of the gpu
|
||||||
|
struct cpu_pass
|
||||||
|
{
|
||||||
|
unsigned int frame;
|
||||||
|
std::vector<std::uint32_t> counts;
|
||||||
|
std::uint64_t total;
|
||||||
|
};
|
||||||
|
struct comparison
|
||||||
|
{
|
||||||
|
unsigned int frame{0};
|
||||||
|
std::uint64_t gputotal{0};
|
||||||
|
std::uint64_t cputotal{0};
|
||||||
|
std::uint32_t entries{0};
|
||||||
|
std::uint32_t compared{0}; // entries compared, at most READBACK_ENTRIES
|
||||||
|
std::uint32_t mismatched{0};
|
||||||
|
std::uint32_t largestdifference{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// takes the newest counts of the gpu which arrived and compares them with the ones of the cpu
|
||||||
|
void collect();
|
||||||
|
|
||||||
|
// entries read back for the comparison; with more of them only the totals and these are compared
|
||||||
|
static constexpr std::uint32_t READBACK_ENTRIES{16384};
|
||||||
|
|
||||||
|
std::unique_ptr<gl::program> m_cull_program;
|
||||||
|
std::unique_ptr<gl::program> m_offsets_program;
|
||||||
|
std::unique_ptr<gl::ssbo> m_records;
|
||||||
|
std::unique_ptr<gl::ssbo> m_jobs;
|
||||||
|
std::unique_ptr<gl::ssbo> m_groups;
|
||||||
|
std::unique_ptr<gl::ssbo> m_lods;
|
||||||
|
std::unique_ptr<gl::ssbo> m_counters;
|
||||||
|
std::unique_ptr<gl::ssbo> m_output;
|
||||||
|
std::unique_ptr<gl::readback> m_readback;
|
||||||
|
gl::gpu_timer m_timer;
|
||||||
|
|
||||||
|
// record buffer: records placed so far, the generation which tells current places from the ones of a replaced buffer
|
||||||
|
std::uint32_t m_recordfill{0};
|
||||||
|
unsigned int m_recordgeneration{1};
|
||||||
|
|
||||||
|
bool m_inpass{false};
|
||||||
|
pass_setup m_setup;
|
||||||
|
std::vector<scene::basic_cell::instance_bucket const *> m_buckets; // of the jobs, in their order
|
||||||
|
std::vector<gl::instance_cull_job> m_joblist;
|
||||||
|
std::vector<gl::instance_cull_group> m_grouplist;
|
||||||
|
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;
|
||||||
|
std::vector<TSubModel const *> m_pending; // walk of the submodel tree
|
||||||
|
std::uint32_t m_candidates{0};
|
||||||
|
std::uint32_t m_entries{0};
|
||||||
|
bool m_dispatched{false};
|
||||||
|
|
||||||
|
std::vector<std::uint32_t> m_cpucounts; // of the current pass, by entry
|
||||||
|
std::deque<cpu_pass> m_cpupasses;
|
||||||
|
std::vector<std::uint32_t> m_readbackdata;
|
||||||
|
comparison m_last;
|
||||||
|
std::uint64_t m_comparedframes{0};
|
||||||
|
std::uint64_t m_mismatchedframes{0};
|
||||||
|
};
|
||||||
@@ -188,6 +188,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
m_sky.init();
|
m_sky.init();
|
||||||
|
m_instancecull.init();
|
||||||
m_exposure.init();
|
m_exposure.init();
|
||||||
m_ao.init();
|
m_ao.init();
|
||||||
}
|
}
|
||||||
@@ -721,6 +722,7 @@ void opengl46_renderer::SwapBuffers()
|
|||||||
m_debugtimestext += m_sky.info_times();
|
m_debugtimestext += m_sky.info_times();
|
||||||
m_debugtimestext += m_exposure.info_times();
|
m_debugtimestext += m_exposure.info_times();
|
||||||
m_debugtimestext += m_ao.info_times();
|
m_debugtimestext += m_ao.info_times();
|
||||||
|
m_debugtimestext += m_instancecull.info_times();
|
||||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||||
if( DebugModeFlag )
|
if( DebugModeFlag )
|
||||||
m_debugtimestext += m_textures.info();
|
m_debugtimestext += m_textures.info();
|
||||||
@@ -762,7 +764,8 @@ void opengl46_renderer::SwapBuffers()
|
|||||||
+ "\nlights: " + to_string( static_cast<int>( m_lights_lit ), 7 )
|
+ "\nlights: " + to_string( static_cast<int>( m_lights_lit ), 7 )
|
||||||
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), "
|
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), "
|
||||||
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
|
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
|
||||||
+ "\n" + m_locallights.info();
|
+ "\n" + m_locallights.info()
|
||||||
|
+ m_instancecull.info();
|
||||||
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
||||||
m_debugstatstext +=
|
m_debugstatstext +=
|
||||||
"\ncluster lists: " + std::to_string( cullstats->index_count )
|
"\ncluster lists: " + std::to_string( cullstats->index_count )
|
||||||
@@ -823,6 +826,15 @@ void opengl46_renderer::draw_debug_ui()
|
|||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
if (ImGui::Begin("Instance culling"))
|
||||||
|
{
|
||||||
|
ImGui::Checkbox("cull instances on the gpu too, compare with the cpu (gfx.gpucull)", &Global.gfx_gpucull);
|
||||||
|
auto const info{m_instancecull.info()};
|
||||||
|
if (!info.empty())
|
||||||
|
ImGui::TextUnformatted(info.c_str() + 1);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
ImGui::SetNextWindowSize(ImVec2S(400, 400));
|
ImGui::SetNextWindowSize(ImVec2S(400, 400));
|
||||||
if (ImGui::Begin("Pickbuffer") && m_pick_tex) {
|
if (ImGui::Begin("Pickbuffer") && m_pick_tex) {
|
||||||
ImGui::Image((ImTextureID)(intptr_t)(m_pick_tex->id), ImGui::GetContentRegionAvail(), ImVec2(0, 1.0), ImVec2(1.0, 0));
|
ImGui::Image((ImTextureID)(intptr_t)(m_pick_tex->id), ImGui::GetContentRegionAvail(), ImVec2(0, 1.0), ImVec2(1.0, 0));
|
||||||
@@ -2548,6 +2560,30 @@ void opengl46_renderer::Render(scene::basic_region *Region)
|
|||||||
for( auto &bucket : m_frame_instance_buckets ) {
|
for( auto &bucket : m_frame_instance_buckets ) {
|
||||||
bucket.second.clear();
|
bucket.second.clear();
|
||||||
}
|
}
|
||||||
|
// the gpu culling runs along the first color pass of the main viewport of a frame, for its result to be compared
|
||||||
|
// with the one of the cpu
|
||||||
|
m_instancecullpass = false;
|
||||||
|
if( ( Global.gfx_gpucull )
|
||||||
|
&& ( m_renderpass.draw_mode == rendermode::color )
|
||||||
|
&& ( m_current_viewport != nullptr )
|
||||||
|
&& ( m_current_viewport->main )
|
||||||
|
&& ( m_instancecullframe != m_framestamp ) ) {
|
||||||
|
m_instancecullframe = m_framestamp;
|
||||||
|
m_instancecullpass = true;
|
||||||
|
// the same choices as in Render_Instanced()
|
||||||
|
auto const reflections { m_renderpass.draw_mode == rendermode::reflections };
|
||||||
|
opengl46_instance_cull::pass_setup setup;
|
||||||
|
setup.frame = m_framestamp;
|
||||||
|
setup.camera = m_renderpass.pass_camera.position();
|
||||||
|
setup.distancecamera = ( m_renderpass.draw_mode == rendermode::shadows ? m_renderpass.viewport_camera.position() : m_renderpass.pass_camera.position() );
|
||||||
|
setup.frustum = &m_renderpass.pass_camera.frustum();
|
||||||
|
setup.distancescale = ( reflections ? 1.f : static_cast<float>( 1.0 / ( static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.ZoomFactor ) * static_cast<double>( Global.fDistanceFactor ) ) ) );
|
||||||
|
setup.distanceoffset = ( reflections ? static_cast<float>( sq( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.f );
|
||||||
|
setup.distancerange = ( reflections ? static_cast<float>( sq( Global.reflectiontune.range_instances ) ) : std::numeric_limits<float>::max() );
|
||||||
|
setup.drawrange = static_cast<float>( sq( m_renderpass.draw_range + 250 ) );
|
||||||
|
setup.zoom = Global.ZoomFactor;
|
||||||
|
m_instancecull.begin_pass( setup );
|
||||||
|
}
|
||||||
// build a list of region sections to render
|
// build a list of region sections to render
|
||||||
glm::vec3 const cameraposition{m_renderpass.pass_camera.position()};
|
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));
|
auto const camerax = static_cast<int>(std::floor(cameraposition.x / scene::EU07_SECTIONSIZE + scene::EU07_REGIONSIDESECTIONCOUNT / 2));
|
||||||
@@ -2982,11 +3018,22 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
|||||||
// still happens inside Render_Instanced(), so correctness is unchanged -- only
|
// still happens inside Render_Instanced(), so correctness is unchanged -- only
|
||||||
// the number of calls and glBufferSubData round-trips drops sharply.
|
// the number of calls and glBufferSubData round-trips drops sharply.
|
||||||
// (For pickscenery/pickcontrols modes the map stays empty, so this is a no-op.)
|
// (For pickscenery/pickcontrols modes the map stays empty, so this is a no-op.)
|
||||||
|
if( m_instancecullpass ) {
|
||||||
|
// the view of the pass is in place here, the one Render_Instanced() takes
|
||||||
|
m_instancecull.dispatch( OpenGLMatrices.data( GL_MODELVIEW ) );
|
||||||
|
}
|
||||||
for( auto &bucket : m_frame_instance_buckets ) {
|
for( auto &bucket : m_frame_instance_buckets ) {
|
||||||
if( bucket.second.empty() ) { continue; }
|
if( bucket.second.empty() ) { continue; }
|
||||||
Render_Instanced( bucket.first.pModel, bucket.second );
|
Render_Instanced( bucket.first.pModel, bucket.second, ( m_instancecullpass ? &m_instance_bandcounts : nullptr ) );
|
||||||
|
if( m_instancecullpass ) {
|
||||||
|
m_instancecull.cpu_result( bucket.first, m_instance_bandcounts );
|
||||||
|
}
|
||||||
bucket.second.clear();
|
bucket.second.clear();
|
||||||
}
|
}
|
||||||
|
if( m_instancecullpass ) {
|
||||||
|
m_instancecull.end_pass();
|
||||||
|
m_instancecullpass = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void opengl46_renderer::Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end)
|
void opengl46_renderer::Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end)
|
||||||
@@ -3221,6 +3268,10 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|
|||||||
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
|
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
|
||||||
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
||||||
}
|
}
|
||||||
|
if( m_instancecullpass ) {
|
||||||
|
// the gpu tests every instance of the bucket, the way Render_Instanced() does
|
||||||
|
m_instancecull.add_bucket( bucket.first, instancebucket );
|
||||||
|
}
|
||||||
auto const &records { instancebucket.records };
|
auto const &records { instancebucket.records };
|
||||||
std::vector<queued_instance> *dest { nullptr }; // looked up when there's something to put in it
|
std::vector<queued_instance> *dest { nullptr }; // looked up when there's something to put in it
|
||||||
for( std::size_t idx = 0; idx < records.size(); ++idx ) {
|
for( std::size_t idx = 0; idx < records.size(); ++idx ) {
|
||||||
@@ -3270,7 +3321,7 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
|
|||||||
for( auto *instance : Bucket.instances ) {
|
for( auto *instance : Bucket.instances ) {
|
||||||
scene::basic_cell::instance_record record;
|
scene::basic_cell::instance_record record;
|
||||||
if( instance != nullptr ) {
|
if( instance != nullptr ) {
|
||||||
record.position = glm::vec3{ instance->location() - Origin };
|
record.position = packed_vec3{ instance->location() - Origin };
|
||||||
record.radius = instance->radius();
|
record.radius = instance->radius();
|
||||||
record.rangesquaredmin = tofloat( instance->m_rangesquaredmin );
|
record.rangesquaredmin = tofloat( instance->m_rangesquaredmin );
|
||||||
record.rangesquaredmax = tofloat( instance->m_rangesquaredmax );
|
record.rangesquaredmax = tofloat( instance->m_rangesquaredmax );
|
||||||
@@ -3286,6 +3337,7 @@ void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::b
|
|||||||
Bucket.records.emplace_back( record );
|
Bucket.records.emplace_back( record );
|
||||||
}
|
}
|
||||||
Bucket.recordsversion = m_instancerecordsversion;
|
Bucket.recordsversion = m_instancerecordsversion;
|
||||||
|
Bucket.gpustale = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
|
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
|
||||||
@@ -3315,8 +3367,9 @@ std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Mo
|
|||||||
// instance_modelview[i] is the camera-space root transform of an instance (precomputed below)
|
// instance_modelview[i] is the camera-space root transform of an instance (precomputed below)
|
||||||
// and model_ubs.modelview is the submodel-local chain (accumulated by the matrix stack starting
|
// and model_ubs.modelview is the submodel-local chain (accumulated by the matrix stack starting
|
||||||
// from identity, NOT the camera/instance frame).
|
// from identity, NOT the camera/instance frame).
|
||||||
void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances )
|
void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances, std::vector<std::uint32_t> *Bandcounts )
|
||||||
{
|
{
|
||||||
|
if( Bandcounts != nullptr ) { Bandcounts->clear(); }
|
||||||
if( Model == nullptr ) { return; }
|
if( Model == nullptr ) { return; }
|
||||||
if( Instances.empty() ) { return; }
|
if( Instances.empty() ) { return; }
|
||||||
|
|
||||||
@@ -3353,6 +3406,7 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<queued_in
|
|||||||
std::sort( m_instance_lodbounds.begin(), m_instance_lodbounds.end() );
|
std::sort( m_instance_lodbounds.begin(), m_instance_lodbounds.end() );
|
||||||
m_instance_lodbounds.erase( std::unique( m_instance_lodbounds.begin(), m_instance_lodbounds.end() ), m_instance_lodbounds.end() );
|
m_instance_lodbounds.erase( std::unique( m_instance_lodbounds.begin(), m_instance_lodbounds.end() ), m_instance_lodbounds.end() );
|
||||||
auto const bandcount { m_instance_lodbounds.size() + 1 };
|
auto const bandcount { m_instance_lodbounds.size() + 1 };
|
||||||
|
if( Bandcounts != nullptr ) { Bandcounts->assign( bandcount, 0 ); }
|
||||||
|
|
||||||
// 1. Visibility / distance cull, by the records of the instances. Collect surviving instances with
|
// 1. Visibility / distance cull, by the records of the instances. Collect surviving instances with
|
||||||
// their precomputed camera-space root modelview matrices and lod bands. m_instance_survivors and the
|
// their precomputed camera-space root modelview matrices and lod bands. m_instance_survivors and the
|
||||||
@@ -3456,6 +3510,11 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<queued_in
|
|||||||
for( auto const &survivor : m_instance_survivors ) {
|
for( auto const &survivor : m_instance_survivors ) {
|
||||||
m_instance_modelviews[ m_instance_bandcursors[ survivor.band ]++ ] = survivor.modelview;
|
m_instance_modelviews[ m_instance_bandcursors[ survivor.band ]++ ] = survivor.modelview;
|
||||||
}
|
}
|
||||||
|
if( Bandcounts != nullptr ) {
|
||||||
|
for( std::size_t band = 0; band < bandcount; ++band ) {
|
||||||
|
( *Bandcounts )[ band ] = static_cast<std::uint32_t>( m_instance_bandstarts[ ( band + 1 ) * distanceslots ] - m_instance_bandstarts[ band * distanceslots ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
// all bands go to the instance buffer at once, each band is drawn from its own base instance
|
// all bands go to the instance buffer at once, each band is drawn from its own base instance
|
||||||
auto const baseinstance { Upload_Instance_Modelviews( m_instance_modelviews.data(), m_instance_modelviews.size() ) };
|
auto const baseinstance { Upload_Instance_Modelviews( m_instance_modelviews.data(), m_instance_modelviews.size() ) };
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "rendering/opengl46/opengl46exposure.h"
|
#include "rendering/opengl46/opengl46exposure.h"
|
||||||
#include "rendering/opengl46/opengl46ao.h"
|
#include "rendering/opengl46/opengl46ao.h"
|
||||||
#include "rendering/opengl46/opengl46locallights.h"
|
#include "rendering/opengl46/opengl46locallights.h"
|
||||||
|
#include "rendering/opengl46/opengl46instancecull.h"
|
||||||
|
|
||||||
// bare-bones render controller, in lack of anything better yet
|
// bare-bones render controller, in lack of anything better yet
|
||||||
class opengl46_renderer : public gfx_renderer {
|
class opengl46_renderer : public gfx_renderer {
|
||||||
@@ -316,7 +317,8 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
// ones still are. Instances are individually frustum/distance culled by their records, the
|
// ones still are. Instances are individually frustum/distance culled by their records, the
|
||||||
// TAnimModel itself is read only for the first instance drawn. Each lod band drawn counts as
|
// TAnimModel itself is read only for the first instance drawn. Each lod band drawn counts as
|
||||||
// one instanced_drawcall in draw_stats; the instances drawn are added to the instances counter.
|
// one instanced_drawcall in draw_stats; the instances drawn are added to the instances counter.
|
||||||
void Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances );
|
// Bandcounts, when given, gets the number of instances drawn in each lod band.
|
||||||
|
void Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances, std::vector<std::uint32_t> *Bandcounts = nullptr );
|
||||||
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
|
// adds the bucketed instances of a scene cell to the frame-level buckets drawn by Render_Instanced(),
|
||||||
// less the ones their records show to be out of sight in the current render pass
|
// less the ones their records show to be out of sight in the current render pass
|
||||||
void Queue_Instances( scene::basic_cell const *Cell );
|
void Queue_Instances( scene::basic_cell const *Cell );
|
||||||
@@ -473,6 +475,12 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
// and whenever scene::node_state_version does, as instances are shown, hidden or drawn on their own
|
// and whenever scene::node_state_version does, as instances are shown, hidden or drawn on their own
|
||||||
unsigned int m_instancerecordsversion { 1 };
|
unsigned int m_instancerecordsversion { 1 };
|
||||||
unsigned int m_nodestateversion { 0 }; // scene::node_state_version which m_instancerecordsversion was last checked against
|
unsigned int m_nodestateversion { 0 }; // scene::node_state_version which m_instancerecordsversion was last checked against
|
||||||
|
// culling of instances in compute passes, run along the first color pass of the main viewport of a frame
|
||||||
|
// when Global.gfx_gpucull is set; for now its result is only compared with the one of the cpu
|
||||||
|
opengl46_instance_cull m_instancecull;
|
||||||
|
bool m_instancecullpass { false }; // the current pass feeds it
|
||||||
|
unsigned int m_instancecullframe { ~0u }; // frame it last ran in
|
||||||
|
std::vector<std::uint32_t> m_instance_bandcounts; // reported by Render_Instanced() for the comparison
|
||||||
renderpass_config m_colorpass; // parametrs of most recent color pass
|
renderpass_config m_colorpass; // parametrs of most recent color pass
|
||||||
std::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
|
std::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
|
||||||
std::vector<TSubModel const *> m_pickcontrolsitems;
|
std::vector<TSubModel const *> m_pickcontrolsitems;
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ public:
|
|||||||
inline
|
inline
|
||||||
std::vector<glm::vec4> &
|
std::vector<glm::vec4> &
|
||||||
frustum_points() { return m_frustumpoints; }
|
frustum_points() { return m_frustumpoints; }
|
||||||
|
inline
|
||||||
|
cFrustum const &
|
||||||
|
frustum() const { return m_frustum; }
|
||||||
// transforms provided set of clip space points to world space
|
// transforms provided set of clip space points to world space
|
||||||
template <class Iterator_>
|
template <class Iterator_>
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ public:
|
|||||||
// pass rejects most of them. the renderer makes the copies and goes by them, and reads the instance itself only
|
// pass rejects most of them. the renderer makes the copies and goes by them, and reads the instance itself only
|
||||||
// for the first one of a batch drawn. the layout follows std430, for the copies to be handed to the gpu as they are
|
// for the first one of a batch drawn. the layout follows std430, for the copies to be handed to the gpu as they are
|
||||||
struct instance_record {
|
struct instance_record {
|
||||||
glm::vec3 position { 0.f }; // location of the instance, relative to the origin of the records of its bucket
|
packed_vec3 position { 0.f }; // location of the instance, relative to the origin of the records of its bucket
|
||||||
float radius { 0.f };
|
float radius { 0.f };
|
||||||
float rangesquaredmin { 0.f }; // visibility range of the instance
|
float rangesquaredmin { 0.f }; // visibility range of the instance
|
||||||
float rangesquaredmax { 0.f };
|
float rangesquaredmax { 0.f };
|
||||||
@@ -251,6 +251,9 @@ public:
|
|||||||
glm::vec4 rotationscale[ 3 ] {}; // columns of rotation and scale of the instance, w unused
|
glm::vec4 rotationscale[ 3 ] {}; // columns of rotation and scale of the instance, w unused
|
||||||
};
|
};
|
||||||
static_assert( sizeof( instance_record ) == 80, "instance_record has to keep its std430 layout" );
|
static_assert( sizeof( instance_record ) == 80, "instance_record has to keep its std430 layout" );
|
||||||
|
static_assert( offsetof( instance_record, radius ) == 12, "instance_record has to keep its std430 layout" );
|
||||||
|
static_assert( offsetof( instance_record, flags ) == 24, "instance_record has to keep its std430 layout" );
|
||||||
|
static_assert( offsetof( instance_record, rotationscale ) == 32, "instance_record has to keep its std430 layout" );
|
||||||
enum instance_record_flags : std::uint32_t {
|
enum instance_record_flags : std::uint32_t {
|
||||||
instance_visible = 1 << 0,
|
instance_visible = 1 << 0,
|
||||||
instance_instanceable = 1 << 1, // false once an event gave the instance animations or a skin of its own
|
instance_instanceable = 1 << 1, // false once an event gave the instance animations or a skin of its own
|
||||||
@@ -262,6 +265,12 @@ public:
|
|||||||
mutable glm::dvec3 recordorigin { 0.0 }; // the records hold locations relative to it
|
mutable glm::dvec3 recordorigin { 0.0 }; // the records hold locations relative to it
|
||||||
mutable float lodrangesquared { 0.f }; // past it no submodel of the model of the bucket is drawn
|
mutable float lodrangesquared { 0.f }; // past it no submodel of the model of the bucket is drawn
|
||||||
mutable unsigned int recordsversion { 0 };
|
mutable unsigned int recordsversion { 0 };
|
||||||
|
// place of the records in the record buffer of the gpu culling: first record, room, and the generation of
|
||||||
|
// the buffer it is good for. gpustale: the records changed since they were sent
|
||||||
|
mutable std::uint32_t gpufirst { 0 };
|
||||||
|
mutable std::uint32_t gpucapacity { 0 };
|
||||||
|
mutable unsigned int gpugeneration { 0 };
|
||||||
|
mutable bool gpustale { true };
|
||||||
};
|
};
|
||||||
using instance_bucketdata_map = std::unordered_map< instance_bucket_key, instance_bucket, instance_bucket_key_hash >;
|
using instance_bucketdata_map = std::unordered_map< instance_bucket_key, instance_bucket, instance_bucket_key_hash >;
|
||||||
using sound_sequence = std::vector<sound_source *>;
|
using sound_sequence = std::vector<sound_source *>;
|
||||||
|
|||||||
151
shaders/instance_cull.comp
Normal file
151
shaders/instance_cull.comp
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// 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.
|
||||||
|
// stage 0 counts the instances which pass, 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 their modelview matrices there, in no particular order
|
||||||
|
|
||||||
|
#include <common>
|
||||||
|
|
||||||
|
layout(local_size_x = INSTANCE_CULL_GROUP_SIZE) in;
|
||||||
|
|
||||||
|
// scene::basic_cell::instance_record
|
||||||
|
struct instance_record
|
||||||
|
{
|
||||||
|
vec3 position; // relative to the origin of the bucket
|
||||||
|
float radius;
|
||||||
|
float rangesquaredmin;
|
||||||
|
float rangesquaredmax;
|
||||||
|
uint flags;
|
||||||
|
uint pad;
|
||||||
|
vec4 rotationscale[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct instance_cull_job
|
||||||
|
{
|
||||||
|
vec3 origin;
|
||||||
|
uint first;
|
||||||
|
vec3 distanceorigin;
|
||||||
|
uint count;
|
||||||
|
uint group;
|
||||||
|
uint candidatestart;
|
||||||
|
uint pad0;
|
||||||
|
uint pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct instance_cull_group
|
||||||
|
{
|
||||||
|
uint lodfirst;
|
||||||
|
uint lodcount;
|
||||||
|
uint entryfirst;
|
||||||
|
uint pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std430, binding = INSTANCE_RECORD_SSBO_BINDING) readonly buffer instance_record_ssbo
|
||||||
|
{
|
||||||
|
instance_record records[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = INSTANCE_JOB_SSBO_BINDING) readonly buffer instance_job_ssbo
|
||||||
|
{
|
||||||
|
instance_cull_job jobs[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = INSTANCE_GROUP_SSBO_BINDING) readonly buffer instance_group_ssbo
|
||||||
|
{
|
||||||
|
instance_cull_group groups[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = INSTANCE_LOD_SSBO_BINDING) readonly buffer instance_lod_ssbo
|
||||||
|
{
|
||||||
|
float lodbounds[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = INSTANCE_COUNTER_SSBO_BINDING) buffer instance_counter_ssbo
|
||||||
|
{
|
||||||
|
uint counters[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = INSTANCE_OUTPUT_SSBO_BINDING) writeonly buffer instance_output_ssbo
|
||||||
|
{
|
||||||
|
mat4 modelviews[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// rotation of the view of the pass: the instances are placed relative to its camera
|
||||||
|
layout(location = 0) uniform mat4 view;
|
||||||
|
// planes of the frustum of the pass, relative to its camera
|
||||||
|
layout(location = 1) uniform vec4 planes[6];
|
||||||
|
// x: scale of the squared distance, y: added to it past the range test, z: range of the scaled distance before the
|
||||||
|
// addition (reflections), w: squared draw range
|
||||||
|
layout(location = 7) uniform vec4 distance_params;
|
||||||
|
// x: zoom factor
|
||||||
|
layout(location = 8) uniform vec4 size_params;
|
||||||
|
// x: jobs, y: records in all jobs, z: entries, w: stage
|
||||||
|
layout(location = 9) uniform uvec4 counts;
|
||||||
|
|
||||||
|
const uint INSTANCE_VISIBLE = 1u;
|
||||||
|
const uint INSTANCE_INSTANCEABLE = 2u;
|
||||||
|
// smallest part of the view an instance has to take
|
||||||
|
const float MIN_SCREEN_SIZE = 0.003;
|
||||||
|
|
||||||
|
// the job which holds the given record: the last one starting at it or before it
|
||||||
|
uint find_job(uint candidate)
|
||||||
|
{
|
||||||
|
uint low = 0u;
|
||||||
|
uint high = counts.x - 1u;
|
||||||
|
while (low < high)
|
||||||
|
{
|
||||||
|
uint middle = (low + high + 1u) / 2u;
|
||||||
|
if (jobs[middle].candidatestart <= candidate)
|
||||||
|
low = middle;
|
||||||
|
else
|
||||||
|
high = middle - 1u;
|
||||||
|
}
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// the dispatch can be two dimensional when the records don't fit in the work group count limit of one
|
||||||
|
uint candidate = (gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x) * INSTANCE_CULL_GROUP_SIZE + gl_LocalInvocationIndex;
|
||||||
|
if (candidate >= counts.y)
|
||||||
|
return;
|
||||||
|
|
||||||
|
instance_cull_job job = jobs[find_job(candidate)];
|
||||||
|
instance_record record = records[job.first + (candidate - job.candidatestart)];
|
||||||
|
// instances hidden, or drawn on their own after getting animations or skins from events
|
||||||
|
if ((record.flags & (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE)) != (INSTANCE_VISIBLE | INSTANCE_INSTANCEABLE))
|
||||||
|
return;
|
||||||
|
|
||||||
|
vec3 location = job.origin + record.position;
|
||||||
|
for (int plane = 0; plane < 6; ++plane)
|
||||||
|
{
|
||||||
|
if (dot(planes[plane].xyz, location) + planes[plane].w <= -record.radius)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 offset = job.distanceorigin + record.position;
|
||||||
|
float distancesquared = dot(offset, offset) * distance_params.x;
|
||||||
|
if (distancesquared > distance_params.z)
|
||||||
|
return;
|
||||||
|
distancesquared += distance_params.y;
|
||||||
|
if ((distancesquared < record.rangesquaredmin) || (distancesquared >= record.rangesquaredmax))
|
||||||
|
return;
|
||||||
|
if (distancesquared > distance_params.w)
|
||||||
|
return;
|
||||||
|
if (record.radius * record.radius * size_params.x / distancesquared < MIN_SCREEN_SIZE * MIN_SCREEN_SIZE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// band: the number of lod bounds the distance reached, like upper_bound over the sorted bounds
|
||||||
|
instance_cull_group group = groups[job.group];
|
||||||
|
uint band = 0u;
|
||||||
|
for (uint bound = 0u; bound < group.lodcount; ++bound)
|
||||||
|
{
|
||||||
|
if (lodbounds[group.lodfirst + bound] <= distancesquared)
|
||||||
|
band = bound + 1u;
|
||||||
|
}
|
||||||
|
uint entry = group.entryfirst + band;
|
||||||
|
|
||||||
|
if (counts.w == 0u)
|
||||||
|
{
|
||||||
|
atomicAdd(counters[INSTANCE_COUNTER_HEADER + entry], 1u);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint slot = atomicAdd(counters[INSTANCE_COUNTER_HEADER + counts.z + entry], 1u);
|
||||||
|
modelviews[slot] = view * mat4(record.rotationscale[0], record.rotationscale[1], record.rotationscale[2], vec4(location, 1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
53
shaders/instance_cull_offsets.comp
Normal file
53
shaders/instance_cull_offsets.comp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// turns the counts of instances of the entries, written by stage 0 of instance_cull.comp, into the first slots of the
|
||||||
|
// entries in the output (cursors, which stage 1 advances as it writes), and puts the total in the header.
|
||||||
|
// a single work group: each invocation sums a run of the entries, then the sums are scanned in shared memory
|
||||||
|
|
||||||
|
#include <common>
|
||||||
|
|
||||||
|
layout(local_size_x = INSTANCE_OFFSETS_GROUP_SIZE) in;
|
||||||
|
|
||||||
|
layout(std430, binding = INSTANCE_COUNTER_SSBO_BINDING) buffer instance_counter_ssbo
|
||||||
|
{
|
||||||
|
uint counters[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// x: entries
|
||||||
|
layout(location = 0) uniform uvec4 counts;
|
||||||
|
|
||||||
|
shared uint sums[INSTANCE_OFFSETS_GROUP_SIZE];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
uint entries = counts.x;
|
||||||
|
uint invocation = gl_LocalInvocationIndex;
|
||||||
|
uint run = (entries + INSTANCE_OFFSETS_GROUP_SIZE - 1u) / INSTANCE_OFFSETS_GROUP_SIZE;
|
||||||
|
uint begin = min(invocation * run, entries);
|
||||||
|
uint end = min(begin + run, entries);
|
||||||
|
|
||||||
|
uint sum = 0u;
|
||||||
|
for (uint entry = begin; entry < end; ++entry)
|
||||||
|
sum += counters[INSTANCE_COUNTER_HEADER + entry];
|
||||||
|
sums[invocation] = sum;
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
|
||||||
|
// inclusive scan of the sums
|
||||||
|
for (uint step = 1u; step < INSTANCE_OFFSETS_GROUP_SIZE; step *= 2u)
|
||||||
|
{
|
||||||
|
uint added = (invocation >= step ? sums[invocation - step] : 0u);
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
sums[invocation] += added;
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint offset = sums[invocation] - sum;
|
||||||
|
for (uint entry = begin; entry < end; ++entry)
|
||||||
|
{
|
||||||
|
counters[INSTANCE_COUNTER_HEADER + entries + entry] = offset;
|
||||||
|
offset += counters[INSTANCE_COUNTER_HEADER + entry];
|
||||||
|
}
|
||||||
|
if (invocation == INSTANCE_OFFSETS_GROUP_SIZE - 1u)
|
||||||
|
counters[1] = sums[invocation];
|
||||||
|
}
|
||||||
@@ -1426,6 +1426,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
|||||||
Parser.getTokens(1);
|
Parser.getTokens(1);
|
||||||
Parser >> gfx_lights_clustered;
|
Parser >> gfx_lights_clustered;
|
||||||
}
|
}
|
||||||
|
else if (Token == "gfx.gpucull")
|
||||||
|
{
|
||||||
|
Parser.getTokens(1);
|
||||||
|
Parser >> gfx_gpucull;
|
||||||
|
}
|
||||||
else if (Token == "gfx.lights.clusterheatmap")
|
else if (Token == "gfx.lights.clusterheatmap")
|
||||||
{
|
{
|
||||||
Parser.getTokens(1);
|
Parser.getTokens(1);
|
||||||
@@ -1830,6 +1835,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
|||||||
export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled );
|
export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled );
|
||||||
export_as_text( Output, "gfx.lights.clustered", gfx_lights_clustered );
|
export_as_text( Output, "gfx.lights.clustered", gfx_lights_clustered );
|
||||||
export_as_text( Output, "gfx.lights.clusterheatmap", gfx_lights_clusterheatmap );
|
export_as_text( Output, "gfx.lights.clusterheatmap", gfx_lights_clusterheatmap );
|
||||||
|
export_as_text( Output, "gfx.gpucull", gfx_gpucull );
|
||||||
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );
|
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );
|
||||||
export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter );
|
export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter );
|
||||||
// TODO: export gfx_postfx_motionblur_format
|
// TODO: export gfx_postfx_motionblur_format
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ struct global_settings {
|
|||||||
bool gfx_envmap_enabled = true;
|
bool gfx_envmap_enabled = true;
|
||||||
bool gfx_lights_clustered = true; // headlights from the light lists of the clusters (no limit of lit headlights) or the first dynamiclights of them
|
bool gfx_lights_clustered = true; // headlights from the light lists of the clusters (no limit of lit headlights) or the first dynamiclights of them
|
||||||
bool gfx_lights_clusterheatmap = false; // debug view of the light clusters
|
bool gfx_lights_clusterheatmap = false; // debug view of the light clusters
|
||||||
|
bool gfx_gpucull = false; // culls model instances of the main view in a compute pass too, and compares the result with the cpu (opengl 4.6)
|
||||||
bool gfx_postfx_motionblur_enabled = true;
|
bool gfx_postfx_motionblur_enabled = true;
|
||||||
float gfx_postfx_motionblur_shutter = 0.01f;
|
float gfx_postfx_motionblur_shutter = 0.01f;
|
||||||
GLenum gfx_postfx_motionblur_format = GL_RG16F;
|
GLenum gfx_postfx_motionblur_format = GL_RG16F;
|
||||||
|
|||||||
Reference in New Issue
Block a user