Smarter way of the overlay rendering (overlay is always refreshed, while cached&noncached targets only if the viewport or items have changed).
This commit is contained in:
parent
e87eea7abc
commit
43ae1cb98d
|
@ -127,6 +127,8 @@ void EDA_DRAW_PANEL_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
|
||||||
void EDA_DRAW_PANEL_GAL::onSize( wxSizeEvent& aEvent )
|
void EDA_DRAW_PANEL_GAL::onSize( wxSizeEvent& aEvent )
|
||||||
{
|
{
|
||||||
m_gal->ResizeScreen( aEvent.GetSize().x, aEvent.GetSize().y );
|
m_gal->ResizeScreen( aEvent.GetSize().x, aEvent.GetSize().y );
|
||||||
|
m_view->SetTargetDirty( KiGfx::TARGET_CACHED );
|
||||||
|
m_view->SetTargetDirty( KiGfx::TARGET_NONCACHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,24 +136,19 @@ void EDA_DRAW_PANEL_GAL::Refresh( bool eraseBackground, const wxRect* rect )
|
||||||
{
|
{
|
||||||
#ifdef __WXDEBUG__
|
#ifdef __WXDEBUG__
|
||||||
prof_counter time;
|
prof_counter time;
|
||||||
|
|
||||||
prof_start( &time, false );
|
prof_start( &time, false );
|
||||||
#endif /* __WXDEBUG__ */
|
#endif /* __WXDEBUG__ */
|
||||||
|
|
||||||
printf("Refresh!\n");
|
|
||||||
|
|
||||||
m_gal->BeginDrawing();
|
m_gal->BeginDrawing();
|
||||||
m_gal->SetBackgroundColor( KiGfx::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
|
m_gal->SetBackgroundColor( KiGfx::COLOR4D( 0.0, 0.0, 0.0, 1.0 ) );
|
||||||
m_gal->ClearScreen();
|
m_gal->ClearScreen();
|
||||||
|
|
||||||
m_gal->DrawGrid();
|
|
||||||
m_view->Redraw();
|
m_view->Redraw();
|
||||||
|
|
||||||
m_gal->EndDrawing();
|
m_gal->EndDrawing();
|
||||||
|
|
||||||
#ifdef __WXDEBUG__
|
#ifdef __WXDEBUG__
|
||||||
prof_end( &time );
|
prof_end( &time );
|
||||||
|
|
||||||
wxLogDebug( wxT( "EDA_DRAW_PANEL_GAL::Refresh: %.0f ms (%.0f fps)" ),
|
wxLogDebug( wxT( "EDA_DRAW_PANEL_GAL::Refresh: %.0f ms (%.0f fps)" ),
|
||||||
static_cast<double>( time.value ) / 1000.0, 1000000.0 / static_cast<double>( time.value ) );
|
static_cast<double>( time.value ) / 1000.0, 1000000.0 / static_cast<double>( time.value ) );
|
||||||
#endif /* __WXDEBUG__ */
|
#endif /* __WXDEBUG__ */
|
||||||
|
@ -184,6 +181,9 @@ void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType )
|
||||||
m_gal->SetScreenDPI( 106 ); // Display resolution setting
|
m_gal->SetScreenDPI( 106 ); // Display resolution setting
|
||||||
m_gal->ComputeWorldScreenMatrix();
|
m_gal->ComputeWorldScreenMatrix();
|
||||||
|
|
||||||
|
wxSize size = GetClientSize();
|
||||||
|
m_gal->ResizeScreen( size.GetX(), size.GetY() );
|
||||||
|
|
||||||
if( m_painter )
|
if( m_painter )
|
||||||
m_painter->SetGAL( m_gal );
|
m_painter->SetGAL( m_gal );
|
||||||
|
|
||||||
|
@ -193,9 +193,6 @@ void EDA_DRAW_PANEL_GAL::SwitchBackend( GalType aGalType )
|
||||||
m_view->RecacheAllItems( true );
|
m_view->RecacheAllItems( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
wxSize size = GetClientSize();
|
|
||||||
m_gal->ResizeScreen( size.GetX(), size.GetY() );
|
|
||||||
|
|
||||||
m_currentGal = aGalType;
|
m_currentGal = aGalType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,6 @@ using namespace KiGfx;
|
||||||
CAIRO_COMPOSITOR::CAIRO_COMPOSITOR( cairo_t** aMainContext ) :
|
CAIRO_COMPOSITOR::CAIRO_COMPOSITOR( cairo_t** aMainContext ) :
|
||||||
m_current( 0 ), m_currentContext( aMainContext ), m_mainContext( *aMainContext )
|
m_current( 0 ), m_currentContext( aMainContext ), m_mainContext( *aMainContext )
|
||||||
{
|
{
|
||||||
// Obtain the transformation matrix used in the main context
|
|
||||||
cairo_get_matrix( m_mainContext, &m_matrix );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,11 +63,10 @@ void CAIRO_COMPOSITOR::Resize( unsigned int aWidth, unsigned int aHeight )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned int CAIRO_COMPOSITOR::GetBuffer()
|
unsigned int CAIRO_COMPOSITOR::CreateBuffer()
|
||||||
{
|
{
|
||||||
// Pixel storage
|
// Pixel storage
|
||||||
BitmapPtr bitmap( new unsigned int[m_bufferSize] );
|
BitmapPtr bitmap( new unsigned int[m_bufferSize] );
|
||||||
|
|
||||||
memset( bitmap.get(), 0x00, m_bufferSize * sizeof(int) );
|
memset( bitmap.get(), 0x00, m_bufferSize * sizeof(int) );
|
||||||
|
|
||||||
// Create the Cairo surface
|
// Create the Cairo surface
|
||||||
|
@ -78,10 +75,10 @@ unsigned int CAIRO_COMPOSITOR::GetBuffer()
|
||||||
CAIRO_FORMAT_ARGB32, m_width,
|
CAIRO_FORMAT_ARGB32, m_width,
|
||||||
m_height, m_stride );
|
m_height, m_stride );
|
||||||
cairo_t* context = cairo_create( surface );
|
cairo_t* context = cairo_create( surface );
|
||||||
#ifdef __WXDEBUG__
|
#ifdef __WXDEBUG__
|
||||||
cairo_status_t status = cairo_status( context );
|
cairo_status_t status = cairo_status( context );
|
||||||
wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, "Cairo context creation error" );
|
wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, "Cairo context creation error" );
|
||||||
#endif /* __WXDEBUG__ */
|
#endif /* __WXDEBUG__ */
|
||||||
|
|
||||||
// Set default settings for the buffer
|
// Set default settings for the buffer
|
||||||
cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );
|
cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );
|
||||||
|
@ -89,6 +86,7 @@ unsigned int CAIRO_COMPOSITOR::GetBuffer()
|
||||||
cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );
|
cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );
|
||||||
|
|
||||||
// Use the same transformation matrix as the main context
|
// Use the same transformation matrix as the main context
|
||||||
|
cairo_get_matrix( m_mainContext, &m_matrix );
|
||||||
cairo_set_matrix( context, &m_matrix );
|
cairo_set_matrix( context, &m_matrix );
|
||||||
|
|
||||||
// Store the new buffer
|
// Store the new buffer
|
||||||
|
@ -101,53 +99,41 @@ unsigned int CAIRO_COMPOSITOR::GetBuffer()
|
||||||
|
|
||||||
void CAIRO_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
|
void CAIRO_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
|
||||||
{
|
{
|
||||||
if( aBufferHandle <= usedBuffers() )
|
wxASSERT_MSG( aBufferHandle <= usedBuffers(), wxT( "Tried to use a not existing buffer" ) );
|
||||||
{
|
|
||||||
m_current = aBufferHandle - 1;
|
|
||||||
*m_currentContext = m_buffers[m_current].context;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __WXDEBUG__
|
// Get currently used transformation matrix, so it can be applied to the new buffer
|
||||||
else
|
cairo_get_matrix( *m_currentContext, &m_matrix );
|
||||||
wxLogDebug( wxT( "Tried to use a not existing buffer" ) );
|
|
||||||
#endif
|
m_current = aBufferHandle - 1;
|
||||||
|
*m_currentContext = m_buffers[m_current].context;
|
||||||
|
|
||||||
|
// Apply the current transformation matrix
|
||||||
|
cairo_set_matrix( *m_currentContext, &m_matrix );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CAIRO_COMPOSITOR::ClearBuffer()
|
void CAIRO_COMPOSITOR::ClearBuffer()
|
||||||
{
|
{
|
||||||
// Reset the transformation matrix, so it is possible to composite images using
|
// Clear the pixel storage
|
||||||
// screen coordinates instead of world coordinates
|
memset( m_buffers[m_current].bitmap.get(), 0x00, m_bufferSize * sizeof(int) );
|
||||||
cairo_identity_matrix( m_buffers[m_current].context );
|
|
||||||
|
|
||||||
cairo_set_source_rgba( m_buffers[m_current].context, 0.0, 0.0, 0.0, 0.0 );
|
|
||||||
cairo_rectangle( m_buffers[m_current].context, 0.0, 0.0, m_width, m_height );
|
|
||||||
cairo_fill( m_buffers[m_current].context );
|
|
||||||
|
|
||||||
// Restore the transformation matrix
|
|
||||||
cairo_set_matrix( m_buffers[m_current].context, &m_matrix );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CAIRO_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle )
|
void CAIRO_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle )
|
||||||
{
|
{
|
||||||
if( aBufferHandle <= usedBuffers() )
|
wxASSERT_MSG( aBufferHandle <= usedBuffers(), wxT( "Tried to use a not existing buffer" ) );
|
||||||
{
|
|
||||||
// Reset the transformation matrix, so it is possible to composite images using
|
|
||||||
// screen coordinates instead of world coordinates
|
|
||||||
cairo_identity_matrix( m_mainContext );
|
|
||||||
|
|
||||||
cairo_set_source_surface( m_mainContext, m_buffers[aBufferHandle - 1].surface, 0.0, 0.0 );
|
// Reset the transformation matrix, so it is possible to composite images using
|
||||||
cairo_paint( m_mainContext );
|
// screen coordinates instead of world coordinates
|
||||||
|
cairo_get_matrix( m_mainContext, &m_matrix );
|
||||||
|
cairo_identity_matrix( m_mainContext );
|
||||||
|
|
||||||
// Restore the transformation matrix
|
// Draw the selected buffer contents
|
||||||
cairo_set_matrix( m_mainContext, &m_matrix );
|
cairo_set_source_surface( m_mainContext, m_buffers[aBufferHandle - 1].surface, 0.0, 0.0 );
|
||||||
}
|
cairo_paint( m_mainContext );
|
||||||
|
|
||||||
#ifdef __WXDEBUG__
|
// Restore the transformation matrix
|
||||||
else
|
cairo_set_matrix( m_mainContext, &m_matrix );
|
||||||
wxLogDebug( wxT( "Tried to use a not existing buffer" ) );
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ CAIRO_GAL::CAIRO_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
|
||||||
isGrouping = false;
|
isGrouping = false;
|
||||||
isInitialized = false;
|
isInitialized = false;
|
||||||
isDeleteSavedPixels = false;
|
isDeleteSavedPixels = false;
|
||||||
|
validCompositor = false;
|
||||||
groupCounter = 0;
|
groupCounter = 0;
|
||||||
|
|
||||||
// Connecting the event handlers
|
// Connecting the event handlers
|
||||||
|
@ -90,7 +91,11 @@ CAIRO_GAL::~CAIRO_GAL()
|
||||||
void CAIRO_GAL::BeginDrawing()
|
void CAIRO_GAL::BeginDrawing()
|
||||||
{
|
{
|
||||||
initSurface();
|
initSurface();
|
||||||
setCompositor();
|
if( !validCompositor )
|
||||||
|
setCompositor();
|
||||||
|
|
||||||
|
compositor->SetMainContext( context );
|
||||||
|
compositor->SetBuffer( mainBuffer );
|
||||||
|
|
||||||
// Cairo grouping prevents display of overlapping items on the same layer in the lighter color
|
// Cairo grouping prevents display of overlapping items on the same layer in the lighter color
|
||||||
cairo_push_group( currentContext );
|
cairo_push_group( currentContext );
|
||||||
|
@ -120,9 +125,9 @@ void CAIRO_GAL::EndDrawing()
|
||||||
for( size_t count = 0; count < bufferSize; count++ )
|
for( size_t count = 0; count < bufferSize; count++ )
|
||||||
{
|
{
|
||||||
unsigned int value = bitmapBuffer[count];
|
unsigned int value = bitmapBuffer[count];
|
||||||
*wxOutputPtr++ = (value >> 16) & 0xff; // Red pixel
|
*wxOutputPtr++ = ( value >> 16 ) & 0xff; // Red pixel
|
||||||
*wxOutputPtr++ = (value >> 8) & 0xff; // Green pixel
|
*wxOutputPtr++ = ( value >> 8 ) & 0xff; // Green pixel
|
||||||
*wxOutputPtr++ = value & 0xff; // Blue pixel
|
*wxOutputPtr++ = value & 0xff; // Blue pixel
|
||||||
}
|
}
|
||||||
|
|
||||||
wxImage img( (int) screenSize.x, (int) screenSize.y, (unsigned char*) wxOutput, true );
|
wxImage img( (int) screenSize.x, (int) screenSize.y, (unsigned char*) wxOutput, true );
|
||||||
|
@ -273,6 +278,10 @@ void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
|
||||||
deleteBitmaps();
|
deleteBitmaps();
|
||||||
allocateBitmaps();
|
allocateBitmaps();
|
||||||
|
|
||||||
|
if( validCompositor )
|
||||||
|
compositor->Resize( aWidth, aHeight );
|
||||||
|
validCompositor = false;
|
||||||
|
|
||||||
SetSize( wxSize( aWidth, aHeight ) );
|
SetSize( wxSize( aWidth, aHeight ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,7 +728,7 @@ void CAIRO_GAL::SetTarget( RenderTarget aTarget )
|
||||||
{
|
{
|
||||||
// If the compositor is not set, that means that there is a recaching process going on
|
// If the compositor is not set, that means that there is a recaching process going on
|
||||||
// and we do not need the compositor now
|
// and we do not need the compositor now
|
||||||
if( !compositor )
|
if( !validCompositor )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Cairo grouping prevents display of overlapping items on the same layer in the lighter color
|
// Cairo grouping prevents display of overlapping items on the same layer in the lighter color
|
||||||
|
@ -751,6 +760,31 @@ RenderTarget CAIRO_GAL::GetTarget() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CAIRO_GAL::ClearTarget( RenderTarget aTarget )
|
||||||
|
{
|
||||||
|
// Save the current state
|
||||||
|
unsigned int currentBuffer = 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( currentBuffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
VECTOR2D CAIRO_GAL::ComputeCursorToWorld( const VECTOR2D& aCursorPosition )
|
VECTOR2D CAIRO_GAL::ComputeCursorToWorld( const VECTOR2D& aCursorPosition )
|
||||||
{
|
{
|
||||||
MATRIX3x3D inverseMatrix = worldScreenMatrix.Inverse();
|
MATRIX3x3D inverseMatrix = worldScreenMatrix.Inverse();
|
||||||
|
@ -972,8 +1006,10 @@ void CAIRO_GAL::setCompositor()
|
||||||
compositor->Resize( screenSize.x, screenSize.y );
|
compositor->Resize( screenSize.x, screenSize.y );
|
||||||
|
|
||||||
// Prepare buffers
|
// Prepare buffers
|
||||||
mainBuffer = compositor->GetBuffer();
|
mainBuffer = compositor->CreateBuffer();
|
||||||
overlayBuffer = compositor->GetBuffer();
|
overlayBuffer = compositor->CreateBuffer();
|
||||||
|
|
||||||
|
validCompositor = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ void OPENGL_COMPOSITOR::Initialize()
|
||||||
GL_RENDERBUFFER, m_depthBuffer );
|
GL_RENDERBUFFER, m_depthBuffer );
|
||||||
|
|
||||||
// Unbind the framebuffer, so by default all the rendering goes directly to the display
|
// Unbind the framebuffer, so by default all the rendering goes directly to the display
|
||||||
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
glBindFramebuffer( GL_FRAMEBUFFER, DIRECT_RENDERING );
|
||||||
m_currentFbo = 0;
|
m_currentFbo = 0;
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
|
@ -91,7 +91,7 @@ void OPENGL_COMPOSITOR::Resize( unsigned int aWidth, unsigned int aHeight )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned int OPENGL_COMPOSITOR::GetBuffer()
|
unsigned int OPENGL_COMPOSITOR::CreateBuffer()
|
||||||
{
|
{
|
||||||
wxASSERT( m_initialized );
|
wxASSERT( m_initialized );
|
||||||
|
|
||||||
|
@ -169,8 +169,8 @@ unsigned int OPENGL_COMPOSITOR::GetBuffer()
|
||||||
|
|
||||||
|
|
||||||
ClearBuffer();
|
ClearBuffer();
|
||||||
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
glBindFramebuffer( GL_FRAMEBUFFER, DIRECT_RENDERING );
|
||||||
m_currentFbo = 0;
|
m_currentFbo = DIRECT_RENDERING;
|
||||||
|
|
||||||
// Store the new buffer
|
// Store the new buffer
|
||||||
OPENGL_BUFFER buffer = { textureTarget, attachmentPoint };
|
OPENGL_BUFFER buffer = { textureTarget, attachmentPoint };
|
||||||
|
@ -186,10 +186,10 @@ void OPENGL_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Change the rendering destination to the selected attachment point
|
// Change the rendering destination to the selected attachment point
|
||||||
if( aBufferHandle == 0 )
|
if( aBufferHandle == DIRECT_RENDERING )
|
||||||
{
|
{
|
||||||
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
glBindFramebuffer( GL_FRAMEBUFFER, DIRECT_RENDERING );
|
||||||
m_currentFbo = 0;
|
m_currentFbo = DIRECT_RENDERING;
|
||||||
}
|
}
|
||||||
else if( m_currentFbo != m_framebuffer )
|
else if( m_currentFbo != m_framebuffer )
|
||||||
{
|
{
|
||||||
|
@ -197,7 +197,7 @@ void OPENGL_COMPOSITOR::SetBuffer( unsigned int aBufferHandle )
|
||||||
m_currentFbo = m_framebuffer;
|
m_currentFbo = m_framebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( m_currentFbo != 0 && m_current != aBufferHandle - 1 )
|
if( m_currentFbo != DIRECT_RENDERING )
|
||||||
{
|
{
|
||||||
m_current = aBufferHandle - 1;
|
m_current = aBufferHandle - 1;
|
||||||
glDrawBuffer( m_buffers[m_current].attachmentPoint );
|
glDrawBuffer( m_buffers[m_current].attachmentPoint );
|
||||||
|
@ -219,8 +219,8 @@ void OPENGL_COMPOSITOR::DrawBuffer( unsigned int aBufferHandle )
|
||||||
wxASSERT( m_initialized );
|
wxASSERT( m_initialized );
|
||||||
|
|
||||||
// Switch to the main framebuffer and blit the scene
|
// Switch to the main framebuffer and blit the scene
|
||||||
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
glBindFramebuffer( GL_FRAMEBUFFER, DIRECT_RENDERING );
|
||||||
m_currentFbo = 0;
|
m_currentFbo = DIRECT_RENDERING;
|
||||||
|
|
||||||
// Depth test has to be disabled to make transparency working
|
// Depth test has to be disabled to make transparency working
|
||||||
glDisable( GL_DEPTH_TEST );
|
glDisable( GL_DEPTH_TEST );
|
||||||
|
@ -279,4 +279,4 @@ void OPENGL_COMPOSITOR::clean()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GLuint OPENGL_COMPOSITOR::m_currentFbo = 0;
|
GLuint OPENGL_COMPOSITOR::m_currentFbo = DIRECT_RENDERING;
|
||||||
|
|
|
@ -130,8 +130,8 @@ void OPENGL_GAL::BeginDrawing()
|
||||||
|
|
||||||
// Prepare rendering target buffers
|
// Prepare rendering target buffers
|
||||||
compositor.Initialize();
|
compositor.Initialize();
|
||||||
mainBuffer = compositor.GetBuffer();
|
mainBuffer = compositor.CreateBuffer();
|
||||||
overlayBuffer = compositor.GetBuffer();
|
overlayBuffer = compositor.CreateBuffer();
|
||||||
|
|
||||||
isFramebufferInitialized = true;
|
isFramebufferInitialized = true;
|
||||||
}
|
}
|
||||||
|
@ -187,13 +187,10 @@ void OPENGL_GAL::BeginDrawing()
|
||||||
SetFillColor( fillColor );
|
SetFillColor( fillColor );
|
||||||
SetStrokeColor( strokeColor );
|
SetStrokeColor( strokeColor );
|
||||||
|
|
||||||
// Prepare buffers for drawing
|
// Unbind buffers - set compositor for direct drawing
|
||||||
compositor.SetBuffer( mainBuffer );
|
compositor.SetBuffer( OPENGL_COMPOSITOR::DIRECT_RENDERING );
|
||||||
compositor.ClearBuffer();
|
|
||||||
compositor.SetBuffer( overlayBuffer );
|
|
||||||
compositor.ClearBuffer();
|
|
||||||
compositor.SetBuffer( 0 ); // Unbind buffers
|
|
||||||
|
|
||||||
|
// Remove all previously stored items
|
||||||
nonCachedManager.Clear();
|
nonCachedManager.Clear();
|
||||||
overlayManager.Clear();
|
overlayManager.Clear();
|
||||||
|
|
||||||
|
@ -711,6 +708,31 @@ RenderTarget OPENGL_GAL::GetTarget() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OPENGL_GAL::ClearTarget( RenderTarget 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
VECTOR2D OPENGL_GAL::ComputeCursorToWorld( const VECTOR2D& aCursorPosition )
|
VECTOR2D OPENGL_GAL::ComputeCursorToWorld( const VECTOR2D& aCursorPosition )
|
||||||
{
|
{
|
||||||
VECTOR2D cursorPosition = aCursorPosition;
|
VECTOR2D cursorPosition = aCursorPosition;
|
||||||
|
|
|
@ -39,6 +39,28 @@
|
||||||
|
|
||||||
using namespace KiGfx;
|
using namespace KiGfx;
|
||||||
|
|
||||||
|
VIEW::VIEW( bool aIsDynamic ) :
|
||||||
|
m_enableOrderModifier( false ),
|
||||||
|
m_scale( 1.0 ),
|
||||||
|
m_painter( NULL ),
|
||||||
|
m_gal( NULL ),
|
||||||
|
m_dynamic( aIsDynamic )
|
||||||
|
{
|
||||||
|
// Redraw everything at the beginning
|
||||||
|
for( int i = 0; i < TARGETS_NUMBER; ++i )
|
||||||
|
SetTargetDirty( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VIEW::~VIEW()
|
||||||
|
{
|
||||||
|
BOOST_FOREACH( LayerMap::value_type& l, m_layers )
|
||||||
|
{
|
||||||
|
delete l.second.items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void VIEW::AddLayer( int aLayer, bool aDisplayOnly )
|
void VIEW::AddLayer( int aLayer, bool aDisplayOnly )
|
||||||
{
|
{
|
||||||
if( m_layers.find( aLayer ) == m_layers.end() )
|
if( m_layers.find( aLayer ) == m_layers.end() )
|
||||||
|
@ -197,25 +219,6 @@ int VIEW::Query( const BOX2I& aRect, std::vector<LayerItemPair>& aResult )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
VIEW::VIEW( bool aIsDynamic ) :
|
|
||||||
m_enableOrderModifier( false ),
|
|
||||||
m_scale( 1.0 ),
|
|
||||||
m_painter( NULL ),
|
|
||||||
m_gal( NULL ),
|
|
||||||
m_dynamic( aIsDynamic )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
VIEW::~VIEW()
|
|
||||||
{
|
|
||||||
BOOST_FOREACH( LayerMap::value_type& l, m_layers )
|
|
||||||
{
|
|
||||||
delete l.second.items;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
VECTOR2D VIEW::ToWorld( const VECTOR2D& aCoord, bool aAbsolute ) const
|
VECTOR2D VIEW::ToWorld( const VECTOR2D& aCoord, bool aAbsolute ) const
|
||||||
{
|
{
|
||||||
MATRIX3x3D matrix = m_gal->GetWorldScreenMatrix().Inverse();
|
MATRIX3x3D matrix = m_gal->GetWorldScreenMatrix().Inverse();
|
||||||
|
@ -267,6 +270,11 @@ void VIEW::SetGAL( GAL* aGal )
|
||||||
// clear group numbers, so everything is going to be recached
|
// clear group numbers, so everything is going to be recached
|
||||||
clearGroupCache();
|
clearGroupCache();
|
||||||
|
|
||||||
|
// every target has to be refreshed
|
||||||
|
SetTargetDirty( TARGET_CACHED );
|
||||||
|
SetTargetDirty( TARGET_NONCACHED );
|
||||||
|
SetTargetDirty( TARGET_OVERLAY );
|
||||||
|
|
||||||
// force the new GAL to display the current viewport.
|
// force the new GAL to display the current viewport.
|
||||||
SetCenter( m_center );
|
SetCenter( m_center );
|
||||||
SetScale( m_scale );
|
SetScale( m_scale );
|
||||||
|
@ -326,6 +334,10 @@ void VIEW::SetScale( double aScale, const VECTOR2D& aAnchor )
|
||||||
|
|
||||||
SetCenter( m_center - delta );
|
SetCenter( m_center - delta );
|
||||||
m_scale = aScale;
|
m_scale = aScale;
|
||||||
|
|
||||||
|
// Redraw everything after the viewport has changed
|
||||||
|
SetTargetDirty( TARGET_CACHED );
|
||||||
|
SetTargetDirty( TARGET_NONCACHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -334,6 +346,10 @@ void VIEW::SetCenter( const VECTOR2D& aCenter )
|
||||||
m_center = aCenter;
|
m_center = aCenter;
|
||||||
m_gal->SetLookAtPoint( m_center );
|
m_gal->SetLookAtPoint( m_center );
|
||||||
m_gal->ComputeWorldScreenMatrix();
|
m_gal->ComputeWorldScreenMatrix();
|
||||||
|
|
||||||
|
// Redraw everything after the viewport has changed
|
||||||
|
SetTargetDirty( TARGET_CACHED );
|
||||||
|
SetTargetDirty( TARGET_NONCACHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,6 +363,8 @@ void VIEW::sortLayers()
|
||||||
m_orderedLayers[n++] = &i->second;
|
m_orderedLayers[n++] = &i->second;
|
||||||
|
|
||||||
sort( m_orderedLayers.begin(), m_orderedLayers.end(), compareRenderingOrder );
|
sort( m_orderedLayers.begin(), m_orderedLayers.end(), compareRenderingOrder );
|
||||||
|
|
||||||
|
SetTargetDirty( TARGET_CACHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -554,15 +572,15 @@ void VIEW::redrawRect( const BOX2I& aRect )
|
||||||
{
|
{
|
||||||
BOOST_FOREACH( VIEW_LAYER* l, m_orderedLayers )
|
BOOST_FOREACH( VIEW_LAYER* l, m_orderedLayers )
|
||||||
{
|
{
|
||||||
if( l->enabled && areRequiredLayersEnabled( l->id ) )
|
if( l->enabled && isTargetDirty( l->target ) && areRequiredLayersEnabled( l->id ) )
|
||||||
{
|
{
|
||||||
drawItem drawFunc( this, l );
|
drawItem drawFunc( this, l );
|
||||||
|
|
||||||
m_gal->SetTarget( l->target );
|
m_gal->SetTarget( l->target );
|
||||||
m_gal->SetLayerDepth( l->renderingOrder );
|
m_gal->SetLayerDepth( l->renderingOrder );
|
||||||
l->items->Query( aRect, drawFunc );
|
l->items->Query( aRect, drawFunc );
|
||||||
l->isDirty = false;
|
|
||||||
}
|
}
|
||||||
|
l->isDirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,9 +666,27 @@ void VIEW::Redraw()
|
||||||
VECTOR2D screenSize = m_gal->GetScreenPixelSize();
|
VECTOR2D screenSize = m_gal->GetScreenPixelSize();
|
||||||
BOX2I rect( ToWorld( VECTOR2D( 0, 0 ) ),
|
BOX2I rect( ToWorld( VECTOR2D( 0, 0 ) ),
|
||||||
ToWorld( screenSize ) - ToWorld( VECTOR2D( 0, 0 ) ) );
|
ToWorld( screenSize ) - ToWorld( VECTOR2D( 0, 0 ) ) );
|
||||||
|
|
||||||
rect.Normalize();
|
rect.Normalize();
|
||||||
|
|
||||||
|
if( isTargetDirty( TARGET_CACHED ) || isTargetDirty( TARGET_NONCACHED ) )
|
||||||
|
{
|
||||||
|
// TARGET_CACHED and TARGET_NONCACHED have to be redrawn together, as they contain
|
||||||
|
// layers that rely on each other (eg. netnames are noncached, but tracks - are cached)
|
||||||
|
m_gal->ClearTarget( TARGET_NONCACHED );
|
||||||
|
m_gal->ClearTarget( TARGET_CACHED );
|
||||||
|
|
||||||
|
SetTargetDirty( TARGET_NONCACHED );
|
||||||
|
SetTargetDirty( TARGET_CACHED );
|
||||||
|
|
||||||
|
m_gal->DrawGrid();
|
||||||
|
}
|
||||||
|
m_gal->ClearTarget( TARGET_OVERLAY );
|
||||||
|
|
||||||
redrawRect( rect );
|
redrawRect( rect );
|
||||||
|
|
||||||
|
// All targets were redrawn, so nothing is dirty
|
||||||
|
SetTargetDirty( TARGET_CACHED, false );
|
||||||
|
SetTargetDirty( TARGET_NONCACHED, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -801,3 +837,19 @@ void VIEW::RecacheAllItems( bool aImmediately )
|
||||||
wxLogDebug( wxT( "RecacheAllItems::%.1f ms" ), (double) totalRealTime.value / 1000.0 );
|
wxLogDebug( wxT( "RecacheAllItems::%.1f ms" ), (double) totalRealTime.value / 1000.0 );
|
||||||
#endif /* __WXDEBUG__ */
|
#endif /* __WXDEBUG__ */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool VIEW::isTargetDirty( int aTarget ) const
|
||||||
|
{
|
||||||
|
wxASSERT( aTarget < TARGETS_NUMBER );
|
||||||
|
|
||||||
|
// Check if any of layers belonging to the target is dirty
|
||||||
|
BOOST_FOREACH( VIEW_LAYER* l, m_orderedLayers )
|
||||||
|
{
|
||||||
|
if( l->target == aTarget && l->isDirty )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no layer is dirty, just check the target status itself
|
||||||
|
return m_dirtyTargets[aTarget];
|
||||||
|
}
|
||||||
|
|
|
@ -50,8 +50,14 @@ public:
|
||||||
/// @copydoc COMPOSITOR::Resize()
|
/// @copydoc COMPOSITOR::Resize()
|
||||||
virtual void Resize( unsigned int aWidth, unsigned int aHeight );
|
virtual void Resize( unsigned int aWidth, unsigned int aHeight );
|
||||||
|
|
||||||
|
/// @copydoc COMPOSITOR::CreateBuffer()
|
||||||
|
virtual unsigned int CreateBuffer();
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::GetBuffer()
|
/// @copydoc COMPOSITOR::GetBuffer()
|
||||||
virtual unsigned int GetBuffer();
|
inline virtual unsigned int GetBuffer() const
|
||||||
|
{
|
||||||
|
return m_current + 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::SetBuffer()
|
/// @copydoc COMPOSITOR::SetBuffer()
|
||||||
virtual void SetBuffer( unsigned int aBufferHandle );
|
virtual void SetBuffer( unsigned int aBufferHandle );
|
||||||
|
@ -62,6 +68,21 @@ public:
|
||||||
/// @copydoc COMPOSITOR::DrawBuffer()
|
/// @copydoc COMPOSITOR::DrawBuffer()
|
||||||
virtual void DrawBuffer( unsigned int aBufferHandle );
|
virtual void DrawBuffer( unsigned int aBufferHandle );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function SetMainContext()
|
||||||
|
* Sets a context to be treated as the main context (ie. as a target of buffers rendering and
|
||||||
|
* as a source of settings for newly created buffers).
|
||||||
|
*
|
||||||
|
* @param aMainContext is the context that should be treated as the main one.
|
||||||
|
*/
|
||||||
|
inline virtual void SetMainContext( cairo_t* aMainContext )
|
||||||
|
{
|
||||||
|
m_mainContext = aMainContext;
|
||||||
|
|
||||||
|
// Use the context's transformation matrix
|
||||||
|
cairo_get_matrix( m_mainContext, &m_matrix );
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef boost::shared_array<unsigned int> BitmapPtr;
|
typedef boost::shared_array<unsigned int> BitmapPtr;
|
||||||
typedef struct
|
typedef struct
|
||||||
|
@ -71,7 +92,7 @@ protected:
|
||||||
BitmapPtr bitmap; ///< Pixel storage
|
BitmapPtr bitmap; ///< Pixel storage
|
||||||
} CAIRO_BUFFER;
|
} CAIRO_BUFFER;
|
||||||
|
|
||||||
unsigned int m_current; ///< Currently used buffer handle
|
unsigned int m_current; ///< Currently used buffer handle
|
||||||
typedef std::deque<CAIRO_BUFFER> CAIRO_BUFFERS;
|
typedef std::deque<CAIRO_BUFFER> CAIRO_BUFFERS;
|
||||||
|
|
||||||
/// Pointer to the current context, so it can be changed
|
/// Pointer to the current context, so it can be changed
|
||||||
|
|
|
@ -221,6 +221,9 @@ public:
|
||||||
/// @copydoc GAL::GetTarget()
|
/// @copydoc GAL::GetTarget()
|
||||||
virtual RenderTarget GetTarget() const;
|
virtual RenderTarget GetTarget() const;
|
||||||
|
|
||||||
|
/// @copydoc GAL::ClearTarget()
|
||||||
|
virtual void ClearTarget( RenderTarget aTarget );
|
||||||
|
|
||||||
// -------
|
// -------
|
||||||
// Cursor
|
// Cursor
|
||||||
// -------
|
// -------
|
||||||
|
@ -267,6 +270,7 @@ private:
|
||||||
unsigned int mainBuffer; ///< Handle to the main buffer
|
unsigned int mainBuffer; ///< Handle to the main buffer
|
||||||
unsigned int overlayBuffer; ///< Handle to the overlay buffer
|
unsigned int overlayBuffer; ///< Handle to the overlay buffer
|
||||||
RenderTarget currentTarget; ///< Current rendering target
|
RenderTarget currentTarget; ///< Current rendering target
|
||||||
|
bool validCompositor; ///< Compositor initialization flag
|
||||||
|
|
||||||
// Variables related to wxWidgets
|
// Variables related to wxWidgets
|
||||||
wxWindow* parentWindow; ///< Parent window
|
wxWindow* parentWindow; ///< Parent window
|
||||||
|
|
|
@ -57,12 +57,20 @@ public:
|
||||||
virtual void Resize( unsigned int aWidth, unsigned int aHeight ) = 0;
|
virtual void Resize( unsigned int aWidth, unsigned int aHeight ) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function GetBuffer()
|
* Function CreateBuffer()
|
||||||
* prepares a new buffer that may be used as a rendering target.
|
* prepares a new buffer that may be used as a rendering target.
|
||||||
*
|
*
|
||||||
* @return is the handle of the buffer. In case of failure 0 (zero) is returned as the handle.
|
* @return is the handle of the buffer. In case of failure 0 (zero) is returned as the handle.
|
||||||
*/
|
*/
|
||||||
virtual unsigned int GetBuffer() = 0;
|
virtual unsigned int CreateBuffer() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function GetBuffer()
|
||||||
|
* returns currently used buffer handle.
|
||||||
|
*
|
||||||
|
* @return Currently used buffer handle.
|
||||||
|
*/
|
||||||
|
virtual unsigned int GetBuffer() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function SetBuffer()
|
* Function SetBuffer()
|
||||||
|
|
|
@ -42,6 +42,9 @@ namespace KiGfx
|
||||||
TARGET_NONCACHED, ///< Auxiliary rendering target (noncached)
|
TARGET_NONCACHED, ///< Auxiliary rendering target (noncached)
|
||||||
TARGET_OVERLAY ///< Items that may change while the view stays the same (noncached)
|
TARGET_OVERLAY ///< Items that may change while the view stays the same (noncached)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Number of available rendering targets
|
||||||
|
static const int TARGETS_NUMBER = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* DEFINITIONS_H_ */
|
#endif /* DEFINITIONS_H_ */
|
||||||
|
|
|
@ -579,6 +579,13 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual RenderTarget GetTarget() const = 0;
|
virtual RenderTarget GetTarget() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears the target for rendering.
|
||||||
|
*
|
||||||
|
* @param aTarget is the target to be cleared.
|
||||||
|
*/
|
||||||
|
virtual void ClearTarget( RenderTarget aTarget ) = 0;
|
||||||
|
|
||||||
// -------------
|
// -------------
|
||||||
// Grid methods
|
// Grid methods
|
||||||
// -------------
|
// -------------
|
||||||
|
|
|
@ -49,18 +49,30 @@ public:
|
||||||
/// @copydoc COMPOSITOR::Resize()
|
/// @copydoc COMPOSITOR::Resize()
|
||||||
virtual void Resize( unsigned int aWidth, unsigned int aHeight );
|
virtual void Resize( unsigned int aWidth, unsigned int aHeight );
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::GetBuffer()
|
/// @copydoc COMPOSITOR::CreateBuffer()
|
||||||
virtual unsigned int GetBuffer();
|
virtual unsigned int CreateBuffer();
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::SetBuffer()
|
/// @copydoc COMPOSITOR::SetBuffer()
|
||||||
virtual void SetBuffer( unsigned int aBufferHandle );
|
virtual void SetBuffer( unsigned int aBufferHandle );
|
||||||
|
|
||||||
|
/// @copydoc COMPOSITOR::GetBuffer()
|
||||||
|
inline virtual unsigned int GetBuffer() const
|
||||||
|
{
|
||||||
|
if( m_currentFbo == DIRECT_RENDERING )
|
||||||
|
return DIRECT_RENDERING;
|
||||||
|
|
||||||
|
return m_current + 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::ClearBuffer()
|
/// @copydoc COMPOSITOR::ClearBuffer()
|
||||||
virtual void ClearBuffer();
|
virtual void ClearBuffer();
|
||||||
|
|
||||||
/// @copydoc COMPOSITOR::DrawBuffer()
|
/// @copydoc COMPOSITOR::DrawBuffer()
|
||||||
virtual void DrawBuffer( unsigned int aBufferHandle );
|
virtual void DrawBuffer( unsigned int aBufferHandle );
|
||||||
|
|
||||||
|
// Constant used by glBindFramebuffer to turn off rendering to framebuffers
|
||||||
|
static const unsigned int DIRECT_RENDERING = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
|
|
@ -214,6 +214,9 @@ public:
|
||||||
/// @copydoc GAL::GetTarget()
|
/// @copydoc GAL::GetTarget()
|
||||||
virtual RenderTarget GetTarget() const;
|
virtual RenderTarget GetTarget() const;
|
||||||
|
|
||||||
|
/// @copydoc GAL::ClearTarget()
|
||||||
|
virtual void ClearTarget( RenderTarget aTarget );
|
||||||
|
|
||||||
// -------
|
// -------
|
||||||
// Cursor
|
// Cursor
|
||||||
// -------
|
// -------
|
||||||
|
|
|
@ -392,6 +392,20 @@ public:
|
||||||
*/
|
*/
|
||||||
bool IsDirty() const;
|
bool IsDirty() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function SetTargetDirty()
|
||||||
|
* Sets or clears target 'dirty' flag.
|
||||||
|
* @param aTarget is the target to set.
|
||||||
|
* @param aState says if the flag should be set or cleared.
|
||||||
|
*/
|
||||||
|
inline void SetTargetDirty( int aTarget, bool aState = true )
|
||||||
|
{
|
||||||
|
wxASSERT( aTarget < TARGETS_NUMBER );
|
||||||
|
|
||||||
|
m_dirtyTargets[aTarget] = aState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static const int VIEW_MAX_LAYERS = 128; ///* maximum number of layers that may be shown
|
static const int VIEW_MAX_LAYERS = 128; ///* maximum number of layers that may be shown
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -462,6 +476,14 @@ private:
|
||||||
return ( m_layers.at( aLayer ).target == TARGET_CACHED );
|
return ( m_layers.at( aLayer ).target == TARGET_CACHED );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function isTargetDirty()
|
||||||
|
* Returns true if any of layers belonging to the target or the target itself should be
|
||||||
|
* redrawn.
|
||||||
|
* @return True if the above condition is fulfilled.
|
||||||
|
*/
|
||||||
|
bool isTargetDirty( int aTarget ) const;
|
||||||
|
|
||||||
/// Contains set of possible displayed layers and its properties
|
/// Contains set of possible displayed layers and its properties
|
||||||
LayerMap m_layers;
|
LayerMap m_layers;
|
||||||
|
|
||||||
|
@ -487,6 +509,9 @@ private:
|
||||||
/// static (eg. image/PDF) - does not.
|
/// static (eg. image/PDF) - does not.
|
||||||
bool m_dynamic;
|
bool m_dynamic;
|
||||||
|
|
||||||
|
/// Flags to mark targets as dirty, so they have to be redrawn on the next refresh event
|
||||||
|
bool m_dirtyTargets[TARGETS_NUMBER];
|
||||||
|
|
||||||
/// Rendering order modifier for layers that are marked as top layers
|
/// Rendering order modifier for layers that are marked as top layers
|
||||||
static const int TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS;
|
static const int TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue