diff options
Diffstat (limited to 'common/gal/opengl')
-rw-r--r-- | common/gal/opengl/cached_container.cpp | 547 | ||||
-rw-r--r-- | common/gal/opengl/gpu_manager.cpp | 302 | ||||
-rw-r--r-- | common/gal/opengl/noncached_container.cpp | 89 | ||||
-rw-r--r-- | common/gal/opengl/opengl_compositor.cpp | 291 | ||||
-rw-r--r-- | common/gal/opengl/opengl_gal.cpp | 1204 | ||||
-rw-r--r-- | common/gal/opengl/shader.cpp | 272 | ||||
-rw-r--r-- | common/gal/opengl/shader.frag | 75 | ||||
-rw-r--r-- | common/gal/opengl/shader.vert | 95 | ||||
-rw-r--r-- | common/gal/opengl/vertex_container.cpp | 60 | ||||
-rw-r--r-- | common/gal/opengl/vertex_item.cpp | 53 | ||||
-rw-r--r-- | common/gal/opengl/vertex_manager.cpp | 234 |
11 files changed, 3222 insertions, 0 deletions
diff --git a/common/gal/opengl/cached_container.cpp b/common/gal/opengl/cached_container.cpp new file mode 100644 index 0000000..8cf3513 --- /dev/null +++ b/common/gal/opengl/cached_container.cpp @@ -0,0 +1,547 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 cached_container.cpp + * @brief Class to store instances of VERTEX with caching. It allows storing VERTEX objects and + * associates them with VERTEX_ITEMs. This leads to a possibility of caching vertices data in the + * GPU memory and a fast reuse of that data. + */ + +#include <gal/opengl/cached_container.h> +#include <gal/opengl/vertex_manager.h> +#include <gal/opengl/vertex_item.h> +#include <gal/opengl/shader.h> +#include <confirm.h> +#include <wx/log.h> +#include <list> +#ifdef __WXDEBUG__ +#include <profile.h> +#endif /* __WXDEBUG__ */ + +using namespace KIGFX; + +CACHED_CONTAINER::CACHED_CONTAINER( unsigned int aSize ) : + VERTEX_CONTAINER( aSize ), m_item( NULL ) +{ + // In the beginning there is only free space + m_freeChunks.insert( CHUNK( aSize, 0 ) ); + + // Do not have uninitialized members: + m_chunkSize = 0; + m_chunkOffset = 0; + m_itemSize = 0; +} + + +void CACHED_CONTAINER::SetItem( VERTEX_ITEM* aItem ) +{ + wxASSERT( aItem != NULL ); + + m_item = aItem; + m_itemSize = m_item->GetSize(); + m_chunkSize = m_itemSize; + + if( m_itemSize == 0 ) + m_items.insert( m_item ); // The item was not stored before + else + m_chunkOffset = m_item->GetOffset(); + +#if CACHED_CONTAINER_TEST > 1 + wxLogDebug( wxT( "Adding/editing item 0x%08lx (size %d)" ), (long) m_item, m_itemSize ); +#endif +} + + +void CACHED_CONTAINER::FinishItem() +{ + wxASSERT( m_item != NULL ); + wxASSERT( m_item->GetSize() == m_itemSize ); + + // Finishing the previously edited item + if( m_itemSize < m_chunkSize ) + { + // There is some not used but reserved memory left, so we should return it to the pool + int itemOffset = m_item->GetOffset(); + + // Add the not used memory back to the pool + m_freeChunks.insert( CHUNK( m_chunkSize - m_itemSize, itemOffset + m_itemSize ) ); + m_freeSpace += ( m_chunkSize - m_itemSize ); + // mergeFreeChunks(); // veery slow and buggy + } + +#if CACHED_CONTAINER_TEST > 1 + wxLogDebug( wxT( "Finishing item 0x%08lx (size %d)" ), (long) m_item, m_itemSize ); + test(); + m_item = NULL; // electric fence +#endif +} + + +VERTEX* CACHED_CONTAINER::Allocate( unsigned int aSize ) +{ + wxASSERT( m_item != NULL ); + + if( m_failed ) + return NULL; + + if( m_itemSize + aSize > m_chunkSize ) + { + // There is not enough space in the currently reserved chunk, so we have to resize it + + // Reserve a bigger memory chunk for the current item and + // make it multiple of 3 to store triangles + m_chunkSize = ( 2 * m_itemSize ) + aSize + ( 3 - aSize % 3 ); + // Save the current size before reallocating + m_chunkOffset = reallocate( m_chunkSize ); + + if( m_chunkOffset > m_currentSize ) + { + m_failed = true; + return NULL; + } + } + + VERTEX* reserved = &m_vertices[m_chunkOffset + m_itemSize]; + m_itemSize += aSize; + // Now the item officially possesses the memory chunk + m_item->setSize( m_itemSize ); + + // The content has to be updated + m_dirty = true; + +#if CACHED_CONTAINER_TEST > 1 + test(); +#endif +#if CACHED_CONTAINER_TEST > 2 + showFreeChunks(); + showReservedChunks(); +#endif + + return reserved; +} + + +void CACHED_CONTAINER::Delete( VERTEX_ITEM* aItem ) +{ + wxASSERT( aItem != NULL ); + wxASSERT( m_items.find( aItem ) != m_items.end() ); + + int size = aItem->GetSize(); + int offset = aItem->GetOffset(); + +#if CACHED_CONTAINER_TEST > 1 + wxLogDebug( wxT( "Removing 0x%08lx (size %d offset %d)" ), (long) aItem, size, offset ); +#endif + + // Insert a free memory chunk entry in the place where item was stored + if( size > 0 ) + { + m_freeChunks.insert( CHUNK( size, offset ) ); + m_freeSpace += size; + // Indicate that the item is not stored in the container anymore + aItem->setSize( 0 ); + } + + m_items.erase( aItem ); + +#if CACHED_CONTAINER_TEST > 1 + test(); +#endif + + // Dynamic memory freeing, there is no point in holding + // a large amount of memory when there is no use for it + if( m_freeSpace > ( 0.75 * m_currentSize ) && m_currentSize > m_initialSize ) + { + resizeContainer( 0.5 * m_currentSize ); + } +} + + +void CACHED_CONTAINER::Clear() +{ + // Change size to the default one + m_vertices = static_cast<VERTEX*>( realloc( m_vertices, + m_initialSize * sizeof( VERTEX ) ) ); + + // Reset state variables + m_freeSpace = m_initialSize; + m_currentSize = m_initialSize; + m_failed = false; + + // Set the size of all the stored VERTEX_ITEMs to 0, so it is clear that they are not held + // in the container anymore + ITEMS::iterator it; + + for( it = m_items.begin(); it != m_items.end(); ++it ) + { + ( *it )->setSize( 0 ); + } + + m_items.clear(); + + + // Now there is only free space left + m_freeChunks.clear(); + m_freeChunks.insert( CHUNK( m_freeSpace, 0 ) ); +} + + +unsigned int CACHED_CONTAINER::reallocate( unsigned int aSize ) +{ + wxASSERT( aSize > 0 ); + +#if CACHED_CONTAINER_TEST > 2 + wxLogDebug( wxT( "Resize 0x%08lx from %d to %d" ), (long) m_item, m_itemSize, aSize ); +#endif + + // 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 greater power of 2 + result = resizeContainer( pow( 2, ceil( log2( m_currentSize * 2 + aSize ) ) ) ); + } + + if( !result ) + return UINT_MAX; + } + + // Look for the free space chunk of at least given size + FREE_CHUNK_MAP::iterator newChunk = m_freeChunks.lower_bound( aSize ); + + if( newChunk == m_freeChunks.end() ) + { + // In the case when there is enough space to store the vertices, + // but the free space is not continous we should defragment the container + if( !defragment() ) + return UINT_MAX; + + // Update the current offset + m_chunkOffset = m_item->GetOffset(); + + // 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 + newChunk = m_freeChunks.begin(); + } + + // Parameters of the allocated cuhnk + unsigned int chunkSize = newChunk->first; + unsigned int chunkOffset = newChunk->second; + + wxASSERT( chunkSize >= aSize ); + wxASSERT( chunkOffset < m_currentSize ); + + // Check if the item was previously stored in the container + if( m_itemSize > 0 ) + { +#if CACHED_CONTAINER_TEST > 3 + wxLogDebug( wxT( "Moving 0x%08x from 0x%08x to 0x%08x" ), + (int) m_item, oldChunkOffset, chunkOffset ); +#endif + // The item was reallocated, so we have to copy all the old data to the new place + memcpy( &m_vertices[chunkOffset], &m_vertices[m_chunkOffset], m_itemSize * VertexSize ); + + // Free the space previously used by the chunk + wxASSERT( m_itemSize > 0 ); + m_freeChunks.insert( CHUNK( m_itemSize, m_chunkOffset ) ); + m_freeSpace += m_itemSize; + } + + // Remove the allocated chunk from the free space pool + m_freeChunks.erase( newChunk ); + + // 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; + // mergeFreeChunks(); // veery slow and buggy + + m_item->setOffset( chunkOffset ); + + return chunkOffset; +} + + +bool CACHED_CONTAINER::defragment( VERTEX* aTarget ) +{ +#if CACHED_CONTAINER_TEST > 0 + wxLogDebug( wxT( "Defragmenting" ) ); + + prof_counter totalTime; + prof_start( &totalTime ); +#endif + + if( aTarget == NULL ) + { + // No target was specified, so we have to reallocate our own space + int size = m_currentSize * sizeof( VERTEX ); + aTarget = static_cast<VERTEX*>( malloc( size ) ); + + if( aTarget == NULL ) + { + DisplayError( NULL, wxString::Format( + wxT( "CACHED_CONTAINER::defragment: Run out of memory (malloc %d bytes)" ), + size ) ); + return false; + } + } + + int newOffset = 0; + ITEMS::iterator it, it_end; + + for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it ) + { + VERTEX_ITEM* item = *it; + int itemOffset = item->GetOffset(); + int itemSize = item->GetSize(); + + // Move an item to the new container + memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VertexSize ); + + // Update new offset + item->setOffset( newOffset ); + + // Move to the next free space + newOffset += itemSize; + } + + free( m_vertices ); + m_vertices = aTarget; + + // Now there is only one big chunk of free memory + m_freeChunks.clear(); + wxASSERT( m_freeSpace > 0 ); + m_freeChunks.insert( CHUNK( m_freeSpace, m_currentSize - m_freeSpace ) ); + +#if CACHED_CONTAINER_TEST > 0 + prof_end( &totalTime ); + + wxLogDebug( wxT( "Defragmented the container storing %d vertices / %.1f ms" ), + m_currentSize - m_freeSpace, totalTime.msecs() ); +#endif + + return true; +} + + +void CACHED_CONTAINER::mergeFreeChunks() +{ + if( m_freeChunks.size() <= 1 ) // There are no chunks that can be merged + return; + +#if CACHED_CONTAINER_TEST > 0 + prof_counter totalTime; + prof_start( &totalTime ); +#endif + + // Reversed free chunks map - this one stores chunk size with its offset as the key + std::list<CHUNK> freeChunks; + + FREE_CHUNK_MAP::const_iterator it, it_end; + + for( it = m_freeChunks.begin(), it_end = m_freeChunks.end(); it != it_end; ++it ) + { + freeChunks.push_back( std::make_pair( it->second, it->first ) ); + } + + m_freeChunks.clear(); + freeChunks.sort(); + + std::list<CHUNK>::const_iterator itf, itf_end; + unsigned int offset = freeChunks.front().first; + unsigned int size = freeChunks.front().second; + freeChunks.pop_front(); + + for( itf = freeChunks.begin(), itf_end = freeChunks.end(); itf != itf_end; ++itf ) + { + if( itf->first == offset + size ) + { + // These chunks can be merged, so just increase the current chunk size and go on + size += itf->second; + } + else + { + // These chunks cannot be merged + // So store the previous one + m_freeChunks.insert( std::make_pair( size, offset ) ); + // and let's check the next chunk + offset = itf->first; + size = itf->second; + + } + } + + // Add the last one + m_freeChunks.insert( std::make_pair( size, offset ) ); + +#if CACHED_CONTAINER_TEST > 0 + prof_end( &totalTime ); + + wxLogDebug( wxT( "Merged free chunks / %.1f ms" ), totalTime.msecs() ); +#endif + + test(); +} + + +bool CACHED_CONTAINER::resizeContainer( unsigned int aNewSize ) +{ + wxASSERT( aNewSize != m_currentSize ); + +#if CACHED_CONTAINER_TEST > 0 + wxLogDebug( wxT( "Resizing container from %d to %d" ), m_currentSize, aNewSize ); +#endif + + VERTEX* newContainer; + + if( aNewSize < m_currentSize ) + { + // Shrinking container + // Sanity check, no shrinking if we cannot fit all the data + if( reservedSpace() > aNewSize ) + return false; + + int size = aNewSize * sizeof( VERTEX ); + newContainer = static_cast<VERTEX*>( malloc( size ) ); + + if( newContainer == NULL ) + { + DisplayError( NULL, wxString::Format( + wxT( "CACHED_CONTAINER::resizeContainer:\n" + "Run out of memory (malloc %d bytes)" ), + size ) ); + return false; + } + + // Defragment directly to the new, smaller container + defragment( newContainer ); + + // We have to correct freeChunks after defragmentation + m_freeChunks.clear(); + wxASSERT( aNewSize - reservedSpace() > 0 ); + m_freeChunks.insert( CHUNK( aNewSize - reservedSpace(), reservedSpace() ) ); + } + else + { + // Enlarging container + int size = aNewSize * sizeof( VERTEX ); + newContainer = static_cast<VERTEX*>( realloc( m_vertices, size ) ); + + if( newContainer == NULL ) + { + DisplayError( NULL, wxString::Format( + wxT( "CACHED_CONTAINER::resizeContainer:\n" + "Run out of memory (realloc from %d to %d bytes)" ), + m_currentSize * sizeof( VERTEX ), size ) ); + return false; + } + + // Add an entry for the new memory chunk at the end of the container + m_freeChunks.insert( CHUNK( aNewSize - m_currentSize, m_currentSize ) ); + } + + m_vertices = newContainer; + + m_freeSpace += ( aNewSize - m_currentSize ); + m_currentSize = aNewSize; + + return true; +} + + +#ifdef CACHED_CONTAINER_TEST +void CACHED_CONTAINER::showFreeChunks() +{ + FREE_CHUNK_MAP::iterator it; + + wxLogDebug( wxT( "Free chunks:" ) ); + + for( it = m_freeChunks.begin(); it != m_freeChunks.end(); ++it ) + { + unsigned int offset = getChunkOffset( *it ); + unsigned int size = getChunkSize( *it ); + wxASSERT( size > 0 ); + + wxLogDebug( wxT( "[0x%08x-0x%08x] (size %d)" ), + offset, offset + size - 1, size ); + } +} + + +void CACHED_CONTAINER::showReservedChunks() +{ + ITEMS::iterator it; + + wxLogDebug( wxT( "Reserved chunks:" ) ); + + for( it = m_items.begin(); it != m_items.end(); ++it ) + { + VERTEX_ITEM* item = *it; + unsigned int offset = item->GetOffset(); + unsigned int size = item->GetSize(); + wxASSERT( size > 0 ); + + wxLogDebug( wxT( "[0x%08x-0x%08x] @ 0x%08lx (size %d)" ), + offset, offset + size - 1, (long) item, size ); + } +} + + +void CACHED_CONTAINER::test() +{ + // Free space check + unsigned int freeSpace = 0; + FREE_CHUNK_MAP::iterator itf; + + for( itf = m_freeChunks.begin(); itf != m_freeChunks.end(); ++itf ) + freeSpace += getChunkSize( *itf ); + + wxASSERT( freeSpace == m_freeSpace ); + + // Reserved space check + /*unsigned int reservedSpace = 0; + ITEMS::iterator itr; + for( itr = m_items.begin(); itr != m_items.end(); ++itr ) + reservedSpace += ( *itr )->GetSize(); + reservedSpace += m_itemSize; // Add the current chunk size + + wxASSERT( ( freeSpace + reservedSpace ) == m_currentSize );*/ + + // Overlapping check TBD +} + +#endif /* CACHED_CONTAINER_TEST */ diff --git a/common/gal/opengl/gpu_manager.cpp b/common/gal/opengl/gpu_manager.cpp new file mode 100644 index 0000000..be34a56 --- /dev/null +++ b/common/gal/opengl/gpu_manager.cpp @@ -0,0 +1,302 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 gpu_manager.cpp + * @brief Class to handle uploading vertices and indices to GPU in drawing purposes. + */ + +#include <gal/opengl/gpu_manager.h> +#include <gal/opengl/cached_container.h> +#include <gal/opengl/noncached_container.h> +#include <gal/opengl/shader.h> +#include <typeinfo> +#include <wx/msgdlg.h> +#include <confirm.h> +#ifdef PROFILE +#include <profile.h> +#include <wx/debug.h> +#include <wx/log.h> +#endif /* PROFILE */ + +using namespace KIGFX; + +GPU_MANAGER* GPU_MANAGER::MakeManager( VERTEX_CONTAINER* aContainer ) +{ + if( typeid( *aContainer ) == typeid( CACHED_CONTAINER ) ) + return new GPU_CACHED_MANAGER( aContainer ); + else if( typeid( *aContainer ) == typeid( NONCACHED_CONTAINER ) ) + return new GPU_NONCACHED_MANAGER( aContainer ); + + wxASSERT_MSG( false, wxT( "Not handled container type" ) ); + return NULL; +} + + +GPU_MANAGER::GPU_MANAGER( VERTEX_CONTAINER* aContainer ) : + m_isDrawing( false ), m_container( aContainer ), m_shader( NULL ), m_shaderAttrib( 0 ) +{ +} + + +GPU_MANAGER::~GPU_MANAGER() +{ +} + + +void GPU_MANAGER::SetShader( SHADER& aShader ) +{ + m_shader = &aShader; + + m_shaderAttrib = m_shader->GetAttribute( "attrShaderParams" ); + + if( m_shaderAttrib == -1 ) + { + DisplayError( NULL, wxT( "Could not get the shader attribute location" ) ); + } +} + + +// Cached manager +GPU_CACHED_MANAGER::GPU_CACHED_MANAGER( VERTEX_CONTAINER* aContainer ) : + GPU_MANAGER( aContainer ), m_buffersInitialized( false ), m_indicesPtr( NULL ), + m_verticesBuffer( 0 ), m_indicesBuffer( 0 ), m_indicesSize( 0 ), m_indicesCapacity( 0 ) +{ + // Allocate the biggest possible buffer for indices + resizeIndices( aContainer->GetSize() ); +} + + +GPU_CACHED_MANAGER::~GPU_CACHED_MANAGER() +{ + if( m_buffersInitialized ) + { + glBindBuffer( GL_ARRAY_BUFFER, 0 ); + glDeleteBuffers( 1, &m_verticesBuffer ); + glDeleteBuffers( 1, &m_indicesBuffer ); + } +} + + +void GPU_CACHED_MANAGER::Initialize() +{ + wxASSERT( !m_buffersInitialized ); + + if( !m_buffersInitialized ) + { + glGenBuffers( 1, &m_verticesBuffer ); + glGenBuffers( 1, &m_indicesBuffer ); + m_buffersInitialized = true; + } +} + + +void GPU_CACHED_MANAGER::BeginDrawing() +{ + wxASSERT( !m_isDrawing ); + + if( m_container->IsDirty() ) + uploadToGpu(); + + // Number of vertices to be drawn in the EndDrawing() + m_indicesSize = 0; + // Set the indices pointer to the beginning of the indices-to-draw buffer + m_indicesPtr = m_indices.get(); + + m_isDrawing = true; +} + + +void GPU_CACHED_MANAGER::DrawIndices( unsigned int aOffset, unsigned int aSize ) +{ + wxASSERT( m_isDrawing ); + + // Copy indices of items that should be drawn to GPU memory + for( unsigned int i = aOffset; i < aOffset + aSize; *m_indicesPtr++ = i++ ); + + m_indicesSize += aSize; +} + + +void GPU_CACHED_MANAGER::DrawAll() +{ + wxASSERT( m_isDrawing ); + + m_indicesSize = m_container->GetSize(); + for( unsigned int i = 0; i < m_indicesSize; *m_indicesPtr++ = i++ ); +} + + +void GPU_CACHED_MANAGER::EndDrawing() +{ + wxASSERT( m_isDrawing ); + + // Prepare buffers + glEnableClientState( GL_VERTEX_ARRAY ); + glEnableClientState( GL_COLOR_ARRAY ); + + // Bind vertices data buffers + glBindBuffer( GL_ARRAY_BUFFER, m_verticesBuffer ); + glVertexPointer( CoordStride, GL_FLOAT, VertexSize, 0 ); + glColorPointer( ColorStride, GL_UNSIGNED_BYTE, VertexSize, (GLvoid*) ColorOffset ); + + if( m_shader != NULL ) // Use shader if applicable + { + m_shader->Use(); + glEnableVertexAttribArray( m_shaderAttrib ); + glVertexAttribPointer( m_shaderAttrib, ShaderStride, GL_FLOAT, GL_FALSE, + VertexSize, (GLvoid*) ShaderOffset ); + } + + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_indicesBuffer ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_indicesSize * sizeof(int), (GLvoid*) m_indices.get(), GL_DYNAMIC_DRAW ); + + glDrawElements( GL_TRIANGLES, m_indicesSize, GL_UNSIGNED_INT, 0 ); + + glBindBuffer( GL_ARRAY_BUFFER, 0 ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); + + // Deactivate vertex array + glDisableClientState( GL_COLOR_ARRAY ); + glDisableClientState( GL_VERTEX_ARRAY ); + + if( m_shader != NULL ) + { + glDisableVertexAttribArray( m_shaderAttrib ); + m_shader->Deactivate(); + } + + m_isDrawing = false; +} + + +void GPU_CACHED_MANAGER::uploadToGpu() +{ +#ifdef PROFILE + prof_counter totalTime; + prof_start( &totalTime ); +#endif /* PROFILE */ + + if( !m_buffersInitialized ) + Initialize(); + + int bufferSize = m_container->GetSize(); + GLfloat* vertices = (GLfloat*) m_container->GetAllVertices(); + + // Upload vertices coordinates and shader types to GPU memory + glBindBuffer( GL_ARRAY_BUFFER, m_verticesBuffer ); + glBufferData( GL_ARRAY_BUFFER, bufferSize * VertexSize, vertices, GL_STATIC_DRAW ); + glBindBuffer( GL_ARRAY_BUFFER, 0 ); + + // Allocate the biggest possible buffer for indices + resizeIndices( bufferSize ); + + if( glGetError() != GL_NO_ERROR ) + DisplayError( NULL, wxT( "Error during data upload to the GPU memory" ) ); + +#ifdef PROFILE + prof_end( &totalTime ); + + wxLogDebug( wxT( "Uploading %d vertices to GPU / %.1f ms" ), bufferSize, totalTime.msecs() ); +#endif /* PROFILE */ +} + + +void GPU_CACHED_MANAGER::resizeIndices( unsigned int aNewSize ) +{ + if( aNewSize > m_indicesCapacity ) + { + m_indicesCapacity = aNewSize; + m_indices.reset( new GLuint[m_indicesCapacity] ); + } +} + + +// Noncached manager +GPU_NONCACHED_MANAGER::GPU_NONCACHED_MANAGER( VERTEX_CONTAINER* aContainer ) : + GPU_MANAGER( aContainer ) +{ +} + + +void GPU_NONCACHED_MANAGER::Initialize() +{ + // Nothing has to be intialized +} + + +void GPU_NONCACHED_MANAGER::BeginDrawing() +{ + // Nothing has to be prepared +} + + +void GPU_NONCACHED_MANAGER::DrawIndices( unsigned int aOffset, unsigned int aSize ) +{ + wxASSERT_MSG( false, wxT( "Not implemented yet" ) ); +} + + +void GPU_NONCACHED_MANAGER::DrawAll() +{ + // This is the default use case, nothing has to be done + // The real rendering takes place in the EndDrawing() function +} + + +void GPU_NONCACHED_MANAGER::EndDrawing() +{ + VERTEX* vertices = m_container->GetAllVertices(); + GLfloat* coordinates = (GLfloat*) ( vertices ); + GLubyte* colors = (GLubyte*) ( vertices ) + ColorOffset; + + // Prepare buffers + glEnableClientState( GL_VERTEX_ARRAY ); + glEnableClientState( GL_COLOR_ARRAY ); + + glVertexPointer( CoordStride, GL_FLOAT, VertexSize, coordinates ); + glColorPointer( ColorStride, GL_UNSIGNED_BYTE, VertexSize, colors ); + + if( m_shader != NULL ) // Use shader if applicable + { + GLfloat* shaders = (GLfloat*) ( vertices ) + ShaderOffset / sizeof(GLfloat); + + m_shader->Use(); + glEnableVertexAttribArray( m_shaderAttrib ); + glVertexAttribPointer( m_shaderAttrib, ShaderStride, GL_FLOAT, GL_FALSE, + VertexSize, shaders ); + } + + glDrawArrays( GL_TRIANGLES, 0, m_container->GetSize() ); + + // Deactivate vertex array + glDisableClientState( GL_COLOR_ARRAY ); + glDisableClientState( GL_VERTEX_ARRAY ); + + if( m_shader != NULL ) + { + glDisableVertexAttribArray( m_shaderAttrib ); + m_shader->Deactivate(); + } +} diff --git a/common/gal/opengl/noncached_container.cpp b/common/gal/opengl/noncached_container.cpp new file mode 100644 index 0000000..c902c47 --- /dev/null +++ b/common/gal/opengl/noncached_container.cpp @@ -0,0 +1,89 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 noncached_container.cpp + * @brief Class to store instances of VERTEX without caching. It allows a fast one-frame drawing + * and then clearing the buffer and starting from scratch. + */ + +#include <gal/opengl/noncached_container.h> +#include <cstdlib> + +using namespace KIGFX; + +NONCACHED_CONTAINER::NONCACHED_CONTAINER( unsigned int aSize ) : + VERTEX_CONTAINER( aSize ), m_freePtr( 0 ) +{ +} + + +NONCACHED_CONTAINER::~NONCACHED_CONTAINER() +{ +} + + +void NONCACHED_CONTAINER::SetItem( VERTEX_ITEM* aItem ) +{ + // Nothing has to be done, as the noncached container + // does not care about VERTEX_ITEMs ownership +} + + +VERTEX* NONCACHED_CONTAINER::Allocate( unsigned int aSize ) +{ + if( m_freeSpace < aSize ) + { + // Double the space + VERTEX* newVertices = static_cast<VERTEX*>( realloc( m_vertices, + m_currentSize * 2 * + sizeof(VERTEX) ) ); + + if( newVertices != NULL ) + { + m_vertices = newVertices; + m_freeSpace += m_currentSize; + m_currentSize *= 2; + } + else + { + return NULL; + } + } + + VERTEX* freeVertex = &m_vertices[m_freePtr]; + + // Move to the next free chunk + m_freePtr += aSize; + m_freeSpace -= aSize; + + return freeVertex; +} + + +void NONCACHED_CONTAINER::Clear() +{ + m_freePtr = 0; + m_freeSpace = m_currentSize; +} diff --git a/common/gal/opengl/opengl_compositor.cpp b/common/gal/opengl/opengl_compositor.cpp new file mode 100644 index 0000000..9f35d9e --- /dev/null +++ b/common/gal/opengl/opengl_compositor.cpp @@ -0,0 +1,291 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013-2015 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 opengl_compositor.cpp + * @brief Class that handles multitarget rendering (i.e. to different textures/surfaces) and + * later compositing into a single image (OpenGL flavour). + */ + +#include <gal/opengl/opengl_compositor.h> + +#include <stdexcept> +#include <cassert> + +using namespace KIGFX; + +OPENGL_COMPOSITOR::OPENGL_COMPOSITOR() : + m_initialized( false ), m_current( 0 ), m_currentFbo( DIRECT_RENDERING ) +{ + // Avoid not initialized members: + m_framebuffer = 0; + m_depthBuffer = 0; +} + + +OPENGL_COMPOSITOR::~OPENGL_COMPOSITOR() +{ + if( m_initialized ) + clean(); +} + + +void OPENGL_COMPOSITOR::Initialize() +{ + if( m_initialized ) + return; + + // We need framebuffer objects for drawing the screen contents + // Generate framebuffer and a depth buffer + glGenFramebuffersEXT( 1, &m_framebuffer ); + glBindFramebufferEXT( GL_FRAMEBUFFER, m_framebuffer ); + m_currentFbo = m_framebuffer; + + // Allocate memory for the depth buffer + // Attach the depth buffer to the framebuffer + glGenRenderbuffersEXT( 1, &m_depthBuffer ); + glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_depthBuffer ); + + // Use here a size of 24 bits for the depth buffer, 8 bits for the stencil buffer + // this is required later for anti-aliasing + glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, m_width, m_height ); + glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER_EXT, m_depthBuffer ); + + // Unbind the framebuffer, so by default all the rendering goes directly to the display + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, DIRECT_RENDERING ); + m_currentFbo = DIRECT_RENDERING; + + m_initialized = true; +} + + +void OPENGL_COMPOSITOR::Resize( unsigned int aWidth, unsigned int aHeight ) +{ + if( m_initialized ) + clean(); + + m_width = aWidth; + m_height = aHeight; +} + + +unsigned int OPENGL_COMPOSITOR::CreateBuffer() +{ + assert( m_initialized ); + + unsigned int maxBuffers; + + // Get the maximum number of buffers + glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS, (GLint*) &maxBuffers ); + + if( usedBuffers() >= maxBuffers ) + { + throw std::runtime_error("Cannot create more framebuffers. OpenGL rendering " + "backend requires at least 3 framebuffers. You may try to update/change " + "your graphic drivers."); + } + + // GL_COLOR_ATTACHMENTn are consecutive integers + GLuint attachmentPoint = GL_COLOR_ATTACHMENT0 + usedBuffers(); + GLuint textureTarget; + + // Generate the texture for the pixel storage + glGenTextures( 1, &textureTarget ); + glBindTexture( GL_TEXTURE_2D, textureTarget ); + + // Set texture parameters + glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, NULL ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + + // Bind the texture to the specific attachment point, clear and rebind the screen + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_framebuffer ); + m_currentFbo = m_framebuffer; + glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, attachmentPoint, + GL_TEXTURE_2D, textureTarget, 0 ); + + // Check the status, exit if the framebuffer can't be created + GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); + + if( status != GL_FRAMEBUFFER_COMPLETE_EXT ) + { + switch( status ) + { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + throw std::runtime_error( "Cannot create the framebuffer." ); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + throw std::runtime_error( "The framebuffer attachment points are incomplete." ); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + throw std::runtime_error( "The framebuffer does not have at least one " + "image attached to it." ); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + throw std::runtime_error( "The framebuffer read buffer is incomplete." ); + break; + + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + throw std::runtime_error( "The combination of internal formats of the attached " + "images violates an implementation-dependent set of restrictions." ); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + throw std::runtime_error( "GL_RENDERBUFFER_SAMPLES is not the same for " + "all attached renderbuffers" ); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT: + throw std::runtime_error( "Framebuffer incomplete layer targets errors." ); + break; + + default: + throw std::runtime_error( "Cannot create the framebuffer." ); + break; + } + + return 0; + } + + ClearBuffer(); + + // Return to direct rendering (we were asked only to create a buffer, not switch to one) + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, DIRECT_RENDERING ); + m_currentFbo = DIRECT_RENDERING; + + // Store the new buffer + OPENGL_BUFFER buffer = { textureTarget, attachmentPoint }; + m_buffers.push_back( buffer ); + + return usedBuffers(); +} + + +void OPENGL_COMPOSITOR::SetBuffer( unsigned int aBufferHandle ) +{ + if( aBufferHandle > usedBuffers() ) + return; + + // Change the rendering destination to the selected attachment point + if( aBufferHandle == DIRECT_RENDERING ) + { + m_currentFbo = DIRECT_RENDERING; + } + else if( m_currentFbo != m_framebuffer ) + { + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_framebuffer ); + m_currentFbo = m_framebuffer; + } + + if( m_currentFbo != DIRECT_RENDERING ) + { + m_current = aBufferHandle - 1; + glDrawBuffer( m_buffers[m_current].attachmentPoint ); + } +} + + +void OPENGL_COMPOSITOR::ClearBuffer() +{ + assert( m_initialized ); + + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); +} + + +void OPENGL_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle ) +{ + assert( m_initialized ); + assert( aBufferHandle != 0 && aBufferHandle <= usedBuffers() ); + + // Switch to the main framebuffer and blit the scene + glBindFramebufferEXT( GL_FRAMEBUFFER, DIRECT_RENDERING ); + m_currentFbo = DIRECT_RENDERING; + + // Depth test has to be disabled to make transparency working + glDisable( GL_DEPTH_TEST ); + glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA ); + + // Enable texturing and bind the main texture + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, m_buffers[aBufferHandle - 1].textureTarget ); + + // Draw a full screen quad with the texture + glMatrixMode( GL_MODELVIEW ); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode( GL_PROJECTION ); + glPushMatrix(); + glLoadIdentity(); + + glBegin( GL_TRIANGLES ); + glTexCoord2f( 0.0f, 1.0f ); + glVertex2f( -1.0f, -1.0f ); + glTexCoord2f( 1.0f, 1.0f ); + glVertex2f( 1.0f, -1.0f ); + glTexCoord2f( 1.0f, 0.0f ); + glVertex2f( 1.0f, 1.0f ); + + glTexCoord2f( 0.0f, 1.0f ); + glVertex2f( -1.0f, -1.0f ); + glTexCoord2f( 1.0f, 0.0f ); + glVertex2f( 1.0f, 1.0f ); + glTexCoord2f( 0.0f, 0.0f ); + glVertex2f( -1.0f, 1.0f ); + glEnd(); + + glPopMatrix(); + glMatrixMode( GL_MODELVIEW ); + glPopMatrix(); +} + + +void OPENGL_COMPOSITOR::clean() +{ + assert( m_initialized ); + + glBindFramebufferEXT( GL_FRAMEBUFFER, DIRECT_RENDERING ); + m_currentFbo = DIRECT_RENDERING; + + OPENGL_BUFFERS::const_iterator it; + + for( it = m_buffers.begin(); it != m_buffers.end(); ++it ) + { + glDeleteTextures( 1, &it->textureTarget ); + } + + m_buffers.clear(); + + glDeleteFramebuffersEXT( 1, &m_framebuffer ); + glDeleteRenderbuffersEXT( 1, &m_depthBuffer ); + + m_initialized = false; +} diff --git a/common/gal/opengl/opengl_gal.cpp b/common/gal/opengl/opengl_gal.cpp new file mode 100644 index 0000000..945161f --- /dev/null +++ b/common/gal/opengl/opengl_gal.cpp @@ -0,0 +1,1204 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de + * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors. + * Copyright (C) 2013-2016 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * Graphics Abstraction Layer (GAL) for OpenGL + * + * 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 + */ + +#include <gal/opengl/opengl_gal.h> +#include <gal/definitions.h> + +#include <wx/log.h> +#include <macros.h> +#ifdef __WXDEBUG__ +#include <profile.h> +#endif /* __WXDEBUG__ */ + +#include <limits> +#include <boost/bind.hpp> + +using namespace KIGFX; + +static void InitTesselatorCallbacks( GLUtesselator* aTesselator ); +const int glAttributes[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 8, 0 }; +wxGLContext* OPENGL_GAL::glContext = NULL; + +OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener, + wxEvtHandler* aPaintListener, const wxString& aName ) : + wxGLCanvas( aParent, wxID_ANY, (int*) glAttributes, wxDefaultPosition, wxDefaultSize, + wxEXPAND, aName ), + mouseListener( aMouseListener ), + paintListener( aPaintListener ), + cachedManager( true ), + nonCachedManager( false ), + overlayManager( false ) +{ + if( glContext == NULL ) + glContext = new wxGLContext( this ); + + // Check if OpenGL requirements are met + runTest(); + + // Make VBOs use shaders + cachedManager.SetShader( shader ); + nonCachedManager.SetShader( shader ); + overlayManager.SetShader( shader ); + + // Initialize the flags + isFramebufferInitialized = false; + isGrouping = false; + groupCounter = 0; + +#ifdef RETINA_OPENGL_PATCH + SetViewWantsBestResolution( true ); +#endif + + // Connecting the event handlers + Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::onPaint ) ); + + // Mouse events are skipped to the parent + Connect( wxEVT_MOTION, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_LEFT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); + Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); +#ifdef USE_OSX_MAGNIFY_EVENT + Connect( wxEVT_MAGNIFY, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); +#endif +#if defined _WIN32 || defined _WIN64 + Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); +#endif + + SetSize( aParent->GetSize() ); + screenSize = VECTOR2I( aParent->GetSize() ); + + // Grid color settings are different in Cairo and OpenGL + SetGridColor( COLOR4D( 0.8, 0.8, 0.8, 0.1 ) ); + + // Tesselator initialization + tesselator = gluNewTess(); + InitTesselatorCallbacks( tesselator ); + + if( tesselator == NULL ) + throw std::runtime_error( "Could not create the tesselator" ); + + gluTessProperty( tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE ); + + currentManager = &nonCachedManager; +} + + +OPENGL_GAL::~OPENGL_GAL() +{ + glFlush(); + + gluDeleteTess( tesselator ); + ClearCache(); +} + + +void OPENGL_GAL::BeginDrawing() +{ + if( !IsShownOnScreen() ) + return; + + SetCurrent( *glContext ); + clientDC = new wxClientDC( this ); + +#ifdef RETINA_OPENGL_PATCH + const float scaleFactor = GetBackingScaleFactor(); +#else + const float scaleFactor = 1.0f; +#endif + + // Set up the view port + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glViewport( 0, 0, (GLsizei) screenSize.x * scaleFactor, (GLsizei) screenSize.y * scaleFactor ); + + // Create the screen transformation + glOrtho( 0, (GLint) screenSize.x, 0, (GLsizei) screenSize.y, + -depthRange.x, -depthRange.y ); + + if( !isFramebufferInitialized ) + { + // Prepare rendering target buffers + compositor.Initialize(); + mainBuffer = compositor.CreateBuffer(); + overlayBuffer = compositor.CreateBuffer(); + + isFramebufferInitialized = true; + } + + // Disable 2D Textures + glDisable( GL_TEXTURE_2D ); + + // Enable the depth buffer + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LESS ); + + // Setup blending, required for transparent objects + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + glMatrixMode( GL_MODELVIEW ); + + // Set up the world <-> screen transformation + ComputeWorldScreenMatrix(); + GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + matrixData[0] = worldScreenMatrix.m_data[0][0]; + matrixData[1] = worldScreenMatrix.m_data[1][0]; + matrixData[2] = worldScreenMatrix.m_data[2][0]; + matrixData[4] = worldScreenMatrix.m_data[0][1]; + matrixData[5] = worldScreenMatrix.m_data[1][1]; + matrixData[6] = worldScreenMatrix.m_data[2][1]; + matrixData[12] = worldScreenMatrix.m_data[0][2]; + matrixData[13] = worldScreenMatrix.m_data[1][2]; + matrixData[14] = worldScreenMatrix.m_data[2][2]; + glLoadMatrixd( matrixData ); + + // Set defaults + SetFillColor( fillColor ); + SetStrokeColor( strokeColor ); + + // Unbind buffers - set compositor for direct drawing + compositor.SetBuffer( OPENGL_COMPOSITOR::DIRECT_RENDERING ); + + // Remove all previously stored items + nonCachedManager.Clear(); + overlayManager.Clear(); + + cachedManager.BeginDrawing(); + nonCachedManager.BeginDrawing(); + overlayManager.BeginDrawing(); +} + + +void OPENGL_GAL::EndDrawing() +{ + // Cached & non-cached containers are rendered to the same buffer + compositor.SetBuffer( mainBuffer ); + nonCachedManager.EndDrawing(); + cachedManager.EndDrawing(); + + // Overlay container is rendered to a different buffer + compositor.SetBuffer( overlayBuffer ); + overlayManager.EndDrawing(); + + // Be sure that the framebuffer is not colorized (happens on specific GPU&drivers combinations) + glColor4d( 1.0, 1.0, 1.0, 1.0 ); + + // Draw the remaining contents, blit the rendering targets to the screen, swap the buffers + compositor.DrawBuffer( mainBuffer ); + compositor.DrawBuffer( overlayBuffer ); + blitCursor(); + + glFlush(); + SwapBuffers(); + + delete clientDC; +} + + +void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) +{ + const VECTOR2D startEndVector = aEndPoint - aStartPoint; + double lineAngle = startEndVector.Angle(); + + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + drawLineQuad( aStartPoint, aEndPoint ); + + // Line caps + if( lineWidth > 1.0 ) + { + drawFilledSemiCircle( aStartPoint, lineWidth / 2, lineAngle + M_PI / 2 ); + drawFilledSemiCircle( aEndPoint, lineWidth / 2, lineAngle - M_PI / 2 ); + } +} + + +void OPENGL_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint, + double aWidth ) +{ + VECTOR2D startEndVector = aEndPoint - aStartPoint; + double lineAngle = startEndVector.Angle(); + + if( isFillEnabled ) + { + // Filled tracks + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + + SetLineWidth( aWidth ); + drawLineQuad( aStartPoint, aEndPoint ); + + // Draw line caps + drawFilledSemiCircle( aStartPoint, aWidth / 2, lineAngle + M_PI / 2 ); + drawFilledSemiCircle( aEndPoint, aWidth / 2, lineAngle - M_PI / 2 ); + } + else + { + // Outlined tracks + double lineLength = startEndVector.EuclideanNorm(); + + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + Save(); + + currentManager->Translate( aStartPoint.x, aStartPoint.y, 0.0 ); + currentManager->Rotate( lineAngle, 0.0f, 0.0f, 1.0f ); + + drawLineQuad( VECTOR2D( 0.0, aWidth / 2.0 ), + VECTOR2D( lineLength, aWidth / 2.0 ) ); + + drawLineQuad( VECTOR2D( 0.0, -aWidth / 2.0 ), + VECTOR2D( lineLength, -aWidth / 2.0 ) ); + + // Draw line caps + drawStrokedSemiCircle( VECTOR2D( 0.0, 0.0 ), aWidth / 2, M_PI / 2 ); + drawStrokedSemiCircle( VECTOR2D( lineLength, 0.0 ), aWidth / 2, -M_PI / 2 ); + + Restore(); + } +} + + +void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) +{ + if( isFillEnabled ) + { + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + + /* Draw a triangle that contains the circle, then shade it leaving only the circle. + * Parameters given to setShader are indices of the triangle's vertices + * (if you want to understand more, check the vertex shader source [shader.vert]). + * Shader uses this coordinates to determine if fragments are inside the circle or not. + * v2 + * /\ + * //\\ + * v0 /_\/_\ v1 + */ + currentManager->Shader( SHADER_FILLED_CIRCLE, 1.0 ); + currentManager->Vertex( aCenterPoint.x - aRadius * sqrt( 3.0f ), // v0 + aCenterPoint.y - aRadius, layerDepth ); + + currentManager->Shader( SHADER_FILLED_CIRCLE, 2.0 ); + currentManager->Vertex( aCenterPoint.x + aRadius * sqrt( 3.0f ), // v1 + aCenterPoint.y - aRadius, layerDepth ); + + currentManager->Shader( SHADER_FILLED_CIRCLE, 3.0 ); + currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + aRadius * 2.0f, // v2 + layerDepth ); + } + + if( isStrokeEnabled ) + { + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + /* Draw a triangle that contains the circle, then shade it leaving only the circle. + * Parameters given to setShader are indices of the triangle's vertices + * (if you want to understand more, check the vertex shader source [shader.vert]). + * and the line width. Shader uses this coordinates to determine if fragments are + * inside the circle or not. + * v2 + * /\ + * //\\ + * v0 /_\/_\ v1 + */ + double outerRadius = aRadius + ( lineWidth / 2 ); + currentManager->Shader( SHADER_STROKED_CIRCLE, 1.0, aRadius, lineWidth ); + currentManager->Vertex( aCenterPoint.x - outerRadius * sqrt( 3.0f ), // v0 + aCenterPoint.y - outerRadius, layerDepth ); + + currentManager->Shader( SHADER_STROKED_CIRCLE, 2.0, aRadius, lineWidth ); + currentManager->Vertex( aCenterPoint.x + outerRadius * sqrt( 3.0f ), // v1 + aCenterPoint.y - outerRadius, layerDepth ); + + currentManager->Shader( SHADER_STROKED_CIRCLE, 3.0, aRadius, lineWidth ); + currentManager->Vertex( aCenterPoint.x, aCenterPoint.y + outerRadius * 2.0f, // v2 + layerDepth ); + } +} + + +void OPENGL_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle, + double aEndAngle ) +{ + if( aRadius <= 0 ) + return; + + // Swap the angles, if start angle is greater than end angle + SWAP( aStartAngle, >, aEndAngle ); + + Save(); + currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0 ); + + if( isStrokeEnabled ) + { + const double alphaIncrement = 2.0 * M_PI / CIRCLE_POINTS; + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + VECTOR2D p( cos( aStartAngle ) * aRadius, sin( aStartAngle ) * aRadius ); + double alpha; + + for( alpha = aStartAngle + alphaIncrement; alpha <= aEndAngle; alpha += alphaIncrement ) + { + VECTOR2D p_next( cos( alpha ) * aRadius, sin( alpha ) * aRadius ); + DrawLine( p, p_next ); + + p = p_next; + } + + // Draw the last missing part + if( alpha != aEndAngle ) + { + VECTOR2D p_last( cos( aEndAngle ) * aRadius, sin( aEndAngle ) * aRadius ); + DrawLine( p, p_last ); + } + } + + if( isFillEnabled ) + { + const double alphaIncrement = 2 * M_PI / CIRCLE_POINTS; + double alpha; + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + currentManager->Shader( SHADER_NONE ); + + // Triangle fan + for( alpha = aStartAngle; ( alpha + alphaIncrement ) < aEndAngle; ) + { + currentManager->Vertex( 0.0, 0.0, 0.0 ); + currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 ); + alpha += alphaIncrement; + currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 ); + } + + // The last missing triangle + const VECTOR2D endPoint( cos( aEndAngle ) * aRadius, sin( aEndAngle ) * aRadius ); + currentManager->Vertex( 0.0, 0.0, 0.0 ); + currentManager->Vertex( cos( alpha ) * aRadius, sin( alpha ) * aRadius, 0.0 ); + currentManager->Vertex( endPoint.x, endPoint.y, 0.0 ); + } + + Restore(); +} + + +void OPENGL_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) +{ + // Compute the diagonal points of the rectangle + VECTOR2D diagonalPointA( aEndPoint.x, aStartPoint.y ); + VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y ); + + // Stroke the outline + if( isStrokeEnabled ) + { + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + std::deque<VECTOR2D> pointList; + pointList.push_back( aStartPoint ); + pointList.push_back( diagonalPointA ); + pointList.push_back( aEndPoint ); + pointList.push_back( diagonalPointB ); + pointList.push_back( aStartPoint ); + DrawPolyline( pointList ); + } + + // Fill the rectangle + if( isFillEnabled ) + { + currentManager->Shader( SHADER_NONE ); + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + + currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); + currentManager->Vertex( diagonalPointA.x, diagonalPointA.y, layerDepth ); + currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); + + currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); + currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); + currentManager->Vertex( diagonalPointB.x, diagonalPointB.y, layerDepth ); + } +} + + +void OPENGL_GAL::DrawPolyline( const std::deque<VECTOR2D>& aPointList ) +{ + if( aPointList.size() < 2 ) + return; + + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + std::deque<VECTOR2D>::const_iterator it = aPointList.begin(); + + // Start from the second point + for( ++it; it != aPointList.end(); ++it ) + { + const VECTOR2D startEndVector = ( *it - *( it - 1 ) ); + double lineAngle = startEndVector.Angle(); + + drawLineQuad( *( it - 1 ), *it ); + + // There is no need to draw line caps on both ends of polyline's segments + drawFilledSemiCircle( *( it - 1 ), lineWidth / 2, lineAngle + M_PI / 2 ); + } + + // ..and now - draw the ending cap + const VECTOR2D startEndVector = ( *( it - 1 ) - *( it - 2 ) ); + double lineAngle = startEndVector.Angle(); + drawFilledSemiCircle( *( it - 1 ), lineWidth / 2, lineAngle - M_PI / 2 ); +} + + +void OPENGL_GAL::DrawPolyline( const VECTOR2D aPointList[], int aListSize ) +{ + if( aListSize < 2 ) + return; + + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + + // Start from the second point + for( int i = 1; i < aListSize; ++i ) + { + const VECTOR2D startEndVector = ( aPointList[i] - aPointList[i - 1] ); + double lineAngle = startEndVector.Angle(); + + drawLineQuad( aPointList[i - 1], aPointList[i] ); + + // There is no need to draw line caps on both ends of polyline's segments + drawFilledSemiCircle( aPointList[i - 1], lineWidth / 2, lineAngle + M_PI / 2 ); + } + + // ..and now - draw the ending cap + const VECTOR2D startEndVector = ( aPointList[aListSize - 1] - aPointList[aListSize - 2] ); + double lineAngle = startEndVector.Angle(); + drawFilledSemiCircle( aPointList[aListSize - 1], lineWidth / 2, lineAngle - M_PI / 2 ); +} + + +void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList ) +{ + currentManager->Shader( SHADER_NONE ); + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + + // Any non convex polygon needs to be tesselated + // for this purpose the GLU standard functions are used + TessParams params = { currentManager, tessIntersects }; + gluTessBeginPolygon( tesselator, ¶ms ); + gluTessBeginContour( tesselator ); + + boost::shared_array<GLdouble> points( new GLdouble[3 * aPointList.size()] ); + int v = 0; + + for( std::deque<VECTOR2D>::const_iterator it = aPointList.begin(); it != aPointList.end(); ++it ) + { + points[v] = it->x; + points[v + 1] = it->y; + points[v + 2] = layerDepth; + gluTessVertex( tesselator, &points[v], &points[v] ); + v += 3; + } + + gluTessEndContour( tesselator ); + gluTessEndPolygon( tesselator ); + + // Free allocated intersecting points + tessIntersects.clear(); + + // vertexList destroyed here +} + + +void OPENGL_GAL::DrawPolygon( const VECTOR2D aPointList[], int aListSize ) +{ + currentManager->Shader( SHADER_NONE ); + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + + // Any non convex polygon needs to be tesselated + // for this purpose the GLU standard functions are used + TessParams params = { currentManager, tessIntersects }; + gluTessBeginPolygon( tesselator, ¶ms ); + gluTessBeginContour( tesselator ); + + boost::shared_array<GLdouble> points( new GLdouble[3 * aListSize] ); + int v = 0; + const VECTOR2D* ptr = aPointList; + + for( int i = 0; i < aListSize; ++i ) + { + points[v] = ptr->x; + points[v + 1] = ptr->y; + points[v + 2] = layerDepth; + gluTessVertex( tesselator, &points[v], &points[v] ); + ++ptr; + v += 3; + } + + gluTessEndContour( tesselator ); + gluTessEndPolygon( tesselator ); + + // Free allocated intersecting points + tessIntersects.clear(); + + // vertexList destroyed here +} + + +void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA, + const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint ) +{ + // FIXME The drawing quality needs to be improved + // FIXME Perhaps choose a quad/triangle strip instead? + // FIXME Brute force method, use a better (recursive?) algorithm + + std::deque<VECTOR2D> pointList; + + double t = 0.0; + double dt = 1.0 / (double) CURVE_POINTS; + + for( int i = 0; i <= CURVE_POINTS; i++ ) + { + double omt = 1.0 - t; + double omt2 = omt * omt; + double omt3 = omt * omt2; + double t2 = t * t; + double t3 = t * t2; + + VECTOR2D vertex = omt3 * aStartPoint + 3.0 * t * omt2 * aControlPointA + + 3.0 * t2 * omt * aControlPointB + t3 * aEndPoint; + + pointList.push_back( vertex ); + + t += dt; + } + + DrawPolyline( pointList ); +} + + +void OPENGL_GAL::ResizeScreen( int aWidth, int aHeight ) +{ + screenSize = VECTOR2I( aWidth, aHeight ); + +#ifdef RETINA_OPENGL_PATCH + const float scaleFactor = GetBackingScaleFactor(); +#else + const float scaleFactor = 1.0f; +#endif + + // Resize framebuffers + compositor.Resize( aWidth * scaleFactor, aHeight * scaleFactor ); + isFramebufferInitialized = false; + + wxGLCanvas::SetSize( aWidth, aHeight ); +} + + +bool OPENGL_GAL::Show( bool aShow ) +{ + bool s = wxGLCanvas::Show( aShow ); + + if( aShow ) + wxGLCanvas::Raise(); + + return s; +} + + +void OPENGL_GAL::Flush() +{ + glFlush(); +} + + +void OPENGL_GAL::ClearScreen( const COLOR4D& aColor ) +{ + // Clear screen + glClearColor( aColor.r, aColor.g, aColor.b, aColor.a ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); +} + + +void OPENGL_GAL::Transform( const MATRIX3x3D& aTransformation ) +{ + GLdouble matrixData[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + + matrixData[0] = aTransformation.m_data[0][0]; + matrixData[1] = aTransformation.m_data[1][0]; + matrixData[2] = aTransformation.m_data[2][0]; + matrixData[4] = aTransformation.m_data[0][1]; + matrixData[5] = aTransformation.m_data[1][1]; + matrixData[6] = aTransformation.m_data[2][1]; + matrixData[12] = aTransformation.m_data[0][2]; + matrixData[13] = aTransformation.m_data[1][2]; + matrixData[14] = aTransformation.m_data[2][2]; + + glMultMatrixd( matrixData ); +} + + +void OPENGL_GAL::Rotate( double aAngle ) +{ + currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f ); +} + + +void OPENGL_GAL::Translate( const VECTOR2D& aVector ) +{ + currentManager->Translate( aVector.x, aVector.y, 0.0f ); +} + + +void OPENGL_GAL::Scale( const VECTOR2D& aScale ) +{ + currentManager->Scale( aScale.x, aScale.y, 0.0f ); +} + + +void OPENGL_GAL::Save() +{ + currentManager->PushMatrix(); +} + + +void OPENGL_GAL::Restore() +{ + currentManager->PopMatrix(); +} + + +int OPENGL_GAL::BeginGroup() +{ + isGrouping = true; + + boost::shared_ptr<VERTEX_ITEM> newItem( new VERTEX_ITEM( cachedManager ) ); + int groupNumber = getNewGroupNumber(); + groups.insert( std::make_pair( groupNumber, newItem ) ); + + return groupNumber; +} + + +void OPENGL_GAL::EndGroup() +{ + cachedManager.FinishItem(); + isGrouping = false; +} + + +void OPENGL_GAL::DrawGroup( int aGroupNumber ) +{ + cachedManager.DrawItem( *groups[aGroupNumber] ); +} + + +void OPENGL_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor ) +{ + cachedManager.ChangeItemColor( *groups[aGroupNumber], aNewColor ); +} + + +void OPENGL_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth ) +{ + cachedManager.ChangeItemDepth( *groups[aGroupNumber], aDepth ); +} + + +void OPENGL_GAL::DeleteGroup( int aGroupNumber ) +{ + // Frees memory in the container as well + groups.erase( aGroupNumber ); +} + + +void OPENGL_GAL::ClearCache() +{ + groups.clear(); + cachedManager.Clear(); +} + + +void OPENGL_GAL::SaveScreen() +{ + wxASSERT_MSG( false, wxT( "Not implemented yet" ) ); +} + + +void OPENGL_GAL::RestoreScreen() +{ + wxASSERT_MSG( false, wxT( "Not implemented yet" ) ); +} + + +void OPENGL_GAL::SetTarget( RENDER_TARGET aTarget ) +{ + switch( aTarget ) + { + default: + case TARGET_CACHED: + currentManager = &cachedManager; + break; + + case TARGET_NONCACHED: + currentManager = &nonCachedManager; + break; + + case TARGET_OVERLAY: + currentManager = &overlayManager; + break; + } + + currentTarget = aTarget; +} + + +RENDER_TARGET OPENGL_GAL::GetTarget() const +{ + return currentTarget; +} + + +void OPENGL_GAL::ClearTarget( RENDER_TARGET aTarget ) +{ + // Save the current state + unsigned int oldTarget = compositor.GetBuffer(); + + switch( aTarget ) + { + // Cached and noncached items are rendered to the same buffer + default: + case TARGET_CACHED: + case TARGET_NONCACHED: + compositor.SetBuffer( mainBuffer ); + break; + + case TARGET_OVERLAY: + compositor.SetBuffer( overlayBuffer ); + break; + } + + compositor.ClearBuffer(); + + // Restore the previous state + compositor.SetBuffer( oldTarget ); +} + + +void OPENGL_GAL::DrawCursor( const VECTOR2D& aCursorPosition ) +{ + // Now we should only store the position of the mouse cursor + // The real drawing routines are in blitCursor() + VECTOR2D screenCursor = worldScreenMatrix * aCursorPosition; + + cursorPosition = screenWorldMatrix * VECTOR2D( screenCursor.x, screenSize.y - screenCursor.y ); +} + + +void OPENGL_GAL::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) +{ + compositor.SetBuffer( mainBuffer ); + + // We do not need a very precise comparison here (the lineWidth is set by GAL::DrawGrid()) + if( fabs( lineWidth - 2.0 * gridLineWidth / worldScale ) < 0.1 ) + glLineWidth( 1.0 ); + else + glLineWidth( 2.0 ); + + glColor4d( gridColor.r, gridColor.g, gridColor.b, gridColor.a ); + + glBegin( GL_LINES ); + glVertex3d( aStartPoint.x, aStartPoint.y, layerDepth ); + glVertex3d( aEndPoint.x, aEndPoint.y, layerDepth ); + glEnd(); + + // Restore the default color, so textures will be drawn properly + glColor4d( 1.0, 1.0, 1.0, 1.0 ); +} + + +void OPENGL_GAL::drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) +{ + /* Helper drawing: ____--- v3 ^ + * ____---- ... \ \ + * ____---- ... \ end \ + * v1 ____---- ... ____---- \ width + * ---- ...___---- \ \ + * \ ___...-- \ v + * \ ____----... ____---- v2 + * ---- ... ____---- + * start \ ... ____---- + * \... ____---- + * ---- + * v0 + * dots mark triangles' hypotenuses + */ + + VECTOR2D startEndVector = aEndPoint - aStartPoint; + double lineLength = startEndVector.EuclideanNorm(); + + if( lineLength <= 0.0 ) + return; + + double scale = 0.5 * lineWidth / lineLength; + + // The perpendicular vector also needs transformations + glm::vec4 vector = currentManager->GetTransformation() * + glm::vec4( -startEndVector.y * scale, startEndVector.x * scale, 0.0, 0.0 ); + + // Line width is maintained by the vertex shader + currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth ); + currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0 + + currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth ); + currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v1 + + currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth ); + currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3 + + currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth ); + currentManager->Vertex( aStartPoint.x, aStartPoint.y, layerDepth ); // v0 + + currentManager->Shader( SHADER_LINE, -vector.x, -vector.y, lineWidth ); + currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v3 + + currentManager->Shader( SHADER_LINE, vector.x, vector.y, lineWidth ); + currentManager->Vertex( aEndPoint.x, aEndPoint.y, layerDepth ); // v2 +} + + +void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle ) +{ + if( isFillEnabled ) + { + currentManager->Color( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); + drawFilledSemiCircle( aCenterPoint, aRadius, aAngle ); + } + + if( isStrokeEnabled ) + { + currentManager->Color( strokeColor.r, strokeColor.g, strokeColor.b, strokeColor.a ); + drawStrokedSemiCircle( aCenterPoint, aRadius, aAngle ); + } +} + + +void OPENGL_GAL::drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, + double aAngle ) +{ + Save(); + currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f ); + currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f ); + + /* Draw a triangle that contains the semicircle, then shade it to leave only + * the semicircle. Parameters given to setShader are indices of the triangle's vertices + * (if you want to understand more, check the vertex shader source [shader.vert]). + * Shader uses these coordinates to determine if fragments are inside the semicircle or not. + * v2 + * /\ + * /__\ + * v0 //__\\ v1 + */ + currentManager->Shader( SHADER_FILLED_CIRCLE, 4.0f ); + currentManager->Vertex( -aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0 + + currentManager->Shader( SHADER_FILLED_CIRCLE, 5.0f ); + currentManager->Vertex( aRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1 + + currentManager->Shader( SHADER_FILLED_CIRCLE, 6.0f ); + currentManager->Vertex( 0.0f, aRadius * 2.0f, layerDepth ); // v2 + + Restore(); +} + + +void OPENGL_GAL::drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, + double aAngle ) +{ + double outerRadius = aRadius + ( lineWidth / 2 ); + + Save(); + currentManager->Translate( aCenterPoint.x, aCenterPoint.y, 0.0f ); + currentManager->Rotate( aAngle, 0.0f, 0.0f, 1.0f ); + + /* Draw a triangle that contains the semicircle, then shade it to leave only + * the semicircle. Parameters given to setShader are indices of the triangle's vertices + * (if you want to understand more, check the vertex shader source [shader.vert]), the + * radius and the line width. Shader uses these coordinates to determine if fragments are + * inside the semicircle or not. + * v2 + * /\ + * /__\ + * v0 //__\\ v1 + */ + currentManager->Shader( SHADER_STROKED_CIRCLE, 4.0f, aRadius, lineWidth ); + currentManager->Vertex( -outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v0 + + currentManager->Shader( SHADER_STROKED_CIRCLE, 5.0f, aRadius, lineWidth ); + currentManager->Vertex( outerRadius * 3.0f / sqrt( 3.0f ), 0.0f, layerDepth ); // v1 + + currentManager->Shader( SHADER_STROKED_CIRCLE, 6.0f, aRadius, lineWidth ); + currentManager->Vertex( 0.0f, outerRadius * 2.0f, layerDepth ); // v2 + + Restore(); +} + + +void OPENGL_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) ) +{ + PostPaint(); +} + + +void OPENGL_GAL::skipMouseEvent( wxMouseEvent& aEvent ) +{ + // Post the mouse event to the event listener registered in constructor, if any + if( mouseListener ) + wxPostEvent( mouseListener, aEvent ); +} + + +void OPENGL_GAL::blitCursor() +{ + if( !isCursorEnabled ) + return; + + compositor.SetBuffer( OPENGL_COMPOSITOR::DIRECT_RENDERING ); + + VECTOR2D cursorBegin = cursorPosition - cursorSize / ( 2 * worldScale ); + VECTOR2D cursorEnd = cursorPosition + cursorSize / ( 2 * worldScale ); + VECTOR2D cursorCenter = ( cursorBegin + cursorEnd ) / 2; + + glDisable( GL_TEXTURE_2D ); + glLineWidth( 1.0 ); + glColor4d( cursorColor.r, cursorColor.g, cursorColor.b, cursorColor.a ); + + glBegin( GL_LINES ); + glVertex2d( cursorCenter.x, cursorBegin.y ); + glVertex2d( cursorCenter.x, cursorEnd.y ); + + glVertex2d( cursorBegin.x, cursorCenter.y ); + glVertex2d( cursorEnd.x, cursorCenter.y ); + glEnd(); +} + + +unsigned int OPENGL_GAL::getNewGroupNumber() +{ + wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(), + wxT( "There are no free slots to store a group" ) ); + + while( groups.find( groupCounter ) != groups.end() ) + { + groupCounter++; + } + + return groupCounter++; +} + + +bool OPENGL_GAL::runTest() +{ + wxDialog dlgtest( GetParent(), -1, wxT( "opengl test" ), wxPoint( 50, 50 ), + wxDLG_UNIT( GetParent(), wxSize( 50, 50 ) ) ); + OPENGL_TEST* test = new OPENGL_TEST( &dlgtest, this ); + + dlgtest.Raise(); // on Linux, on some windows managers (Unity for instance) this is needed to actually show the dialog + dlgtest.ShowModal(); + bool result = test->IsOk(); + + if( !result ) + throw std::runtime_error( test->GetError() ); + + return result; +} + + +OPENGL_GAL::OPENGL_TEST::OPENGL_TEST( wxDialog* aParent, OPENGL_GAL* aGal ) : + wxGLCanvas( aParent, wxID_ANY, glAttributes, wxDefaultPosition, + wxDefaultSize, 0, wxT( "GLCanvas" ) ), + m_parent( aParent ), m_gal( aGal ), m_tested( false ), m_result( false ) +{ + m_timeoutTimer.SetOwner( this ); + Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) ); + Connect( wxEVT_TIMER, wxTimerEventHandler( OPENGL_GAL::OPENGL_TEST::OnTimeout ) ); + m_parent->Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::OnDialogPaint ), NULL, this ); +} + + +void OPENGL_GAL::OPENGL_TEST::Render( wxPaintEvent& WXUNUSED( aEvent ) ) +{ + if( !m_tested ) + { + if( !IsShownOnScreen() ) + return; + + m_timeoutTimer.Stop(); + m_result = true; // Assume everything is fine, until proven otherwise + + // One test is enough - close the testing dialog when the test is finished + Disconnect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) ); + CallAfter( boost::bind( &wxDialog::EndModal, m_parent, wxID_NONE ) ); + + SetCurrent( *OPENGL_GAL::glContext ); + GLenum err = glewInit(); + + if( GLEW_OK != err ) + { + error( (const char*) glewGetErrorString( err ) ); + return; + } + else + { + wxLogDebug( wxString( wxT( "Status: Using GLEW " ) ) + + FROM_UTF8( (char*) glewGetString( GLEW_VERSION ) ) ); + } + + // Check the OpenGL version (minimum 2.1 is required) + if( GLEW_VERSION_2_1 ) + { + wxLogInfo( wxT( "OpenGL 2.1 supported." ) ); + } + else + { + error( "OpenGL 2.1 or higher is required!" ); + return; + } + + // Framebuffers have to be supported + if( !GLEW_EXT_framebuffer_object ) + { + error( "Framebuffer objects are not supported!" ); + return; + } + + // Vertex buffer has to be supported + if( !GLEW_ARB_vertex_buffer_object ) + { + error( "Vertex buffer objects are not supported!" ); + return; + } + + // Prepare shaders + if( !m_gal->shader.LoadBuiltinShader( 0, SHADER_TYPE_VERTEX ) ) + { + error( "Cannot compile vertex shader!" ); + return; + } + + if( !m_gal->shader.LoadBuiltinShader( 1, SHADER_TYPE_FRAGMENT ) ) + { + error( "Cannot compile fragment shader!" ); + return; + } + + if( !m_gal->shader.Link() ) + { + error( "Cannot link the shaders!" ); + return; + } + + m_tested = true; + } +} + + +void OPENGL_GAL::OPENGL_TEST::OnTimeout( wxTimerEvent& aEvent ) +{ + error( "Could not create OpenGL canvas" ); + m_parent->EndModal( wxID_NONE ); +} + + +void OPENGL_GAL::OPENGL_TEST::OnDialogPaint( wxPaintEvent& aEvent ) +{ + // GL canvas may never appear on the screen (e.g. due to missing GL extensions), and the test + // will not be run. Therefore give at most a second to perform the test, otherwise we conclude + // it has failed. + // Also, wxWidgets OnShow event is triggered before a window is shown, therefore here we use + // OnPaint event, which is executed when a window is actually visible. + m_timeoutTimer.StartOnce( 1000 ); +} + + +void OPENGL_GAL::OPENGL_TEST::error( const std::string& aError ) +{ + m_timeoutTimer.Stop(); + m_result = false; + m_tested = true; + m_error = aError; +} + +// ------------------------------------- // Callback functions for the tesselator // ------------------------------------- // Compare Redbook Chapter 11 +void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData ) +{ + GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr ); + OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData ); + VERTEX_MANAGER* vboManager = param->vboManager; + + if( vboManager ) + vboManager->Vertex( vertex[0], vertex[1], vertex[2] ); +} + + +void CALLBACK CombineCallback( GLdouble coords[3], + GLdouble* vertex_data[4], + GLfloat weight[4], GLdouble** dataOut, void* aData ) +{ + GLdouble* vertex = new GLdouble[3]; + OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData ); + + // Save the pointer so we can delete it later + param->intersectPoints.push_back( boost::shared_array<GLdouble>( vertex ) ); + + memcpy( vertex, coords, 3 * sizeof(GLdouble) ); + + *dataOut = vertex; +} + + +void CALLBACK EdgeCallback( GLboolean aEdgeFlag ) +{ + // This callback is needed to force GLU tesselator to use triangles only +} + + +void CALLBACK ErrorCallback( GLenum aErrorCode ) +{ + //throw std::runtime_error( std::string( "Tessellation error: " ) + + //std::string( (const char*) gluErrorString( aErrorCode ) ); +} + + +static void InitTesselatorCallbacks( GLUtesselator* aTesselator ) +{ + gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, ( void (CALLBACK*)() )VertexCallback ); + gluTessCallback( aTesselator, GLU_TESS_COMBINE_DATA, ( void (CALLBACK*)() )CombineCallback ); + gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, ( void (CALLBACK*)() )EdgeCallback ); + gluTessCallback( aTesselator, GLU_TESS_ERROR, ( void (CALLBACK*)() )ErrorCallback ); +} diff --git a/common/gal/opengl/shader.cpp b/common/gal/opengl/shader.cpp new file mode 100644 index 0000000..228d53d --- /dev/null +++ b/common/gal/opengl/shader.cpp @@ -0,0 +1,272 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de + * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors. + * + * Graphics Abstraction Layer (GAL) for OpenGL + * + * Shader class + * + * 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 + */ + +#include <iostream> +#include <fstream> +#include <stdexcept> + +#include <cstring> +#include <cassert> + +#include <gal/opengl/shader.h> +#include "shader_src.h" + +using namespace KIGFX; + +SHADER::SHADER() : + isProgramCreated( false ), + isShaderLinked( false ), + active( false ), + maximumVertices( 4 ), + geomInputType( GL_LINES ), + geomOutputType( GL_LINES ) + +{ + // Do not have uninitialized members: + programNumber = 0; +} + + +SHADER::~SHADER() +{ + if( isProgramCreated ) + { + // Delete the shaders and the program + for( std::deque<GLuint>::iterator it = shaderNumbers.begin(); it != shaderNumbers.end(); + ++it ) + { + glDeleteShader( *it ); + } + + glDeleteProgram( programNumber ); + } +} + + +bool SHADER::LoadBuiltinShader( unsigned int aShaderNumber, SHADER_TYPE aShaderType ) +{ + if( aShaderNumber >= shaders_number ) + return false; + + return addSource( std::string( shaders_src[aShaderNumber] ), aShaderType ); +} + + +bool SHADER::LoadShaderFromFile( const std::string& aShaderSourceName, SHADER_TYPE aShaderType ) +{ + // Load shader sources + const std::string shaderSource = readSource( aShaderSourceName ); + + return addSource( shaderSource, aShaderType ); +} + + +void SHADER::ConfigureGeometryShader( GLuint maxVertices, GLuint geometryInputType, + GLuint geometryOutputType ) +{ + maximumVertices = maxVertices; + geomInputType = geometryInputType; + geomOutputType = geometryOutputType; +} + + +bool SHADER::Link() +{ + // Shader linking + glLinkProgram( programNumber ); + programInfo( programNumber ); + + // Check the Link state + glGetObjectParameterivARB( programNumber, GL_OBJECT_LINK_STATUS_ARB, + (GLint*) &isShaderLinked ); + +#ifdef DEBUG + if( !isShaderLinked ) + { + int maxLength; + glGetProgramiv( programNumber, GL_INFO_LOG_LENGTH, &maxLength ); + maxLength = maxLength + 1; + char* linkInfoLog = new char[maxLength]; + glGetProgramInfoLog( programNumber, maxLength, &maxLength, linkInfoLog ); + std::cerr << "Shader linking error:" << std::endl; + std::cerr << linkInfoLog; + delete[] linkInfoLog; + } +#endif /* DEBUG */ + + return isShaderLinked; +} + + +int SHADER::AddParameter( const std::string& aParameterName ) +{ + GLint location = glGetUniformLocation( programNumber, aParameterName.c_str() ); + + if( location != -1 ) + parameterLocation.push_back( location ); + + return location; +} + + +void SHADER::SetParameter( int parameterNumber, float value ) const +{ + glUniform1f( parameterLocation[parameterNumber], value ); +} + + +void SHADER::SetParameter( int parameterNumber, int value ) const +{ + glUniform1i( parameterLocation[parameterNumber], value ); +} + + +int SHADER::GetAttribute( std::string aAttributeName ) const +{ + return glGetAttribLocation( programNumber, aAttributeName.c_str() ); +} + + +void SHADER::programInfo( GLuint aProgram ) +{ + GLint glInfoLogLength = 0; + GLint writtenChars = 0; + + // Get the length of the info string + glGetProgramiv( aProgram, GL_INFO_LOG_LENGTH, &glInfoLogLength ); + + // Print the information + if( glInfoLogLength > 2 ) + { + GLchar* glInfoLog = new GLchar[glInfoLogLength]; + glGetProgramInfoLog( aProgram, glInfoLogLength, &writtenChars, glInfoLog ); + + std::cerr << glInfoLog << std::endl; + + delete[] glInfoLog; + } +} + + +void SHADER::shaderInfo( GLuint aShader ) +{ + GLint glInfoLogLength = 0; + GLint writtenChars = 0; + + // Get the length of the info string + glGetShaderiv( aShader, GL_INFO_LOG_LENGTH, &glInfoLogLength ); + + // Print the information + if( glInfoLogLength > 2 ) + { + GLchar* glInfoLog = new GLchar[glInfoLogLength]; + glGetShaderInfoLog( aShader, glInfoLogLength, &writtenChars, glInfoLog ); + + std::cerr << glInfoLog << std::endl; + + delete[] glInfoLog; + } +} + + +std::string SHADER::readSource( std::string aShaderSourceName ) +{ + // Open the shader source for reading + std::ifstream inputFile( aShaderSourceName.c_str(), std::ifstream::in ); + std::string shaderSource; + + if( !inputFile ) + throw std::runtime_error( "Can't read the shader source: " + aShaderSourceName ); + + std::string shaderSourceLine; + + // Read all lines from the text file + while( getline( inputFile, shaderSourceLine ) ) + { + shaderSource += shaderSourceLine; + shaderSource += "\n"; + } + + return shaderSource; +} + + +bool SHADER::addSource( const std::string& aShaderSource, SHADER_TYPE aShaderType ) +{ + assert( !isShaderLinked ); + + // Create the program + if( !isProgramCreated ) + { + programNumber = glCreateProgram(); + isProgramCreated = true; + } + + // Create a shader + GLuint shaderNumber = glCreateShader( aShaderType ); + shaderNumbers.push_back( shaderNumber ); + + // Get the program info + programInfo( programNumber ); + + // Copy to char array + char* source = new char[aShaderSource.size() + 1]; + strncpy( source, aShaderSource.c_str(), aShaderSource.size() + 1 ); + const char** source_ = (const char**) ( &source ); + + // Attach the source + glShaderSource( shaderNumber, 1, source_, NULL ); + programInfo( programNumber ); + + // Delete the allocated char array + delete[] source; + + // Compile and attach shader to the program + glCompileShader( shaderNumber ); + GLint status; + glGetShaderiv( shaderNumber, GL_COMPILE_STATUS, &status ); + + if( status != GL_TRUE ) + { + shaderInfo( shaderNumber ); + throw std::runtime_error( "Shader compilation error" ); + } + + glAttachShader( programNumber, shaderNumber ); + programInfo( programNumber ); + + // Special handling for the geometry shader + if( aShaderType == SHADER_TYPE_GEOMETRY ) + { + glProgramParameteriEXT( programNumber, GL_GEOMETRY_VERTICES_OUT_EXT, maximumVertices ); + glProgramParameteriEXT( programNumber, GL_GEOMETRY_INPUT_TYPE_EXT, geomInputType ); + glProgramParameteriEXT( programNumber, GL_GEOMETRY_OUTPUT_TYPE_EXT, geomOutputType ); + } + + return true; +} diff --git a/common/gal/opengl/shader.frag b/common/gal/opengl/shader.frag new file mode 100644 index 0000000..7d7d96c --- /dev/null +++ b/common/gal/opengl/shader.frag @@ -0,0 +1,75 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * Fragment shader + * + * 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 + */ + +#version 120 + +// Shader types +const float SHADER_LINE = 1.0; +const float SHADER_FILLED_CIRCLE = 2.0; +const float SHADER_STROKED_CIRCLE = 3.0; + +varying vec4 shaderParams; +varying vec2 circleCoords; + +void filledCircle( vec2 aCoord ) +{ + if( dot( aCoord, aCoord ) < 1.0 ) + gl_FragColor = gl_Color; + else + discard; +} + + +void strokedCircle( vec2 aCoord, float aRadius, float aWidth ) +{ + float outerRadius = aRadius + ( aWidth / 2 ); + float innerRadius = aRadius - ( aWidth / 2 ); + float relWidth = innerRadius / outerRadius; + + if( ( dot( aCoord, aCoord ) < 1.0 ) && + ( dot( aCoord, aCoord ) > relWidth * relWidth ) ) + gl_FragColor = gl_Color; + else + discard; +} + + +void main() +{ + if( shaderParams[0] == SHADER_FILLED_CIRCLE ) + { + filledCircle( circleCoords ); + } + else if( shaderParams[0] == SHADER_STROKED_CIRCLE ) + { + strokedCircle( circleCoords, shaderParams[2], shaderParams[3] ); + } + else + { + // Simple pass-through + gl_FragColor = gl_Color; + } +} diff --git a/common/gal/opengl/shader.vert b/common/gal/opengl/shader.vert new file mode 100644 index 0000000..24521a4 --- /dev/null +++ b/common/gal/opengl/shader.vert @@ -0,0 +1,95 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * Vertex shader + * + * 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 + */ + +#version 120 + +// Shader types +const float SHADER_LINE = 1.0; +const float SHADER_FILLED_CIRCLE = 2.0; +const float SHADER_STROKED_CIRCLE = 3.0; + +// Minimum line width +const float MIN_WIDTH = 1.0; + +attribute vec4 attrShaderParams; +varying vec4 shaderParams; +varying vec2 circleCoords; + +void main() +{ + // Pass attributes to the fragment shader + shaderParams = attrShaderParams; + + if( shaderParams[0] == SHADER_LINE ) + { + float lineWidth = shaderParams[3]; + float worldScale = gl_ModelViewMatrix[0][0]; + + // Make lines appear to be at least 1 pixel wide + if( worldScale * lineWidth < MIN_WIDTH ) + gl_Position = gl_ModelViewProjectionMatrix * + ( gl_Vertex + vec4( shaderParams.yz * MIN_WIDTH / ( worldScale * lineWidth ), 0.0, 0.0 ) ); + else + gl_Position = gl_ModelViewProjectionMatrix * + ( gl_Vertex + vec4( shaderParams.yz, 0.0, 0.0 ) ); + } + else if( ( shaderParams[0] == SHADER_STROKED_CIRCLE ) || + ( shaderParams[0] == SHADER_FILLED_CIRCLE ) ) + { + // Compute relative circle coordinates basing on indices + // Circle + if( shaderParams[1] == 1.0 ) + circleCoords = vec2( -sqrt( 3.0 ), -1.0 ); + else if( shaderParams[1] == 2.0 ) + circleCoords = vec2( sqrt( 3.0 ), -1.0 ); + else if( shaderParams[1] == 3.0 ) + circleCoords = vec2( 0.0, 2.0 ); + + // Semicircle + else if( shaderParams[1] == 4.0 ) + circleCoords = vec2( -3.0 / sqrt( 3.0 ), 0.0 ); + else if( shaderParams[1] == 5.0 ) + circleCoords = vec2( 3.0 / sqrt( 3.0 ), 0.0 ); + else if( shaderParams[1] == 6.0 ) + circleCoords = vec2( 0.0, 2.0 ); + + // Make the line appear to be at least 1 pixel wide + float lineWidth = shaderParams[3]; + float worldScale = gl_ModelViewMatrix[0][0]; + + if( worldScale * lineWidth < MIN_WIDTH ) + shaderParams[3] = shaderParams[3] / ( worldScale * lineWidth ); + + gl_Position = ftransform(); + } + else + { + // Pass through the coordinates like in the fixed pipeline + gl_Position = ftransform(); + } + + gl_FrontColor = gl_Color; +} diff --git a/common/gal/opengl/vertex_container.cpp b/common/gal/opengl/vertex_container.cpp new file mode 100644 index 0000000..fa41ecb --- /dev/null +++ b/common/gal/opengl/vertex_container.cpp @@ -0,0 +1,60 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 vertex_container.cpp + * @brief Class to store vertices and handle transfers between system memory and GPU memory. + */ + +#include <gal/opengl/vertex_container.h> +#include <gal/opengl/cached_container.h> +#include <gal/opengl/noncached_container.h> +#include <gal/opengl/shader.h> +#include <cstdlib> +#include <cstring> + +using namespace KIGFX; + +VERTEX_CONTAINER* VERTEX_CONTAINER::MakeContainer( bool aCached ) +{ + if( aCached ) + return new CACHED_CONTAINER; + else + return new NONCACHED_CONTAINER; +} + + +VERTEX_CONTAINER::VERTEX_CONTAINER( unsigned int aSize ) : + m_freeSpace( aSize ), m_currentSize( aSize ), m_initialSize( aSize ), + m_failed( false ), m_dirty( true ) +{ + m_vertices = static_cast<VERTEX*>( malloc( aSize * sizeof( VERTEX ) ) ); + memset( m_vertices, 0x00, aSize * sizeof( VERTEX ) ); +} + + +VERTEX_CONTAINER::~VERTEX_CONTAINER() +{ + free( m_vertices ); +} diff --git a/common/gal/opengl/vertex_item.cpp b/common/gal/opengl/vertex_item.cpp new file mode 100644 index 0000000..a6fd352 --- /dev/null +++ b/common/gal/opengl/vertex_item.cpp @@ -0,0 +1,53 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 vertex_item.cpp + * @brief Class to handle an item held in a container. + */ + +#include <gal/opengl/vertex_item.h> +#include <gal/opengl/vertex_manager.h> +#include <cstring> + +using namespace KIGFX; + +VERTEX_ITEM::VERTEX_ITEM( const VERTEX_MANAGER& aManager ) : + m_manager( aManager ), m_offset( 0 ), m_size( 0 ) +{ + // As the item is created, we are going to modify it, so call to SetItem() is needed + m_manager.SetItem( *this ); +} + + +VERTEX_ITEM::~VERTEX_ITEM() +{ + m_manager.FreeItem( *this ); +} + + +VERTEX* VERTEX_ITEM::GetVertices() const +{ + return m_manager.GetVertices( *this ); +} diff --git a/common/gal/opengl/vertex_manager.cpp b/common/gal/opengl/vertex_manager.cpp new file mode 100644 index 0000000..d055b52 --- /dev/null +++ b/common/gal/opengl/vertex_manager.cpp @@ -0,0 +1,234 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski <maciej.suminski@cern.ch> + * + * 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 vertex_manager.cpp + * @brief Class to control vertex container and GPU with possibility of emulating old-style OpenGL + * 1.0 state machine using modern OpenGL methods. + */ + +#include <gal/opengl/vertex_manager.h> +#include <gal/opengl/cached_container.h> +#include <gal/opengl/noncached_container.h> +#include <gal/opengl/gpu_manager.h> +#include <gal/opengl/vertex_item.h> +#include <confirm.h> + +using namespace KIGFX; + +VERTEX_MANAGER::VERTEX_MANAGER( bool aCached ) : + m_noTransform( true ), m_transform( 1.0f ) +{ + m_container.reset( VERTEX_CONTAINER::MakeContainer( aCached ) ); + m_gpu.reset( GPU_MANAGER::MakeManager( m_container.get() ) ); + + // There is no shader used by default + for( unsigned int i = 0; i < ShaderStride; ++i ) + m_shader[i] = 0.0f; +} + + +void VERTEX_MANAGER::Vertex( GLfloat aX, GLfloat aY, GLfloat aZ ) const +{ + // flag to avoid hanging by calling DisplayError too many times: + static bool show_err = true; + + // Obtain the pointer to the vertex in the currently used container + VERTEX* newVertex = m_container->Allocate( 1 ); + + if( newVertex == NULL ) + { + if( show_err ) + { + DisplayError( NULL, wxT( "VERTEX_MANAGER::Vertex: Vertex allocation error" ) ); + show_err = false; + } + + return; + } + + putVertex( *newVertex, aX, aY, aZ ); +} + + +void VERTEX_MANAGER::Vertices( const VERTEX aVertices[], unsigned int aSize ) const +{ + // flag to avoid hanging by calling DisplayError too many times: + static bool show_err = true; + + // Obtain pointer to the vertex in currently used container + VERTEX* newVertex = m_container->Allocate( aSize ); + + if( newVertex == NULL ) + { + if( show_err ) + { + DisplayError( NULL, wxT( "VERTEX_MANAGER::Vertices: Vertex allocation error" ) ); + show_err = false; + } + + return; + } + + // Put vertices in already allocated memory chunk + for( unsigned int i = 0; i < aSize; ++i ) + { + putVertex( newVertex[i], aVertices[i].x, aVertices[i].y, aVertices[i].z ); + } +} + + +void VERTEX_MANAGER::SetItem( VERTEX_ITEM& aItem ) const +{ + m_container->SetItem( &aItem ); +} + + +void VERTEX_MANAGER::FinishItem() const +{ + m_container->FinishItem(); +} + + +void VERTEX_MANAGER::FreeItem( VERTEX_ITEM& aItem ) const +{ + m_container->Delete( &aItem ); +} + + +void VERTEX_MANAGER::ChangeItemColor( const VERTEX_ITEM& aItem, const COLOR4D& aColor ) const +{ + unsigned int size = aItem.GetSize(); + unsigned int offset = aItem.GetOffset(); + + VERTEX* vertex = m_container->GetVertices( offset ); + + for( unsigned int i = 0; i < size; ++i ) + { + vertex->r = aColor.r * 255.0; + vertex->g = aColor.g * 255.0; + vertex->b = aColor.b * 255.0; + vertex->a = aColor.a * 255.0; + vertex++; + } + + m_container->SetDirty(); +} + + +void VERTEX_MANAGER::ChangeItemDepth( const VERTEX_ITEM& aItem, GLfloat aDepth ) const +{ + unsigned int size = aItem.GetSize(); + unsigned int offset = aItem.GetOffset(); + + VERTEX* vertex = m_container->GetVertices( offset ); + + for( unsigned int i = 0; i < size; ++i ) + { + vertex->z = aDepth; + vertex++; + } + + m_container->SetDirty(); +} + + +VERTEX* VERTEX_MANAGER::GetVertices( const VERTEX_ITEM& aItem ) const +{ + if( aItem.GetSize() == 0 ) + return NULL; // The item is not stored in the container + + return m_container->GetVertices( aItem.GetOffset() ); +} + + +void VERTEX_MANAGER::SetShader( SHADER& aShader ) const +{ + m_gpu->SetShader( aShader ); +} + + +void VERTEX_MANAGER::Clear() const +{ + m_container->Clear(); +} + + +void VERTEX_MANAGER::BeginDrawing() const +{ + m_gpu->BeginDrawing(); +} + + +void VERTEX_MANAGER::DrawItem( const VERTEX_ITEM& aItem ) const +{ + int size = aItem.GetSize(); + + if( size > 0 ) + { + int offset = aItem.GetOffset(); + m_gpu->DrawIndices( offset, size ); + } +} + + +void VERTEX_MANAGER::EndDrawing() const +{ + m_gpu->EndDrawing(); +} + + +void VERTEX_MANAGER::putVertex( VERTEX& aTarget, GLfloat aX, GLfloat aY, GLfloat aZ ) const +{ + // Modify the vertex according to the currently used transformations + if( m_noTransform ) + { + // Simply copy coordinates, when the transform matrix is the identity matrix + aTarget.x = aX; + aTarget.y = aY; + aTarget.z = aZ; + } + else + { + // Apply transformations + glm::vec4 transVertex( aX, aY, aZ, 1.0f ); + transVertex = m_transform * transVertex; + + aTarget.x = transVertex.x; + aTarget.y = transVertex.y; + aTarget.z = transVertex.z; + } + + // Apply currently used color + aTarget.r = m_color[0]; + aTarget.g = m_color[1]; + aTarget.b = m_color[2]; + aTarget.a = m_color[3]; + + // Apply currently used shader + for( unsigned int j = 0; j < ShaderStride; ++j ) + { + aTarget.shader[j] = m_shader[j]; + } +} |