Skip to content

Commit

Permalink
doc: add docs for GL context asserts and other troubleshooting.
Browse files Browse the repository at this point in the history
  • Loading branch information
mosra committed Jul 17, 2021
1 parent 27b0527 commit 2bd933d
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 98 deletions.
18 changes: 9 additions & 9 deletions doc/compilation-speedup.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 55 additions & 39 deletions doc/opengl-wrapping.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

<b></b>

@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

Expand Down
47 changes: 37 additions & 10 deletions doc/platform.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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

<b></b>

@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
Expand Down
2 changes: 1 addition & 1 deletion doc/snippets/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 0 additions & 17 deletions doc/snippets/MagnumGL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<GL::Mesh, GL::Buffer, GL::Buffer>;
#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) {}
Expand Down
4 changes: 2 additions & 2 deletions doc/snippets/MagnumPlatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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})}
{
// ...
}
Expand Down

0 comments on commit 2bd933d

Please sign in to comment.