LCOV - code coverage report
Current view: top level - source/renderer - VertexBuffer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 131 0.0 %
Date: 2021-09-24 14:46:47 Functions: 0 15 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "VertexBuffer.h"
      21             : 
      22             : #include "lib/ogl.h"
      23             : #include "lib/sysdep/cpu.h"
      24             : #include "Renderer.h"
      25             : #include "ps/CLogger.h"
      26             : #include "ps/Errors.h"
      27             : 
      28             : #include <algorithm>
      29             : #include <iterator>
      30             : 
      31             : // Absolute maximum (bytewise) size of each GL vertex buffer object.
      32             : // Make it large enough for the maximum feasible mesh size (64K vertexes,
      33             : // 64 bytes per vertex in InstancingModelRenderer).
      34             : // TODO: measure what influence this has on performance
      35             : constexpr std::size_t MAX_VB_SIZE_BYTES = 4 * 1024 * 1024;
      36             : 
      37           0 : CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target)
      38           0 :     : CVertexBuffer(vertexSize, usage, target, MAX_VB_SIZE_BYTES)
      39             : {
      40           0 : }
      41             : 
      42           0 : CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target, size_t maximumBufferSize)
      43           0 :     : m_VertexSize(vertexSize), m_Handle(0), m_Usage(usage), m_Target(target), m_HasNeededChunks(false)
      44             : {
      45           0 :     size_t size = maximumBufferSize;
      46             : 
      47           0 :     if (target == GL_ARRAY_BUFFER) // vertex data buffer
      48             :     {
      49             :         // We want to store 16-bit indices to any vertex in a buffer, so the
      50             :         // buffer must never be bigger than vertexSize*64K bytes since we can
      51             :         // address at most 64K of them with 16-bit indices
      52           0 :         size = std::min(size, vertexSize*65536);
      53             :     }
      54             : 
      55             :     // store max/free vertex counts
      56           0 :     m_MaxVertices = m_FreeVertices = size / vertexSize;
      57             : 
      58             :     // allocate raw buffer
      59           0 :     pglGenBuffersARB(1, &m_Handle);
      60           0 :     pglBindBufferARB(m_Target, m_Handle);
      61           0 :     pglBufferDataARB(m_Target, m_MaxVertices * m_VertexSize, 0, m_Usage);
      62           0 :     pglBindBufferARB(m_Target, 0);
      63             : 
      64             :     // create sole free chunk
      65           0 :     VBChunk* chunk = new VBChunk;
      66           0 :     chunk->m_Owner = this;
      67           0 :     chunk->m_Count = m_FreeVertices;
      68           0 :     chunk->m_Index = 0;
      69           0 :     m_FreeList.emplace_back(chunk);
      70           0 : }
      71             : 
      72           0 : CVertexBuffer::~CVertexBuffer()
      73             : {
      74             :     // Must have released all chunks before destroying the buffer
      75           0 :     ENSURE(m_AllocList.empty());
      76             : 
      77           0 :     if (m_Handle)
      78           0 :         pglDeleteBuffersARB(1, &m_Handle);
      79             : 
      80           0 :     for (VBChunk* const& chunk : m_FreeList)
      81           0 :         delete chunk;
      82           0 : }
      83             : 
      84             : 
      85           0 : bool CVertexBuffer::CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target) const
      86             : {
      87           0 :     return usage == m_Usage && target == m_Target && vertexSize == m_VertexSize;
      88             : }
      89             : 
      90             : ///////////////////////////////////////////////////////////////////////////////
      91             : // Allocate: try to allocate a buffer of given number of vertices (each of
      92             : // given size), with the given type, and using the given texture - return null
      93             : // if no free chunks available
      94           0 : CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target, void* backingStore)
      95             : {
      96             :     // check this is the right kind of buffer
      97           0 :     if (!CompatibleVertexType(vertexSize, usage, target))
      98             :         return nullptr;
      99             : 
     100           0 :     if (UseStreaming(usage))
     101           0 :         ENSURE(backingStore != nullptr);
     102             : 
     103             :     // quick check there's enough vertices spare to allocate
     104           0 :     if (numVertices > m_FreeVertices)
     105             :         return nullptr;
     106             : 
     107             :     // trawl free list looking for first free chunk with enough space
     108           0 :     std::vector<VBChunk*>::iterator best_iter = m_FreeList.end();
     109           0 :     for (std::vector<VBChunk*>::iterator iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter)
     110             :     {
     111           0 :         if (numVertices == (*iter)->m_Count)
     112             :         {
     113             :             best_iter = iter;
     114             :             break;
     115             :         }
     116           0 :         else if (numVertices < (*iter)->m_Count && (best_iter == m_FreeList.end() || (*best_iter)->m_Count < (*iter)->m_Count))
     117             :             best_iter = iter;
     118             :     }
     119             : 
     120             :     // We could not find a large enough chunk.
     121           0 :     if (best_iter == m_FreeList.end())
     122             :         return nullptr;
     123             : 
     124           0 :     VBChunk* chunk = *best_iter;
     125           0 :     m_FreeList.erase(best_iter);
     126           0 :     m_FreeVertices -= chunk->m_Count;
     127             : 
     128           0 :     chunk->m_BackingStore = backingStore;
     129           0 :     chunk->m_Dirty = false;
     130           0 :     chunk->m_Needed = false;
     131             : 
     132             :     // split chunk into two; - allocate a new chunk using all unused vertices in the
     133             :     // found chunk, and add it to the free list
     134           0 :     if (chunk->m_Count > numVertices)
     135             :     {
     136           0 :         VBChunk* newchunk = new VBChunk;
     137           0 :         newchunk->m_Owner = this;
     138           0 :         newchunk->m_Count = chunk->m_Count - numVertices;
     139           0 :         newchunk->m_Index = chunk->m_Index + numVertices;
     140           0 :         m_FreeList.emplace_back(newchunk);
     141           0 :         m_FreeVertices += newchunk->m_Count;
     142             : 
     143             :         // resize given chunk
     144           0 :         chunk->m_Count = numVertices;
     145             :     }
     146             : 
     147             :     // return found chunk
     148           0 :     m_AllocList.push_back(chunk);
     149           0 :     return chunk;
     150             : }
     151             : 
     152             : ///////////////////////////////////////////////////////////////////////////////
     153             : // Release: return given chunk to this buffer
     154           0 : void CVertexBuffer::Release(VBChunk* chunk)
     155             : {
     156             :     // Update total free count before potentially modifying this chunk's count
     157           0 :     m_FreeVertices += chunk->m_Count;
     158             : 
     159           0 :     m_AllocList.erase(std::find(m_AllocList.begin(), m_AllocList.end(), chunk));
     160             : 
     161             :     // Sorting O(nlogn) shouldn't be too far from O(n) by performance, because
     162             :     // the container is partly sorted already.
     163           0 :     std::sort(
     164             :         m_FreeList.begin(), m_FreeList.end(),
     165           0 :         [](const VBChunk* chunk1, const VBChunk* chunk2) -> bool
     166             :         {
     167           0 :             return chunk1->m_Index < chunk2->m_Index;
     168             :         });
     169             : 
     170             :     // Coalesce with any free-list items that are adjacent to this chunk;
     171             :     // merge the found chunk with the new one, and remove the old one
     172             :     // from the list.
     173           0 :     for (std::vector<VBChunk*>::iterator iter = m_FreeList.begin(); iter != m_FreeList.end();)
     174             :     {
     175           0 :         if ((*iter)->m_Index == chunk->m_Index + chunk->m_Count
     176           0 :          || (*iter)->m_Index + (*iter)->m_Count == chunk->m_Index)
     177             :         {
     178           0 :             chunk->m_Index = std::min(chunk->m_Index, (*iter)->m_Index);
     179           0 :             chunk->m_Count += (*iter)->m_Count;
     180           0 :             delete *iter;
     181           0 :             iter = m_FreeList.erase(iter);
     182           0 :             if (!m_FreeList.empty() && iter != m_FreeList.begin())
     183           0 :                 iter = std::prev(iter);
     184             :         }
     185             :         else
     186             :         {
     187           0 :             ++iter;
     188             :         }
     189             :     }
     190             : 
     191           0 :     m_FreeList.emplace_back(chunk);
     192           0 : }
     193             : 
     194             : ///////////////////////////////////////////////////////////////////////////////
     195             : // UpdateChunkVertices: update vertex data for given chunk
     196           0 : void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data)
     197             : {
     198           0 :     ENSURE(m_Handle);
     199           0 :     if (UseStreaming(m_Usage))
     200             :     {
     201             :         // The VBO is now out of sync with the backing store
     202           0 :         chunk->m_Dirty = true;
     203             : 
     204             :         // Sanity check: Make sure the caller hasn't tried to reallocate
     205             :         // their backing store
     206           0 :         ENSURE(data == chunk->m_BackingStore);
     207             :     }
     208             :     else
     209             :     {
     210           0 :         pglBindBufferARB(m_Target, m_Handle);
     211           0 :         pglBufferSubDataARB(m_Target, chunk->m_Index * m_VertexSize, chunk->m_Count * m_VertexSize, data);
     212           0 :         pglBindBufferARB(m_Target, 0);
     213             :     }
     214           0 : }
     215             : 
     216             : ///////////////////////////////////////////////////////////////////////////////
     217             : // Bind: bind to this buffer; return pointer to address required as parameter
     218             : // to glVertexPointer ( + etc) calls
     219           0 : u8* CVertexBuffer::Bind()
     220             : {
     221           0 :     pglBindBufferARB(m_Target, m_Handle);
     222             : 
     223           0 :     if (UseStreaming(m_Usage))
     224             :     {
     225           0 :         if (!m_HasNeededChunks)
     226             :             return nullptr;
     227             : 
     228             :         // If any chunks are out of sync with the current VBO, and are
     229             :         // needed for rendering this frame, we'll need to re-upload the VBO
     230           0 :         bool needUpload = false;
     231           0 :         for (VBChunk* const& chunk : m_AllocList)
     232             :         {
     233           0 :             if (chunk->m_Dirty && chunk->m_Needed)
     234             :             {
     235             :                 needUpload = true;
     236             :                 break;
     237             :             }
     238             :         }
     239             : 
     240           0 :         if (needUpload)
     241             :         {
     242             :             // Tell the driver that it can reallocate the whole VBO
     243           0 :             pglBufferDataARB(m_Target, m_MaxVertices * m_VertexSize, NULL, m_Usage);
     244             : 
     245             :             // (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used
     246             :             // here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with
     247             :             // current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do
     248             :             // the explicit glBufferData.)
     249             : 
     250           0 :             while (true)
     251             :             {
     252           0 :                 void* p = pglMapBufferARB(m_Target, GL_WRITE_ONLY);
     253           0 :                 if (p == NULL)
     254             :                 {
     255             :                     // This shouldn't happen unless we run out of virtual address space
     256           0 :                     LOGERROR("glMapBuffer failed");
     257           0 :                     break;
     258             :                 }
     259             : 
     260             : #ifndef NDEBUG
     261             :                 // To help detect bugs where PrepareForRendering() was not called,
     262             :                 // force all not-needed data to 0, so things won't get rendered
     263             :                 // with undefined (but possibly still correct-looking) data.
     264             :                 memset(p, 0, m_MaxVertices * m_VertexSize);
     265             : #endif
     266             : 
     267             :                 // Copy only the chunks we need. (This condition is helpful when
     268             :                 // the VBO contains data for every unit in the world, but only a
     269             :                 // handful are visible on screen and we don't need to bother copying
     270             :                 // the rest.)
     271           0 :                 for (VBChunk* const& chunk : m_AllocList)
     272           0 :                     if (chunk->m_Needed)
     273           0 :                         memcpy((u8 *)p + chunk->m_Index * m_VertexSize, chunk->m_BackingStore, chunk->m_Count * m_VertexSize);
     274             : 
     275           0 :                 if (pglUnmapBufferARB(m_Target) == GL_TRUE)
     276             :                     break;
     277             : 
     278             :                 // Unmap might fail on e.g. resolution switches, so just try again
     279             :                 // and hope it will eventually succeed
     280           0 :                 debug_printf("glUnmapBuffer failed, trying again...\n");
     281           0 :             }
     282             : 
     283             :             // Anything we just uploaded is clean; anything else is dirty
     284             :             // since the rest of the VBO content is now undefined
     285           0 :             for (VBChunk* const& chunk : m_AllocList)
     286             :             {
     287           0 :                 if (chunk->m_Needed)
     288             :                 {
     289           0 :                     chunk->m_Dirty = false;
     290           0 :                     chunk->m_Needed = false;
     291             :                 }
     292             :                 else
     293           0 :                     chunk->m_Dirty = true;
     294             :             }
     295             :         }
     296             :         else
     297             :         {
     298             :             // Reset the flags for the next phase.
     299           0 :             for (VBChunk* const& chunk : m_AllocList)
     300           0 :                 chunk->m_Needed = false;
     301             :         }
     302             : 
     303           0 :         m_HasNeededChunks = false;
     304             :     }
     305             : 
     306             :     return nullptr;
     307             : }
     308             : 
     309           0 : void CVertexBuffer::Unbind()
     310             : {
     311           0 :     pglBindBufferARB(GL_ARRAY_BUFFER, 0);
     312           0 :     pglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
     313           0 : }
     314             : 
     315           0 : size_t CVertexBuffer::GetBytesReserved() const
     316             : {
     317           0 :     return MAX_VB_SIZE_BYTES;
     318             : }
     319             : 
     320           0 : size_t CVertexBuffer::GetBytesAllocated() const
     321             : {
     322           0 :     return (m_MaxVertices - m_FreeVertices) * m_VertexSize;
     323             : }
     324             : 
     325           0 : void CVertexBuffer::DumpStatus() const
     326             : {
     327           0 :     debug_printf("freeverts = %d\n", static_cast<int>(m_FreeVertices));
     328             : 
     329           0 :     size_t maxSize = 0;
     330           0 :     for (VBChunk* const& chunk : m_FreeList)
     331             :     {
     332           0 :         debug_printf("free chunk %p: size=%d\n", static_cast<void *>(chunk), static_cast<int>(chunk->m_Count));
     333           0 :         maxSize = std::max(chunk->m_Count, maxSize);
     334             :     }
     335           0 :     debug_printf("max size = %d\n", static_cast<int>(maxSize));
     336           0 : }
     337             : 
     338           0 : bool CVertexBuffer::UseStreaming(GLenum usage)
     339             : {
     340           0 :     return usage == GL_DYNAMIC_DRAW || usage == GL_STREAM_DRAW;
     341             : }
     342             : 
     343           0 : void CVertexBuffer::PrepareForRendering(VBChunk* chunk)
     344             : {
     345           0 :     chunk->m_Needed = true;
     346           0 :     m_HasNeededChunks = true;
     347           0 : }

Generated by: LCOV version 1.13