diff --git a/doc/compilation-speedup.dox b/doc/compilation-speedup.dox index 688401977..53dbba44e 100644 --- a/doc/compilation-speedup.dox +++ b/doc/compilation-speedup.dox @@ -25,27 +25,27 @@ namespace Magnum { /** @page compilation-speedup Speeding up compilation -@brief Techniques for reducing compilation times. +@brief Techniques for reducing compilation times used by Magnum itself and recommended for application code as well. @tableofcontents @m_footernavigation @section compilation-forward-declarations Forward declarations instead of includes -Essential thing when speeding up compilation is reducing number of @cpp #include @ce -directives in both headers and source files. Magnum is strictly applying this -policy in all header files, so all types which are not directly used in the -header have only forward declarations. +An essential thing when speeding up compilation is reducing number of +@cpp #include @ce directives in both headers and source files. Magnum is +strictly applying this policy in all header files, so all types which are not +directly used in the header have only forward declarations. For example, when including @ref Magnum.h, you get shortcut typedefs for floating-point vectors and matrices like @ref Vector3 and @ref Matrix4, but to actually use any of them, you have to include the respective header, e.g. @ref Magnum/Math/Vector3.h. -You are encouraged to use forward declarations also in your code. However, for -some types it can be too cumbersome --- e.g. too many template parameters, -typedefs etc. In this case a header with forward declarations is usually -available, each namespace has its own: +You are encouraged to use forward declarations in your code as well. However, +for some types it can be too cumbersome --- e.g. too many template parameters, +default template arguments, typedefs etc. Instead, forward declaration headers +are available, with each namespace having its own: - @ref Corrade/Corrade.h - @ref Corrade/Containers/Containers.h diff --git a/doc/opengl-wrapping.dox b/doc/opengl-wrapping.dox index 2a3821728..d642ca725 100644 --- a/doc/opengl-wrapping.dox +++ b/doc/opengl-wrapping.dox @@ -31,9 +31,9 @@ namespace Magnum { @m_footernavigation The purpose of the @ref GL library is to simplify interaction with the OpenGL -API using type-safe C++11 features, abstracting away extension and platform -differences, tracking the state for optimum performance and selecting the best -available code path for given system. +API using type-safe C++11 features and RAII, abstracting away extension and +platform differences, tracking the state for optimum performance and selecting +the best available code path for given driver. Magnum provides wrappers for most native OpenGL objects like buffers, textures, meshes, queries, transform feedback objects, shaders etc., but makes it @@ -42,49 +42,65 @@ libraries if the user wants to. @section opengl-wrapping-instances OpenGL object wrapper instances -By default, all underlying OpenGL objects are created in wrapper class -constructor and deleted in wrapper class destructor. Constructing an object -using default constructor requires active @ref GL::Context instance. All OpenGL -objects are movable (but not copyable), although for performance reasons (and -contrary to standard C++11 practice), the moved-from instance does *not* have -any associated OpenGL object and is thus in *invalid state*. Using instance in -moved-from state may result in OpenGL errors being generated, in some cases -even application crashes. - -Besides the default behavior, it is possible to construct the object without -creating the underlying OpenGL object using the @ref NoCreate tag. -Constructing the object this way does not require any active context and its -state is then equivalent to the moved-from state. It is useful in case you need -to construct the object before creating context (such as class members) or if -you know you would overwrite it later with another object: - -@snippet MagnumGL.cpp opengl-wrapping-nocreate - - - -@m_class{m-note m-warning} - -@par - Note that calling anything on objects created this way is not defined (and - not checked or guarded in any way) and may result in crashes. If you want delayed object creation with safety checks (however with some extra - memory overhead), wrap the objects in an @ref Corrade::Containers::Optional. +By default, all wrapper classes follow RAII -- underlying OpenGL objects are +created in the class constructor and deleted in the wrapper class destructor. +All OpenGL objects are movable (but not copyable) and moves are *destructive* +--- the moved-from instance does *not* have any associated OpenGL object and +is thus in an *invalid state*. Calling anything on instances in a moved-from +state may thus result in OpenGL errors being generated, in some cases even +application crashes. If you need to preserve the underlying OpenGL object after destruction, you can call @cpp release() @ce. It returns ID of the underlying object, the instance -is then equivalent to moved-from state and you are responsible for proper +is then equivalent to the moved-from state and you are responsible for proper deletion of the returned OpenGL object (note that it is possible to just query -ID of the underlying without releasing it using `id()`). It is also possible to -do the opposite --- wrapping existing OpenGL object ID into Magnum object -instance using @cpp wrap() @ce. +ID of the underlying without releasing it using @cpp id() @ce). It is also +possible to do the opposite --- wrapping an existing OpenGL object ID into a +Magnum object instance using @cpp wrap() @ce: @snippet MagnumGL.cpp opengl-wrapping-transfer -The @cpp NoCreate @ce constructor, @cpp wrap() @ce and @cpp release() @ce -functions are available for all OpenGL classes except @ref GL::Shader, where -wrapping external instances makes less sense. - -Note that interaction with third-party OpenGL code as shown above usually needs -special attention: +The @cpp wrap() @ce and @cpp release() @ce functions are available for all +OpenGL classes except for @ref GL::Shader, instances of which are rather +short-lived and thus wrapping external instances makes less sense. + +@attention + Note that interaction with third-party OpenGL code as shown above usually + needs special attention due to the global nature of the OpenGL state + tracker. See @ref opengl-state-tracking below for more information. + +@subsection opengl-wrapping-instances-nocreate Delayed context creation and NoCreate constructors + +Constructing a Magnum GL object requires an active @ref GL::Context instance. +By default, this instance is automatically created by +@ref Platform::Sdl2Application "Platform::*Application" constructors, however +if you use @ref platform-configuration-delayed "delayed context creation" or +are directly interfacing with @ref platform-custom "custom platform toolkits", +this is not the case. If OpenGL functionality gets called before the +@ref GL::Context instance is created (or after it has been destroyed), you may +end up with the following assertion: + +@code{.shell-session} +GL::Context::current(): no current context +@endcode + +In the common case of delayed context creation, this problem can be +circumvented by constructing the OpenGL objects using the @ref NoCreate tag +first and populating them with live instances once the context is ready. For +example: + +@snippet MagnumGL-application.cpp opengl-wrapping-nocreate + +Please note that objects constructed using the @ref NoCreate tag are equivalent +to the moved-from state, and thus again calling anything on these may result in +OpenGL errors being generated or even application crashes --- you're +responsible for correctly initializing the objects before use. If you are fine +with trading some overhead for extra safety checks, wrap the objects in an +@relativeref{Corrade,Containers::Optional} instead of using the @ref NoCreate +constructor. + +The @ref NoCreate constructor is available for all OpenGL classes, including +builtin shaders. @section opengl-state-tracking State tracking and interaction with third-party code diff --git a/doc/platform.dox b/doc/platform.dox index bae1a6483..557c079ae 100644 --- a/doc/platform.dox +++ b/doc/platform.dox @@ -143,22 +143,49 @@ this: @snippet MagnumPlatform.cpp configuration -However, sometimes you would need to configure the application based on some -configuration file or system introspection. In that case you can pass -@ref NoCreate instead of @ref Platform::Sdl2Application::Configuration "Configuration" -instance and then specify it later with @ref Platform::Sdl2Application::create() "create()": +@subsection platform-configuration-delayed Delayed context creation + +Sometimes you may want to set up the application based on a configuration file +or system introspection, which can't really be done inside the base class +initializer. You can specify @ref NoCreate in the constructor instead and pass +the @relativeref{Platform::Sdl2Application,Configuration} later to a +@relativeref{Platform::Sdl2Application,create()} function: @snippet MagnumPlatform.cpp createcontext -If the context creation in constructor or @ref Platform::Sdl2Application::create() "create()" -fails, the application exits. However, it is also possible to negotiate the -context using @ref Platform::Sdl2Application::tryCreate() "tryCreate()". The -only difference is that this function returns `false` instead of exiting. You -can for example try enabling MSAA and if the context creation fails, fall back -to no-AA rendering: +If context creation in the constructor or in +@relativeref{Platform::Sdl2Application,create()} fails, the application prints +an error message to standard output and exits. While that frees you from having +to do explicit error handling, sometimes a more graceful behavior may be +desirable --- with @relativeref{Platform::Sdl2Application,tryCreate()} the +function returns @cpp false @ce instead of exiting and it's up to you whether +you abort the launch or retry with different configuration. You can for example +try enabling MSAA first, and if the context creation fails, fall back to no-AA +rendering: @snippet MagnumPlatform.cpp trycreatecontext + + +@m_class{m-block m-warning} + +@par Implications for GL objects as class members + Delaying GL context creation to + @relativeref{Platform::Sdl2Application,create()} / + @relativeref{Platform::Sdl2Application,tryCreate()} means that, at the + point when class members get constructed, the context isn't available yet. + If you have GL objects such as @ref GL::Mesh or @ref Shaders::PhongGL as + class members, the application may hit the following assert during startup: +@par + @code{.shell-session} + GL::Context::current(): no current context + @endcode +@par + The solution is to construct the GL objects with @ref NoCreate constructors + as well and populate them with live instances only after the context has + been created. See @ref opengl-wrapping-instances-nocreate for more + information. + @section platform-custom Using custom platform toolkits In case you want to use some not-yet-supported toolkit or you don't want to use diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index 9af7aca4b..f1c6299e6 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -203,7 +203,7 @@ if(WITH_SDL2APPLICATION AND TARGET_GL) add_library(snippets-MagnumPlatform STATIC MagnumPlatform.cpp - MagnumGL-framebuffer.cpp) + MagnumGL-application.cpp) target_link_libraries(snippets-MagnumPlatform PRIVATE MagnumSdl2Application) set_target_properties( diff --git a/doc/snippets/MagnumGL-framebuffer.cpp b/doc/snippets/MagnumGL-application.cpp similarity index 80% rename from doc/snippets/MagnumGL-framebuffer.cpp rename to doc/snippets/MagnumGL-application.cpp index 70b82db50..e17606270 100644 --- a/doc/snippets/MagnumGL-framebuffer.cpp +++ b/doc/snippets/MagnumGL-application.cpp @@ -26,11 +26,38 @@ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/DefaultFramebuffer.h" #include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Mesh.h" #include "Magnum/Platform/Sdl2Application.h" #include "Magnum/Platform/GLContext.h" +#include "Magnum/Shaders/PhongGL.h" using namespace Magnum; +#define DOXYGEN_IGNORE(...) __VA_ARGS__ + +/* [opengl-wrapping-nocreate] */ +class MyApplication: public Platform::Application { + DOXYGEN_IGNORE(explicit MyApplication(const Arguments& arguments);) + + private: + /* Placeholders without an underlying GL object */ + GL::Mesh _mesh{NoCreate}; + Shaders::PhongGL _shader{NoCreate}; + DOXYGEN_IGNORE() +}; + +MyApplication::MyApplication(const Arguments& arguments): + Platform::Application{arguments, NoCreate} +{ + DOXYGEN_IGNORE() + create(); + + /* GL context is ready, now it's safe to populate the GL objects */ + _mesh = GL::Mesh{}; + _shader = Shaders::PhongGL{}; +} +/* [opengl-wrapping-nocreate] */ + struct A: Platform::Sdl2Application { /* [DefaultFramebuffer-usage-viewport] */ void viewportEvent(ViewportEvent& event) override { diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index e11b93612..5876a4c3d 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -120,23 +120,6 @@ carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}) } #endif -{ -#if defined(CORRADE_TARGET_GCC) && __GNUC__ >= 11 -#pragma GCC diagnostic push -/* Stupid thing. YES I WANT THIS TO BE A FUNCTION, CAN YOU SHUT UP */ -#pragma GCC diagnostic ignored "-Wvexing-parse" -#endif -auto importSomeMesh() -> std::tuple; -#if defined(CORRADE_TARGET_GCC) && __GNUC__ >= 11 -#pragma GCC diagnostic pop -#endif -/* [opengl-wrapping-nocreate] */ -GL::Mesh mesh{NoCreate}; -GL::Buffer vertices{NoCreate}, indices{NoCreate}; -std::tie(mesh, vertices, indices) = importSomeMesh(); -/* [opengl-wrapping-nocreate] */ -} - { struct Foo { void setSomeBuffer(GLuint) {} diff --git a/doc/snippets/MagnumPlatform.cpp b/doc/snippets/MagnumPlatform.cpp index 7da163055..a1f312e97 100644 --- a/doc/snippets/MagnumPlatform.cpp +++ b/doc/snippets/MagnumPlatform.cpp @@ -89,9 +89,9 @@ struct MyApplication: Platform::Application { /* [configuration] */ MyApplication::MyApplication(const Arguments& arguments): - Platform::Application(arguments, Configuration{} + Platform::Application{arguments, Configuration{} .setTitle("My Application") - .setSize({800, 600})) + .setSize({800, 600})} { // ... } diff --git a/doc/troubleshooting.dox b/doc/troubleshooting.dox index 909788932..cc618cc8b 100644 --- a/doc/troubleshooting.dox +++ b/doc/troubleshooting.dox @@ -25,29 +25,87 @@ namespace Magnum { /** @page troubleshooting Troubleshooting -@brief Various tricks to overcome common building and rendering issues. +@brief Various tricks and solutions to common pitfalls. +@tableofcontents @m_footernavigation @section troubleshooting-building Building issues -If your project suddenly stops building after Magnum upgrade, check these +If your project suddenly stops building after a Magnum upgrade, check these things: -- If the building fails on CMake step, be sure that you have up-to-date +- If building fails on the CMake step, be sure that you have up-to-date `FindCorrade.cmake`, `FindMagnum.cmake` and other CMake modules in your - project (`FindSDL2.cmake`). They are contained in `modules/` directory of - Magnum sources (and sources of other projects) also are installed into - `share/cmake/Magnum`. -- In some cases when the changes done to build system are too drastic, - recreating the build dir or clearing CMake cache is needed, but this is - a very rare occasion. -- The library is constantly evolving, thus some API might get deprecated over - time (and later removed). Either build the libraries with `BUILD_DEPRECATED` - or switch to non-deprecated features. See @ref building for more - information. - -@section troubleshooting-rendering Rendering issues + project (`FindSDL2.cmake`, ...). They are contained in `modules/` directory + of Magnum repositories and also get installed into `share/cmake/Magnum`. +- In very rare cases the CMake build directory may not survive an upgrade. If + you get some particularly cursed errors, try recreating it from scratch. +- The library is constantly evolving, meaning that APIs may get deprecated + and removed over time. If you upgrade to a version that deprecated a + particular API you use, the code will emit a deprecation warning, + suggesting a migration path. Except for rare cases, the deprecated APIs are + guaranteed to stay in the codebase for a year at least (or two subsequent + version releases, whichever is longer) and only then they get removed. To + make sure you're no longer using any deprecated functionality, it's + possible to disable the `BUILD_DEPRECATED` @ref building-features "CMake option" + when building Magnum, although for a smoother experience it's recommended + to flip this option back on when upgrading. A list of deprecated APIs is + maintained @ref changelog-latest-deprecated "in the changelog". +- In some rare cases, it's impossible to provide a deprecated migration path + and an API gets changed in a backwards-incompatible manner, directly + leading to a compile error. Another possibility is that transitive header + dependencies got cleaned up for @ref compilation-forward-declarations "speeding up compilation" and you're now missing an @cpp #include @ce. The major + compatiblity breakages are always listed @ref changelog-latest-compatibility "in the changelog". +- If you upgrade from a really old release, it's recommended to gradually + upgrade over tagged versions of Magnum and not jumping directly to latest + version. That way you're more likely to follow the deprecation path, + instead of directly ending up with long-deprecated APIs being gone with no + direct way to know what should be used instead. If in doubt, browse the + @ref changelog "old changelogs". + +@section troubleshooting-runtime Runtime issues + +Magnum makes heavy use of assertions to catch programmer errors (as opposed to +runtime or data-dependent errors, which get handled in more graceful ways), and +because past experience showed their usefulness, majority of assertions is +@ref CORRADE_NO_ASSERT "by default kept even in release builds". + +If your application abruptly exits, it's important to know whether it was a +regular *exit*, an *abort* or a *crash*, as each of these may point to a +different problem. + +- Except for crashes, in which case the application usually won't even have a + chance to complain about anything, you should get a message on the standard + error output: + - On Unix systems the output can be seen when running from a console + - On Windows, if you don't see the console, switch the build back to a + console app @ref platform-windows-hiding-console "instead of a GUI app" + --- in case of CMake you can temporarily remove the `WIN32` part from + your @cmake add_executable() @ce call + - On Android @ref platforms-android-output-redirection "you can use logcat" + - On Emscripten the output is printed to the browser console +- A regular exit may happen during startup due to an error in arguments + passed on the command line, or when a window / context creation fails. The + error output should mention what happened. +- An abort is an assertion failure, with the error message telling you why. + It's an abort in order to trigger a debugger break (or the assertion dialog + on Windows) or create a core dump (on Unix systems) so you can see the + backtrace leading to the error. + - One of the more frequent assertion messages is + @cb{.shell-session} GL::Context::current(): no current context @ce. + This happens when you try to use OpenGL functionality when the OpenGL + context is not yet created (or no longer exists). See + @ref opengl-wrapping-instances-nocreate for more information. +- A crash is usually a memory issue. To find the root cause, by far the + easiest is to hook up AddressSanitizer (`-fsanitize=address` on GCC, Clang + and [recent MSVC](https://docs.microsoft.com/en-us/cpp/sanitizers/asan)). + It can detect out-of-bounds accesses, use-after-free, leaks and other + issues and upon discovering a problem it prints a lengthy diagnostic about + what happened, where does the memory come from and what code touched it and + how. + +@section troubleshooting-opengl OpenGL issues If you are experiencing the so-called "black screen of death", weird behavior or crashes on GL calls, you might want to try these things: @@ -99,7 +157,7 @@ or crashes on GL calls, you might want to try these things: having similar ability to reset its state tracker), otherwise you may need to save and restore GL state manually for that library to work. -@section troubleshooting-debugging Debugging rendering +@subsection troubleshooting-opengl-debugging Debugging rendering - Use @ref GL::TimeQuery to find hot spots in the rendering code. - @ref GL::DebugMessage::insert() "Mark relevant parts of code" to find them diff --git a/src/Magnum/GL/Context.h b/src/Magnum/GL/Context.h index 94d7a4337..fe01327be 100644 --- a/src/Magnum/GL/Context.h +++ b/src/Magnum/GL/Context.h @@ -204,12 +204,12 @@ instances for other OpenGL contexts, *first* you need to "unset" the current one with @ref makeCurrent() and *then* create another instance, which will then become implicitly active: -@snippet MagnumGL-framebuffer.cpp Context-makeCurrent-nullptr +@snippet MagnumGL-application.cpp Context-makeCurrent-nullptr Once all needed instances are created, switch between them right after making the underlying GL context current: -@snippet MagnumGL-framebuffer.cpp Context-makeCurrent +@snippet MagnumGL-application.cpp Context-makeCurrent @section GL-Context-multithreading Thread safety diff --git a/src/Magnum/GL/DefaultFramebuffer.h b/src/Magnum/GL/DefaultFramebuffer.h index 3f01d0ff7..27390c828 100644 --- a/src/Magnum/GL/DefaultFramebuffer.h +++ b/src/Magnum/GL/DefaultFramebuffer.h @@ -49,7 +49,7 @@ classes, pass the new size in your @ref Platform::Sdl2Application::viewportEvent() "viewportEvent()" implementation, for example: -@snippet MagnumGL-framebuffer.cpp DefaultFramebuffer-usage-viewport +@snippet MagnumGL-application.cpp DefaultFramebuffer-usage-viewport Next thing you probably want is to clear all used buffers before performing any drawing. Again, in case you're using one of the @@ -57,7 +57,7 @@ any drawing. Again, in case you're using one of the @ref Platform::Sdl2Application::drawEvent() "drawEvent()" implementation, for example: -@snippet MagnumGL-framebuffer.cpp DefaultFramebuffer-usage-clear +@snippet MagnumGL-application.cpp DefaultFramebuffer-usage-clear See documentation of particular functions and @ref Framebuffer documentation for more involved usage, usage of non-default or multiple framebuffers.