Alternative way of handling OpenGL initialization & errors

Some faults could result in a crash, as they were not properly
handled. Now the rendering loop is wrapped with try..catch block
which will revert to Cairo in case of an error and display an
error message.

Fixes: lp:1655766
* https://bugs.launchpad.net/kicad/+bug/1655766
This commit is contained in:
Maciej Suminski 2017-01-13 16:48:23 +01:00
parent ca085de6aa
commit 88eb648cbb
4 changed files with 120 additions and 163 deletions

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2016 CERN
* Copyright (C) 2013-2017 CERN
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
@ -175,25 +175,40 @@ void EDA_DRAW_PANEL_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
m_view->UpdateItems();
m_gal->BeginDrawing();
m_gal->ClearScreen( settings->GetBackgroundColor() );
KIGFX::COLOR4D gridColor = settings->GetLayerColor( ITEM_GAL_LAYER( GRID_VISIBLE ) );
m_gal->SetGridColor( gridColor );
if( m_view->IsDirty() )
try
{
m_view->ClearTargets();
m_gal->BeginDrawing();
m_gal->ClearScreen( settings->GetBackgroundColor() );
// Grid has to be redrawn only when the NONCACHED target is redrawn
if( m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED ) )
m_gal->DrawGrid();
KIGFX::COLOR4D gridColor = settings->GetLayerColor( ITEM_GAL_LAYER( GRID_VISIBLE ) );
m_gal->SetGridColor( gridColor );
m_view->Redraw();
if( m_view->IsDirty() )
{
m_view->ClearTargets();
// Grid has to be redrawn only when the NONCACHED target is redrawn
if( m_view->IsTargetDirty( KIGFX::TARGET_NONCACHED ) )
m_gal->DrawGrid();
m_view->Redraw();
}
m_gal->DrawCursor( m_viewControls->GetCursorPosition() );
m_gal->EndDrawing();
}
catch( std::runtime_error& err )
{
assert( GetBackend() != GAL_TYPE_CAIRO );
m_gal->DrawCursor( m_viewControls->GetCursorPosition() );
m_gal->EndDrawing();
// Cairo is supposed to be the safe backend, there is not a single "throw" in its code
SwitchBackend( GAL_TYPE_CAIRO );
if( m_edaFrame )
m_edaFrame->UseGalCanvas( true );
DisplayError( m_parent, wxString( err.what() ) );
}
#ifdef PROFILE
totalRealTime.Stop();

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2016 CERN
* Copyright (C) 2013-2017 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* This program is free software; you can redistribute it and/or
@ -91,6 +91,18 @@ void OPENGL_COMPOSITOR::Initialize()
}
VECTOR2U dims = m_antialiasing->GetInternalBufferSize();
assert( dims.x != 0 && dims.y != 0 );
GLint maxBufSize;
glGetIntegerv( GL_MAX_RENDERBUFFER_SIZE_EXT, &maxBufSize );
// VECTOR2U is unsigned, so no need to check if < 0
if( dims.x > (unsigned) maxBufSize || dims.y >= (unsigned) maxBufSize )
{
throw std::runtime_error(
wxString::Format( "Requested render buffer size is %ux%u, but the max supported size is %dx%d",
dims.x, dims.y, maxBufSize, maxBufSize ) );
}
// We need framebuffer objects for drawing the screen contents
// Generate framebuffer and a depth buffer

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
* Copyright (C) 2012-2016 Kicad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2013-2016 CERN
* Copyright (C) 2013-2017 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* Graphics Abstraction Layer (GAL) for OpenGL
@ -82,24 +82,17 @@ OPENGL_GAL::OPENGL_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
++instanceCounter;
// Check if OpenGL requirements are met. The test procedure also initializes a few parts
// of the OpenGL backend (e.g. GLEW), so it is required that it is invoked before any GL calls.
runTest();
// Make VBOs use shaders
cachedManager = new VERTEX_MANAGER( true );
cachedManager->SetShader( *shader );
nonCachedManager = new VERTEX_MANAGER( false );
nonCachedManager->SetShader( *shader );
overlayManager = new VERTEX_MANAGER( false );
overlayManager->SetShader( *shader );
compositor = new OPENGL_COMPOSITOR;
compositor->SetAntialiasingMode( options.gl_antialiasing_mode );
cachedManager = new VERTEX_MANAGER( true );
nonCachedManager = new VERTEX_MANAGER( false );
overlayManager = new VERTEX_MANAGER( false );
// Initialize the flags
isFramebufferInitialized = false;
isBitmapFontInitialized = false;
isInitialized = false;
isGrouping = false;
groupCounter = 0;
@ -191,6 +184,7 @@ OPENGL_GAL::~OPENGL_GAL()
}
void OPENGL_GAL::OnGalDisplayOptionsChanged( const GAL_DISPLAY_OPTIONS& aDisplayOptions )
{
if( options.gl_antialiasing_mode != compositor->GetAntialiasingMode() )
@ -201,6 +195,7 @@ void OPENGL_GAL::OnGalDisplayOptionsChanged( const GAL_DISPLAY_OPTIONS& aDisplay
}
}
void OPENGL_GAL::BeginDrawing()
{
if( !IsShownOnScreen() )
@ -210,6 +205,9 @@ void OPENGL_GAL::BeginDrawing()
PROF_COUNTER totalRealTime( "OPENGL_GAL::BeginDrawing()", true );
#endif /* __WXDEBUG__ */
if( !isInitialized )
init();
GL_CONTEXT_MANAGER::Get().LockCtx( glPrivContext, this );
// Set up the view port
@ -221,10 +219,18 @@ void OPENGL_GAL::BeginDrawing()
if( !isFramebufferInitialized )
{
// Prepare rendering target buffers
compositor->Initialize();
mainBuffer = compositor->CreateBuffer();
overlayBuffer = compositor->CreateBuffer();
try
{
// Prepare rendering target buffers
compositor->Initialize();
mainBuffer = compositor->CreateBuffer();
overlayBuffer = compositor->CreateBuffer();
}
catch( std::runtime_error& e )
{
GL_CONTEXT_MANAGER::Get().UnlockCtx( glPrivContext );
throw; // DRAW_PANEL_GAL will handle it
}
isFramebufferInitialized = true;
}
@ -363,6 +369,12 @@ void OPENGL_GAL::EndDrawing()
void OPENGL_GAL::BeginUpdate()
{
if( !IsShownOnScreen() )
return;
if( !isInitialized )
init();
GL_CONTEXT_MANAGER::Get().LockCtx( glPrivContext, this );
cachedManager->Map();
}
@ -370,6 +382,9 @@ void OPENGL_GAL::BeginUpdate()
void OPENGL_GAL::EndUpdate()
{
if( !isInitialized )
return;
cachedManager->Unmap();
GL_CONTEXT_MANAGER::Get().UnlockCtx( glPrivContext );
}
@ -1500,88 +1515,42 @@ unsigned int OPENGL_GAL::getNewGroupNumber()
}
bool OPENGL_GAL::runTest()
void OPENGL_GAL::init()
{
static bool tested = false;
static bool testResult = false;
std::string errorMessage = "OpenGL test failed";
wxASSERT( IsShownOnScreen() );
if( !tested )
GL_CONTEXT_MANAGER::Get().LockCtx( glPrivContext, this );
GLenum err = glewInit();
try
{
wxDialog dlgtest( GetParent(), -1, wxT( "opengl test" ), wxPoint( 50, 50 ),
wxDLG_UNIT( GetParent(), wxSize( 50, 50 ) ) );
OPENGL_TEST* test = new OPENGL_TEST( &dlgtest, this, glPrivContext );
dlgtest.Raise(); // on Linux, on some windows managers (Unity for instance) this is needed to actually show the dialog
dlgtest.ShowModal();
testResult = test->IsOk();
if( !testResult )
errorMessage = test->GetError();
}
if( !testResult )
throw std::runtime_error( errorMessage );
return testResult;
}
OPENGL_GAL::OPENGL_TEST::OPENGL_TEST( wxDialog* aParent, OPENGL_GAL* aGal, wxGLContext* aContext ) :
wxGLCanvas( aParent, wxID_ANY, glAttributes, wxDefaultPosition,
wxDefaultSize, 0, wxT( "GLCanvas" ) ),
m_parent( aParent ), m_gal( aGal ), m_context( aContext ), m_tested( false ), m_result( false )
{
m_timeoutTimer.SetOwner( this );
Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) );
Connect( wxEVT_TIMER, wxTimerEventHandler( OPENGL_GAL::OPENGL_TEST::OnTimeout ) );
m_parent->Connect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::OnDialogPaint ), NULL, this );
}
void OPENGL_GAL::OPENGL_TEST::Render( wxPaintEvent& WXUNUSED( aEvent ) )
{
if( !m_tested )
{
if( !IsShownOnScreen() )
return;
m_timeoutTimer.Stop();
m_result = true; // Assume everything is fine, until proven otherwise
// One test is enough - close the testing dialog when the test is finished
Disconnect( wxEVT_PAINT, wxPaintEventHandler( OPENGL_GAL::OPENGL_TEST::Render ) );
CallAfter( std::bind( &wxDialog::EndModal, m_parent, wxID_NONE ) );
GL_CONTEXT_MANAGER::Get().LockCtx( m_context, this );
GLenum err = glewInit();
if( GLEW_OK != err )
error( (const char*) glewGetErrorString( err ) );
throw std::runtime_error( (const char*) glewGetErrorString( err ) );
// Check the OpenGL version (minimum 2.1 is required)
else if( !GLEW_VERSION_2_1 )
error( "OpenGL 2.1 or higher is required!" );
if( !GLEW_VERSION_2_1 )
throw std::runtime_error( "OpenGL 2.1 or higher is required!" );
// Framebuffers have to be supported
else if( !GLEW_EXT_framebuffer_object )
error( "Framebuffer objects are not supported!" );
if( !GLEW_EXT_framebuffer_object )
throw std::runtime_error( "Framebuffer objects are not supported!" );
// Vertex buffer has to be supported
else if( !GLEW_ARB_vertex_buffer_object )
error( "Vertex buffer objects are not supported!" );
if( !GLEW_ARB_vertex_buffer_object )
throw std::runtime_error( "Vertex buffer objects are not supported!" );
// Prepare shaders
else if( !m_gal->shader->IsLinked() && !m_gal->shader->LoadShaderFromStrings( SHADER_TYPE_VERTEX, BUILTIN_SHADERS::kicad_vertex_shader ) )
error( "Cannot compile vertex shader!" );
if( !shader->IsLinked() && !shader->LoadShaderFromStrings( SHADER_TYPE_VERTEX, BUILTIN_SHADERS::kicad_vertex_shader ) )
throw std::runtime_error( "Cannot compile vertex shader!" );
else if( !m_gal->shader->IsLinked() && !m_gal->shader->LoadShaderFromStrings(SHADER_TYPE_FRAGMENT, BUILTIN_SHADERS::kicad_fragment_shader ) )
error( "Cannot compile fragment shader!" );
if( !shader->IsLinked() && !shader->LoadShaderFromStrings( SHADER_TYPE_FRAGMENT, BUILTIN_SHADERS::kicad_fragment_shader ) )
throw std::runtime_error( "Cannot compile fragment shader!" );
else if( !m_gal->shader->IsLinked() && !m_gal->shader->Link() )
error( "Cannot link the shaders!" );
if( !shader->IsLinked() && !shader->Link() )
throw std::runtime_error( "Cannot link the shaders!" );
// Check if video card supports textures big enough to fit font atlas
// Check if video card supports textures big enough to fit the font atlas
int maxTextureSize;
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxTextureSize );
@ -1589,41 +1558,25 @@ void OPENGL_GAL::OPENGL_TEST::Render( wxPaintEvent& WXUNUSED( aEvent ) )
{
// TODO implement software texture scaling
// for bitmap fonts and use a higher resolution texture?
error( "Requested texture size is not supported" );
throw std::runtime_error( "Requested texture size is not supported" );
}
GL_CONTEXT_MANAGER::Get().UnlockCtx( m_context );
m_tested = true;
}
catch( std::runtime_error& e )
{
GL_CONTEXT_MANAGER::Get().UnlockCtx( glPrivContext );
throw;
}
// Make VBOs use shaders
cachedManager->SetShader( *shader );
nonCachedManager->SetShader( *shader );
overlayManager->SetShader( *shader );
GL_CONTEXT_MANAGER::Get().UnlockCtx( glPrivContext );
isInitialized = true;
}
void OPENGL_GAL::OPENGL_TEST::OnTimeout( wxTimerEvent& aEvent )
{
error( "Could not create OpenGL canvas" );
m_parent->EndModal( wxID_NONE );
}
void OPENGL_GAL::OPENGL_TEST::OnDialogPaint( wxPaintEvent& aEvent )
{
// GL canvas may never appear on the screen (e.g. due to missing GL extensions), and the test
// will not be run. Therefore give at most a second to perform the test, otherwise we conclude
// it has failed.
// Also, wxWidgets OnShow event is triggered before a window is shown, therefore here we use
// OnPaint event, which is executed when a window is actually visible.
m_timeoutTimer.StartOnce( 1000 );
}
void OPENGL_GAL::OPENGL_TEST::error( const std::string& aError )
{
m_timeoutTimer.Stop();
m_result = false;
m_tested = true;
m_error = aError;
}
// ------------------------------------- // Callback functions for the tesselator // ------------------------------------- // Compare Redbook Chapter 11
void CALLBACK VertexCallback( GLvoid* aVertexPtr, void* aData )
{

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
* Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
* Copyright (C) 2013-2016 CERN
* Copyright (C) 2013-2017 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* Graphics Abstraction Layer (GAL) for OpenGL
@ -87,7 +87,11 @@ public:
virtual ~OPENGL_GAL();
/// @copydoc GAL::IsInitialized()
virtual bool IsInitialized() const override { return IsShownOnScreen(); }
virtual bool IsInitialized() const override
{
// is*Initialized flags, but it is enough for OpenGL to show up
return IsShownOnScreen();
}
///> @copydoc GAL::IsVisible()
bool IsVisible() const override
@ -312,6 +316,8 @@ private:
bool isFramebufferInitialized; ///< Are the framebuffers initialized?
static bool isBitmapFontLoaded; ///< Is the bitmap font texture loaded?
bool isBitmapFontInitialized; ///< Is the shader set to use bitmap fonts?
bool isInitialized; ///< Basic initialization flag, has to be done
///< when the window is visible
bool isGrouping; ///< Was a group started?
// Polygon tesselation
@ -416,38 +422,9 @@ private:
unsigned int getNewGroupNumber();
/**
* @brief Checks if the required OpenGL version and extensions are supported.
* @return true in case of success.
* @brief Basic OpenGL initialization.
*/
bool runTest();
// Helper class to determine OpenGL capabilities
class OPENGL_TEST: public wxGLCanvas
{
public:
OPENGL_TEST( wxDialog* aParent, OPENGL_GAL* aGal, wxGLContext* aContext );
void Render( wxPaintEvent& aEvent );
void OnTimeout( wxTimerEvent& aEvent );
void OnDialogPaint( wxPaintEvent& aEvent );
inline bool IsTested() const { return m_tested; }
inline bool IsOk() const { return m_result && m_tested; }
inline std::string GetError() const { return m_error; }
private:
void error( const std::string& aError );
wxDialog* m_parent;
OPENGL_GAL* m_gal;
wxGLContext* m_context;
bool m_tested;
bool m_result;
std::string m_error;
wxTimer m_timeoutTimer;
};
friend class OPENGL_TEST;
void init();
};
} // namespace KIGFX