diff --git a/doc/changelog.dox b/doc/changelog.dox index 55a855c53..a4a315ac1 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -51,6 +51,8 @@ See also: including mapping to @ref GL::PixelFormat / @ref GL::PixelType, @ref GL::TextureFormat and @ref Vk::PixelFormat and (partial) support in @ref DebugTools::CompareImage +- New @ref Degh, @ref Radh, @ref Range1Dh, @ref Range2Dh and @ref Range3Dh + typedefs for half-float angles and ranges @subsubsection changelog-latest-new-debugtools DebugTools library @@ -734,6 +736,10 @@ See also: reflected in the docs and thus hidden from users. Now it's shown in the docs as a set of @ref Debug etc typedefs instead, to make them more discoverable. +- Improved documentation about @ref opengl-wrapping-instances-nocreate "delayed OpenGL context creation" + and related pitfalls (see also [mosra/magnum-bootstrap#26](https://github.com/mosra/magnum-bootstrap/issues/26)) +- Added introductory documentation about @ref features "library layout and essential workflows" + (see [mosra/magnum#526](https://github.com/mosra/magnum/issues/526)) @section changelog-2020-06 2020.06 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/features.dox b/doc/features.dox index eb8eccef4..e9328c959 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -26,10 +26,146 @@ namespace Magnum { /** @page features Feature guide -@brief Fundamental principles and design goals. +@brief High-level introduction to design of the Magnum library and basic building blocks. -Overview and tutorials of high-level feature groups in Magnum. It's not -necessary to read through everything, pick only what you need. +@tableofcontents + +@todoc FUCK OFF doxygen, why are you not able to link to Corrade, Corrade::Utility and Magnum/Math/ you dumb shit?! + +Before you continue further, here are the essential bits of knowledge to help +you around. + +@section features-naming Library organization and naming scheme + +The @ref Magnum project consists of a library with core functionality and +optional sub-libraries. Each library is in its own namespace, which corresponds +to a sub-folder on the include path -- so e.g. things from @ref Magnum::Math +are included from @m_class{m-doc} [Magnum/Math/](dir_d816e7cf853e6723911731706bcab386.html). +@ref Magnum builds upon +@m_class{m-doc-external} [Corrade](https://doc.magnum.graphics/corrade/), which +provides platform abstraction, basic utilities and containers. + +To reduce redundant verbosity, references throughout the documentation and +code in example snippets have the the root @ref Magnum and +@m_class{m-doc-external} [Corrade](https://doc.magnum.graphics/corrade/namespaceCorrade.html) +namespaces omitted. In other words, as if the following was done: + +@snippet Magnum.cpp features-using-namespace + +If Magnum is the primary library you're building your project on, it's +recommended that you do the same --- we're careful to not pollute the root +namespace with overly generic names. However, except for `Literals` namespaces, +@cpp using @ce the subnamespaces is *not recommended*, as naming in those is +deliberately picked short and may introduce conflicts (such as +@relativeref{Corrade,Containers::String} vs +@relativeref{Corrade,Utility::String} or @ref GL::PixelFormat vs +@ref Vk::PixelFormat). + +If you frown upon the thought of @cpp using namespace Magnum @ce yet don't want +to wear your fingers off by repeatedly writing @cpp Magnum:: @ce everywhere, a +common shorthand that projects go with is the following --- similarly in spirit +to @cb{.py} import numpy as np @ce: + +@snippet Magnum.cpp features-using-namespace-alias + +@section features-includes Include files and forward declarations + +Depending on where you come from, the @cpp #include @ce policy used by Magnum +might either make you happy or freak you out. In short, there's no "include the +world" file, and instead you're supposed to include dedicated headers for APIs +you want to use. The main reason is compile times, and the speed gain from +doing it this way is too great to be ignored. + +The general rule is that each top-level class has a corresponding include, so +for example @ref Math::Matrix4 is included as @ref Magnum/Math/Matrix4.h. It's +not always like that though, and to help you out, documentation of each +namespace and class tells you what to include, and if particular namespace +members are defined in different headers, then detailed docs of each has a +corresponding @cpp #include @ce directive listed as well, ready to be copied. + +In order to have the includes actually independent of each other, most Magnum +types are forward-declared, and where possible, the header only relies on +forward declarations. Which means that often the type already exists as a +declaration, and in order to actually use it, you have to include the concrete +definition of it. If you don't, the compiler will complain about use of an +incomplete type. For example: + +@snippet Magnum.cpp features-forward-declaration-use + +Of course not all headers can be written with just forward declarations, so +there still are some transitive dependencies between headers (for example, the +@ref Magnum/Math/Matrix4.h header pulls in @ref Magnum/Math/Vector4.h as well). +But from most part this is an implementation detail and as such shouldn't be +relied on. + +For happy compile times you're encouraged to rely on forward declarations in +your code as well. See @ref compilation-forward-declarations for more +information. + +@section features-debug-output Debug output + +One of the essential debugging workflows is inspection of variable contents by +printing them out. Magnum defines a lot of new math types, enums and +containers and it would be very painful if you had to loop over their contents +or perform manual enum-to-string conversion every time you want to see what's +inside. + +Because writing to standard output and printing values for debugging purposes +are *distinct* use cases with potentially conflicting requirements (should an +enum value get written as a number? or as a name? a fully qualified name?), +Magnum *doesn't* provide @cpp operator<< @ce overloads for @ref std::ostream. + +Instead, there's @relativeref{Corrade,Utility::Debug}, for your typing +convenience also aliased to just @ref Debug directly in the @ref Magnum +namespace. On its own it's able to print builtin types, all usual C++ +containers as well as their Corrade equivalents. Magnum then implements debug +printers for its own math types, basic structures and most enums, which get +printed as fully-qualified names. For example: + +@m_class{m-code-figure} + +@parblock + +@snippet Magnum.cpp features-debug-output + + + +@m_class{m-nopad} + +@code{.shell-session} +Image format is PixelFormat::RGBA8Srgb and size Vector(256, 192) +Color of the bottom-left pixel is #33b27f +@endcode + +@endparblock + +The main goal of this utility is convenience and readability --- values are +implicitly delimited by spaces and ended with a newline, container contents +written with commas etc. Check out the class documentation for advanced +features like colors, output redirection or printing file/line info. + +@section features-examples Learn by example + +Before you do a deep dive into the documentation, and if you haven't done +already, it's recommended to go through the Getting Started Guide and check out +at least the first example: + +@m_class{m-row} + +@parblock + +@m_div{m-col-m-6} @m_div{m-button m-primary} @m_div{m-big}Getting Started@m_enddiv @m_div{m-small} bootstrap a basic project structure @m_enddiv @m_enddiv @m_enddiv + +@m_div{m-col-m-6} @m_div{m-button m-success} @m_div{m-big}Your First Triangle@m_enddiv @m_div{m-small} a step-by-step tutorial @m_enddiv @m_enddiv @m_enddiv + +@endparblock + +@section features-building-blocks Learn through documentation + +Each of the following pages provides a high-level description of a certain area +of the library. It's recommended to read through these first to understand the +overall principles and only then go to documentation of each concrete class and +function. - @subpage platform --- @copybrief platform - @subpage types --- @copybrief types diff --git a/doc/getting-started.dox b/doc/getting-started.dox index 544bc2eb5..6d4f10506 100644 --- a/doc/getting-started.dox +++ b/doc/getting-started.dox @@ -345,11 +345,19 @@ See @ref platform for more information. @section getting-started-tutorials Follow tutorials and learn the principles Now that you have your first application up and running, the best way to -continue is to render your first triangle. Then you can dig deeper and try -other examples, read about @ref features "fundamental principles" in the -documentation and start experimenting on your own! +continue is to get familiar with the basic workflows and render your first +triangle. Then you can dig deeper and try other examples, read about basic +building blocks in the documentation and start experimenting on your own! -@m_div{m-button m-success} @m_div{m-big}Your First Triangle@m_enddiv @m_div{m-small} a step-by-step tutorial @m_enddiv @m_enddiv +@m_class{m-row} + +@parblock + +@m_div{m-col-m-6} @m_div{m-button m-info} @m_div{m-big}The Fundamentals@m_enddiv @m_div{m-small} make yourself comfortable first @m_enddiv @m_enddiv @m_enddiv + +@m_div{m-col-m-6} @m_div{m-button m-success} @m_div{m-big}Your First Triangle@m_enddiv @m_div{m-small} a step-by-step tutorial @m_enddiv @m_enddiv @m_enddiv + +@endparblock @section getting-started-more Additional information 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/Magnum.cpp b/doc/snippets/Magnum.cpp index b76710945..67b66c8de 100644 --- a/doc/snippets/Magnum.cpp +++ b/doc/snippets/Magnum.cpp @@ -26,6 +26,7 @@ #include #include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Image.h" #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" @@ -37,6 +38,8 @@ #include "Magnum/GL/Texture.h" #endif +#define DOXYGEN_IGNORE(...) __VA_ARGS__ + using namespace Magnum; using namespace Magnum::Math::Literals; @@ -64,6 +67,42 @@ class MeshResourceLoader: public AbstractResourceLoader { int main() { +{ +/* [features-using-namespace] */ +using namespace Corrade; +using namespace Magnum; +/* [features-using-namespace] */ +} + +{ +/* [features-using-namespace-alias] */ +namespace Cr = Corrade; +namespace Mn = Magnum; +/* [features-using-namespace-alias] */ +} + +{ +/* The same #include is already above so this shouldn't hurt */ +/* [features-forward-declaration-use] */ +#include /* only a Matrix4 forward declaration */ +#include /* the actual definition */ + +DOXYGEN_IGNORE() + +Matrix4 a = Matrix4::translation({3.0f, 1.0f, 0.5f}); +/* [features-forward-declaration-use] */ +static_cast(a); +} + +{ +/* [features-debug-output] */ +Image2D image = DOXYGEN_IGNORE(Image2D{{}, {}, {}}); + +Debug{} << "Image format is" << image.format() << "and size" << image.size(); +Debug{} << "Color of the first pixel is" << image.pixels()[0][0]; +/* [features-debug-output] */ +} + { std::nullptr_t data{}; /* [Image-pixels] */ 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/doc/types.dox b/doc/types.dox index 6831fec4b..6b055bb46 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -31,15 +31,19 @@ namespace Magnum { @m_footernavigation @m_keyword{Type system,,} -The root @ref Magnum namespace defines a few aliases for essential math types. -See its documentation for more information about usage. +Magnum defines a variety of scalar, vector and matrix types. Most of the +functionality is implemented using template classes in the @ref Math library, +with the most common variants brought as typedefs into the root @ref Magnum +namespace. @section types-builtin Builtin types -Magnum provides typedefs for builtin integral and floating-point arithmetic -types to ensure portability (e.g. @ref Int is *always* 32bit), maintain -consistency and reduce confusion (e.g. @ref std::int32_t, @cpp int @ce and -@cpp GLint @ce all refer to the same type). +Magnum provides its own typedefs for builtin integral and floating-point +arithmetic types to ensure portability, maintain consistency and reduce +confusion. E.g., the @ref Int typedef is guaranteed to *always* be 32-bit and +Magnum's own code and documentation prefers to use it over a wild mixture of +@ref std::int32_t, @cpp int @ce, @cpp GLint @ce and @cpp ALint @ce that all +refer to the same type. | Magnum type | Size | Equivalent GLSL type | | ------------------ | -------------- | ----------------------- | @@ -56,8 +60,8 @@ consistency and reduce confusion (e.g. @ref std::int32_t, @cpp int @ce and | @ref Double | 64bit | @glsl double @ce | Types not meant to be used in arithmetic (such as @cpp bool @ce or -@cpp std::size_t @ce) or types which cannot be directly passed to GLSL shaders -(such as @cpp long double @ce) have no typedefs. +@cpp std::size_t @ce) or types which have no use in GPU computations (such as +@cpp long double @ce) have no typedefs. Types from the above table are then used to define other types. All following types are aliases of corresponding types in @ref Math namespace. No suffix @@ -121,11 +125,11 @@ about the differences. @section types-binary Binary representation -Scalar types with GLSL equivalent are verified to be exactly the same as -corresponding `GL*` types. Matrix and vector classes have the same binary -representation as corresponding array of numeric values without any additional -data or padding (e.g. @cpp sizeof(Vector3i) == sizeof(Int[3]) @ce), all -matrices are stored in column-major order. +Scalar types with a GLSL equivalent are guaranteed to have exactly the same +binary representation. Consequently, matrix and vector classes also have the +same binary representation as corresponding array of numeric values without any +additional data or padding (e.g. @cpp sizeof(Vector3i) == sizeof(Int[3]) @ce), +all matrices are stored in column-major order. This means that all scalar, matrix and vector types can be used directly for filling GPU buffers and textures without any need for data extraction or 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. diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 82fc79e15..12d7688cc 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -967,6 +967,36 @@ typedef Math::Matrix4x3 Matrix4x3h; */ typedef Math::Matrix4x4 Matrix4x4h; +/** +@brief Angle in half-float degrees +@m_since_latest +*/ +typedef Math::Deg Degh; + +/** +@brief Angle in half-float radians +@m_since_latest +*/ +typedef Math::Rad Radh; + +/** +@brief One-dimensional half-float range +@m_since_latest +*/ +typedef Math::Range1D Range1Dh; + +/** +@brief Two-dimensional half-float range +@m_since_latest +*/ +typedef Math::Range2D Range2Dh; + +/** +@brief Three-dimensional half-float range +@m_since_latest +*/ +typedef Math::Range3D Range3Dh; + /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ /** diff --git a/src/Magnum/Math/Angle.h b/src/Magnum/Math/Angle.h index 9edb499c7..13f4e70d8 100644 --- a/src/Magnum/Math/Angle.h +++ b/src/Magnum/Math/Angle.h @@ -94,7 +94,7 @@ These silent errors are easily avoided by requiring explicit conversions: @snippet MagnumMath.cpp Deg-usage-explicit-conversion -@see @ref Magnum::Deg, @ref Magnum::Degd +@see @ref Magnum::Deg, @ref Magnum::Degh, @ref Magnum::Degd */ template class Deg: public Unit { public: @@ -166,7 +166,7 @@ constexpr Deg operator "" _degf(long double value) { return Deg(Fl @brief Angle in radians See @ref Deg for more information. -@see @ref Magnum::Rad, @ref Magnum::Radd +@see @ref Magnum::Rad, @ref Magnum::Radh, @ref Magnum::Radd */ template class Rad: public Unit { public: diff --git a/src/Magnum/Math/CubicHermite.h b/src/Magnum/Math/CubicHermite.h index bb7106d07..9e74148f6 100644 --- a/src/Magnum/Math/CubicHermite.h +++ b/src/Magnum/Math/CubicHermite.h @@ -137,7 +137,7 @@ template class CubicHermite { constexpr /*implicit*/ CubicHermite(const T& inTangent, const T& point, const T& outTangent) noexcept: _inTangent{inTangent}, _point{point}, _outTangent{outTangent} {} /** - * @brief Construct subic Hermite spline point from another of different type + * @brief Construct cubic Hermite spline point from another of different type * * Performs only default casting on the values, no rounding or * anything else. diff --git a/src/Magnum/Math/Range.h b/src/Magnum/Math/Range.h index 10e2333e6..91c7d21b9 100644 --- a/src/Magnum/Math/Range.h +++ b/src/Magnum/Math/Range.h @@ -348,8 +348,8 @@ template class Range { Convenience alternative to @cpp Range<1, T> @ce. See @ref Range for more information. -@see @ref Range2D, @ref Range3D, @ref Magnum::Range1D, @ref Magnum::Range1Di, - @ref Magnum::Range1Dd +@see @ref Range2D, @ref Range3D, @ref Magnum::Range1D, @ref Magnum::Range1Dh, + @ref Magnum::Range1Dd, @ref Magnum::Range1Di */ #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ template using Range1D = Range<1, T>; @@ -359,8 +359,8 @@ template using Range1D = Range<1, T>; @brief Two-dimensional range See @ref Range for more information. -@see @ref Range1D, @ref Range3D, @ref Magnum::Range2D, @ref Magnum::Range2Di, - @ref Magnum::Range2Dd +@see @ref Range1D, @ref Range3D, @ref Magnum::Range2D, @ref Magnum::Range2Dh, + @ref Magnum::Range2Dd, @ref Magnum::Range2Di */ template class Range2D: public Range<2, T> { public: @@ -513,8 +513,8 @@ template class Range2D: public Range<2, T> { @brief Three-dimensional range See @ref Range for more information. -@see @ref Range1D, @ref Range2D, @ref Magnum::Range3D, @ref Magnum::Range3Di, - @ref Magnum::Range3Dd +@see @ref Range1D, @ref Range2D, @ref Magnum::Range3D, @ref Magnum::Range3Dh, + @ref Magnum::Range3Dd, @ref Magnum::Range3Di */ template class Range3D: public Range<3, T> { public: diff --git a/src/Magnum/MeshTools/sceneconverter.cpp b/src/Magnum/MeshTools/sceneconverter.cpp index e7c814b0c..7a04a5acd 100644 --- a/src/Magnum/MeshTools/sceneconverter.cpp +++ b/src/Magnum/MeshTools/sceneconverter.cpp @@ -45,6 +45,7 @@ #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshObjectData3D.h" +#include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Trade/Implementation/converterUtilities.h" @@ -289,6 +290,13 @@ used.)") std::string name; }; + struct SkinInfo { + UnsignedInt skin; + UnsignedInt references; + Trade::SkinData3D data{{}, {}}; + std::string name; + }; + struct LightInfo { UnsignedInt light; UnsignedInt references; @@ -338,6 +346,7 @@ used.)") Containers::Array materialReferenceCount{importer->materialCount()}; Containers::Array lightReferenceCount{importer->lightCount()}; Containers::Array meshReferenceCount{importer->meshCount()}; + Containers::Array skinReferenceCount{importer->skin3DCount()}; for(UnsignedInt i = 0; i != importer->object3DCount(); ++i) { Containers::Pointer object = importer->object3D(i); if(!object) continue; @@ -347,6 +356,8 @@ used.)") ++meshReferenceCount[meshObject.instance()]; if(std::size_t(meshObject.material()) < materialReferenceCount.size()) ++materialReferenceCount[meshObject.material()]; + if(std::size_t(meshObject.skin()) < skinReferenceCount.size()) + ++skinReferenceCount[meshObject.skin()]; } else if(object->instanceType() == Trade::ObjectInstanceType3D::Light) { if(std::size_t(object->instance()) < lightReferenceCount.size()) ++lightReferenceCount[object->instance()]; @@ -374,6 +385,27 @@ used.)") arrayAppend(animationInfos, std::move(info)); } + /* Skin properties */ + Containers::Array skinInfos; + for(UnsignedInt i = 0; i != importer->skin3DCount(); ++i) { + Containers::Optional skin; + { + Duration d{importTime}; + if(!(skin = importer->skin3D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.skin = i; + info.name = importer->skin3DName(i); + info.references = skinReferenceCount[i]; + info.data = *std::move(skin); + + arrayAppend(skinInfos, std::move(info)); + } + /* Light properties */ Containers::Array lightInfos; for(UnsignedInt i = 0; i != importer->lightCount(); ++i) { @@ -569,6 +601,19 @@ used.)") not so much for things like complex numbers or quats */ } } + for(const SkinInfo& info: skinInfos) { + Debug d; + d << "Skin" << info.skin; + /* Print reference count only if there actually is a scene, + otherwise this information is useless */ + if(importer->object3DCount()) + d << Utility::formatString("(referenced by {} objects)", info.references); + d << Debug::nospace << ":"; + if(!info.name.empty()) d << info.name; + + d << Debug::newline << " Joints:" << info.data.joints(); + } + for(const LightInfo& info: lightInfos) { Debug d; d << "Light" << info.light; diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 080a74988..e4df296c6 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -202,10 +202,10 @@ MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, {}, {}, vertexCount, importerState} {} -MeshData::~MeshData() = default; - MeshData::MeshData(MeshData&&) noexcept = default; +MeshData::~MeshData() = default; + MeshData& MeshData::operator=(MeshData&&) noexcept = default; Containers::ArrayView MeshData::mutableIndexData() & { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 6b84034ae..0a2912549 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -150,7 +150,7 @@ enum class MeshAttribute: UnsignedShort { /** * This and all higher values are for importer-specific attributes. Can be * of any type. See documentation of a particular importer for details. - * @see @ref isMeshAttributeCustom(MeshAttribute) + * @see @ref isMeshAttributeCustom(), * @ref meshAttributeCustom(MeshAttribute), * @ref meshAttributeCustom(UnsignedShort) */ @@ -294,22 +294,23 @@ or @ref MeshTools::interleave(const Trade::MeshData& data, Containers::ArrayView @section Trade-MeshAttributeData-usage Usage -The most straightforward usage is constructing an instance from a pair of +The most straightforward usage is constructing an instance from a pair of a @ref MeshAttribute and a strided view. The @ref VertexFormat gets inferred from the view type: @snippet MagnumTrade.cpp MeshAttributeData-usage -Alternatively, you can pass a typeless @cpp const void @ce view and supply -@ref VertexFormat explicitly, or a 2D view. +Alternatively, you can pass a typeless @cpp const void @ce or a 2D view and +supply @ref VertexFormat explicitly. @subsection Trade-MeshAttributeData-usage-offset-only Offset-only attribute data If the actual attribute data location is not known yet, the instance can be created as "offset-only", meaning the actual view gets created only later when passed to a @ref MeshData instance with a concrete vertex data array. This is -useful for example when vertex layout is static (and thus can be defined at -compile time), but the actual data is allocated / populated at runtime: +useful mainly to avoid pointer patching during data serialization, but also for +example when vertex layout is static (and thus can be defined at compile time), +but the actual data is allocated / populated at runtime: @snippet MagnumTrade.cpp MeshAttributeData-usage-offset-only @@ -350,8 +351,8 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * attributes. * * Expects that @p data stride is large enough to fit all @p arraySize - * items of @p type, @p type corresponds to @p name and @p arraySize is - * zero for builtin attributes. + * items of @p format, @p format corresponds to @p name and + * @p arraySize is zero for builtin attributes. */ explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize = 0) noexcept; @@ -364,8 +365,8 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * attributes. * * Expects that the second dimension of @p data is contiguous and its - * size matches @p type and @p arraSize, that @p type corresponds to - * @p name and @p arraySize is zero for builtin attributes. + * size matches @p format and @p arraySize, that @p format corresponds + * to @p name and @p arraySize is zero for builtin attributes. */ explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data, UnsignedShort arraySize = 0) noexcept; @@ -515,8 +516,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * @brief Type-erased attribute data * * Expects that the attribute is not offset-only, in that case use the - * @ref data(Containers::ArrayView) const overload - * instead. + * @ref data(Containers::ArrayView) const overload instead. * @see @ref isOffsetOnly() */ constexpr Containers::StridedArrayView1D data() const { @@ -524,7 +524,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ {_data.pointer, ~std::size_t{}}, _vertexCount, - (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array"), _stride)}; + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is offset-only, supply a data array"), _stride)}; } /** @@ -544,7 +544,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { private: friend MeshData; - /* nullptr first, to avoid accidental matches as much as possible */ + /* Delegated to by all ArrayView constructors, which additionally check + either stride or second dimension size. Nullptr first, to avoid + accidental matches as much as possible. */ constexpr explicit MeshAttributeData(std::nullptr_t, MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize) noexcept; VertexFormat _format; @@ -644,10 +646,15 @@ cases when it's desirable to modify the data in-place, there's the @ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices() and @ref mutableAttribute() set of functions. To use these, you need to check that the data are mutable using @ref indexDataFlags() or @ref vertexDataFlags() -first. The following snippet applies a transformation to the mesh data: +first, and if not then you may want to make a mutable copy first using +@ref MeshTools::owned(). The following snippet applies a transformation to the +mesh positions: @snippet MagnumTrade.cpp MeshData-usage-mutable +If the transformation includes a rotation or non-uniform scaling, you may want +to do a similar operation with normals and tangents as well. + @section Trade-MeshData-populating Populating an instance A @ref MeshData instance by default takes over the ownership of an @@ -938,14 +945,14 @@ class MAGNUM_TRADE_EXPORT MeshData { */ explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; - ~MeshData(); - /** @brief Copying is not allowed */ MeshData(const MeshData&) = delete; /** @brief Move constructor */ MeshData(MeshData&&) noexcept; + ~MeshData(); + /** @brief Copying is not allowed */ MeshData& operator=(const MeshData&) = delete; @@ -1301,11 +1308,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than @ref attributeCount() const. * The second dimension represents the actual data type (its size is - * equal to format size for known @ref VertexFormat values and to - * attribute stride for implementation-specific values) and is - * guaranteed to be contiguous. Use the templated overload below to get - * the attribute in a concrete type. + * equal to format size for known @ref VertexFormat values, possibly + * multiplied by array size, and to attribute stride for + * implementation-specific values) and is guaranteed to be contiguous. + * Use the templated overload below to get the attribute in a concrete + * type. * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref vertexFormatSize(), * @ref isVertexFormatImplementationSpecific() */ Containers::StridedArrayView2D attribute(UnsignedInt id) const; @@ -1338,7 +1347,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * to usual types, but note that these operations involve extra * allocation and data conversion. * @see @ref attribute(MeshAttribute, UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref mutableAttribute(UnsignedInt), * @ref isVertexFormatImplementationSpecific(), * @ref attributeArraySize() */ @@ -1409,13 +1418,15 @@ class MAGNUM_TRADE_EXPORT MeshData { * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. * Expects that the vertex format is *not* implementation-specific, in * that case you can only access the attribute via the typeless - * @ref attribute(MeshAttribute, UnsignedInt) const above. You can also - * use the non-templated @ref positions2DAsArray(), - * @ref positions3DAsArray(), @ref normalsAsArray(), - * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() - * accessors to get common attributes converted to usual types, but - * note that these operations involve extra data conversion and an - * allocation. + * @ref attribute(MeshAttribute, UnsignedInt) const above. The + * attribute is also expected to not be an array, in that case you need + * to use the overload below by using @cpp T[] @ce instead of + * @cpp T @ce. You can also use the non-templated + * @ref positions2DAsArray(), @ref positions3DAsArray(), + * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and + * @ref colorsAsArray() accessors to get common attributes converted to + * usual types, but note that these operations involve extra data + * conversion and an allocation. * @see @ref attribute(UnsignedInt) const, * @ref mutableAttribute(MeshAttribute, UnsignedInt), * @ref isVertexFormatImplementationSpecific() @@ -1808,7 +1819,7 @@ namespace Implementation { /* Implicit mapping from a format to enum (1:1) */ template constexpr VertexFormat vertexFormatFor() { /* C++ why there isn't an obvious way to do such a thing?! */ - static_assert(sizeof(T) == 0, "unsupported attribute type"); + static_assert(sizeof(T) == 0, "unsupported vertex format"); return {}; } #ifndef DOXYGEN_GENERATING_OUTPUT @@ -2093,7 +2104,14 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{nullptr, name, Implementation::vertexFormatFor::type>(), data, 0} {} -template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{(CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr), name, Implementation::vertexFormatFor::type>(), Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, UnsignedShort(data.size()[1])} {} +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{ + /* Not using isContiguous<1>() as that's not constexpr */ + (CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr), + name, + Implementation::vertexFormatFor::type>(), + Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, + UnsignedShort(data.size()[1]) +} {} template Containers::ArrayView MeshData::indices() const { CORRADE_ASSERT(isIndexed(), diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index ce9bbfa0f..1169f45c7 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -576,8 +576,11 @@ void MeshDataTest::constructAttribute() { CORRADE_COMPARE(positions.offset(positionData), 0); CORRADE_COMPARE(positions.stride(), sizeof(Vector2)); CORRADE_VERIFY(positions.data().data() == positionData); - /* This is allowed too for simplicity, it just ignores the parameter */ - CORRADE_VERIFY(positions.data(positionData).data() == positionData); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[3*sizeof(Vector2)]; + CORRADE_VERIFY(positions.data(someArray).data() == positionData); constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); @@ -605,6 +608,9 @@ void MeshDataTest::constructAttributeDefault() { } void MeshDataTest::constructAttributeCustom() { + /* Verifying it doesn't hit any assertion about disallowed type for given + attribute */ + const Short idData[3]{}; MeshAttributeData ids{meshAttributeCustom(13), Containers::arrayView(idData)}; CORRADE_COMPARE(ids.name(), meshAttributeCustom(13)); @@ -789,11 +795,13 @@ void MeshDataTest::constructAttributeWrongDataAccess() { CORRADE_VERIFY(!a.isOffsetOnly()); CORRADE_VERIFY(b.isOffsetOnly()); + a.data(positionData); /* This is fine, no asserts */ + std::ostringstream out; Error redirectError{&out}; b.data(); CORRADE_COMPARE(out.str(), - "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); + "Trade::MeshAttributeData::data(): the attribute is offset-only, supply a data array\n"); } constexpr Vector2 ArrayVertexData[3*4] @@ -886,7 +894,7 @@ void MeshDataTest::constructArrayAttribute2DNonContiguous() { void MeshDataTest::constructArrayAttributeTypeErased() { Vector2 vertexData[3*4]; Containers::StridedArrayView1D attribute{vertexData, 3, 4*sizeof(Vector2)}; - MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, attribute, 4}; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, Containers::arrayCast(attribute), 4}; CORRADE_VERIFY(!data.isOffsetOnly()); CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); CORRADE_COMPARE(data.format(), VertexFormat::Vector2);