17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 02:18:35 +01:00

Merge remote-tracking branch 'origin/feat/switch-fastenings' into feat/renderers-cleanup

This commit is contained in:
2026-10-31 17:13:45 +01:00
29 changed files with 2676 additions and 177 deletions

View File

@@ -25,9 +25,11 @@ http://mozilla.org/MPL/2.0/.
#include "world/catenary_span.h"
#include "world/MemCell.h"
#include "world/TrackSleepers.h"
#include "world/SleeperRules.h"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "rendering/common/skyocclusion.h"
#include <chrono>
namespace gfx
{
@@ -587,6 +589,8 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L
++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)
{
@@ -1488,10 +1492,11 @@ void scene_walk::Render_Sleepers( TTrack *Track )
walk_sleepers_drawn( *Track, lodbounds, survivors );
}
// 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 (and with walk_timbers_gpu() on the gpu as well). 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 scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last )
{
if( Global.SleeperDistance <= 0.f ) { return; }
@@ -1511,19 +1516,21 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
track_sleepers::release_far( camera, range + 600.0 );
}
}
glm::mat4 const originmodelview = OpenGLMatrices.data( GL_MODELVIEW );
for( auto &bucket : m_timberbuckets ) {
bucket.instances.clear();
if( m_timberlayframe != m_framestamp ) {
m_timberlayframe = m_framestamp;
m_timberlaying = 0.0;
}
auto const bucket_for = [&]( track_sleepers::beam_model const *Model, int const Part, float const Band ) -> std::vector<glm::mat4> & {
// 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;
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() };
@@ -1533,27 +1540,14 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
}
}
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; }
if( glm::length( track->location() - camera ) > range + track->radius() ) {
auto const trackdistance = glm::length( track->location() - camera );
if( trackdistance > range + track->radius() ) {
// worked out ahead, in the background, before it comes in range
if( false == shadows && track->m_sleepers3d.dirty && glm::length( track->location() - camera ) < range + track->radius() + 300.0 ) {
if( false == shadows && track->m_sleepers3d.dirty && trackdistance < range + track->radius() + 300.0 ) {
if( gfx::walk_sync::parallel() ) {
m_deferredprefetch.push_back( track );
}
@@ -1563,17 +1557,61 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
}
continue;
}
if( track->m_sleepers3d.dirty ) {
// laid by one walk at a time (walk_sync)
gfx::walk_sync::change change;
if( track->m_sleepers3d.dirty ) {
track_sleepers::lay( *track );
auto const fastened = ( trackdistance < fasteningrange + track->radius() );
if( track->m_sleepers3d.dirty || ( fastened && false == track->m_sleepers3d.fastened ) ) {
// the nearest at once, the others while the frame has the time, the cells nearest coming first; till then a track draws
// what it had
if( trackdistance < track_sleepers::lay_near + track->radius() || m_timberlaying < track_sleepers::lay_budget ) {
// laid by one walk at a time (walk_sync)
gfx::walk_sync::change change;
auto const start = std::chrono::steady_clock::now();
if( track->m_sleepers3d.dirty ) {
track_sleepers::lay( *track, fastened );
}
else if( fastened && false == track->m_sleepers3d.fastened ) {
track_sleepers::fasten( *track );
}
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; }
auto const gpu = walk_timbers_gpu();
// the instances of the tracks on the gpu; those of the cells before expanded first when the buffer is to be started over
if( gpu && false == walk_timbers_place( m_timbertracks, false ) ) {
Flush_Timbers();
walk_timbers_place( m_timbertracks, true );
}
// the runs of them in range and in sight, turned by the modelview of the cell: on the gpu, or here
glm::mat4 const originmodelview = OpenGLMatrices.data( GL_MODELVIEW );
auto const origin { static_cast<std::uint32_t>( m_timberorigins.size() ) };
if( gpu ) {
m_timberorigins.push_back( originmodelview );
}
// 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::vector<glm::mat4> const &Transforms, std::uint32_t const From, std::uint32_t const Source, std::uint32_t const Count ) {
if( gpu ) {
Bucket.runs.push_back( { Source, Count, 0, origin } );
}
else {
for( auto index = From; index < From + Count; ++index ) {
Bucket.instances.emplace_back( originmodelview * Transforms[ index ] );
}
}
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 { gpu ? walk_timbers_first( *track ) : 0 };
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 );
@@ -1581,64 +1619,100 @@ void scene_walk::Render_Timbers( scene::basic_cell::path_sequence::const_iterato
if( false == m_renderpass.pass_camera.visible( scene::bounding_area{ centre, chunk.radius } ) ) { continue; }
if( laid.timbers->stretchable ) {
for( int part = 0; part < track_sleepers::part_count; ++part ) {
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 ] );
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; }
add_run( bucket_for( laid.timbers, part, 0.f ), laid.instances[ part ], chunk.first[ part ], 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 ) ), laid.whole, chunk.wholefirst, 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 ) ), laid.plain, chunk.plainfirst, plainfirst + chunk.plainfirst, chunk.plainlast - chunk.plainfirst );
}
}
}
}
// the sleepers and timbers gathered by Render_Timbers() over the cells of the pass placed in the instance buffer of the frame at once
// (with walk_timbers_gpu() made into modelviews on the gpu there), and each bucket drawn in one instanced batch
void scene_walk::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
if( walk_timbers_gpu() ) {
auto target { walk_instances_reserve( 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;
}
}
walk_timbers_expand( m_timberorigins, m_timberranges );
}
else {
for( auto &bucket : m_timberbuckets ) {
if( bucket.count == 0 ) { continue; }
bucket.first = walk_instances( bucket.instances.data(), bucket.instances.size() );
}
}
auto const alpha = ( 0x30300030 ^ 0x0F0F000F );
for( auto &bucket : m_timberbuckets ) {
if( bucket.count == 0 ) { continue; }
auto const &beammodel = *bucket.model;
auto *model = beammodel.model;
// the parts of the model shown and hidden for the bucket, held till it's drawn (walk_sync)
gfx::walk_sync::model_hold hold { 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;
// the parts of the model shown and hidden for the bucket, held till it's drawn (walk_sync)
gfx::walk_sync::model_hold hold { 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 ) {
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 { walk_instances( 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 ) {
beammodel.parts[ part ]->iVisible = 1;
}
}
bucket.instances.clear();
bucket.runs.clear();
bucket.count = 0;
}
m_renderpass.draw_stats.instances += static_cast<int>( total );
m_timberorigins.clear();
}
bool scene_walk::Render(TDynamicObject *Dynamic)

View File

@@ -168,9 +168,11 @@ class scene_walk
// no-op if the track has no sleepermodel, Global.SleeperDistance is 0, or the camera is beyond
// Global.SleeperDistance from the track origin.
void Render_Sleepers(TTrack *Track);
// 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();
bool Render_cab(TDynamicObject const *Dynamic, float const Lightlevel, bool const Alpha = false);
bool Render_interior(bool const Alpha = false);
bool Render_lowpoly(TDynamicObject *Dynamic, float const Squaredistance, bool const Setup, bool const Alpha = false);
@@ -234,6 +236,32 @@ class scene_walk
virtual void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) = 0;
// places the modelview matrices of instances in the instance buffer of the frame. returns: base instance of the first of them
virtual std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) = 0;
// the instances of the sleepers and timbers of the tracks kept by the backend on the gpu, and the runs of them drawn made into the
// modelviews of the instance buffer of the frame there (the opengl renderer, opengl46_timbers); by default the walk turns them on
// the cpu and places them with walk_instances()
virtual bool walk_timbers_gpu() const
{
return false;
}
// the instances of the tracks placed on the gpu; false when they don't fit and Restart is false: the runs gathered before are to
// be drawn first
virtual bool walk_timbers_place(std::vector<TTrack *> const &Tracks, bool const Restart)
{
return true;
}
// where the instances of a placed track lie on the gpu: the first of the parts in turn, then of the whole timbers, then of the
// plain sleepers
virtual std::uint32_t walk_timbers_first(TTrack const &Track) const
{
return 0;
}
// the room for Count modelviews in the instance buffer of the frame, for the gpu to write. returns: base instance of the first
virtual std::uint32_t walk_instances_reserve(std::size_t const Count)
{
return 0;
}
// the runs written to the instance buffer of the frame, each as the modelview of its origin times its transforms
virtual void walk_timbers_expand(std::vector<glm::mat4> const &Origins, std::vector<gl::timber_range> const &Ranges) {}
virtual void walk_cull_face(bool const Enabled) = 0;
// the glare of a lamp: drawn without the depth test, added to what is there; drawn only where the point of the light was
// seen (walk_light_query() of the last frame, when the backend has it)
@@ -343,21 +371,29 @@ class scene_walk
gl::model_ubs model_ubs;
unsigned int m_framestamp{0}; // id of currently rendered gfx frame
unsigned int m_timbersweepframe{0}; // the frame the beams of the tracks far away were last let go in
unsigned int m_timberlayframe{0}; // the frame the time spent laying the beams is counted for
double m_timberlaying{0.0}; // in ms
float m_fogrange{2000.0f};
// when > 0, the walk is inside a Render_Instanced() call: the draws are made for this many instances from
// m_current_instance_base onwards. The vertex shader reads per-instance modelview from the instance buffer
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 sleepers and timbers drawn by Render_Timbers, by the model and its part or its level of detail: their camera-space
// transforms, or with walk_timbers_gpu() the runs of their instances on the gpu; 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

View File

@@ -213,6 +213,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
for (auto &shadowcull : m_shadowcull)
shadowcull.init(&m_instancecull);
m_hiz.init();
m_timbers.init();
m_shadowfit.init();
m_exposure.init();
m_ao.init();
@@ -718,6 +719,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();
@@ -3604,6 +3606,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() };
@@ -4995,6 +5007,31 @@ std::uint32_t opengl46_renderer::walk_instances(glm::mat4 const *Modelviews, std
return Upload_Instance_Modelviews(Modelviews, Count);
}
bool opengl46_renderer::walk_timbers_gpu() const
{
return true;
}
bool opengl46_renderer::walk_timbers_place(std::vector<TTrack *> const &Tracks, bool const Restart)
{
return m_timbers.place(Tracks, Restart);
}
std::uint32_t opengl46_renderer::walk_timbers_first(TTrack const &Track) const
{
return m_timbers.first(Track);
}
std::uint32_t opengl46_renderer::walk_instances_reserve(std::size_t const Count)
{
return Reserve_Instance_Modelviews(Count);
}
void opengl46_renderer::walk_timbers_expand(std::vector<glm::mat4> const &Origins, std::vector<gl::timber_range> const &Ranges)
{
m_timbers.expand(Origins, Ranges, m_instancestream->buffer());
}
void opengl46_renderer::walk_cull_face(bool const Enabled)
{
if (Enabled)

View File

@@ -47,6 +47,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46skyocclusion.h"
#include "rendering/opengl46/opengl46instancecull.h"
#include "rendering/opengl46/opengl46hiz.h"
#include "rendering/opengl46/opengl46timbers.h"
#include "rendering/opengl46/opengl46shadowfit.h"
#include "rendering/common/scenewalk.h"
#include "rendering/common/temporalaa.h"
@@ -217,6 +218,12 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
void walk_draw(gfx::geometry_handle const &Geometry) override;
void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) override;
std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) override;
// the instances of the sleepers and timbers of the tracks on the gpu (m_timbers)
bool walk_timbers_gpu() const override;
bool walk_timbers_place(std::vector<TTrack *> const &Tracks, bool const Restart) override;
std::uint32_t walk_timbers_first(TTrack const &Track) const override;
std::uint32_t walk_instances_reserve(std::size_t const Count) override;
void walk_timbers_expand(std::vector<glm::mat4> const &Origins, std::vector<gl::timber_range> const &Ranges) override;
void walk_cull_face(bool const Enabled) override;
void walk_glare(bool const Enabled) override;
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
@@ -348,6 +355,8 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
bool Render(world_environment *Environment);
// 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
@@ -600,6 +609,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
int m_shadowcascade { -1 }; // the region is drawn for this cascade of the sun shadows
// 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()

View 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();
}

View 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
};