Commit cd517f67 authored by Maciej Suminski's avatar Maciej Suminski

Added VBO_CONTAINER as a faster storage for vertices (OPENGL_GAL), tuned for...

Added VBO_CONTAINER as a faster storage for vertices (OPENGL_GAL), tuned for exchanging data with GPU.

Removed a few unnecessary variables and fields from OPENGL_GAL.
Added function GAL::ClearCache() for freeing memory used by cached items.
Fixed a few memory leaks (tesselator, PAINTER's settings & VIEW_ITEM's groups).
Changed a few functions into inlines.
parent 876bf75d
...@@ -21,6 +21,7 @@ set(GAL_SRCS ...@@ -21,6 +21,7 @@ set(GAL_SRCS
gal/opengl/opengl_gal.cpp gal/opengl/opengl_gal.cpp
gal/opengl/shader.cpp gal/opengl/shader.cpp
gal/opengl/vbo_item.cpp gal/opengl/vbo_item.cpp
gal/opengl/vbo_container.cpp
gal/cairo/cairo_gal.cpp gal/cairo/cairo_gal.cpp
view/wx_view_controls.cpp view/wx_view_controls.cpp
) )
......
...@@ -99,10 +99,7 @@ CAIRO_GAL::~CAIRO_GAL() ...@@ -99,10 +99,7 @@ CAIRO_GAL::~CAIRO_GAL()
delete cursorPixels; delete cursorPixels;
delete cursorPixelsSaved; delete cursorPixelsSaved;
for( int i = groups.size() - 1; i >= 0; --i ) ClearCache();
{
DeleteGroup( i );
}
deleteBitmaps(); deleteBitmaps();
} }
...@@ -687,13 +684,22 @@ void CAIRO_GAL::EndGroup() ...@@ -687,13 +684,22 @@ void CAIRO_GAL::EndGroup()
} }
void CAIRO_GAL::ClearCache()
{
for( int i = groups.size() - 1; i >= 0; --i )
{
DeleteGroup( i );
}
}
void CAIRO_GAL::DeleteGroup( int aGroupNumber ) void CAIRO_GAL::DeleteGroup( int aGroupNumber )
{ {
storePath(); storePath();
// Delete the Cairo paths // Delete the Cairo paths
for( std::deque<GroupElement>::iterator it = groups[aGroupNumber].begin(), end = groups[aGroupNumber].end(); std::deque<GroupElement>::iterator it, end;
it != end; ++it ) for( it = groups[aGroupNumber].begin(), end = groups[aGroupNumber].end(); it != end; ++it )
{ {
if( it->command == CMD_FILL_PATH || it->command == CMD_STROKE_PATH ) if( it->command == CMD_FILL_PATH || it->command == CMD_STROKE_PATH )
{ {
......
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
#include <cmath> #include <cmath>
#include <gal/opengl/opengl_gal.h> #include <gal/opengl/opengl_gal.h>
#include <gal/opengl/vbo_container.h>
#include <gal/definitions.h> #include <gal/definitions.h>
#include <gal/opengl/glm/gtc/matrix_transform.hpp>
#include <wx/log.h> #include <wx/log.h>
#include <macros.h> #include <macros.h>
...@@ -78,7 +78,6 @@ OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener, ...@@ -78,7 +78,6 @@ OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
isVboInitialized = false; isVboInitialized = false;
vboNeedsUpdate = false; vboNeedsUpdate = false;
curVboItem = NULL; curVboItem = NULL;
vboSize = 0;
transform = glm::mat4( 1.0f ); // Identity matrix transform = glm::mat4( 1.0f ); // Identity matrix
SetSize( parentSize ); SetSize( parentSize );
...@@ -107,12 +106,26 @@ OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener, ...@@ -107,12 +106,26 @@ OPENGL_GAL::OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) ); Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( OPENGL_GAL::skipMouseEvent ) );
#endif #endif
vboContainer = new VBO_CONTAINER;
// Tesselator initialization
tesselator = gluNewTess();
InitTesselatorCallbacks( tesselator );
gluTessProperty( tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
if( !isUseShader ) if( !isUseShader )
{ {
// (3 vertices per triangle) * (2 items [circle&semicircle]) * (number of points per item)
precomputedContainer = new VBO_CONTAINER( 3 * 2 * CIRCLE_POINTS );
// Compute the unit circles, used for speed up of the circle drawing // Compute the unit circles, used for speed up of the circle drawing
verticesCircle = new VBO_ITEM( precomputedContainer );
computeUnitCircle(); computeUnitCircle();
verticesCircle->Finish();
verticesSemiCircle = new VBO_ITEM( precomputedContainer );
computeUnitSemiCircle(); computeUnitSemiCircle();
//computeUnitArcs(); // TODO remove? it is not used anywhere verticesSemiCircle->Finish();
} }
} }
...@@ -121,6 +134,13 @@ OPENGL_GAL::~OPENGL_GAL() ...@@ -121,6 +134,13 @@ OPENGL_GAL::~OPENGL_GAL()
{ {
glFlush(); glFlush();
if( !isUseShader )
{
delete verticesCircle;
delete verticesSemiCircle;
delete precomputedContainer;
}
// Delete the buffers // Delete the buffers
if( isFrameBufferInitialized ) if( isFrameBufferInitialized )
{ {
...@@ -128,16 +148,13 @@ OPENGL_GAL::~OPENGL_GAL() ...@@ -128,16 +148,13 @@ OPENGL_GAL::~OPENGL_GAL()
deleteFrameBuffer( &frameBufferBackup, &depthBufferBackup, &textureBackup ); deleteFrameBuffer( &frameBufferBackup, &depthBufferBackup, &textureBackup );
} }
if( isVboInitialized ) gluDeleteTess( tesselator );
{
std::deque<VBO_ITEM*>::iterator it, end;
for( it = vboItems.begin(), end = vboItems.end(); it != end; it++ ) if( isVboInitialized )
{ {
delete *it; ClearCache();
}
deleteVertexBufferObjects(); deleteVertexBufferObjects();
delete vboContainer;
} }
delete glContext; delete glContext;
...@@ -241,8 +258,8 @@ void OPENGL_GAL::initFrameBuffers() ...@@ -241,8 +258,8 @@ void OPENGL_GAL::initFrameBuffers()
void OPENGL_GAL::initVertexBufferObjects() void OPENGL_GAL::initVertexBufferObjects()
{ {
// Generate buffers for vertices and indices // Generate buffers for vertices and indices
glGenBuffers( 1, &curVboVertId ); glGenBuffers( 1, &vboVertices );
glGenBuffers( 1, &curVboIndId ); glGenBuffers( 1, &vboIndices );
isVboInitialized = true; isVboInitialized = true;
} }
...@@ -253,8 +270,8 @@ void OPENGL_GAL::deleteVertexBufferObjects() ...@@ -253,8 +270,8 @@ void OPENGL_GAL::deleteVertexBufferObjects()
glBindBuffer( GL_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
glDeleteBuffers( 1, &curVboVertId ); glDeleteBuffers( 1, &vboVertices );
glDeleteBuffers( 1, &curVboIndId ); glDeleteBuffers( 1, &vboIndices );
isVboInitialized = false; isVboInitialized = false;
} }
...@@ -348,7 +365,6 @@ void OPENGL_GAL::BeginDrawing() ...@@ -348,7 +365,6 @@ void OPENGL_GAL::BeginDrawing()
// Compile the shaders // Compile the shaders
if( !isShaderInitialized && isUseShader ) if( !isShaderInitialized && isUseShader )
{ {
shader.ConfigureGeometryShader( 3, GL_TRIANGLES, GL_TRIANGLES );
shader.AddSource( shaderPath + std::string( "/shader.vert" ), SHADER_TYPE_VERTEX ); shader.AddSource( shaderPath + std::string( "/shader.vert" ), SHADER_TYPE_VERTEX );
shader.AddSource( shaderPath + std::string( "/shader.frag" ), SHADER_TYPE_FRAGMENT ); shader.AddSource( shaderPath + std::string( "/shader.frag" ), SHADER_TYPE_FRAGMENT );
if( !shader.Link() ) if( !shader.Link() )
...@@ -417,9 +433,10 @@ void OPENGL_GAL::BeginDrawing() ...@@ -417,9 +433,10 @@ void OPENGL_GAL::BeginDrawing()
// Number of vertices to be drawn // Number of vertices to be drawn
indicesSize = 0; indicesSize = 0;
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, curVboIndId ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndices );
// Discard old buffer, so we can use it again // Discard old buffer, so we can use it again
glBufferData( GL_ELEMENT_ARRAY_BUFFER, vboSize * VBO_ITEM::IndByteSize, NULL, GL_STREAM_DRAW ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, vboContainer->GetSize() * VBO_ITEM::IndByteSize,
NULL, GL_STREAM_DRAW );
// Map the GPU memory, so we can store indices that are going to be drawn // Map the GPU memory, so we can store indices that are going to be drawn
indicesPtr = static_cast<GLuint*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) ); indicesPtr = static_cast<GLuint*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
...@@ -493,7 +510,7 @@ void OPENGL_GAL::EndDrawing() ...@@ -493,7 +510,7 @@ void OPENGL_GAL::EndDrawing()
glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_COLOR_ARRAY );
// Bind vertices data buffers // Bind vertices data buffers
glBindBuffer( GL_ARRAY_BUFFER, curVboVertId ); glBindBuffer( GL_ARRAY_BUFFER, vboVertices );
glVertexPointer( VBO_ITEM::CoordStride, GL_FLOAT, VBO_ITEM::VertByteSize, 0 ); glVertexPointer( VBO_ITEM::CoordStride, GL_FLOAT, VBO_ITEM::VertByteSize, 0 );
glColorPointer( VBO_ITEM::ColorStride, GL_FLOAT, VBO_ITEM::VertByteSize, glColorPointer( VBO_ITEM::ColorStride, GL_FLOAT, VBO_ITEM::VertByteSize,
(GLvoid*) VBO_ITEM::ColorByteOffset ); (GLvoid*) VBO_ITEM::ColorByteOffset );
...@@ -544,40 +561,27 @@ void OPENGL_GAL::rebuildVbo() ...@@ -544,40 +561,27 @@ void OPENGL_GAL::rebuildVbo()
prof_start( &totalTime, false ); prof_start( &totalTime, false );
#endif /* __WXDEBUG__ */ #endif /* __WXDEBUG__ */
// Buffer for storing cached items data GLfloat* data = (GLfloat*) vboContainer->GetAllVertices();
GLfloat* verticesBuffer = new GLfloat[VBO_ITEM::VertStride * vboSize];
// Pointer for easier usage with memcpy // Upload vertices coordinates and shader types to GPU memory
GLfloat* verticesBufferPtr = verticesBuffer; glBindBuffer( GL_ARRAY_BUFFER, vboVertices );
glBufferData( GL_ARRAY_BUFFER, vboContainer->GetSize() * VBO_ITEM::VertByteSize,
// Fill out buffers with data data, GL_DYNAMIC_DRAW );
for( std::deque<VBO_ITEM*>::iterator vboItem = vboItems.begin();
vboItem != vboItems.end(); vboItem++ )
{
int size = (*vboItem)->GetSize();
memcpy( verticesBufferPtr, (*vboItem)->GetVertices(), size * VBO_ITEM::VertByteSize );
verticesBufferPtr += size * VBO_ITEM::VertStride;
}
// Upload vertices coordinates, shader types and indices to GPU memory
glBindBuffer( GL_ARRAY_BUFFER, curVboVertId );
glBufferData( GL_ARRAY_BUFFER, vboSize * VBO_ITEM::VertByteSize, verticesBuffer, GL_DYNAMIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ARRAY_BUFFER, 0 );
// Allocate the biggest possible buffer for indices // Allocate the biggest possible buffer for indices
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, curVboIndId ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndices );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, vboSize * VBO_ITEM::IndByteSize, NULL, GL_STREAM_DRAW ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, vboContainer->GetSize() * VBO_ITEM::IndByteSize,
NULL, GL_STREAM_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
delete[] verticesBuffer;
vboNeedsUpdate = false; vboNeedsUpdate = false;
#ifdef __WXDEBUG__ #ifdef __WXDEBUG__
prof_end( &totalTime ); prof_end( &totalTime );
wxLogDebug( wxT( "Rebuilding VBO::%d vertices / %.1f ms" ), wxLogDebug( wxT( "Rebuilding VBO::%d vertices / %.1f ms" ),
vboSize, (double) totalTime.value / 1000.0 ); vboContainer->GetSize(), (double) totalTime.value / 1000.0 );
#endif /* __WXDEBUG__ */ #endif /* __WXDEBUG__ */
} }
...@@ -726,74 +730,6 @@ inline void OPENGL_GAL::drawLineCap( const VECTOR2D& aStartPoint, const VECTOR2D ...@@ -726,74 +730,6 @@ inline void OPENGL_GAL::drawLineCap( const VECTOR2D& aStartPoint, const VECTOR2D
} }
void OPENGL_GAL::begin( GLenum aMode )
{
if( !isGrouping )
glBegin( aMode );
}
void OPENGL_GAL::end()
{
if( !isGrouping )
glEnd();
}
void OPENGL_GAL::vertex3( double aX, double aY, double aZ )
{
if( isGrouping )
{
// New vertex coordinates for VBO
const GLfloat vertex[] = { aX, aY, aZ };
curVboItem->PushVertex( vertex );
}
else
{
glVertex3d( aX, aY, aZ );
}
}
void OPENGL_GAL::translate3( double aX, double aY, double aZ )
{
if( isGrouping )
{
transform = glm::translate( transform, glm::vec3( aX, aY, aZ ) );
}
else
{
glTranslated( aX, aY, aZ );
}
}
void OPENGL_GAL::color4( double aRed, double aGreen, double aBlue, double aAlpha )
{
if( isGrouping )
{
curVboItem->UseColor( COLOR4D( aRed, aGreen, aBlue, aAlpha ) );
}
else
{
glColor4d( aRed, aGreen, aBlue, aAlpha );
}
}
void OPENGL_GAL::color4( const COLOR4D& aColor )
{
if( isGrouping )
{
curVboItem->UseColor( aColor );
}
else
{
glColor4d( aColor.r, aColor.g, aColor.b, aColor.a);
}
}
void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) void OPENGL_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{ {
if( isFillEnabled ) if( isFillEnabled )
...@@ -1206,7 +1142,13 @@ void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) ...@@ -1206,7 +1142,13 @@ void OPENGL_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
if( isGrouping ) if( isGrouping )
{ {
curVboItem->PushVertices( verticesCircle.GetVertices(), verticesCircle.GetSize() ); VBO_VERTEX* circle = new VBO_VERTEX[CIRCLE_POINTS * 3];
memcpy( circle, verticesCircle->GetVertices(),
VBO_ITEM::VertByteSize * CIRCLE_POINTS * 3 );
curVboItem->PushVertices( circle, CIRCLE_POINTS * 3 );
delete[] circle;
} }
else else
{ {
...@@ -1256,7 +1198,13 @@ void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, d ...@@ -1256,7 +1198,13 @@ void OPENGL_GAL::drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, d
if( isGrouping ) if( isGrouping )
{ {
curVboItem->PushVertices( verticesSemiCircle.GetVertices(), verticesSemiCircle.GetSize() ); VBO_VERTEX* semiCircle = new VBO_VERTEX[CIRCLE_POINTS * 3];
memcpy( semiCircle, verticesSemiCircle->GetVertices(),
VBO_ITEM::VertByteSize * CIRCLE_POINTS * 3 );
curVboItem->PushVertices( semiCircle, CIRCLE_POINTS * 3 );
delete[] semiCircle;
} }
else else
{ {
...@@ -1416,32 +1364,19 @@ void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList ) ...@@ -1416,32 +1364,19 @@ void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
setShader( SHADER_NONE ); setShader( SHADER_NONE );
GLUtesselator* tesselator = gluNewTess();
typedef std::vector<OGLPOINT> OGLPOINTS; typedef std::vector<OGLPOINT> OGLPOINTS;
// Do only one heap allocation, can do because we know size in advance. // Do only one heap allocation, can do because we know size in advance.
// std::vector is then fastest // std::vector is then fastest
OGLPOINTS vertexList( aPointList.size(), OGLPOINT( "fastest" ) ); OGLPOINTS vertexList( aPointList.size(), OGLPOINT( "fastest" ) );
InitTesselatorCallbacks( tesselator );
gluTessProperty( tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_POSITIVE );
glNormal3d( 0.0, 0.0, 1.0 ); glNormal3d( 0.0, 0.0, 1.0 );
color4( fillColor.r, fillColor.g, fillColor.b, fillColor.a ); color4( fillColor.r, fillColor.g, fillColor.b, fillColor.a );
glShadeModel( GL_FLAT ); glShadeModel( GL_FLAT );
if( isGrouping )
{ TessParams params = { curVboItem, tessIntersects };
// Store polygon triangles' coordinates to the current VBO item gluTessBeginPolygon( tesselator, &params );
gluTessBeginPolygon( tesselator, curVboItem );
}
else
{
// Display polygons directly
gluTessBeginPolygon( tesselator, NULL );
}
gluTessBeginContour( tesselator ); gluTessBeginContour( tesselator );
// use operator=( const POINTS& ) // use operator=( const POINTS& )
...@@ -1456,7 +1391,13 @@ void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList ) ...@@ -1456,7 +1391,13 @@ void OPENGL_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
gluTessEndContour( tesselator ); gluTessEndContour( tesselator );
gluTessEndPolygon( tesselator ); gluTessEndPolygon( tesselator );
gluDeleteTess( tesselator ); // Free allocated intersecting points
std::vector<GLdouble*>::iterator it, it_end;
for( it = tessIntersects.begin(), it_end = tessIntersects.end(); it < it_end; ++it )
{
delete[] *it;
}
tessIntersects.clear();
// vertexList destroyed here // vertexList destroyed here
} }
...@@ -1639,8 +1580,7 @@ int OPENGL_GAL::BeginGroup() ...@@ -1639,8 +1580,7 @@ int OPENGL_GAL::BeginGroup()
vboNeedsUpdate = true; vboNeedsUpdate = true;
// Save the pointer for caching the current item // Save the pointer for caching the current item
curVboItem = new VBO_ITEM; curVboItem = new VBO_ITEM( vboContainer );
curVboItem->SetOffset( vboSize );
vboItems.push_back( curVboItem ); vboItems.push_back( curVboItem );
return vboItems.size() - 1; return vboItems.size() - 1;
...@@ -1649,27 +1589,30 @@ int OPENGL_GAL::BeginGroup() ...@@ -1649,27 +1589,30 @@ int OPENGL_GAL::BeginGroup()
void OPENGL_GAL::EndGroup() void OPENGL_GAL::EndGroup()
{ {
vboSize += curVboItem->GetSize(); curVboItem->Finish();
curVboItem = NULL; curVboItem = NULL;
isGrouping = false; isGrouping = false;
} }
void OPENGL_GAL::DeleteGroup( int aGroupNumber ) void OPENGL_GAL::ClearCache()
{ {
#ifdef __WXDEBUG__ std::deque<VBO_ITEM*>::iterator it, end;
if( (unsigned) aGroupNumber < vboItems.size() ) for( it = vboItems.begin(), end = vboItems.end(); it != end; it++ )
{ {
wxLogDebug( wxT( "Tried to delete not existing group" ) ); delete *it;
} }
#endif /* __WXDEBUG__ */
std::deque<VBO_ITEM*>::iterator it = vboItems.begin(); vboItems.clear();
std::advance( it, aGroupNumber ); }
// vboSize -= it->GetSize(); // FIXME?
delete *it; void OPENGL_GAL::DeleteGroup( int aGroupNumber )
// vboItems.erase( it ); // makes change to group numbers - that's veeery bad {
VBO_ITEM* item = vboItems[aGroupNumber];
vboItems[aGroupNumber] = NULL;
delete item;
vboNeedsUpdate = true; vboNeedsUpdate = true;
} }
...@@ -1687,26 +1630,6 @@ void OPENGL_GAL::DrawGroup( int aGroupNumber ) ...@@ -1687,26 +1630,6 @@ void OPENGL_GAL::DrawGroup( int aGroupNumber )
} }
// TODO it is not used anywhere
/*void OPENGL_GAL::computeUnitArcs()
{
displayListsArcs = glGenLists( CIRCLE_POINTS + 1 );
// Create an individual display list for each arc in with an angle [0 .. 2pi]
for( int j = 0; j < CIRCLE_POINTS + 1; j++ )
{
glNewList( displayListsArcs + j, GL_COMPILE );
for( int i = 0; i < j; i++ )
{
glVertex2d( cos( 2 * M_PI / CIRCLE_POINTS * i ), sin( 2 * M_PI / CIRCLE_POINTS * i ) );
}
glEndList();
}
}*/
void OPENGL_GAL::computeUnitCircle() void OPENGL_GAL::computeUnitCircle()
{ {
displayListCircle = glGenLists( 1 ); displayListCircle = glGenLists( 1 );
...@@ -1718,30 +1641,28 @@ void OPENGL_GAL::computeUnitCircle() ...@@ -1718,30 +1641,28 @@ void OPENGL_GAL::computeUnitCircle()
// Insert in a display list and a vector // Insert in a display list and a vector
for( int i = 0; i < CIRCLE_POINTS; i++ ) for( int i = 0; i < CIRCLE_POINTS; i++ )
{ {
const GLfloat v0[] = { 0.0f, 0.0f, 0.0f }; VBO_VERTEX v0( 0.0f, 0.0f, 0.0f );
const GLfloat v1[] = VBO_VERTEX v1(
{
cos( 2.0 * M_PI / CIRCLE_POINTS * i ), // x cos( 2.0 * M_PI / CIRCLE_POINTS * i ), // x
sin( 2.0 * M_PI / CIRCLE_POINTS * i ), // y sin( 2.0 * M_PI / CIRCLE_POINTS * i ), // y
0.0f // z 0.0f // z
}; );
const GLfloat v2[] = VBO_VERTEX v2(
{
cos( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // x cos( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // x
sin( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // y sin( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // y
0.0f // z 0.0f // z
}; );
glVertex2d( 0, 0 ); glVertex2d( 0, 0 );
verticesCircle.PushVertex( v0 ); verticesCircle->PushVertex( &v0 );
glVertex2d( v1[0], v1[1] ); glVertex2d( v1.x, v1.y );
verticesCircle.PushVertex( v1 ); verticesCircle->PushVertex( &v1 );
unitCirclePoints.push_back( VECTOR2D( v1[0], v1[1] ) ); // TODO remove unitCirclePoints.push_back( VECTOR2D( v1.x, v1.y ) ); // TODO remove
glVertex2d( v2[0], v2[1] ); glVertex2d( v2.x, v2.y );
verticesCircle.PushVertex( v2 ); verticesCircle->PushVertex( &v2 );
unitCirclePoints.push_back( VECTOR2D( v2[0], v2[1] ) ); // TODO remove unitCirclePoints.push_back( VECTOR2D( v2.x, v2.y ) ); // TODO remove
} }
glEnd(); glEnd();
...@@ -1759,28 +1680,26 @@ void OPENGL_GAL::computeUnitSemiCircle() ...@@ -1759,28 +1680,26 @@ void OPENGL_GAL::computeUnitSemiCircle()
for( int i = 0; i < CIRCLE_POINTS / 2; ++i ) for( int i = 0; i < CIRCLE_POINTS / 2; ++i )
{ {
GLfloat v0[] = { 0.0f, 0.0f, 0.0f }; VBO_VERTEX v0( 0.0f, 0.0f, 0.0f );
GLfloat v1[] = VBO_VERTEX v1(
{
cos( 2.0 * M_PI / CIRCLE_POINTS * i ), // x cos( 2.0 * M_PI / CIRCLE_POINTS * i ), // x
sin( 2.0 * M_PI / CIRCLE_POINTS * i ), // y sin( 2.0 * M_PI / CIRCLE_POINTS * i ), // y
0.0f // z 0.0f // z
}; );
GLfloat v2[] = VBO_VERTEX v2(
{
cos( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // x cos( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // x
sin( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // y sin( 2.0 * M_PI / CIRCLE_POINTS * ( i + 1 ) ), // y
0.0f // z 0.0f // z
}; );
glVertex2d( 0, 0 ); glVertex2d( 0, 0 );
verticesSemiCircle.PushVertex( v0 ); verticesSemiCircle->PushVertex( &v0 );
glVertex2d( v1[0], v1[1] ); glVertex2d( v1.x, v1.y );
verticesSemiCircle.PushVertex( v1 ); verticesSemiCircle->PushVertex( &v1 );
glVertex2d( v2[0], v2[1] ); glVertex2d( v2.x, v2.y );
verticesSemiCircle.PushVertex( v2 ); verticesSemiCircle->PushVertex( &v2 );
} }
glEnd(); glEnd();
...@@ -1825,12 +1744,13 @@ void OPENGL_GAL::ComputeWorldScreenMatrix() ...@@ -1825,12 +1744,13 @@ void OPENGL_GAL::ComputeWorldScreenMatrix()
void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData ) void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
{ {
GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr ); GLdouble* vertex = static_cast<GLdouble*>( aVertexPtr );
OPENGL_GAL::TessParams* param = static_cast<OPENGL_GAL::TessParams*>( aData );
VBO_ITEM* vboItem = param->vboItem;
if( aData ) if( vboItem )
{ {
VBO_ITEM* vboItem = static_cast<VBO_ITEM*>( aData ); VBO_VERTEX newVertex( vertex[0], vertex[1], vertex[2] );
const GLfloat newVertex[] = { vertex[0], vertex[1], vertex[2] }; vboItem->PushVertex( &newVertex );
vboItem->PushVertex( newVertex );
} }
else else
{ {
...@@ -1841,9 +1761,13 @@ void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData ) ...@@ -1841,9 +1761,13 @@ void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
void CALLBACK CombineCallback( GLdouble coords[3], void CALLBACK CombineCallback( GLdouble coords[3],
GLdouble* vertex_data[4], GLdouble* vertex_data[4],
GLfloat weight[4], GLdouble** dataOut ) GLfloat weight[4], GLdouble** dataOut, void* aData )
{ {
GLdouble* vertex = new GLdouble[3]; 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( vertex );
memcpy( vertex, coords, 3 * sizeof(GLdouble) ); memcpy( vertex, coords, 3 * sizeof(GLdouble) );
...@@ -1884,7 +1808,7 @@ void CALLBACK ErrorCallback( GLenum aErrorCode ) ...@@ -1884,7 +1808,7 @@ void CALLBACK ErrorCallback( GLenum aErrorCode )
void InitTesselatorCallbacks( GLUtesselator* aTesselator ) void InitTesselatorCallbacks( GLUtesselator* aTesselator )
{ {
gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, ( void (CALLBACK*)() )VertexCallback ); gluTessCallback( aTesselator, GLU_TESS_VERTEX_DATA, ( void (CALLBACK*)() )VertexCallback );
gluTessCallback( aTesselator, GLU_TESS_COMBINE, ( void (CALLBACK*)() )CombineCallback ); gluTessCallback( aTesselator, GLU_TESS_COMBINE_DATA, ( void (CALLBACK*)() )CombineCallback );
gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, ( void (CALLBACK*)() )EdgeCallback ); gluTessCallback( aTesselator, GLU_TESS_EDGE_FLAG, ( void (CALLBACK*)() )EdgeCallback );
gluTessCallback( aTesselator, GLU_TESS_BEGIN_DATA, ( void (CALLBACK*)() )BeginCallback ); gluTessCallback( aTesselator, GLU_TESS_BEGIN_DATA, ( void (CALLBACK*)() )BeginCallback );
gluTessCallback( aTesselator, GLU_TESS_END_DATA, ( void (CALLBACK*)() )EndCallback ); gluTessCallback( aTesselator, GLU_TESS_END_DATA, ( void (CALLBACK*)() )EndCallback );
......
/*
* 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 vbo_container.cpp
* @brief Class to store VBO_ITEMs.
*/
#include <gal/opengl/vbo_container.h>
#include <gal/opengl/vbo_item.h>
#include <cstring>
#include <wx/log.h>
#ifdef __WXDEBUG__
#include <profile.h>
#endif /* __WXDEBUG__ */
#define CONTAINER_TEST 1
using namespace KiGfx;
VBO_CONTAINER::VBO_CONTAINER( int aSize ) :
m_freeSpace( aSize ), m_currentSize( aSize ), itemStarted( false )
{
m_vertices = new VBO_VERTEX[aSize];
// In the beginning there is only free space
m_freeChunks.insert( Chunk( aSize, 0 ) );
}
VBO_CONTAINER::~VBO_CONTAINER()
{
delete[] m_vertices;
}
void VBO_CONTAINER::StartItem( VBO_ITEM* aVboItem )
{
itemStarted = true;
item = aVboItem;
itemSize = 0;
// Reserve minimal sensible chunk size (at least to store a single triangle)
itemChunkSize = 3;
allocate( aVboItem, itemChunkSize );
}
void VBO_CONTAINER::EndItem()
{
if( itemSize < itemChunkSize )
{
// There is some memory left, so we should return it to the pool
int itemChunkOffset = item->GetOffset();
m_reservedChunks.erase( item );
m_reservedChunks.insert( ReservedChunk( item, Chunk( itemSize, itemChunkOffset ) ) );
m_freeChunks.insert( Chunk( itemChunkSize - itemSize, itemChunkOffset + itemSize ) );
m_freeSpace += ( itemChunkSize - itemSize );
}
itemStarted = false;
}
void VBO_CONTAINER::Add( VBO_ITEM* aVboItem, const VBO_VERTEX* aVertex, unsigned int aSize )
{
unsigned int offset;
if( itemStarted ) // There is an item being created with an unknown size..
{
unsigned int itemChunkOffset;
// ..and unfortunately does not fit into currently reserved chunk
if( itemSize + aSize > itemChunkSize )
{
// Find the previous chunk for the item, save it, so it can be removed later
ReservedChunkMap::iterator it = m_reservedChunks.find( item );
// Reserve bigger memory for the current item
int newSize = ( 2 * itemSize ) + aSize;
itemChunkOffset = allocate( aVboItem, newSize );
// Check if there was no error
if( itemChunkOffset > m_currentSize )
return;
// Save previous chunk's offset for copying data
int oldItemChunkOffset = getChunkOffset( *it );
// Copy all the old data
memcpy( &m_vertices[itemChunkOffset], &m_vertices[oldItemChunkOffset],
itemSize * VBO_ITEM::VertByteSize );
// Return memory used by the previous chunk
free( it );
itemChunkSize = newSize;
}
else
{
itemChunkOffset = item->GetOffset();
}
// Store new vertices in the chunk reserved for the unknown-sized item
offset = itemChunkOffset + itemSize;
itemSize += aSize;
}
else
{
// Add vertices to previously already finished item
wxASSERT_MSG( false, wxT( "Warning: not tested yet" ) );
ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem );
unsigned int chunkSize = getChunkSize( *it );
unsigned int itemSize = aVboItem->GetSize();
if( chunkSize < itemSize + aSize )
{
resizeChunk( aVboItem, itemSize + aSize );
it = m_reservedChunks.find( aVboItem );
}
offset = getChunkOffset( *it ) + itemSize;
}
memcpy( &m_vertices[offset], aVertex, aSize * VBO_ITEM::VertByteSize );
}
VBO_VERTEX* VBO_CONTAINER::GetAllVertices() const
{
return m_vertices;
}
VBO_VERTEX* VBO_CONTAINER::GetVertices( const VBO_ITEM* aVboItem ) const
{
int offset = aVboItem->GetOffset();
return &m_vertices[offset];
}
unsigned int VBO_CONTAINER::allocate( VBO_ITEM* aVboItem, unsigned int aSize )
{
// Is there enough space to store vertices?
if( m_freeSpace < aSize )
{
bool result;
// Would it be enough to double the current space?
if( aSize < m_freeSpace + m_currentSize )
{
// Yes: exponential growing
result = resizeContainer( m_currentSize * 2 );
}
else
{
// No: grow to the nearest bigger power of 2
result = resizeContainer( getPowerOf2( m_currentSize * 2 + aSize ) );
}
// An error has occurred
if( !result )
return UINT_MAX;
}
// Look for the space with at least given size
FreeChunkMap::iterator it = m_freeChunks.lower_bound( aSize );
if( it == m_freeChunks.end() )
{
// This means that there is enough space for
// storing vertices, but the space is not continous
if( !defragment() )
return false;
// We can take the first free chunk, as there is only one after defragmentation
// and we can be sure that it provides enough space to store the object
it = m_freeChunks.begin();
}
unsigned int chunkSize = it->first;
unsigned int chunkOffset = it->second;
m_freeChunks.erase( it );
wxASSERT( chunkSize >= aSize );
// If there is some space left, return it to the pool - add an entry for it
if( chunkSize > aSize )
{
m_freeChunks.insert( Chunk( chunkSize - aSize, chunkOffset + aSize ) );
}
m_freeSpace -= aSize;
m_reservedChunks.insert( ReservedChunk( aVboItem, Chunk( aSize, chunkOffset ) ) );
aVboItem->SetOffset( chunkOffset );
return chunkOffset;
}
void VBO_CONTAINER::free( const ReservedChunkMap::iterator& aChunk )
{
// Remove the chunk from the reserved chunks map and add to the free chunks map
int size = getChunkSize( *aChunk );
int offset = getChunkOffset( *aChunk );
m_reservedChunks.erase( aChunk );
m_freeChunks.insert( Chunk( size, offset ) );
m_freeSpace += size;
}
bool VBO_CONTAINER::defragment( VBO_VERTEX* aTarget )
{
if( m_freeChunks.size() <= 1 )
{
// There is no point in defragmenting, as there is only one or no free chunks
return true;
}
if( aTarget == NULL )
{
// No target was specified, so we have to allocate our own space
aTarget = new (std::nothrow) VBO_VERTEX[m_currentSize];
if( aTarget == NULL )
{
wxLogError( wxT( "Run out of memory" ) );
return false;
}
}
int newOffset = 0;
ReservedChunkMap::iterator it, it_end;
for( it = m_reservedChunks.begin(), it_end = m_reservedChunks.end(); it != it_end; ++it )
{
VBO_ITEM* vboItem = getChunkVboItem( *it );
int itemOffset = getChunkOffset( *it );
int itemSize = getChunkSize( *it );
// Move an item to the new container
memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VBO_ITEM::VertByteSize );
// Update new offset
vboItem->SetOffset( newOffset );
setChunkOffset( *it, newOffset );
// Move to the next free space
newOffset += itemSize;
}
delete[] m_vertices;
m_vertices = aTarget;
// Now there is only one big chunk of free memory
m_freeChunks.clear();
m_freeChunks.insert( Chunk( m_freeSpace, m_currentSize - m_freeSpace ) );
return true;
}
void VBO_CONTAINER::resizeChunk( VBO_ITEM* aVboItem, int aNewSize )
{
wxASSERT_MSG( false, wxT( "Warning: not tested yet" ) );
// TODO ESPECIALLY test the case of shrinking chunk
ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem );
int size = getChunkSize( *it );
int offset = getChunkOffset( *it );
int newOffset = allocate( aVboItem, aNewSize );
memcpy( &m_vertices[newOffset], &m_vertices[offset], size * VBO_ITEM::VertByteSize );
// Remove the chunk from the reserved chunks map and add to the free chunks map
m_reservedChunks.erase( it );
m_freeChunks.insert( Chunk( size, offset ) );
m_freeSpace += size;
}
bool VBO_CONTAINER::resizeContainer( unsigned int aNewSize )
{
unsigned int copySize;
if( aNewSize < m_currentSize )
{
// Sanity check, no shrinking if we cannot fit all the data
if( ( m_currentSize - m_freeSpace ) > aNewSize )
return false;
defragment();
copySize = ( m_currentSize - m_freeSpace );
}
else
{
copySize = m_currentSize;
}
VBO_VERTEX* newContainer = new (std::nothrow) VBO_VERTEX[aNewSize];
if( newContainer == NULL )
{
wxLogError( wxT( "Run out of memory" ) );
return false;
}
memcpy( newContainer, m_vertices, copySize * VBO_ITEM::VertByteSize );
delete[] m_vertices;
m_vertices = newContainer;
// Update variables
unsigned int lastFreeSize = 0;
unsigned int lastFreeOffset = 0;
// Search for the last free chunk *at the end of the container* (not the last chunk in general)
FreeChunkMap::reverse_iterator lastFree, freeEnd;
for( lastFree = m_freeChunks.rbegin(), freeEnd = m_freeChunks.rend();
lastFree != freeEnd && lastFreeSize + lastFreeOffset != m_currentSize; ++lastFree )
{
lastFreeSize = getChunkSize( *lastFree );
lastFreeOffset = getChunkOffset( *lastFree );
}
if( lastFreeSize + lastFreeOffset == m_currentSize )
{
// We found a chunk at the end of the container
m_freeChunks.erase( lastFree.base() );
// so we can merge it with the new free chunk
m_freeChunks.insert( Chunk( aNewSize - m_currentSize + lastFreeSize, // size
m_currentSize - lastFreeSize ) ); // offset
}
else
{
// As there is no free chunk at the end of container - simply add a new entry
if( aNewSize > m_currentSize ) // only in the case of enlargement
{
m_freeChunks.insert( Chunk( aNewSize - m_currentSize, // size
m_currentSize ) ); // offset
}
}
m_freeSpace += ( aNewSize - m_currentSize );
m_currentSize = aNewSize;
return true;
}
...@@ -27,107 +27,98 @@ ...@@ -27,107 +27,98 @@
* @brief Class to handle an item held in a Vertex Buffer Object. * @brief Class to handle an item held in a Vertex Buffer Object.
*/ */
#include <gal/opengl/vbo_container.h>
#include <gal/opengl/vbo_item.h> #include <gal/opengl/vbo_item.h>
#include <cstring> #include <cstring>
using namespace KiGfx; using namespace KiGfx;
VBO_ITEM::VBO_ITEM() : VBO_ITEM::VBO_ITEM( VBO_CONTAINER* aContainer ) :
m_vertices( NULL ),
m_offset( 0 ), m_offset( 0 ),
m_size( 0 ), m_size( 0 ),
m_container( aContainer ),
m_isDirty( true ), m_isDirty( true ),
m_transform( NULL ) m_transform( NULL )
{ {
// By default no shader is used // By default no shader is used
m_shader[0] = 0; m_shader[0] = 0;
// Prepare a block for storing vertices & indices // The item's size is not known yet, so we just start an item in the container
useNewBlock(); aContainer->StartItem( this );
} }
VBO_ITEM::~VBO_ITEM() VBO_ITEM::~VBO_ITEM()
{ {
if( m_isDirty ) m_container->Free( this );
{
// Data is still stored in blocks
std::list<VBO_VERTEX*>::const_iterator v_it, v_end;
for( v_it = m_vertBlocks.begin(), v_end = m_vertBlocks.end(); v_it != v_end; ++v_it )
delete[] *v_it;
}
if( m_vertices )
delete m_vertices;
} }
void VBO_ITEM::PushVertex( const GLfloat* aVertex ) void VBO_ITEM::PushVertex( VBO_VERTEX* aVertex )
{ {
if( m_spaceLeft == 0 )
useNewBlock();
if( m_transform != NULL ) if( m_transform != NULL )
{ {
// Apply transformations // Apply transformations
// X, Y, Z coordinates glm::vec4 vertex( aVertex->x, aVertex->y, aVertex->z, 1.0f );
glm::vec4 vertex( aVertex[0], aVertex[1], aVertex[2], 1.0f );
vertex = *m_transform * vertex; vertex = *m_transform * vertex;
// Replace only coordinates, leave color as it is // Replace only coordinates, leave color as it is
memcpy( &m_vertPtr->x, &vertex[0], CoordByteSize ); aVertex->x = vertex.x;
} aVertex->y = vertex.y;
else aVertex->z = vertex.z;
{
// Add the new vertex
memcpy( &m_vertPtr->x, aVertex, CoordByteSize );
} }
// Apply currently used color // Apply currently used color
memcpy( &m_vertPtr->r, m_color, ColorByteSize ); aVertex->r = m_color[0];
aVertex->g = m_color[1];
aVertex->b = m_color[2];
aVertex->a = m_color[3];
// Apply currently used shader // Apply currently used shader
memcpy( &m_vertPtr->shader, m_shader, ShaderByteSize ); for( int i = 0; i < ShaderStride; ++i )
{
aVertex->shader[i] = m_shader[i];
}
// Move to the next free space m_container->Add( this, aVertex );
m_vertPtr++;
m_size++; m_size++;
m_isDirty = true; m_isDirty = true;
m_spaceLeft--;
} }
void VBO_ITEM::PushVertices( const GLfloat* aVertices, GLuint aSize ) void VBO_ITEM::PushVertices( VBO_VERTEX* aVertices, GLuint aSize )
{ {
for( unsigned int i = 0; i < aSize; ++i ) for( unsigned int i = 0; i < aSize; ++i )
{ {
PushVertex( &aVertices[i * VertStride] ); PushVertex( &aVertices[i] );
} }
} }
GLfloat* VBO_ITEM::GetVertices() VBO_VERTEX* VBO_ITEM::GetVertices()
{ {
if( m_isDirty ) if( m_isDirty )
prepareFinal(); Finish();
return m_vertices; return m_container->GetVertices( this );
} }
void VBO_ITEM::ChangeColor( const COLOR4D& aColor ) void VBO_ITEM::ChangeColor( const COLOR4D& aColor )
{ {
wxASSERT_MSG( false, wxT( "This was not tested yet" ) );
if( m_isDirty ) if( m_isDirty )
prepareFinal(); Finish();
// Point to color of vertices // Point to color of vertices
GLfloat* vertexPtr = m_vertices + ColorOffset; VBO_VERTEX* vertexPtr = GetVertices();
const GLfloat newColor[] = { aColor.r, aColor.g, aColor.b, aColor.a }; const GLfloat newColor[] = { aColor.r, aColor.g, aColor.b, aColor.a };
for( int i = 0; i < m_size; ++i ) for( unsigned int i = 0; i < m_size; ++i )
{ {
memcpy( vertexPtr, newColor, ColorByteSize ); memcpy( &vertexPtr->r, newColor, ColorByteSize );
// Move on to the next vertex // Move on to the next vertex
vertexPtr++; vertexPtr++;
...@@ -135,38 +126,10 @@ void VBO_ITEM::ChangeColor( const COLOR4D& aColor ) ...@@ -135,38 +126,10 @@ void VBO_ITEM::ChangeColor( const COLOR4D& aColor )
} }
void VBO_ITEM::useNewBlock() void VBO_ITEM::Finish()
{ {
VBO_VERTEX* newVertBlock = new VBO_VERTEX[BLOCK_SIZE]; // The unknown-sized item has just ended, so we need to inform the container about it
m_container->EndItem();
m_vertPtr = newVertBlock;
m_vertBlocks.push_back( newVertBlock );
m_spaceLeft = BLOCK_SIZE;
}
void VBO_ITEM::prepareFinal()
{
if( m_vertices )
delete m_vertices;
// Allocate memory that would store all of vertices
m_vertices = new GLfloat[m_size * VertStride];
// Set the pointer that will move along the buffer
GLfloat* vertPtr = m_vertices;
// Copy blocks of vertices one after another to m_vertices
std::list<VBO_VERTEX*>::const_iterator v_it;
for( v_it = m_vertBlocks.begin(); *v_it != m_vertBlocks.back(); ++v_it )
{
memcpy( vertPtr, *v_it, BLOCK_SIZE * VertByteSize );
delete[] *v_it;
vertPtr += ( BLOCK_SIZE * VertStride );
}
// In the last block we need to copy only used vertices
memcpy( vertPtr, *v_it, ( BLOCK_SIZE - m_spaceLeft ) * VertByteSize );
m_isDirty = false; m_isDirty = false;
} }
...@@ -66,8 +66,8 @@ bool STROKE_FONT::LoadNewStrokeFont( const char* const aNewStrokeFont[], int aNe ...@@ -66,8 +66,8 @@ bool STROKE_FONT::LoadNewStrokeFont( const char* const aNewStrokeFont[], int aNe
while( aNewStrokeFont[j][i] ) while( aNewStrokeFont[j][i] )
{ {
VECTOR2D point; VECTOR2D point( 0.0, 0.0 );
char coordinate[2]; char coordinate[2] = { 0, };
for( int k = 0; k < 2; k++ ) for( int k = 0; k < 2; k++ )
{ {
......
...@@ -79,6 +79,7 @@ PAINTER::PAINTER( GAL* aGal ) : ...@@ -79,6 +79,7 @@ PAINTER::PAINTER( GAL* aGal ) :
PAINTER::~PAINTER() PAINTER::~PAINTER()
{ {
delete m_stroke_font; delete m_stroke_font;
delete m_settings;
} }
......
...@@ -473,6 +473,11 @@ void VIEW::Clear() ...@@ -473,6 +473,11 @@ void VIEW::Clear()
l->items->RemoveAll(); l->items->RemoveAll();
} }
if( m_useGroups )
{
m_gal->ClearCache();
}
} }
......
...@@ -111,7 +111,7 @@ void VIEW_ITEM::setGroup( int aLayer, int aId ) ...@@ -111,7 +111,7 @@ void VIEW_ITEM::setGroup( int aLayer, int aId )
if( m_groupsSize > 0 ) if( m_groupsSize > 0 )
{ {
std::copy( m_groups, m_groups + m_groupsSize, newGroups ); std::copy( m_groups, m_groups + m_groupsSize, newGroups );
delete m_groups; delete[] m_groups;
} }
m_groups = newGroups; m_groups = newGroups;
...@@ -123,7 +123,7 @@ void VIEW_ITEM::deleteGroups() ...@@ -123,7 +123,7 @@ void VIEW_ITEM::deleteGroups()
{ {
if( m_groupsSize > 0 ) if( m_groupsSize > 0 )
{ {
delete m_groups; delete[] m_groups;
m_groupsSize = 0; m_groupsSize = 0;
} }
} }
......
...@@ -212,6 +212,9 @@ public: ...@@ -212,6 +212,9 @@ public:
/// @copydoc GAL::DeleteGroup() /// @copydoc GAL::DeleteGroup()
virtual void DeleteGroup( int aGroupNumber ); virtual void DeleteGroup( int aGroupNumber );
/// @copydoc GAL::ClearCache()
virtual void ClearCache();
// -------------------------------------------------------- // --------------------------------------------------------
// Handling the world <-> screen transformation // Handling the world <-> screen transformation
// -------------------------------------------------------- // --------------------------------------------------------
......
...@@ -370,6 +370,11 @@ public: ...@@ -370,6 +370,11 @@ public:
*/ */
virtual void DeleteGroup( int aGroupNumber ) = 0; virtual void DeleteGroup( int aGroupNumber ) = 0;
/**
* @brief Delete all data created during caching of graphic items.
*/
virtual void ClearCache() = 0;
// -------------------------------------------------------- // --------------------------------------------------------
// Handling the world <-> screen transformation // Handling the world <-> screen transformation
// -------------------------------------------------------- // --------------------------------------------------------
......
...@@ -35,6 +35,8 @@ ...@@ -35,6 +35,8 @@
// OpenGL mathematics library // OpenGL mathematics library
#define GLM_FORCE_RADIANS #define GLM_FORCE_RADIANS
#include <gal/opengl/glm/gtc/matrix_transform.hpp>
#include <gal/opengl/vbo_item.h> #include <gal/opengl/vbo_item.h>
#include <gal/opengl/shader.h> #include <gal/opengl/shader.h>
...@@ -56,7 +58,7 @@ ...@@ -56,7 +58,7 @@
namespace KiGfx namespace KiGfx
{ {
class SHADER; class SHADER;
class VBO_ITEM; class VBO_CONTAINER;
/** /**
* @brief Class OpenGL_GAL is the OpenGL implementation of the Graphics Abstraction Layer. * @brief Class OpenGL_GAL is the OpenGL implementation of the Graphics Abstraction Layer.
...@@ -236,6 +238,9 @@ public: ...@@ -236,6 +238,9 @@ public:
/// @copydoc GAL::DeleteGroup() /// @copydoc GAL::DeleteGroup()
virtual void DeleteGroup( int aGroupNumber ); virtual void DeleteGroup( int aGroupNumber );
/// @copydoc GAL::ClearCache()
virtual void ClearCache();
// -------------------------------------------------------- // --------------------------------------------------------
// Handling the world <-> screen transformation // Handling the world <-> screen transformation
// -------------------------------------------------------- // --------------------------------------------------------
...@@ -315,6 +320,13 @@ public: ...@@ -315,6 +320,13 @@ public:
shaderPath = aPath; shaderPath = aPath;
} }
///< Parameters passed to the GLU tesselator
typedef struct
{
VBO_ITEM* vboItem; ///< VBO_ITEM for storing new vertices
std::vector<GLdouble*>& intersectPoints; ///< Intersect points, that have to be freed
} TessParams;
protected: protected:
virtual void DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ); virtual void DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint );
...@@ -336,20 +348,19 @@ private: ...@@ -336,20 +348,19 @@ private:
wxEvtHandler* mouseListener; wxEvtHandler* mouseListener;
wxEvtHandler* paintListener; wxEvtHandler* paintListener;
// Display lists // Display lists (used in shaderless mode)
GLuint displayListsArcs; ///< Arc display list VBO_CONTAINER* precomputedContainer; ///< Container for storing display lists
VBO_ITEM verticesArc;
GLuint displayListCircle; ///< Circle display list GLuint displayListCircle; ///< Circle display list
VBO_ITEM verticesCircle; VBO_ITEM* verticesCircle;
GLuint displayListSemiCircle; ///< Semi circle display list GLuint displayListSemiCircle; ///< Semi circle display list
VBO_ITEM verticesSemiCircle; VBO_ITEM* verticesSemiCircle;
// Vertex buffer objects related fields // Vertex buffer objects related fields
std::deque<VBO_ITEM*> vboItems; ///< Stores informations about VBO objects std::deque<VBO_ITEM*> vboItems; ///< Stores informations about VBO objects
VBO_ITEM* curVboItem; ///< Currently used VBO_ITEM (for grouping) VBO_ITEM* curVboItem; ///< Currently used VBO_ITEM (for grouping)
GLuint curVboVertId; ///< Currently used vertices VBO handle VBO_CONTAINER* vboContainer; ///< Container for storing VBO_ITEMs
GLuint curVboIndId; ///< Currently used indices VBO handle GLuint vboVertices; ///< Currently used vertices VBO handle
int vboSize; ///< Amount of vertices stored in VBO GLuint vboIndices; ///< Currently used indices VBO handle
bool vboNeedsUpdate; ///< Flag indicating if VBO should be rebuilt bool vboNeedsUpdate; ///< Flag indicating if VBO should be rebuilt
glm::mat4 transform; ///< Current transformation matrix glm::mat4 transform; ///< Current transformation matrix
std::stack<glm::mat4> transformStack; ///< Stack of transformation matrices std::stack<glm::mat4> transformStack; ///< Stack of transformation matrices
...@@ -362,6 +373,7 @@ private: ...@@ -362,6 +373,7 @@ private:
// Polygon tesselation // Polygon tesselation
GLUtesselator* tesselator; ///< Pointer to the tesselator GLUtesselator* tesselator; ///< Pointer to the tesselator
std::vector<GLdouble*> tessIntersects; ///< Storage of intersecting points
// Shader // Shader
// Possible types of shaders // Possible types of shaders
...@@ -519,12 +531,20 @@ private: ...@@ -519,12 +531,20 @@ private:
* @brief Starts drawing in immediate mode or does nothing if an item's caching has started. * @brief Starts drawing in immediate mode or does nothing if an item's caching has started.
* @param aMode specifies the primitive or primitives that will be created. * @param aMode specifies the primitive or primitives that will be created.
*/ */
inline void begin( GLenum aMode ); inline void begin( GLenum aMode )
{
if( !isGrouping )
glBegin( aMode );
}
/** /**
* @brief Ends drawing in immediate mode or does nothing if an item's caching has started. * @brief Ends drawing in immediate mode or does nothing if an item's caching has started.
*/ */
inline void end(); inline void end()
{
if( !isGrouping )
glEnd();
}
/** /**
* @brief Adds vertex to the current item or draws it in immediate mode. * @brief Adds vertex to the current item or draws it in immediate mode.
...@@ -532,7 +552,19 @@ private: ...@@ -532,7 +552,19 @@ private:
* @param aY is Y coordinate. * @param aY is Y coordinate.
* @param aZ is Z coordinate. * @param aZ is Z coordinate.
*/ */
inline void vertex3( double aX, double aY, double aZ ); inline void vertex3( double aX, double aY, double aZ )
{
if( isGrouping )
{
// New vertex coordinates for VBO
VBO_VERTEX vertex( aX, aY, aZ );
curVboItem->PushVertex( &vertex );
}
else
{
glVertex3d( aX, aY, aZ );
}
}
/** /**
* @brief Function that replaces glTranslate and behaves according to isGrouping variable. * @brief Function that replaces glTranslate and behaves according to isGrouping variable.
...@@ -543,7 +575,17 @@ private: ...@@ -543,7 +575,17 @@ private:
* @param aY is translation in Y axis direction. * @param aY is translation in Y axis direction.
* @param aZ is translation in Z axis direction. * @param aZ is translation in Z axis direction.
*/ */
inline void translate3( double aX, double aY, double aZ ); inline void translate3( double aX, double aY, double aZ )
{
if( isGrouping )
{
transform = glm::translate( transform, glm::vec3( aX, aY, aZ ) );
}
else
{
glTranslated( aX, aY, aZ );
}
}
/** /**
* @brief Function that replaces glColor and behaves according to isGrouping variable. * @brief Function that replaces glColor and behaves according to isGrouping variable.
...@@ -555,7 +597,17 @@ private: ...@@ -555,7 +597,17 @@ private:
* @param aB is blue component. * @param aB is blue component.
* @param aA is alpha component. * @param aA is alpha component.
*/ */
inline void color4( double aRed, double aGreen, double aBlue, double aAlpha ); inline void color4( double aRed, double aGreen, double aBlue, double aAlpha )
{
if( isGrouping )
{
curVboItem->UseColor( COLOR4D( aRed, aGreen, aBlue, aAlpha ) );
}
else
{
glColor4d( aRed, aGreen, aBlue, aAlpha );
}
}
/** /**
* @brief Function that replaces glColor and behaves according to isGrouping variable. * @brief Function that replaces glColor and behaves according to isGrouping variable.
...@@ -564,7 +616,17 @@ private: ...@@ -564,7 +616,17 @@ private:
* *
* @param aColor is the new color. * @param aColor is the new color.
*/ */
inline void color4( const COLOR4D& aColor ); inline void color4( const COLOR4D& aColor )
{
if( isGrouping )
{
curVboItem->UseColor( aColor );
}
else
{
glColor4d( aColor.r, aColor.g, aColor.b, aColor.a);
}
}
/** /**
* @brief Function that sets shader and its parameters for the currently used VBO_ITEM. * @brief Function that sets shader and its parameters for the currently used VBO_ITEM.
......
/*
* 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 vbo_container.h
* @brief Class to store VBO_ITEMs.
*/
#ifndef VBO_CONTAINER_H_
#define VBO_CONTAINER_H_
#include <GL/gl.h>
#include <map>
#include <wx/log.h>
namespace KiGfx
{
class VBO_ITEM;
typedef struct VBO_VERTEX VBO_VERTEX;
class VBO_CONTAINER
{
public:
VBO_CONTAINER( int aSize = 1048576 );
~VBO_CONTAINER();
///< Maps size of free memory chunks to their offsets
typedef std::pair<const unsigned int, unsigned int> Chunk;
typedef std::multimap<const unsigned int, unsigned int> FreeChunkMap;
///< Maps size of reserved memory chunks to their owners (VBO_ITEMs)
typedef std::pair<VBO_ITEM* const, Chunk> ReservedChunk;
typedef std::multimap<VBO_ITEM* const, Chunk> ReservedChunkMap;
/**
* Function StartItem()
* Starts an unknown sized item. After calling the function it is possible to add vertices
* using function Add().
* @param aVboItem is the item that is going to store vertices in the container.
*/
void StartItem( VBO_ITEM* aVboItem );
/**
* Function EndItem()
* Marks current item as finished and returns unused memory to the pool. StartItem() function
* has to be called first.
*/
void EndItem();
/**
* Function Add()
* Stores given number of vertices in the container for the specific VBO_ITEM.
* @param aVboItem is the owner of the vertices.
* @param aVertex are vertices data to be stored.
* @param aSize is the number of vertices to be added.
*/
void Add( VBO_ITEM* aVboItem, const VBO_VERTEX* aVertex, unsigned int aSize = 1 );
/**
* Function Free()
* Frees the chunk reserved by the aVboItem.
* @param aVboItem is the owner of the chunk to be freed.
*/
inline void Free( VBO_ITEM* aVboItem )
{
ReservedChunkMap::iterator it = m_reservedChunks.find( aVboItem );
free( it );
// Dynamic memory freeing, there is no point in holding
// a large amount of memory when there is no use for it
if( m_freeSpace > ( m_currentSize / 2 ) )
{
resizeContainer( m_currentSize / 2 );
}
}
/**
* Function GetAllVertices()
* Returns all vertices stored in the container. It is especially useful for transferring
* data to the GPU memory.
*/
VBO_VERTEX* GetAllVertices() const;
/**
* Function GetVertices()
* Returns vertices stored by the specific item.
* @aVboItem is the specific item.
*/
VBO_VERTEX* GetVertices( const VBO_ITEM* aVboItem ) const;
/**
* Function GetSize()
* Returns amount of vertices currently stored in the container.
*/
inline int GetSize() const
{
return m_currentSize;
}
private:
///< Stores size & offset of free chunks.
FreeChunkMap m_freeChunks;
///< Stores owners (VBO_ITEM*) of reserved chunks and their size & offset.
ReservedChunkMap m_reservedChunks;
/**
* Function allocate()
* Finds an offset where the number of vertices can be stored in a continous space. If there is
* no such chunk, appropriate amount of memory is allocated first.
* @param aVboItem is the owner of vertices to be stored.
* @param aSize is the number of vertices to be stored.
*/
unsigned int allocate( VBO_ITEM* aVboItem, unsigned int aSize );
/**
* Function getChunkSize()
* Returns size of the given chunk (works both for reserved and free chunks).
* @param aChunk is the chunk.
*/
inline int getChunkSize( const Chunk& aChunk ) const
{
return aChunk.first;
}
inline int getChunkSize( const ReservedChunk& aChunk ) const
{
return aChunk.second.first;
}
/**
* Function getChunkOffset()
* Returns offset of the given chunk (works both for reserved and free chunks).
* @param aChunk is the chunk.
*/
inline unsigned int getChunkOffset( const Chunk& aChunk ) const
{
return aChunk.second;
}
inline unsigned int getChunkOffset( const ReservedChunk& aChunk ) const
{
return aChunk.second.second;
}
/**
* Function getChunkOffset()
* Upadtes offset of the given chunk (works both for reserved and free chunks).
* !! IMPORTANT: it does not reallocate the chunk, it just changes its properties.
* @param aChunk is the chunk.
*/
inline void setChunkOffset( Chunk& aChunk, unsigned int aOffset ) const
{
aChunk.second = aOffset;
}
inline void setChunkOffset( ReservedChunk& aChunk, unsigned int aOffset ) const
{
aChunk.second.second = aOffset;
}
/**
* Function getChunkVboItem()
* Returns owner of the given reserved chunk.
* @param aChunk is the chunk.
*/
inline VBO_ITEM* getChunkVboItem( const ReservedChunk& aChunk ) const
{
return aChunk.first;
}
/**
* Function defragment()
* Removes empty spaces between chunks, so after that there is a long continous space
* for storing vertices at the and of the container.
* @return false in case of failure (eg. memory shortage)
*/
bool defragment( VBO_VERTEX* aTarget = NULL );
/**
* Function resizeChunk()
* Changes size of the chunk that stores vertices of aVboItem.
* @param aVboItem is the item for which reserved space size should be changed.
* @param aNewSize is the new size for the aVboItem, expressed in vertices number.
*/
void resizeChunk( VBO_ITEM* aVboItem, int aNewSize );
/**
* Function resizeContainer()
* Prepares a bigger container of a given size.
* @param aNewSize is the new size of container, expressed in vertices
* @return false in case of failure (eg. memory shortage)
*/
bool resizeContainer( unsigned int aNewSize );
/**
* Function free()
* Frees the space described in aChunk and returns it to the free space pool.
* @param aChunk is a space to be freed.
*/
void free( const ReservedChunkMap::iterator& aChunk );
///< How many vertices we can store in the container
unsigned int m_freeSpace;
///< How big is the current container, expressed in vertices
unsigned int m_currentSize;
///< Actual storage memory
VBO_VERTEX* m_vertices;
///< A flag saying if there is the item with an unknown size being added
bool itemStarted;
///< Variables holding the state of the item currently being added
unsigned int itemSize, itemChunkSize;
VBO_ITEM* item;
/**
* Function getPowerOf2()
* Returns the nearest power of 2, bigger than aNumber.
* @param aNumber is the number for which we look for a bigger power of 2.
*/
unsigned int getPowerOf2( unsigned int aNumber )
{
unsigned int power = 1;
while( power < aNumber && power )
power <<= 1;
return power;
}
};
} // namespace KiGfx
#endif /* VBO_CONTAINER_H_ */
...@@ -36,8 +36,6 @@ ...@@ -36,8 +36,6 @@
#include <cstddef> #include <cstddef>
#include <list>
namespace KiGfx namespace KiGfx
{ {
typedef struct VBO_VERTEX typedef struct VBO_VERTEX
...@@ -45,12 +43,31 @@ typedef struct VBO_VERTEX ...@@ -45,12 +43,31 @@ typedef struct VBO_VERTEX
GLfloat x, y, z; // Coordinates GLfloat x, y, z; // Coordinates
GLfloat r, g, b, a; // Color GLfloat r, g, b, a; // Color
GLfloat shader[4]; // Shader type & params GLfloat shader[4]; // Shader type & params
GLfloat _padding;
VBO_VERTEX()
{}
VBO_VERTEX( const GLfloat aX, const GLfloat aY, const GLfloat aZ ) :
x( aX ), y( aY ), z( aZ )
{}
VBO_VERTEX( const GLfloat *aData ) :
x( aData[0] ), y( aData[1] ), z( aData[2] )
{}
operator GLfloat*()
{
return &x;
}
} VBO_VERTEX; } VBO_VERTEX;
class VBO_CONTAINER;
class VBO_ITEM class VBO_ITEM
{ {
public: public:
VBO_ITEM(); VBO_ITEM( VBO_CONTAINER* aContainer );
~VBO_ITEM(); ~VBO_ITEM();
/** /**
...@@ -60,7 +77,7 @@ public: ...@@ -60,7 +77,7 @@ public:
* @param aVertex is a vertex to be added. * @param aVertex is a vertex to be added.
* @param aShader is an attribute for shader. * @param aShader is an attribute for shader.
*/ */
void PushVertex( const GLfloat* aVertex ); void PushVertex( VBO_VERTEX* aVertex );
/** /**
* Function PushVertices() * Function PushVertices()
...@@ -71,14 +88,14 @@ public: ...@@ -71,14 +88,14 @@ public:
* @param aSize is an amount of vertices to be added. * @param aSize is an amount of vertices to be added.
* @param aShader is an attribute for shader. * @param aShader is an attribute for shader.
*/ */
void PushVertices( const GLfloat* aVertices, GLuint aSize ); void PushVertices( VBO_VERTEX* aVertices, GLuint aSize );
/** /**
* Function GetVertices() * Function GetVertices()
* Returns a pointer to the array containing all vertices. * Returns a pointer to the array containing all vertices.
* @return Pointer to vertices packed in format {X, Y, Z, R, G, B, A}. * @return Pointer to vertices packed in format {X, Y, Z, R, G, B, A}.
*/ */
GLfloat* GetVertices(); VBO_VERTEX* GetVertices();
/** /**
...@@ -86,7 +103,7 @@ public: ...@@ -86,7 +103,7 @@ public:
* Returns information about number of vertices stored. * Returns information about number of vertices stored.
* @param Amount of vertices. * @param Amount of vertices.
*/ */
inline int GetSize() const inline unsigned int GetSize() const
{ {
return m_size; return m_size;
} }
...@@ -96,7 +113,7 @@ public: ...@@ -96,7 +113,7 @@ public:
* Sets data offset in the VBO. * Sets data offset in the VBO.
* @param aOffset is the offset expressed as a number of vertices. * @param aOffset is the offset expressed as a number of vertices.
*/ */
void SetOffset( int aOffset ) void SetOffset( unsigned int aOffset )
{ {
m_offset = aOffset; m_offset = aOffset;
} }
...@@ -106,7 +123,7 @@ public: ...@@ -106,7 +123,7 @@ public:
* Returns data offset in the VBO. * Returns data offset in the VBO.
* @return Data offset expressed as a number of vertices. * @return Data offset expressed as a number of vertices.
*/ */
inline int GetOffset() const inline unsigned int GetOffset() const
{ {
return m_offset; return m_offset;
} }
...@@ -156,16 +173,8 @@ public: ...@@ -156,16 +173,8 @@ public:
} }
} }
///< Informs the container that there will be no more vertices for the current VBO_ITEM
inline void FreeVerticesData() void Finish();
{
if( m_vertices && !m_isDirty )
{
delete[] m_vertices;
m_vertices = NULL;
}
}
///< Data organization information for vertices {X,Y,Z,R,G,B,A} (@see VBO_VERTEX). ///< Data organization information for vertices {X,Y,Z,R,G,B,A} (@see VBO_VERTEX).
static const int VertByteSize = sizeof(VBO_VERTEX); static const int VertByteSize = sizeof(VBO_VERTEX);
...@@ -191,28 +200,12 @@ public: ...@@ -191,28 +200,12 @@ public:
static const int IndByteSize = sizeof(GLuint); static const int IndByteSize = sizeof(GLuint);
private: private:
///< Contains vertices coordinates and colors. ///< Offset and size of data stored in the VBO_CONTAINER.
///< Packed by 7 floats for each vertex: {X, Y, Z, R, G, B, A} unsigned int m_offset;
GLfloat* m_vertices; unsigned int m_size;
///< Lists of data blocks storing vertices ///< Storage for vertices.
std::list<VBO_VERTEX*> m_vertBlocks; VBO_CONTAINER* m_container;
///< Pointers to current blocks that should be used for storing data
VBO_VERTEX* m_vertPtr;
///< How many vertices can be stored in the current buffer
int m_spaceLeft;
///< Number of vertices stored in a single block
static const int BLOCK_SIZE = 256;
///< Creates a new block for storing vertices data
void useNewBlock();
///< Prepares a continuous block of data that can be copied to graphics card buffer.
void prepareFinal();
///< Offset and size of data in VBO.
int m_offset;
int m_size;
///< Color used for new vertices pushed. ///< Color used for new vertices pushed.
GLfloat m_color[ColorStride]; GLfloat m_color[ColorStride];
......
...@@ -68,7 +68,7 @@ public: ...@@ -68,7 +68,7 @@ public:
ALL = 0xff ALL = 0xff
}; };
VIEW_ITEM() : m_view( NULL ), m_viewVisible( true ), m_groupsSize( 0 ) {} VIEW_ITEM() : m_view( NULL ), m_viewVisible( true ), m_groups( NULL ), m_groupsSize( 0 ) {}
/** /**
* Destructor. For dynamic views, removes the item from the view. * Destructor. For dynamic views, removes the item from the view.
...@@ -76,6 +76,7 @@ public: ...@@ -76,6 +76,7 @@ public:
virtual ~VIEW_ITEM() virtual ~VIEW_ITEM()
{ {
ViewRelease(); ViewRelease();
delete[] m_groups;
}; };
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment