17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

perf(tracks): geometry of neighbouring tracks of a cell which share the material drawn together, with one material bind and one upload of model data; chunks of the same geometry bank sent to the gpu in a single multi draw call

This commit is contained in:
2026-10-07 17:13:32 +02:00
parent 398fdac6a1
commit e335b89fcb
6 changed files with 165 additions and 16 deletions

View File

@@ -332,6 +332,13 @@ geometry_bank::draw_instanced( gfx::geometry_handle const &Geometry, gfx::stream
return draw_instanced_( Geometry, Units, InstanceCount, Streams );
}
// draws geometry stored in supplied sequence of chunks of the bank
std::size_t
geometry_bank::draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams ) {
// template method implementation
return draw_batch_( First, Last, Units, Streams );
}
// frees subclass-specific resources associated with the bank, typically called when the bank wasn't in use for a period of time
void
geometry_bank::release() {
@@ -436,6 +443,32 @@ geometrybank_manager::draw_instanced( gfx::geometry_handle const &Geometry, std:
m_primitivecount += bankrecord.first->draw_instanced( Geometry, m_units, InstanceCount, Streams );
}
// draws geometry stored in supplied sequence of chunks
void
geometrybank_manager::draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, unsigned int const Streams ) {
while( First != Last ) {
if( *First == null_handle ) {
++First;
continue;
}
// neighbouring chunks stored in the same bank go to the bank together
auto const *last { First + 1 };
while( ( last != Last )
&& ( last->bank == First->bank ) ) {
++last;
}
auto &bankrecord = bank( *First );
bankrecord.second = m_garbagecollector.timestamp();
m_primitivecount += bankrecord.first->draw_batch( First, last, m_units, Streams );
First = last;
}
}
// provides direct access to index data of specfied chunk
gfx::index_array const &
geometrybank_manager::indices( gfx::geometry_handle const &Geometry ) const {

View File

@@ -121,6 +121,8 @@ public:
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*
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;
// 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;
// draws geometry stored in supplied list of chunks
template <typename Iterator_>
auto draw( Iterator_ First, Iterator_ Last, gfx::stream_units const &Units, unsigned int const Streams = basic_streams ) ->std::size_t {
@@ -187,6 +189,11 @@ private:
std::size_t count { 0 };
for( std::size_t i = 0; i < InstanceCount; ++i ) { count += draw_( Geometry, Units, Streams ); }
return count; }
// draw_batch() subclass details. Default implementation falls back to regular draw of each chunk.
virtual 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 {
std::size_t count { 0 };
for( ; First != Last; ++First ) { count += draw_( *First, Units, Streams ); }
return count; }
// resource release subclass details
virtual void release_() = 0;
};
@@ -218,6 +225,8 @@ public:
// draws geometry stored in specified chunk InstanceCount times via GPU instancing.
// The shader reads per-instance modelview matrices from instance_ubo[gl_InstanceID].
void draw_instanced( gfx::geometry_handle const &Geometry, std::size_t const InstanceCount, 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
void draw_batch( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, unsigned int const Streams = basic_streams );
template <typename Iterator_>
void draw( Iterator_ First, Iterator_ Last, unsigned int const Streams = basic_streams ) {
while( First != Last ) {

View File

@@ -259,6 +259,66 @@ opengl33_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry,
return prims * InstanceCount;
}
// draw_batch() subclass details — neighbouring non-indexed chunks of the same type are sent with a single GL call.
// The chunks are drawn in the supplied order, same as with a draw_() call for each of them.
std::size_t
opengl33_vaogeometrybank::draw_batch_( gfx::geometry_handle const *First, gfx::geometry_handle const *Last, gfx::stream_units const &Units, unsigned int const Streams )
{
std::size_t count { 0 };
if( !glMultiDrawArrays ) {
// GLES context doesn't provide the call
for( ; First != Last; ++First ) { count += draw_( *First, Units, Streams ); }
return count;
}
setup_buffer();
std::array<GLint, 64> offsets;
std::array<GLsizei, 64> sizes;
GLsizei pending { 0 };
unsigned int pendingtype { 0 };
auto const flush = [ & ]() {
if( pending == 0 ) { return; }
m_vao->bind();
if( pending == 1 ) { ::glDrawArrays( pendingtype, offsets[ 0 ], sizes[ 0 ] ); }
else { ::glMultiDrawArrays( pendingtype, offsets.data(), sizes.data(), pending ); }
pending = 0;
};
for( ; First != Last; ++First ) {
auto const &chunkrecord = m_chunkrecords.at( First->chunk - 1 );
if( ( false == chunkrecord.is_good )
|| ( chunkrecord.index_count > 0 ) ) {
// chunks which wait for upload of their data and indexed chunks take the regular route
flush();
count += draw_( *First, Units, Streams );
continue;
}
auto const type { gfx::geometry_bank::chunk( *First ).type };
if( ( pending == static_cast<GLsizei>( offsets.size() ) )
|| ( ( pending > 0 ) && ( type != pendingtype ) ) ) {
flush();
}
pendingtype = type;
offsets[ pending ] = static_cast<GLint>( chunkrecord.vertex_offset );
sizes[ pending ] = static_cast<GLsizei>( chunkrecord.vertex_count );
++pending;
switch( type ) {
case GL_TRIANGLES: { count += chunkrecord.vertex_count / 3; break; }
case GL_TRIANGLE_STRIP: { count += ( chunkrecord.vertex_count > 2 ? chunkrecord.vertex_count - 2 : 0 ); break; }
default: { break; }
}
}
flush();
return count;
}
// release () subclass details
void
opengl33_vaogeometrybank::release_() {

View File

@@ -56,6 +56,9 @@ private:
// draw_instanced() subclass details — issues glDrawElementsInstancedBaseVertex
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_batch() subclass details — issues glMultiDrawArrays for runs of non-indexed chunks of the same type
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;
// release() subclass details
void
release_() override;

View File

@@ -2854,7 +2854,13 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
void opengl33_renderer::Draw_Geometry(std::vector<gfx::geometrybank_handle>::iterator begin, std::vector<gfx::geometrybank_handle>::iterator end)
{
m_geometry.draw(begin, end);
if (begin == end)
{
return;
}
// the handles are handed over as a whole, so chunks kept in the same bank can be drawn with fewer calls
auto const *first{&(*begin)};
m_geometry.draw_batch(first, first + std::distance(begin, end));
}
void opengl33_renderer::Draw_Geometry(const gfx::geometrybank_handle &handle)
@@ -4066,6 +4072,31 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
}
}
// geometry of neighbouring tracks which share the material is gathered and drawn together.
// only neighbours are combined, the tracks are still drawn in their order in the cell
material_handle batchmaterial{null_handle};
auto const flush_batch = [&]()
{
if (false == m_pathbatch.empty())
{
draw(std::begin(m_pathbatch), std::end(m_pathbatch));
m_pathbatch.clear();
}
batchmaterial = null_handle;
};
// returns: true if the material starts a new batch, false if it continues the current one
auto const begin_batch = [&](material_handle const Material)
{
if (Material == batchmaterial)
{
return false;
}
flush_batch();
Bind_Material(Material);
batchmaterial = Material;
return true;
};
// TODO: render auto generated trackbeds together with regular trackbeds in pass 1, and all rails in pass 2
// first pass, material 1
for (auto first{First}; first != Last; ++first)
@@ -4092,15 +4123,21 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
{
if (track->eEnvironment != e_flat)
{
// track with light level of its own is drawn on its own
flush_batch();
setup_environment_light(track->eEnvironment);
}
Bind_Material(track->m_material1);
draw(std::begin(track->Geometry1), std::end(track->Geometry1));
if (track->eEnvironment != e_flat)
{
Bind_Material(track->m_material1);
draw(std::begin(track->Geometry1), std::end(track->Geometry1));
// restore default lighting
setup_environment_light();
break;
}
if (false == begin_batch(track->m_material1))
{
// the track is drawn with the call of its predecessor
--m_renderpass.draw_stats.drawcalls;
}
m_pathbatch.insert(std::end(m_pathbatch), std::begin(track->Geometry1), std::end(track->Geometry1));
break;
}
case rendermode::shadows:
@@ -4130,6 +4167,7 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
}
}
}
flush_batch();
// second pass, material 2
for (auto first{First}; first != Last; ++first)
{
@@ -4151,15 +4189,16 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
{
if (track->eEnvironment != e_flat)
{
flush_batch();
setup_environment_light(track->eEnvironment);
}
Bind_Material(track->m_material2);
draw(std::begin(track->Geometry2), std::end(track->Geometry2));
if (track->eEnvironment != e_flat)
{
Bind_Material(track->m_material2);
draw(std::begin(track->Geometry2), std::end(track->Geometry2));
// restore default lighting
setup_environment_light();
break;
}
begin_batch(track->m_material2);
m_pathbatch.insert(std::end(m_pathbatch), std::begin(track->Geometry2), std::end(track->Geometry2));
break;
}
case rendermode::shadows:
@@ -4185,6 +4224,7 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
}
}
}
flush_batch();
// third pass, material 3
for (auto first{First}; first != Last; ++first)
@@ -4212,15 +4252,16 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
{
if (track->eEnvironment != e_flat)
{
flush_batch();
setup_environment_light(track->eEnvironment);
}
Bind_Material(track->SwitchExtension->m_material3);
draw(track->SwitchExtension->Geometry3);
if (track->eEnvironment != e_flat)
{
Bind_Material(track->SwitchExtension->m_material3);
draw(track->SwitchExtension->Geometry3);
// restore default lighting
setup_environment_light();
break;
}
begin_batch(track->SwitchExtension->m_material3);
m_pathbatch.emplace_back(track->SwitchExtension->Geometry3);
break;
}
case rendermode::shadows:
@@ -4246,6 +4287,7 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator
}
}
}
flush_batch();
// fourth pass: per-track sleeper models (sleepermodel optional directive).
// drawn after rails/trackbeds so depth pre-pass culling is favourable, and only in passes

View File

@@ -388,6 +388,8 @@ class opengl33_renderer : public gfx_renderer {
// 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*).
scene::basic_cell::instance_bucket_map m_frame_instance_buckets;
// 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;
// copies of instance bounds 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
unsigned int m_instanceboundsversion { 1 };