diff --git a/doc/platform.dox b/doc/platform.dox index bc58a29b4..fa02d29c0 100644 --- a/doc/platform.dox +++ b/doc/platform.dox @@ -284,8 +284,6 @@ int main(int argc, char** argv) { } // Delete OpenGL context ... - - return 0; } @endcode @@ -319,6 +317,74 @@ target_link_libraries(myapplication Magnum::Context) @endcode +@section platform-windowless-contexts Manually managing windowless contexts + +In case you need to manage windowless OpenGL contexts manually (for example +to use Magnum for data processing in a thread or when having more than one +OpenGL context), there is a possibility to directly use the context wrappers +from windowless applications. Each @ref WindowlessEglApplication "Windowless*Application" +is accompanied by a @ref WindowlessEglContext "Windowless*Context" class that +manages just GL context creation, making it current and destruction. Similarly +to using custom platform toolkits above, the workflow is to first create a GL +context instance, then making it current and finally instantiating the +@ref Platform::Context instance to initialize Magnum. + +Similarly as with the applications, to simplify the porting, the library +provides `Platform::WindowlessGLContext` typedef, but only if just one +windowless application header is included. + +@attention With this approach it is possible to switch between different GL + contexts, but make sure that Magnum is used only with its OpenGL context. + +@code +int main(int argc, char** argv) { + Platform::WindowlessGLContext glContext{{}}; + glContext.makeCurrent(); + Platform::Context context{argc, argv}; + + // Your GL code ... + + // Make another context current + eglMakeCurrent(); + + // Someone else's code ... + + // Make Magnum context current again + glContext.makeCurrent(); + + // Your GL code again ... + + // Magnum context gets destroyed + // Windowless GL context gets destroyed +} +@endcode + +The main purpose of windowless contexts is threaded OpenGL, used for example +for background data processing. The workflow is to create the windowless +context on the main thread, but make it current in the worker thread. This way +the main thread state isn't affected so it can have any other GL context +current (for example for the main application rendering). + +@note Context creation is not thread safe on all platforms, that's why it still + has to be done on the main thread. + +@code +int main() { + Platform::WindowlessGLContext glContext{{}}; + + std::thread worker{[&glContext]{ + glContext.makeCurrent(); + Platform::Context context{0, nullptr}; + + // Use Magnum here ... + }}; + + // Independent main application code here ... + + worker.join(); +} +@endcode + - Next page: @ref types */ } diff --git a/src/Magnum/Platform/WindowlessCglApplication.cpp b/src/Magnum/Platform/WindowlessCglApplication.cpp index 49140e99b..b43ae3455 100644 --- a/src/Magnum/Platform/WindowlessCglApplication.cpp +++ b/src/Magnum/Platform/WindowlessCglApplication.cpp @@ -35,25 +35,7 @@ namespace Magnum { namespace Platform { -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments): WindowlessCglApplication{arguments, Configuration{}} {} -#endif - -WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessCglApplication{arguments, nullptr} { - createContext(configuration); -} - -WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments, std::nullptr_t): _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} - -void WindowlessCglApplication::createContext() { createContext({}); } - -void WindowlessCglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessCglApplication::tryCreateContext(const Configuration&) { - CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessCglApplication::tryCreateContext(): context already created", false); - +WindowlessCglContext::WindowlessCglContext(const Configuration&, Context*) { int formatCount; CGLPixelFormatAttribute attributes32[] = { kCGLPFAAccelerated, @@ -62,7 +44,7 @@ bool WindowlessCglApplication::tryCreateContext(const Configuration&) { CGLPixelFormatAttribute(0) }; if(CGLChoosePixelFormat(attributes32, &_pixelFormat, &formatCount) != kCGLNoError) { - Error() << "Platform::WindowlessCglApplication::tryCreateContext(): cannot choose pixel format for GL 3.2, falling back to 3.0"; + Warning() << "Platform::WindowlessCglContext: cannot choose pixel format for GL 3.2, falling back to 3.0"; CGLPixelFormatAttribute attributes30[] = { kCGLPFAAccelerated, @@ -71,7 +53,7 @@ bool WindowlessCglApplication::tryCreateContext(const Configuration&) { CGLPixelFormatAttribute(0) }; if(CGLChoosePixelFormat(attributes30, &_pixelFormat, &formatCount) != kCGLNoError) { - Error() << "Platform::WindowlessCglApplication::tryCreateContext(): cannot choose pixel format for GL 3.0, falling back to 2.1"; + Warning() << "Platform::WindowlessCglContext: cannot choose pixel format for GL 3.0, falling back to 2.1"; CGLPixelFormatAttribute attributes21[] = { kCGLPFAAccelerated, @@ -80,31 +62,68 @@ bool WindowlessCglApplication::tryCreateContext(const Configuration&) { CGLPixelFormatAttribute(0) }; if(CGLChoosePixelFormat(attributes21, &_pixelFormat, &formatCount) != kCGLNoError) { - Error() << "Platform::WindowlessCglApplication::tryCreateContext(): cannot choose pixel format"; - return false; + Error() << "Platform::WindowlessCglContext: cannot choose pixel format"; + return; } } } - if(CGLCreateContext(_pixelFormat, nullptr, &_glContext) != kCGLNoError) { - Error() << "Platform::WindowlessCglApplication::tryCreateContext(): cannot create context"; - return false; - } + if(CGLCreateContext(_pixelFormat, nullptr, &_context) != kCGLNoError) + Error() << "Platform::WindowlessCglContext: cannot create context"; +} - if(CGLSetCurrentContext(_glContext) != kCGLNoError) { - Error() << "Platform::WindowlessCglApplication::tryCreateContext(): cannot make context current"; - return false; - } +WindowlessCglContext::WindowlessCglContext(WindowlessCglContext&& other): _pixelFormat{other._pixelFormat}, _context{other._context} { + other._pixelFormat = {}; + other._context = {}; +} - /* Return true if the initialization succeeds */ - return _context->tryCreate(); +WindowlessCglContext::~WindowlessCglContext() { + if(_context) CGLDestroyContext(_context); + if(_pixelFormat) CGLDestroyPixelFormat(_pixelFormat); } -WindowlessCglApplication::~WindowlessCglApplication() { - _context.reset(); +WindowlessCglContext& WindowlessCglContext::operator=(WindowlessCglContext&& other) { + using std::swap; + swap(other._pixelFormat, _pixelFormat); + swap(other._context, _context); + return *this; +} + +bool WindowlessCglContext::makeCurrent() { + if(CGLSetCurrentContext(_context) == kCGLNoError) + return true; + + Error() << "Platform::WindowlessCglContext::makeCurrent(): cannot make context current"; + return false; +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments): WindowlessCglApplication{arguments, Configuration{}} {} +#endif + +WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessCglApplication{arguments, NoCreate} { + createContext(configuration); +} + +WindowlessCglApplication::WindowlessCglApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} + +WindowlessCglApplication::~WindowlessCglApplication() = default; + +void WindowlessCglApplication::createContext() { createContext({}); } + +void WindowlessCglApplication::createContext(const Configuration& configuration) { + if(!tryCreateContext(configuration)) std::exit(1); +} + +bool WindowlessCglApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessCglApplication::tryCreateContext(): context already created", false); + + WindowlessCglContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) + return false; - CGLDestroyContext(_glContext); - CGLDestroyPixelFormat(_pixelFormat); + _glContext = std::move(glContext); + return true; } }} diff --git a/src/Magnum/Platform/WindowlessCglApplication.h b/src/Magnum/Platform/WindowlessCglApplication.h index 693fe8ee8..9acb76868 100644 --- a/src/Magnum/Platform/WindowlessCglApplication.h +++ b/src/Magnum/Platform/WindowlessCglApplication.h @@ -28,12 +28,13 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessCglApplication, macro @ref MAGNUM_WINDOWLESSCGLAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessCglApplication, @ref Magnum::Platform::WindowlessCglContext, macro @ref MAGNUM_WINDOWLESSCGLAPPLICATION_MAIN() */ #include #include "Magnum/OpenGL.h" +#include "Magnum/Tags.h" #include #include #include @@ -44,13 +45,99 @@ namespace Magnum { namespace Platform { /** -@brief Windowless CGL application +@brief Windowless CGL context + +GL context used in @ref WindowlessCglApplication. Does not have any default +framebuffer. It is built if `WITH_WINDOWLESSCGLAPPLICATION` is enabled in +CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessCglContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * If version is not specified in @p configuration, it first tries to + * create core context (OpenGL 3.2+), if that fails, tries OpenGL 3.0+ + * and as a last attempt falls back to compatibility OpenGL 2.1 + * context. + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessCglContext(const Configuration& configuration, Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessCglContext(NoCreateT) {} -Application for offscreen rendering using pure CGL. Does not have any default -framebuffer. + /** @brief Copying is not allowed */ + WindowlessCglContext(const WindowlessCglContext&) = delete; + + /** @brief Move constructor */ + WindowlessCglContext(WindowlessCglContext&& other); + + /** @brief Copying is not allowed */ + WindowlessCglContext& operator=(const WindowlessCglContext&) = delete; + + /** @brief Move assignment */ + WindowlessCglContext& operator=(WindowlessCglContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessCglContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); -This application library is available on desktop OpenGL on OS X. It -is built if `WITH_WINDOWLESSCGLAPPLICATION` is enabled in CMake. + private: + CGLPixelFormatObj _pixelFormat{}; + CGLContextObj _context{}; +}; + +/** +@brief Configuration + +@see @ref WindowlessCglContext(), + @ref WindowlessCglApplication::WindowlessCglApplication(), + @ref WindowlessCglApplication::createContext(), + @ref WindowlessCglApplication::tryCreateContext() +*/ +class WindowlessCglContext::Configuration { + public: + constexpr /*implicit*/ Configuration() {} +}; + +/** +@brief Windowless CGL application + +Application for offscreen rendering using @ref WindowlessCglContext. This +application library is available on desktop OpenGL on OS X. It is built if +`WITH_WINDOWLESSCGLAPPLICATION` is enabled in CMake. ## Bootstrap application @@ -104,7 +191,13 @@ class WindowlessCglApplication { char** argv; /**< @brief Argument values */ }; - class Configuration; + /** + * @brief Configuration + * + * @see @ref WindowlessCglApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessCglContext::Configuration Configuration; /** * @brief Default constructor @@ -115,6 +208,7 @@ class WindowlessCglApplication { * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessCglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessCglApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -125,13 +219,21 @@ class WindowlessCglApplication { #endif /** - * @brief Constructor + * @brief Construct without creating the context * @param arguments Application arguments * * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessCglApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessCglApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessCglApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessCglApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessCglApplication(const Arguments&, NoCreateT) instead") explicit WindowlessCglApplication(const Arguments& arguments, std::nullptr_t): WindowlessCglApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessCglApplication(const WindowlessCglApplication&) = delete; @@ -166,11 +268,7 @@ class WindowlessCglApplication { * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. - * - * On desktop GL, if version is not specified in @p configuration, the - * application first tries to create core context (OpenGL 3.2+), if - * that fails, tries OpenGL 3.0+ and as a last attempt falls back to - * compatibility OpenGL 2.1 context. + * @see @ref WindowlessCglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -189,23 +287,10 @@ class WindowlessCglApplication { bool tryCreateContext(const Configuration& configuration); private: - CGLContextObj _glContext; - CGLPixelFormatObj _pixelFormat; - + WindowlessCglContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessCglApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessCglApplication::Configuration { - public: - constexpr /*implicit*/ Configuration() {} -}; - /** @hideinitializer @brief Entry point for windowless CGL application @param className Class name @@ -232,6 +317,7 @@ aliased to `MAGNUM_WINDOWLESSAPPLICATION_MAIN()`. #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessCglApplication WindowlessApplication; +typedef WindowlessCglContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSCGLAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 61d61ce22..ca63f7a5e 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -35,30 +35,12 @@ namespace Magnum { namespace Platform { -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments): WindowlessEglApplication{arguments, Configuration{}} {} -#endif - -WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessEglApplication{arguments, nullptr} { - createContext(configuration); -} - -WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments, std::nullptr_t): _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} - -void WindowlessEglApplication::createContext() { createContext({}); } - -void WindowlessEglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessEglApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessEglApplication::tryCreateContext(): context already created", false); - +WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, Context*) { /* Initialize */ _display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if(!eglInitialize(_display, nullptr, nullptr)) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); - return false; + return; } const EGLenum api = @@ -70,7 +52,7 @@ bool WindowlessEglApplication::tryCreateContext(const Configuration& configurati ; if(!eglBindAPI(api)) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot bind EGL API:" << Implementation::eglErrorString(eglGetError()); - return false; + return; } /* Choose EGL config */ @@ -87,15 +69,16 @@ bool WindowlessEglApplication::tryCreateContext(const Configuration& configurati #endif EGL_NONE }; + EGLConfig config; EGLint configCount; - if(!eglChooseConfig(_display, attribs, &_config, 1, &configCount)) { + if(!eglChooseConfig(_display, attribs, &config, 1, &configCount)) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get EGL visual config:" << Implementation::eglErrorString(eglGetError()); - return false; + return; } if(!configCount) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): no matching EGL visual config available"; - return false; + return; } const EGLint attributes[] = { @@ -113,25 +96,64 @@ bool WindowlessEglApplication::tryCreateContext(const Configuration& configurati EGL_NONE }; - if(!(_glContext = eglCreateContext(_display, _config, EGL_NO_CONTEXT, attributes))) { + if(!(_context = eglCreateContext(_display, config, EGL_NO_CONTEXT, attributes))) { Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); - return false; - } - if(!eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, _glContext)) { - Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot make context current:" << Implementation::eglErrorString(eglGetError()); - return false; + return; } +} - /* Return true if the initialization succeeds */ - return _context->tryCreate(); +WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other): _display{other._display}, _context{other._context} { + other._display = {}; + other._context = {}; } -WindowlessEglApplication::~WindowlessEglApplication() { - _context.reset(); +WindowlessEglContext::~WindowlessEglContext() { + if(_context) eglDestroyContext(_display, _context); + if(_display) eglTerminate(_display); +} - eglDestroyContext(_display, _glContext); - eglDestroySurface(_display, EGL_NO_SURFACE); - eglTerminate(_display); +WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext && other) { + using std::swap; + swap(other._display, _display); + swap(other._context, _context); + return *this; } +bool WindowlessEglContext::makeCurrent() { + if(eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, _context)) + return true; + + Error() << "Platform::WindowlessEglApplication::tryCreateContext(): cannot make context current:" << Implementation::eglErrorString(eglGetError()); + return false; +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments): WindowlessEglApplication{arguments, Configuration{}} {} +#endif + +WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessEglApplication{arguments, NoCreate} { + createContext(configuration); +} + +WindowlessEglApplication::WindowlessEglApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} + +void WindowlessEglApplication::createContext() { createContext({}); } + +void WindowlessEglApplication::createContext(const Configuration& configuration) { + if(!tryCreateContext(configuration)) std::exit(1); +} + +bool WindowlessEglApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessEglApplication::tryCreateContext(): context already created", false); + + WindowlessEglContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) + return false; + + _glContext = std::move(glContext); + return true; +} + +WindowlessEglApplication::~WindowlessEglApplication() = default; + }} diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index b262ef3f7..c656154b0 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessEglApplication, macro @ref MAGNUM_WINDOWLESSEGLAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessEglApplication, @ref Magnum::Platform::WindowlessEglContext, macro @ref MAGNUM_WINDOWLESSEGLAPPLICATION_MAIN() */ #include @@ -41,17 +41,138 @@ #include "Magnum/Magnum.h" #include "Magnum/OpenGL.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" namespace Magnum { namespace Platform { +/** +@brief Windowless EGL context + +GL context using EGL without any windowing system, used in +@ref WindowlessEglApplication. Does not have any default framebuffer. It is +built if `WITH_WINDOWLESSEGLAPPLICATION` is enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessEglContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessEglContext(const Configuration& configuration, const Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessEglContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessEglContext(const WindowlessEglContext&) = delete; + + /** @brief Move constructor */ + WindowlessEglContext(WindowlessEglContext&& other); + + /** @brief Copying is not allowed */ + WindowlessEglContext& operator=(const WindowlessEglContext&) = delete; + + /** @brief Move assignment */ + WindowlessEglContext& operator=(WindowlessEglContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessEglContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + EGLDisplay _display{}; + EGLContext _context{}; +}; + +/** +@brief Configuration + +@see @ref WindowlessEglContext(), + @ref WindowlessEglApplication::WindowlessEglApplication(), + @ref WindowlessEglApplication::createContext(), + @ref WindowlessEglApplication::tryCreateContext() +*/ +class WindowlessEglContext::Configuration { + public: + /** + * @brief Context flag + * + * @see @ref Flags, @ref setFlags(), @ref Context::Flag + */ + enum class Flag: int { + Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR /**< Create debug context */ + }; + + /** + * @brief Context flags + * + * @see @ref setFlags(), @ref Context::Flags + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Containers::EnumSet Flags; + #else + typedef Containers::EnumSet Flags; + #endif + + constexpr /*implicit*/ Configuration() {} + + /** @brief Context flags */ + Flags flags() const { return _flags; } + + /** + * @brief Set context flags + * @return Reference to self (for method chaining) + * + * Default is no flag. See also @ref Context::flags(). + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + private: + Flags _flags; +}; + /** @brief Windowless EGL application -Application for offscreen rendering using EGL without any windowing system. -Does not have any default framebuffer. Supported mainly on OpenGL ES drivers, -for desktop OpenGL the only driver that supports this configuration is -NVidia >= 355. See other `Windowless*Application` classes for an alternative. +Application for offscreen rendering using @ref WindowlessEglContext. Supported +mainly on OpenGL ES drivers, for desktop OpenGL the only driver that supports +this configuration is NVidia >= 355. See other `Windowless*Application` classes +for an alternative. It is built if `WITH_WINDOWLESSEGLAPPLICATION` is enabled in CMake. @@ -104,7 +225,13 @@ class WindowlessEglApplication { char** argv; /**< @brief Argument values */ }; - class Configuration; + /** + * @brief Configuration + * + * @see @ref WindowlessEglApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessEglContext::Configuration Configuration; /** * @brief Default constructor @@ -115,6 +242,7 @@ class WindowlessEglApplication { * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessEglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessEglApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -131,7 +259,15 @@ class WindowlessEglApplication { * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessEglApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessEglApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessEglApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessEglApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessEglApplication(const Arguments&, NoCreateT) instead") explicit WindowlessEglApplication(const Arguments& arguments, std::nullptr_t): WindowlessEglApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessEglApplication(const WindowlessEglApplication&) = delete; @@ -166,6 +302,7 @@ class WindowlessEglApplication { * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessEglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -184,61 +321,10 @@ class WindowlessEglApplication { bool tryCreateContext(const Configuration& configuration); private: - EGLDisplay _display; - EGLConfig _config; - EGLContext _glContext; - + WindowlessEglContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessEglApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessEglApplication::Configuration { - public: - /** - * @brief Context flag - * - * @see @ref Flags, @ref setFlags(), @ref Context::Flag - */ - enum class Flag: int { - Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR /**< Create debug context */ - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - #ifndef DOXYGEN_GENERATING_OUTPUT - typedef Containers::EnumSet Flags; - #else - typedef Containers::EnumSet Flags; - #endif - - constexpr /*implicit*/ Configuration() {} - - /** @brief Context flags */ - Flags flags() const { return _flags; } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. See also @ref Context::flags(). - */ - Configuration& setFlags(Flags flags) { - _flags = flags; - return *this; - } - - private: - Flags _flags; -}; - /** @hideinitializer @brief Entry point for windowless EGL application @param className Class name @@ -258,6 +344,7 @@ windowless application header is included this macro is also aliased to #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessEglApplication WindowlessApplication; +typedef WindowlessEglContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSEGLAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessGlxApplication.cpp b/src/Magnum/Platform/WindowlessGlxApplication.cpp index 0e1379eee..fcb693f7e 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.cpp +++ b/src/Magnum/Platform/WindowlessGlxApplication.cpp @@ -37,32 +37,15 @@ namespace { enum { None = 0L }; } namespace Magnum { namespace Platform { -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments): WindowlessGlxApplication{arguments, Configuration{}} {} -#endif - -WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments, const Configuration& configuration): WindowlessGlxApplication{arguments, nullptr} { - createContext(configuration); -} - -WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments, std::nullptr_t): _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} - -void WindowlessGlxApplication::createContext() { createContext({}); } - -void WindowlessGlxApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessGlxApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessGlxApplication::tryCreateContext(): context already created", false); +WindowlessGlxContext::WindowlessGlxContext(const WindowlessGlxContext::Configuration& configuration, Context* const magnumContext) { _display = XOpenDisplay(nullptr); /* Check version */ int major, minor; glXQueryVersion(_display, &major, &minor); if(major == 1 && minor < 4) { - Error() << "Platform::WindowlessGlxApplication::tryCreateContext(): GLX version 1.4 or greater is required"; - return false; + Error() << "Platform::WindowlessGlxContext: GLX version 1.4 or greater is required"; + return; } /* Choose config */ @@ -70,8 +53,8 @@ bool WindowlessGlxApplication::tryCreateContext(const Configuration& configurati constexpr static const int fbAttributes[] = { None }; GLXFBConfig* configs = glXChooseFBConfig(_display, DefaultScreen(_display), fbAttributes, &configCount); if(!configCount) { - Error() << "Platform::WindowlessGlxApplication::tryCreateContext(): no supported framebuffer configuration found"; - return false; + Error() << "Platform::WindowlessGlxContext: no supported framebuffer configuration found"; + return; } /* Create pbuffer */ @@ -82,6 +65,10 @@ bool WindowlessGlxApplication::tryCreateContext(const Configuration& configurati }; _pbuffer = glXCreatePbuffer(_display, configs[0], pbufferAttributes); + /* Get pointer to proper context creation function */ + const PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = reinterpret_cast(glXGetProcAddress(reinterpret_cast("glXCreateContextAttribsARB"))); + + /* Optimistically choose core context first */ const GLint contextAttributes[] = { #ifdef MAGNUM_TARGET_GLES #ifdef MAGNUM_TARGET_GLES3 @@ -95,8 +82,6 @@ bool WindowlessGlxApplication::tryCreateContext(const Configuration& configurati GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_ES2_PROFILE_BIT_EXT, GLX_CONTEXT_FLAGS_ARB, GLint(configuration.flags()), #else - /* Similarly to what's done in Sdl2Application, try to request core - context first */ GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, @@ -104,73 +89,122 @@ bool WindowlessGlxApplication::tryCreateContext(const Configuration& configurati #endif 0 }; - - const PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = reinterpret_cast(glXGetProcAddress(reinterpret_cast("glXCreateContextAttribsARB"))); - _glContext = glXCreateContextAttribsARB(_display, configs[0], nullptr, True, contextAttributes); + _context = glXCreateContextAttribsARB(_display, configs[0], nullptr, True, contextAttributes); #ifndef MAGNUM_TARGET_GLES - /* Fall back to (forward compatible) GL 2.1, if version is not - user-specified and either core context creation fails or we are on - binary NVidia/AMD drivers on Linux/Windows. Instead of creating forward- - compatible context with highest available version, they force the - version to the one specified, which is completely useless behavior. */ - #ifndef CORRADE_TARGET_APPLE - constexpr static const char nvidiaVendorString[] = "NVIDIA Corporation"; - constexpr static const char amdVendorString[] = "ATI Technologies Inc."; - const char* vendorString; - #endif - if(!_glContext - #ifndef CORRADE_TARGET_APPLE - /* We need to make the context current first, sorry about the UGLY code - and HOPEFULLY THERE WON'T BE MORE WORKAROUNDS */ - || (glXMakeContextCurrent(_display, _pbuffer, _pbuffer, _glContext) && - (vendorString = reinterpret_cast(glGetString(GL_VENDOR)), - (std::strncmp(vendorString, nvidiaVendorString, sizeof(nvidiaVendorString)) == 0 || - std::strncmp(vendorString, amdVendorString, sizeof(amdVendorString)) == 0) && - !_context->isDriverWorkaroundDisabled("amd-nv-no-forward-compatible-core-context"))) - #endif - ) { - /* Don't print any warning when doing the NV workaround, because the - bug will be there probably forever */ - if(!_glContext) Warning() - << "Platform::WindowlessGlxApplication::tryCreateContext(): cannot create core context, falling back to compatibility context"; - else { - glXMakeCurrent(_display, None, nullptr); - glXDestroyContext(_display, _glContext); - } + /* Fall back to (forward compatible) GL 2.1 if core context creation fails */ + if(!_context) { + Warning() << "Platform::WindowlessGlxContext: cannot create core context, falling back to compatibility context"; const GLint fallbackContextAttributes[] = { GLX_CONTEXT_FLAGS_ARB, GLint(configuration.flags()), 0 }; + _context = glXCreateContextAttribsARB(_display, configs[0], nullptr, True, fallbackContextAttributes); + + /* Fall back to (forward compatible) GL 2.1 if we are on binary NVidia/AMD + drivers on Linux/Windows. Instead of creating forward-compatible context + with highest available version, they force the version to the one + specified, which is completely useless behavior. */ + } else { + /* We need to make the context current to read out vendor string, so + save the previous values so we can safely revert back without + messing up the state */ + GLXDrawable currentDrawable = glXGetCurrentDrawable(); + GLXDrawable currentReadDrawable = glXGetCurrentReadDrawable(); + GLXContext currentContext = glXGetCurrentContext(); + if(!glXMakeContextCurrent(_display, _pbuffer, _pbuffer, _context)) { + Error() << "Platform::WindowlessGlxContext: cannot make context current"; + return; + } - _glContext = glXCreateContextAttribsARB(_display, configs[0], nullptr, True, fallbackContextAttributes); + /* The workaround check is the last so it doesn't appear in workaround + list on unrelated drivers */ + constexpr static const char nvidiaVendorString[] = "NVIDIA Corporation"; + constexpr static const char amdVendorString[] = "ATI Technologies Inc."; + const char* const vendorString = reinterpret_cast(glGetString(GL_VENDOR)); + if((std::strncmp(vendorString, nvidiaVendorString, sizeof(nvidiaVendorString)) == 0 || + std::strncmp(vendorString, amdVendorString, sizeof(amdVendorString)) == 0) && + (!magnumContext || !magnumContext->isDriverWorkaroundDisabled("amd-nv-no-forward-compatible-core-context"))) + { + /* Destroy the core context and create a compatibility one */ + glXDestroyContext(_display, _context); + const GLint fallbackContextAttributes[] = { + GLX_CONTEXT_FLAGS_ARB, GLint(configuration.flags()), + 0 + }; + _context = glXCreateContextAttribsARB(_display, configs[0], nullptr, True, fallbackContextAttributes); + } + + /* Revert back the old context */ + if(!glXMakeContextCurrent(_display, currentDrawable, currentReadDrawable, currentContext)) { + Error() << "Platform::WindowlessGlxContext: cannot make the previous context current"; + return; + } } #endif XFree(configs); - if(!_glContext) { - Error() << "Platform::WindowlessGlxApplication::tryCreateContext(): cannot create context"; - return false; - } + if(!_context) + Error() << "Platform::WindowlessGlxContext: cannot create context"; +} - /* Set OpenGL context as current */ - if(!glXMakeContextCurrent(_display, _pbuffer, _pbuffer, _glContext)) { - Error() << "Platform::WindowlessGlxApplication::tryCreateContext(): cannot make context current"; - return false; - } +WindowlessGlxContext::WindowlessGlxContext(WindowlessGlxContext&& other): _display{other._display}, _pbuffer{other._pbuffer}, _context{other._context} { + other._display = {}; + other._context = {}; + other._pbuffer = {}; +} + +WindowlessGlxContext::~WindowlessGlxContext() { + if(_context) glXDestroyContext(_display, _context); + if(_pbuffer) glXDestroyPbuffer(_display, _pbuffer); + if(_display) XCloseDisplay(_display); +} - /* Return true if the initialization succeeds */ - return _context->tryCreate(); +WindowlessGlxContext& WindowlessGlxContext::operator=(WindowlessGlxContext&& other) { + using std::swap; + swap(other._display, _display); + swap(other._pbuffer, _pbuffer); + swap(other._context, _context); + return *this; } -WindowlessGlxApplication::~WindowlessGlxApplication() { - _context.reset(); +bool WindowlessGlxContext::makeCurrent() { + if(glXMakeContextCurrent(_display, _pbuffer, _pbuffer, _context)) + return true; - glXMakeCurrent(_display, None, nullptr); - glXDestroyPbuffer(_display, _pbuffer); - glXDestroyContext(_display, _glContext); + Error() << "Platform::WindowlessGlxContext::makeCurrent(): cannot make context current"; + return false; } +#ifndef DOXYGEN_GENERATING_OUTPUT +WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments): WindowlessGlxApplication{arguments, Configuration{}} {} +#endif + +WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments, const Configuration& configuration): WindowlessGlxApplication{arguments, NoCreate} { + createContext(configuration); +} + +WindowlessGlxApplication::WindowlessGlxApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} + +void WindowlessGlxApplication::createContext() { createContext({}); } + +void WindowlessGlxApplication::createContext(const Configuration& configuration) { + if(!tryCreateContext(configuration)) std::exit(1); +} + +bool WindowlessGlxApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessGlxApplication::tryCreateContext(): context already created", false); + + WindowlessGlxContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) + return false; + + _glContext = std::move(glContext); + return true; +} + +WindowlessGlxApplication::~WindowlessGlxApplication() = default; + }} diff --git a/src/Magnum/Platform/WindowlessGlxApplication.h b/src/Magnum/Platform/WindowlessGlxApplication.h index 0e7325957..5821aae9d 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.h +++ b/src/Magnum/Platform/WindowlessGlxApplication.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessGlxApplication, macro @ref MAGNUM_WINDOWLESSGLXAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessGlxApplication, @ref Magnum::Platform::WindowlessGlxContext, macro @ref MAGNUM_WINDOWLESSGLXAPPLICATION_MAIN() */ #include @@ -43,16 +43,147 @@ #undef Status #include "Magnum/Magnum.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" namespace Magnum { namespace Platform { /** -@brief Windowless GLX application +@brief Windowless GLX context + +GL context using pure X11 and GLX, used in @ref WindowlessGlxApplication. Does +not have any default framebuffer. It is built if `WITH_WINDOWLESSGLXAPPLICATION` +is enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessGlxContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * On desktop GL, if version is not specified in @p configuration, the + * application first tries to create core context (OpenGL 3.1+) and if + * that fails, falls back to compatibility OpenGL 2.1 context. However, + * on binary AMD and NVidia drivers, creating core context does not use + * the largest available version. If the application detects such case + * (and given workaround is not disabled in optionally passed + * @ref context instance), the core context is destroyed and + * compatibility OpenGL 2.1 context is created instead to make the + * driver use the latest available version. + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessGlxContext(const Configuration& configuration, Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessGlxContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessGlxContext(const WindowlessGlxContext&) = delete; + + /** @brief Move constructor */ + WindowlessGlxContext(WindowlessGlxContext&& other); + + /** @brief Copying is not allowed */ + WindowlessGlxContext& operator=(const WindowlessGlxContext&) = delete; + + /** @brief Move assignment */ + WindowlessGlxContext& operator=(WindowlessGlxContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessGlxContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + Display* _display{}; + GLXPbuffer _pbuffer{}; + GLXContext _context{}; +}; + +/** +@brief Configuration + +@see @ref WindowlessGlxContext(), + @ref WindowlessGlxApplication::WindowlessGlxApplication(), + @ref WindowlessGlxApplication::createContext(), + @ref WindowlessGlxApplication::tryCreateContext() +*/ +class WindowlessGlxContext::Configuration { + public: + /** + * @brief Context flag + * + * @see @ref Flags, @ref setFlags(), @ref Context::Flag + */ + enum class Flag: int { + Debug = GLX_CONTEXT_DEBUG_BIT_ARB /**< Create debug context */ + }; + + /** + * @brief Context flags + * + * @see @ref setFlags(), @ref Context::Flags + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Containers::EnumSet Flags; + #else + typedef Containers::EnumSet Flags; + #endif + + constexpr /*implicit*/ Configuration() {} + + /** @brief Context flags */ + Flags flags() const { return _flags; } + + /** + * @brief Set context flags + * @return Reference to self (for method chaining) + * + * Default is no flag. See also @ref Context::flags(). + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + private: + Flags _flags; +}; -Application for offscreen rendering using pure X11 and GLX. +/** +@brief Windowless GLX application -This application library is available on desktop OpenGL and +Application for offscreen rendering using @ref WindowlessGlxContext. This +application library is available on desktop OpenGL and @ref MAGNUM_TARGET_DESKTOP_GLES "OpenGL ES emulation on desktop" on Linux. It depends on **X11** library and is built if `WITH_WINDOWLESSGLXAPPLICATION` is enabled in CMake. @@ -107,7 +238,13 @@ class WindowlessGlxApplication { char** argv; /**< @brief Argument values */ }; - class Configuration; + /** + * @brief Configuration + * + * @see @ref WindowlessGlxApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessGlxContext::Configuration Configuration; /** * @brief Default constructor @@ -118,6 +255,7 @@ class WindowlessGlxApplication { * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessGlxContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessGlxApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -134,7 +272,15 @@ class WindowlessGlxApplication { * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessGlxApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessGlxApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessGlxApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessGlxApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessGlxApplication(const Arguments&, NoCreateT) instead") explicit WindowlessGlxApplication(const Arguments& arguments, std::nullptr_t): WindowlessGlxApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessGlxApplication(const WindowlessGlxApplication&) = delete; @@ -169,15 +315,7 @@ class WindowlessGlxApplication { * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. - * - * On desktop GL, if version is not specified in @p configuration, the - * application first tries to create core context (OpenGL 3.1+) and if - * that fails, falls back to compatibility OpenGL 2.1 context. However, - * on binary AMD and NVidia drivers, creating core context does not use - * the largest available version. If the application detects such case, - * the core context is destroyed and compatibility OpenGL 2.1 context - * is created instead to make the driver use the latest available - * version. + * @see @ref WindowlessGlxContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -196,61 +334,10 @@ class WindowlessGlxApplication { bool tryCreateContext(const Configuration& configuration); private: - Display* _display; - GLXContext _glContext; - GLXPbuffer _pbuffer; - + WindowlessGlxContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessGlxApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessGlxApplication::Configuration { - public: - /** - * @brief Context flag - * - * @see @ref Flags, @ref setFlags(), @ref Context::Flag - */ - enum class Flag: int { - Debug = GLX_CONTEXT_DEBUG_BIT_ARB /**< Create debug context */ - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - #ifndef DOXYGEN_GENERATING_OUTPUT - typedef Containers::EnumSet Flags; - #else - typedef Containers::EnumSet Flags; - #endif - - constexpr /*implicit*/ Configuration() {} - - /** @brief Context flags */ - Flags flags() const { return _flags; } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. See also @ref Context::flags(). - */ - Configuration& setFlags(Flags flags) { - _flags = flags; - return *this; - } - - private: - Flags _flags; -}; - /** @hideinitializer @brief Entry point for windowless GLX application @param className Class name @@ -277,6 +364,7 @@ aliased to `MAGNUM_WINDOWLESSAPPLICATION_MAIN()`. #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessGlxApplication WindowlessApplication; +typedef WindowlessGlxContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSGLXAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessIosApplication.h b/src/Magnum/Platform/WindowlessIosApplication.h index 459afc39a..b870c5ab4 100644 --- a/src/Magnum/Platform/WindowlessIosApplication.h +++ b/src/Magnum/Platform/WindowlessIosApplication.h @@ -3,7 +3,7 @@ /* This file is part of Magnum. - Copyright © 2010, 2011, 2012, 2013, 2014, 2015 + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessIosApplication, macro @ref MAGNUM_WINDOWLESSIOSAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessIosApplication, @ref Magnum::Platform::WindowlessIosContext, macro @ref MAGNUM_WINDOWLESSIOSAPPLICATION_MAIN() */ #include @@ -34,6 +34,7 @@ #include "Magnum/Magnum.h" #include "Magnum/OpenGL.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" #ifdef __OBJC__ @@ -44,12 +45,94 @@ struct EAGLContext; namespace Magnum { namespace Platform { +/** +@brief Windowless iOS context + +GL context using EAGL on iOS, used in @ref WindowlessIosApplication. Does not +have any default framebuffer. It is built if `WITH_WINDOWLESSIOSAPPLICATION` is +enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessIosContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessIosContext(const Configuration& configuration, Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessIosContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessIosContext(const WindowlessIosContext&) = delete; + + /** @brief Move constructor */ + WindowlessIosContext(WindowlessIosContext&& other); + + /** @brief Copying is not allowed */ + WindowlessIosContext& operator=(const WindowlessIosContext&) = delete; + + /** @brief Move assignment */ + WindowlessIosContext& operator=(WindowlessIosContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessIosContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + EAGLContext* _context{}; +}; + +/** +@brief Configuration + +@see @ref WindowlessIosContext(), + @ref WindowlessIosApplication::WindowlessCglApplication(), + @ref WindowlessIosApplication::createContext(), + @ref WindowlessIosApplication::tryCreateContext() +*/ +class WindowlessIosContext::Configuration { + public: + constexpr /*implicit*/ Configuration() {} +}; + /** @brief Windowless iOS application -Application for offscreen rendering using EAGL on iOS. Does not have any -default framebuffer. It is built if `WITH_WINDOWLESSIOSAPPLICATION` is enabled -in CMake. +Application for offscreen rendering using @ref WindowlessIosContext. Does not +have any default framebuffer. It is built if `WITH_WINDOWLESSIOSAPPLICATION` is +enabled in CMake. ## Bootstrap application @@ -100,7 +183,13 @@ class WindowlessIosApplication { char** argv; /**< @brief Argument values */ }; - class Configuration; + /** + * @brief Configuration + * + * @see @ref WindowlessIosApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessIosContext::Configuration Configuration; /** * @brief Default constructor @@ -111,6 +200,7 @@ class WindowlessIosApplication { * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessIosContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessIosApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -127,7 +217,15 @@ class WindowlessIosApplication { * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessIosApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessIosApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessIosApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessIosApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessIosApplication(const Arguments&, NoCreateT) instead") explicit WindowlessIosApplication(const Arguments& arguments, std::nullptr_t): WindowlessIosApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessIosApplication(const WindowlessIosApplication&) = delete; @@ -162,6 +260,7 @@ class WindowlessIosApplication { * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessIosContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -180,53 +279,10 @@ class WindowlessIosApplication { bool tryCreateContext(const Configuration& configuration); private: - EAGLContext* _glContext; - + WindowlessIosContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessIosApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessIosApplication::Configuration { - public: - /** - * @brief Context flag - * - * @see @ref Flags, @ref setFlags(), @ref Context::Flag - */ - enum class Flag: int {}; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - typedef Containers::EnumSet Flags; - - constexpr /*implicit*/ Configuration() {} - - /** @brief Context flags */ - Flags flags() const { return _flags; } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. See also @ref Context::flags(). - */ - Configuration& setFlags(Flags flags) { - _flags = flags; - return *this; - } - - private: - Flags _flags; -}; - /** @hideinitializer @brief Entry point for windowless EGL application @param className Class name @@ -246,6 +302,7 @@ windowless application header is included this macro is also aliased to #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessIosApplication WindowlessApplication; +typedef WindowlessIosContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSIOSAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessIosApplication.mm b/src/Magnum/Platform/WindowlessIosApplication.mm index 921f5d261..172e9273d 100644 --- a/src/Magnum/Platform/WindowlessIosApplication.mm +++ b/src/Magnum/Platform/WindowlessIosApplication.mm @@ -1,7 +1,7 @@ /* This file is part of Magnum. - Copyright © 2010, 2011, 2012, 2013, 2014, 2015 + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a @@ -39,15 +39,50 @@ namespace Magnum { namespace Platform { +WindowlessIosContext::WindowlessIosContext(const Configuration&, Context*) { + if(!(_context = [[EAGLContext alloc] + #ifdef MAGNUM_TARGET_GLES2 + initWithAPI:kEAGLRenderingAPIOpenGLES2 + #else + initWithAPI:kEAGLRenderingAPIOpenGLES3 + #endif + ])) + { + Error() << "Platform::WindowlessIosContext(): cannot create EAGL context"; + } +} + +WindowlessIosContext::WindowlessIosContext(WindowlessIosContext&& other): _context{other._context} { + other._context = {}; +} + +WindowlessIosContext::~WindowlessIosContext() { + if(_context) [_context dealloc]; +} + +WindowlessIosContext& WindowlessIosContext::operator=(WindowlessIosContext&& other) { + using std::swap; + swap(other._context, _context); + return *this; +} + +bool WindowlessIosContext::makeCurrent() { + if([EAGLContext setCurrentContext:_context]) + return true; + + Error() << "Platform::WindowlessIosContext::makeCurrent(): cannot make context current"; + return false; +} + #ifndef DOXYGEN_GENERATING_OUTPUT WindowlessIosApplication::WindowlessIosApplication(const Arguments& arguments): WindowlessIosApplication{arguments, Configuration{}} {} #endif -WindowlessIosApplication::WindowlessIosApplication(const Arguments& arguments, const Configuration& configuration): WindowlessIosApplication{arguments, nullptr} { +WindowlessIosApplication::WindowlessIosApplication(const Arguments& arguments, const Configuration& configuration): WindowlessIosApplication{arguments, NoCreate} { createContext(configuration); } -WindowlessIosApplication::WindowlessIosApplication(const Arguments& arguments, std::nullptr_t): _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} +WindowlessIosApplication::WindowlessIosApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} void WindowlessIosApplication::createContext() { createContext({}); } @@ -55,35 +90,17 @@ void WindowlessIosApplication::createContext(const Configuration& configuration) if(!tryCreateContext(configuration)) std::exit(1); } -bool WindowlessIosApplication::tryCreateContext(const Configuration&) { +bool WindowlessIosApplication::tryCreateContext(const Configuration& configuration) { CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessIosApplication::tryCreateContext(): context already created", false); - /* Initialize */ - if(!(_glContext = [[EAGLContext alloc] - #ifdef MAGNUM_TARGET_GLES2 - initWithAPI:kEAGLRenderingAPIOpenGLES2 - #else - initWithAPI:kEAGLRenderingAPIOpenGLES3 - #endif - ])) - { - Error() << "Platform::WindowlessIosApplication::tryCreateContext(): cannot create EAGL context"; - return false; - } - - if(![EAGLContext setCurrentContext:_glContext]) { - Error() << "Platform::WindowlessIosApplication::tryCreateContext(): cannot make context current"; + WindowlessIosContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) return false; - } - /* Return true if the initialization succeeds */ - return _context->tryCreate(); + _glContext = std::move(glContext); + return true; } -WindowlessIosApplication::~WindowlessIosApplication() { - _context.reset(); - - [_glContext dealloc]; -} +WindowlessIosApplication::~WindowlessIosApplication() = default; }} diff --git a/src/Magnum/Platform/WindowlessNaClApplication.cpp b/src/Magnum/Platform/WindowlessNaClApplication.cpp index fd4194d33..22f0cdbce 100644 --- a/src/Magnum/Platform/WindowlessNaClApplication.cpp +++ b/src/Magnum/Platform/WindowlessNaClApplication.cpp @@ -34,6 +34,40 @@ namespace Magnum { namespace Platform { +WindowlessNaClContext::WindowlessNaClContext(pp::Instance& instance, const Configuration&, Context*) { + const std::int32_t attributes[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, + PP_GRAPHICS3DATTRIB_WIDTH, 1, + PP_GRAPHICS3DATTRIB_HEIGHT, 1, + PP_GRAPHICS3DATTRIB_NONE + }; + + std::unique_ptr context{new pp::Graphics3D{*instance, attributes}}; + if(context->is_null()) + Error() << "Platform::WindowlessNaClContext: cannot create context"; + + if(!BindGraphics(*context)) + Error() << "Platform::WindowlessNaClContext: cannot bind graphics"; + + /* All went well, save the context */ + _context = std::move(context); +} + +WindowlessNaClContext::WindowlessNaClContext(WindowlessNaClContext&&) = default; + +WindowlessNaClContext::~WindowlessEglContext() = default; + +WindowlessNaClContext& WindowlessNaClContext::operator=(WindowlessNaClContext&&) = default; + +bool WindowlessNaClContext::makeCurrent() { + if(!_context) return false; + + glSetCurrentContextPPAPI(_context->pp_resource()); + return true; +} + struct WindowlessNaClApplication::ConsoleDebugOutput { explicit ConsoleDebugOutput(pp::Instance* instance); @@ -55,13 +89,11 @@ WindowlessNaClApplication::WindowlessNaClApplication(const Arguments& arguments) #endif WindowlessNaClApplication::WindowlessNaClApplication(const Arguments& arguments, const Configuration& configuration): -WindowlessNaClApplication{arguments, nullptr} { +WindowlessNaClApplication{arguments, NoCreate} { createContext(configuration); } -WindowlessNaClApplication::WindowlessNaClApplication(const Arguments& arguments, std::nullptr_t): Instance(arguments), Graphics3DClient(this), graphics(nullptr), c(nullptr) { - debugOutput = new ConsoleDebugOutput(this); -} +WindowlessNaClApplication::WindowlessNaClApplication(const Arguments& arguments, NoCreate): Instance(arguments), Graphics3DClient(this), _glContext{NoCreate}, _debugOutput{new ConsoleDebugOutput{this}} {} void WindowlessNaClApplication::createContext() { createContext({}); } @@ -69,43 +101,18 @@ void WindowlessNaClApplication::createContext(const Configuration& configuration if(!tryCreateContext(configuration)) std::exit(1); } -bool WindowlessNaClApplication::tryCreateContext(const Configuration&) { - CORRADE_ASSERT(!c, "Platform::WindowlessNaClApplication::tryCreateContext(): context already created", false); - - const std::int32_t attributes[] = { - PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, - PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, - PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, - PP_GRAPHICS3DATTRIB_WIDTH, 1, - PP_GRAPHICS3DATTRIB_HEIGHT, 1, - PP_GRAPHICS3DATTRIB_NONE - }; +bool WindowlessNaClApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessNaClApplication::tryCreateContext(): context already created", false); - graphics = new pp::Graphics3D(this, attributes); - if(graphics->is_null()) { - Error() << "Platform::WindowlessNaClApplication::tryCreateContext(): cannot create context"; - delete graphics; - graphics = nullptr; - return false; - } - if(!BindGraphics(*graphics)) { - Error() << "Platform::WindowlessNaClApplication::tryCreateContext(): cannot bind graphics"; - delete graphics; - graphics = nullptr; + WindowlessNaClContext glContext{*this, configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) return false; - } - glSetCurrentContextPPAPI(graphics->pp_resource()); - - /* Return true if the initialization succeeds */ - return c = Platform::Context::tryCreate().release(); + _glContext = std::move(glContext); + return true; } -WindowlessNaClApplication::~WindowlessNaClApplication() { - delete c; - delete graphics; - delete debugOutput; -} +WindowlessNaClApplication::~WindowlessNaClApplication() = default; bool WindowlessNaClApplication::Init(uint32_t , const char* , const char*) { return exec() == 0; diff --git a/src/Magnum/Platform/WindowlessNaClApplication.h b/src/Magnum/Platform/WindowlessNaClApplication.h index b130f475a..fba8b8e62 100644 --- a/src/Magnum/Platform/WindowlessNaClApplication.h +++ b/src/Magnum/Platform/WindowlessNaClApplication.h @@ -26,9 +26,10 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessNaClApplication, macro @ref MAGNUM_WINDOWLESSNACLAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessNaClApplication, @ref Magnum::Platform::WindowlessNaClContext, macro @ref MAGNUM_WINDOWLESSNACLAPPLICATION_MAIN() */ +#include #include #include @@ -41,6 +42,7 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" namespace pp { @@ -50,13 +52,94 @@ namespace pp { namespace Magnum { namespace Platform { +/** +@brief Windowless NaCl context + +GL context running in [Google Chrome Native Client](https://developers.google.com/native-client/), +used in @ref WindowlessNaClApplication. Does not have any default framebuffer. +It is built if `WITH_WINDOWLESSNACLAPPLICATION` is enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessNaClContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param instance Pepper instance handle + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessNaClContext(pp::Instance& instance, const Configuration& configuration, const Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessNaClContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessNaClContext(const WindowlessNaClContext&) = delete; + + /** @brief Move constructor */ + WindowlessNaClContext(WindowlessNaClContext&&); + + /** @brief Copying is not allowed */ + WindowlessNaClContext& operator=(const WindowlessNaClContext&) = delete; + + /** @brief Move assignment */ + WindowlessNaClContext& operator=(WindowlessNaClContext&&); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessNaClContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + std::unique_ptr _context; +}; + +/** +@brief Configuration + +@see @ref WindowlessNaClContext(), + @ref WindowlessNaClApplication::WindowlessCglApplication(), + @ref WindowlessNaClApplication::createContext(), + @ref WindowlessNaClApplication::tryCreateContext() +*/ +class WindowlessNaClContext::Configuration { + public: + constexpr /*implicit*/ Configuration() {} +}; + /** @nosubgrouping @brief Windowless NaCl application -Application for offscreen rendering running in -[Google Chrome Native Client](https://developers.google.com/native-client/). - -This application library is available only in @ref CORRADE_TARGET_NACL "Native Client", +Application for offscreen rendering using @ref WindowlessNaClContext. This +application library is available only in @ref CORRADE_TARGET_NACL "Native Client", see respective sections in @ref building-corrade-cross-nacl "Corrade's" and @ref building-cross-nacl "Magnum's" building documentation. It is built if `WITH_WINDOWLESSNACLAPPLICATION` is enabled in CMake. @@ -111,7 +194,13 @@ class WindowlessNaClApplication: public pp::Instance, public pp::Graphics3DClien /** @brief Application arguments */ typedef PP_Instance Arguments; - class Configuration; + /** + * @brief Configuration + * + * @see @ref WindowlessNaClApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessNaClContext::Configuration Configuration; /** * @brief Default constructor @@ -122,6 +211,7 @@ class WindowlessNaClApplication: public pp::Instance, public pp::Graphics3DClien * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessNaClContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessNaClApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -138,7 +228,15 @@ class WindowlessNaClApplication: public pp::Instance, public pp::Graphics3DClien * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessNaClApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessNaClApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessNaClApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessNaClApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessNaClApplication(const Arguments&, NoCreateT) instead") explicit WindowlessNaClApplication(const Arguments& arguments, std::nullptr_t): WindowlessNaClApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessNaClApplication(const WindowlessNaClApplication&) = delete; @@ -178,6 +276,7 @@ class WindowlessNaClApplication: public pp::Instance, public pp::Graphics3DClien * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessNaClContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -202,20 +301,9 @@ class WindowlessNaClApplication: public pp::Instance, public pp::Graphics3DClien bool Init(std::uint32_t, const char*, const char*) override; - pp::Graphics3D* graphics; - Platform::Context* c; - ConsoleDebugOutput* debugOutput; -}; - -/** -@brief Configuration - -@see @ref WindowlessNaClApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessNaClApplication::Configuration { - public: - constexpr /*implicit*/ Configuration() {} + WindowlessNaClContext _glContext; + std::unique_ptr _context; + std::unique_ptr _debugOutput; }; namespace Implementation { @@ -256,6 +344,7 @@ application header is included this macro is also aliased to #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessNaClApplication WindowlessApplication; +typedef WindowlessNaClContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSNACLAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessWglApplication.cpp b/src/Magnum/Platform/WindowlessWglApplication.cpp index 72597d12d..75845496e 100644 --- a/src/Magnum/Platform/WindowlessWglApplication.cpp +++ b/src/Magnum/Platform/WindowlessWglApplication.cpp @@ -25,19 +25,31 @@ #include "WindowlessWglApplication.h" +#include #include #include #include "Magnum/Version.h" #include "Magnum/Platform/Context.h" +#ifndef DOXYGEN_GENERATING_OUTPUT +/* Define stuff that we need because I can't be bothered with creating a new + header just for a few defines */ +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#endif + namespace Magnum { namespace Platform { -#ifndef DOXYGEN_GENERATING_OUTPUT -int WindowlessWglApplication::create(LRESULT(CALLBACK windowProcedure)(HWND, UINT, WPARAM, LPARAM)) { +WindowlessWglContext::WindowlessWglContext(const Configuration& configuration, Context* const magnumContext) { + /* Create the window */ const WNDCLASS wc{ 0, - windowProcedure, + DefWindowProc, 0, 0, GetModuleHandle(nullptr), @@ -47,37 +59,10 @@ int WindowlessWglApplication::create(LRESULT(CALLBACK windowProcedure)(HWND, UIN nullptr, L"Magnum Windowless Application" }; - if(!RegisterClass(&wc)) return 1; - - CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", + if(!RegisterClass(&wc)) return; + _window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); - /* Hammer the return code out of the messaging thingy */ - MSG msg; - do {} while(GetMessageW(&msg, nullptr, 0, 0) != 0); - return msg.wParam; -} -#endif - -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments): WindowlessWglApplication{arguments, Configuration{}} {} -#endif - -WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWglApplication{arguments, nullptr} { - createContext(configuration); -} - -WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments, std::nullptr_t): _window(arguments.window), _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} - -void WindowlessWglApplication::createContext() { createContext({}); } - -void WindowlessWglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessWglApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessWglApplication::tryCreateContext(): context already created", false); - /* Get device context */ _deviceContext = GetDC(_window); @@ -104,49 +89,160 @@ bool WindowlessWglApplication::tryCreateContext(const Configuration& configurati const int pixelFormat = ChoosePixelFormat(_deviceContext, &pfd); SetPixelFormat(_deviceContext, pixelFormat, &pfd); - const int attributes[] = { - WGL_CONTEXT_FLAGS_ARB, int(configuration.flags()), - 0 - }; - /* Create temporary context so we are able to get the pointer to - wglCreateContextAttribsARB() */ - HGLRC temporaryContext = wglCreateContext(_deviceContext); + wglCreateContextAttribsARB(). To avoid messing up the app state we need + to save the old active context and then restore it later. */ + const HGLRC currentContext = wglGetCurrentContext(); + const HGLRC temporaryContext = wglCreateContext(_deviceContext); if(!wglMakeCurrent(_deviceContext, temporaryContext)) { - Error() << "Platform::WindowlessWglApplication::tryCreateContext(): cannot make temporary context current:" << GetLastError(); - return false; + Error() << "Platform::WindowlessWglContext: cannot make temporary context current:" << GetLastError(); + wglDeleteContext(temporaryContext); + return; } - /* Get pointer to proper context creation function and create real context - with it */ + /* Get pointer to proper context creation function */ typedef HGLRC(WINAPI*PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int*); const PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = reinterpret_cast( wglGetProcAddress(reinterpret_cast("wglCreateContextAttribsARB"))); - _renderingContext = wglCreateContextAttribsARB(_deviceContext, nullptr, attributes); - /* Delete the temporary context */ - wglMakeCurrent(_deviceContext, nullptr); - wglDeleteContext(temporaryContext); - - if(!_renderingContext) { - Error() << "Platform::WindowlessWglApplication::tryCreateContext(): cannot create context:" << GetLastError(); - return false; + /* Optimistically choose core context first */ + const GLint contextAttributes[] = { + #ifdef MAGNUM_TARGET_GLES + #ifdef MAGNUM_TARGET_GLES3 + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + #elif defined(MAGNUM_TARGET_GLES2) + WGL_CONTEXT_MAJOR_VERSION_ARB, 2, + #else + #error unsupported OpenGL ES version + #endif + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_ES2_PROFILE_BIT_EXT, + WGL_CONTEXT_FLAGS_ARB, GLint(configuration.flags()), + #else + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 1, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB|GLint(configuration.flags()), + #endif + 0 + }; + _context = wglCreateContextAttribsARB(_deviceContext, nullptr, contextAttributes); + + #ifndef MAGNUM_TARGET_GLES + /* Fall back to (forward compatible) GL 2.1 if core context creation fails */ + if(!_context) { + Warning() << "Platform::WindowlessWglContext: cannot create core context, falling back to compatibility context:" << GetLastError(); + + const int fallbackContextAttributes[] = { + WGL_CONTEXT_FLAGS_ARB, int(configuration.flags()), + 0 + }; + _context = wglCreateContextAttribsARB(_deviceContext, nullptr, fallbackContextAttributes); + + /* Fall back to (forward compatible) GL 2.1 if we are on binary NVidia/AMD + drivers on Linux/Windows. Instead of creating forward-compatible context + with highest available version, they force the version to the one + specified, which is completely useless behavior. */ + } else { + /* We need to make the context current to read out vendor string */ + if(!wglMakeCurrent(_deviceContext, _context)) { + Error() << "Platform::WindowlessWglContext: cannot make context current:" << GetLastError(); + + /* Everything failed, at least try to delete the dangling contexts + and revert to the previous context to regain some sanity */ + wglMakeCurrent(_deviceContext, currentContext); + wglDeleteContext(temporaryContext); + return; + } + + /* The workaround check is the last so it doesn't appear in workaround + list on unrelated drivers */ + constexpr static const char nvidiaVendorString[] = "NVIDIA Corporation"; + constexpr static const char amdVendorString[] = "ATI Technologies Inc."; + const char* const vendorString = reinterpret_cast(glGetString(GL_VENDOR)); + if((std::strncmp(vendorString, nvidiaVendorString, sizeof(nvidiaVendorString)) == 0 || + std::strncmp(vendorString, amdVendorString, sizeof(amdVendorString)) == 0) && + (!magnumContext || !magnumContext->isDriverWorkaroundDisabled("amd-nv-no-forward-compatible-core-context"))) + { + /* Destroy the core context and create a compatibility one */ + wglDeleteContext(_context); + const int fallbackContextAttributes[] = { + WGL_CONTEXT_FLAGS_ARB, int(configuration.flags()), + 0 + }; + _context = wglCreateContextAttribsARB(_deviceContext, nullptr, fallbackContextAttributes); + } } + #endif - /* Set OpenGL context as current */ - if(!wglMakeCurrent(_deviceContext, _renderingContext)) { - Error() << "Platform::WindowlessWglApplication::tryCreateContext(): cannot make context current:" << GetLastError(); - return false; + /* Make the previous context active and delete the temporary context */ + if(!wglMakeCurrent(_deviceContext, currentContext)) { + Error() << "Platform::WindowlessWglContext: cannot make the previous context current:" << GetLastError(); + + /* Everything is fucked up, but try to delete the temporary context + anyway */ + wglDeleteContext(temporaryContext); + return; } + wglDeleteContext(temporaryContext); + + if(!_context) + Error() << "Platform::WindowlessWglContext: cannot create context:" << GetLastError(); +} + +WindowlessWglContext::WindowlessWglContext(WindowlessWglContext&& other): _window{other._window}, _deviceContext{other._deviceContext}, _context{other._context} { + other._window = {}; + other._deviceContext = {}; + other._context = {}; +} + +WindowlessWglContext::~WindowlessWglContext() { + if(_context) wglDeleteContext(_context); + if(_window) DestroyWindow(_window); +} + +WindowlessWglContext& WindowlessWglContext::operator=(WindowlessWglContext&& other) { + using std::swap; + swap(other._window, _window); + swap(other._deviceContext, _deviceContext); + swap(other._context, _context); + return *this; +} + +bool WindowlessWglContext::makeCurrent() { + if(wglMakeCurrent(_deviceContext, _context)) + return true; + + Error() << "Platform::WindowlessWglContext::makeCurrent(): cannot make context current:" << GetLastError(); + return false; +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments): WindowlessWglApplication{arguments, Configuration{}} {} +#endif + +WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWglApplication{arguments, NoCreate} { + createContext(configuration); +} + +WindowlessWglApplication::WindowlessWglApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} + +void WindowlessWglApplication::createContext() { createContext({}); } - /* Return true if the initialization succeeds */ - return _context->tryCreate(); +void WindowlessWglApplication::createContext(const Configuration& configuration) { + if(!tryCreateContext(configuration)) std::exit(1); } -WindowlessWglApplication::~WindowlessWglApplication() { - _context.reset(); +bool WindowlessWglApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessWglApplication::tryCreateContext(): context already created", false); + + WindowlessWglContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) + return false; - wglMakeCurrent(_deviceContext, nullptr); - wglDeleteContext(_renderingContext); + _glContext = std::move(glContext); + return true; } +WindowlessWglApplication::~WindowlessWglApplication() = default; + }} diff --git a/src/Magnum/Platform/WindowlessWglApplication.h b/src/Magnum/Platform/WindowlessWglApplication.h index 0b5b4f6d4..2f91b2558 100644 --- a/src/Magnum/Platform/WindowlessWglApplication.h +++ b/src/Magnum/Platform/WindowlessWglApplication.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessWglApplication, macro @ref MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessWglApplication, @ref Magnum::Platform::WindowlessWglContext, macro @ref MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN() */ #include @@ -39,24 +39,153 @@ #include "Magnum/Magnum.h" #include "Magnum/OpenGL.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" #ifndef DOXYGEN_GENERATING_OUTPUT /* Define stuff that we need because I can't be bothered with creating a new - header just for two defines */ -#define WGL_CONTEXT_FLAGS_ARB 0x2094 + header just for a few defines */ #define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 #endif namespace Magnum { namespace Platform { /** -@brief Windowless WGL application +@brief Windowless WGL context + +GL context using pure WINAPI, used in @ref WindowlessWglApplication. It is +built if `WITH_WINDOWLESSWGLAPPLICATION` is enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessWglContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * On desktop GL, if version is not specified in @p configuration, the + * application first tries to create core context (OpenGL 3.1+) and if + * that fails, falls back to compatibility OpenGL 2.1 context. However, + * on binary AMD and NVidia drivers, creating core context does not use + * the largest available version. If the application detects such case + * (and given workaround is not disabled in optionally passed + * @ref context instance), the core context is destroyed and + * compatibility OpenGL 2.1 context is created instead to make the + * driver use the latest available version. + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessWglContext(const Configuration& configuration, Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessWglContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessWglContext(const WindowlessWglContext&) = delete; + + /** @brief Move constructor */ + WindowlessWglContext(WindowlessWglContext&& other); + + /** @brief Copying is not allowed */ + WindowlessWglContext& operator=(const WindowlessWglContext&) = delete; + + /** @brief Move assignment */ + WindowlessWglContext& operator=(WindowlessWglContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessWglContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + HWND _window{}; + HDC _deviceContext{}; + HGLRC _context{}; +}; -Application for offscreen rendering using pure WINAPI. +/** +@brief Configuration -This application library is available on desktop OpenGL on Windows. It is built -if `WITH_WINDOWLESSWGLAPPLICATION` is enabled in CMake. +@see @ref WindowlessWglContext(), + @ref WindowlessWglApplication::WindowlessWglApplication(), + @ref WindowlessWglApplication::createContext(), + @ref WindowlessWglApplication::tryCreateContext() +*/ +class WindowlessWglContext::Configuration { + public: + /** + * @brief Context flag + * + * @see @ref Flags, @ref setFlags(), @ref Context::Flag + */ + enum class Flag: int { + Debug = WGL_CONTEXT_DEBUG_BIT_ARB /**< Create debug context */ + }; + + /** + * @brief Context flags + * + * @see @ref setFlags(), @ref Context::Flags + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Containers::EnumSet Flags; + #else + typedef Containers::EnumSet Flags; + #endif + + constexpr /*implicit*/ Configuration() {} + + /** @brief Context flags */ + Flags flags() const { return _flags; } + + /** + * @brief Set context flags + * @return Reference to self (for method chaining) + * + * Default is no flag. See also @ref Context::flags(). + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + private: + Flags _flags; +}; + +/** +@brief Windowless WGL application + +Application for offscreen rendering using @ref WindowlessWglContext. This +application library is available on desktop OpenGL on Windows. It is built if +`WITH_WINDOWLESSWGLAPPLICATION` is enabled in CMake. ## Bootstrap application @@ -102,20 +231,19 @@ class WindowlessWglApplication { /** @brief Application arguments */ struct Arguments { /** @brief Constructor */ - /*implicit*/ constexpr Arguments(int& argc, char** argv, HWND window) noexcept: argc{argc}, argv{argv}, window{window} {} + /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} int& argc; /**< @brief Argument count */ char** argv; /**< @brief Argument values */ - #ifndef DOXYGEN_GENERATING_OUTPUT - HWND window; - #endif }; - class Configuration; - - #ifndef DOXYGEN_GENERATING_OUTPUT - static int create(LRESULT(CALLBACK windowProcedure)(HWND, UINT, WPARAM, LPARAM)); - #endif + /** + * @brief Configuration + * + * @see @ref WindowlessWglApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessWglContext::Configuration Configuration; /** * @brief Default constructor @@ -126,6 +254,7 @@ class WindowlessWglApplication { * See @ref Configuration for more information. The program exits if * the context cannot be created, see @ref tryCreateContext() for an * alternative. + * @see @ref WindowlessWglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT explicit WindowlessWglApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); @@ -142,7 +271,15 @@ class WindowlessWglApplication { * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessWglApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessWglApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessWglApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessWglApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessWglApplication(const Arguments&, NoCreateT) instead") explicit WindowlessWglApplication(const Arguments& arguments, std::nullptr_t): WindowlessWglApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessWglApplication(const WindowlessWglApplication&) = delete; @@ -177,15 +314,7 @@ class WindowlessWglApplication { * constructor itself. Error message is printed and the program exits * if the context cannot be created, see @ref tryCreateContext() for an * alternative. - * - * On desktop GL, if version is not specified in @p configuration, the - * application first tries to create core context (OpenGL 3.1+) and if - * that fails, falls back to compatibility OpenGL 2.1 context. However, - * on binary AMD and NVidia drivers, creating core context does not use - * the largest available version. If the application detects such case, - * the core context is destroyed and compatibility OpenGL 2.1 context - * is created instead to make the driver use the latest available - * version. + * @see @ref WindowlessWglContext */ #ifdef DOXYGEN_GENERATING_OUTPUT void createContext(const Configuration& configuration = Configuration()); @@ -204,61 +333,10 @@ class WindowlessWglApplication { bool tryCreateContext(const Configuration& configuration); private: - HWND _window; - HDC _deviceContext; - HGLRC _renderingContext; - + WindowlessWglContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessWglApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessWglApplication::Configuration { - public: - /** - * @brief Context flag - * - * @see @ref Flags, @ref setFlags(), @ref Context::Flag - */ - enum class Flag: int { - Debug = WGL_CONTEXT_DEBUG_BIT_ARB /**< Create debug context */ - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - #ifndef DOXYGEN_GENERATING_OUTPUT - typedef Containers::EnumSet Flags; - #else - typedef Containers::EnumSet Flags; - #endif - - constexpr /*implicit*/ Configuration() {} - - /** @brief Context flags */ - Flags flags() const { return _flags; } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. See also @ref Context::flags(). - */ - Configuration& setFlags(Flags flags) { - _flags = flags; - return *this; - } - - private: - Flags _flags; -}; - /** @hideinitializer @brief Entry point for windowless WGL application @param className Class name @@ -270,29 +348,15 @@ windowless application header is included this macro is also aliased to `MAGNUM_WINDOWLESSAPPLICATION_MAIN()`. */ #define MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN(className) \ - int globalArgc; char** globalArgv; \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { \ - switch(message) { \ - case WM_CREATE: \ - { \ - className app({globalArgc, globalArgv, hWnd}); \ - PostQuitMessage(app.exec()); \ - } \ - break; \ - default: return DefWindowProc(hWnd, message, wParam, lParam); \ - } \ - return 0; \ - } \ int main(int argc, char** argv) { \ - globalArgc = argc; \ - globalArgv = argv; \ - return Magnum::Platform::WindowlessWglApplication::create(windowProcedure); \ + className app({argc, argv}); \ + return app.exec(); \ } #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessWglApplication WindowlessApplication; +typedef WindowlessWglContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp index 432274a8f..cf4c944e3 100644 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp @@ -35,11 +35,11 @@ namespace Magnum { namespace Platform { -#ifndef DOXYGEN_GENERATING_OUTPUT -int WindowlessWindowsEglApplication::create(LRESULT(CALLBACK windowProcedure)(HWND, UINT, WPARAM, LPARAM)) { +WindowlessWindowsEglContext::WindowlessWindowsEglContext(const Configuration& configuration, Context*) { + /* Create the window */ const WNDCLASS wc{ 0, - windowProcedure, + DefWindowProc, 0, 0, GetModuleHandle(nullptr), @@ -49,39 +49,12 @@ int WindowlessWindowsEglApplication::create(LRESULT(CALLBACK windowProcedure)(HW nullptr, L"Magnum Windowless Application" }; - if(!RegisterClass(&wc)) return 1; - - CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", + if(!RegisterClass(&wc)) return; + const HWND window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); - /* Hammer the return code out of the messaging thingy */ - MSG msg; - do {} while(GetMessageW(&msg, nullptr, 0, 0) != 0); - return msg.wParam; -} -#endif - -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments): WindowlessWindowsEglApplication{arguments, Configuration{}} {} -#endif - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWindowsEglApplication{arguments, nullptr} { - createContext(configuration); -} - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, std::nullptr_t): _window(arguments.window), _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} - -void WindowlessWindowsEglApplication::createContext() { createContext({}); } - -void WindowlessWindowsEglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessWindowsEglApplication::tryCreateContext(): context already created", false); - /* Initialize */ - _display = eglGetDisplay(GetDC(_window)); + _display = eglGetDisplay(GetDC(window)); if(!eglInitialize(_display, nullptr, nullptr)) { Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); return false; @@ -117,7 +90,8 @@ bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& conf EGL_NONE }; EGLint configCount; - if(!eglChooseConfig(_display, attribs, &_config, 1, &configCount)) { + EGLConfig config; + if(!eglChooseConfig(_display, attribs, &config, 1, &configCount)) { Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot get EGL visual config:" << Implementation::eglErrorString(eglGetError()); return false; } @@ -142,30 +116,73 @@ bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& conf EGL_NONE }; - if(!(_glContext = eglCreateContext(_display, _config, EGL_NO_CONTEXT, attributes))) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); - return false; - } - if(!(_surface = eglCreateWindowSurface(_display, _config, _window, nullptr))) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot create window surface:" << Implementation::eglErrorString(eglGetError()); - return false; + if(!(_context = eglCreateContext(_display, config, EGL_NO_CONTEXT, attributes))) { + Error() << "Platform::WindowlessWindowsEglContext: cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); + return; } - if(!eglMakeCurrent(_display, _surface, _surface, _glContext)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot make context current:" << Implementation::eglErrorString(eglGetError()); - return false; - } + if(!(_surface = eglCreateWindowSurface(_display, config, _window, nullptr))) + Error() << "Platform::WindowlessWindowsEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); +} + +WindowlessWindowsEglContext::WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other): _window{other._window}, _display{other._display}, _surface{other._surface}, _context{other._context} { + other._window = {}; + other._display = {}; + other._surface = {}; + other._context = {}; +} + +WindowlessWindowsEglContext::~WindowlessWindowsEglContext() { + if(_context) eglDestroyContext(_display, _context); + if(_surface) eglDestroySurface(_display, _surface); + if(_display) eglTerminate(_display); + if(_window) DestroyWindow(_window); +} + +WindowlessWindowsEglContext& WindowlessWindowsEglContext::operator=(WindowlessWindowsEglContext&& other) { + using std::swap; + swap(other._window, _window); + swap(other._display, _display); + swap(other._surface, _surface); + swap(other._context, _context); + return *this; +} - /* Return true if the initialization succeeds */ - return _context->tryCreate(); +bool WindowlessWindowsEglContext::makeCurrent() { + if(eglMakeCurrent(_display, _surface, _surface, _context)) + return true; + + Error() << "Platform::WindowlessWindowsEglContext::makeCurrent(): cannot make context current:" << GetLastError(); + return false; } -WindowlessWindowsEglApplication::~WindowlessWindowsEglApplication() { - _context.reset(); +#ifndef DOXYGEN_GENERATING_OUTPUT +WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments): WindowlessWindowsEglApplication{arguments, Configuration{}} {} +#endif - eglDestroyContext(_display, _glContext); - eglDestroySurface(_display, _surface); - eglTerminate(_display); +WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWindowsEglApplication{arguments, NoCreate} { + createContext(configuration); } +WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, std::nullptr_t): _glContext{NoCreate}, _context{new Context{NoCreate, arguments.argc, arguments.argv}} {} + +void WindowlessWindowsEglApplication::createContext() { createContext({}); } + +void WindowlessWindowsEglApplication::createContext(const Configuration& configuration) { + if(!tryCreateContext(configuration)) std::exit(1); +} + +bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& configuration) { + CORRADE_ASSERT(_context->version() == Version::None, "Platform::WindowlessWindowsEglApplication::tryCreateContext(): context already created", false); + + WindowlessWindowsEglContext glContext{configuration, _context.get()}; + if(!glContext.isCreated() || !glContext.makeCurrent() || !_context->tryCreate()) + return false; + + _glContext = std::move(glContext); + return true; +} + +WindowlessWindowsEglApplication::~WindowlessWindowsEglApplication() = default; + }} diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.h b/src/Magnum/Platform/WindowlessWindowsEglApplication.h index 87c6e663f..07a028458 100644 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.h +++ b/src/Magnum/Platform/WindowlessWindowsEglApplication.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Platform::WindowlessWindowsEglApplication, macro @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() + * @brief Class @ref Magnum::Platform::WindowlessWindowsEglApplication, @ref Magnum::Platform::WindowlessWindowsEglContext, macro @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() */ #include @@ -41,15 +41,136 @@ #include "Magnum/Magnum.h" #include "Magnum/OpenGL.h" +#include "Magnum/Tags.h" #include "Magnum/Platform/Platform.h" namespace Magnum { namespace Platform { /** -@brief Windowless Windows/EGL application +@brief Windowless Windows/EGL context + +GL context using pure WINAPI and EGL, used in @ref WindowlessWindowsEglApplication. +It is built if `WITH_WINDOWLESSWINDOWSEGLAPPLICATION` is enabled in CMake. + +Meant to be used when there is a need to manage (multiple) GL contexts +manually. See @ref platform-windowless-contexts for more information. If no +other application header is included, this class is also aliased to +`Platform::WindowlessGLContext`. +*/ +class WindowlessWindowsEglContext { + public: + class Configuration; + + /** + * @brief Constructor + * @param configuration Context configuration + * @param context Optional Magnum context instance constructed + * using @ref NoCreate to manage driver workarounds + * + * Once the context is created, make it current using @ref makeCurrent() + * and create @ref Platform::Context instance to be able to use Magnum. + * @see @ref isCreated() + */ + explicit WindowlessWindowsEglContext(const Configuration& configuration, Context* context = nullptr); + + /** + * @brief Construct without creating the context + * + * Move a instance with created context over to make it usable. + */ + explicit WindowlessWindowsEglContext(NoCreateT) {} + + /** @brief Copying is not allowed */ + WindowlessWindowsEglContext(const WindowlessWindowsEglContext&) = delete; + + /** @brief Move constructor */ + WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other); -Application for offscreen rendering using pure WINAPI and EGL. + /** @brief Copying is not allowed */ + WindowlessWindowsEglContext& operator=(const WindowlessWindowsEglContext&) = delete; + + /** @brief Move assignment */ + WindowlessWindowsEglContext& operator=(WindowlessWindowsEglContext&& other); + + /** + * @brief Destructor + * + * Destroys the context, if any. + */ + ~WindowlessWindowsEglContext(); + + /** @brief Whether the context is created */ + bool isCreated() const { return _context; } + + /** + * @brief Make the context current + * + * Prints error message and returns `false` on failure, otherwise + * returns `true`. + */ + bool makeCurrent(); + + private: + HWND _window{}; + EGLDisplay _display{}; + EGLSurface _surface{}; + EGLContext _context{}; +}; + +/** +@brief Configuration +@see @ref WindowlessWindowsEglContext(), + @ref WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(), + @ref WindowlessWindowsEglApplication::createContext(), + @ref WindowlessWindowsEglApplication::tryCreateContext() +*/ +class WindowlessWindowsEglContext::Configuration { + public: + /** + * @brief Context flag + * + * @see @ref Flags, @ref setFlags(), @ref Context::Flag + */ + enum class Flag: int { + Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR /**< Create debug context */ + }; + + /** + * @brief Context flags + * + * @see @ref setFlags(), @ref Context::Flags + */ + #ifndef DOXYGEN_GENERATING_OUTPUT + typedef Containers::EnumSet Flags; + #else + typedef Containers::EnumSet Flags; + #endif + + constexpr /*implicit*/ Configuration() {} + + /** @brief Context flags */ + Flags flags() const { return _flags; } + + /** + * @brief Set context flags + * @return Reference to self (for method chaining) + * + * Default is no flag. See also @ref Context::flags(). + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + private: + Flags _flags; +}; + +/** +@brief Windowless Windows/EGL application + +Application for offscreen rendering using @ref WindowlessWindowsEglContext. This application library is available on OpenGL ES (also ANGLE) on Windows. It is built if `WITH_WINDOWLESSWINDOWSEGLAPPLICATION` is enabled in CMake. @@ -97,20 +218,19 @@ class WindowlessWindowsEglApplication { /** @brief Application arguments */ struct Arguments { /** @brief Constructor */ - /*implicit*/ constexpr Arguments(int& argc, char** argv, HWND window) noexcept: argc{argc}, argv{argv}, window{window} {} + /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} int& argc; /**< @brief Argument count */ char** argv; /**< @brief Argument values */ - #ifndef DOXYGEN_GENERATING_OUTPUT - HWND window; - #endif }; - class Configuration; - - #ifndef DOXYGEN_GENERATING_OUTPUT - static int create(LRESULT(CALLBACK windowProcedure)(HWND, UINT, WPARAM, LPARAM)); - #endif + /** + * @brief Configuration + * + * @see @ref WindowlessWindowsEglApplication(), @ref createContext(), + * @ref tryCreateContext() + */ + typedef WindowlessWindowsEglContext::Configuration Configuration; /** * @brief Default constructor @@ -137,7 +257,15 @@ class WindowlessWindowsEglApplication { * Unlike above, the context is not created and must be created later * with @ref createContext() or @ref tryCreateContext(). */ - explicit WindowlessWindowsEglApplication(const Arguments& arguments, std::nullptr_t); + explicit WindowlessWindowsEglApplication(const Arguments& arguments, NoCreateT); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @copybrief WindowlessWindowsEglApplication(const Arguments&, NoCreateT) + * @deprecated Use @ref WindowlessWindowsEglApplication(const Arguments&, NoCreateT) instead. + */ + CORRADE_DEPRECATED("use WindowlessWindowsEglApplication(const Arguments&, NoCreateT) instead") explicit WindowlessWindowsEglApplication(const Arguments& arguments, std::nullptr_t): WindowlessWindowsEglApplication{arguments, NoCreate} {} + #endif /** @brief Copying is not allowed */ WindowlessWindowsEglApplication(const WindowlessWindowsEglApplication&) = delete; @@ -190,63 +318,10 @@ class WindowlessWindowsEglApplication { bool tryCreateContext(const Configuration& configuration); private: - HWND _window; - EGLDisplay _display; - EGLConfig _config; - EGLSurface _surface; - EGLContext _glContext; - + WindowlessWindowsEglContext _glContext; std::unique_ptr _context; }; -/** -@brief Configuration - -@see @ref WindowlessWindowsEglApplication(), @ref createContext(), - @ref tryCreateContext() -*/ -class WindowlessWindowsEglApplication::Configuration { - public: - /** - * @brief Context flag - * - * @see @ref Flags, @ref setFlags(), @ref Context::Flag - */ - enum class Flag: int { - Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR /**< Create debug context */ - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - #ifndef DOXYGEN_GENERATING_OUTPUT - typedef Containers::EnumSet Flags; - #else - typedef Containers::EnumSet Flags; - #endif - - constexpr /*implicit*/ Configuration() {} - - /** @brief Context flags */ - Flags flags() const { return _flags; } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. See also @ref Context::flags(). - */ - Configuration& setFlags(Flags flags) { - _flags = flags; - return *this; - } - - private: - Flags _flags; -}; - /** @hideinitializer @brief Entry point for windowless Windows/EGL application @param className Class name @@ -257,30 +332,16 @@ code, see @ref portability-applications for more information. When no other windowless application header is included this macro is also aliased to `MAGNUM_WINDOWLESSAPPLICATION_MAIN()`. */ -#define MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) \ - int globalArgc; char** globalArgv; \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { \ - switch(message) { \ - case WM_CREATE: \ - { \ - className app({globalArgc, globalArgv, hWnd}); \ - PostQuitMessage(app.exec()); \ - } \ - break; \ - default: return DefWindowProc(hWnd, message, wParam, lParam); \ - } \ - return 0; \ - } \ +#define MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) \ int main(int argc, char** argv) { \ - globalArgc = argc; \ - globalArgv = argv; \ - return Magnum::Platform::WindowlessWindowsEglApplication::create(windowProcedure); \ + className app({argc, argv}); \ + return app.exec(); \ } #ifndef DOXYGEN_GENERATING_OUTPUT #ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN typedef WindowlessWindowsEglApplication WindowlessApplication; +typedef WindowlessWindowsEglContext WindowlessGLContext; #define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) #else #undef MAGNUM_WINDOWLESSAPPLICATION_MAIN diff --git a/src/Magnum/Platform/magnum-info.cpp b/src/Magnum/Platform/magnum-info.cpp index 68cf49f56..0c38fa460 100644 --- a/src/Magnum/Platform/magnum-info.cpp +++ b/src/Magnum/Platform/magnum-info.cpp @@ -141,7 +141,7 @@ class MagnumInfo: public Platform::WindowlessApplication { int exec() override { return 0; } }; -MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplication(arguments, nullptr) { +MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} { Utility::Arguments args; args.addBooleanOption('s', "short").setHelp("short", "display just essential info and exit") .addBooleanOption("extension-strings").setHelp("extension-strings", "list all extension strings provided by the driver (implies --short)") diff --git a/src/Magnum/Test/AbstractOpenGLTester.h b/src/Magnum/Test/AbstractOpenGLTester.h index 9e2450d57..e159cd1ae 100644 --- a/src/Magnum/Test/AbstractOpenGLTester.h +++ b/src/Magnum/Test/AbstractOpenGLTester.h @@ -61,7 +61,7 @@ class AbstractOpenGLTester: public TestSuite::Tester { private: struct WindowlessApplication: Platform::WindowlessApplication { - explicit WindowlessApplication(const Arguments& arguments): Platform::WindowlessApplication{arguments, nullptr} {} + explicit WindowlessApplication(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} {} int exec() override final { return 0; } using Platform::WindowlessApplication::tryCreateContext; @@ -94,31 +94,7 @@ std::optional AbstractOpenGLTester:: #define MAGNUM_VERIFY_NO_ERROR() CORRADE_COMPARE(Magnum::Renderer::error(), Magnum::Renderer::Error::NoError) -#ifdef CORRADE_TARGET_WINDOWS -#define MAGNUM_GL_TEST_MAIN(Class) \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); \ - LRESULT CALLBACK windowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { \ - int ret = 0; \ - switch(message) { \ - case WM_CREATE: \ - { \ - Magnum::Test::AbstractOpenGLTester::_windowlessApplicationArguments->window = hWnd; \ - Class t; \ - t.registerTest(__FILE__, #Class); \ - PostQuitMessage(ret = t.exec( \ - Magnum::Test::AbstractOpenGLTester::_windowlessApplicationArguments->argc, \ - Magnum::Test::AbstractOpenGLTester::_windowlessApplicationArguments->argv)); \ - } \ - break; \ - default: return DefWindowProc(hWnd, message, wParam, lParam); \ - } \ - return ret; \ - } \ - int main(int argc, char** argv) { \ - Magnum::Test::AbstractOpenGLTester::_windowlessApplicationArguments.emplace(argc, argv, nullptr); \ - return Magnum::Platform::WindowlessApplication::create(windowProcedure); \ - } -#elif defined(CORRADE_TESTSUITE_TARGET_XCTEST) +#ifdef CORRADE_TESTSUITE_TARGET_XCTEST #define MAGNUM_GL_TEST_MAIN(Class) \ int CORRADE_VISIBILITY_EXPORT corradeTestMain(int argc, char** argv) { \ Magnum::Test::AbstractOpenGLTester::_windowlessApplicationArguments.emplace(argc, argv); \ diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index 78a90dbf4..29f8bbc50 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -101,7 +101,7 @@ class FontConverter: public Platform::WindowlessApplication { Utility::Arguments args; }; -FontConverter::FontConverter(const Arguments& arguments): Platform::WindowlessApplication(arguments, nullptr) { +FontConverter::FontConverter(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} { args.addArgument("input").setHelp("input", "input font") .addArgument("output").setHelp("output", "output filename prefix") .addNamedArgument("font").setHelp("font", "font plugin") diff --git a/src/Magnum/TextureTools/distancefieldconverter.cpp b/src/Magnum/TextureTools/distancefieldconverter.cpp index bded817b8..c06831f36 100644 --- a/src/Magnum/TextureTools/distancefieldconverter.cpp +++ b/src/Magnum/TextureTools/distancefieldconverter.cpp @@ -102,7 +102,7 @@ class DistanceFieldConverter: public Platform::WindowlessApplication { Utility::Arguments args; }; -DistanceFieldConverter::DistanceFieldConverter(const Arguments& arguments): Platform::WindowlessApplication(arguments, nullptr) { +DistanceFieldConverter::DistanceFieldConverter(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} { args.addArgument("input").setHelp("input", "input image") .addArgument("output").setHelp("output", "output image") .addOption("importer", "AnyImageImporter").setHelp("importer", "image importer plugin")