/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013 CERN * @author Maciej Suminski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * @file vbo_container.cpp * @brief Class to store VBO_ITEMs. */ #include #include #include #ifdef __WXDEBUG__ #include #endif /* __WXDEBUG__ */ using namespace KiGfx; VBO_CONTAINER::VBO_CONTAINER( int aSize ) : m_freeSpace( aSize ), m_currentSize( aSize ), itemStarted( false ), m_transform( NULL ) { // By default no shader is used m_shader[0] = 0; m_vertices = new VBO_VERTEX[aSize]; // In the beginning there is only free space m_freeChunks.insert( Chunk( aSize, 0 ) ); } VBO_CONTAINER::~VBO_CONTAINER() { delete[] m_vertices; } void VBO_CONTAINER::StartItem( VBO_ITEM* aVboItem ) { itemStarted = true; item = aVboItem; itemSize = 0; // Reserve minimal sensible chunk size (at least to store a single triangle) itemChunkSize = 3; allocate( aVboItem, itemChunkSize ); } void VBO_CONTAINER::EndItem() { if( itemSize < itemChunkSize ) { // There is some memory left, so we should return it to the pool int itemChunkOffset = item->GetOffset(); m_reservedChunks.erase( item ); m_reservedChunks.insert( ReservedChunk( item, Chunk( itemSize, itemChunkOffset ) ) ); m_freeChunks.insert( Chunk( itemChunkSize - itemSize, itemChunkOffset + itemSize ) ); m_freeSpace += ( itemChunkSize - itemSize ); } itemStarted = false; } void VBO_CONTAINER::Add( VBO_ITEM* aVboItem, const VBO_VERTEX* aVertex, unsigned int aSize ) { unsigned int offset; VBO_VERTEX* vertexPtr; if( itemStarted ) // There is an item being created with an unknown size.. { unsigned int itemChunkOffset; // ..and unfortunately does not fit into currently reserved chunk if( itemSize + aSize > itemChunkSize ) { // Find the previous chunk for the item, save it, so it can be removed later ReservedChunkMap::iterator it = m_reservedChunks.find( item ); // Reserve bigger memory for the current item int newSize = ( 2 * itemSize ) + aSize; itemChunkOffset = allocate( aVboItem, newSize ); // Check if there was no error if( itemChunkOffset > m_currentSize ) return; // Save previous chunk's offset for copying data int oldItemChunkOffset = getChunkOffset( *it ); // Copy all the old data memcpy( &m_vertices[itemChunkOffset], &m_vertices[oldItemChunkOffset], itemSize * VBO_ITEM::VertByteSize ); // Return memory used by the previous chunk free( it ); itemChunkSize = newSize; } else { itemChunkOffset = item->GetOffset(); } // Store new vertices in the chunk reserved for the unknown-sized item offset = itemChunkOffset + itemSize; itemSize += aSize; } else { // Add vertices to previously already finished item wxASSERT_MSG( false, wxT( "Warning: not tested yet" ) ); ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem ); unsigned int chunkSize = getChunkSize( *it ); unsigned int itemSize = aVboItem->GetSize(); if( chunkSize < itemSize + aSize ) { resizeChunk( aVboItem, itemSize + aSize ); it = m_reservedChunks.find( aVboItem ); } offset = getChunkOffset( *it ) + itemSize; } for( unsigned int i = 0; i < aSize; ++i ) { // Pointer to the vertex that we are currently adding vertexPtr = &m_vertices[offset + i]; // Modify the vertex according to the currently used transformations if( m_transform != NULL ) { // Apply transformations glm::vec4 vertex( aVertex[i].x, aVertex[i].y, aVertex[i].z, 1.0f ); vertex = *m_transform * vertex; // Replace only coordinates, leave color as it is vertexPtr->x = vertex.x; vertexPtr->y = vertex.y; vertexPtr->z = vertex.z; } else { // Simply copy coordinates vertexPtr->x = aVertex[i].x; vertexPtr->y = aVertex[i].y; vertexPtr->z = aVertex[i].z; } // Apply currently used color vertexPtr->r = m_color[0]; vertexPtr->g = m_color[1]; vertexPtr->b = m_color[2]; vertexPtr->a = m_color[3]; // Apply currently used shader for( unsigned int j = 0; j < VBO_ITEM::ShaderStride; ++j ) { vertexPtr->shader[j] = m_shader[j]; } } } VBO_VERTEX* VBO_CONTAINER::GetAllVertices() const { return m_vertices; } VBO_VERTEX* VBO_CONTAINER::GetVertices( const VBO_ITEM* aVboItem ) const { int offset = aVboItem->GetOffset(); return &m_vertices[offset]; } unsigned int VBO_CONTAINER::allocate( VBO_ITEM* aVboItem, unsigned int aSize ) { // Is there enough space to store vertices? if( m_freeSpace < aSize ) { bool result; // Would it be enough to double the current space? if( aSize < m_freeSpace + m_currentSize ) { // Yes: exponential growing result = resizeContainer( m_currentSize * 2 ); } else { // No: grow to the nearest bigger power of 2 result = resizeContainer( getPowerOf2( m_currentSize * 2 + aSize ) ); } // An error has occurred if( !result ) { return UINT_MAX; } } // Look for the space with at least given size FreeChunkMap::iterator it = m_freeChunks.lower_bound( aSize ); if( it == m_freeChunks.end() ) { // This means that there is enough space for // storing vertices, but the space is not continous if( !defragment() ) { return UINT_MAX; } // We can take the first free chunk, as there is only one after defragmentation // and we can be sure that it provides enough space to store the object it = m_freeChunks.begin(); } unsigned int chunkSize = it->first; unsigned int chunkOffset = it->second; m_freeChunks.erase( it ); wxASSERT( chunkSize >= aSize ); // If there is some space left, return it to the pool - add an entry for it if( chunkSize > aSize ) { m_freeChunks.insert( Chunk( chunkSize - aSize, chunkOffset + aSize ) ); } m_freeSpace -= aSize; m_reservedChunks.insert( ReservedChunk( aVboItem, Chunk( aSize, chunkOffset ) ) ); aVboItem->SetOffset( chunkOffset ); return chunkOffset; } void VBO_CONTAINER::free( const ReservedChunkMap::iterator& aChunk ) { // Remove the chunk from the reserved chunks map and add to the free chunks map int size = getChunkSize( *aChunk ); int offset = getChunkOffset( *aChunk ); m_reservedChunks.erase( aChunk ); m_freeChunks.insert( Chunk( size, offset ) ); m_freeSpace += size; } bool VBO_CONTAINER::defragment( VBO_VERTEX* aTarget ) { if( m_freeChunks.size() <= 1 ) { // There is no point in defragmenting, as there is only one or no free chunks return true; } if( aTarget == NULL ) { // No target was specified, so we have to allocate our own space aTarget = new (std::nothrow) VBO_VERTEX[m_currentSize]; if( aTarget == NULL ) { wxLogError( wxT( "Run out of memory" ) ); return false; } } int newOffset = 0; ReservedChunkMap::iterator it, it_end; for( it = m_reservedChunks.begin(), it_end = m_reservedChunks.end(); it != it_end; ++it ) { VBO_ITEM* vboItem = getChunkVboItem( *it ); int itemOffset = getChunkOffset( *it ); int itemSize = getChunkSize( *it ); // Move an item to the new container memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VBO_ITEM::VertByteSize ); // Update new offset vboItem->SetOffset( newOffset ); setChunkOffset( *it, newOffset ); // Move to the next free space newOffset += itemSize; } delete[] m_vertices; m_vertices = aTarget; // Now there is only one big chunk of free memory m_freeChunks.clear(); m_freeChunks.insert( Chunk( m_freeSpace, m_currentSize - m_freeSpace ) ); return true; } void VBO_CONTAINER::resizeChunk( VBO_ITEM* aVboItem, int aNewSize ) { wxASSERT_MSG( false, wxT( "Warning: not tested yet" ) ); // TODO ESPECIALLY test the case of shrinking chunk ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem ); int size = getChunkSize( *it ); int offset = getChunkOffset( *it ); int newOffset = allocate( aVboItem, aNewSize ); memcpy( &m_vertices[newOffset], &m_vertices[offset], size * VBO_ITEM::VertByteSize ); // Remove the chunk from the reserved chunks map and add to the free chunks map m_reservedChunks.erase( it ); m_freeChunks.insert( Chunk( size, offset ) ); m_freeSpace += size; } bool VBO_CONTAINER::resizeContainer( unsigned int aNewSize ) { unsigned int copySize; if( aNewSize < m_currentSize ) { // Sanity check, no shrinking if we cannot fit all the data if( ( m_currentSize - m_freeSpace ) > aNewSize ) return false; defragment(); copySize = ( m_currentSize - m_freeSpace ); } else { copySize = m_currentSize; } VBO_VERTEX* newContainer = new (std::nothrow) VBO_VERTEX[aNewSize]; if( newContainer == NULL ) { wxLogError( wxT( "Run out of memory" ) ); return false; } memcpy( newContainer, m_vertices, copySize * VBO_ITEM::VertByteSize ); delete[] m_vertices; m_vertices = newContainer; // Update variables unsigned int lastFreeSize = 0; unsigned int lastFreeOffset = 0; // Search for the last free chunk *at the end of the container* (not the last chunk in general) FreeChunkMap::reverse_iterator lastFree, freeEnd; for( lastFree = m_freeChunks.rbegin(), freeEnd = m_freeChunks.rend(); lastFree != freeEnd && lastFreeSize + lastFreeOffset != m_currentSize; ++lastFree ) { lastFreeSize = getChunkSize( *lastFree ); lastFreeOffset = getChunkOffset( *lastFree ); } if( lastFreeSize + lastFreeOffset == m_currentSize ) { // We found a chunk at the end of the container m_freeChunks.erase( lastFree.base() ); // so we can merge it with the new free chunk m_freeChunks.insert( Chunk( aNewSize - m_currentSize + lastFreeSize, // size m_currentSize - lastFreeSize ) ); // offset } else { // As there is no free chunk at the end of container - simply add a new entry if( aNewSize > m_currentSize ) // only in the case of enlargement { m_freeChunks.insert( Chunk( aNewSize - m_currentSize, // size m_currentSize ) ); // offset } } m_freeSpace += ( aNewSize - m_currentSize ); m_currentSize = aNewSize; return true; }