From 6bcdb7dd0595be571cbfcf133533de8e947040b3 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Sat, 24 Oct 2026 01:49:02 +0200 Subject: [PATCH] perf(gl46): the geometry of the banks without user data in a few large buffers shared by them, so the multi draws take the chunks of many models at once (gfx.geometry.shared) --- rendering/geometrybank.cpp | 18 ++ rendering/geometrybank.h | 10 + rendering/opengl46/opengl46geometrybank.cpp | 277 ++++++++++++++++---- rendering/opengl46/opengl46geometrybank.h | 30 ++- rendering/opengl46/opengl46renderer.cpp | 17 +- rendering/opengl46/opengl46renderer.h | 1 + utilities/Globals.cpp | 6 + utilities/Globals.h | 3 + 8 files changed, 306 insertions(+), 56 deletions(-) diff --git a/rendering/geometrybank.cpp b/rendering/geometrybank.cpp index 2197047d..7075f64d 100644 --- a/rendering/geometrybank.cpp +++ b/rendering/geometrybank.cpp @@ -381,6 +381,13 @@ geometry_bank::draw_indirect_multi( gfx::geometry_handle const &Geometry, std::i return draw_indirect_multi_( Geometry, Offset, Count, Stride ); } +// the buffers the bank keeps its geometry in, when they're shared with other banks, 0 otherwise +std::uint32_t +geometry_bank::draw_group() const { + // template method implementation + return draw_group_(); +} + // 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() { @@ -588,6 +595,17 @@ geometrybank_manager::draw_indirect_multi( gfx::geometry_handle const &Geometry, return bankrecord.first->draw_indirect_multi( Geometry, Offset, Count, Stride ); } +// what the chunks of a multi draw have to share: the shared buffers of the bank of the chunk, or the bank itself. the groups of +// shared buffers have the highest bit set, apart from the numbers of the banks +std::uint32_t +geometrybank_manager::draw_group( gfx::geometry_handle const &Geometry ) const { + + if( Geometry == null_handle ) { return 0; } + + auto const group { bank( Geometry ).first->draw_group() }; + return ( group != 0 ? ( group | 0x80000000u ) : Geometry.bank ); +} + // 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 ae260798..c9f70e6a 100644 --- a/rendering/geometrybank.h +++ b/rendering/geometrybank.h @@ -145,6 +145,10 @@ public: // with a single glMultiDrawElementsIndirect (or glMultiDrawArraysIndirect). the chunks have to be of the type of the // specified one and indexed alike. returns: false if the bank can't, nothing is drawn then auto draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) -> bool; + // the buffers the bank keeps its geometry in, when they're shared with other banks: chunks of banks of the same group can go + // in one multi draw (draw_indirect_multi()). 0 when the bank has buffers of its own. valid once the chunks were made ready + // for drawing (indirect_source()) + auto draw_group() const -> std::uint32_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 { @@ -225,6 +229,8 @@ private: virtual auto indirect_source_( gfx::geometry_handle const & /*Geometry*/, gfx::indirect_source & /*Source*/ ) -> bool { return false; } virtual void draw_indirect_( gfx::geometry_handle const & /*Geometry*/, std::intptr_t const /*Offset*/, unsigned int const /*Streams*/ ) {} virtual auto draw_indirect_multi_( gfx::geometry_handle const & /*Geometry*/, std::intptr_t const /*Offset*/, std::uint32_t const /*Count*/, std::uint32_t const /*Stride*/ ) -> bool { return false; } + // draw_group() subclass details. by default the bank keeps its geometry to itself + virtual auto draw_group_() const -> std::uint32_t { return 0; } // resource release subclass details virtual void release_() = 0; }; @@ -267,6 +273,10 @@ public: // draws chunks of the bank of the specified one, of its type and indexed alike, with Count commands from Offset in the bound // draw indirect buffer, Stride bytes apart, in one call. returns: false if the bank can't, nothing is drawn then auto draw_indirect_multi( gfx::geometry_handle const &Geometry, std::intptr_t const Offset, std::uint32_t const Count, std::uint32_t const Stride ) -> bool; + // what the chunks of a multi draw (draw_indirect_multi()) have to share, besides their type and whether they're indexed: the + // buffers of the bank of the specified chunk. the same for chunks of banks which share their buffers, the bank otherwise. + // valid once the chunk was made ready for drawing (indirect_source()) + auto draw_group( gfx::geometry_handle const &Geometry ) const -> std::uint32_t; template void draw( Iterator_ First, Iterator_ Last, unsigned int const Streams = basic_streams ) { while( First != Last ) { diff --git a/rendering/opengl46/opengl46geometrybank.cpp b/rendering/opengl46/opengl46geometrybank.cpp index a709d829..0ad747b7 100644 --- a/rendering/opengl46/opengl46geometrybank.cpp +++ b/rendering/opengl46/opengl46geometrybank.cpp @@ -11,9 +11,84 @@ http://mozilla.org/MPL/2.0/. #include "rendering/opengl46/opengl46geometrybank.h" #include "utilities/Logs.h" +#include "utilities/Globals.h" + +#include namespace gfx { +// a vertex and an index buffer shared by banks, with the vao they're drawn with. the banks take parts of them, given back when the +// bank lets its buffers go (a chunk added or resized, the bank released); the free parts are kept by their place, joined with the +// neighbouring ones when given back +struct opengl46_geometry_page { + // the size of a page: 4M vertices (192 MiB) and 12M indices (48 MiB). a bank larger than that gets a page of its own size + static constexpr std::size_t VERTICES { 4u << 20 }; + static constexpr std::size_t INDICES { 12u << 20 }; + + gl::buffer vertices; + gl::buffer indices; + gl::vao vao; + std::size_t vertexcapacity { 0 }; + std::size_t indexcapacity { 0 }; + std::map freevertices; // place -> count + std::map freeindices; + std::size_t users { 0 }; // banks with a part of the page + std::uint32_t id { 0 }; +}; + +namespace { + +std::vector> &geometry_pages() { + static std::vector> pages; + return pages; +} + +// takes Count elements from the free parts, the first one large enough. returns: false if there's none +bool take_part( std::map &Free, std::size_t const Count, std::size_t &Offset ) { + + if( Count == 0 ) { + Offset = 0; + return true; + } + for( auto part { Free.begin() }; part != Free.end(); ++part ) { + if( part->second < Count ) { continue; } + Offset = part->first; + auto const rest { part->second - Count }; + Free.erase( part ); + if( rest > 0 ) { + Free.emplace( Offset + Count, rest ); + } + return true; + } + return false; +} + +// gives back Count elements from Offset, joined with the free parts next to them +void give_part( std::map &Free, std::size_t const Offset, std::size_t const Count ) { + + if( Count == 0 ) { return; } + auto part { Free.emplace( Offset, Count ).first }; + auto const next { std::next( part ) }; + if( ( next != Free.end() ) && ( part->first + part->second == next->first ) ) { + part->second += next->second; + Free.erase( next ); + } + if( part != Free.begin() ) { + auto const previous { std::prev( part ) }; + if( previous->first + previous->second == part->first ) { + previous->second += part->second; + Free.erase( part ); + } + } +} + +std::uint32_t next_page_id() { + static std::uint32_t id { 0 }; + return ++id; +} + +} // namespace + // opengl vao/vbo-based variant of the geometry bank // create() subclass details @@ -43,7 +118,7 @@ opengl46_vaogeometrybank::replace_( gfx::geometry_handle const &Geometry ) { void opengl46_vaogeometrybank::setup_buffer() { - if( m_vertexbuffer ) { return; } + if( has_buffer() ) { return; } // if there's no buffer, we'll have to make one // NOTE: this isn't exactly optimal in terms of ensuring the gfx card doesn't stall waiting for the data // may be better to initiate upload earlier (during update phase) and trust this effort won't go to waste @@ -70,6 +145,67 @@ void opengl46_vaogeometrybank::setup_buffer() // the odds for all created chunks to get replaced with empty ones are quite low, but the possibility does exist if( vertexcount == 0 ) { return; } + if( Global.gfx_geometry_shared && ( false == has_userdata ) ) { + // a part of the shared buffers: of a page with room for it, or of a new one + auto &pages { geometry_pages() }; + opengl46_geometry_page *page { nullptr }; + std::size_t vertexoffset { 0 }; + std::size_t indexoffset { 0 }; + for( auto &candidate : pages ) { + if( false == take_part( candidate->freevertices, vertexcount, vertexoffset ) ) { continue; } + if( false == take_part( candidate->freeindices, indexcount, indexoffset ) ) { + give_part( candidate->freevertices, vertexoffset, vertexcount ); + continue; + } + page = candidate.get(); + break; + } + if( page == nullptr ) { + auto newpage { std::make_unique() }; + newpage->id = next_page_id(); + newpage->vertexcapacity = std::max( opengl46_geometry_page::VERTICES, vertexcount ); + newpage->indexcapacity = std::max( opengl46_geometry_page::INDICES, indexcount ); + newpage->vao.bind(); + newpage->indices.allocate( gl::buffer::ELEMENT_ARRAY_BUFFER, static_cast( newpage->indexcapacity * sizeof( gfx::basic_index ) ), GL_STATIC_DRAW ); + if( ::glGetError() == GL_OUT_OF_MEMORY ) { + ErrorLog( "openGL error: out of memory; failed to create a shared geometry index buffer" ); + throw std::bad_alloc(); + } + newpage->vao.setup_ebo( newpage->indices ); + newpage->vertices.allocate( gl::buffer::ARRAY_BUFFER, static_cast( newpage->vertexcapacity * sizeof( gfx::basic_vertex ) ), GL_STATIC_DRAW ); + if( ::glGetError() == GL_OUT_OF_MEMORY ) { + ErrorLog( "openGL error: out of memory; failed to create a shared geometry buffer" ); + throw std::bad_alloc(); + } + auto &vao { newpage->vao }; + auto &buffer { newpage->vertices }; + vao.setup_attrib( buffer, 0, 3, GL_FLOAT, sizeof( basic_vertex ), offsetof( basic_vertex, position ) ); + // NOTE: normal and color streams share the data + vao.setup_attrib( buffer, 1, 3, GL_FLOAT, sizeof( basic_vertex ), offsetof( basic_vertex, normal ) ); + vao.setup_attrib( buffer, 2, 2, GL_FLOAT, sizeof( basic_vertex ), offsetof( basic_vertex, texture ) ); + vao.setup_attrib( buffer, 3, 4, GL_FLOAT, sizeof( basic_vertex ), offsetof( basic_vertex, tangent ) ); + newpage->freevertices.emplace( 0, newpage->vertexcapacity ); + newpage->freeindices.emplace( 0, newpage->indexcapacity ); + take_part( newpage->freevertices, vertexcount, vertexoffset ); + take_part( newpage->freeindices, indexcount, indexoffset ); + page = newpage.get(); + pages.emplace_back( std::move( newpage ) ); + } + ++page->users; + m_page = page; + m_pagevertexoffset = vertexoffset; + m_pagevertexcount = vertexcount; + m_pageindexoffset = indexoffset; + m_pageindexcount = indexcount; + // the chunks lie in the shared buffers after one another, from the place of the bank + for( auto &chunkrecord : m_chunkrecords ) { + chunkrecord.vertex_offset += vertexoffset; + chunkrecord.index_offset += indexoffset; + } + m_vertex_count = static_cast( vertexcount ); + return; + } + if( !m_vao ) { m_vao.emplace(); } @@ -115,6 +251,73 @@ opengl46_vaogeometrybank::setup_attrib(size_t offset) m_vao->setup_attrib( *m_vertexbuffer, 3, 4, GL_FLOAT, sizeof( basic_vertex ), offsetof(basic_vertex, tangent) + offset * sizeof( basic_vertex ) ); } +// a line for the debug panel: the pages of the shared buffers and how much of them the banks take +std::string +opengl46_vaogeometrybank::shared_info() { + + auto const &pages { geometry_pages() }; + if( pages.empty() ) { return {}; } + std::size_t users { 0 }, vertices { 0 }, vertexcapacity { 0 }, indices { 0 }, indexcapacity { 0 }, parts { 0 }; + for( auto const &page : pages ) { + users += page->users; + vertexcapacity += page->vertexcapacity; + indexcapacity += page->indexcapacity; + vertices += page->vertexcapacity; + indices += page->indexcapacity; + for( auto const &part : page->freevertices ) { vertices -= part.second; } + for( auto const &part : page->freeindices ) { indices -= part.second; } + parts += page->freevertices.size(); + } + auto const mib = []( std::size_t const Bytes ) { return std::to_string( ( Bytes + ( 1u << 19 ) ) >> 20 ) + " MiB"; }; + return + " shared geometry buffers: " + std::to_string( pages.size() ) + " pages, " + std::to_string( users ) + " banks, vertices " + + mib( vertices * sizeof( basic_vertex ) ) + " of " + mib( vertexcapacity * sizeof( basic_vertex ) ) + ", indices " + + mib( indices * sizeof( basic_index ) ) + " of " + mib( indexcapacity * sizeof( basic_index ) ) + ", " + std::to_string( parts ) + " free parts\n"; +} + +// binds the vao the bank is drawn with: the one of its page of the shared buffers, or its own +void +opengl46_vaogeometrybank::bind_vao() { + + if( m_page != nullptr ) { m_page->vao.bind(); } + else { m_vao->bind(); } +} + +// sends the data of a chunk to the buffers, unless they hold it already +void +opengl46_vaogeometrybank::upload_chunk( chunk_record &Record, geometry_chunk const &Chunk ) { + + if( Record.is_good ) { return; } + if( m_page != nullptr ) { + // straight to the shared buffers, without binding them: the element buffer is a part of whichever vao is bound + if( Record.index_count > 0 ) { + ::glNamedBufferSubData( m_page->indices, static_cast( Record.index_offset * sizeof( gfx::basic_index ) ), static_cast( Record.index_count * sizeof( gfx::basic_index ) ), Chunk.indices.data() ); + } + ::glNamedBufferSubData( m_page->vertices, static_cast( Record.vertex_offset * sizeof( gfx::basic_vertex ) ), static_cast( Record.vertex_count * sizeof( gfx::basic_vertex ) ), Chunk.vertices.data() ); + Record.is_good = true; + return; + } + m_vao->bind(); + // we may potentially need to upload new buffer data before we can draw it + if( Record.index_count > 0 ) { + m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, Chunk.indices.data(), Record.index_offset * sizeof( gfx::basic_index ), Record.index_count * sizeof( gfx::basic_index ) ); + } + m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, Chunk.vertices.data(), Record.vertex_offset * sizeof( gfx::basic_vertex ), Record.vertex_count * sizeof( gfx::basic_vertex ) ); + if( Record.has_userdata ) { + m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, Chunk.userdata.data(), + static_cast( m_vertex_count * sizeof( gfx::basic_vertex ) + Record.vertex_offset * sizeof( gfx::vertex_userdata ) ), + static_cast( Record.vertex_count * sizeof( gfx::vertex_userdata ) ) ); + } + Record.is_good = true; +} + +// draw_group() subclass details +std::uint32_t +opengl46_vaogeometrybank::draw_group_() const { + + return ( m_page != nullptr ? m_page->id : 0 ); +} + void opengl46_vaogeometrybank::setup_userdata(size_t offset) { const size_t offset_evaluated = m_vertex_count * sizeof(basic_vertex) + offset * sizeof( vertex_userdata ); m_vao->setup_attrib( *m_vertexbuffer, 4, 4, GL_FLOAT, sizeof( vertex_userdata ), offsetof(vertex_userdata, data) + offset_evaluated );} @@ -133,22 +336,10 @@ opengl46_vaogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stre return 0; auto const &chunk = gfx::geometry_bank::chunk( Geometry ); - if( !chunkrecord.is_good ) { - m_vao->bind(); - // we may potentially need to upload new buffer data before we can draw it - if( chunkrecord.index_count > 0 ) { - m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, chunk.indices.data(), chunkrecord.index_offset * sizeof( gfx::basic_index ), chunkrecord.index_count * sizeof( gfx::basic_index ) ); - } - m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), chunkrecord.vertex_offset * sizeof( gfx::basic_vertex ), chunkrecord.vertex_count * sizeof( gfx::basic_vertex ) ); - if(chunkrecord.has_userdata) - m_vertexbuffer->upload(gl::buffer::ARRAY_BUFFER, chunk.userdata.data(), - static_cast(m_vertex_count * sizeof(gfx::basic_vertex) + chunkrecord.vertex_offset * sizeof(gfx::vertex_userdata)), - static_cast(chunkrecord.vertex_count * sizeof(gfx::vertex_userdata))); - chunkrecord.is_good = true; - } + upload_chunk( chunkrecord, chunk ); // render if( chunkrecord.index_count > 0 ) { - m_vao->bind(); + bind_vao(); ::glDrawRangeElementsBaseVertex( chunk.type, 0, chunkrecord.vertex_count, @@ -156,7 +347,7 @@ opengl46_vaogeometrybank::draw_( gfx::geometry_handle const &Geometry, gfx::stre chunkrecord.vertex_offset ); } else { - m_vao->bind(); + bind_vao(); ::glDrawArrays( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count ); } /* @@ -181,24 +372,12 @@ opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry, if( chunkrecord.vertex_count == 0 ) { return 0; } auto const &chunk = gfx::geometry_bank::chunk( Geometry ); - if( !chunkrecord.is_good ) { - m_vao->bind(); - if( chunkrecord.index_count > 0 ) { - m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, chunk.indices.data(), chunkrecord.index_offset * sizeof( gfx::basic_index ), chunkrecord.index_count * sizeof( gfx::basic_index ) ); - } - m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), chunkrecord.vertex_offset * sizeof( gfx::basic_vertex ), chunkrecord.vertex_count * sizeof( gfx::basic_vertex ) ); - if( chunkrecord.has_userdata ) { - m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.userdata.data(), - static_cast(m_vertex_count * sizeof(gfx::basic_vertex) + chunkrecord.vertex_offset * sizeof(gfx::vertex_userdata)), - static_cast(chunkrecord.vertex_count * sizeof(gfx::vertex_userdata)) ); - } - chunkrecord.is_good = true; - } + upload_chunk( chunkrecord, chunk ); GLsizei const inst_count = static_cast(InstanceCount); if( chunkrecord.index_count > 0 ) { - m_vao->bind(); + bind_vao(); ::glDrawElementsInstancedBaseVertexBaseInstance( chunk.type, chunkrecord.index_count, GL_UNSIGNED_INT, @@ -208,7 +387,7 @@ opengl46_vaogeometrybank::draw_instanced_( gfx::geometry_handle const &Geometry, BaseInstance ); } else { - m_vao->bind(); + bind_vao(); ::glDrawArraysInstancedBaseInstance( chunk.type, chunkrecord.vertex_offset, chunkrecord.vertex_count, inst_count, BaseInstance ); } @@ -233,19 +412,7 @@ opengl46_vaogeometrybank::indirect_source_( gfx::geometry_handle const &Geometry if( chunkrecord.vertex_count == 0 ) { return false; } auto const &chunk = gfx::geometry_bank::chunk( Geometry ); - if( !chunkrecord.is_good ) { - m_vao->bind(); - if( chunkrecord.index_count > 0 ) { - m_indexbuffer->upload( gl::buffer::ELEMENT_ARRAY_BUFFER, chunk.indices.data(), chunkrecord.index_offset * sizeof( gfx::basic_index ), chunkrecord.index_count * sizeof( gfx::basic_index ) ); - } - m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.vertices.data(), chunkrecord.vertex_offset * sizeof( gfx::basic_vertex ), chunkrecord.vertex_count * sizeof( gfx::basic_vertex ) ); - if( chunkrecord.has_userdata ) { - m_vertexbuffer->upload( gl::buffer::ARRAY_BUFFER, chunk.userdata.data(), - static_cast(m_vertex_count * sizeof(gfx::basic_vertex) + chunkrecord.vertex_offset * sizeof(gfx::vertex_userdata)), - static_cast(chunkrecord.vertex_count * sizeof(gfx::vertex_userdata)) ); - } - chunkrecord.is_good = true; - } + upload_chunk( chunkrecord, chunk ); Source.indexed = ( chunkrecord.index_count > 0 ); Source.count = static_cast( Source.indexed ? chunkrecord.index_count : chunkrecord.vertex_count ); @@ -265,7 +432,7 @@ opengl46_vaogeometrybank::draw_indirect_( gfx::geometry_handle const &Geometry, if( chunkrecord.vertex_count == 0 ) { return; } auto const &chunk = gfx::geometry_bank::chunk( Geometry ); - m_vao->bind(); + bind_vao(); if( chunkrecord.index_count > 0 ) { ::glDrawElementsIndirect( chunk.type, GL_UNSIGNED_INT, reinterpret_cast( Offset ) ); } @@ -282,11 +449,11 @@ opengl46_vaogeometrybank::draw_indirect_multi_( gfx::geometry_handle const &Geom setup_buffer(); // the counts of the chunks are in the commands, which may be for chunks other than this one - if( ( false == m_vao.has_value() ) || ( Count == 0 ) ) { return true; } + if( ( false == has_buffer() ) || ( Count == 0 ) ) { return true; } auto const &chunkrecord = m_chunkrecords.at(Geometry.chunk - 1); auto const &chunk = gfx::geometry_bank::chunk( Geometry ); - m_vao->bind(); + bind_vao(); if( chunkrecord.index_count > 0 ) { ::glMultiDrawElementsIndirect( chunk.type, GL_UNSIGNED_INT, reinterpret_cast( Offset ), static_cast( Count ), static_cast( Stride ) ); } @@ -312,7 +479,7 @@ opengl46_vaogeometrybank::draw_batch_( gfx::geometry_handle const *First, gfx::g auto const flush = [ & ]() { if( pending == 0 ) { return; } - m_vao->bind(); + bind_vao(); if( pending == 1 ) { ::glDrawArrays( pendingtype, offsets[ 0 ], sizes[ 0 ] ); } else { ::glMultiDrawArrays( pendingtype, offsets.data(), sizes.data(), pending ); } pending = 0; @@ -360,6 +527,20 @@ opengl46_vaogeometrybank::release_() { void opengl46_vaogeometrybank::delete_buffer() { + if( m_page != nullptr ) { + // the part of the shared buffers goes back; a page nobody has a part of goes, as the buffers of a bank did + give_part( m_page->freevertices, m_pagevertexoffset, m_pagevertexcount ); + give_part( m_page->freeindices, m_pageindexoffset, m_pageindexcount ); + if( --m_page->users == 0 ) { + auto &pages { geometry_pages() }; + auto *const emptied { m_page }; + pages.erase( + std::remove_if( std::begin( pages ), std::end( pages ), [ emptied ]( std::unique_ptr const &Page ) { return Page.get() == emptied; } ), + std::end( pages ) ); + } + m_page = nullptr; + m_pagevertexoffset = m_pagevertexcount = m_pageindexoffset = m_pageindexcount = 0; + } m_vao.reset(); m_vertexbuffer.reset(); m_indexbuffer.reset(); diff --git a/rendering/opengl46/opengl46geometrybank.h b/rendering/opengl46/opengl46geometrybank.h index 32f78f82..fa49ad9e 100644 --- a/rendering/opengl46/opengl46geometrybank.h +++ b/rendering/opengl46/opengl46geometrybank.h @@ -15,7 +15,12 @@ http://mozilla.org/MPL/2.0/. namespace gfx { -// opengl vao/vbo-based variant of the geometry bank +struct opengl46_geometry_page; + +// opengl vao/vbo-based variant of the geometry bank. +// a bank without user data keeps its geometry in a part of a large vertex and index buffer shared with other banks, with one +// vao for all of them (opengl46_geometry_page), so the chunks of many banks can be drawn in one multi draw; the offsets of its +// chunks are places in the shared buffers. a bank with user data (an extra vertex stream) has buffers and a vao of its own class opengl46_vaogeometrybank : public geometry_bank { @@ -29,6 +34,10 @@ public: static void reset() {;} + // a line for the debug panel: the pages of the shared buffers and how much of them the banks take + static + std::string + shared_info(); private: // types: @@ -71,8 +80,21 @@ private: // release() subclass details void release_() override; + // draw_group() subclass details: the shared buffers, if the bank keeps its geometry in them + auto + draw_group_() const -> std::uint32_t override; void setup_buffer(); + // the buffers of the bank were set up + bool + has_buffer() const { + return ( ( m_page != nullptr ) || m_vertexbuffer.has_value() ); } + // binds the vao the bank is drawn with + void + bind_vao(); + // sends the data of a chunk to the buffers, unless they hold it already + void + upload_chunk( chunk_record &Record, geometry_chunk const &Chunk ); void setup_attrib(size_t offset = 0); void @@ -86,6 +108,12 @@ private: std::optional m_vao; chunkrecord_sequence m_chunkrecords; // helper data for all stored geometry chunks, in matching order int m_vertex_count; + // the shared buffers holding the geometry of the bank, and its part of them: places of the first vertex and index, counts + opengl46_geometry_page *m_page { nullptr }; + std::size_t m_pagevertexoffset { 0 }; + std::size_t m_pagevertexcount { 0 }; + std::size_t m_pageindexoffset { 0 }; + std::size_t m_pageindexcount { 0 }; }; } // namespace gfx diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index f5eab3b6..732d3537 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -1280,6 +1280,7 @@ void opengl46_renderer::Update_Memory_Info() accounted += geometry.vertices.bytes + geometry.indices.bytes + geometry.userdata.bytes; text += "geometry banks: " + std::to_string( geometry.banks ) + ", " + counted( geometry.vertices, "chunks" ) + ": vertices " + mb( geometry.vertices.bytes ) + ", indices " + mb( geometry.indices.bytes ) + ", user data " + mb( geometry.userdata.bytes ) + "\n"; + text += gfx::opengl46_vaogeometrybank::shared_info(); // models: the geometry their submodels keep of their own auto const models { TModelsManager::memory() }; accounted += models.submodels.bytes; @@ -4186,7 +4187,7 @@ void opengl46_renderer::draw(const gfx::geometry_handle &handle) gfx::indirect_source source; if( m_geometry.indirect_source( handle, source ) ) { m_indirectcommands.push_back( { source.count, 0, source.first, static_cast( source.basevertex ), 0, m_indirectentry, ( source.indexed ? 1u : 0u ), 0 } ); - m_indirectdraws.push_back( { model_ubs, m_indirectmaterial, m_indirectsubmodel, handle, source.type, m_blendingenabled } ); + m_indirectdraws.push_back( { model_ubs, m_indirectmaterial, m_indirectsubmodel, handle, source.type, m_blendingenabled, m_geometry.draw_group( handle ) } ); } return; } @@ -5259,6 +5260,7 @@ void opengl46_renderer::Replay_Walk( walk_band const &Walk, bool const Shadows ) m_indirectcommands.push_back( { source.count, 0, source.first, static_cast( source.basevertex ), 0, m_indirectentry, ( source.indexed ? 1u : 0u ), 0 } ); auto &draw { m_indirectdraws.emplace_back( walkdraw ) }; draw.type = source.type; + draw.group = m_geometry.draw_group( walkdraw.geometry ); // the walk sets the placement of the submodel, and before each draw of the colour passes the parameters of its material // (Bind_Material()); the rest of the block is what the pass has at the moment, as the walk would leave it auto const &walked { walkdraw.ubs }; @@ -5344,7 +5346,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ auto last { index + 1 }; texture_handle othertexture; while( ( last < count ) - && ( m_indirectdraws[ last ].geometry.bank == draw.geometry.bank ) + && ( m_indirectdraws[ last ].group == draw.group ) && ( m_indirectdraws[ last ].type == draw.type ) && ( m_indirectcommands[ last ].indexed == m_indirectcommands[ index ].indexed ) && ( prepass_texture( last, prepassclass, othertexture ) ) @@ -5446,7 +5448,7 @@ opengl46_renderer::indirect_counts opengl46_renderer::Render_Instances_Indirect_ // the draws which follow in the same bank and of the same primitive, with the same texture for the alpha test auto last { index + 1 }; while( ( last < count ) - && ( m_indirectdraws[ last ].geometry.bank == draw.geometry.bank ) + && ( m_indirectdraws[ last ].group == draw.group ) && ( m_indirectdraws[ last ].type == draw.type ) && ( m_indirectcommands[ last ].indexed == m_indirectcommands[ index ].indexed ) && ( Indirect_Shadow_Texture( m_indirectdraws[ last ].material ) == texture ) ) { @@ -5537,7 +5539,7 @@ void opengl46_renderer::Order_Indirect_Draws() for( std::size_t index = 0; index < count; ++index ) { auto const &draw { m_indirectdraws[ index ] }; if( shadows ) { - m_indirectstatekeys.push_back( { Indirect_Shadow_Texture( draw.material ), 0u, draw.geometry.bank, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast( index ) } ); + m_indirectstatekeys.push_back( { Indirect_Shadow_Texture( draw.material ), 0u, draw.group, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast( index ) } ); continue; } auto variant { 0u }; @@ -5547,7 +5549,7 @@ void opengl46_renderer::Order_Indirect_Draws() variant = TSubModel::VariantId( draw.submodel ) % static_cast( material.texture_variants.size() ); } } - m_indirectstatekeys.push_back( { draw.material, variant, draw.geometry.bank, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast( index ) } ); + m_indirectstatekeys.push_back( { draw.material, variant, draw.group, draw.type, m_indirectcommands[ index ].indexed, draw.blending, static_cast( index ) } ); } std::sort( std::begin( m_indirectstatekeys ), std::end( m_indirectstatekeys ), @@ -5654,7 +5656,7 @@ bool opengl46_renderer::Indirect_Mergeable( std::size_t const First, std::size_t if( ( first.material != other.material ) || ( first.blending != other.blending ) || ( first.type != other.type ) - || ( first.geometry.bank != other.geometry.bank ) + || ( first.group != other.group ) || ( m_indirectcommands[ First ].indexed != m_indirectcommands[ Other ].indexed ) ) { return false; } @@ -5873,7 +5875,8 @@ void opengl46_renderer::Render_Scene_Prepass() auto &item { m_sceneprepassitems.emplace_back() }; item.prepassclass = draw.prepassclass; item.texture = ( draw.prepassclass == 2 ? draw.prepasstexture : null_handle ); - item.bank = chunk.geometry.bank; + // the buffers the geometry lies in, shared by many banks + item.bank = m_geometry.draw_group( chunk.geometry ); item.type = source.type; item.indexed = ( source.indexed ? 1u : 0u ); item.instancebuffer = draw.instancebuffer; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index c0254107..01bb1321 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -748,6 +748,7 @@ class opengl46_renderer : public gfx_renderer { gfx::geometry_handle geometry; unsigned int type; // primitive bool blending; // drawn with the transparency hack of submodels faded out + std::uint32_t group { 0 }; // the buffers of the geometry, which the draws of a multi draw share (geometrybank_manager::draw_group()) }; std::vector m_indirectdraws; std::vector m_indirectcommands; // in the order of m_indirectdraws diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 6866c235..db09f80a 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1552,6 +1552,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_gpucull_walkcache; } + else if (Token == "gfx.geometry.shared") + { + Parser.getTokens(1); + Parser >> gfx_geometry_shared; + } else if (Token == "gfx.depthprepass.scene") { Parser.getTokens(1); @@ -2130,6 +2135,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.gpucull.models", gfx_gpucull_models ); export_as_text( Output, "gfx.gpucull.bucketrange", gfx_gpucull_bucketrange ); export_as_text( Output, "gfx.gpucull.walkcache", gfx_gpucull_walkcache ); + export_as_text( Output, "gfx.geometry.shared", gfx_geometry_shared ); export_as_text( Output, "gfx.depthprepass.scene", gfx_depthprepass_scene ); export_as_text( Output, "gfx.depthprepass.replay", gfx_depthprepass_replay ); export_as_text( Output, "gfx.grass", gfx_grass ); diff --git a/utilities/Globals.h b/utilities/Globals.h index c12bee16..54bfb8d3 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -327,6 +327,9 @@ struct global_settings { // made again only when the model changes: the trees of animated models, ones lit by the luminance and the models drawn on their // own are walked every time bool gfx_gpucull_walkcache = true; + // (opengl 4.6) the geometry of the banks without user data (models, scenery) is kept in a few large buffers shared by them, so + // the multi draws can take the chunks of many models at once. read when a bank sets its buffers up (load time) + bool gfx_geometry_shared = true; // (opengl 4.6) the depth of the whole opaque scene of the main view is drawn before its colour, the instances included bool gfx_depthprepass_scene = false; // (opengl 4.6) the depth pass of the scene (gfx_depthprepass_scene, or the one of the contact shadows) walks the scene for