Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/renderer/VertexBuffer.cpp
/* Copyright (C) 2016 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
/* | |||||
* encapsulation of VBOs with sharing | |||||
*/ | |||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "ps/Errors.h" | |||||
#include "VertexBuffer.h" | |||||
#include "lib/ogl.h" | #include "lib/ogl.h" | ||||
#include "lib/sysdep/cpu.h" | #include "lib/sysdep/cpu.h" | ||||
#include "Renderer.h" | #include "Renderer.h" | ||||
#include "VertexBuffer.h" | |||||
#include "VertexBufferManager.h" | |||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/Errors.h" | |||||
#include <algorithm> | |||||
#include <iterator> | |||||
// Absolute maximum (bytewise) size of each GL vertex buffer object. | // Absolute maximum (bytewise) size of each GL vertex buffer object. | ||||
// Make it large enough for the maximum feasible mesh size (64K vertexes, | // Make it large enough for the maximum feasible mesh size (64K vertexes, | ||||
// 64 bytes per vertex in InstancingModelRenderer). | // 64 bytes per vertex in InstancingModelRenderer). | ||||
// TODO: measure what influence this has on performance | // TODO: measure what influence this has on performance | ||||
#define MAX_VB_SIZE_BYTES (4*1024*1024) | #define MAX_VB_SIZE_BYTES (4*1024*1024) | ||||
CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) | CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) | ||||
: m_VertexSize(vertexSize), m_Handle(0), m_SysMem(0), m_Usage(usage), m_Target(target) | : m_VertexSize(vertexSize), m_Handle(0), m_SysMem(0), m_Usage(usage), m_Target(target), m_HasNeededChunks(false) | ||||
{ | { | ||||
size_t size = MAX_VB_SIZE_BYTES; | size_t size = MAX_VB_SIZE_BYTES; | ||||
if (target == GL_ARRAY_BUFFER) // vertex data buffer | if (target == GL_ARRAY_BUFFER) // vertex data buffer | ||||
{ | { | ||||
// We want to store 16-bit indices to any vertex in a buffer, so the | // We want to store 16-bit indices to any vertex in a buffer, so the | ||||
// buffer must never be bigger than vertexSize*64K bytes since we can | // buffer must never be bigger than vertexSize*64K bytes since we can | ||||
// address at most 64K of them with 16-bit indices | // address at most 64K of them with 16-bit indices | ||||
Show All 16 Lines | else | ||||
m_SysMem = new u8[m_MaxVertices * m_VertexSize]; | m_SysMem = new u8[m_MaxVertices * m_VertexSize]; | ||||
} | } | ||||
// create sole free chunk | // create sole free chunk | ||||
VBChunk* chunk = new VBChunk; | VBChunk* chunk = new VBChunk; | ||||
chunk->m_Owner = this; | chunk->m_Owner = this; | ||||
chunk->m_Count = m_FreeVertices; | chunk->m_Count = m_FreeVertices; | ||||
chunk->m_Index = 0; | chunk->m_Index = 0; | ||||
m_FreeList.push_front(chunk); | m_FreeList.emplace_back(chunk); | ||||
} | } | ||||
CVertexBuffer::~CVertexBuffer() | CVertexBuffer::~CVertexBuffer() | ||||
{ | { | ||||
// Must have released all chunks before destroying the buffer | // Must have released all chunks before destroying the buffer | ||||
ENSURE(m_AllocList.empty()); | ENSURE(m_AllocList.empty()); | ||||
if (m_Handle) | if (m_Handle) | ||||
pglDeleteBuffersARB(1, &m_Handle); | pglDeleteBuffersARB(1, &m_Handle); | ||||
delete[] m_SysMem; | SAFE_ARRAY_DELETE(m_SysMem); | ||||
typedef std::list<VBChunk*>::iterator Iter; | for (VBChunk* const& chunk : m_FreeList) | ||||
for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) | delete chunk; | ||||
delete *iter; | |||||
} | } | ||||
bool CVertexBuffer::CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target) | bool CVertexBuffer::CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target) | ||||
{ | { | ||||
if (usage != m_Usage || target != m_Target || vertexSize != m_VertexSize) | return usage == m_Usage && target == m_Target && vertexSize == m_VertexSize; | ||||
return false; | |||||
return true; | |||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////////// | ||||
// Allocate: try to allocate a buffer of given number of vertices (each of | // Allocate: try to allocate a buffer of given number of vertices (each of | ||||
// given size), with the given type, and using the given texture - return null | // given size), with the given type, and using the given texture - return null | ||||
// if no free chunks available | // if no free chunks available | ||||
CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target, void* backingStore) | CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target, void* backingStore) | ||||
{ | { | ||||
// check this is the right kind of buffer | // check this is the right kind of buffer | ||||
if (!CompatibleVertexType(vertexSize, usage, target)) | if (!CompatibleVertexType(vertexSize, usage, target)) | ||||
return 0; | return 0; | ||||
if (UseStreaming(usage)) | if (UseStreaming(usage)) | ||||
ENSURE(backingStore != NULL); | ENSURE(backingStore != NULL); | ||||
// quick check there's enough vertices spare to allocate | // quick check there's enough vertices spare to allocate | ||||
if (numVertices > m_FreeVertices) | if (numVertices > m_FreeVertices) | ||||
return 0; | return 0; | ||||
// trawl free list looking for first free chunk with enough space | // trawl free list looking for first free chunk with enough space | ||||
VBChunk* chunk = 0; | std::vector<VBChunk*>::iterator best_iter = m_FreeList.end(); | ||||
typedef std::list<VBChunk*>::iterator Iter; | for (std::vector<VBChunk*>::iterator iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) | ||||
for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) { | { | ||||
if (numVertices <= (*iter)->m_Count) { | if (numVertices == (*iter)->m_Count) | ||||
chunk = *iter; | { | ||||
// remove this chunk from the free list | best_iter = iter; | ||||
m_FreeList.erase(iter); | |||||
m_FreeVertices -= chunk->m_Count; | |||||
// no need to search further .. | |||||
break; | break; | ||||
} | } | ||||
else if (numVertices < (*iter)->m_Count && (best_iter == m_FreeList.end() || (*best_iter)->m_Count < (*iter)->m_Count)) | |||||
best_iter = iter; | |||||
} | } | ||||
if (!chunk) { | // We could not find a large enough chunk. | ||||
// no big enough spare chunk available | if (best_iter == m_FreeList.end()) | ||||
return 0; | return nullptr; | ||||
} | |||||
VBChunk* chunk = *best_iter; | |||||
m_FreeList.erase(best_iter); | |||||
m_FreeVertices -= chunk->m_Count; | |||||
chunk->m_BackingStore = backingStore; | chunk->m_BackingStore = backingStore; | ||||
chunk->m_Dirty = false; | chunk->m_Dirty = false; | ||||
chunk->m_Needed = false; | chunk->m_Needed = false; | ||||
// split chunk into two; - allocate a new chunk using all unused vertices in the | // split chunk into two; - allocate a new chunk using all unused vertices in the | ||||
// found chunk, and add it to the free list | // found chunk, and add it to the free list | ||||
if (chunk->m_Count > numVertices) | if (chunk->m_Count > numVertices) | ||||
{ | { | ||||
VBChunk* newchunk = new VBChunk; | VBChunk* newchunk = new VBChunk; | ||||
newchunk->m_Owner = this; | newchunk->m_Owner = this; | ||||
newchunk->m_Count = chunk->m_Count - numVertices; | newchunk->m_Count = chunk->m_Count - numVertices; | ||||
newchunk->m_Index = chunk->m_Index + numVertices; | newchunk->m_Index = chunk->m_Index + numVertices; | ||||
m_FreeList.push_front(newchunk); | m_FreeList.emplace_back(newchunk); | ||||
m_FreeVertices += newchunk->m_Count; | m_FreeVertices += newchunk->m_Count; | ||||
// resize given chunk | // resize given chunk | ||||
chunk->m_Count = numVertices; | chunk->m_Count = numVertices; | ||||
} | } | ||||
// return found chunk | // return found chunk | ||||
m_AllocList.push_back(chunk); | m_AllocList.push_back(chunk); | ||||
return chunk; | return chunk; | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////////// | ||||
// Release: return given chunk to this buffer | // Release: return given chunk to this buffer | ||||
void CVertexBuffer::Release(VBChunk* chunk) | void CVertexBuffer::Release(VBChunk* chunk) | ||||
{ | { | ||||
// Update total free count before potentially modifying this chunk's count | // Update total free count before potentially modifying this chunk's count | ||||
m_FreeVertices += chunk->m_Count; | m_FreeVertices += chunk->m_Count; | ||||
m_AllocList.remove(chunk); | m_AllocList.erase(std::find(m_AllocList.begin(), m_AllocList.end(), chunk)); | ||||
typedef std::list<VBChunk*>::iterator Iter; | typedef std::vector<VBChunk*>::iterator Iter; | ||||
// Coalesce with any free-list items that are adjacent to this chunk; | // Coalesce with any free-list items that are adjacent to this chunk; | ||||
// merge the found chunk with the new one, and remove the old one | // merge the found chunk with the new one, and remove the old one | ||||
// from the list, and repeat until no more are found | // from the list, and repeat until no more are found | ||||
bool coalesced; | bool coalesced; | ||||
do | do | ||||
{ | { | ||||
coalesced = false; | coalesced = false; | ||||
for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) | for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) | ||||
{ | { | ||||
if ((*iter)->m_Index == chunk->m_Index + chunk->m_Count | if ((*iter)->m_Index == chunk->m_Index + chunk->m_Count | ||||
|| (*iter)->m_Index + (*iter)->m_Count == chunk->m_Index) | || (*iter)->m_Index + (*iter)->m_Count == chunk->m_Index) | ||||
{ | { | ||||
chunk->m_Index = std::min(chunk->m_Index, (*iter)->m_Index); | chunk->m_Index = std::min(chunk->m_Index, (*iter)->m_Index); | ||||
chunk->m_Count += (*iter)->m_Count; | chunk->m_Count += (*iter)->m_Count; | ||||
delete *iter; | delete *iter; | ||||
m_FreeList.erase(iter); | m_FreeList.erase(iter); | ||||
coalesced = true; | coalesced = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
while (coalesced); | while (coalesced); | ||||
m_FreeList.push_front(chunk); | m_FreeList.emplace_back(chunk); | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////////// | ||||
// UpdateChunkVertices: update vertex data for given chunk | // UpdateChunkVertices: update vertex data for given chunk | ||||
void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data) | void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data) | ||||
{ | { | ||||
if (g_Renderer.m_Caps.m_VBO) | if (g_Renderer.m_Caps.m_VBO) | ||||
{ | { | ||||
Show All 28 Lines | |||||
{ | { | ||||
if (!g_Renderer.m_Caps.m_VBO) | if (!g_Renderer.m_Caps.m_VBO) | ||||
return m_SysMem; | return m_SysMem; | ||||
pglBindBufferARB(m_Target, m_Handle); | pglBindBufferARB(m_Target, m_Handle); | ||||
if (UseStreaming(m_Usage)) | if (UseStreaming(m_Usage)) | ||||
{ | { | ||||
if (!m_HasNeededChunks) | |||||
return nullptr; | |||||
// If any chunks are out of sync with the current VBO, and are | // If any chunks are out of sync with the current VBO, and are | ||||
// needed for rendering this frame, we'll need to re-upload the VBO | // needed for rendering this frame, we'll need to re-upload the VBO | ||||
bool needUpload = false; | bool needUpload = false; | ||||
for (VBChunk* const& chunk : m_AllocList) | for (VBChunk* const& chunk : m_AllocList) | ||||
{ | { | ||||
if (chunk->m_Dirty && chunk->m_Needed) | if (chunk->m_Dirty && chunk->m_Needed) | ||||
{ | { | ||||
needUpload = true; | needUpload = true; | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | #endif | ||||
debug_printf("glUnmapBuffer failed, trying again...\n"); | debug_printf("glUnmapBuffer failed, trying again...\n"); | ||||
} | } | ||||
// Anything we just uploaded is clean; anything else is dirty | // Anything we just uploaded is clean; anything else is dirty | ||||
// since the rest of the VBO content is now undefined | // since the rest of the VBO content is now undefined | ||||
for (VBChunk* const& chunk : m_AllocList) | for (VBChunk* const& chunk : m_AllocList) | ||||
{ | { | ||||
if (chunk->m_Needed) | if (chunk->m_Needed) | ||||
{ | |||||
chunk->m_Dirty = false; | chunk->m_Dirty = false; | ||||
chunk->m_Needed = false; | |||||
} | |||||
else | else | ||||
chunk->m_Dirty = true; | chunk->m_Dirty = true; | ||||
} | } | ||||
} | } | ||||
else | |||||
// Reset the flags for the next phase | { | ||||
// Reset the flags for the next phase. | |||||
for (VBChunk* const& chunk : m_AllocList) | for (VBChunk* const& chunk : m_AllocList) | ||||
chunk->m_Needed = false; | chunk->m_Needed = false; | ||||
} | } | ||||
m_HasNeededChunks = false; | |||||
} | |||||
return (u8*)0; | return (u8*)0; | ||||
} | } | ||||
u8* CVertexBuffer::GetBindAddress() | u8* CVertexBuffer::GetBindAddress() | ||||
{ | { | ||||
if (g_Renderer.m_Caps.m_VBO) | if (g_Renderer.m_Caps.m_VBO) | ||||
return (u8*)0; | return (u8*)0; | ||||
else | else | ||||
Show All 16 Lines | |||||
size_t CVertexBuffer::GetBytesAllocated() const | size_t CVertexBuffer::GetBytesAllocated() const | ||||
{ | { | ||||
return (m_MaxVertices - m_FreeVertices) * m_VertexSize; | return (m_MaxVertices - m_FreeVertices) * m_VertexSize; | ||||
} | } | ||||
void CVertexBuffer::DumpStatus() | void CVertexBuffer::DumpStatus() | ||||
{ | { | ||||
debug_printf("freeverts = %d\n", (int)m_FreeVertices); | debug_printf("freeverts = %d\n", static_cast<int>(m_FreeVertices)); | ||||
size_t maxSize = 0; | size_t maxSize = 0; | ||||
typedef std::list<VBChunk*>::iterator Iter; | for (VBChunk* const& chunk : m_FreeList) | ||||
for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) | |||||
{ | { | ||||
debug_printf("free chunk %p: size=%d\n", (void *)*iter, (int)((*iter)->m_Count)); | debug_printf("free chunk %p: size=%d\n", static_cast<void *>(chunk), static_cast<int>(chunk->m_Count)); | ||||
maxSize = std::max((*iter)->m_Count, maxSize); | maxSize = std::max(chunk->m_Count, maxSize); | ||||
} | } | ||||
debug_printf("max size = %d\n", (int)maxSize); | debug_printf("max size = %d\n", static_cast<int>(maxSize)); | ||||
} | } | ||||
bool CVertexBuffer::UseStreaming(GLenum usage) | bool CVertexBuffer::UseStreaming(GLenum usage) | ||||
{ | { | ||||
return (usage == GL_DYNAMIC_DRAW || usage == GL_STREAM_DRAW); | return (usage == GL_DYNAMIC_DRAW || usage == GL_STREAM_DRAW); | ||||
} | } | ||||
void CVertexBuffer::PrepareForRendering(VBChunk* chunk) | |||||
{ | |||||
chunk->m_Needed = true; | |||||
m_HasNeededChunks = true; | |||||
} |
Wildfire Games · Phabricator