mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-27 12:57:29 +01:00
perf(sleepers): the instances of the sleepers and timbers of a track sent to the gpu once, as it's laid, and kept there in a buffer of their own; in each pass the runs of them in sight turned by the modelviews of their cells into the instance buffer of the frame by a compute pass, one dispatch and one barrier a pass, and drawn in one instanced draw for each model, part and level of detail, in place of the cpu turning every instance every frame
This commit is contained in:
@@ -393,6 +393,7 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46localshadows.cpp"
|
||||
"rendering/opengl46/opengl46instancecull.cpp"
|
||||
"rendering/opengl46/opengl46hiz.cpp"
|
||||
"rendering/opengl46/opengl46timbers.cpp"
|
||||
"rendering/opengl46/opengl46shadowfit.cpp"
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
|
||||
@@ -29,6 +29,11 @@ void gl::glsl_common_setup()
|
||||
"#define HIZ_GROUP_SIZE " + std::to_string(HIZ_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_GROUP_SIZE " + std::to_string(SHADOW_FIT_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_SSBO_BINDING " + std::to_string(SHADOW_FIT_SSBO_BINDING) + "\n" +
|
||||
"#define TIMBER_GROUP_SIZE " + std::to_string(TIMBER_GROUP_SIZE) + "\n" +
|
||||
"#define TIMBER_LOCAL_SSBO_BINDING " + std::to_string(TIMBER_LOCAL_SSBO_BINDING) + "\n" +
|
||||
"#define TIMBER_RANGE_SSBO_BINDING " + std::to_string(TIMBER_RANGE_SSBO_BINDING) + "\n" +
|
||||
"#define TIMBER_OUTPUT_SSBO_BINDING " + std::to_string(TIMBER_OUTPUT_SSBO_BINDING) + "\n" +
|
||||
"#define TIMBER_ORIGIN_SSBO_BINDING " + std::to_string(TIMBER_ORIGIN_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_GROUP_SIZE " + std::to_string(PARTICLE_GROUP_SIZE) + "\n" +
|
||||
"#define PARTICLE_SSBO_BINDING " + std::to_string(PARTICLE_SSBO_BINDING) + "\n" +
|
||||
"#define PARTICLE_SOURCE_SSBO_BINDING " + std::to_string(PARTICLE_SOURCE_SSBO_BINDING) + "\n" +
|
||||
|
||||
@@ -141,6 +141,17 @@ void gl::frame_stream::next_frame()
|
||||
}
|
||||
|
||||
GLintptr gl::frame_stream::push(void const *Data, GLsizeiptr Size)
|
||||
{
|
||||
auto const offset{reserve(Size)};
|
||||
auto const ¤t{m_slots[m_slot]};
|
||||
if (current.data != nullptr)
|
||||
std::memcpy(current.data + offset, Data, static_cast<std::size_t>(Size));
|
||||
else
|
||||
glNamedBufferSubData(current.buffer, offset, Size, Data);
|
||||
return offset;
|
||||
}
|
||||
|
||||
GLintptr gl::frame_stream::reserve(GLsizeiptr Size)
|
||||
{
|
||||
auto *current{&m_slots[m_slot]};
|
||||
auto offset{align_up(m_fill, m_alignment)};
|
||||
@@ -167,13 +178,8 @@ GLintptr gl::frame_stream::push(void const *Data, GLsizeiptr Size)
|
||||
glFinish();
|
||||
++m_waits;
|
||||
}
|
||||
current = &m_slots[m_slot];
|
||||
offset = m_start;
|
||||
}
|
||||
if (current->data != nullptr)
|
||||
std::memcpy(current->data + offset, Data, static_cast<std::size_t>(Size));
|
||||
else
|
||||
glNamedBufferSubData(current->buffer, offset, Size, Data);
|
||||
m_fill = offset + Size;
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace gl
|
||||
// bigger one (generation() changes, buffer() tells which to bind); the old one stays until the frame is done with, so
|
||||
// what was written to it can still be bound and drawn from later in the frame
|
||||
GLintptr push(void const *Data, GLsizeiptr Size);
|
||||
// the room for Size bytes in the buffer of the frame, as push() makes it, left for the gpu to write (a compute pass)
|
||||
GLintptr reserve(GLsizeiptr Size);
|
||||
|
||||
GLuint buffer() const
|
||||
{
|
||||
|
||||
18
gl/ubo.h
18
gl/ubo.h
@@ -506,6 +506,24 @@ namespace gl
|
||||
const GLuint PARTICLE_OUTPUT_SSBO_BINDING = 12; // what the draw reads, a particle_draw per particle drawn
|
||||
const GLuint PARTICLE_COMMAND_SSBO_BINDING = 13; // the indirect draw command, whose instances the passes count
|
||||
|
||||
// the instances of the timbers of the tracks (opengl46_timbers, timber_instances.comp): work group of the pass and the binding
|
||||
// points of its buffers, ones of the instance culling, which binds its buffers before every use; the pass binds its own
|
||||
const GLuint TIMBER_GROUP_SIZE = 64;
|
||||
const GLuint TIMBER_LOCAL_SSBO_BINDING = 9; // the transforms of the instances of the tracks, relative to the origins of the tracks
|
||||
const GLuint TIMBER_RANGE_SSBO_BINDING = 11; // the runs of them drawn, a timber_range each
|
||||
const GLuint TIMBER_OUTPUT_SSBO_BINDING = 12; // the instance buffer of the frame, their modelviews written there
|
||||
const GLuint TIMBER_ORIGIN_SSBO_BINDING = 13; // the modelviews of the origins of the cells of the runs
|
||||
|
||||
// layout std430: GLSL timber_range. a run of instances: count of them from source in the transforms, to target in the output,
|
||||
// turned by the modelview of origin
|
||||
struct timber_range
|
||||
{
|
||||
uint32_t source;
|
||||
uint32_t count;
|
||||
uint32_t target;
|
||||
uint32_t origin;
|
||||
};
|
||||
|
||||
// layout std430: GLSL smoke_particle_s
|
||||
struct particle_smoke
|
||||
{
|
||||
|
||||
@@ -256,6 +256,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
shadowcull.init(&m_instancecull);
|
||||
m_reflectioncull.init(&m_instancecull);
|
||||
m_hiz.init();
|
||||
m_timbers.init();
|
||||
m_shadowfit.init();
|
||||
m_exposure.init();
|
||||
m_ao.init();
|
||||
@@ -739,6 +740,7 @@ bool opengl46_renderer::Render()
|
||||
// the same for the model blocks of the draws
|
||||
m_instancestream->next_frame();
|
||||
Bind_Instance_Stream();
|
||||
m_timbers.next_frame();
|
||||
model_ubo->next_frame();
|
||||
if (m_drawblockstream)
|
||||
m_drawblockstream->next_frame();
|
||||
@@ -3716,6 +3718,8 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
|
||||
++First;
|
||||
}
|
||||
// the sleepers and timbers of the tracks of all the cells at once
|
||||
Flush_Timbers();
|
||||
// second pass draws elements with their own vbos
|
||||
while (first != Last)
|
||||
{
|
||||
@@ -4413,6 +4417,16 @@ std::uint32_t opengl46_renderer::Upload_Instance_Modelviews( glm::mat4 const *Mo
|
||||
return static_cast<std::uint32_t>( offset / static_cast<GLintptr>( sizeof( glm::mat4 ) ) );
|
||||
}
|
||||
|
||||
std::uint32_t opengl46_renderer::Reserve_Instance_Modelviews( std::size_t const Count )
|
||||
{
|
||||
auto const generation { m_instancestream->generation() };
|
||||
auto const offset { m_instancestream->reserve( static_cast<GLsizeiptr>( Count * sizeof( glm::mat4 ) ) ) };
|
||||
if( m_instancestream->generation() != generation ) {
|
||||
Bind_Instance_Stream();
|
||||
}
|
||||
return static_cast<std::uint32_t>( offset / static_cast<GLintptr>( sizeof( glm::mat4 ) ) );
|
||||
}
|
||||
|
||||
void opengl46_renderer::Bind_Instance_Stream() const
|
||||
{
|
||||
GLuint const buffer { m_instancestream->buffer() };
|
||||
@@ -6186,10 +6200,11 @@ void opengl46_renderer::Render_Sleepers( TTrack *Track )
|
||||
}
|
||||
}
|
||||
|
||||
// Renders the sleepers and switch timbers of a run of tracks of a cell. Each beam is made of instances of the parts of a model or
|
||||
// of a whole model, kept by its track relative to the origin of the track. The runs of beams in range and in sight are gathered
|
||||
// for the whole run of tracks by the model and its part or its level of detail, and each is drawn in one instanced batch.
|
||||
// In the shadow pass only those near the camera are drawn, their shadows falling where they can be seen.
|
||||
// Gathers the sleepers and switch timbers of a run of tracks of a cell. Each beam is made of instances of the parts of a model or
|
||||
// of a whole model, kept by its track relative to the origin of the track, on the cpu and on the gpu (m_timbers). The runs of beams
|
||||
// in range and in sight are gathered by the model and its part or its level of detail, for all the cells of the pass, which
|
||||
// Flush_Timbers() draws each in one instanced batch. In the shadow pass only those near the camera are drawn, their shadows falling
|
||||
// where they can be seen.
|
||||
void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last )
|
||||
{
|
||||
if( Global.SleeperDistance <= 0.f ) { return; }
|
||||
@@ -6209,19 +6224,15 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
|
||||
}
|
||||
// the fastenings laid out to a little past where they're drawn
|
||||
auto const fasteningrange = std::min( static_cast<double>( Global.SleeperDistance ), track_sleepers::fastening_range ) + track_sleepers::fastening_lead;
|
||||
glm::mat4 const originmodelview = OpenGLMatrices.data( GL_MODELVIEW );
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
bucket.instances.clear();
|
||||
}
|
||||
auto const bucket_for = [&]( track_sleepers::beam_model const *Model, int const Part, float const Band ) -> std::vector<glm::mat4> & {
|
||||
auto const bucket_for = [&]( track_sleepers::beam_model const *Model, int const Part, float const Band ) -> timber_bucket & {
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
if( bucket.model == Model && bucket.part == Part && bucket.band == Band ) {
|
||||
return bucket.instances;
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
m_timberbuckets.push_back( { Model, Part, Band, {} } );
|
||||
return m_timberbuckets.back().instances; };
|
||||
// the level of detail of the whole model a beam is seen at, from its distance
|
||||
m_timberbuckets.push_back( { Model, Part, Band, {}, 0, 0 } );
|
||||
return m_timberbuckets.back(); };
|
||||
// the level of detail of the whole model a run of beams is seen at, from its distance
|
||||
auto const band_of = []( track_sleepers::beam_model const &Model, double const Distance ) {
|
||||
auto const squaredistance { static_cast<float>( Distance * Distance ) };
|
||||
auto band { Model.bands.empty() ? 0.f : Model.bands.front() };
|
||||
@@ -6231,21 +6242,7 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
|
||||
}
|
||||
}
|
||||
return band; };
|
||||
// the beams of a run made of a whole model, each at the level of detail of its own distance; the run is culled as one
|
||||
auto const put_by_band = [&]( track_sleepers::beam_model const &Model, std::vector<glm::mat4> const &Transforms, std::uint32_t const From, std::uint32_t const To, glm::dvec3 const &Origin ) {
|
||||
std::vector<glm::mat4> *instances { nullptr };
|
||||
auto current { 0.f };
|
||||
for( auto index = From; index < To; ++index ) {
|
||||
auto const &local = Transforms[ index ];
|
||||
auto const band = band_of( Model, glm::length( Origin + glm::dvec3( glm::vec3( local[ 3 ] ) ) - camera ) );
|
||||
if( instances == nullptr || band != current ) {
|
||||
// looked up again only as the band changes; a bucket added on the way moves the others, so the pointer goes with it
|
||||
instances = &bucket_for( &Model, -1, band );
|
||||
current = band;
|
||||
}
|
||||
instances->emplace_back( originmodelview * local );
|
||||
}
|
||||
};
|
||||
m_timbertracks.clear();
|
||||
for( auto first { First }; first != Last; ++first ) {
|
||||
auto *track = *first;
|
||||
if( false == track->m_visible || false == track->m_sleepers3d.active ) { continue; }
|
||||
@@ -6272,10 +6269,33 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
|
||||
m_timberlaying += std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - start ).count();
|
||||
}
|
||||
}
|
||||
if( track->m_sleepers3d.timbers == nullptr || track->m_sleepers3d.chunks.empty() ) { continue; }
|
||||
m_timbertracks.push_back( track );
|
||||
}
|
||||
if( m_timbertracks.empty() ) { return; }
|
||||
// the instances of the tracks on the gpu; those of the cells before expanded first when the buffer is to be started over
|
||||
if( false == m_timbers.place( m_timbertracks, false ) ) {
|
||||
Flush_Timbers();
|
||||
m_timbers.place( m_timbertracks, true );
|
||||
}
|
||||
// the runs of them in range and in sight, turned by the modelview of the cell
|
||||
auto const origin { static_cast<std::uint32_t>( m_timberorigins.size() ) };
|
||||
m_timberorigins.push_back( OpenGLMatrices.data( GL_MODELVIEW ) );
|
||||
// a run of instances of a track for the bucket; the reference to it goes with the next bucket added
|
||||
auto const add_run = [&]( timber_bucket &Bucket, std::uint32_t const Source, std::uint32_t const Count ) {
|
||||
Bucket.runs.push_back( { Source, Count, 0, origin } );
|
||||
Bucket.count += Count; };
|
||||
for( auto *track : m_timbertracks ) {
|
||||
auto const &laid = track->m_sleepers3d;
|
||||
if( laid.timbers == nullptr ) { continue; }
|
||||
// the transforms of the beams are relative to the origin of the track, which the modelview is translated to already
|
||||
auto const &trackmodelview = originmodelview;
|
||||
// where the instances of the parts, of the whole timbers and of the plain sleepers of the track start on the gpu
|
||||
std::array<std::uint32_t, track_sleepers::part_count> partfirst;
|
||||
auto next { m_timbers.first( *track ) };
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
partfirst[ part ] = next;
|
||||
next += static_cast<std::uint32_t>( laid.instances[ part ].size() );
|
||||
}
|
||||
auto const wholefirst { next };
|
||||
auto const plainfirst { wholefirst + static_cast<std::uint32_t>( laid.whole.size() ) };
|
||||
for( auto const &chunk : laid.chunks ) {
|
||||
auto const centre = track->m_origin + glm::dvec3( chunk.centre );
|
||||
auto const distance = glm::length( centre - camera );
|
||||
@@ -6286,66 +6306,86 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_
|
||||
if( chunk.last[ part ] <= chunk.first[ part ] ) { continue; }
|
||||
// the fastenings only near, they're small
|
||||
if( part >= track_sleepers::timber_parts && distance > std::min( range, track_sleepers::fastening_range ) + chunk.radius ) { continue; }
|
||||
auto &instances = bucket_for( laid.timbers, part, 0.f );
|
||||
for( auto index = chunk.first[ part ]; index < chunk.last[ part ]; ++index ) {
|
||||
instances.emplace_back( trackmodelview * laid.instances[ part ][ index ] );
|
||||
add_run( bucket_for( laid.timbers, part, 0.f ), partfirst[ part ] + chunk.first[ part ], chunk.last[ part ] - chunk.first[ part ] );
|
||||
}
|
||||
}
|
||||
// the beams made of a whole model at the level of detail of the distance of the run
|
||||
if( chunk.wholelast > chunk.wholefirst ) {
|
||||
add_run( bucket_for( laid.timbers, -1, band_of( *laid.timbers, distance ) ), wholefirst + chunk.wholefirst, chunk.wholelast - chunk.wholefirst );
|
||||
}
|
||||
if( laid.plains != nullptr && chunk.plainlast > chunk.plainfirst ) {
|
||||
add_run( bucket_for( laid.plains, -1, band_of( *laid.plains, distance ) ), plainfirst + chunk.plainfirst, chunk.plainlast - chunk.plainfirst );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the runs of the sleepers and timbers gathered by Render_Timbers() over the cells of the pass made into modelviews on the gpu at
|
||||
// once, and each bucket drawn in one instanced batch
|
||||
void opengl46_renderer::Flush_Timbers()
|
||||
{
|
||||
std::size_t total { 0 };
|
||||
for( auto const &bucket : m_timberbuckets ) {
|
||||
total += bucket.count;
|
||||
}
|
||||
if( total > 0 ) {
|
||||
// in the instance buffer of the frame bucket after bucket, as in Render_Sleepers(), which the recorded walk of the depth
|
||||
// pass keeps for its replay as well
|
||||
auto target { Reserve_Instance_Modelviews( total ) };
|
||||
m_timberranges.clear();
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
bucket.first = target;
|
||||
for( auto const &run : bucket.runs ) {
|
||||
m_timberranges.push_back( { run.source, run.count, target, run.origin } );
|
||||
target += run.count;
|
||||
}
|
||||
}
|
||||
m_timbers.expand( m_timberorigins, m_timberranges, m_instancestream->buffer() );
|
||||
auto const alpha = ( 0x30300030 ^ 0x0F0F000F );
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
if( bucket.count == 0 ) { continue; }
|
||||
auto const &beammodel = *bucket.model;
|
||||
auto *model = beammodel.model;
|
||||
if( bucket.part >= 0 ) {
|
||||
// only the part of the model is visible, its siblings are skipped as the model is walked
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
if( beammodel.parts[ part ] != nullptr ) {
|
||||
beammodel.parts[ part ]->iVisible = ( part == bucket.part ? 1 : 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
if( chunk.wholelast > chunk.wholefirst ) {
|
||||
put_by_band( *laid.timbers, laid.whole, chunk.wholefirst, chunk.wholelast, track->m_origin );
|
||||
material_data skins {};
|
||||
if( beammodel.skin != null_handle ) {
|
||||
skins.replacable_skins[ 1 ] = beammodel.skin;
|
||||
}
|
||||
if( laid.plains != nullptr && chunk.plainlast > chunk.plainfirst ) {
|
||||
put_by_band( *laid.plains, laid.plain, chunk.plainfirst, chunk.plainlast, track->m_origin );
|
||||
::glPushMatrix();
|
||||
::glLoadIdentity();
|
||||
m_current_instance_count = bucket.count;
|
||||
m_current_instance_base = bucket.first;
|
||||
// inside the range of the level of detail the beams are at
|
||||
model->Root->fSquareDist = ( bucket.part >= 0 ? 0.f : bucket.band + 1.f );
|
||||
model->Root->ReplacableSet( ( beammodel.skin != null_handle ? skins.replacable_skins : nullptr ), alpha );
|
||||
model->Root->pRoot = model;
|
||||
Render( model->Root );
|
||||
m_current_instance_count = 0;
|
||||
m_current_instance_base = 0;
|
||||
::glPopMatrix();
|
||||
++m_renderpass.draw_stats.instanced_drawcalls;
|
||||
if( bucket.part >= 0 ) {
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
if( beammodel.parts[ part ] != nullptr ) {
|
||||
beammodel.parts[ part ]->iVisible = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_renderpass.draw_stats.instances += static_cast<int>( total );
|
||||
}
|
||||
|
||||
auto const alpha = ( 0x30300030 ^ 0x0F0F000F );
|
||||
std::size_t total { 0 };
|
||||
for( auto &bucket : m_timberbuckets ) {
|
||||
auto const &instances = bucket.instances;
|
||||
if( instances.empty() ) { continue; }
|
||||
total += instances.size();
|
||||
auto const &beammodel = *bucket.model;
|
||||
auto *model = beammodel.model;
|
||||
if( bucket.part >= 0 ) {
|
||||
// only the part of the model is visible, its siblings are skipped as the model is walked
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
if( beammodel.parts[ part ] != nullptr ) {
|
||||
beammodel.parts[ part ]->iVisible = ( part == bucket.part ? 1 : 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
material_data skins {};
|
||||
if( beammodel.skin != null_handle ) {
|
||||
skins.replacable_skins[ 1 ] = beammodel.skin;
|
||||
}
|
||||
// the instances go to the instance stream of the frame, as in Render_Sleepers(), which the recorded walk of the depth
|
||||
// pass keeps for its replay as well
|
||||
auto const baseinstance { Upload_Instance_Modelviews( instances.data(), instances.size() ) };
|
||||
::glPushMatrix();
|
||||
::glLoadIdentity();
|
||||
m_current_instance_count = instances.size();
|
||||
m_current_instance_base = baseinstance;
|
||||
// inside the range of the level of detail the beams are at
|
||||
model->Root->fSquareDist = ( bucket.part >= 0 ? 0.f : bucket.band + 1.f );
|
||||
model->Root->ReplacableSet( ( beammodel.skin != null_handle ? skins.replacable_skins : nullptr ), alpha );
|
||||
model->Root->pRoot = model;
|
||||
Render( model->Root );
|
||||
m_current_instance_count = 0;
|
||||
m_current_instance_base = 0;
|
||||
::glPopMatrix();
|
||||
++m_renderpass.draw_stats.instanced_drawcalls;
|
||||
if( bucket.part >= 0 ) {
|
||||
for( int part = 0; part < track_sleepers::part_count; ++part ) {
|
||||
if( beammodel.parts[ part ] != nullptr ) {
|
||||
beammodel.parts[ part ]->iVisible = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
bucket.runs.clear();
|
||||
bucket.count = 0;
|
||||
}
|
||||
m_renderpass.draw_stats.instances += static_cast<int>( total );
|
||||
m_timberorigins.clear();
|
||||
}
|
||||
|
||||
bool opengl46_renderer::Render(TDynamicObject *Dynamic)
|
||||
|
||||
@@ -44,6 +44,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46localshadows.h"
|
||||
#include "rendering/opengl46/opengl46instancecull.h"
|
||||
#include "rendering/opengl46/opengl46hiz.h"
|
||||
#include "rendering/opengl46/opengl46timbers.h"
|
||||
#include "rendering/opengl46/opengl46shadowfit.h"
|
||||
|
||||
// bare-bones render controller, in lack of anything better yet
|
||||
@@ -376,6 +377,8 @@ class opengl46_renderer : public gfx_renderer {
|
||||
static scene::bounding_area Bucket_Area( scene::basic_cell::instance_bucket const &Bucket );
|
||||
// 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 );
|
||||
// the room for Count modelviews in the instance buffer of the frame, for the gpu to write. returns: base instance of the first
|
||||
std::uint32_t Reserve_Instance_Modelviews( std::size_t const Count );
|
||||
// binds the instance buffer of the frame for the vertex shaders (INSTANCE_SSBO_BINDING)
|
||||
void Bind_Instance_Stream() const;
|
||||
// draws the instances queued in the pass from the result of the gpu culling (Global.gfx_gpucull 1): makes the depth
|
||||
@@ -491,9 +494,11 @@ class opengl46_renderer : public gfx_renderer {
|
||||
// queues the sleepers of a track in the gpu culling of the pass (Global.gfx_gpucull_sleepers), with the gates of
|
||||
// Render_Sleepers(), making their records first when they're missing or out of date
|
||||
void Queue_Sleepers( TTrack *Track );
|
||||
// renders the sleepers and switch timbers of the tracks in range (TTrack::m_sleepers3d), laying them first where they're out of date.
|
||||
// the parts of the model are drawn in instanced batches for the whole run of tracks, the runs of beams out of sight left out
|
||||
// gathers the sleepers and switch timbers of the tracks in range (TTrack::m_sleepers3d), laying them first where they're out of
|
||||
// date. the parts of the model are drawn in instanced batches for all the cells of the pass, the runs of beams out of sight left out
|
||||
void Render_Timbers( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last );
|
||||
// draws what Render_Timbers() gathered in the pass since the last time
|
||||
void Flush_Timbers();
|
||||
// queues a model instance drawn on its own in the gpu culling of the pass (Global.gfx_gpucull_models), as a bucket of
|
||||
// its own, making its record first when it's missing or out of date. Inrange: the cell of the instance lies within the
|
||||
// range of the pass. returns true when the instance isn't drawn by the cpu in the pass: it's drawn from the result of the
|
||||
@@ -709,6 +714,7 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
|
||||
// depth pyramid for the occlusion test of the gpu culling
|
||||
opengl46_hiz m_hiz;
|
||||
opengl46_timbers m_timbers; // the instances of the sleepers and timbers of the tracks on the gpu
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (Global.gfx_shadow_sdsm)
|
||||
opengl46_shadow_fit m_shadowfit;
|
||||
// with a cascade fitted: the corners of the area its map is to cover, for setup_pass()
|
||||
@@ -989,15 +995,20 @@ class opengl46_renderer : public gfx_renderer {
|
||||
// per-instance modelview from instance_ssbo[gl_BaseInstance + gl_InstanceID]. Reset to 0 on exit.
|
||||
std::size_t m_current_instance_count { 0 };
|
||||
std::uint32_t m_current_instance_base { 0 };
|
||||
// camera-space transforms of the sleepers and timbers drawn by Render_Timbers, by the model and its part or its level of
|
||||
// detail; kept from frame to frame to reuse their allocations
|
||||
// the runs of the instances of the sleepers and timbers drawn by Render_Timbers, by the model and its part or its level of
|
||||
// detail, where they lie in m_timbers; kept from frame to frame to reuse their allocations
|
||||
struct timber_bucket {
|
||||
track_sleepers::beam_model const *model { nullptr };
|
||||
int part { -1 }; // of a model put together from parts; -1: the whole model
|
||||
float band { 0.f }; // the squared distance the level of detail of the whole model is seen from
|
||||
std::vector<glm::mat4> instances;
|
||||
std::vector<gl::timber_range> runs;
|
||||
std::uint32_t count { 0 }; // instances of the runs
|
||||
std::uint32_t first { 0 }; // base instance of their modelviews in the instance buffer of the frame
|
||||
};
|
||||
std::vector<timber_bucket> m_timberbuckets;
|
||||
std::vector<TTrack *> m_timbertracks; // the tracks of a cell with beams to draw
|
||||
std::vector<gl::timber_range> m_timberranges; // the runs of all the buckets, as they're expanded
|
||||
std::vector<glm::mat4> m_timberorigins; // the modelviews of the cells of the runs
|
||||
// persistent scratch buffer for Render_Instanced(): holds the per-instance
|
||||
// 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
|
||||
|
||||
152
rendering/opengl46/opengl46timbers.cpp
Normal file
152
rendering/opengl46/opengl46timbers.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
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/opengl46timbers.h"
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "world/Track.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
// the buffer to start with, and how soon after being started over it's too small for the tracks in sight
|
||||
std::uint32_t constexpr initial_capacity{65536};
|
||||
unsigned int constexpr restart_frames{600};
|
||||
|
||||
// the instances of a track, in the order they lie in the buffer
|
||||
std::uint32_t count_of(track_sleepers::laid const &Laid)
|
||||
{
|
||||
std::size_t count{Laid.whole.size() + Laid.plain.size()};
|
||||
for (auto const &instances : Laid.instances)
|
||||
count += instances.size();
|
||||
return static_cast<std::uint32_t>(count);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_timbers::~opengl46_timbers()
|
||||
{
|
||||
if (m_buffer != 0)
|
||||
glDeleteBuffers(1, &m_buffer);
|
||||
}
|
||||
|
||||
void opengl46_timbers::init()
|
||||
{
|
||||
gl::shader shader("timber_instances.comp");
|
||||
m_program = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
// the modelviews of the origins and the runs, both placed at whole matrices
|
||||
m_ranges = std::make_unique<gl::frame_stream>(
|
||||
static_cast<GLsizeiptr>(4096 * sizeof(gl::timber_range)), static_cast<GLsizeiptr>(sizeof(glm::mat4)), Global.gfx_bufferstreaming);
|
||||
restart(0);
|
||||
}
|
||||
|
||||
void opengl46_timbers::restart(std::uint32_t const Needed)
|
||||
{
|
||||
auto capacity{std::max(m_capacity, initial_capacity)};
|
||||
if ((m_capacity > 0) && (m_frame - m_restartframe < restart_frames))
|
||||
capacity *= 2;
|
||||
while (capacity < 2 * Needed)
|
||||
capacity *= 2;
|
||||
// the draws and passes issued with the old buffer keep it until the gpu is done with them
|
||||
if (m_buffer != 0)
|
||||
glDeleteBuffers(1, &m_buffer);
|
||||
glCreateBuffers(1, &m_buffer);
|
||||
glNamedBufferStorage(m_buffer, static_cast<GLsizeiptr>(capacity) * static_cast<GLsizeiptr>(sizeof(glm::mat4)), nullptr, GL_DYNAMIC_STORAGE_BIT);
|
||||
m_capacity = capacity;
|
||||
m_fill = 0;
|
||||
m_restartframe = m_frame;
|
||||
m_tracks.clear();
|
||||
}
|
||||
|
||||
bool opengl46_timbers::place(std::vector<TTrack *> const &Tracks, bool const Restart)
|
||||
{
|
||||
auto const placed_now = [&](TTrack const &Track) {
|
||||
auto const found{m_tracks.find(&Track)};
|
||||
return (found != m_tracks.end()) && (found->second.made == Track.m_sleepers3d.made);
|
||||
};
|
||||
std::uint32_t needed{0};
|
||||
std::uint32_t all{0};
|
||||
for (auto const *track : Tracks)
|
||||
{
|
||||
auto const count{count_of(track->m_sleepers3d)};
|
||||
all += count;
|
||||
if (false == placed_now(*track))
|
||||
needed += count;
|
||||
}
|
||||
if (needed == 0)
|
||||
return true;
|
||||
if (m_fill + needed > m_capacity)
|
||||
{
|
||||
if (false == Restart)
|
||||
return false;
|
||||
restart(all);
|
||||
}
|
||||
for (auto const *track : Tracks)
|
||||
{
|
||||
if (placed_now(*track))
|
||||
continue;
|
||||
auto const &laid{track->m_sleepers3d};
|
||||
auto const first{m_fill};
|
||||
auto const send = [&](std::vector<glm::mat4> const &Instances) {
|
||||
if (false == Instances.empty())
|
||||
glNamedBufferSubData(m_buffer, static_cast<GLintptr>(m_fill) * static_cast<GLintptr>(sizeof(glm::mat4)),
|
||||
static_cast<GLsizeiptr>(Instances.size() * sizeof(glm::mat4)), Instances.data());
|
||||
m_fill += static_cast<std::uint32_t>(Instances.size());
|
||||
};
|
||||
for (auto const &instances : laid.instances)
|
||||
send(instances);
|
||||
send(laid.whole);
|
||||
send(laid.plain);
|
||||
m_tracks[track] = {laid.made, first};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t opengl46_timbers::first(TTrack const &Track) const
|
||||
{
|
||||
auto const found{m_tracks.find(&Track)};
|
||||
return (found != m_tracks.end() ? found->second.first : 0);
|
||||
}
|
||||
|
||||
void opengl46_timbers::expand(std::vector<glm::mat4> const &Origins, std::vector<gl::timber_range> const &Ranges, GLuint const Output)
|
||||
{
|
||||
if (Ranges.empty())
|
||||
return;
|
||||
// sent together, in one buffer of the stream
|
||||
auto const originbytes{Origins.size() * sizeof(glm::mat4)};
|
||||
m_upload.resize(originbytes + Ranges.size() * sizeof(gl::timber_range));
|
||||
std::memcpy(m_upload.data(), Origins.data(), originbytes);
|
||||
std::memcpy(m_upload.data() + originbytes, Ranges.data(), Ranges.size() * sizeof(gl::timber_range));
|
||||
auto const origins{m_ranges->push(m_upload.data(), static_cast<GLsizeiptr>(m_upload.size()))};
|
||||
auto const ranges{origins + static_cast<GLintptr>(originbytes)};
|
||||
GLuint const buffers[]{m_buffer, m_ranges->buffer()};
|
||||
::glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::TIMBER_LOCAL_SSBO_BINDING, 1, &buffers[0]);
|
||||
::glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::TIMBER_RANGE_SSBO_BINDING, 1, &buffers[1]);
|
||||
::glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::TIMBER_OUTPUT_SSBO_BINDING, 1, &Output);
|
||||
::glBindBuffersBase(GL_SHADER_STORAGE_BUFFER, gl::TIMBER_ORIGIN_SSBO_BINDING, 1, &buffers[1]);
|
||||
// a dispatch takes at most 65535 work groups
|
||||
auto const first{static_cast<std::uint32_t>(ranges / static_cast<GLintptr>(sizeof(gl::timber_range)))};
|
||||
auto const origin{static_cast<std::uint32_t>(origins / static_cast<GLintptr>(sizeof(glm::mat4)))};
|
||||
for (std::size_t done = 0; done < Ranges.size();)
|
||||
{
|
||||
auto const count{std::min<std::size_t>(Ranges.size() - done, 65535)};
|
||||
::glProgramUniform4ui(*m_program, 0, first + static_cast<std::uint32_t>(done), origin, 0, 0);
|
||||
m_program->dispatch(static_cast<GLuint>(count));
|
||||
done += count;
|
||||
}
|
||||
::glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
}
|
||||
|
||||
void opengl46_timbers::next_frame()
|
||||
{
|
||||
++m_frame;
|
||||
m_ranges->next_frame();
|
||||
}
|
||||
66
rendering/opengl46/opengl46timbers.h
Normal file
66
rendering/opengl46/opengl46timbers.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/shader.h"
|
||||
#include "gl/streambuffer.h"
|
||||
#include "gl/ubo.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// the instances of the timbers and sleepers of the tracks (track_sleepers::laid) kept on the gpu, those of a track sent once as
|
||||
// it's laid, and the runs of them drawn made into the modelviews of the instance buffer of the frame by a compute pass
|
||||
// (timber_instances.comp): for a run the cpu gives only where it lies and where it goes, not its matrices
|
||||
class opengl46_timbers
|
||||
{
|
||||
public:
|
||||
opengl46_timbers() = default;
|
||||
opengl46_timbers(opengl46_timbers const &) = delete;
|
||||
opengl46_timbers &operator=(opengl46_timbers const &) = delete;
|
||||
~opengl46_timbers();
|
||||
|
||||
// throws gl::shader_exception
|
||||
void init();
|
||||
// the instances of the tracks on the gpu, sent again for a track laid anew since; when those to send don't fit in what's left
|
||||
// of the buffer, it's started over for all the tracks, a bigger one when it was started over not long before. without
|
||||
// Restart nothing is sent then, and false returned: the runs of the instances placed before are to be expanded first
|
||||
bool place(std::vector<TTrack *> const &Tracks, bool const Restart);
|
||||
// where the instances of a track placed lie in the buffer: the first of the parts in turn, then of the whole timbers, then of
|
||||
// the plain sleepers
|
||||
std::uint32_t first(TTrack const &Track) const;
|
||||
// the runs written to the instance buffer Output, each as the modelview of its origin times its transforms, for the draws after
|
||||
void expand(std::vector<glm::mat4> const &Origins, std::vector<gl::timber_range> const &Ranges, GLuint const Output);
|
||||
void next_frame();
|
||||
|
||||
private:
|
||||
struct placed
|
||||
{
|
||||
std::uint64_t made; // the making of the instances of the track sent
|
||||
std::uint32_t first;
|
||||
};
|
||||
|
||||
// a new buffer, room for Needed instances at least, the tracks to be sent again
|
||||
void restart(std::uint32_t const Needed);
|
||||
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
std::unique_ptr<gl::frame_stream> m_ranges;
|
||||
GLuint m_buffer{0};
|
||||
std::uint32_t m_capacity{0}; // in instances
|
||||
std::uint32_t m_fill{0};
|
||||
unsigned int m_frame{0};
|
||||
unsigned int m_restartframe{0};
|
||||
std::unordered_map<TTrack const *, placed> m_tracks;
|
||||
std::vector<std::uint8_t> m_upload; // the origins and the runs of an expand(), as they're sent
|
||||
};
|
||||
46
shaders/timber_instances.comp
Normal file
46
shaders/timber_instances.comp
Normal file
@@ -0,0 +1,46 @@
|
||||
// the modelviews of the instances of the timbers and sleepers of the tracks drawn in a pass (opengl46_timbers): their transforms
|
||||
// are kept relative to the origins of the tracks, each run of them is turned by the modelview of the origin of its cell and written
|
||||
// to the instance buffer of the frame, where the instanced draws read it. a work group a run
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = TIMBER_GROUP_SIZE) in;
|
||||
|
||||
struct timber_range
|
||||
{
|
||||
uint source;
|
||||
uint count;
|
||||
uint target;
|
||||
uint origin;
|
||||
};
|
||||
|
||||
layout(std430, binding = TIMBER_LOCAL_SSBO_BINDING) readonly buffer timber_local_ssbo
|
||||
{
|
||||
mat4 timber_local[];
|
||||
};
|
||||
|
||||
layout(std430, binding = TIMBER_RANGE_SSBO_BINDING) readonly buffer timber_range_ssbo
|
||||
{
|
||||
timber_range timber_ranges[];
|
||||
};
|
||||
|
||||
layout(std430, binding = TIMBER_OUTPUT_SSBO_BINDING) writeonly buffer timber_output_ssbo
|
||||
{
|
||||
mat4 timber_output[];
|
||||
};
|
||||
|
||||
layout(std430, binding = TIMBER_ORIGIN_SSBO_BINDING) readonly buffer timber_origin_ssbo
|
||||
{
|
||||
mat4 timber_origins[];
|
||||
};
|
||||
|
||||
// x: the first run of the dispatch, y: the first modelview of the origins
|
||||
layout(location = 0) uniform uvec4 params;
|
||||
|
||||
void main()
|
||||
{
|
||||
timber_range range = timber_ranges[params.x + gl_WorkGroupID.x];
|
||||
mat4 origin = timber_origins[params.y + range.origin];
|
||||
for (uint index = gl_LocalInvocationID.x; index < range.count; index += TIMBER_GROUP_SIZE)
|
||||
timber_output[range.target + index] = origin * timber_local[range.source + index];
|
||||
}
|
||||
@@ -125,11 +125,15 @@ std::pair<double, double> shared_zone( TTrack const &Track, int const Path ) {
|
||||
|
||||
namespace {
|
||||
|
||||
// the instances made so far, of all the tracks
|
||||
std::uint64_t makings { 0 };
|
||||
|
||||
// the beams as instances of the parts of the model of the track: those of the fastenings with them, or left for later and the beams
|
||||
// kept for them meanwhile
|
||||
void instance( TTrack &Track, std::vector<beam> &&Beams, bool const Fastened ) {
|
||||
|
||||
auto &laid { Track.m_sleepers3d };
|
||||
laid.made = ++makings;
|
||||
for( auto &instances : laid.instances ) {
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ struct laid {
|
||||
std::vector<glm::mat4> whole; // transforms of the timbers of a model without parts, drawn whole
|
||||
std::vector<glm::mat4> plain; // transforms of the plain sleepers, whole models, relative to the origin of the track
|
||||
std::vector<chunk> chunks;
|
||||
std::uint64_t made { 0 }; // which making of the instances they are, none other of any track the same: a copy of them tells by it
|
||||
bool fastened { false }; // the fastenings are laid, else they're laid as the track comes near where they're drawn
|
||||
std::vector<beam> beams; // kept for the fastenings till they're laid
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user