mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 13:58:36 +01:00
Cull instances by compact records and draw them from a shader storage buffer
This commit is contained in:
@@ -14,7 +14,7 @@ void gl::glsl_common_setup()
|
|||||||
"#define MAX_LIGHTS " + std::to_string(MAX_LIGHTS) + "U\n" +
|
"#define MAX_LIGHTS " + std::to_string(MAX_LIGHTS) + "U\n" +
|
||||||
"#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 MAX_INSTANCES_PER_BATCH " + std::to_string(MAX_INSTANCES_PER_BATCH) + "U\n" +
|
"#define INSTANCE_SSBO_BINDING " + std::to_string(INSTANCE_SSBO_BINDING) + "\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" +
|
||||||
@@ -184,24 +184,21 @@ void gl::glsl_common_setup()
|
|||||||
vec4 wiper_timer_return;
|
vec4 wiper_timer_return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-instance modelview matrices for GPU-instanced draws.
|
#ifdef STAGE_VERTEX
|
||||||
// The UBO itself is declared in every stage so the binding stays valid,
|
// Per-instance modelview matrices for GPU-instanced draws, element 0 is identity.
|
||||||
// but the helper functions reference gl_InstanceID which is vertex-only,
|
// Draws which aren't instanced have gl_BaseInstance == 0 and gl_InstanceID == 0,
|
||||||
// so they are guarded behind STAGE_VERTEX.
|
// so effective_modelview() reduces to model_ubo.modelview. For instanced draws
|
||||||
layout (std140) uniform instance_ubo
|
// the C++ side uploads camera-space root transforms of the instances and draws
|
||||||
|
// them from base instance onwards, with model_ubo.modelview set to the submodel-local
|
||||||
|
// chain (computed from identity, NOT from the camera/instance), so the product
|
||||||
|
// gives the correct final transform per instance per submodel.
|
||||||
|
layout(std430, binding = INSTANCE_SSBO_BINDING) readonly buffer instance_ssbo
|
||||||
{
|
{
|
||||||
mat4 instance_modelview[MAX_INSTANCES_PER_BATCH];
|
mat4 instance_modelview[];
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef STAGE_VERTEX
|
|
||||||
// For non-instanced draws gl_InstanceID == 0 and slot 0 is permanently set
|
|
||||||
// to identity, so effective_modelview() reduces to model_ubo.modelview.
|
|
||||||
// For instanced draws the C++ side uploads camera-space root transforms for
|
|
||||||
// instances 0..N-1 and sets model_ubo.modelview to the submodel-local chain
|
|
||||||
// (computed from identity, NOT from the camera/instance), so the product
|
|
||||||
// gives the correct final transform per instance per submodel.
|
|
||||||
mat4 effective_modelview() {
|
mat4 effective_modelview() {
|
||||||
return instance_modelview[gl_InstanceID] * modelview;
|
return instance_modelview[gl_BaseInstance + gl_InstanceID] * modelview;
|
||||||
}
|
}
|
||||||
mat3 effective_modelviewnormal() {
|
mat3 effective_modelviewnormal() {
|
||||||
// instance_modelview can include per-instance scale (uniform or per-axis),
|
// instance_modelview can include per-instance scale (uniform or per-axis),
|
||||||
@@ -209,7 +206,7 @@ void gl::glsl_common_setup()
|
|||||||
// case. Compute the correct normal matrix via transpose(inverse(...)).
|
// case. Compute the correct normal matrix via transpose(inverse(...)).
|
||||||
// For pure rotation+translation this still produces the rotation matrix
|
// For pure rotation+translation this still produces the rotation matrix
|
||||||
// (one extra mat3 inverse per vertex — modern GPUs handle it cheaply).
|
// (one extra mat3 inverse per vertex — modern GPUs handle it cheaply).
|
||||||
mat3 instance_mv3 = mat3(instance_modelview[gl_InstanceID]);
|
mat3 instance_mv3 = mat3(instance_modelview[gl_BaseInstance + gl_InstanceID]);
|
||||||
mat3 instance_normal = transpose(inverse(instance_mv3));
|
mat3 instance_normal = transpose(inverse(instance_mv3));
|
||||||
return instance_normal * modelviewnormal;
|
return instance_normal * modelviewnormal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,9 +281,6 @@ void gl::program::init()
|
|||||||
|
|
||||||
if ((index = glGetUniformBlockIndex(*this, "model_ubo")) != GL_INVALID_INDEX)
|
if ((index = glGetUniformBlockIndex(*this, "model_ubo")) != GL_INVALID_INDEX)
|
||||||
glUniformBlockBinding(*this, index, 1);
|
glUniformBlockBinding(*this, index, 1);
|
||||||
|
|
||||||
if ((index = glGetUniformBlockIndex(*this, "instance_ubo")) != GL_INVALID_INDEX)
|
|
||||||
glUniformBlockBinding(*this, index, 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gl::program::program()
|
gl::program::program()
|
||||||
|
|||||||
15
gl/ubo.h
15
gl/ubo.h
@@ -104,17 +104,6 @@ namespace gl
|
|||||||
|
|
||||||
static_assert(sizeof(model_ubs) == 208 + 16 * MAX_PARAMS, "bad size of ubs");
|
static_assert(sizeof(model_ubs) == 208 + 16 * MAX_PARAMS, "bad size of ubs");
|
||||||
|
|
||||||
// maximum number of instances per single GPU-instanced draw call.
|
|
||||||
// 256 mat4 = 16 KiB, the guaranteed minimum UBO size. Bigger batches must split.
|
|
||||||
const size_t MAX_INSTANCES_PER_BATCH = 256;
|
|
||||||
|
|
||||||
struct instance_ubs
|
|
||||||
{
|
|
||||||
glm::mat4 instance_modelview[MAX_INSTANCES_PER_BATCH];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(instance_ubs) == 64 * MAX_INSTANCES_PER_BATCH, "bad size of instance_ubs");
|
|
||||||
|
|
||||||
// layout std430 (shader storage buffers)
|
// layout std430 (shader storage buffers)
|
||||||
// structs must match with GLSL, offsets checked below
|
// structs must match with GLSL, offsets checked below
|
||||||
|
|
||||||
@@ -295,6 +284,10 @@ namespace gl
|
|||||||
const GLuint ATMOSPHERE_RESULT_SSBO_BINDING = 7;
|
const GLuint ATMOSPHERE_RESULT_SSBO_BINDING = 7;
|
||||||
const GLuint ATMOSPHERE_UBO_BINDING = 4;
|
const GLuint ATMOSPHERE_UBO_BINDING = 4;
|
||||||
|
|
||||||
|
// binding point of the per-instance modelview matrices of instanced draws (glsl: instance_ssbo, mat4 per instance).
|
||||||
|
// element 0 is identity, the matrix of every draw which isn't instanced
|
||||||
|
const GLuint INSTANCE_SSBO_BINDING = 8;
|
||||||
|
|
||||||
// number of coefficients of the spherical harmonics of order 2 (bands 0..2)
|
// number of coefficients of the spherical harmonics of order 2 (bands 0..2)
|
||||||
const size_t SKY_SH_COEFFICIENTS = 9;
|
const size_t SKY_SH_COEFFICIENTS = 9;
|
||||||
|
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ std::shared_ptr<TAnimContainer> TAnimModel::AddContainer(std::string const &Name
|
|||||||
// containers are added by events after the instance was loaded and bucketed;
|
// containers are added by events after the instance was loaded and bucketed;
|
||||||
// the instanced path skips RaAnimate(), so this instance has to be drawn on its own
|
// the instanced path skips RaAnimate(), so this instance has to be drawn on its own
|
||||||
m_instanceable = false;
|
m_instanceable = false;
|
||||||
|
++scene::node_state_version;
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -781,6 +782,7 @@ void TAnimModel::SkinSet( int const Index, material_handle const Material ) {
|
|||||||
m_materialdata.replacable_skins[ std::clamp( Index, 1, 4 ) ] = Material;
|
m_materialdata.replacable_skins[ std::clamp( Index, 1, 4 ) ] = Material;
|
||||||
// the instance stays in the bucket of its old skin set, where it would get the textures of the batch
|
// the instance stays in the bucket of its old skin set, where it would get the textures of the batch
|
||||||
m_instanceable = false;
|
m_instanceable = false;
|
||||||
|
++scene::node_state_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TAnimModel::AnimUpdate(double dt)
|
void TAnimModel::AnimUpdate(double dt)
|
||||||
|
|||||||
@@ -327,9 +327,9 @@ geometry_bank::draw( gfx::geometry_handle const &Geometry, gfx::stream_units con
|
|||||||
|
|
||||||
// draws geometry stored in specified chunk InstanceCount times via instanced draw call
|
// draws geometry stored in specified chunk InstanceCount times via instanced draw call
|
||||||
std::size_t
|
std::size_t
|
||||||
geometry_bank::draw_instanced( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams ) {
|
geometry_bank::draw_instanced( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams ) {
|
||||||
if( InstanceCount == 0 ) { return 0; }
|
if( InstanceCount == 0 ) { return 0; }
|
||||||
return draw_instanced_( Geometry, Units, InstanceCount, Streams );
|
return draw_instanced_( Geometry, Units, InstanceCount, BaseInstance, Streams );
|
||||||
}
|
}
|
||||||
|
|
||||||
// draws geometry stored in supplied sequence of chunks of the bank
|
// draws geometry stored in supplied sequence of chunks of the bank
|
||||||
@@ -432,7 +432,7 @@ geometrybank_manager::draw( gfx::geometry_handle const &Geometry, unsigned int c
|
|||||||
|
|
||||||
// draws geometry stored in specified chunk InstanceCount times via instanced draw call
|
// draws geometry stored in specified chunk InstanceCount times via instanced draw call
|
||||||
void
|
void
|
||||||
geometrybank_manager::draw_instanced( gfx::geometry_handle const &Geometry, std::size_t const InstanceCount, unsigned int const Streams ) {
|
geometrybank_manager::draw_instanced( gfx::geometry_handle const &Geometry, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams ) {
|
||||||
|
|
||||||
if( Geometry == null_handle ) { return; }
|
if( Geometry == null_handle ) { return; }
|
||||||
if( InstanceCount == 0 ) { return; }
|
if( InstanceCount == 0 ) { return; }
|
||||||
@@ -440,7 +440,7 @@ geometrybank_manager::draw_instanced( gfx::geometry_handle const &Geometry, std:
|
|||||||
auto &bankrecord = bank( Geometry );
|
auto &bankrecord = bank( Geometry );
|
||||||
|
|
||||||
bankrecord.second = m_garbagecollector.timestamp();
|
bankrecord.second = m_garbagecollector.timestamp();
|
||||||
m_primitivecount += bankrecord.first->draw_instanced( Geometry, m_units, InstanceCount, Streams );
|
m_primitivecount += bankrecord.first->draw_instanced( Geometry, m_units, InstanceCount, BaseInstance, Streams );
|
||||||
}
|
}
|
||||||
|
|
||||||
// draws geometry stored in supplied sequence of chunks
|
// draws geometry stored in supplied sequence of chunks
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ public:
|
|||||||
auto append( gfx::vertex_array &Vertices, gfx::userdata_array& Userdata, gfx::geometry_handle const &Geometry ) -> bool;
|
auto append( gfx::vertex_array &Vertices, gfx::userdata_array& Userdata, gfx::geometry_handle const &Geometry ) -> bool;
|
||||||
// draws geometry stored in specified chunk
|
// draws geometry stored in specified chunk
|
||||||
auto draw( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) -> std::size_t;
|
auto draw( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) -> std::size_t;
|
||||||
// draws geometry stored in specified chunk N times via glDrawElementsInstanced*
|
// draws geometry stored in specified chunk N times via glDrawElementsInstanced*, as instances BaseInstance and onwards
|
||||||
auto draw_instanced( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams = basic_streams ) -> std::size_t;
|
auto draw_instanced( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams = basic_streams ) -> std::size_t;
|
||||||
// draws geometry stored in supplied sequence of chunks of the bank, in their order. the bank is free to combine them into fewer draw calls
|
// draws geometry stored in supplied sequence of chunks of the bank, in their order. the bank is free to combine them into fewer draw calls
|
||||||
auto draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) -> std::size_t;
|
auto draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) -> std::size_t;
|
||||||
// draws geometry stored in supplied list of chunks
|
// draws geometry stored in supplied list of chunks
|
||||||
@@ -185,7 +185,7 @@ private:
|
|||||||
// draw() subclass details
|
// draw() subclass details
|
||||||
virtual auto draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t = 0;
|
virtual auto draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t = 0;
|
||||||
// draw_instanced() subclass details. Default implementation falls back to N regular draws.
|
// draw_instanced() subclass details. Default implementation falls back to N regular draws.
|
||||||
virtual auto draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams ) -> std::size_t {
|
virtual auto draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams ) -> std::size_t {
|
||||||
std::size_t count { 0 };
|
std::size_t count { 0 };
|
||||||
for( std::size_t i = 0; i < InstanceCount; ++i ) { count += draw_( Geometry, Units, Streams ); }
|
for( std::size_t i = 0; i < InstanceCount; ++i ) { count += draw_( Geometry, Units, Streams ); }
|
||||||
return count; }
|
return count; }
|
||||||
@@ -223,8 +223,8 @@ public:
|
|||||||
// draws geometry stored in specified chunk
|
// draws geometry stored in specified chunk
|
||||||
void draw( gfx::geometry_handle const &Geometry, unsigned int const Streams = basic_streams );
|
void draw( gfx::geometry_handle const &Geometry, unsigned int const Streams = basic_streams );
|
||||||
// draws geometry stored in specified chunk InstanceCount times via GPU instancing.
|
// draws geometry stored in specified chunk InstanceCount times via GPU instancing.
|
||||||
// The shader reads per-instance modelview matrices from instance_ubo[gl_InstanceID].
|
// The shader reads per-instance modelview matrices from instance_ssbo[gl_BaseInstance + gl_InstanceID].
|
||||||
void draw_instanced( gfx::geometry_handle const &Geometry, std::size_t const InstanceCount, unsigned int const Streams = basic_streams );
|
void draw_instanced( gfx::geometry_handle const &Geometry, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams = basic_streams );
|
||||||
// draws geometry stored in supplied sequence of chunks, in their order. neighbouring chunks of the same bank are passed to it together
|
// draws geometry stored in supplied sequence of chunks, in their order. neighbouring chunks of the same bank are passed to it together
|
||||||
void draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, unsigned int const Streams = basic_streams );
|
void draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, unsigned int const Streams = basic_streams );
|
||||||
template <typename Iterator_>
|
template <typename Iterator_>
|
||||||
|
|||||||
@@ -171,9 +171,9 @@ opengl46_vaogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stre
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw_instanced() subclass details — single GL instanced draw for InstanceCount instances.
|
// draw_instanced() subclass details — single GL instanced draw for InstanceCount instances.
|
||||||
// The vertex shader reads per-instance modelview from instance_ubo[gl_InstanceID].
|
// The vertex shader reads per-instance modelview from instance_ssbo[gl_BaseInstance + gl_InstanceID].
|
||||||
std::size_t
|
std::size_t
|
||||||
opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams )
|
opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams )
|
||||||
{
|
{
|
||||||
setup_buffer();
|
setup_buffer();
|
||||||
|
|
||||||
@@ -199,16 +199,17 @@ opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry,
|
|||||||
|
|
||||||
if( chunkrecord.index_count > 0 ) {
|
if( chunkrecord.index_count > 0 ) {
|
||||||
m_vao->bind();
|
m_vao->bind();
|
||||||
::glDrawElementsInstancedBaseVertex(
|
::glDrawElementsInstancedBaseVertexBaseInstance(
|
||||||
chunk.type,
|
chunk.type,
|
||||||
chunkrecord.index_count, GL_UNSIGNED_INT,
|
chunkrecord.index_count, GL_UNSIGNED_INT,
|
||||||
reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ),
|
reinterpret_cast<void const *>( chunkrecord.index_offset * sizeof( gfx::basic_index ) ),
|
||||||
inst_count,
|
inst_count,
|
||||||
chunkrecord.vertex_offset );
|
chunkrecord.vertex_offset,
|
||||||
|
BaseInstance );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_vao->bind();
|
m_vao->bind();
|
||||||
::glDrawArraysInstanced( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count, inst_count );
|
::glDrawArraysInstancedBaseInstance( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count, inst_count, BaseInstance );
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const vertexcount { ( chunkrecord.index_count > 0 ? chunkrecord.index_count : chunkrecord.vertex_count ) };
|
auto const vertexcount { ( chunkrecord.index_count > 0 ? chunkrecord.index_count : chunkrecord.vertex_count ) };
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ private:
|
|||||||
// draw() subclass details
|
// draw() subclass details
|
||||||
auto
|
auto
|
||||||
draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
|
draw_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
|
||||||
// draw_instanced() subclass details — issues glDrawElementsInstancedBaseVertex
|
// draw_instanced() subclass details — issues glDrawElementsInstancedBaseVertexBaseInstance
|
||||||
auto
|
auto
|
||||||
draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, unsigned int const Streams ) -> std::size_t override;
|
draw_instanced_( gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, std::size_t const InstanceCount, std::uint32_t const BaseInstance, unsigned int const Streams ) -> std::size_t override;
|
||||||
// draw_batch() subclass details — issues glMultiDrawArrays for runs of non-indexed chunks of the same type
|
// draw_batch() subclass details — issues glMultiDrawArrays for runs of non-indexed chunks of the same type
|
||||||
auto
|
auto
|
||||||
draw_batch_( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
|
draw_batch_( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams ) -> std::size_t override;
|
||||||
|
|||||||
@@ -179,7 +179,11 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
|||||||
model_ubo = std::make_unique<gl::ubo>(sizeof(gl::model_ubs), 1, GL_STREAM_DRAW);
|
model_ubo = std::make_unique<gl::ubo>(sizeof(gl::model_ubs), 1, GL_STREAM_DRAW);
|
||||||
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
|
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
|
||||||
light_ssbo->bind_base();
|
light_ssbo->bind_base();
|
||||||
instance_ubo = std::make_unique<gl::ubo>(sizeof(gl::instance_ubs), 3, GL_STREAM_DRAW);
|
for (auto &buffer : m_instancebuffers)
|
||||||
|
{
|
||||||
|
// grows when a frame needs more
|
||||||
|
buffer = std::make_unique<gl::ssbo>(static_cast<GLsizeiptr>(16384 * sizeof(glm::mat4)), gl::INSTANCE_SSBO_BINDING);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -203,13 +207,15 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
|||||||
model_ubo->update(model_ubs);
|
model_ubo->update(model_ubs);
|
||||||
scene_ubo->update(scene_ubs);
|
scene_ubo->update(scene_ubs);
|
||||||
|
|
||||||
// initialize instance_ubo slot 0 to identity. This is the matrix sampled by
|
// element 0 of the instance buffers is identity. This is the matrix sampled by
|
||||||
// gl_InstanceID==0 in non-instanced draws, so existing rendering paths
|
// non-instanced draws, so existing rendering paths
|
||||||
// continue to multiply effective_modelview = identity * modelview = modelview.
|
// continue to multiply effective_modelview = identity * modelview = modelview.
|
||||||
|
for (auto &buffer : m_instancebuffers)
|
||||||
{
|
{
|
||||||
glm::mat4 identity( 1.0f );
|
glm::mat4 const identity(1.0f);
|
||||||
instance_ubo->update( reinterpret_cast<uint8_t const*>(&identity), 0, sizeof(identity) );
|
buffer->update(identity);
|
||||||
}
|
}
|
||||||
|
m_instancebuffers[m_instancebufferindex]->bind_base();
|
||||||
|
|
||||||
int samples = 1 << Global.iMultisampling;
|
int samples = 1 << Global.iMultisampling;
|
||||||
if (samples > 1)
|
if (samples > 1)
|
||||||
@@ -630,10 +636,16 @@ bool opengl46_renderer::Render()
|
|||||||
opengl_texture::reset_unit_cache();
|
opengl_texture::reset_unit_cache();
|
||||||
// textures of new heightmap terrain layers go to the arrays the terrain shader reads them from
|
// textures of new heightmap terrain layers go to the arrays the terrain shader reads them from
|
||||||
m_terrainmaterials.update();
|
m_terrainmaterials.update();
|
||||||
if( EditorModeFlag ) {
|
if( ( EditorModeFlag ) || ( m_nodestateversion != scene::node_state_version ) ) {
|
||||||
// the editor moves and resizes model instances in place, which leaves the copies of their bounds out of date
|
// the editor moves and resizes model instances in place, events show and hide them and make them drawn on
|
||||||
++m_instanceboundsversion;
|
// their own: the instance records are out of date
|
||||||
|
++m_instancerecordsversion;
|
||||||
|
m_nodestateversion = scene::node_state_version;
|
||||||
}
|
}
|
||||||
|
// instanced draws of the frame place their matrices in the next of the instance buffers, the one used the longest ago
|
||||||
|
m_instancebufferindex = ( m_instancebufferindex + 1 ) % m_instancebuffers.size();
|
||||||
|
m_instancebufferfill = 1;
|
||||||
|
m_instancebuffers[ m_instancebufferindex ]->bind_base();
|
||||||
|
|
||||||
m_renderpass.draw_mode = rendermode::none; // force setup anew
|
m_renderpass.draw_mode = rendermode::none; // force setup anew
|
||||||
m_renderpass.draw_stats = debug_stats();
|
m_renderpass.draw_stats = debug_stats();
|
||||||
@@ -729,6 +741,8 @@ void opengl46_renderer::SwapBuffers()
|
|||||||
+ " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 )
|
+ " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 )
|
||||||
+ " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 )
|
+ " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 )
|
||||||
+ " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n"
|
+ " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n"
|
||||||
|
+ " instance buffer: " + std::to_string( m_instancebufferfill - 1 ) + " matrices, "
|
||||||
|
+ std::to_string( m_instancebuffers[ m_instancebufferindex ]->size() / 1024 ) + " KiB\n"
|
||||||
+ " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total )
|
+ " inst-pool:" + std::to_string( TAnimModel::s_instanceable_total )
|
||||||
+ "/" + std::to_string( TAnimModel::s_classified_total )
|
+ "/" + std::to_string( TAnimModel::s_classified_total )
|
||||||
+ " rej(noModel/lights/anim/animSubM): "
|
+ " rej(noModel/lights/anim/animSubM): "
|
||||||
@@ -2999,7 +3013,7 @@ void opengl46_renderer::draw(const gfx::geometry_handle &handle)
|
|||||||
if( m_current_instance_count > 0 ) {
|
if( m_current_instance_count > 0 ) {
|
||||||
// inside Render_Instanced(): one GL instanced draw replaces what would
|
// inside Render_Instanced(): one GL instanced draw replaces what would
|
||||||
// otherwise be N regular draws (one per instance) at this submodel
|
// otherwise be N regular draws (one per instance) at this submodel
|
||||||
m_geometry.draw_instanced( handle, m_current_instance_count );
|
m_geometry.draw_instanced( handle, m_current_instance_count, m_current_instance_base );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_geometry.draw( handle );
|
m_geometry.draw( handle );
|
||||||
@@ -3182,13 +3196,13 @@ void opengl46_renderer::Render(TAnimModel *Instance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adds the bucketed instances of a scene cell to the frame-level buckets, for Render_Instanced() to draw.
|
// adds the bucketed instances of a scene cell to the frame-level buckets, for Render_Instanced() to draw.
|
||||||
// Render_Instanced() culls each instance it's given. the tests it makes are repeated here on the copies of instance
|
// Render_Instanced() culls each instance it's given by its record. the tests it makes are repeated here, with some slack
|
||||||
// bounds kept by the buckets, with some slack for the single precision of the copies, and the instances which fail are
|
// for the single precision of the records, and the instances which fail are left out of the queue
|
||||||
// left out: the instances themselves are fetched only when there's a chance for them to be drawn
|
|
||||||
void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|
void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|
||||||
{
|
{
|
||||||
// the editor moves and resizes the instances in place, the copies can't be relied on while it's active
|
// the editor moves and resizes the instances in place. the records are made anew for each frame then,
|
||||||
auto const usebounds { false == EditorModeFlag };
|
// but aren't relied on to leave instances out of the queue
|
||||||
|
auto const userecords { false == EditorModeFlag };
|
||||||
auto const reflections { m_renderpass.draw_mode == rendermode::reflections };
|
auto const reflections { m_renderpass.draw_mode == rendermode::reflections };
|
||||||
// same choices as in Render_Instanced(): shadows measure the distance from the real camera, reflections
|
// same choices as in Render_Instanced(): shadows measure the distance from the real camera, reflections
|
||||||
// ignore zoom and distance factor, test the plain distance against their own range and add the fidelity offset
|
// ignore zoom and distance factor, test the plain distance against their own range and add the fidelity offset
|
||||||
@@ -3198,42 +3212,39 @@ void opengl46_renderer::Queue_Instances( scene::basic_cell const *Cell )
|
|||||||
auto const distanceoffset { reflections ? sq( static_cast<double>( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.0 };
|
auto const distanceoffset { reflections ? sq( static_cast<double>( EU07_REFLECTIONFIDELITYOFFSET ) ) : 0.0 };
|
||||||
auto const slack { 1.001 };
|
auto const slack { 1.001 };
|
||||||
auto const distancelimit { slack * ( reflections ? sq( static_cast<double>( Global.reflectiontune.range_instances ) ) : sq( static_cast<double>( m_renderpass.draw_range ) + 250.0 ) ) };
|
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 copies is too large a part of the distance for the slack to cover it
|
// 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 ) };
|
auto const distancetested { sq( 64.0 ) };
|
||||||
|
|
||||||
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
|
for( auto const &bucket : Cell->m_instancebuckets_opaque ) {
|
||||||
auto const &instances { bucket.second.instances };
|
auto const &instancebucket { bucket.second };
|
||||||
if( false == usebounds ) {
|
if( ( instancebucket.recordsversion != m_instancerecordsversion )
|
||||||
auto &dest { m_frame_instance_buckets[ bucket.first ] };
|
|| ( instancebucket.records.size() != instancebucket.instances.size() ) ) {
|
||||||
dest.insert( dest.end(), instances.begin(), instances.end() );
|
Update_Instance_Records( bucket.first.pModel, instancebucket, Cell->m_area.center );
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if( ( bucket.second.boundsversion != m_instanceboundsversion )
|
auto const &records { instancebucket.records };
|
||||||
|| ( bucket.second.bounds.size() != instances.size() ) ) {
|
std::vector<queued_instance> *dest { nullptr }; // looked up when there's something to put in it
|
||||||
Update_Instance_Bounds( bucket.first.pModel, bucket.second );
|
for( std::size_t idx = 0; idx < records.size(); ++idx ) {
|
||||||
}
|
if( userecords ) {
|
||||||
auto const &bounds { bucket.second.bounds };
|
auto const &record { records[ idx ] };
|
||||||
std::vector<TAnimModel *> *dest { nullptr }; // looked up when there's something to put in it
|
auto const center { instancebucket.recordorigin + glm::dvec3{ record.position } };
|
||||||
for( std::size_t idx = 0; idx < bounds.size(); ++idx ) {
|
auto const distancesquared { glm::length2( center - cameraposition ) };
|
||||||
auto const &instancebounds { bounds[ idx ] };
|
if( distancesquared > distancetested ) {
|
||||||
auto const center { glm::dvec3{ instancebounds.center } };
|
auto const scaleddistancesquared { distancesquared * distancescale };
|
||||||
auto const distancesquared { glm::length2( center - cameraposition ) };
|
if( scaleddistancesquared > distancelimit ) { continue; }
|
||||||
if( distancesquared > distancetested ) {
|
if( scaleddistancesquared + distanceoffset >= slack * std::min( instancebucket.lodrangesquared, record.rangesquaredmax ) ) { continue; }
|
||||||
auto const scaleddistancesquared { distancesquared * distancescale };
|
}
|
||||||
if( scaleddistancesquared > distancelimit ) { continue; }
|
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ center, record.radius } ) ) { continue; }
|
||||||
if( scaleddistancesquared + distanceoffset >= slack * instancebounds.rangesquaredmax ) { continue; }
|
|
||||||
}
|
}
|
||||||
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ center, instancebounds.radius } ) ) { continue; }
|
|
||||||
if( dest == nullptr ) {
|
if( dest == nullptr ) {
|
||||||
dest = &m_frame_instance_buckets[ bucket.first ];
|
dest = &m_frame_instance_buckets[ bucket.first ];
|
||||||
}
|
}
|
||||||
dest->emplace_back( instances[ idx ] );
|
dest->push_back( { &instancebucket, idx } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes the copies of the bounds of the instances held by a bucket of a scene cell
|
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
|
||||||
void opengl46_renderer::Update_Instance_Bounds( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket )
|
void opengl46_renderer::Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin )
|
||||||
{
|
{
|
||||||
// a submodel is drawn only closer than its fSquareMaxDist, see Render( TSubModel * ). past the largest of these
|
// a submodel is drawn only closer than its fSquareMaxDist, see Render( TSubModel * ). past the largest of these
|
||||||
// there's nothing of the model left to draw, which makes it another limit of the range of its instances
|
// there's nothing of the model left to draw, which makes it another limit of the range of its instances
|
||||||
@@ -3248,31 +3259,63 @@ void opengl46_renderer::Update_Instance_Bounds( TModel3d const *Model, scene::ba
|
|||||||
m_instance_lodpending.push_back( submodel->Child );
|
m_instance_lodpending.push_back( submodel->Child );
|
||||||
m_instance_lodpending.push_back( submodel->Next );
|
m_instance_lodpending.push_back( submodel->Next );
|
||||||
}
|
}
|
||||||
|
Bucket.lodrangesquared = lodrangesquared;
|
||||||
|
Bucket.recordorigin = Origin;
|
||||||
|
|
||||||
Bucket.bounds.clear();
|
auto const tofloat = []( double const Value ) {
|
||||||
Bucket.bounds.reserve( Bucket.instances.size() );
|
return static_cast<float>( std::min( Value, static_cast<double>( std::numeric_limits<float>::max() ) ) ); };
|
||||||
for( auto const *instance : Bucket.instances ) {
|
|
||||||
scene::basic_cell::instance_bounds instancebounds;
|
Bucket.records.clear();
|
||||||
|
Bucket.records.reserve( Bucket.instances.size() );
|
||||||
|
for( auto *instance : Bucket.instances ) {
|
||||||
|
scene::basic_cell::instance_record record;
|
||||||
if( instance != nullptr ) {
|
if( instance != nullptr ) {
|
||||||
instancebounds.center = glm::vec3{ instance->m_area.center };
|
record.position = glm::vec3{ instance->location() - Origin };
|
||||||
instancebounds.radius = instance->m_area.radius;
|
record.radius = instance->radius();
|
||||||
instancebounds.rangesquaredmax = std::min( lodrangesquared, static_cast<float>( std::min( instance->m_rangesquaredmax, static_cast<double>( std::numeric_limits<float>::max() ) ) ) );
|
record.rangesquaredmin = tofloat( instance->m_rangesquaredmin );
|
||||||
|
record.rangesquaredmax = tofloat( instance->m_rangesquaredmax );
|
||||||
|
record.flags =
|
||||||
|
( instance->visible() ? scene::basic_cell::instance_visible : 0u )
|
||||||
|
| ( instance->m_instanceable ? scene::basic_cell::instance_instanceable : 0u );
|
||||||
|
auto const &rotationscale { instance->rotation_scale() };
|
||||||
|
for( int column = 0; column < 3; ++column ) {
|
||||||
|
record.rotationscale[ column ] = rotationscale[ column ];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Bucket.bounds.emplace_back( instancebounds );
|
// an empty slot keeps its record without flags, and isn't drawn
|
||||||
|
Bucket.records.emplace_back( record );
|
||||||
}
|
}
|
||||||
Bucket.boundsversion = m_instanceboundsversion;
|
Bucket.recordsversion = m_instancerecordsversion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
|
||||||
|
std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count )
|
||||||
|
{
|
||||||
|
auto &buffer { m_instancebuffers[ m_instancebufferindex ] };
|
||||||
|
auto const needed { static_cast<GLsizeiptr>( ( m_instancebufferfill + Count ) * sizeof( glm::mat4 ) ) };
|
||||||
|
if( needed > buffer->size() ) {
|
||||||
|
// the draws issued with the replaced buffer keep it until the gpu is done with them. the new one starts with identity
|
||||||
|
buffer->resize( std::max( needed, 2 * buffer->size() ) );
|
||||||
|
glm::mat4 const identity( 1.0f );
|
||||||
|
buffer->update( identity );
|
||||||
|
buffer->bind_base();
|
||||||
|
m_instancebufferfill = 1;
|
||||||
|
}
|
||||||
|
auto const base { static_cast<std::uint32_t>( m_instancebufferfill ) };
|
||||||
|
buffer->update( Modelviews, static_cast<GLintptr>( base * sizeof( glm::mat4 ) ), static_cast<GLsizeiptr>( Count * sizeof( glm::mat4 ) ) );
|
||||||
|
m_instancebufferfill += Count;
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// True GPU-instanced render path for a group of TAnimModel instances sharing the same
|
// True GPU-instanced render path for a group of TAnimModel instances sharing the same
|
||||||
// TModel3d. The submodel tree is walked ONCE per batch; at every submodel that draws
|
// TModel3d. The submodel tree is walked ONCE per lod band; at every submodel that draws
|
||||||
// geometry we issue a single glDrawElementsInstancedBaseVertex(N) covering all visible
|
// geometry we issue a single glDrawElementsInstancedBaseVertexBaseInstance(N) covering all
|
||||||
// instances. The vertex shader multiplies effective_modelview = instance_modelview[gl_InstanceID]
|
// visible instances of the band. The vertex shader multiplies effective_modelview =
|
||||||
// * model_ubs.modelview, where instance_modelview[i] is the camera-space root transform
|
// instance_modelview[gl_BaseInstance + gl_InstanceID] * model_ubs.modelview, where
|
||||||
// of instance i (precomputed below) and model_ubs.modelview is the submodel-local chain
|
// instance_modelview[i] is the camera-space root transform of an instance (precomputed below)
|
||||||
// (accumulated by the matrix stack starting from identity, NOT the camera/instance frame).
|
// and model_ubs.modelview is the submodel-local chain (accumulated by the matrix stack starting
|
||||||
//
|
// from identity, NOT the camera/instance frame).
|
||||||
// Batch size is capped at MAX_INSTANCES_PER_BATCH (256). Larger groups are split.
|
void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<queued_instance> const &Instances )
|
||||||
void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimModel *> const &Instances )
|
|
||||||
{
|
{
|
||||||
if( Model == nullptr ) { return; }
|
if( Model == nullptr ) { return; }
|
||||||
if( Instances.empty() ) { return; }
|
if( Instances.empty() ) { return; }
|
||||||
@@ -3287,7 +3330,8 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
// whose materials are all excluded by the shadow rank cutoff (e.g. grass) would otherwise go through
|
// whose materials are all excluded by the shadow rank cutoff (e.g. grass) would otherwise go through
|
||||||
// the whole per-instance culling and the batched draws in every shadow pass, only for every submodel
|
// the whole per-instance culling and the batched draws in every shadow pass, only for every submodel
|
||||||
// to be skipped in Render(TSubModel*)
|
// to be skipped in Render(TSubModel*)
|
||||||
auto const *skins { Instances.front() != nullptr && Instances.front()->Material() != nullptr ? Instances.front()->Material()->replacable_skins : nullptr };
|
auto const *frontinstance { Instances.front().bucket->instances[ Instances.front().index ] };
|
||||||
|
auto const *skins { frontinstance != nullptr && frontinstance->Material() != nullptr ? frontinstance->Material()->replacable_skins : nullptr };
|
||||||
bool castsshadow { m_renderpass.draw_mode != rendermode::shadows };
|
bool castsshadow { m_renderpass.draw_mode != rendermode::shadows };
|
||||||
m_instance_lodbounds.clear();
|
m_instance_lodbounds.clear();
|
||||||
m_instance_lodpending.clear();
|
m_instance_lodpending.clear();
|
||||||
@@ -3310,9 +3354,9 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
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 };
|
||||||
|
|
||||||
// 1. Visibility / distance cull. Collect surviving instances with their precomputed
|
// 1. Visibility / distance cull, by the records of the instances. Collect surviving instances with
|
||||||
// camera-space root modelview matrices and lod bands. m_instance_survivors and the other
|
// their precomputed camera-space root modelview matrices and lod bands. m_instance_survivors and the
|
||||||
// scratch vectors are persistent members reused across every Render_Instanced() call:
|
// other scratch vectors are persistent members reused across every Render_Instanced() call:
|
||||||
// clear() drops the contents but keeps the allocated capacity, so after the first few
|
// clear() drops the contents but keeps the allocated capacity, so after the first few
|
||||||
// frames this stops calling malloc/free entirely.
|
// frames this stops calling malloc/free entirely.
|
||||||
m_instance_survivors.clear();
|
m_instance_survivors.clear();
|
||||||
@@ -3324,55 +3368,58 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
// per-instance transform has been pushed yet.
|
// per-instance transform has been pushed yet.
|
||||||
glm::mat4 const view_matrix = OpenGLMatrices.data( GL_MODELVIEW );
|
glm::mat4 const view_matrix = OpenGLMatrices.data( GL_MODELVIEW );
|
||||||
|
|
||||||
bool prepared_shared_state = false; // RaPrepare() runs once per bucket
|
TAnimModel *batch_instance { nullptr }; // the first instance drawn; its RaPrepare() and material serve the whole bucket
|
||||||
material_data const *batch_material { nullptr };
|
material_data const *batch_material { nullptr };
|
||||||
float farthest_distancesquared { 0.f };
|
float farthest_distancesquared { 0.f };
|
||||||
|
|
||||||
for( auto *Instance : Instances ) {
|
for( auto const &queued : Instances ) {
|
||||||
if( Instance == nullptr ) { continue; }
|
auto const &record { queued.bucket->records[ queued.index ] };
|
||||||
// instances which lost their instanceable status after being bucketed (animation or skin
|
// instances which lost their instanceable status after being bucketed (animation or skin
|
||||||
// assigned by an event) are drawn by the per-node path
|
// assigned by an event) are drawn by the per-node path
|
||||||
if( false == Instance->m_instanceable ) { continue; }
|
if( ( record.flags & scene::basic_cell::instance_instanceable ) == 0 ) { continue; }
|
||||||
if( false == Instance->m_visible ) { continue; }
|
if( ( record.flags & scene::basic_cell::instance_visible ) == 0 ) { continue; }
|
||||||
if( false == m_renderpass.pass_camera.visible( Instance->m_area ) ) { continue; }
|
glm::dvec3 const location { queued.bucket->recordorigin + glm::dvec3{ record.position } };
|
||||||
|
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ location, record.radius } ) ) { continue; }
|
||||||
|
|
||||||
double distancesquared;
|
double distancesquared;
|
||||||
switch( m_renderpass.draw_mode ) {
|
switch( m_renderpass.draw_mode ) {
|
||||||
case rendermode::shadows:
|
case rendermode::shadows:
|
||||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.viewport_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
distancesquared = glm::length2( ( location - m_renderpass.viewport_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
||||||
break;
|
break;
|
||||||
case rendermode::reflections:
|
case rendermode::reflections:
|
||||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.pass_camera.position() ) );
|
distancesquared = glm::length2( ( location - m_renderpass.pass_camera.position() ) );
|
||||||
if( distancesquared > Global.reflectiontune.range_instances * Global.reflectiontune.range_instances ) { continue; }
|
if( distancesquared > Global.reflectiontune.range_instances * Global.reflectiontune.range_instances ) { continue; }
|
||||||
distancesquared += sq( EU07_REFLECTIONFIDELITYOFFSET );
|
distancesquared += sq( EU07_REFLECTIONFIDELITYOFFSET );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
distancesquared = glm::length2( ( Instance->location() - m_renderpass.pass_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
distancesquared = glm::length2( ( location - m_renderpass.pass_camera.position() ) / (double)Global.ZoomFactor ) / Global.fDistanceFactor;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if( ( distancesquared < Instance->m_rangesquaredmin )
|
if( ( distancesquared < record.rangesquaredmin )
|
||||||
|| ( distancesquared >= Instance->m_rangesquaredmax ) ) { continue; }
|
|| ( distancesquared >= record.rangesquaredmax ) ) { continue; }
|
||||||
|
|
||||||
auto const drawdistancethreshold { m_renderpass.draw_range + 250 };
|
auto const drawdistancethreshold { m_renderpass.draw_range + 250 };
|
||||||
if( distancesquared > sq( drawdistancethreshold ) ) { continue; }
|
if( distancesquared > sq( drawdistancethreshold ) ) { continue; }
|
||||||
|
|
||||||
auto const radiussquared { Instance->radius() * Instance->radius() };
|
auto const radiussquared { record.radius * record.radius };
|
||||||
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) { continue; }
|
if( radiussquared * Global.ZoomFactor / distancesquared < 0.003 * 0.003 ) { continue; }
|
||||||
|
|
||||||
if( !prepared_shared_state ) {
|
if( batch_instance == nullptr ) {
|
||||||
Instance->RaPrepare();
|
batch_instance = queued.bucket->instances[ queued.index ];
|
||||||
prepared_shared_state = true;
|
batch_instance->RaPrepare();
|
||||||
batch_material = Instance->Material();
|
batch_material = batch_instance->Material();
|
||||||
}
|
}
|
||||||
Instance->m_framestamp = m_framestamp;
|
|
||||||
|
|
||||||
// Build the camera-relative root modelview for this instance:
|
// Build the camera-relative root modelview for this instance:
|
||||||
// mv = view * translate(instance_pos - camera_pos) * rotate(instance_angles) * scale(m_scale)
|
// mv = view * translate(instance_pos - camera_pos) * rotate(instance_angles) * scale(m_scale)
|
||||||
// The rotation and scale part is cached by the instance, the translation goes into its last
|
// The rotation and scale part comes from the record, the translation goes into its last
|
||||||
// column. The scale is folded in so the GPU-instanced path produces visually identical output
|
// column. The scale is folded in so the GPU-instanced path produces visually identical output
|
||||||
// to the regular per-instance path; the shader applies it via effective_modelview = instance_mv * model_local.
|
// to the regular per-instance path; the shader applies it via effective_modelview = instance_mv * model_local.
|
||||||
glm::mat4 root { Instance->rotation_scale() };
|
glm::mat4 const root {
|
||||||
root[ 3 ] = glm::vec4( glm::vec3( Instance->location() - m_renderpass.pass_camera.position() ), 1.f );
|
record.rotationscale[ 0 ],
|
||||||
|
record.rotationscale[ 1 ],
|
||||||
|
record.rotationscale[ 2 ],
|
||||||
|
glm::vec4( glm::vec3( location - m_renderpass.pass_camera.position() ), 1.f ) };
|
||||||
|
|
||||||
auto const distance { static_cast<float>( distancesquared ) };
|
auto const distance { static_cast<float>( distancesquared ) };
|
||||||
farthest_distancesquared = std::max( farthest_distancesquared, distance );
|
farthest_distancesquared = std::max( farthest_distancesquared, distance );
|
||||||
@@ -3384,7 +3431,7 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
|
|
||||||
if( m_instance_survivors.empty() ) { return; }
|
if( m_instance_survivors.empty() ) { return; }
|
||||||
|
|
||||||
// group the modelviews by lod band into a contiguous array, for the UBO upload. every distance in
|
// group the modelviews by lod band into a contiguous array, for the upload. every distance in
|
||||||
// a band selects the same lod; the nearest one represents the band.
|
// a band selects the same lod; the nearest one represents the band.
|
||||||
// within a band the instances are additionally ordered roughly near-to-far, in distance slots. dense
|
// within a band the instances are additionally ordered roughly near-to-far, in distance slots. dense
|
||||||
// alpha tested foliage then gets the most out of the early depth test, as the nearest instances fill
|
// alpha tested foliage then gets the most out of the early depth test, as the nearest instances fill
|
||||||
@@ -3409,55 +3456,43 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
// 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 alpha = ( batch_material != nullptr ? batch_material->textures_alpha : 0x30300030 );
|
auto alpha = ( batch_material != nullptr ? batch_material->textures_alpha : 0x30300030 );
|
||||||
alpha ^= 0x0F0F000F;
|
alpha ^= 0x0F0F000F;
|
||||||
|
|
||||||
// 2. Walk the submodel tree once per sub-batch. The submodel-local matrix
|
// 2. Walk the submodel tree once per lod band. The submodel-local matrix
|
||||||
// stack starts at identity; the per-instance camera transform comes from
|
// stack starts at identity; the per-instance camera transform comes from
|
||||||
// instance_modelview[gl_InstanceID] in the shader.
|
// instance_modelview[gl_BaseInstance + gl_InstanceID] in the shader.
|
||||||
for( std::size_t band = 0; band < bandcount; ++band ) {
|
for( std::size_t band = 0; band < bandcount; ++band ) {
|
||||||
|
|
||||||
std::size_t offset_idx = m_instance_bandstarts[ band * distanceslots ];
|
std::size_t const band_start = m_instance_bandstarts[ band * distanceslots ];
|
||||||
std::size_t const band_end = m_instance_bandstarts[ ( band + 1 ) * distanceslots ];
|
std::size_t const band_end = m_instance_bandstarts[ ( band + 1 ) * distanceslots ];
|
||||||
while( offset_idx < band_end ) {
|
if( band_start == band_end ) { continue; }
|
||||||
std::size_t const this_batch = std::min<std::size_t>( band_end - offset_idx, gl::MAX_INSTANCES_PER_BATCH );
|
|
||||||
|
|
||||||
// 2a. Upload N modelviews to instance_ubo[0..N-1].
|
// 2a. Push identity onto the matrix stack so submodel transforms
|
||||||
instance_ubo->update(
|
// accumulate from identity, not from camera/instance space.
|
||||||
reinterpret_cast<uint8_t const *>( m_instance_modelviews.data() + offset_idx ),
|
::glPushMatrix();
|
||||||
0,
|
::glLoadIdentity();
|
||||||
static_cast<int>( this_batch * sizeof( glm::mat4 ) ) );
|
|
||||||
|
|
||||||
// 2b. Push identity onto the matrix stack so submodel transforms
|
// 2b. Configure the existing TModel3d/TSubModel render path. Setting
|
||||||
// accumulate from identity, not from camera/instance space.
|
// m_current_instance_count routes draw(handle) calls to draw_instanced.
|
||||||
::glPushMatrix();
|
m_current_instance_count = band_end - band_start;
|
||||||
::glLoadIdentity();
|
m_current_instance_base = baseinstance + static_cast<std::uint32_t>( band_start );
|
||||||
|
|
||||||
// 2c. Configure the existing TModel3d/TSubModel render path. Setting
|
Model->Root->fSquareDist = m_instance_banddistances[ band ]; // shared global, used by submodel LOD
|
||||||
// m_current_instance_count routes draw(handle) calls to draw_instanced.
|
Model->Root->ReplacableSet( ( batch_material != nullptr ? batch_material->replacable_skins : nullptr ), alpha );
|
||||||
m_current_instance_count = this_batch;
|
Model->Root->pRoot = Model;
|
||||||
|
|
||||||
Model->Root->fSquareDist = m_instance_banddistances[ band ]; // shared global, used by submodel LOD
|
Render( Model->Root );
|
||||||
Model->Root->ReplacableSet( ( batch_material != nullptr ? batch_material->replacable_skins : nullptr ), alpha );
|
|
||||||
Model->Root->pRoot = Model;
|
|
||||||
|
|
||||||
Render( Model->Root );
|
m_current_instance_count = 0;
|
||||||
|
m_current_instance_base = 0;
|
||||||
|
|
||||||
m_current_instance_count = 0;
|
::glPopMatrix();
|
||||||
|
|
||||||
::glPopMatrix();
|
++m_renderpass.draw_stats.instanced_drawcalls;
|
||||||
|
|
||||||
offset_idx += this_batch;
|
|
||||||
++m_renderpass.draw_stats.instanced_drawcalls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 3. Restore instance_modelview[0] to identity so subsequent
|
|
||||||
// non-instanced draws continue to compute identity * modelview.
|
|
||||||
// done once the last batch is out, as everything drawn up to that point is instanced
|
|
||||||
{
|
|
||||||
glm::mat4 const identity( 1.0f );
|
|
||||||
instance_ubo->update( reinterpret_cast<uint8_t const *>( &identity ), 0, sizeof( identity ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_renderpass.draw_stats.instances += static_cast<int>( m_instance_survivors.size() );
|
m_renderpass.draw_stats.instances += static_cast<int>( m_instance_survivors.size() );
|
||||||
@@ -3467,8 +3502,8 @@ void opengl46_renderer::Render_Instanced( TModel3d *Model, std::vector<TAnimMode
|
|||||||
// Renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) using the
|
// Renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) using the
|
||||||
// existing GPU-instanced submodel pipeline. The track owns a vector of pre-baked
|
// existing GPU-instanced submodel pipeline. The track owns a vector of pre-baked
|
||||||
// local-space matrices; we compose each with `view * translate(track_origin - camera)`
|
// local-space matrices; we compose each with `view * translate(track_origin - camera)`
|
||||||
// to get a camera-space modelview, then issue batched glDrawElementsInstancedBaseVertex
|
// to get a camera-space modelview, then issue glDrawElementsInstancedBaseVertexBaseInstance
|
||||||
// calls -- one batch per MAX_INSTANCES_PER_BATCH sleepers.
|
// calls -- one per LOD band of the sleepers.
|
||||||
//
|
//
|
||||||
// Skipped entirely when:
|
// Skipped entirely when:
|
||||||
// - Global.SleeperDistance == 0 (sleeper rendering globally disabled)
|
// - Global.SleeperDistance == 0 (sleeper rendering globally disabled)
|
||||||
@@ -3665,7 +3700,7 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track )
|
|||||||
[]( std::pair<float, glm::mat4> const &Left, std::pair<float, glm::mat4> const &Right ) {
|
[]( std::pair<float, glm::mat4> const &Left, std::pair<float, glm::mat4> const &Right ) {
|
||||||
return Left.first < Right.first; } );
|
return Left.first < Right.first; } );
|
||||||
|
|
||||||
// contiguous copy of the sorted modelview matrices, for the UBO upload.
|
// contiguous copy of the sorted modelview matrices, for the upload.
|
||||||
// Per-sleeper distances stay available as survivors[i].first, in lockstep.
|
// Per-sleeper distances stay available as survivors[i].first, in lockstep.
|
||||||
std::vector<glm::mat4> instance_modelviews;
|
std::vector<glm::mat4> instance_modelviews;
|
||||||
instance_modelviews.reserve( survivors.size() );
|
instance_modelviews.reserve( survivors.size() );
|
||||||
@@ -3705,12 +3740,12 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track )
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3 -- draw. Walk the sorted survivors one LOD band at a time. Each
|
// Phase 3 -- draw. Walk the sorted survivors one LOD band at a time. Each
|
||||||
// band is submitted as one or more instanced draws (split only when it
|
// band is submitted as one instanced draw from its own base instance; every
|
||||||
// exceeds MAX_INSTANCES_PER_BATCH); every draw in the band uses an in-band
|
// draw uses an in-band fSquareDist, so each sleeper renders at the LOD its own
|
||||||
// fSquareDist, so each sleeper renders at the LOD its own distance selects
|
// distance selects while instancing stays fully in effect.
|
||||||
// while instancing stays fully in effect.
|
|
||||||
auto *Model = Track->m_sleeper_model;
|
auto *Model = Track->m_sleeper_model;
|
||||||
std::size_t const total = instance_modelviews.size();
|
std::size_t const total = instance_modelviews.size();
|
||||||
|
auto const baseinstance { Upload_Instance_Modelviews( instance_modelviews.data(), total ) };
|
||||||
std::size_t band_start = 0;
|
std::size_t band_start = 0;
|
||||||
while( band_start < total ) {
|
while( band_start < total ) {
|
||||||
// the band ends at the first sleeper distance that reaches the next LOD
|
// the band ends at the first sleeper distance that reaches the next LOD
|
||||||
@@ -3728,42 +3763,27 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track )
|
|||||||
// sleeper is used as the representative fSquareDist.
|
// sleeper is used as the representative fSquareDist.
|
||||||
float const band_distancesquared = survivors[ band_start ].first;
|
float const band_distancesquared = survivors[ band_start ].first;
|
||||||
|
|
||||||
std::size_t offset_idx = band_start;
|
::glPushMatrix();
|
||||||
while( offset_idx < band_end ) {
|
::glLoadIdentity();
|
||||||
std::size_t const this_batch = std::min<std::size_t>( band_end - offset_idx, gl::MAX_INSTANCES_PER_BATCH );
|
|
||||||
|
|
||||||
instance_ubo->update(
|
m_current_instance_count = band_end - band_start;
|
||||||
reinterpret_cast<uint8_t const *>( instance_modelviews.data() + offset_idx ),
|
m_current_instance_base = baseinstance + static_cast<std::uint32_t>( band_start );
|
||||||
0,
|
|
||||||
static_cast<int>( this_batch * sizeof( glm::mat4 ) ) );
|
|
||||||
|
|
||||||
::glPushMatrix();
|
Model->Root->fSquareDist = band_distancesquared;
|
||||||
::glLoadIdentity();
|
auto alpha = ( has_skin ? sleeper_material.textures_alpha : 0x30300030 );
|
||||||
|
alpha ^= 0x0F0F000F;
|
||||||
|
Model->Root->ReplacableSet( ( has_skin ? sleeper_material.replacable_skins : nullptr ), alpha );
|
||||||
|
Model->Root->pRoot = Model;
|
||||||
|
|
||||||
m_current_instance_count = this_batch;
|
Render( Model->Root );
|
||||||
|
|
||||||
Model->Root->fSquareDist = band_distancesquared;
|
m_current_instance_count = 0;
|
||||||
auto alpha = ( has_skin ? sleeper_material.textures_alpha : 0x30300030 );
|
m_current_instance_base = 0;
|
||||||
alpha ^= 0x0F0F000F;
|
|
||||||
Model->Root->ReplacableSet( ( has_skin ? sleeper_material.replacable_skins : nullptr ), alpha );
|
|
||||||
Model->Root->pRoot = Model;
|
|
||||||
|
|
||||||
Render( Model->Root );
|
::glPopMatrix();
|
||||||
|
|
||||||
m_current_instance_count = 0;
|
++m_renderpass.draw_stats.instanced_drawcalls;
|
||||||
|
|
||||||
::glPopMatrix();
|
|
||||||
|
|
||||||
// restore instance_modelview[0] to identity so subsequent non-instanced draws
|
|
||||||
// continue to compute identity * modelview (mirroring Render_Instanced).
|
|
||||||
{
|
|
||||||
glm::mat4 const identity( 1.0f );
|
|
||||||
instance_ubo->update( reinterpret_cast<uint8_t const *>( &identity ), 0, sizeof( identity ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_idx += this_batch;
|
|
||||||
++m_renderpass.draw_stats.instanced_drawcalls;
|
|
||||||
}
|
|
||||||
band_start = band_end;
|
band_start = band_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,17 +305,25 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
|
void Render(cell_sequence::iterator First, cell_sequence::iterator Last);
|
||||||
void Render(scene::shape_node const &Shape, bool const Ignorerange);
|
void Render(scene::shape_node const &Shape, bool const Ignorerange);
|
||||||
void Render(TAnimModel *Instance);
|
void Render(TAnimModel *Instance);
|
||||||
|
// an instance of a bucket of a scene cell, queued to be drawn by Render_Instanced()
|
||||||
|
struct queued_instance {
|
||||||
|
scene::basic_cell::instance_bucket const *bucket;
|
||||||
|
std::size_t index; // of the instance and its record in the bucket
|
||||||
|
};
|
||||||
// batched render path for many TAnimModel instances that share the same TModel3d.
|
// batched render path for many TAnimModel instances that share the same TModel3d.
|
||||||
// Caller guarantees every instance has m_instanceable == true (no per-instance lights,
|
// Caller guarantees every instance was instanceable when bucketed (no per-instance lights,
|
||||||
// no submodel animation, no replacable skins). Each call counts as one
|
// no submodel animation, same replacable skins); the records of the instances tell which
|
||||||
// instanced_drawcall in draw_stats and contributes Instances.size() to the
|
// ones still are. Instances are individually frustum/distance culled by their records, the
|
||||||
// instances counter. Instances are still individually frustum/distance culled.
|
// TAnimModel itself is read only for the first instance drawn. Each lod band drawn counts as
|
||||||
void Render_Instanced( TModel3d *Model, std::vector<TAnimModel *> const &Instances );
|
// 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 );
|
||||||
// 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 the copies of their bounds 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 );
|
||||||
// makes the copies of the bounds of the instances held by a bucket of a scene cell
|
// makes the records of the instances held by a bucket of a scene cell, with locations relative to Origin
|
||||||
void Update_Instance_Bounds( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket );
|
void Update_Instance_Records( TModel3d const *Model, scene::basic_cell::instance_bucket const &Bucket, glm::dvec3 const &Origin );
|
||||||
|
// places supplied modelview matrices in the instance buffer of the frame. returns: base instance of the first of them
|
||||||
|
std::uint32_t Upload_Instance_Modelviews( glm::mat4 const *Modelviews, std::size_t const Count );
|
||||||
bool Render(TDynamicObject *Dynamic);
|
bool Render(TDynamicObject *Dynamic);
|
||||||
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
|
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
|
||||||
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
|
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
|
||||||
@@ -455,14 +463,16 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
// Render_Instanced() runs once per unique model across the whole pass instead
|
// Render_Instanced() runs once per unique model across the whole pass instead
|
||||||
// of once per cell -- collapsing many tiny instanced draws into a few large
|
// of once per cell -- collapsing many tiny instanced draws into a few large
|
||||||
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
|
// batches. Reused every pass; the vectors are emptied (not freed) at the top of Render(scene::basic_region*).
|
||||||
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
|
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<queued_instance>, scene::basic_cell::instance_bucket_key_hash> m_frame_instance_buckets;
|
||||||
// textures used by vehicle models, for the release of far textures
|
// textures used by vehicle models, for the release of far textures
|
||||||
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
|
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
|
||||||
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
|
// geometry of neighbouring tracks of a cell which share the material, gathered to be drawn together. reused by each Render(path_sequence)
|
||||||
std::vector<gfx::geometrybank_handle> m_pathbatch;
|
std::vector<gfx::geometrybank_handle> m_pathbatch;
|
||||||
// copies of instance bounds kept by the buckets of scene cells are good as long as they carry this number.
|
// instance records kept by the buckets of scene cells are good as long as they carry this number.
|
||||||
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place
|
// it changes with each frame drawn with the scenery editor active, as the editor moves and resizes instances in place,
|
||||||
unsigned int m_instanceboundsversion { 1 };
|
// 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_nodestateversion { 0 }; // scene::node_state_version which m_instancerecordsversion was last checked against
|
||||||
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;
|
||||||
@@ -506,18 +516,20 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
std::unique_ptr<gl::ubo> scene_ubo;
|
std::unique_ptr<gl::ubo> scene_ubo;
|
||||||
std::unique_ptr<gl::ubo> model_ubo;
|
std::unique_ptr<gl::ubo> model_ubo;
|
||||||
std::unique_ptr<gl::ssbo> light_ssbo;
|
std::unique_ptr<gl::ssbo> light_ssbo;
|
||||||
// per-instance modelview UBO for GPU-instanced draws.
|
// per-instance modelview matrices of GPU-instanced draws, one buffer for each of the last frames, used in turn so
|
||||||
// Slot 0 is initialized once to identity and never overwritten by non-instanced
|
// a frame doesn't write to the buffer the gpu may still read for an earlier one. element 0 of each is identity
|
||||||
// rendering, so existing draws (where gl_InstanceID==0) compute identity*modelview
|
// and never overwritten: draws which aren't instanced (gl_BaseInstance == gl_InstanceID == 0) compute
|
||||||
// in the vertex shader and behave exactly as before. Render_Instanced uploads
|
// identity*modelview in the vertex shader. Render_Instanced and Render_Sleepers append the camera-space root
|
||||||
// per-instance camera-space root matrices to slots 0..N-1, calls glDrawElementsInstanced*,
|
// matrices of their instances past the ones placed earlier in the frame, and draw them from that base instance
|
||||||
// then restores slot 0 to identity for subsequent non-instanced draws.
|
std::array<std::unique_ptr<gl::ssbo>, 3> m_instancebuffers;
|
||||||
std::unique_ptr<gl::ubo> instance_ubo;
|
std::size_t m_instancebufferindex { 0 }; // the buffer of the current frame
|
||||||
|
std::size_t m_instancebufferfill { 1 }; // matrices placed in it so far, identity included
|
||||||
// when > 0, the renderer is inside a Render_Instanced() call. The draw(handle)
|
// when > 0, the renderer is inside a Render_Instanced() call. The draw(handle)
|
||||||
// method routes to glDrawElementsInstanced* with this many instances rather
|
// method routes to glDrawElementsInstanced* with this many instances from
|
||||||
// than a single regular draw. The vertex shader reads per-instance modelview
|
// m_current_instance_base onwards rather than a single regular draw. The vertex shader reads
|
||||||
// from instance_ubo[gl_InstanceID]. Reset to 0 by Render_Instanced() on exit.
|
// per-instance modelview from instance_ssbo[gl_BaseInstance + gl_InstanceID]. Reset to 0 on exit.
|
||||||
std::size_t m_current_instance_count { 0 };
|
std::size_t m_current_instance_count { 0 };
|
||||||
|
std::uint32_t m_current_instance_base { 0 };
|
||||||
// persistent scratch buffer for Render_Instanced(): holds the per-instance
|
// persistent scratch buffer for Render_Instanced(): holds the per-instance
|
||||||
// camera-space root modelview matrices for the batch currently being built.
|
// camera-space root modelview matrices for the batch currently being built.
|
||||||
// Kept as a member rather than a function-local so its heap allocation is
|
// Kept as a member rather than a function-local so its heap allocation is
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ basic_cell::insert( TAnimModel *Instance ) {
|
|||||||
}
|
}
|
||||||
auto &bucket { m_instancebuckets_opaque[ key ] };
|
auto &bucket { m_instancebuckets_opaque[ key ] };
|
||||||
bucket.instances.emplace_back( Instance );
|
bucket.instances.emplace_back( Instance );
|
||||||
bucket.bounds.clear();
|
bucket.records.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// re-calculate cell bounding area, in case model extends outside the cell's boundaries
|
// re-calculate cell bounding area, in case model extends outside the cell's boundaries
|
||||||
@@ -484,7 +484,7 @@ basic_cell::erase( TAnimModel *Instance ) {
|
|||||||
instances.erase(
|
instances.erase(
|
||||||
std::remove( std::begin( instances ), std::end( instances ), Instance ),
|
std::remove( std::begin( instances ), std::end( instances ), Instance ),
|
||||||
std::end( instances ) );
|
std::end( instances ) );
|
||||||
bucket->second.bounds.clear();
|
bucket->second.records.clear();
|
||||||
if( instances.empty() ) {
|
if( instances.empty() ) {
|
||||||
bucket = m_instancebuckets_opaque.erase( bucket );
|
bucket = m_instancebuckets_opaque.erase( bucket );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,19 +237,31 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
using instance_bucket_map = std::unordered_map< instance_bucket_key, std::vector<TAnimModel *>, instance_bucket_key_hash >;
|
using instance_bucket_map = std::unordered_map< instance_bucket_key, std::vector<TAnimModel *>, instance_bucket_key_hash >;
|
||||||
// what it takes to tell that an instance is out of sight, copied from the instance.
|
// what it takes to cull and place an instance, copied from the instance.
|
||||||
// a cell can hold thousands of instances, each of them large and placed wherever the memory was free, and a render
|
// a cell can hold thousands of instances, each of them large and placed wherever the memory was free, and a render
|
||||||
// pass rejects most of them. the renderer makes the copies and goes by them, to fetch only the instances it may draw
|
// pass rejects most of them. the renderer makes the copies and goes by them, and reads the instance itself only
|
||||||
struct instance_bounds {
|
// for the first one of a batch drawn. the layout follows std430, for the copies to be handed to the gpu as they are
|
||||||
glm::vec3 center { 0.f }; // location of the instance, in the precision the frustum test takes it
|
struct instance_record {
|
||||||
|
glm::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 rangesquaredmax { 0.f }; // past it the instance isn't drawn: by its own range, or the lod stages of its model
|
float rangesquaredmin { 0.f }; // visibility range of the instance
|
||||||
|
float rangesquaredmax { 0.f };
|
||||||
|
std::uint32_t flags { 0 }; // instance_record_flags
|
||||||
|
std::uint32_t pad { 0 };
|
||||||
|
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" );
|
||||||
|
enum instance_record_flags : std::uint32_t {
|
||||||
|
instance_visible = 1 << 0,
|
||||||
|
instance_instanceable = 1 << 1, // false once an event gave the instance animations or a skin of its own
|
||||||
};
|
};
|
||||||
struct instance_bucket {
|
struct instance_bucket {
|
||||||
std::vector<TAnimModel *> instances;
|
std::vector<TAnimModel *> instances;
|
||||||
// caches owned by the renderer; emptied whenever the content of the bucket changes
|
// caches owned by the renderer; emptied whenever the content of the bucket changes
|
||||||
mutable std::vector<instance_bounds> bounds; // copies for the instances, in the same order
|
mutable std::vector<instance_record> records; // copies for the instances, in the same order
|
||||||
mutable unsigned int boundsversion { 0 };
|
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 unsigned int recordsversion { 0 };
|
||||||
};
|
};
|
||||||
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 *>;
|
||||||
|
|||||||
@@ -345,6 +345,10 @@ private:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// changes whenever a node is shown or hidden, or a model instance stops being drawn along with other instances of its
|
||||||
|
// model. copies of these states (instance records of the renderer) are made anew once it changes
|
||||||
|
inline unsigned int node_state_version { 1 };
|
||||||
|
|
||||||
// base interface for nodes which can be actvated in scenario editor
|
// base interface for nodes which can be actvated in scenario editor
|
||||||
class basic_node {
|
class basic_node {
|
||||||
|
|
||||||
@@ -460,6 +464,9 @@ basic_node::location() {
|
|||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
basic_node::visible( bool const Visible ) {
|
basic_node::visible( bool const Visible ) {
|
||||||
|
if( Visible != m_visible ) {
|
||||||
|
++node_state_version;
|
||||||
|
}
|
||||||
m_visible = Visible;
|
m_visible = Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user