diff --git a/rendering/geometrybank.cpp b/rendering/geometrybank.cpp index e853f4dc..f2db8b8b 100644 --- a/rendering/geometrybank.cpp +++ b/rendering/geometrybank.cpp @@ -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 { diff --git a/rendering/geometrybank.h b/rendering/geometrybank.h index 39735e00..20baf4ed 100644 --- a/rendering/geometrybank.h +++ b/rendering/geometrybank.h @@ -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 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 void draw( Iterator_ First, Iterator_ Last, unsigned int const Streams = basic_streams ) { while( First != Last ) { diff --git a/rendering/opengl33geometrybank.cpp b/rendering/opengl33geometrybank.cpp index 506d22f1..4713ec69 100644 --- a/rendering/opengl33geometrybank.cpp +++ b/rendering/opengl33geometrybank.cpp @@ -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 offsets; + std::array 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( offsets.size() ) ) + || ( ( pending > 0 ) && ( type != pendingtype ) ) ) { + flush(); + } + pendingtype = type; + offsets[ pending ] = static_cast( chunkrecord.vertex_offset ); + sizes[ pending ] = static_cast( 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_() { diff --git a/rendering/opengl33geometrybank.h b/rendering/opengl33geometrybank.h index f5cd7e37..79526f10 100644 --- a/rendering/opengl33geometrybank.h +++ b/rendering/opengl33geometrybank.h @@ -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; diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index d207b204..752ba842 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -2854,7 +2854,13 @@ void opengl33_renderer::Render(cell_sequence::iterator First, cell_sequence::ite void opengl33_renderer::Draw_Geometry(std::vector::iterator begin, std::vector::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 diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index d3b177f5..b028f929 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -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 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 };