diff --git a/CMakeLists.txt b/CMakeLists.txt index eb365ee5f..7cdef0fd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,11 +30,19 @@ if(HUNTER_ENABLED) include(${CMAKE_CURRENT_LIST_DIR}/package/hunter/HunterInit.cmake) endif() +# Don't restrict INTERPROCEDURAL_OPTIMIZATION only for icc on Linux +if(POLICY CMP0069) + cmake_policy(SET CMP0069 NEW) +endif() # If CMAKE_AUTOMOC is set, all uses of corrade_add_resource() would otherwise # complain on 3.10 that AUTOMOC is not processing GENERATED files if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() +# Superprojects can use just set(WITH_BLAH ON) without FORCE CACHE on 3.13+ +if(POLICY CMP0077) + cmake_policy(SET CMP0077 NEW) +endif() project(Magnum CXX) @@ -394,7 +402,7 @@ set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBU set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/audioimporters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/audioimporters) -# Plugin dirs based on wheter we are in debug or release build, needed by some +# Plugin dirs based on whether we are in debug or release build, needed by some # command-line tools if(CORRADE_TARGET_WINDOWS) set(MAGNUM_PLUGINS_DEBUG_DIR_INIT ${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}) diff --git a/COPYING b/COPYING index 4af80acfd..a5e6fdb35 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020 Vladimír Vondruš and contributors + 2020, 2021 Vladimír Vondruš and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/doc/building.dox b/doc/building.dox index a2baba56e..13f942db0 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -214,7 +214,7 @@ There are also ArchLinux packages for @ref building-plugins-packages-arch "Magnu @subsection building-packages-msys MSYS2 packages [MSYS2](https://www.msys2.org/) package for the latest stable release is -mantained in the official repos. Installing is as simple as this: +maintained in the official repos. Installing is as simple as this: @code{.sh} pacman -S mingw-w64-x86_64-magnum # or mingw-w64-i686-magnum diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 1c549b7bd..819039aef 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -1470,7 +1470,7 @@ for a high-level overview. - Support for @gl_extension{ARB,vertex_type_10f_11f_11f_rev} in @cpp AbstractShaderProgram::Attribute::DataType @ce - New variants of @cpp Shader::compile() @ce and @cpp AbstractShaderProgram::link() @ce, - allowing the driver to perform paralell compilation in multiple threads. + allowing the driver to perform parallel compilation in multiple threads. - Added @cpp *Texture::maxSize() @ce queries - @ref MeshTools::compile() for automagic creation of 2D and 3D meshes from imported data diff --git a/doc/changelog.dox b/doc/changelog.dox index 0ce99357e..55a855c53 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -147,6 +147,12 @@ See also: @subsubsection changelog-latest-new-shaders Shaders library +- All builtin shaders now have opt-in support for uniform buffers on desktop, + OpenGL ES 3.0+ and WebGL 2.0, including multi-draw functionality for + massive driver overhead reduction. The @ref shaders overview page was + updated with an introduction the new features. +- @ref Shaders::FlatGL and @ref Shaders::PhongGL now support texture arrays, + available also in multi-draw and instanced scenarios - Added @ref Shaders::PhongGL::setNormalTextureScale(), consuming the recently added @ref Trade::MaterialAttribute::NormalTextureScale material attribute @@ -155,6 +161,9 @@ See also: @ref Trade::LightData - Added @ref Shaders::PhongGL::setLightSpecularColors() for better control over specular highlights +- Added @ref Shaders::PhongGL::Flag::NoSpecular as a significantly faster + alternative to setting specular color to @cpp 0x00000000_rgbaf @ce in case + specular highlights are not desired @subsubsection changelog-latest-new-shadertools ShaderTools library @@ -223,6 +232,15 @@ See also: @subsubsection changelog-latest-changes-gl GL library +- @ref GL::AbstractShaderProgram::draw(), + @relativeref{GL::AbstractShaderProgram,drawTransformFeedback()} and + @relativeref{GL::AbstractShaderProgram,dispatchCompute()} APIs now return + a reference to self and all subclasses in @ref Shaders return a subclass + reference from these. The functions used to @cpp void @ce as that made more + sense in the classic workflow where a large set of uniforms had to be set + prior to every draw, however with the new multidraw workflows that's no + longer the case and the inability to chain draw calls proved to be + annoying. - The @ref GL::Context class got significantly optimized in terms of compile time, header size and runtime as well, significantly reducing the amount of allocations done at startup. @@ -317,7 +335,24 @@ See also: @subsubsection changelog-latest-changes-trade Trade library -- Recognizing TIFF file header magic in @ref Trade::AnyImageImporter "AnyImageImporter" +- @ref Trade::AbstractImageConverter::doConvertToFile() and + @ref Trade::AbstractSceneConverter::doConvertToFile() are now + @cpp protected @ce instead of @cpp private @ce to allow calling them from + plugin implementations and reuse the provided fallback to + @relativeref{Trade::AbstractImageConverter,doConvertToData()}, for example + when the implementation only neeeds to do a format detection based on file + extension +- Recognizing BMP and TIFF file header magic in @relativeref{Trade,AnyImageImporter} +- @ref Audio::AnyImporter "AnyAudioImporter", + @relativeref{Trade,AnyImageImporter}, @relativeref{Trade,AnyImageConverter}, + @relativeref{Trade,AnySceneImporter}, @relativeref{Trade,AnySceneConverter} + and @ref ShaderTools::AnyConverter "AnyShaderConverter" are now capable of + propagating configuration options to the concrete plugin, which is useful + mainly when using @ref magnum-imageconverter and other utilities as you can + now specify just the `-i` / `-c` options without having to specify which + plugin to apply the option to with `-I` / `-C`. For better usability, the + plugins also warn if the user specifies and option that is not present in + the target implementation. - Added @ref Trade::PhongMaterialData::hasCommonTextureTransformation(), @ref Trade::PhongMaterialData::ambientTextureMatrix(), @ref Trade::PhongMaterialData::diffuseTextureMatrix(), @@ -401,6 +436,9 @@ See also: - For meshes with multiple sets of vertex attributes (such as texture coordinates), @ref MeshTools::compile() should be using only the first set but it wasn't. +- @ref Shaders::MeshVisualizerGL3D "Shaders::MeshVisualizerGL*D" shader + compilation failed with missing @glsl gl_PrimitiveID @ce due to GLSL 3.20 + not being properly used for the @glsl #version @ce directive - @ref Shaders::PhongGL was normalizing light direction in vertex shader, causing the fragment-interpolated direction being incorrect with visible artifacts on long polygons under low light angle @@ -444,7 +482,10 @@ See also: It was also commonly confused with @relativeref{Corrade,Containers::Array}, which is a type with totally different semantics. To avoid breaking existing code, conversion from and to @ref Math::Vector is now provided and - this type is included in all places where it was originally used. + this type is included in all places where it was originally used. For + custom uses, the @relativeref{Corrade,Containers::Array1}, @relativeref{Corrade,Containers::Array2} or + @relativeref{Corrade,Containers::Array3} types provide a suitable + alternative as well. - Markup styling for Emscripten application was switched to prefer using CSS classes instead of the @cb{.css} #container @ce, @cb{.css} #sizer @ce, @cb{.css} #expander @ce, @cb{.css} #listener @ce, @cb{.css} #canvas @ce, @@ -459,19 +500,17 @@ See also: @ref DebugTools::FrameProfilerGL. The new name plays better with IDE autocompletion and makes the GL-specific class appear next to the API-independent base in alphabetically sorted lists. -- @cpp Shaders::AbstractVector @ce, @cpp Shaders::DistanceFieldVector @ce, - @cpp Shaders::Flat @ce, @cpp Shaders::Generic @ce, - @cpp Shaders::MeshVisualizer2D @ce, @cpp Shaders::MeshVisualizer3D @ce, - @cpp Shaders::Phong @ce, @cpp Shaders::Vector @ce, - @cpp Shaders::VertexColor @ce and related 2D/3D typedefs are deprecated in - favor of @ref Shaders::AbstractVectorGL, - @ref Shaders::DistanceFieldVectorGL, @ref Shaders::FlatGL, - @ref Shaders::GenericGL, @ref Shaders::MeshVisualizerGL2D, - @ref Shaders::MeshVisualizerGL3D, @ref Shaders::PhongGL, - @ref Shaders::VectorGL, @ref Shaders::VertexColorGL and correspondingly - renamed typedefs to make room for Vulkan shaders and functionality shared - between OpenGL and Vulkan implementation such as uniform buffer layout - definitions +- @cpp Shaders::DistanceFieldVector @ce, @cpp Shaders::Flat @ce, + @cpp Shaders::Generic @ce, @cpp Shaders::MeshVisualizer2D @ce, + @cpp Shaders::MeshVisualizer3D @ce, @cpp Shaders::Phong @ce, + @cpp Shaders::Vector @ce, @cpp Shaders::VertexColor @ce and related 2D/3D + typedefs are deprecated in favor of @ref Shaders::DistanceFieldVectorGL, + @ref Shaders::FlatGL, @ref Shaders::GenericGL, + @ref Shaders::MeshVisualizerGL2D, @ref Shaders::MeshVisualizerGL3D, + @ref Shaders::PhongGL, @ref Shaders::VectorGL, @ref Shaders::VertexColorGL + and correspondingly renamed typedefs to make room for Vulkan shaders and + functionality shared between OpenGL and Vulkan implementation such as + uniform buffer layout definitions - @ref Shaders::PhongGL::setLightPositions() and @ref Shaders::PhongGL::setLightPosition() taking three-component vectors are deprecated in favor of variants taking four-component vectors, where the @@ -655,6 +694,10 @@ See also: isn't available anymore. For backwards compatibility, light positions supplied through three-component vectors are now represented as directional lights, which is close, but not exactly the same as before. +- The @cpp Shaders::AbstractVector @ce base class for @ref Shaders::VectorGL + and @ref Shaders::DistanceFieldVectorGL is removed, as its benefits were + rather questionable --- on the contrary, it made subclass implementation + more verbose and less clear - Mutable access to @ref Trade::PhongMaterialData color and texture information, deprecated in 2020.06, is now removed, as it's impossible to implement through the redesigned @ref Trade::MaterialData APIs. However @@ -684,8 +727,9 @@ See also: - Added a note about MinGW GCC and Clang ABI incompatibility to @ref platforms-windows "Windows platform docs" (see [mosra/magnum#227](https://github.com/mosra/magnum/issues/227) and [mosra/magnum#439](https://github.com/mosra/magnum/issues/439)) -- Various documentation fixes (see [mosra/magnum#492](https://github.com/mosra/magnum/issues/492)) -- @ref Corrade::Utility::Debug and friends were always brought to the ) +- Various documentation fixes (see [mosra/magnum#492](https://github.com/mosra/magnum/issues/492), + [mosra/magnum#521](https://github.com/mosra/magnum/pull/521)) +- @ref Corrade::Utility::Debug and friends were always brought to the @ref Magnum namespace via a @cpp using @ce directive, but this was never 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 @@ -770,7 +814,7 @@ Released 2020-06-27, tagged as @cpp "intel-windows-crazy-broken-vao-dsa" @ce workarounds for Intel Windows drivers, disabling @gl_extension{ARB,direct_state_access} code paths in everything releated to buffers and meshes. There are several - issues occuring only in heavier apps, impossible to track down and + issues occurring only in heavier apps, impossible to track down and reproduce in a controlled environment. These two replace the previous @cpp "intel-windows-buggy-dsa-bufferdata-for-index-buffers" @ce workaround that attempted to fix this by doing an explicit buffer binding in some @@ -1085,7 +1129,7 @@ Released 2020-06-27, tagged as [mosra/magnum#401](https://github.com/mosra/magnum/pull/401)) - HiDPI capability checking on macOS and iOS got fixed to behave correctly when the capability is explicitly set to @cb{.xml} @ce in the - `Info.plist` file and additionally, unless overriden, it's assumed to be + `Info.plist` file and additionally, unless overridden, it's assumed to be enabled by default on macOS 10.15+ and iOS 13+. Applications running on these platforms no longer need to supply a custom `Info.plist` in order to enable HiDPI. See @ref platforms-macos-hidpi for more information. @@ -1402,7 +1446,7 @@ Released 2020-06-27, tagged as was deprecated in 2019.10 is now removed. Usually a deprecated feature is kept for at least a year before removal, but in this case it was severely limiting multithreaded applications and removing it was necessary. -- Locations of generic shader attributes was changed in order to accomodate +- Locations of generic shader attributes was changed in order to accommodate for new attributes and use cases. This may break custom shaders if these rely on generic attribute definitions or are used together with @ref MeshTools::compile(). To avoid further breakages you're advised to @@ -1522,7 +1566,7 @@ Released 2019-10-24, tagged as - @ref DebugTools::CompareImage and @ref DebugTools::CompareImageToFile now accept also @ref Corrade::Containers::StridedArrayView2D on the left side of the comparison for added flexibility. See - @ref DebugTools-CompareImage-pixels for more infromation. + @ref DebugTools-CompareImage-pixels for more information. @subsubsection changelog-2019-10-new-gl GL library diff --git a/doc/coding-style.dox b/doc/coding-style.dox index a64610807..157f71b1b 100644 --- a/doc/coding-style.dox +++ b/doc/coding-style.dox @@ -70,7 +70,7 @@ removing redundant prefixes) is encouraged. When a namespace has classes which are commonly forward-declared, consider making a forward declaration header --- it should have the same name as the -namespace itself and contain foward declarations for all classes, enums and +namespace itself and contain forward declarations for all classes, enums and copies of all meaningful typedefs. See @ref compilation-forward-declarations for more information. @@ -312,7 +312,7 @@ CORRADE_COMPARE(b, Vector2(1, 0)); CORRADE_VERIFY(!(std::is_convertible::value)); @endcode -If some type should be constructible also from base type (additionaly to copy +If some type should be constructible also from base type (additionally to copy constructor), don't forget to test that too. The test is also usually needed only for low-level frequently used types (vectors, matrices) where such error would do largest harm. Depending on how copy constructor is implemented, you diff --git a/doc/credits.dox b/doc/credits.dox index 9bf8b54ba..2001cd2fc 100644 --- a/doc/credits.dox +++ b/doc/credits.dox @@ -39,7 +39,7 @@ overview the dependencies are color-coded: no license requirements, such as usage of public APIs like Vulkan or OpenAL or usage of platform-specific interfaces. - A @m_class{m-label m-success} **green** label marks licenses that make the - dependency safe to use in a commerical setting without having to release + dependency safe to use in a commercial setting without having to release your source code, usually requiring you to give attribution. - A @m_class{m-label m-primary} **light blue** label marks public domain software. In some countries there's no notion of public domain, in which @@ -81,6 +81,8 @@ Big thanks to everyone involved! Are the below lists missing your name or something's wrong? [Let us know!](https://magnum.graphics/contact/) +- **Aaron Gokaslan** ([\@Skylion007](https://github.com/Skylion007)) --- + various minor code modernization, typo fixes - **[\@abgita](https://github.com/abgita)** --- minor typo fixes - **Alan Jefferson** ([\@alanjfs](https://github.com/alanjfs)) --- extensive usability and first-time-use feedback @@ -210,7 +212,7 @@ Are the below lists missing your name or something's wrong? - **Thibault Jochem** ([\@Tryum](https://github.com/Tryum)) --- @ref Platform::GlfwApplication improvements - **Thomas Tissot-Dupont** ([\@dolphineye](https://github.com/dolphineye)) - --- OpenGL ES compatiblity improvements + --- OpenGL ES compatibility improvements - **Travis Watkins** ([\@amaranth](https://github.com/amaranth)) --- support for windowless applications under macOS */ diff --git a/doc/custom-buildsystems.dox b/doc/custom-buildsystems.dox index fba069895..0d907e2ec 100644 --- a/doc/custom-buildsystems.dox +++ b/doc/custom-buildsystems.dox @@ -84,7 +84,7 @@ more information. While dynamic plugins work without buildsystem integration, static plugins are handled automagically with CMake and you need to replicate the magic manually -when using a custom buildsystem. This is just about compiling an additonal +when using a custom buildsystem. This is just about compiling an additional `*.cpp` file together with your final app, see @ref plugins-static for more information. diff --git a/doc/debug-tools.dox b/doc/debug-tools.dox index c1b2f0b2a..4481059f1 100644 --- a/doc/debug-tools.dox +++ b/doc/debug-tools.dox @@ -46,7 +46,7 @@ of them to any object. Basic usage involves instancing @ref DebugTools::ResourceManager and keeping it for the whole lifetime of debug renderers. Next you need some @ref SceneGraph::DrawableGroup instance. You can use the same group as for the -rest of your scene, but preferrably use dedicated one for debug renderers, so +rest of your scene, but preferably use dedicated one for debug renderers, so you can easily enable or disable debug rendering. Next step is to create configuration for your debug renderers and create diff --git a/doc/developers.dox b/doc/developers.dox index 3c8ccd731..5f0af2f54 100644 --- a/doc/developers.dox +++ b/doc/developers.dox @@ -634,7 +634,7 @@ unless it doesn't affect public API at all. capabilities on the same platform (DSA / non-DSA is one of them), add separate code paths: - new private and `MAGNUM_LOCAL` `thingImplementation*()` functions, each - implementing one code path, preferrably @cpp static @ce + implementing one code path, preferably @cpp static @ce - a `thingImplementation` (member) function pointer in `src/Magnum/Implementation/SomeState.h` that gets populated in `src/Magnum/GL/Implementation/SomeState.cpp` based on extension / version availability @@ -683,7 +683,7 @@ unless it doesn't affect public API at all. `KnownWorkarounds` array in `src/Magnum/GL/Implementation/driverSpecific.cpp`, ideally reuse some of the already existing vendor prefixes 2. If the workaround can be tied down to a particular platform / target, add - apropriate @cpp #ifdef @ce around it + appropriate @cpp #ifdef @ce around it 3. Create (or extend) a pair of (member) @cpp private @ce `MAGNUM_LOCAL` `*Implementation*()` functions in the affected class, `*ImplementationDefault()` having the optimistic default behavior, the other having the workaround. @@ -706,7 +706,7 @@ unless it doesn't affect public API at all. @ref opengl-workarounds Removeing a workaround is simply a matter of searching for its string, removing -all occurences of that string and removing all `*Implementation*()` functions +all occurrences of that string and removing all `*Implementation*()` functions that were used only if the workaround was in place. No need to deprecate anything, users explicitly disabling given workaround will only be informed that such workaround does not exist anymore. @@ -992,7 +992,7 @@ inverse. replace `... —` with `..., 20XZ —` there as well 4. Copy all `Find*.cmake` modules to dependent projects to update the copyright year in these as well -5. Update other occurences by hand: +5. Update other occurrences by hand: - `package/debian/copyright` - `doc/conf.py` - All flextGL `*.template` files in `src/external/OpenGL` and @@ -1061,7 +1061,7 @@ inverse. - add a temporary \@anchor changelog-latest (and equivalent in other repos) on top so the links from main page work properly -8. Convert all occurences of +8. Convert all occurrences of - \@m_since_latest_{thing} to \@m_since_{thing,20XY,ab} - \@m_since_latest to \@m_since{20XY,ab} - \@m_deprecated_since_latest_{thing} to @@ -1103,7 +1103,7 @@ inverse. new functionality - add release annoucement link under the button on front page 21. Publish the release announcement, verify it looks correct -22. Advertise the release announcement, preferrably Monday 5 PM, never Friday +22. Advertise the release announcement, preferably Monday 5 PM, never Friday or weekends - come up with some 100-character-long extended title - Twitter (extended title + url and some hashtags), first dry-run the diff --git a/doc/file-formats.dox b/doc/file-formats.dox index 40980c65b..4548a40c9 100644 --- a/doc/file-formats.dox +++ b/doc/file-formats.dox @@ -28,16 +28,17 @@ namespace Magnum { @brief Support tables for widely used image, scene, audio and font formats @tableofcontents - -The @ref Trade::AnyImageImporter "AnyImageImporter", -@ref Trade::AnySceneImporter "AnySceneImporter", -@ref Trade::AnyImageConverter "AnyImageConverter", -@ref Trade::AnySceneConverter "AnySceneConverter" and other `Any*` plugins can -be used for generic handling of any of the recognized formats, they'll proxy -the operation to a concrete plugin implementation. The following tables list -the most widely used formats with alternative plugin implementations and known -caveats for each. When one format is supported by more than one plugin, you -can use @ref Corrade::PluginManager::AbstractManager::setPreferredPlugins() to +@m_footernavigation + +The @ref Audio::AnyImporter "AnyAudioImporter", +@relativeref{Trade,AnyImageImporter}, @relativeref{Trade,AnySceneImporter}, +@relativeref{Trade,AnyImageConverter}, @relativeref{Trade,AnySceneConverter} +and @ref ShaderTools::AnyConverter "AnyShaderConverter" plugins can be used for +generic handling of any of the recognized formats, they'll proxy the operation +to a concrete plugin implementation. The following tables list the most widely +used formats with alternative plugin implementations and known caveats for +each. When one format is supported by more than one plugin, you can use +@ref Corrade::PluginManager::AbstractManager::setPreferredPlugins() to prioritize a particular plugin implementation. See the @ref file-formats-legend section at the bottom of the page for a @@ -610,7 +611,7 @@ mentioning requirements coming from the license: dependencies, which don't require anything from you in order to use them and put no restrictions on use - @m_class{m-label m-success} **green** marks licenses that make the - dependency safe to use in a commerical setting without having to release + dependency safe to use in a commercial setting without having to release your source code, usually requiring you to give attribution. - @m_class{m-label m-warning} **yellow** marks licenses that require you to either dynamically link to the software to be able to use it in a diff --git a/doc/generated/easings.cpp b/doc/generated/easings.cpp index 6e925cb6d..90ad058e4 100644 --- a/doc/generated/easings.cpp +++ b/doc/generated/easings.cpp @@ -204,7 +204,7 @@ const Color3 success = 0x3bd267_srgbf; int main() { using namespace Animation::Easing; - #define _c(name) Utility::String::lowercase(#name), name + #define _c(name) Utility::String::lowercase(std::string{#name}), name generate(_c(linear), {}, /* [linear] */ CubicBezier2D{Vector2{0.0f}, Vector2{1.0f/3.0f}, diff --git a/doc/platforms-android.dox b/doc/platforms-android.dox index 5be3d9997..ff75adf55 100644 --- a/doc/platforms-android.dox +++ b/doc/platforms-android.dox @@ -250,7 +250,7 @@ endif() @endcode On the first run, the macro will attempt to detect SDK location, Android Build -Tools version and Android Platfrom version and it prints them to the output +Tools version and Android Platform version and it prints them to the output like this: @code{.shell-session} @@ -1137,7 +1137,7 @@ This for 3.3.0: @endcode And probably a dozen other variants, but I am not willing to waste my time any -further by enumerating all possible embarrasing crashes of cursed tools. The +further by enumerating all possible embarrassing crashes of cursed tools. The oldest Android Gradle plugin version that worked with Gradle 6.6.1 and NDK r19 was 3.6.0, and to change it update the following entry in your `build.gradle`: diff --git a/doc/platforms-html5.dox b/doc/platforms-html5.dox index 2714c4a75..0f348206f 100644 --- a/doc/platforms-html5.dox +++ b/doc/platforms-html5.dox @@ -379,7 +379,7 @@ using a @cb{.css} padding-bottom @ce style with a percentage equal to inverse of the ratio for @cb{.css} div#expander @ce --- for example, a 2.35:1 ratio would be @cb{.html}
@ce. -Size of the canvas can be also overriden by specifying one of the +Size of the canvas can be also overridden by specifying one of the @cb{.css} .mn-width-* @ce CSS classes on the @cb{.html}
@ce: diff --git a/doc/platforms-windows.dox b/doc/platforms-windows.dox index c5d5dc934..704b3ecfa 100644 --- a/doc/platforms-windows.dox +++ b/doc/platforms-windows.dox @@ -128,7 +128,7 @@ much as possible: @section platform-windows-icon Executable icon In order to supply an icon for the executable, make an `*.ico` file -(preferrably out of multiple different sizes) and create a `*.rc` file +(preferably out of multiple different sizes) and create a `*.rc` file referencing it. The first argument can be anything (it can be used for retrieving the icon later at runtime via Windows APIs), Windows always pick the first icon in the `*.rc` file for the executable. diff --git a/doc/shaders.dox b/doc/shaders.dox index eb867025f..f04dd56c3 100644 --- a/doc/shaders.dox +++ b/doc/shaders.dox @@ -34,8 +34,7 @@ namespace Magnum { Magnum contains a set of general-purpose shaders for easy prototyping, UI rendering and data visualization/debugging in both 2D and 3D scenes. The -following shaders are available, see documentation of each class for sample -output and example setup: +following shaders are available: - @ref Shaders::FlatGL "Shaders::FlatGL*D" --- flat shading using single color or texture @@ -48,32 +47,245 @@ output and example setup: - @ref Shaders::MeshVisualizerGL2D / @ref Shaders::MeshVisualizerGL3D --- wireframe visualization -All the builtin shaders can be used on unextended OpenGL 2.1 and OpenGL ES 2.0 -/ WebGL 1.0, but they try to use the most recent technology available to have -them as efficient as possible on every configuration. +The essential functionality of builtin shaders can be used even on unextended +OpenGL 2.1 and OpenGL ES 2.0 / WebGL 1.0, but the code will try to use the most +recent technology available to have them as efficient as possible on every +configuration. Some functionality, such as uniform buffers, texture arrays or +object ID rendering, requires newer versions or extensions, as noted in +documentation of a particular feature. @section shaders-usage Usage -Shader usage is divided into two parts: configuring vertex attributes in the -mesh and configuring the shader itself. +Shader usage is divided into two parts: describing vertex attributes in the +mesh and setting up the shader itself. -Each shader expects some set of vertex attributes, thus when adding vertex +Each shader expects some set of vertex attributes, thus when adding a vertex buffer into the mesh, you need to specify which shader attributes are on which position in the buffer. See @ref GL::Mesh::addVertexBuffer() for details and -usage examples. Example mesh configuration for @ref Shaders::PhongGL shader: +usage examples. Example mesh configuration for the @ref Shaders::PhongGL +shader: @snippet MagnumShaders-gl.cpp shaders-setup Each shader then has its own set of configuration functions. Some configuration is static, specified commonly as flags in constructor, directly affecting compiled shader code. Other configuration is specified through uniforms and -various binding points, commonly exposed through various setters. All shader -uniforms have a reasonable defaults so you are able to see at least something -when using the shader directly without any further configuration, but in most -cases you may want to specify at least the transformation/projection matrices. -Example configuration and rendering using @link Shaders::PhongGL @endlink: +various binding points, commonly exposed through various setters. For uniforms +there's two different workflows --- a classical one, where uniforms have +immediate setters, and a uniform buffer workflow, where the uniform parameters +are saved to a structure and then uploaded to a GPU buffer. Let's compare both +approaches: -@snippet MagnumShaders-gl.cpp shaders-rendering +@subsection shaders-usage-classic Using classic uniforms + +The most straightforward and portable way, working even on old OpenGL ES 2.0 +and WebGL 1.0 platforms, is using classic uniform setters. All shader uniforms +have a reasonable defaults so you are able to see at least something when using +the shader directly without any further configuration, but in most cases you +may want to specify at least the transformation/projection matrices. +Example configuration and rendering using @link Shaders::PhongGL @endlink --- +by default it's just colored and uses a single light, and we set a color of +both in addition to transformation, projection and normal matrices: + +@snippet MagnumShaders-gl.cpp shaders-classic + +@subsection shaders-usage-ubo Using uniform buffers + +Uniform buffers require GL 3.1, OpenGL ES 3.0 or WebGL 2.0 and are more verbose +to set up, but when used the right way they can result in greatly reduced +driver overhead. Uniform buffers get enabled using the +@relativeref{Shaders::PhongGL,Flag::UniformBuffers} flag that's implemented for +all builtin shaders, and after that you're not supposed to use most of the +`set*()` APIs anymore, instead you have to fill uniform structures, upload them +to @ref GL::Buffer instances and then bind those via various `bind*Buffer()` +APIs. To simplify porting, documentation of each classic uniform setter lists +the equivalent uniform buffer APIs. + +Because some parameters such as projection, material or light setup don't +change every draw, they are organized into buffers based on expected frequency +of change. This way you can fill the projection and material buffers just once +at the start, light setup only when the camera position changes and with much +less to upload for every draw. The separation is also done in a way that makes +it possible to reuse projection/transformation data among different shaders, +e.g. for a depth pre-pass. + +In the following example, projection and transformation parameters are supplied +via generic shader-independent @ref Shaders::ProjectionUniform3D and +@ref Shaders::TransformationUniform3D structures and Phong-specific parameters +then via @ref Shaders::PhongDrawUniform, @ref Shaders::PhongMaterialUniform and +@ref Shaders::PhongLightUniform structures. While the structures expose the +fields directly, the data layout may be non-trivial and it's thus recommended +to use the setters unless they prove to be a performance bottleneck: + +@snippet MagnumShaders-gl.cpp shaders-ubo + +Altogether, this results in the same output as in the classic uniform case +shown above. Similarly to the classic uniforms, default-constructed structures +have reasonable defaults to make the shader render at least something, but note +that you *have to* bind the buffer to get the defaults, without a buffer bound +you'll get a fully black mesh at best and nothing rendered at all in the worst +cases. + +@m_class{m-block m-success} + +@par Importance of buffer usage and storage flags + With uniform buffers, it's very important what kind of memory gets used for + the backing storage. In the above snippets, the (implicit) + @ref GL::BufferUsage::StaticDraw got used for simplicity, but for data that + are changed for every draw it could make sense to pick + @ref GL::BufferUsage::DynamicDraw instead. +@par + The most ideal way may be to use @gl_extension{ARB,buffer_storage} + from OpenGL 4.4, and instead of @ref GL::Buffer::setData() calling + @relativeref{GL::Buffer,setStorage()} directly with the uniform data and + with empty @ref GL::Buffer::StorageFlags, which makes the buffer immutable. + Updating such immutable buffer can be still done via + @ref GL::Buffer::copy() from another buffer, but setting or mapping the + data from the CPU side won't be possible. +@par + As with everything, be sure to profile and pick the best workflow for your + target platform --- what's best for desktop may not be the best in WebGL, + and what works with WebGL running on top GL may not be the best with WebGL + that's itself implemented using D3D or Metal. + +@subsection shaders-usage-multidraw Multidraw and reducing driver overhead + +The main advantage of uniform buffers is the ability to specify data for +multiple draws together --- after all, having to reupload three or four buffers +for every draw like shown above wouldn't be really faster or easier than +setting the uniforms directly. On the other hand, uploading everything first +and binding a different subrange each time would avoid the reupload, but since +most drivers have uniform buffer alignment requirement as high as 256 bytes +(@ref GL::Buffer::uniformOffsetAlignment()), the per-draw buffers would have to +be very sparse. + +Instead, it's possible to construct the shaders with a statically defined +draw count, fill the buffers with data for that many draws at once and then use +@relativeref{Shaders::PhongGL,setDrawOffset()} to pick concrete per-draw +parameters. Since material parameters are commonly shared among multiple draws, +the desired usage is to upload unique materials and then reference them via a +@ref Shaders::PhongDrawUniform::materialId "Shaders::*DrawUniform::materialId". +The following snippet shows drawing three different meshes, where two of them +share the same material definition. The projection and light buffer is the same +as above: + +@snippet MagnumShaders-gl.cpp shaders-multi + +While this minimizes the state changes to just a single immediate uniform being +changed between draws, it's possible to go even further by using +@ref GL::MeshView instances onto a single @ref GL::Mesh instead of several +different @ref GL::Mesh objects --- that way the attribute layout doesn't need +to be updated and it's just submitting draws with different offsets and counts. + +Finally, with mesh views and on platforms that support @gl_extension{ARB,shader_draw_parameters} from OpenGL 4.6 or the +@gl_extension{ANGLE,multi_draw} / @webgl_extension{WEBGL,multi_draw} ES and +WebGL extension, it's possible to directly submit a multi-draw command. The +shader needs to have @relativeref{Shaders::PhongGL,Flag::MultiDraw} enabled, +which will make it use the @glsl gl_DrawID @ce builtin to pick the per-draw +parameters on its own. The above snippet modified for multidraw would then look +like this, uniform upload and binding is the same as before: + +@snippet MagnumShaders-gl.cpp shaders-multidraw + + + +@m_class{m-block m-warning} + +@par Uniform buffer size limits + Note that size of a single uniform buffer that can be bound to a shader is + quite limited (@ref GL::AbstractShaderProgram::maxUniformBlockSize(), + usually just 16 or 64 kB), and another reason the parameters are separated + and deduplicated among several buffers is to maximize use of that memory. + With that you should always be able to submit at least 256 draws at once as + the biggest per-draw uniform structure used by builtin shaders has a size + of a 4x4 matrix. +@par + For larger batches the expected workflow is to still upload everything at + once but then bind and draw smaller (and properly aligned) subranges that + fit into the limit. For convenience, all uniform structures are guaranteed + to fit evenly into multiples of 768 bytes, which should be large enough for + even the strictest @ref GL::Buffer::uniformOffsetAlignment() requirements. + +@subsection shaders-usage-instancing Instancing + +@ref Shaders::FlatGL and @ref Shaders::PhongGL support instancing, which allows +them to render the same mesh several times but with different transformation +and material applied. It can be thought of as a more constrained variant of the +multidraw mentioned above, but instead of uniform buffers the per-instance +parameters are passed through instanced mesh attributes. + +No uniform buffer requirement means this feature can be used even on OpenGL ES +2.0 and WebGL 1.0 targets if corresponding instancing extensions are available. +Using attributes instead of uniform buffers also means there's no limitation on +how many instances can be drawn at once, on the other hand a mesh can have only +a certain amount of attribute bindings and thus only the basic properties can +be specified per-instance such as the transformation matrix or base color. + +The following snippet shows a setup similar to the multidraw above, except that +it's just the same sphere drawn three times in different locations and with a +different material applied. Note that the per-instance color is achieved by +using the usual vertex color attribute, only instanced: + +@snippet MagnumShaders-gl.cpp shaders-instancing + +@subsection shaders-usage-textures Using textures + +Unless the shader requires a texture to work (which is the case of +@ref Shaders::VectorGL and @ref Shaders::DistanceFieldVectorGL), by default all +shaders are just colored. Enabling a texture is done via a flag (such as +@ref Shaders::Phong::Flag::DiffuseTexture) and then the texture is bound via an +appropriate `bind*Texture()` call. In most cases the texture value is +multiplied with the corresponding color uniform. + +@snippet MagnumShaders-gl.cpp shaders-textures + +All shaders that support textures are also able to apply arbitrary +transformation to the texture coordinate attribute by enabling +@relativeref{Shaders::PhongGL,Flag::TextureTransformation} on a particular +shader. Desired transformation is then supplied via +@relativeref{Shaders::PhongGL,setTextureMatrix()} (or a +@ref Shaders::TextureTransformationUniform in case uniform buffers are used). +This can be useful for animations, when you have a larger atlas with switchable +texture variations for a single mesh, or when you have texture coordinates +quantized in some nontrivial way. + +Texture transformation is also useful in the +@ref shaders-usage-multidraw "multidraw" or +@ref shaders-usage-instancing "instancing" scenarios mentioned above, since +each draw will most likely require a different texture. There are two options: + +- Upload the textures to subrectangles of a larger @ref GL::Texture2D and + then specify @ref Shaders::TextureTransformationUniform::offset and + @relativeref{Shaders::TextureTransformationUniform,rotationScaling} for + each draw, or in case of an instanced draw supply an instanced + @relativeref{Shaders::PhongGL,TextureOffset} attribute and have a global + scale set for all instanced via + @relativeref{Shaders::PhongGL,setTextureMatrix()}. +- Enable @relativeref{Shaders::PhongGL,Flag::TextureArrays} in the shader + (not available on OpenGL ES 2.0 or WebGL 1.0), upload the textures to + slices of a @ref GL::Texture2DArray and specify + @ref Shaders::TextureTransformationUniform::layer for each draw, or in case + of an instanced draw supply a layer in an instanced + @relativeref{Shaders::PhongGL,TextureOffsetLayer} attribute. + +While with a @ref GL::Texture2D you may hit texture size limits (not to mention +you possible issues with materials that relied on a certain wrapping mode), +@ref GL::Texture2DArray is generally able to contain a lot more data, however +all slices have to be of the same size. You can also combine the two approaches +and pack differently sized textures to slices of a texture array and then set +both offset/scale and a layer per-draw. + +The following snippet shows a multi-draw setup with a different texture array +layer used by each draw. While the projection, transformation, draw material +and light buffers are the same as before, there's a new per-draw +@ref Shaders::TextureTransformationUniform buffer supplying the layer +information: + +@snippet MagnumShaders-gl.cpp shaders-texture-arrays + +While the primary use case of texture arrays is with uniform buffers and +multidraw, they work in the classic uniform workflow as well --- use +@relativeref{Shaders::PhongGL,setTextureLayer()} there instead. @section shaders-generic Generic vertex attributes and framebuffer attachments @@ -81,28 +293,29 @@ Many shaders share the same vertex attribute definitions, such as positions, normals, texture coordinates etc. It's thus possible to configure the mesh for a *generic* shader and then render it with any compatible shader. Definition of all generic attributes is available in the -@ref Shaders::GenericGL class. Configuration of the above mesh using generic -attributes could then look like this: +@ref Shaders::GenericGL class. Setup of the mesh @ref shaders-usage "shown above" +using generic attributes could then look like this: @snippet MagnumShaders-gl.cpp shaders-generic -Note that in this particular case both configurations are equivalent, because -@ref Shaders::PhongGL also uses generic vertex attribute definitions. Then you -can render the mesh using @ref Shaders::PhongGL shader like above, or use for -example @ref Shaders::FlatGL3D or even @ref Shaders::MeshVisualizerGL3D with -the same mesh reconfiguration. The unused attributes will be simply ignored. +Note that in this particular case both setups are equivalent, because +@ref Shaders::PhongGL attribute definitions are just aliases to the generic +ones. Then you can render the mesh using the @ref Shaders::PhongGL shader like +above, or use for example @ref Shaders::FlatGL3D or even +@ref Shaders::MeshVisualizerGL3D with the same mesh reconfiguration. The unused +attributes will be simply ignored. @snippet MagnumShaders-gl.cpp shaders-meshvisualizer The @ref MeshTools::compile() utility configures meshes using generic vertex -attribute definitions to make them usable with any shader. - -Besides vertex attributes, the @ref Shaders::GenericGL contains generic -definitions for framebuffer outputs as well --- in many cases a shader has just -one (color) output, but some shaders such as @ref Shaders::FlatGL or -@ref Shaders::PhongGL offer an object ID output as well. A setup equivalent to -what's done in Flat shader's @ref Shaders-FlatGL-object-id but using the -generic definitions would look like this: +attribute definitions to make them usable with any builtin shader. + +Besides vertex attributes, @ref Shaders::GenericGL contains generic definitions +for framebuffer outputs as well --- in many cases a shader has just one (color) +output, but some shaders such as @ref Shaders::FlatGL or @ref Shaders::PhongGL +offer an object ID output as well. A setup equivalent to what's done in Flat +shader's @ref Shaders-FlatGL-object-id but using the generic definitions would +look like this: @snippet MagnumShaders-gl.cpp shaders-generic-object-id */ diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index dd8510fd4..9af7aca4b 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -141,7 +141,7 @@ if(WITH_DEBUGTOOLS) # CompareImage documentation snippet. I need it executable so I can # copy&paste the output to the documentation. Also not using # corrade_add_test() because it shouldn't be run as part of CTest as it - # purposedly fails. + # purposely fails. add_executable(debugtools-compareimage debugtools-compareimage.cpp) target_link_libraries(debugtools-compareimage PRIVATE MagnumDebugTools diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index a17f87d98..e11b93612 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -121,7 +121,15 @@ 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}; @@ -269,11 +277,32 @@ enum: UnsignedInt { /* [AbstractShaderProgram-output-attributes] */ #if !defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) -/* [AbstractShaderProgram-hide-irrelevant] */ +/* [AbstractShaderProgram-return-hide-irrelevant] */ +public: + MyShader& draw(GL::Mesh& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MyShader& draw(GL::Mesh&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MyShader& draw(GL::MeshView& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MyShader& draw(GL::MeshView&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + /* Omit these if the shader is not ready for multidraw */ + MyShader& draw(Containers::ArrayView> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + MyShader& draw(std::initializer_list> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + private: using GL::AbstractShaderProgram::drawTransformFeedback; using GL::AbstractShaderProgram::dispatchCompute; -/* [AbstractShaderProgram-hide-irrelevant] */ +/* [AbstractShaderProgram-return-hide-irrelevant] */ public: #endif diff --git a/doc/snippets/MagnumMath.cpp b/doc/snippets/MagnumMath.cpp index 81ebbeab8..9ecdd54f2 100644 --- a/doc/snippets/MagnumMath.cpp +++ b/doc/snippets/MagnumMath.cpp @@ -625,6 +625,11 @@ static_cast(radians); } { +#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 /* [Deg-usage-convert] */ Double foo(); @@ -634,6 +639,9 @@ Radd radians{foo()}; /* [Deg-usage-convert] */ static_cast(degrees); static_cast(radians); +#if defined(CORRADE_TARGET_GCC) && __GNUC__ >= 11 +#pragma GCC diagnostic pop +#endif } { @@ -646,6 +654,11 @@ static_cast(b); } { +#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 Double foo(); /* [Deg-usage-comparison] */ Rad angle(); @@ -654,6 +667,9 @@ Deg x = angle(); // convert to degrees for easier comparison if(x < 30.0_degf) foo(); //if(x > 1.57_radf) bar(); // error, both need to be of the same type /* [Deg-usage-comparison] */ +#if defined(CORRADE_TARGET_GCC) && __GNUC__ >= 11 +#pragma GCC diagnostic pop +#endif } { diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index 96177ac94..0489a52a2 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -35,6 +35,7 @@ #include "Magnum/GL/DefaultFramebuffer.h" #include "Magnum/GL/Framebuffer.h" #include "Magnum/GL/Mesh.h" +#include "Magnum/GL/MeshView.h" #include "Magnum/GL/Shader.h" #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" @@ -54,6 +55,16 @@ #include "Magnum/Shaders/VertexColorGL.h" #include "Magnum/Trade/LightData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#include "Magnum/Shaders/DistanceFieldVector.h" +#include "Magnum/Shaders/Flat.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/MeshVisualizer.h" +#include "Magnum/Shaders/Phong.h" +#include "Magnum/Shaders/Vector.h" +#endif + #define DOXYGEN_IGNORE(...) __VA_ARGS__ using namespace Magnum; @@ -86,29 +97,241 @@ mesh.addVertexBuffer(vertices, 0, //... ; /* [shaders-setup] */ +} +#endif -/* [shaders-rendering] */ -Matrix4 transformationMatrix, projectionMatrix; -GL::Texture2D diffuseTexture, specularTexture; +{ +GL::Mesh mesh; +/* [shaders-classic] */ +Matrix4 transformationMatrix{DOXYGEN_IGNORE()}, projectionMatrix{DOXYGEN_IGNORE()}; -Shaders::PhongGL shader{Shaders::PhongGL::Flag::DiffuseTexture}; -shader.bindDiffuseTexture(diffuseTexture) +Shaders::PhongGL shader; +shader .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) .setNormalMatrix(transformationMatrix.normalMatrix()) + .setDiffuseColor(0x2f83cc_rgbf) + .setLightColors({0xe9ecae_rgbf}) + .draw(mesh); +/* [shaders-classic] */ +} + +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; +/* [shaders-ubo] */ +GL::Buffer projectionUniform, lightUniform, materialUniform, + transformationUniform, drawUniform; +projectionUniform.setData({ + Shaders::ProjectionUniform3D{} + .setProjectionMatrix(projectionMatrix) +}); +lightUniform.setData({ + Shaders::PhongLightUniform{} + .setColor(0xe9ecae_rgbf) +}); +materialUniform.setData({ + Shaders::PhongMaterialUniform{} + .setDiffuseColor(0x2f83cc_rgbf) +}); +transformationUniform.setData({ + Shaders::TransformationUniform3D{} + .setTransformationMatrix(transformationMatrix) +}); +drawUniform.setData({ + Shaders::PhongDrawUniform{} + .setNormalMatrix(transformationMatrix.normalMatrix()) +}); + +Shaders::PhongGL shader{Shaders::PhongGL::Flag::UniformBuffers}; +shader + .bindProjectionBuffer(projectionUniform) + .bindLightBuffer(lightUniform) + .bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .draw(mesh); +/* [shaders-ubo] */ +} + +{ +GL::Buffer projectionUniform, transformationUniform, drawUniform, lightUniform, + materialUniform; +/* [shaders-multi] */ +GL::Mesh redCone{DOXYGEN_IGNORE()}, yellowCube{DOXYGEN_IGNORE()}, redSphere{DOXYGEN_IGNORE()}; +Matrix4 redConeTransformation{DOXYGEN_IGNORE()}, + yellowCubeTransformation{DOXYGEN_IGNORE()}, + redSphereTransformation{DOXYGEN_IGNORE()}; + +materialUniform.setData({ + Shaders::PhongMaterialUniform{} + .setDiffuseColor(0xcd3431_rgbf), + Shaders::PhongMaterialUniform{} + .setDiffuseColor(0xc7cf2f_rgbf), +}); +transformationUniform.setData({ + Shaders::TransformationUniform3D{} + .setTransformationMatrix(redConeTransformation), + Shaders::TransformationUniform3D{} + .setTransformationMatrix(yellowCubeTransformation), + Shaders::TransformationUniform3D{} + .setTransformationMatrix(redSphereTransformation), +}); +drawUniform.setData({ + Shaders::PhongDrawUniform{} + .setNormalMatrix(redConeTransformation.normalMatrix()) + .setMaterialId(0), + Shaders::PhongDrawUniform{} + .setNormalMatrix(yellowCubeTransformation.normalMatrix()) + .setMaterialId(1), + Shaders::PhongDrawUniform{} + .setNormalMatrix(redSphereTransformation.normalMatrix()) + .setMaterialId(0), +}); + +/* One light, two materials, three draws */ +Shaders::PhongGL shader{Shaders::PhongGL::Flag::UniformBuffers, 1, 2, 3}; +shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindLightBuffer(lightUniform) + .bindMaterialBuffer(materialUniform) + .setDrawOffset(0) + .draw(redCone) + .setDrawOffset(1) + .draw(yellowCube) + .setDrawOffset(2) + .draw(redSphere); +/* [shaders-multi] */ +} + +{ +GL::Mesh mesh; +/* [shaders-multidraw] */ +GL::MeshView redConeView{DOXYGEN_IGNORE(mesh)}, yellowCubeView{DOXYGEN_IGNORE(mesh)}, redSphereView{DOXYGEN_IGNORE(mesh)}; +DOXYGEN_IGNORE() + +/* One light, two materials, three draws; with multidraw enabled */ +Shaders::PhongGL shader{Shaders::PhongGL::Flag::MultiDraw, 1, 2, 3}; +shader + DOXYGEN_IGNORE() + .draw({redConeView, yellowCubeView, redSphereView}); +/* [shaders-multidraw] */ +} +#endif + +{ +Matrix4 projectionMatrix; +/* [shaders-instancing] */ +Matrix4 redSphereTransformation{DOXYGEN_IGNORE()}, + yellowSphereTransformation{DOXYGEN_IGNORE()}, + greenSphereTransformation{DOXYGEN_IGNORE()}; + +struct { + Matrix4 transformationMatrix; + Matrix3x3 normalMatrix; + Color3 color; +} instanceData[]{ + {redSphereTransformation, + redSphereTransformation.normalMatrix(), + 0xcd3431_rgbf}, + {yellowSphereTransformation, + yellowSphereTransformation.normalMatrix(), + 0xc7cf2f_rgbf}, + {greenSphereTransformation, + greenSphereTransformation.normalMatrix(), + 0x3bd267_rgbf}, +}; + +GL::Mesh sphereInstanced{DOXYGEN_IGNORE()}; +sphereInstanced.addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + Shaders::PhongGL::TransformationMatrix{}, + Shaders::PhongGL::NormalMatrix{}, + Shaders::PhongGL::Color3{}); +sphereInstanced.setInstanceCount(3); + +Shaders::PhongGL shader{Shaders::PhongGL::Flag::InstancedTransformation| + Shaders::PhongGL::Flag::VertexColor}; +shader .setProjectionMatrix(projectionMatrix) + DOXYGEN_IGNORE() + .draw(sphereInstanced); +/* [shaders-instancing] */ +} + +{ +GL::Mesh mesh; +/* [shaders-textures] */ +GL::Texture2D diffuseTexture; +DOXYGEN_IGNORE() + +Shaders::PhongGL shader{Shaders::PhongGL::Flag::DiffuseTexture}; +shader.bindDiffuseTexture(diffuseTexture) + DOXYGEN_IGNORE() .draw(mesh); -/* [shaders-rendering] */ +/* [shaders-textures] */ +} +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +GL::MeshView redConeView{DOXYGEN_IGNORE(mesh)}, yellowCubeView{DOXYGEN_IGNORE(mesh)}, redSphereView{DOXYGEN_IGNORE(mesh)}; +/* [shaders-texture-arrays] */ +ImageView2D coneDiffuse{DOXYGEN_IGNORE({}, {})}, cubeDiffuse{DOXYGEN_IGNORE({}, {})}, sphereDiffuse{DOXYGEN_IGNORE({}, {})}; + +GL::Texture2DArray diffuseTexture; +diffuseTexture + DOXYGEN_IGNORE() + /* Assuming all iamges have the same format and size */ + .setStorage(1, GL::textureFormat(coneDiffuse.format()), + {coneDiffuse.size(), 3}) + .setSubImage(0, {}, coneDiffuse) + .setSubImage(1, {}, cubeDiffuse) + .setSubImage(2, {}, sphereDiffuse); + +GL::Buffer textureTransformationUniform; +textureTransformationUniform.setData({ + Shaders::TextureTransformationUniform{} + .setLayer(0), + Shaders::TextureTransformationUniform{} + .setLayer(1), + Shaders::TextureTransformationUniform{} + .setLayer(2), +}); + +Shaders::PhongGL shader{ + Shaders::PhongGL::Flag::MultiDraw| + Shaders::PhongGL::Flag::DiffuseTexture| + Shaders::PhongGL::Flag::TextureArrays, + 1, 2, 3}; +shader + DOXYGEN_IGNORE() + .bindDiffuseTexture(diffuseTexture) + .bindTextureTransformationBuffer(textureTransformationUniform) + .draw({redConeView, yellowCubeView, redSphereView}); +/* [shaders-texture-arrays] */ +} +#endif + +{ +GL::Buffer vertices; +GL::Mesh mesh; /* [shaders-generic] */ mesh.addVertexBuffer(vertices, 0, Shaders::GenericGL3D::Position{}, Shaders::GenericGL3D::Normal{}, Shaders::GenericGL3D::TextureCoordinates{}); /* [shaders-generic] */ +} +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; /* [shaders-meshvisualizer] */ -Shaders::MeshVisualizerGL3D visualizerShader{Shaders::MeshVisualizerGL3D::Flag::Wireframe}; -visualizerShader +Shaders::MeshVisualizerGL3D shader{Shaders::MeshVisualizerGL3D::Flag::Wireframe}; +shader .setColor(0x2f83cc_rgbf) .setWireframeColor(0xdcdcdc_rgbf) .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) @@ -118,6 +341,9 @@ visualizerShader /* [shaders-meshvisualizer] */ } +/* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 + on GCC 4.8 in the [60] array */ +#if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [DistanceFieldVectorGL-usage1] */ struct Vertex { @@ -150,12 +376,51 @@ Shaders::DistanceFieldVectorGL2D shader; shader.setColor(0x2f83cc_rgbf) .setOutlineColor(0xdcdcdc_rgbf) .setOutlineRange(0.6f, 0.4f) - .bindVectorTexture(texture) .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .bindVectorTexture(texture) .draw(mesh); /* [DistanceFieldVectorGL-usage2] */ } +#endif + +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix3 transformationMatrix, projectionMatrix; +GL::Texture2D texture; +/* [DistanceFieldVectorGL-ubo] */ +GL::Buffer projectionTransformationUniform, materialUniform, drawUniform; +projectionTransformationUniform.setData({ + Shaders::TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(transformationMatrix*projectionMatrix) +}); +materialUniform.setData({ + Shaders::DistanceFieldVectorMaterialUniform{} + .setColor(0x2f83cc_rgbf) + .setOutlineColor(0xdcdcdc_rgbf) + .setOutlineRange(0.6f, 0.4f) +}); +drawUniform.setData({ + Shaders::DistanceFieldVectorDrawUniform{} + .setMaterialId(0) +}); + +Shaders::DistanceFieldVectorGL2D shader{ + Shaders::DistanceFieldVectorGL2D::Flag::UniformBuffers +}; +shader + .bindTransformationProjectionBuffer(projectionTransformationUniform) + .bindMaterialBuffer(materialUniform) + .bindDrawBuffer(drawUniform) + .bindVectorTexture(texture) + .draw(mesh); +/* [DistanceFieldVectorGL-ubo] */ +} +#endif +/* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 + on GCC 4.8 in the [60] array */ +#if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [FlatGL-usage-colored1] */ struct Vertex { @@ -217,6 +482,7 @@ shader.setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) .draw(mesh); /* [FlatGL-usage-textured2] */ } +#endif #ifndef MAGNUM_TARGET_GLES2 { @@ -274,6 +540,35 @@ mesh.setInstanceCount(Containers::arraySize(instanceData)) /* [FlatGL-usage-instancing] */ } +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; +/* [FlatGL-ubo] */ +GL::Buffer projectionTransformationUniform, materialUniform, drawUniform; +projectionTransformationUniform.setData({ + Shaders::TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix(transformationMatrix*projectionMatrix) +}); +materialUniform.setData({ + Shaders::FlatMaterialUniform{} + .setColor(0x2f83cc_rgbf) +}); +drawUniform.setData({ + Shaders::FlatDrawUniform{} + .setMaterialId(0) +}); + +Shaders::FlatGL3D shader{Shaders::FlatGL3D::Flag::UniformBuffers}; +shader + .bindTransformationProjectionBuffer(projectionTransformationUniform) + .bindMaterialBuffer(materialUniform) + .bindDrawBuffer(drawUniform) + .draw(mesh); +/* [FlatGL-ubo] */ +} +#endif + { struct: GL::AbstractShaderProgram { void foo() { @@ -320,6 +615,9 @@ mesh.setInstanceCount(Containers::arraySize(instanceData)) /* [PhongGL-usage-instancing] */ } +/* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 + on GCC 4.8 in the [60] array */ +#if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [MeshVisualizerGL3D-usage-geom1] */ struct Vertex { @@ -463,6 +761,47 @@ shader.setColorMapTransformation(0.0f, 1.0f/Math::max(objectIds)) } #endif +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; +GL::Texture2D texture; +/* [MeshVisualizerGL3D-ubo] */ +GL::Buffer projectionUniform, materialUniform, transformationUniform, + drawUniform; +projectionUniform.setData({ + Shaders::ProjectionUniform3D{} + .setProjectionMatrix(projectionMatrix) +}); +materialUniform.setData({ + Shaders::MeshVisualizerMaterialUniform{} + .setColor(0x2f83cc_rgbf) + .setWireframeColor(0xdcdcdc_rgbf) +}); +transformationUniform.setData({ + Shaders::TransformationUniform3D{} + .setTransformationMatrix(transformationMatrix) +}); +drawUniform.setData({ + Shaders::MeshVisualizerDrawUniform3D{} + .setMaterialId(0) +}); + +Shaders::MeshVisualizerGL3D shader{ + Shaders::MeshVisualizerGL3D::Flag::Wireframe| + Shaders::MeshVisualizerGL3D::Flag::UniformBuffers +}; +shader + .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) + .bindProjectionBuffer(projectionUniform) + .bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .draw(mesh); +/* [MeshVisualizerGL3D-ubo] */ +} +#endif + #if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [PhongGL-usage-colored1] */ @@ -580,6 +919,48 @@ shader.bindTextures(&diffuseAlphaTexture, &diffuseAlphaTexture, nullptr, nullptr /* [PhongGL-usage-alpha] */ } +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; +GL::Texture2D texture; +/* [PhongGL-ubo] */ +GL::Buffer projectionUniform, lightUniform, materialUniform, + transformationUniform, drawUniform; +projectionUniform.setData({ + Shaders::ProjectionUniform3D{} + .setProjectionMatrix(projectionMatrix) +}); +lightUniform.setData({ + Shaders::PhongLightUniform{} +}); +materialUniform.setData({ + Shaders::PhongMaterialUniform{} + .setDiffuseColor(0x2f83cc_rgbf) + .setShininess(200.0f) +}); +transformationUniform.setData({ + Shaders::TransformationUniform3D{} + .setTransformationMatrix(transformationMatrix) +}); +drawUniform.setData({ + Shaders::PhongDrawUniform{} + .setNormalMatrix(transformationMatrix.normalMatrix()) + .setMaterialId(0) +}); + +Shaders::PhongGL shader{Shaders::PhongGL::Flag::UniformBuffers}; +shader + .bindProjectionBuffer(projectionUniform) + .bindLightBuffer(lightUniform) + .bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .draw(mesh); +/* [PhongGL-ubo] */ +} +#endif + #if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [VectorGL-usage1] */ @@ -611,7 +992,40 @@ shader.setColor(0x2f83cc_rgbf) .draw(mesh); /* [VectorGL-usage2] */ } +#endif +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix3 transformationMatrix, projectionMatrix; +GL::Texture2D texture; +/* [VectorGL-ubo] */ +GL::Buffer projectionTransformationUniform, materialUniform, drawUniform; +projectionTransformationUniform.setData({ + Shaders::TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(transformationMatrix*projectionMatrix) +}); +materialUniform.setData({ + Shaders::VectorMaterialUniform{} + .setColor(0x2f83cc_rgbf) +}); +drawUniform.setData({ + Shaders::VectorDrawUniform{} + .setMaterialId(0) +}); + +Shaders::VectorGL2D shader{Shaders::VectorGL2D::Flag::UniformBuffers}; +shader + .bindTransformationProjectionBuffer(projectionTransformationUniform) + .bindMaterialBuffer(materialUniform) + .bindDrawBuffer(drawUniform) + .bindVectorTexture(texture) + .draw(mesh); +/* [VectorGL-ubo] */ +} +#endif + +#if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [VertexColorGL-usage1] */ struct Vertex { @@ -643,4 +1057,23 @@ shader.setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) } #endif +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix4 transformationMatrix, projectionMatrix; +/* [VertexColorGL-ubo] */ +GL::Buffer projectionTransformationUniform; +projectionTransformationUniform.setData({ + Shaders::TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix(transformationMatrix*projectionMatrix) +}); + +Shaders::VertexColorGL3D shader{Shaders::VertexColorGL3D::Flag::UniformBuffers}; +shader + .bindTransformationProjectionBuffer(projectionTransformationUniform) + .draw(mesh); +/* [VertexColorGL-ubo] */ +} +#endif + } diff --git a/doc/transformations.dox b/doc/transformations.dox index b1ad714f3..7c389596d 100644 --- a/doc/transformations.dox +++ b/doc/transformations.dox @@ -319,7 +319,7 @@ create helper functions, if you need them. The @ref SceneGraph API provides a hierarchical transformation hierarchy and a correct camera-relative transformation is calculated automatically in the -background, avoiding the need for manualy handling of model / view +background, avoiding the need for manually handling of model / view transformations. In particular, camera position is always specified as relative to scene root and it gets inverted when calculating the final per-object transformation. See @ref scenegraph for detailed description. diff --git a/modules/FindCorrade.cmake b/modules/FindCorrade.cmake index 9a36cce28..39e7cd4b7 100644 --- a/modules/FindCorrade.cmake +++ b/modules/FindCorrade.cmake @@ -100,7 +100,7 @@ # CORRADE_TARGET_MINGW - Defined if compiling under MinGW # CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT - Defined if PluginManager # doesn't support dynamic plugin loading due to platform limitations -# CORRADE_TESTSUITE_TARGET_XCTEST - Defined if TestSuite is targetting Xcode +# CORRADE_TESTSUITE_TARGET_XCTEST - Defined if TestSuite is targeting Xcode # XCTest # CORRADE_UTILITY_USE_ANSI_COLORS - Defined if ANSI escape sequences are used # for colored output with Utility::Debug on Windows diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 1c150aa05..e1fb257b2 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -608,7 +608,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # Dynamic plugins don't have any prefix (e.g. `lib` on Linux), # search with empty prefix and then reset that back so we don't - # accidentaly break something else + # accidentally break something else set(_tmp_prefixes "${CMAKE_FIND_LIBRARY_PREFIXES}") set(CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES};") diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index 4a962d6b5..a277dd9dd 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -65,20 +65,23 @@ build() { check() { cd "$_rootdir/build" - CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_robustness GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest + export CORRADE_TEST_SKIP_BENCHMARKS=ON + export CORRADE_TEST_COLOR=ON + ctest --output-on-failure -j5 + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_robustness GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest # Run all Vulkan tests with SwiftShader as well # Keep in sync with PKGBUILD-coverage, PKGBUILD-release and # package/ci/unix-desktop-vulkan.sh - MAGNUM_DEVICE=cpu CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=cpu ctest --output-on-failure -j5 -R VkTest for device in "" cpu; do - MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest - MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest done } diff --git a/package/archlinux/PKGBUILD-coverage b/package/archlinux/PKGBUILD-coverage index 43d54ff3c..1dafdf4d6 100644 --- a/package/archlinux/PKGBUILD-coverage +++ b/package/archlinux/PKGBUILD-coverage @@ -65,21 +65,24 @@ build() { check() { cd "$_rootdir/build-coverage" - CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 || true + export CORRADE_TEST_SKIP_BENCHMARKS=ON + export CORRADE_TEST_COLOR=ON + ctest --output-on-failure -j5 || true - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_texture_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest || true - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access GL_ARB_robustness GL_ARB_multi_bind" CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest || true - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest || true - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest || true - MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_texture_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access GL_ARB_robustness GL_ARB_multi_bind" ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest || true # Run all Vulkan tests with SwiftShader as well # Keep in sync with PKGBUILD, PKGBUILD-release and # package/ci/unix-desktop-vulkan.sh - MAGNUM_DEVICE=cpu CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest || true + MAGNUM_DEVICE=cpu ctest --output-on-failure -j5 -R VkTest || true for device in "" cpu; do - MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest || true - MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest || true + MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest || true + MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest || true done ./Debug/bin/magnum-al-info > /dev/null diff --git a/package/archlinux/PKGBUILD-release b/package/archlinux/PKGBUILD-release index f3ff212c5..bda38eda3 100644 --- a/package/archlinux/PKGBUILD-release +++ b/package/archlinux/PKGBUILD-release @@ -101,22 +101,25 @@ build() { } check() { + export CORRADE_TEST_SKIP_BENCHMARKS=ON + export CORRADE_TEST_COLOR=ON for i in build build-release; do cd "$_rootdir/$i" - CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_robustness GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest - MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest + ctest --output-on-failure -j5 + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_invalidate_subdata GL_ARB_multi_bind GL_ARB_robustness GL_ARB_separate_shader_objects GL_ARB_texture_storage GL_ARB_texture_storage_multisample GL_ARB_shading_language_420pack GL_ARB_explicit_uniform_location GL_ARB_explicit_attrib_location GL_ARB_texture_filter_anisotropic" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest # Run all Vulkan tests with SwiftShader as well # Keep in sync with PKGBUILD, PKGBUILD-coverage and # package/ci/unix-desktop-vulkan.sh - MAGNUM_DEVICE=cpu CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=cpu ctest --output-on-failure -j5 -R VkTest for device in "" cpu; do - MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest - MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 CORRADE_TEST_SKIP_BENCHMARKS=ON CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=$device MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest + MAGNUM_DEVICE=$device MAGNUM_DISABLE_EXTENSIONS="VK_KHR_get_physical_device_properties2 VK_KHR_get_memory_requirements2 VK_KHR_bind_memory2 VK_KHR_create_renderpass2 VK_KHR_copy_commands2 VK_KHR_maintenance1 VK_KHR_multiview VK_KHR_maintenance2" MAGNUM_VULKAN_VERSION=1.0 ctest --output-on-failure -j5 -R VkTest done done } diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 904df3606..15ab951c8 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -58,7 +58,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E GLTest || exit /b +ctest -V -E "GLTest|GLBenchmark" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/appveyor-desktop-mingw.bat b/package/ci/appveyor-desktop-mingw.bat index 59d3a1edd..33811414f 100644 --- a/package/ci/appveyor-desktop-mingw.bat +++ b/package/ci/appveyor-desktop-mingw.bat @@ -60,7 +60,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E "(GL|Vk)Test" || exit /b +ctest -V -E "GLTest|GLBenchmark|VkTest" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/appveyor-desktop.bat b/package/ci/appveyor-desktop.bat index 260f23b13..9972e9cdf 100644 --- a/package/ci/appveyor-desktop.bat +++ b/package/ci/appveyor-desktop.bat @@ -69,7 +69,7 @@ cmake --build . || exit /b rem Test set CORRADE_TEST_COLOR=ON -ctest -V -E "(GL|Vk)Test" || exit /b +ctest -V -E "GLTest|GLBenchmark|VkTest" || exit /b rem Test install, after running the tests as for them it shouldn't be needed cmake --build . --target install || exit /b diff --git a/package/ci/appveyor-lcov.sh b/package/ci/appveyor-lcov.sh index 1ffaac39c..be9bca756 100644 --- a/package/ci/appveyor-lcov.sh +++ b/package/ci/appveyor-lcov.sh @@ -9,7 +9,10 @@ curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.ta pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig pacman -U --noconfirm msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz -pacman -Sy --noconfirm mingw-w64-x86_64-perl +# Newer packages use zstd, but this old pacman has no idea what that is. +# Download the last perl that's still compressed with xz. +curl -O http://repo.msys2.org/msys/x86_64/perl-5.30.2-1-x86_64.pkg.tar.xz +pacman -U --noconfirm perl-5.30.2-1-x86_64.pkg.tar.xz # mingw lcov package is empty, so download and use it manually # https://github.com/appveyor/ci/issues/1628 diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index a07acb662..0a7e6b3ae 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -16,9 +16,12 @@ executors: ubuntu-18_04: docker: - image: ubuntu:bionic-20200921 - xcode-10_3: + xcode-11_2: + # Molten-vk isn't in (non-updated) Homebrew on the 9.4 or 10.0/1/2 image, + # have to use 10.3 instead; since 2021-06-08 it refuses to work on 10.14 so + # have to use 11.2 at least macos: - xcode: 10.3.0 + xcode: 11.2.1 xcode-11_6: macos: xcode: 11.6.0 @@ -391,9 +394,7 @@ jobs: script: unix-desktop.sh macos-gl: - # Molten-vk isn't in (non-updated) Homebrew on the 9.4 or 10.0/1/2 image, - # have to use 10.3 instead - executor: xcode-10_3 + executor: xcode-11_2 environment: CMAKE_CXX_FLAGS: --coverage CONFIGURATION: Debug @@ -425,8 +426,9 @@ jobs: macos-static: # Molten-vk isn't in (non-updated) Homebrew on the 9.4 or 10.0/1/2 image, - # have to use 10.3 instead - executor: xcode-10_3 + # have to use 10.3 instead; since 2021-06-08 it refuses to work on 10.14 so + # have to use 11.2 at least + executor: xcode-11_2 environment: # STUPID yml interprets unquoted ON as a boolean BUILD_STATIC: "ON" diff --git a/package/ci/emscripten.sh b/package/ci/emscripten.sh index 25a9df853..66bc6a8f8 100755 --- a/package/ci/emscripten.sh +++ b/package/ci/emscripten.sh @@ -73,7 +73,7 @@ cmake .. \ ninja # Test -CORRADE_TEST_COLOR=ON ctest -V -E "(GL|AL)Test" +CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark|ALTest" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/package/ci/travis-android-gles.sh b/package/ci/travis-android-gles.sh index 9d9e062bf..4b4521e02 100755 --- a/package/ci/travis-android-gles.sh +++ b/package/ci/travis-android-gles.sh @@ -83,7 +83,7 @@ ninja -j4 echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a emulator -avd test -no-audio -no-window & android-wait-for-emulator -CORRADE_TEST_COLOR=ON ctest -V -E GLTest +CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/package/ci/travis-ios-simulator.sh b/package/ci/travis-ios-simulator.sh index b887aecf4..896fe5225 100755 --- a/package/ci/travis-ios-simulator.sh +++ b/package/ci/travis-ios-simulator.sh @@ -70,7 +70,7 @@ set -o pipefail && cmake --build . --config Release | xcpretty # TODO: find a better way to avoid # Library not loaded: /System/Library/Frameworks/OpenGLES.framework/OpenGLES # error -DYLD_FALLBACK_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/OpenGLES.framework/ DYLD_FALLBACK_FRAMEWORK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks CORRADE_TEST_COLOR=ON ctest -V -C Release -E GLTest +DYLD_FALLBACK_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/OpenGLES.framework/ DYLD_FALLBACK_FRAMEWORK_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks CORRADE_TEST_COLOR=ON ctest -V -C Release -E "GLTest|GLBenchmark" # Test install, after running the tests as for them it shouldn't be needed set -o pipefail && cmake --build . --config Release --target install | xcpretty diff --git a/package/ci/unix-desktop-gles.sh b/package/ci/unix-desktop-gles.sh index 80a153493..f709f19b1 100755 --- a/package/ci/unix-desktop-gles.sh +++ b/package/ci/unix-desktop-gles.sh @@ -56,6 +56,12 @@ cmake .. \ -DBUILD_GL_TESTS=ON \ -G Ninja ninja $NINJA_JOBS + +# Don't run any benchmarks. SwiftShader doesn't support +# EXT_disjoint_timer_query anyway and the CPU-side things are run in the usual +# desktop build already. +export CORRADE_TEST_SKIP_BENCHMARKS=ON + CORRADE_TEST_COLOR=ON ctest -V MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index b3dfb30ad..84964def9 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -67,7 +67,7 @@ cmake .. \ -DBUILD_PLUGINS_STATIC=$BUILD_STATIC \ -G Ninja ninja $NINJA_JOBS -ASAN_OPTIONS="color=always" LSAN_OPTIONS="color=always suppressions=$(pwd)/../package/ci/leaksanitizer.conf" TSAN_OPTIONS="color=always" CORRADE_TEST_COLOR=ON ctest -V -E "(GL|Vk)Test" +ASAN_OPTIONS="color=always" LSAN_OPTIONS="color=always suppressions=$(pwd)/../package/ci/leaksanitizer.conf" TSAN_OPTIONS="color=always" CORRADE_TEST_COLOR=ON ctest -V -E "GLTest|GLBenchmark|VkTest" # Test install, after running the tests as for them it shouldn't be needed ninja install diff --git a/src/Magnum/Animation/Test/PlayerTest.cpp b/src/Magnum/Animation/Test/PlayerTest.cpp index 2d8c1eba4..10199165b 100644 --- a/src/Magnum/Animation/Test/PlayerTest.cpp +++ b/src/Magnum/Animation/Test/PlayerTest.cpp @@ -120,7 +120,7 @@ const struct { initializer (not when there's just a struct and also not when the struct is only a chrono member itself). Keeping at least one instance here so I can monitor when this gets fixed. */ - #if !defined(CORRADE_MSVC2017_COMPATIBILITY) || defined(CORRADE_MSVC2015_COMPATIBLITY) + #if !defined(CORRADE_MSVC2017_COMPATIBILITY) || defined(CORRADE_MSVC2015_COMPATIBILITY) std::chrono::hours{100*365*24}, #else std::chrono::minutes{100*365*24*60}, diff --git a/src/Magnum/Animation/Test/TrackTest.cpp b/src/Magnum/Animation/Test/TrackTest.cpp index 5b1bc79d6..a77ed079a 100644 --- a/src/Magnum/Animation/Test/TrackTest.cpp +++ b/src/Magnum/Animation/Test/TrackTest.cpp @@ -48,6 +48,9 @@ struct TrackTest: TestSuite::Tester { void constructInitializerListInterpolationInterpolator(); void constructInitializerListInterpolationInterpolatorDefaults(); + void constructCopy(); + void constructMove(); + void convertView(); void at(); @@ -106,6 +109,9 @@ TrackTest::TrackTest() { &TrackTest::constructInitializerListInterpolationInterpolator, &TrackTest::constructInitializerListInterpolationInterpolatorDefaults, + &TrackTest::constructCopy, + &TrackTest::constructMove, + &TrackTest::convertView}); addInstancedTests({&TrackTest::at, @@ -389,6 +395,20 @@ void TrackTest::constructInitializerListInterpolationInterpolatorDefaults() { CORRADE_COMPARE(a.values()[0], (Vector3{3.0f, 1.0f, 0.1f})); } +void TrackTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible>::value); + CORRADE_VERIFY(!std::is_copy_assignable>::value); +} + +void TrackTest::constructMove() { + /* The move is defaulted, so verify just the right attributes */ + + CORRADE_VERIFY(std::is_nothrow_move_constructible>::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable>::value); + CORRADE_VERIFY(std::is_nothrow_move_constructible>::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable>::value); +} + void TrackTest::convertView() { Track a{ {{1.0f, {3.0f, 1.0f, 0.1f}}, diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index 25b1ed463..3b2d9d146 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -234,13 +234,13 @@ template&) = delete; /** @brief Move constructor */ - Track(Track&&) = default; + Track(Track&&) noexcept = default; /** @brief Copying is not allowed */ Track& operator=(const Track&) = delete; /** @brief Move constructor */ - Track& operator=(Track&&) = default; + Track& operator=(Track&&) noexcept = default; /** @brief Conversion to a view */ operator TrackView() const noexcept { diff --git a/src/Magnum/Array.h b/src/Magnum/Array.h index cb115ae1d..0eb34dc0b 100644 --- a/src/Magnum/Array.h +++ b/src/Magnum/Array.h @@ -141,9 +141,10 @@ CORRADE_IGNORE_DEPRECATED_PUSH /** @brief One-dimensional array @tparam T Data type -@m_deprecated_since_latest Use @ref Math::Vector instead. +@m_deprecated_since_latest Use @ref Math::Vector or @ref Containers::Array1 + instead. */ -template class CORRADE_DEPRECATED("use Math::Vector instead") Array1D: public Array<1, T> { +template class CORRADE_DEPRECATED("use Math::Vector or Containers::Array1 instead") Array1D: public Array<1, T> { public: /** @copydoc Array::Array() */ constexpr /*implicit*/ Array1D() = default; @@ -164,9 +165,10 @@ template class CORRADE_DEPRECATED("use Math::Vector instead") Array1D: /** @brief Two-dimensional array @tparam T Data type -@m_deprecated_since_latest Use @ref Math::Vector2 instead. +@m_deprecated_since_latest Use @ref Math::Vector2 or @ref Containers::Array2 + instead. */ -template class CORRADE_DEPRECATED("use Math::Vector2 instead") Array2D: public Array<2, T> { +template class CORRADE_DEPRECATED("use Math::Vector2 or Containers::Array2 instead") Array2D: public Array<2, T> { public: /** @copydoc Array::Array() */ constexpr /*implicit*/ Array2D() = default; @@ -198,9 +200,10 @@ template class CORRADE_DEPRECATED("use Math::Vector2 instead") Array2D /** @brief Three-dimensional array @tparam T Data type -@m_deprecated_since_latest Use @ref Math::Vector3 instead. +@m_deprecated_since_latest Use @ref Math::Vector3 or @ref Containers::Array3 + instead. */ -template class CORRADE_DEPRECATED("use Math::Vector3 instead") Array3D: public Array<3, T> { +template class CORRADE_DEPRECATED("use Math::Vector3 or Containers::Array3 instead") Array3D: public Array<3, T> { public: /** @copydoc Array::Array() */ constexpr /*implicit*/ Array3D() {} diff --git a/src/Magnum/Audio/Renderer.h b/src/Magnum/Audio/Renderer.h index a61c88114..3308019cb 100644 --- a/src/Magnum/Audio/Renderer.h +++ b/src/Magnum/Audio/Renderer.h @@ -50,7 +50,7 @@ class Renderer { * @see @ref error() */ enum class Error: ALenum { - NoError = AL_NO_ERROR, /**< No error occured */ + NoError = AL_NO_ERROR, /**< No error occurred */ InvalidName = AL_INVALID_NAME, /**< Invalid name parameter */ InvalidEnum = AL_INVALID_ENUM, /**< Invalid enum parameter */ InvalidValue = AL_INVALID_VALUE, /**< Invalid enum value parameter */ diff --git a/src/Magnum/DebugTools/Test/CMakeLists.txt b/src/Magnum/DebugTools/Test/CMakeLists.txt index b42d79ded..764c463eb 100644 --- a/src/Magnum/DebugTools/Test/CMakeLists.txt +++ b/src/Magnum/DebugTools/Test/CMakeLists.txt @@ -88,6 +88,20 @@ if(WITH_TRADE) if(WITH_TGAIMPORTER) target_link_libraries(DebugToolsCompareImageTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGECONVERTER) + add_dependencies(DebugToolsCompareImageTest AnyImageConverter) + endif() + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(DebugToolsCompareImageTest AnyImageImporter) + endif() + if(WITH_TGAIMAGECONVERTER) + add_dependencies(DebugToolsCompareImageTest TgaImageConverter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(DebugToolsCompareImageTest TgaImporter) + endif() endif() endif() @@ -130,6 +144,20 @@ if(TARGET_GL) if(WITH_TGAIMPORTER) target_link_libraries(DebugToolsScreenshotGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGECONVERTER) + add_dependencies(DebugToolsScreenshotGLTest AnyImageConverter) + endif() + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(DebugToolsScreenshotGLTest AnyImageImporter) + endif() + if(WITH_TGAIMAGECONVERTER) + add_dependencies(DebugToolsScreenshotGLTest TgaImageConverter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(DebugToolsScreenshotGLTest TgaImporter) + endif() endif() if(CORRADE_BUILD_STATIC AND NOT BUILD_PLUGINS_STATIC) @@ -164,6 +192,16 @@ if(TARGET_GL) target_link_libraries(DebugToolsForceRendererGLTest PRIVATE TgaImporter) target_link_libraries(DebugToolsObjectRendererGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(DebugToolsForceRendererGLTest AnyImageImporter) + add_dependencies(DebugToolsObjectRendererGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(DebugToolsForceRendererGLTest TgaImporter) + add_dependencies(DebugToolsObjectRendererGLTest TgaImporter) + endif() endif() endif() endif() diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 05685f68e..06603814a 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -350,11 +350,11 @@ std::pair AbstractShaderProgram::validate() { return {success, std::move(message)}; } -void AbstractShaderProgram::draw(Mesh& mesh) { - CORRADE_ASSERT(mesh._countSet, "GL::AbstractShaderProgram::draw(): Mesh::setCount() was never called, probably a mistake?", ); +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh) { + CORRADE_ASSERT(mesh._countSet, "GL::AbstractShaderProgram::draw(): Mesh::setCount() was never called, probably a mistake?", *this); /* Nothing to draw, exit without touching any state */ - if(!mesh._count || !mesh._instanceCount) return; + if(!mesh._count || !mesh._instanceCount) return *this; use(); @@ -363,13 +363,14 @@ void AbstractShaderProgram::draw(Mesh& mesh) { #else mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset); #endif + return *this; } -void AbstractShaderProgram::draw(MeshView& mesh) { - CORRADE_ASSERT(mesh._countSet, "GL::AbstractShaderProgram::draw(): MeshView::setCount() was never called, probably a mistake?", ); +AbstractShaderProgram& AbstractShaderProgram::draw(MeshView& mesh) { + CORRADE_ASSERT(mesh._countSet, "GL::AbstractShaderProgram::draw(): MeshView::setCount() was never called, probably a mistake?", *this); /* Nothing to draw, exit without touching any state */ - if(!mesh._count || !mesh._instanceCount) return; + if(!mesh._count || !mesh._instanceCount) return *this; use(); @@ -378,17 +379,18 @@ void AbstractShaderProgram::draw(MeshView& mesh) { #else mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset); #endif + return *this; } -void AbstractShaderProgram::draw(Containers::ArrayView> meshes) { - if(meshes.empty()) return; +AbstractShaderProgram& AbstractShaderProgram::draw(Containers::ArrayView> meshes) { + if(meshes.empty()) return *this; use(); #ifndef CORRADE_NO_ASSERT const Mesh* original = &*meshes.front()->_original; for(std::size_t i = 0; i != meshes.size(); ++i) - CORRADE_ASSERT(&*meshes[i]->_original == original, "GL::AbstractShaderProgram::draw(): all meshes must be views of the same original mesh, expected" << original << "but got" << &*meshes[i]->_original << "at index" << i, ); + CORRADE_ASSERT(&*meshes[i]->_original == original, "GL::AbstractShaderProgram::draw(): all meshes must be views of the same original mesh, expected" << original << "but got" << &*meshes[i]->_original << "at index" << i, *this); #endif #ifndef MAGNUM_TARGET_GLES @@ -396,34 +398,38 @@ void AbstractShaderProgram::draw(Containers::ArrayView> meshes) { - draw(Containers::arrayView(meshes)); +AbstractShaderProgram& AbstractShaderProgram::draw(std::initializer_list> meshes) { + return draw(Containers::arrayView(meshes)); } #ifndef MAGNUM_TARGET_GLES -void AbstractShaderProgram::drawTransformFeedback(Mesh& mesh, TransformFeedback& xfb, UnsignedInt stream) { +AbstractShaderProgram& AbstractShaderProgram::drawTransformFeedback(Mesh& mesh, TransformFeedback& xfb, UnsignedInt stream) { /* Nothing to draw, exit without touching any state */ - if(!mesh._instanceCount) return; + if(!mesh._instanceCount) return *this; use(); mesh.drawInternal(xfb, stream, mesh._instanceCount); + return *this; } -void AbstractShaderProgram::drawTransformFeedback(MeshView& mesh, TransformFeedback& xfb, UnsignedInt stream) { - /* Nothing to draw, exit without touching any state */ - if(!mesh._instanceCount) return; +AbstractShaderProgram& AbstractShaderProgram::drawTransformFeedback(MeshView& mesh, TransformFeedback& xfb, UnsignedInt stream) { + /* If nothing to draw, exit without touching any state */ + if(mesh._instanceCount) return *this; use(); mesh._original->drawInternal(xfb, stream, mesh._instanceCount); + return *this; } #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void AbstractShaderProgram::dispatchCompute(const Vector3ui& workgroupCount) { +AbstractShaderProgram& AbstractShaderProgram::dispatchCompute(const Vector3ui& workgroupCount) { use(); glDispatchCompute(workgroupCount.x(), workgroupCount.y(), workgroupCount.z()); + return *this; } #endif diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index fe54fae93..ab23a5156 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -87,12 +87,14 @@ functions and properties: @snippet MagnumGL.cpp AbstractShaderProgram-xfb -
  • And optionally, **hiding irrelevant draw/dispatch functions** to prevent - users from accidentally calling @ref draw() on compute shaders, - @ref drawTransformFeedback() on shaders that don't have transform feedback - or @ref dispatchCompute() on shaders that aren't compute. For example: - - @snippet MagnumGL.cpp AbstractShaderProgram-hide-irrelevant +
  • And optionally, **return derived type from relevant draw/dispatch functions** + to make it possible for users to easily chain draw calls, and on the other + hand **hide the irrelevant APIs** to prevent users from accidentally + calling @ref draw() on compute shaders, @ref drawTransformFeedback() on + shaders that don't have transform feedback or @ref dispatchCompute() on + shaders that aren't compute. For example: + + @snippet MagnumGL.cpp AbstractShaderProgram-return-hide-irrelevant @subsection GL-AbstractShaderProgram-attribute-location Binding attribute and fragment data location @@ -109,7 +111,7 @@ layout(location = 1) in vec3 normal; layout(location = 2) in vec2 textureCoordinates; @endcode -Similarly for ouput attributes, you can also specify blend equation color index +Similarly for output attributes, you can also specify blend equation color index for them (see @ref Renderer::BlendFunction for more information about using color input index): @@ -584,7 +586,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { static Int maxCombinedShaderOutputResources(); /** - * @brief Max supported shader storage block size + * @brief Max supported shader storage block size in bytes * * The result is cached, repeated queries don't result in repeated * OpenGL calls. If neither extension @gl_extension{ARB,shader_storage_buffer_object} @@ -598,7 +600,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { #endif /** - * @brief Max supported uniform block size + * @brief Max supported uniform block size in bytes * * The result is cached, repeated queries don't result in repeated * OpenGL calls. If extension @gl_extension{ARB,uniform_buffer_object} @@ -751,7 +753,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { /** * @brief Draw a mesh - * @param mesh Mesh to draw + * @return Reference to self (for method chaining) * @m_since{2020,06} * * Expects that @p mesh is compatible with this shader and is fully set @@ -794,16 +796,19 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @requires_gl Specifying base vertex for indexed meshes is not * available in OpenGL ES or WebGL. */ - void draw(Mesh& mesh); + AbstractShaderProgram& draw(Mesh& mesh); /** * @overload * @m_since{2020,06} */ - void draw(Mesh&& mesh) { draw(mesh); } + AbstractShaderProgram& draw(Mesh&& mesh) { + return draw(mesh); + } /** * @brief Draw a mesh view + * @return Reference to self (for method chaining) * @m_since{2020,06} * * See @ref draw(Mesh&) for more information. @@ -828,16 +833,19 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @requires_gl Specifying base vertex for indexed meshes is not * available in OpenGL ES or WebGL. */ - void draw(MeshView& mesh); + AbstractShaderProgram& draw(MeshView& mesh); /** * @overload * @m_since{2020,06} */ - void draw(MeshView&& mesh) { draw(mesh); } + AbstractShaderProgram& draw(MeshView&& mesh) { + return draw(mesh); + } /** * @brief Draw multiple meshes at once + * @return Reference to self (for method chaining) * @m_since{2020,06} * * On OpenGL ES, if neither @gl_extension{EXT,multi_draw_arrays} nor @@ -872,13 +880,13 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * if the mesh is indexed and @ref MeshView::baseVertex() is not * `0` */ - void draw(Containers::ArrayView> meshes); + AbstractShaderProgram& draw(Containers::ArrayView> meshes); /** * @overload * @m_since{2020,06} */ - void draw(std::initializer_list> meshes); + AbstractShaderProgram& draw(std::initializer_list> meshes); #ifndef MAGNUM_TARGET_GLES /** @@ -886,6 +894,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @param mesh Mesh to draw * @param xfb Transform feedback to use for vertex count * @param stream Transform feedback stream ID + * @return Reference to self (for method chaining) * @m_since{2020,06} * * Expects that @p mesh is compatible with this shader, is fully set up @@ -911,10 +920,11 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @requires_gl42 Extension @gl_extension{ARB,transform_feedback_instanced} * if @ref Mesh::instanceCount() is more than `1` */ - void drawTransformFeedback(Mesh& mesh, TransformFeedback& xfb, UnsignedInt stream = 0); + AbstractShaderProgram& drawTransformFeedback(Mesh& mesh, TransformFeedback& xfb, UnsignedInt stream = 0); /** * @brief Draw a mesh view with vertices coming out of transform feedback + * @return Reference to self (for method chaining) * @m_since{2020,06} * * Everything set by @ref MeshView::setCount(), @@ -931,13 +941,14 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @requires_gl42 Extension @gl_extension{ARB,transform_feedback_instanced} * if @ref MeshView::instanceCount() is more than `1` */ - void drawTransformFeedback(MeshView& mesh, TransformFeedback& xfb, UnsignedInt stream = 0); + AbstractShaderProgram& drawTransformFeedback(MeshView& mesh, TransformFeedback& xfb, UnsignedInt stream = 0); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** * @brief Dispatch compute * @param workgroupCount Workgroup count in given dimension + * @return Reference to self (for method chaining) * * Valid only on programs with compute shader attached. * @see @fn_gl{DispatchCompute} @@ -946,7 +957,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * and older. * @requires_gles Compute shaders are not available in WebGL. */ - void dispatchCompute(const Vector3ui& workgroupCount); + AbstractShaderProgram& dispatchCompute(const Vector3ui& workgroupCount); #endif protected: diff --git a/src/Magnum/GL/Attribute.h b/src/Magnum/GL/Attribute.h index 8a6ae43fe..1b27fc848 100644 --- a/src/Magnum/GL/Attribute.h +++ b/src/Magnum/GL/Attribute.h @@ -360,7 +360,7 @@ template class Attribute { * * Used for describing matrix attributes. Implicitly the same as size * of given vector type (e.g. @cpp 9 @ce for a - * @ref Magnum::Matrix3 "Matrix3"), but can be overriden for example to + * @ref Magnum::Matrix3 "Matrix3"), but can be overridden for example to * ensure four-byte column alignment with 1- and 2-byte data types. * @see @ref Vectors */ diff --git a/src/Magnum/GL/Buffer.h b/src/Magnum/GL/Buffer.h index 041099796..1e9baaba4 100644 --- a/src/Magnum/GL/Buffer.h +++ b/src/Magnum/GL/Buffer.h @@ -477,7 +477,7 @@ class MAGNUM_GL_EXPORT Buffer: public AbstractObject { /** * Only one or more discrete subranges of the mapping will be * modified. See @ref flushMappedRange() for more information. May - * only be used in conjuction with @ref MapFlag::Write. + * only be used in conjunction with @ref MapFlag::Write. */ #ifndef MAGNUM_TARGET_GLES2 FlushExplicit = GL_MAP_FLUSH_EXPLICIT_BIT, diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 935e8c524..6933702e8 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -176,7 +176,7 @@ add_library(MagnumGLObjects OBJECT ${MagnumGL_HEADERS} ${MagnumGL_PRIVATE_HEADERS}) # We can use both implicit include path (GLES2/gl2.h) where our headers can -# be overriden with system ones or explicit (MagnumExternal/OpenGL/GLES2/gl2ext.h) +# be overridden with system ones or explicit (MagnumExternal/OpenGL/GLES2/gl2ext.h) # where only our headers will be used target_include_directories(MagnumGLObjects PUBLIC $) diff --git a/src/Magnum/GL/Context.cpp b/src/Magnum/GL/Context.cpp index 53d9463bf..fdeafb5b8 100644 --- a/src/Magnum/GL/Context.cpp +++ b/src/Magnum/GL/Context.cpp @@ -719,7 +719,7 @@ Context::Context(NoCreateT, Utility::Arguments& args, Int argc, const char** arg bother with String allocations. */ const Containers::StringView disabledWorkarounds = args.value("disable-workarounds"); if(!disabledWorkarounds.isEmpty()) { - const Containers::Array split = disabledWorkarounds.splitWithoutEmptyParts(); + const Containers::Array split = disabledWorkarounds.splitOnWhitespaceWithoutEmptyParts(); arrayReserve(_driverWorkarounds, split.size()); for(const Containers::StringView workaround: split) disableDriverWorkaround(workaround); @@ -730,7 +730,7 @@ Context::Context(NoCreateT, Utility::Arguments& args, Int argc, const char** arg and another binary search in tryCreate(). */ const Containers::StringView disabledExtensions = args.value("disable-extensions"); if(!disabledExtensions.isEmpty()) { - const Containers::Array split = disabledExtensions.splitWithoutEmptyParts(); + const Containers::Array split = disabledExtensions.splitOnWhitespaceWithoutEmptyParts(); arrayReserve(_disabledExtensions, split.size()); for(const Containers::StringView extension: split) { if(const Extension* found = findExtension(extension)) { @@ -751,7 +751,7 @@ Context::Context(Context&& other) noexcept: #ifdef MAGNUM_BUILD_DEPRECATED _supportedExtensions{std::move(other._supportedExtensions)}, #endif - _state{std::move(other._state)}, + _state{other._state}, _detectedDrivers{std::move(other._detectedDrivers)}, _driverWorkarounds{std::move(other._driverWorkarounds)}, _disabledExtensions{std::move(other._disabledExtensions)}, @@ -1074,7 +1074,7 @@ Containers::Array Context::extensionStrings() const { #ifndef MAGNUM_TARGET_GLES3 /* OpenGL 2.1 / OpenGL ES 2.0 doesn't have glGetStringi() */ - return Containers::StringView{reinterpret_cast(glGetString(GL_EXTENSIONS)), Containers::StringViewFlag::Global}.splitWithoutEmptyParts(); + return Containers::StringView{reinterpret_cast(glGetString(GL_EXTENSIONS)), Containers::StringViewFlag::Global}.splitOnWhitespaceWithoutEmptyParts(); #endif } diff --git a/src/Magnum/GL/Extensions.h b/src/Magnum/GL/Extensions.h index 92db6ce13..321dd9b0b 100644 --- a/src/Magnum/GL/Extensions.h +++ b/src/Magnum/GL/Extensions.h @@ -35,7 +35,7 @@ namespace Magnum { namespace GL { /* Standard Android build system thinks that it's okay to define unmangled unprefixed macros. I think that whoever did that needs to be punished, - becuase I am then not able to use that identifier for extension names. + because I am then not able to use that identifier for extension names. Use CORRADE_TARGET_ANDROID here instead. */ #ifdef ANDROID #undef ANDROID diff --git a/src/Magnum/GL/Implementation/driverSpecific.cpp b/src/Magnum/GL/Implementation/driverSpecific.cpp index 6f999a175..27b899907 100644 --- a/src/Magnum/GL/Implementation/driverSpecific.cpp +++ b/src/Magnum/GL/Implementation/driverSpecific.cpp @@ -415,7 +415,7 @@ auto Context::detectedDriver() -> DetectedDrivers { _detectedDrivers = DetectedDrivers{}; - #ifndef MAGNUM_TARGET_WEBGL + #if !defined(MAGNUM_TARGET_WEBGL) && !defined(CORRADE_TARGET_APPLE) const Containers::StringView renderer = rendererString(); #endif #if !defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_WEBGL) diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index ce5f329f5..7b77a0e4e 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -275,7 +275,7 @@ this: @snippet MagnumGL.cpp Mesh-dynamic -@section GL-Mesh-buffer-ownership Transfering buffer ownership +@section GL-Mesh-buffer-ownership Transferring buffer ownership If a vertex/index buffer is used only by a single mesh, it's possible to transfer its ownership to the mesh itself to simplify resource management on @@ -298,7 +298,7 @@ getting only a moved-out instance. For example: Basic workflow is: bind specific framebuffer for drawing (if needed), set up respective shader (see @ref GL-AbstractShaderProgram-rendering-workflow "AbstractShaderProgram documentation" -for more infromation) and call @ref AbstractShaderProgram::draw(). +for more information) and call @ref AbstractShaderProgram::draw(). @section GL-Mesh-webgl-restrictions WebGL restrictions diff --git a/src/Magnum/GL/MeshView.cpp b/src/Magnum/GL/MeshView.cpp index 19c4fffc0..73717072f 100644 --- a/src/Magnum/GL/MeshView.cpp +++ b/src/Magnum/GL/MeshView.cpp @@ -174,8 +174,8 @@ void MeshView::multiDrawElementsBaseVertexImplementationANGLE(const GLenum mode, Containers::ArrayView instanceCount; Containers::ArrayView baseInstance; Containers::ArrayTuple data{ - {Containers::NoInit, std::size_t(drawCount), instanceCount}, - {Containers::ValueInit, std::size_t(drawCount), baseInstance}, + {NoInit, std::size_t(drawCount), instanceCount}, + {ValueInit, std::size_t(drawCount), baseInstance}, }; for(GLsizei& i: instanceCount) i = 1; diff --git a/src/Magnum/GL/RenderbufferFormat.h b/src/Magnum/GL/RenderbufferFormat.h index 0d037d79c..0a0a3182a 100644 --- a/src/Magnum/GL/RenderbufferFormat.h +++ b/src/Magnum/GL/RenderbufferFormat.h @@ -114,7 +114,7 @@ enum class RenderbufferFormat: GLenum { * RGBA, each component normalized unsigned byte. * @requires_gles30 Extension @gl_extension{ARM,rgba8} or @gl_extension{OES,rgb8_rgba8} * in OpenGL ES 2.0. - * @requires_webgl20 Not availabe in WebGL 1.0, use for example + * @requires_webgl20 Not available in WebGL 1.0, use for example * @ref RenderbufferFormat::RGB565 or @ref RenderbufferFormat::RGBA4 * instead. */ diff --git a/src/Magnum/GL/Renderer.h b/src/Magnum/GL/Renderer.h index ec7433992..8f1a71fe8 100644 --- a/src/Magnum/GL/Renderer.h +++ b/src/Magnum/GL/Renderer.h @@ -2114,7 +2114,7 @@ class MAGNUM_GL_EXPORT Renderer { * WebGL. */ enum class GraphicsResetStatus: GLenum { - /** No reset occured since last call. */ + /** No reset occurred since last call. */ NoError = GL_NO_ERROR, /** diff --git a/src/Magnum/GL/Test/CMakeLists.txt b/src/Magnum/GL/Test/CMakeLists.txt index 37aac840a..60e0de0b2 100644 --- a/src/Magnum/GL/Test/CMakeLists.txt +++ b/src/Magnum/GL/Test/CMakeLists.txt @@ -168,6 +168,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(GLRendererGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(GLRendererGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(GLRendererGLTest TgaImporter) + endif() endif() corrade_add_test(GLShaderGLTest ShaderGLTest.cpp diff --git a/src/Magnum/GL/Test/ContextTest.cpp b/src/Magnum/GL/Test/ContextTest.cpp index afcba1fff..cbdd1c4f3 100644 --- a/src/Magnum/GL/Test/ContextTest.cpp +++ b/src/Magnum/GL/Test/ContextTest.cpp @@ -101,7 +101,7 @@ void ContextTest::isExtension() { } /* Variadic check (used in variadic Configuration::addDisabledExtensions()), - check that it properly fails for each occurence of a non-extension */ + check that it properly fails for each occurrence of a non-extension */ #ifndef MAGNUM_TARGET_WEBGL CORRADE_VERIFY(Implementation::IsExtension< Extensions::KHR::debug, diff --git a/src/Magnum/Implementation/converterUtilities.h b/src/Magnum/Implementation/converterUtilities.h index 906e8ac63..b86090fb1 100644 --- a/src/Magnum/Implementation/converterUtilities.h +++ b/src/Magnum/Implementation/converterUtilities.h @@ -38,7 +38,7 @@ namespace Magnum { namespace Implementation { /* Used only in executables where we don't want it to be exported */ namespace { -void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& options) { +void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& anyPluginName, const std::string& options) { for(const std::string& option: Utility::String::splitWithoutEmptyParts(options, ',')) { auto keyValue = Utility::String::partition(option, '='); Utility::String::trimInPlace(keyValue[0]); @@ -60,8 +60,12 @@ void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& option /* Provide a warning message in case the plugin doesn't define given option in its default config. The plugin is not *required* to have those tho (could be backward compatibility entries, for example), so - not an error. */ - if(groupNotRecognized || !group->hasValue(keyParts.back())) { + not an error. + + If it's an Any* plugin, then this check is provided by it directly, + and since the Any* plugin obviously don't expose the options of the concrete plugins, this warning would fire for them always, which + wouldn't help anything. */ + if((groupNotRecognized || !group->hasValue(keyParts.back())) && plugin.plugin() != anyPluginName) { Warning{} << "Option" << keyValue[0] << "not recognized by" << plugin.plugin(); } diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 2d39b24aa..82fc79e15 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -1171,9 +1171,9 @@ typedef Math::Frustum Frustumd; #ifndef DOXYGEN_GENERATING_OUTPUT #ifdef MAGNUM_BUILD_DEPRECATED template class CORRADE_DEPRECATED("use Math::Vector instead") Array; -template class CORRADE_DEPRECATED("use Math::Vector instead") Array1D; -template class CORRADE_DEPRECATED("use Math::Vector2 instead") Array2D; -template class CORRADE_DEPRECATED("use Math::Vector3 instead") Array3D; +template class CORRADE_DEPRECATED("use Math::Vector or Containers::Array1 instead") Array1D; +template class CORRADE_DEPRECATED("use Math::Vector2 or Containers::Array2 instead") Array2D; +template class CORRADE_DEPRECATED("use Math::Vector3 or Containers::Array3 instead") Array3D; #endif enum class InputFileCallbackPolicy: UnsignedByte; diff --git a/src/Magnum/Math/DualComplex.h b/src/Magnum/Math/DualComplex.h index 8e32b917e..e17daa110 100644 --- a/src/Magnum/Math/DualComplex.h +++ b/src/Magnum/Math/DualComplex.h @@ -108,7 +108,7 @@ template class DualComplex: public Dual> { } /** - * @brief Create dual complext from rotation complex and translation vector + * @brief Create dual complex from rotation complex and translation vector * @m_since_latest * * @f[ @@ -242,7 +242,7 @@ template class DualComplex: public Dual> { } /** - * @brief Multipy with dual complex number + * @brief Multiply with dual complex number * * @f[ * \hat a \hat b = a_0 b_0 + \epsilon (a_0 b_\epsilon + a_\epsilon) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp index fdb287db3..9e5fe1d84 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.cpp +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -73,7 +73,7 @@ std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D table{ @@ -113,7 +113,7 @@ std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D table{ diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 97cb61b0f..6d7f421ef 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -133,5 +133,13 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(MeshToolsCompileGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(MeshToolsCompileGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(MeshToolsCompileGLTest TgaImporter) + endif() endif() endif() diff --git a/src/Magnum/MeshTools/Tipsify.cpp b/src/Magnum/MeshTools/Tipsify.cpp index d27e87194..556de89a6 100644 --- a/src/Magnum/MeshTools/Tipsify.cpp +++ b/src/Magnum/MeshTools/Tipsify.cpp @@ -39,7 +39,7 @@ template void tipsifyInPlaceImplementation(const Containers::StridedArr Containers::Array liveTriangleCount, neighborOffset, neighbors; Implementation::buildAdjacency(indices, vertexCount, liveTriangleCount, neighborOffset, neighbors); - /* Global time, per-vertex caching timestamps, per-triangle emmited flag */ + /* Global time, per-vertex caching timestamps, per-triangle emitted flag */ UnsignedInt time = cacheSize+1; Containers::Array timestamp{vertexCount}; /** @todo Have some bitset/staticbitset class for this */ diff --git a/src/Magnum/MeshTools/Tipsify.h b/src/Magnum/MeshTools/Tipsify.h index 8e589dc8c..bfb3a93fe 100644 --- a/src/Magnum/MeshTools/Tipsify.h +++ b/src/Magnum/MeshTools/Tipsify.h @@ -53,7 +53,7 @@ Optimizes the mesh for vertex-bound applications by rearranging its index array for beter usage of post-transform vertex cache. Algorithm used: * *Pedro V. Sander, Diego Nehab, and Joshua Barczak --- Fast Triangle Reordering for Vertex Locality and Reduced Overdraw, SIGGRAPH 2007, -http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/index.php*. +https://gfx.cs.princeton.edu/pubs/Sander_2007_%3eTR/tipsy.pdf*. @todo Ability to compute vertex count automatically */ MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); diff --git a/src/Magnum/MeshTools/sceneconverter.cpp b/src/Magnum/MeshTools/sceneconverter.cpp index be9639758..e7c814b0c 100644 --- a/src/Magnum/MeshTools/sceneconverter.cpp +++ b/src/Magnum/MeshTools/sceneconverter.cpp @@ -263,7 +263,7 @@ used.)") /* Set options, if passed */ if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); - Implementation::setOptions(*importer, args.value("importer-options")); + Implementation::setOptions(*importer, "AnySceneImporter", args.value("importer-options")); std::chrono::high_resolution_clock::duration importTime; @@ -823,7 +823,7 @@ used.)") /* Set options, if passed */ if(args.isSet("verbose")) converter->addFlags(Trade::SceneConverterFlag::Verbose); if(i < args.arrayValueCount("converter-options")) - Implementation::setOptions(*converter, args.arrayValue("converter-options", i)); + Implementation::setOptions(*converter, "AnySceneConverter", args.arrayValue("converter-options", i)); /* This is the last --converter (or the implicit AnySceneConverter at the end), output to a file and exit the loop */ diff --git a/src/Magnum/Platform/AndroidApplication.h b/src/Magnum/Platform/AndroidApplication.h index cb663ce70..92a7b0767 100644 --- a/src/Magnum/Platform/AndroidApplication.h +++ b/src/Magnum/Platform/AndroidApplication.h @@ -766,7 +766,7 @@ class AndroidApplication::MouseEvent: public InputEvent { /** * Left mouse button. Note that this button is not set if only - * touch or stylus event occured. + * touch or stylus event occurred. * @attention Available since Android 4.0 (API level 14), not * detectable in earlier versions. */ @@ -851,7 +851,7 @@ class AndroidApplication::MouseMoveEvent: public InputEvent { enum class Button: std::int32_t { /** * Left mouse button. Note that this button is not set if only - * touch or stylus event occured. + * touch or stylus event occurred. * @attention Available since Android 4.0 (API level 14), not * detectable in earlier versions. */ diff --git a/src/Magnum/Platform/EmscriptenApplication.cpp b/src/Magnum/Platform/EmscriptenApplication.cpp index 5e3e018ce..500829dc8 100644 --- a/src/Magnum/Platform/EmscriptenApplication.cpp +++ b/src/Magnum/Platform/EmscriptenApplication.cpp @@ -284,7 +284,7 @@ void EmscriptenApplication::create(const Configuration& configuration, const GLC Vector2 EmscriptenApplication::dpiScaling(const Configuration& configuration) const { std::ostream* verbose = _verboseLog ? Debug::output() : nullptr; - /* Use values from the configuration only if not overriden on command line. + /* Use values from the configuration only if not overridden on command line. In any case explicit scaling has a precedence before the policy. */ if(!_commandLineDpiScaling.isZero()) { Debug{verbose} << "Platform::EmscriptenApplication: user-defined DPI scaling" << _commandLineDpiScaling; @@ -824,7 +824,7 @@ void EmscriptenApplication::redraw() { is possible), and no amount of \\\\$$$ helps avoiding that xylophone); but doing so means we forever hardcode what functions are exported and thus whatever extra Emscripten needs - to export will be overriden by this, causing only pain and + to export will be overridden by this, causing only pain and misery. So instead we rely on the implementation details of dynCall, diff --git a/src/Magnum/Platform/EmscriptenApplication.h b/src/Magnum/Platform/EmscriptenApplication.h index 949355f56..aa95ec83a 100644 --- a/src/Magnum/Platform/EmscriptenApplication.h +++ b/src/Magnum/Platform/EmscriptenApplication.h @@ -234,7 +234,7 @@ for a guide covering all platform differences. For this application in particular, @ref windowSize() can be different than @ref framebufferSize() on HiDPI displays --- which is different from @ref Sdl2Application behavior on Emscripten. By default, @ref dpiScaling() is -@cpp 1.0f @ce in both dimensions but it can be overriden using custom DPI +@cpp 1.0f @ce in both dimensions but it can be overridden using custom DPI scaling --- the `--magnum-dpi-scaling` command-line options are supported the same way as in @ref Sdl2Application, only in the form of URL GET parameters, similarly to all other @ref platforms-html5-environment "command-line options". diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 70da60d79..0f0d4574c 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -147,7 +147,7 @@ Vector2 GlfwApplication::dpiScaling(const Configuration& configuration) { /** @todo */ #endif - /* Use values from the configuration only if not overriden on command line + /* Use values from the configuration only if not overridden on command line to something non-default. In any case explicit scaling has a precedence before the policy. */ Implementation::GlfwDpiScalingPolicy dpiScalingPolicy{}; diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index c8f45615b..e5941d99d 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -116,6 +116,10 @@ the first part and point `CMAKE_PREFIX_PATH` to its installation dir if necessary. @code{.cmake} +set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) +# These two will be off-by-default when GLFW 3.4 gets released +set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) add_subdirectory(glfw EXCLUDE_FROM_ALL) set(WITH_GLFWAPPLICATION ON CACHE BOOL "" FORCE) @@ -1120,7 +1124,7 @@ class GlfwApplication::Configuration { * @brief DPI scaling policy * * DPI scaling policy when requesting a particular window size. Can - * be overriden on command-line using `--magnum-dpi-scaling` or via + * be overridden on command-line using `--magnum-dpi-scaling` or via * the `MAGNUM_DPI_SCALING` environment variable. * @see @ref setSize(), @ref Platform-Sdl2Application-dpi */ diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 8a536e9e7..4effb5ee8 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -207,7 +207,7 @@ Vector2 Sdl2Application::dpiScaling(const Configuration& configuration) { /* Handled below, warning printed only when using virtual DPI scaling */ #endif - /* Use values from the configuration only if not overriden on command line + /* Use values from the configuration only if not overridden on command line to something non-default. In any case explicit scaling has a precedence before the policy. */ Implementation::Sdl2DpiScalingPolicy dpiScalingPolicy{}; @@ -887,7 +887,7 @@ bool Sdl2Application::mainLoopIteration() { switch(event.type) { case SDL_WINDOWEVENT: switch(event.window.event) { - /* Not using SDL_WINDOWEVENT_RESIZED, because that doens't + /* Not using SDL_WINDOWEVENT_RESIZED, because that doesn't get fired when the window is resized programmatically (such as through setMaxWindowSize()) */ case SDL_WINDOWEVENT_SIZE_CHANGED: { diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index f0561bbdd..b4fdbe514 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -419,25 +419,25 @@ The default is depending on the platform: @ref windowSize() and @ref framebufferSize() will differ depending on whether `NSHighResolutionCapable` is enabled in the `*.plist` file or not. By default, @ref dpiScaling() is @cpp 1.0f @ce in both dimensions but it - can be overriden using custom DPI scaling. + can be overridden using custom DPI scaling. - On Windows, the default is @ref Configuration::DpiScalingPolicy::Framebuffer. The @ref windowSize() and @ref framebufferSize() is always the same. Depending on whether the DPI awareness was enabled in the manifest file or set by the `SetProcessDpiAwareness()` API, @ref dpiScaling() is either @cpp 1.0f @ce in both dimensions, indicating a low-DPI screen or a non-DPI-aware app, or some other value for HiDPI screens. In both cases the - value can be overriden using custom DPI scaling. + value can be overridden using custom DPI scaling. - On Linux, the default is @ref Configuration::DpiScalingPolicy::Virtual, taken from the `Xft.dpi` property. If the property is not available, it falls back to @ref Configuration::DpiScalingPolicy::Physical, querying the monitor DPI value. The @ref windowSize() and @ref framebufferSize() is always the same, @ref dpiScaling() contains the queried DPI scaling value. - The value can be overriden using custom DPI scaling. + The value can be overridden using custom DPI scaling. - On @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", the default is physical DPI scaling, taken from [Window.getDevicePixelRatio()](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). The @ref windowSize() and @ref framebufferSize() is always the same, @ref dpiScaling() contains the queried DPI scaling value. The value can be - overriden using custom DPI scaling. Note that this is different from the + overridden using custom DPI scaling. Note that this is different from the behavior in @ref EmscriptenApplication --- Emscripten's SDL implementation has some additional emulation code that reports event coordinates in framebuffer pixels instead of CSS pixels. See @@ -1755,7 +1755,7 @@ class Sdl2Application::Configuration { * @brief DPI scaling policy * * DPI scaling policy when requesting a particular window size. Can - * be overriden on command-line using `--magnum-dpi-scaling` or via + * be overridden on command-line using `--magnum-dpi-scaling` or via * the `MAGNUM_DPI_SCALING` environment variable. * @see @ref setSize(), @ref Platform-Sdl2Application-dpi */ diff --git a/src/Magnum/Platform/WindowlessCglApplication.cpp b/src/Magnum/Platform/WindowlessCglApplication.cpp index ed8efa5d4..c92f9cfc4 100644 --- a/src/Magnum/Platform/WindowlessCglApplication.cpp +++ b/src/Magnum/Platform/WindowlessCglApplication.cpp @@ -72,7 +72,7 @@ WindowlessCglContext::WindowlessCglContext(const Configuration& configuration, G Error() << "Platform::WindowlessCglContext: cannot create context"; } -WindowlessCglContext::WindowlessCglContext(WindowlessCglContext&& other): _pixelFormat{other._pixelFormat}, _context{other._context} { +WindowlessCglContext::WindowlessCglContext(WindowlessCglContext&& other) noexcept: _pixelFormat{other._pixelFormat}, _context{other._context} { other._pixelFormat = {}; other._context = {}; } @@ -82,7 +82,7 @@ WindowlessCglContext::~WindowlessCglContext() { if(_pixelFormat) CGLDestroyPixelFormat(_pixelFormat); } -WindowlessCglContext& WindowlessCglContext::operator=(WindowlessCglContext&& other) { +WindowlessCglContext& WindowlessCglContext::operator=(WindowlessCglContext&& other) noexcept { using std::swap; swap(other._pixelFormat, _pixelFormat); swap(other._context, _context); diff --git a/src/Magnum/Platform/WindowlessCglApplication.h b/src/Magnum/Platform/WindowlessCglApplication.h index 0d76c5841..ec2ad98e6 100644 --- a/src/Magnum/Platform/WindowlessCglApplication.h +++ b/src/Magnum/Platform/WindowlessCglApplication.h @@ -98,13 +98,13 @@ class WindowlessCglContext { WindowlessCglContext(const WindowlessCglContext&) = delete; /** @brief Move constructor */ - WindowlessCglContext(WindowlessCglContext&& other); + WindowlessCglContext(WindowlessCglContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessCglContext& operator=(const WindowlessCglContext&) = delete; /** @brief Move assignment */ - WindowlessCglContext& operator=(WindowlessCglContext&& other); + WindowlessCglContext& operator=(WindowlessCglContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 89fa13406..081004e83 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -499,7 +499,7 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G #endif } -WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other): +WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other) noexcept: #ifndef MAGNUM_TARGET_WEBGL _sharedContext{other._sharedContext}, #endif @@ -541,7 +541,7 @@ WindowlessEglContext::~WindowlessEglContext() { _display) eglTerminate(_display); } -WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& other) { +WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& other) noexcept { using std::swap; #ifndef MAGNUM_TARGET_WEBGL swap(other._sharedContext, _sharedContext); diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index 1eaf0fbe5..2ccc1478f 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -108,13 +108,13 @@ class WindowlessEglContext { WindowlessEglContext(const WindowlessEglContext&) = delete; /** @brief Move constructor */ - WindowlessEglContext(WindowlessEglContext&& other); + WindowlessEglContext(WindowlessEglContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessEglContext& operator=(const WindowlessEglContext&) = delete; /** @brief Move assignment */ - WindowlessEglContext& operator=(WindowlessEglContext&& other); + WindowlessEglContext& operator=(WindowlessEglContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Platform/WindowlessGlxApplication.cpp b/src/Magnum/Platform/WindowlessGlxApplication.cpp index ed0d2d80e..d2b5791e2 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.cpp +++ b/src/Magnum/Platform/WindowlessGlxApplication.cpp @@ -280,7 +280,7 @@ WindowlessGlxContext::WindowlessGlxContext(const WindowlessGlxContext::Configura } } -WindowlessGlxContext::WindowlessGlxContext(WindowlessGlxContext&& other): _display{other._display}, _pbuffer{other._pbuffer}, _context{other._context} { +WindowlessGlxContext::WindowlessGlxContext(WindowlessGlxContext&& other) noexcept: _display{other._display}, _pbuffer{other._pbuffer}, _context{other._context} { other._display = {}; other._context = {}; other._pbuffer = {}; @@ -292,7 +292,7 @@ WindowlessGlxContext::~WindowlessGlxContext() { if(_display) XCloseDisplay(_display); } -WindowlessGlxContext& WindowlessGlxContext::operator=(WindowlessGlxContext&& other) { +WindowlessGlxContext& WindowlessGlxContext::operator=(WindowlessGlxContext&& other) noexcept { using std::swap; swap(other._display, _display); swap(other._pbuffer, _pbuffer); diff --git a/src/Magnum/Platform/WindowlessGlxApplication.h b/src/Magnum/Platform/WindowlessGlxApplication.h index f549f7d1f..5b3f39906 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.h +++ b/src/Magnum/Platform/WindowlessGlxApplication.h @@ -119,13 +119,13 @@ class WindowlessGlxContext { WindowlessGlxContext(const WindowlessGlxContext&) = delete; /** @brief Move constructor */ - WindowlessGlxContext(WindowlessGlxContext&& other); + WindowlessGlxContext(WindowlessGlxContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessGlxContext& operator=(const WindowlessGlxContext&) = delete; /** @brief Move assignment */ - WindowlessGlxContext& operator=(WindowlessGlxContext&& other); + WindowlessGlxContext& operator=(WindowlessGlxContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Platform/WindowlessIosApplication.h b/src/Magnum/Platform/WindowlessIosApplication.h index 5932a13b4..40ca1fdd1 100644 --- a/src/Magnum/Platform/WindowlessIosApplication.h +++ b/src/Magnum/Platform/WindowlessIosApplication.h @@ -91,13 +91,13 @@ class WindowlessIosContext { WindowlessIosContext(const WindowlessIosContext&) = delete; /** @brief Move constructor */ - WindowlessIosContext(WindowlessIosContext&& other); + WindowlessIosContext(WindowlessIosContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessIosContext& operator=(const WindowlessIosContext&) = delete; /** @brief Move assignment */ - WindowlessIosContext& operator=(WindowlessIosContext&& other); + WindowlessIosContext& operator=(WindowlessIosContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Platform/WindowlessIosApplication.mm b/src/Magnum/Platform/WindowlessIosApplication.mm index c3778778f..8665f0315 100644 --- a/src/Magnum/Platform/WindowlessIosApplication.mm +++ b/src/Magnum/Platform/WindowlessIosApplication.mm @@ -52,7 +52,7 @@ WindowlessIosContext::WindowlessIosContext(const Configuration&, GLContext*) { } } -WindowlessIosContext::WindowlessIosContext(WindowlessIosContext&& other): _context{other._context} { +WindowlessIosContext::WindowlessIosContext(WindowlessIosContext&& other) noexcept: _context{other._context} { other._context = {}; } @@ -60,7 +60,7 @@ WindowlessIosContext::~WindowlessIosContext() { if(_context) [_context dealloc]; } -WindowlessIosContext& WindowlessIosContext::operator=(WindowlessIosContext&& other) { +WindowlessIosContext& WindowlessIosContext::operator=(WindowlessIosContext&& other) noexcept { using std::swap; swap(other._context, _context); return *this; diff --git a/src/Magnum/Platform/WindowlessWglApplication.cpp b/src/Magnum/Platform/WindowlessWglApplication.cpp index 760c1dcd6..5a3b33b12 100644 --- a/src/Magnum/Platform/WindowlessWglApplication.cpp +++ b/src/Magnum/Platform/WindowlessWglApplication.cpp @@ -266,7 +266,7 @@ WindowlessWglContext::WindowlessWglContext(const Configuration& configuration, G Error() << "Platform::WindowlessWglContext: cannot create context:" << GetLastError(); } -WindowlessWglContext::WindowlessWglContext(WindowlessWglContext&& other): _window{other._window}, _deviceContext{other._deviceContext}, _context{other._context} { +WindowlessWglContext::WindowlessWglContext(WindowlessWglContext&& other) noexcept: _window{other._window}, _deviceContext{other._deviceContext}, _context{other._context} { other._window = {}; other._deviceContext = {}; other._context = {}; @@ -277,7 +277,7 @@ WindowlessWglContext::~WindowlessWglContext() { if(_window) DestroyWindow(_window); } -WindowlessWglContext& WindowlessWglContext::operator=(WindowlessWglContext&& other) { +WindowlessWglContext& WindowlessWglContext::operator=(WindowlessWglContext&& other) noexcept { using std::swap; swap(other._window, _window); swap(other._deviceContext, _deviceContext); diff --git a/src/Magnum/Platform/WindowlessWglApplication.h b/src/Magnum/Platform/WindowlessWglApplication.h index 923b51514..c27cd66c9 100644 --- a/src/Magnum/Platform/WindowlessWglApplication.h +++ b/src/Magnum/Platform/WindowlessWglApplication.h @@ -106,13 +106,13 @@ class WindowlessWglContext { WindowlessWglContext(const WindowlessWglContext&) = delete; /** @brief Move constructor */ - WindowlessWglContext(WindowlessWglContext&& other); + WindowlessWglContext(WindowlessWglContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessWglContext& operator=(const WindowlessWglContext&) = delete; /** @brief Move assignment */ - WindowlessWglContext& operator=(WindowlessWglContext&& other); + WindowlessWglContext& operator=(WindowlessWglContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp index c959bdbfc..3c16543db 100644 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp @@ -163,7 +163,7 @@ WindowlessWindowsEglContext::WindowlessWindowsEglContext(const Configuration& co Error() << "Platform::WindowlessWindowsEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); } -WindowlessWindowsEglContext::WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other): _window{other._window}, _display{other._display}, _surface{other._surface}, _context{other._context} { +WindowlessWindowsEglContext::WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept: _window{other._window}, _display{other._display}, _surface{other._surface}, _context{other._context} { other._window = {}; other._display = {}; other._surface = {}; @@ -177,7 +177,7 @@ WindowlessWindowsEglContext::~WindowlessWindowsEglContext() { if(_window) DestroyWindow(_window); } -WindowlessWindowsEglContext& WindowlessWindowsEglContext::operator=(WindowlessWindowsEglContext&& other) { +WindowlessWindowsEglContext& WindowlessWindowsEglContext::operator=(WindowlessWindowsEglContext&& other) noexcept { using std::swap; swap(other._window, _window); swap(other._display, _display); diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.h b/src/Magnum/Platform/WindowlessWindowsEglApplication.h index e0a6df5f8..555ef681a 100644 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.h +++ b/src/Magnum/Platform/WindowlessWindowsEglApplication.h @@ -91,13 +91,13 @@ class WindowlessWindowsEglContext { WindowlessWindowsEglContext(const WindowlessWindowsEglContext&) = delete; /** @brief Move constructor */ - WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other); + WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept; /** @brief Copying is not allowed */ WindowlessWindowsEglContext& operator=(const WindowlessWindowsEglContext&) = delete; /** @brief Move assignment */ - WindowlessWindowsEglContext& operator=(WindowlessWindowsEglContext&& other); + WindowlessWindowsEglContext& operator=(WindowlessWindowsEglContext&& other) noexcept; /** * @brief Destructor diff --git a/src/Magnum/Primitives/Test/GradientTest.cpp b/src/Magnum/Primitives/Test/GradientTest.cpp index 1df2e159e..bb0f852e1 100644 --- a/src/Magnum/Primitives/Test/GradientTest.cpp +++ b/src/Magnum/Primitives/Test/GradientTest.cpp @@ -60,7 +60,7 @@ GradientTest::GradientTest() { using namespace Magnum::Math::Literals; void GradientTest::gradient2D() { - /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ + /* The corners should have 0.2, 0.4, 0.6, 0.8 blends */ Trade::MeshData gradient = Primitives::gradient2D( {-1.0f, 2.0f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f}, {0.4f, 1.0f, 0.0f}); @@ -115,7 +115,7 @@ void GradientTest::gradient2DVertical() { } void GradientTest::gradient3D() { - /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ + /* The corners should have 0.2, 0.4, 0.6, 0.8 blends */ Trade::MeshData gradient = Primitives::gradient3D( {-1.0f, 2.0f, -1.5f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f, -1.5f}, {0.4f, 1.0f, 0.0f}); diff --git a/src/Magnum/ResourceManager.h b/src/Magnum/ResourceManager.h index b7f66101b..9315a7dde 100644 --- a/src/Magnum/ResourceManager.h +++ b/src/Magnum/ResourceManager.h @@ -570,7 +570,7 @@ template struct ResourceManagerData::Data { Data(const Data&) = delete; - Data(Data&& other): data(other.data), state(other.state), policy(other.policy), referenceCount(other.referenceCount) { + Data(Data&& other) noexcept: data{other.data}, state{other.state}, policy{other.policy}, referenceCount{other.referenceCount} { other.data = nullptr; other.referenceCount = 0; } diff --git a/src/Magnum/SceneGraph/Object.hpp b/src/Magnum/SceneGraph/Object.hpp index 4e44e795d..6267c5558 100644 --- a/src/Magnum/SceneGraph/Object.hpp +++ b/src/Magnum/SceneGraph/Object.hpp @@ -211,7 +211,7 @@ template std::vector Ob /* Mark all original objects as joints and create initial list of joints from them */ for(std::size_t i = 0; i != objects.size(); ++i) { - /* Multiple occurences of one object in the array, don't overwrite it + /* Multiple occurrences of one object in the array, don't overwrite it with different counter */ if(objects[i].get().counter != 0xFFFFu) continue; @@ -231,7 +231,7 @@ template std::vector Ob /* Mark all objects up the hierarchy as visited */ auto it = objects.begin(); while(!objects.empty()) { - /* Already visited, remove and continue to next (duplicate occurence) */ + /* Already visited, remove and continue to next (duplicate occurrence) */ if(it->get().flags & Flag::Visited) { it = objects.erase(it); continue; @@ -276,7 +276,7 @@ template std::vector Ob for(std::size_t i = 0; i != jointTransformations.size(); ++i) computeJointTransformation(jointObjects, jointTransformations, i, finalTransformation); - /* Copy transformation for second or next occurences from first occurence + /* Copy transformation for second or next occurrences from first occurrence of duplicate object */ for(std::size_t i = 0; i != objectCount; ++i) { if(jointObjects[i].get().counter != i) @@ -285,7 +285,7 @@ template std::vector Ob /* All visited marks are now cleaned, clean joint marks and counters */ for(auto i: jointObjects) { - /* All not-already cleaned objects (...duplicate occurences) should + /* All not-already cleaned objects (...duplicate occurrences) should have joint mark */ CORRADE_INTERNAL_ASSERT(i.get().counter == 0xFFFFu || i.get().flags & Flag::Joint); i.get().flags &= ~Flag::Joint; @@ -301,7 +301,7 @@ template typename Transformation::DataType Object> o = jointObjects[joint]; /* Transformation already computed ("unvisited" by this function before - either due to recursion or duplicate object occurences), done */ + either due to recursion or duplicate object occurrences), done */ if(!(o.get().flags & Flag::Visited)) return jointTransformations[joint]; /* Initialize transformation */ diff --git a/src/Magnum/SceneGraph/Test/AnimableTest.cpp b/src/Magnum/SceneGraph/Test/AnimableTest.cpp index 36306c1c4..e2bff1025 100644 --- a/src/Magnum/SceneGraph/Test/AnimableTest.cpp +++ b/src/Magnum/SceneGraph/Test/AnimableTest.cpp @@ -370,7 +370,7 @@ template void AnimableTest::pause() { CORRADE_COMPARE(animable.time, 1.5f); /* Unpausing, next step should continue from absolute time when pause - occured */ + occurred */ animable.setState(AnimationState::Running); group.step(5.0f, 0.5f); CORRADE_COMPARE(animable.state(), AnimationState::Running); diff --git a/src/Magnum/ShaderTools/shaderconverter.cpp b/src/Magnum/ShaderTools/shaderconverter.cpp index d0c2c0233..55d1c252b 100644 --- a/src/Magnum/ShaderTools/shaderconverter.cpp +++ b/src/Magnum/ShaderTools/shaderconverter.cpp @@ -366,7 +366,7 @@ see documentation of a particular converter for more information.)") /* Set options if passed */ if(i < args.arrayValueCount("converter-options")) - Implementation::setOptions(*converter, args.arrayValue("converter-options", i)); + Implementation::setOptions(*converter, "AnyShaderConverter", args.arrayValue("converter-options", i)); /* Parse format, if passed. If --info is desired, implicitly set the output format to SPIR-V */ diff --git a/src/Magnum/Shaders/AbstractVector.h b/src/Magnum/Shaders/AbstractVector.h deleted file mode 100644 index 192371879..000000000 --- a/src/Magnum/Shaders/AbstractVector.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef Magnum_Shaders_AbstractVector_h -#define Magnum_Shaders_AbstractVector_h -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#ifdef MAGNUM_BUILD_DEPRECATED -/** @file - * @brief Typedef @ref Magnum::Shaders::AbstractVector, alias @ref Magnum::Shaders::AbstractVector2D, @ref Magnum::Shaders::AbstractVector3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/AbstractVectorGL.h, the - * @ref Magnum::Shaders::AbstractVectorGL "AbstractVectorGL" class and - * related typedefs instead. - */ -#endif - -#include "Magnum/configure.h" - -#ifdef MAGNUM_BUILD_DEPRECATED -#include - -#include "Magnum/Shaders/AbstractVectorGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/AbstractVectorGL.h, the AbstractVectorGL class and related typedefs instead") - -namespace Magnum { namespace Shaders { - -/** @brief @copybrief AbstractVectorGL - * @m_deprecated_since_latest Use @ref AbstractVectorGL instead. - */ -#ifndef CORRADE_MSVC2015_COMPATIBILITY /* Multiple definitions still broken */ -template using AbstractVector CORRADE_DEPRECATED_ALIAS("use AbstractVectorGL instead") = AbstractVectorGL; -#endif - -/** @brief @copybrief AbstractVectorGL2D - * @m_deprecated_since_latest Use @ref AbstractVectorGL2D instead. - */ -typedef CORRADE_DEPRECATED("use AbstractVectorGL2D instead") AbstractVectorGL2D AbstractVector2D; - -/** @brief @copybrief AbstractVectorGL3D - * @m_deprecated_since_latest Use @ref AbstractVectorGL3D instead. - */ -typedef CORRADE_DEPRECATED("use AbstractVectorGL3D instead") AbstractVectorGL3D AbstractVector3D; - -}} -#else -#error use Magnum/Shaders/AbstractVectorGL.h, the AbstractVectorGL class and related typedefs instead -#endif - -#endif diff --git a/src/Magnum/Shaders/AbstractVectorGL.cpp b/src/Magnum/Shaders/AbstractVectorGL.cpp deleted file mode 100644 index e02251094..000000000 --- a/src/Magnum/Shaders/AbstractVectorGL.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#include "AbstractVectorGL.h" - -#include "Magnum/GL/Texture.h" -#include "Magnum/Shaders/visibility.h" - -namespace Magnum { namespace Shaders { - -template AbstractVectorGL& AbstractVectorGL::bindVectorTexture(GL::Texture2D& texture) { - texture.bind(VectorTextureUnit); - return *this; -} - -#ifndef DOXYGEN_GENERATING_OUTPUT -template class MAGNUM_SHADERS_EXPORT AbstractVectorGL<2>; -template class MAGNUM_SHADERS_EXPORT AbstractVectorGL<3>; -#endif - -}} diff --git a/src/Magnum/Shaders/AbstractVectorGL.h b/src/Magnum/Shaders/AbstractVectorGL.h deleted file mode 100644 index 95933640d..000000000 --- a/src/Magnum/Shaders/AbstractVectorGL.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef Magnum_Shaders_AbstractVectorGL_h -#define Magnum_Shaders_AbstractVectorGL_h -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -/** @file - * @brief Class @ref Magnum::Shaders::AbstractVectorGL, typedef @ref Magnum::Shaders::AbstractVectorGL2D, @ref Magnum::Shaders::AbstractVectorGL3D - * @m_since_latest - */ - -#include "Magnum/GL/AbstractShaderProgram.h" -#include "Magnum/Shaders/GenericGL.h" - -namespace Magnum { namespace Shaders { - -/** -@brief Base for vector OpenGL shaders -@m_since_latest - -See @ref DistanceFieldVectorGL and @ref VectorGL for more information. -@see @ref shaders, @ref AbstractVectorGL2D, @ref AbstractVectorGL3D -*/ -template class AbstractVectorGL: public GL::AbstractShaderProgram { - public: - /** - * @brief Vertex position - * - * @ref shaders-generic "Generic attribute", - * @ref Magnum::Vector2 "Vector2" in 2D, @ref Magnum::Vector3 "Vector3" - * in 3D. - */ - typedef typename GenericGL::Position Position; - - /** - * @brief 2D texture coordinates - * - * @ref shaders-generic "Generic attribute", - * @ref Magnum::Vector2 "Vector2". - */ - typedef typename GenericGL::TextureCoordinates TextureCoordinates; - - enum: UnsignedInt { - /** - * Color shader output. @ref shaders-generic "Generic output", - * present always. Expects three- or four-component floating-point - * or normalized buffer attachment. - */ - ColorOutput = GenericGL::ColorOutput - }; - - /** @brief Copying is not allowed */ - AbstractVectorGL(const AbstractVectorGL&) = delete; - - /** @brief Move constructor */ - AbstractVectorGL(AbstractVectorGL&&) noexcept = default; - - /** @brief Copying is not allowed */ - AbstractVectorGL& operator=(const AbstractVectorGL&) = delete; - - /** @brief Move assignment */ - AbstractVectorGL& operator=(AbstractVectorGL&&) noexcept = default; - - /** @{ - * @name Texture binding - */ - - /** - * @brief Bind vector texture - * @return Reference to self (for method chaining) - * - * @see @ref DistanceFieldVectorGL::Flag::TextureTransformation, - * @ref VectorGL::Flag::TextureTransformation, - * @ref DistanceFieldVectorGL::setTextureMatrix(), - * @ref VectorGL::setTextureMatrix() - */ - AbstractVectorGL& bindVectorTexture(GL::Texture2D& texture); - - /** - * @} - */ - - #ifndef DOXYGEN_GENERATING_OUTPUT - protected: - #else - private: - #endif - /* Those textures are quite specific (and likely reused multiple times - per frame for e.g. text rendering, so put them in a specific slot. - Older iOS (and iOS WebGL) has only 8 texture units, so can't go - above that. Unit 7 is used by TextureTools::DistanceField. */ - enum: Int { VectorTextureUnit = 6 }; - - explicit AbstractVectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} - explicit AbstractVectorGL() = default; - ~AbstractVectorGL() = default; -}; - -/** -@brief Base for two-dimensional vector OpenGL shaders -@m_since_latest -*/ -typedef AbstractVectorGL<2> AbstractVectorGL2D; - -/** -@brief Base for three-dimensional vector OpenGL shader -@m_since_latest -*/ -typedef AbstractVectorGL<3> AbstractVectorGL3D; - -}} - -#endif diff --git a/src/Magnum/Shaders/CMakeLists.txt b/src/Magnum/Shaders/CMakeLists.txt index b5b55947b..08551065a 100644 --- a/src/Magnum/Shaders/CMakeLists.txt +++ b/src/Magnum/Shaders/CMakeLists.txt @@ -31,9 +31,6 @@ corrade_add_resource(MagnumShaders_RESOURCES_GL resources-gl.conf) set_target_properties(MagnumShaders_RESOURCES_GL-dependencies PROPERTIES FOLDER "Magnum/Shaders") set(MagnumShaders_SRCS - AbstractVectorGL.cpp - VertexColorGL.cpp - ${MagnumShaders_RESOURCES_GL}) set(MagnumShaders_GracefulAssert_SRCS @@ -41,16 +38,22 @@ set(MagnumShaders_GracefulAssert_SRCS FlatGL.cpp MeshVisualizerGL.cpp PhongGL.cpp - VectorGL.cpp) + VectorGL.cpp + VertexColorGL.cpp) set(MagnumShaders_HEADERS + DistanceFieldVector.h DistanceFieldVectorGL.h - AbstractVectorGL.h + Flat.h FlatGL.h + Generic.h GenericGL.h + MeshVisualizer.h MeshVisualizerGL.h + Phong.h PhongGL.h Shaders.h + Vector.h VectorGL.h VertexColorGL.h @@ -58,13 +61,6 @@ set(MagnumShaders_HEADERS if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumShaders_HEADERS - DistanceFieldVector.h - AbstractVector.h - Flat.h - Generic.h - MeshVisualizer.h - Phong.h - Vector.h VertexColor.h) endif() diff --git a/src/Magnum/Shaders/DistanceFieldVector.frag b/src/Magnum/Shaders/DistanceFieldVector.frag index 78b1b89f0..16385febf 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.frag +++ b/src/Magnum/Shaders/DistanceFieldVector.frag @@ -29,8 +29,13 @@ #define texture texture2D #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -63,10 +68,59 @@ uniform lowp float smoothness #endif ; +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +struct DrawUniform { + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 color; + lowp vec4 reserved; + lowp vec4 outlineColor; + lowp vec4 outlineRangeSmoothnessReserved; + #define material_outlineRange outlineRangeSmoothnessReserved.xy + #define material_smoothness outlineRangeSmoothnessReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Textures */ -#ifdef EXPLICIT_TEXTURE_LAYER -/* See AbstractVector.h for details about the ID */ +#ifdef EXPLICIT_BINDING layout(binding = 6) #endif uniform lowp sampler2D vectorTexture; @@ -75,6 +129,10 @@ uniform lowp sampler2D vectorTexture; in mediump vec2 interpolatedTextureCoordinates; +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + /* OUtput */ #ifdef NEW_GLSL @@ -85,6 +143,18 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + lowp const float smoothness = materials[materialId].material_smoothness; + lowp const vec4 color = materials[materialId].color; + lowp const vec4 outlineColor = materials[materialId].outlineColor; + lowp const vec2 outlineRange = materials[materialId].material_outlineRange; + #endif + lowp float intensity = texture(vectorTexture, interpolatedTextureCoordinates).r; /* Fill color */ diff --git a/src/Magnum/Shaders/DistanceFieldVector.h b/src/Magnum/Shaders/DistanceFieldVector.h index c3b15183c..5bd8510ac 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.h +++ b/src/Magnum/Shaders/DistanceFieldVector.h @@ -25,26 +25,288 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::DistanceFieldVector, alias @ref Magnum::Shaders::DistanceFieldVector2D, @ref Magnum::Shaders::DistanceFieldVector3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/DistanceFieldVectorGL.h, - * the @ref Magnum::Shaders::DistanceFieldVectorGL "DistanceFieldVectorGL" - * class and related typedefs instead. + * @brief Struct @ref Magnum::Shaders::DistanceFieldVectorDrawUniform, @ref Magnum::Shaders::DistanceFieldVectorMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/DistanceFieldVectorGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/DistanceFieldVectorGL.h, the DistanceFieldVectorGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for distance field vector shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform; material-related properties are expected to +be shared among multiple draw calls and thus are provided in a separate +@ref DistanceFieldVectorMaterialUniform structure, referenced by +@ref materialId. +@see @ref DistanceFieldVectorGL::bindDrawBuffer() +*/ +struct DistanceFieldVectorDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit DistanceFieldVectorDrawUniform(DefaultInitT = DefaultInit) noexcept: + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + #ifndef CORRADE_TARGET_BIG_ENDIAN + , _pad0{} + #endif + , _pad1{}, _pad2{}, _pad3{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit DistanceFieldVectorDrawUniform(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref DistanceFieldVectorMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to the + * @ref DistanceFieldVectorGL::DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) + * constructor, if material count is @cpp 1 @ce, this field is assumed to + * be @cpp 0 @ce and isn't even read by the shader. Default value is + * @cpp 0 @ce, meaning the first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + alignas(4) UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + alignas(4) UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for objectId */ + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Material uniform for distance field vector shaders +@m_since_latest + +Describes material properties referenced from +@ref DistanceFieldVectorDrawUniform::materialId. +@see @ref DistanceFieldVectorGL::bindMaterialBuffer() +*/ +struct DistanceFieldVectorMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit DistanceFieldVectorMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + /* Otherwise it refuses to constexpr, on 3.8 at least */ + _pad0{}, _pad1{}, _pad2{}, _pad3{}, + #endif + outlineColor{0.0f, 0.0f, 0.0f, 0.0f}, outlineStart{0.5f}, outlineEnd{1.0f}, smoothness{0.04f} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad4{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit DistanceFieldVectorMaterialUniform(NoInitT) noexcept: color{NoInit}, outlineColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref outlineColor field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setOutlineColor(const Color4& color) { + outlineColor = color; + return *this; + } + + /** + * @brief Set the @ref outlineStart and @ref outlineEnd fields + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setOutlineRange(Float start, Float end) { + outlineStart = start; + outlineEnd = end; + return *this; + } + + /** + * @brief Set the @ref smoothness field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setSmoothness(Float smoothness) { + this->smoothness = smoothness; + return *this; + } + + /** + * @} + */ + + /** + * @brief Fill color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * @see @ref DistanceFieldVectorGL::setColor() + */ + Color4 color; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for backgroundColor */ + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif + + /** + * @brief Outline color + * + * Default value is @cpp 0x00000000_rgbaf @ce and the outline is not drawn + * --- see @ref outlineStart and @ref outlineEnd for more information. + * @see @ref DistanceFieldVectorGL::setColor() + */ + Color4 outlineColor; + + /** + * @brief Outline start + * + * Describe where fill ends and possible outline starts. Default value is + * @cpp 0.5f @ce, larger values will make the vector art look thinner, + * smaller will make it look thicker. + * @see @ref DistanceFieldVectorGL::setOutlineRange() + */ + Float outlineStart; + + /** + * @brief Outline end + * + * Describe where outline ends. If set to a value larger than + * @ref outlineStart, the outline is not drawn. Initial value is + * @cpp 1.0f @ce. + * @see @ref DistanceFieldVectorGL::setOutlineRange() + */ + Float outlineEnd; + + /** + * @brief Smoothness radius + * + * Larger values will make edges look less aliased (but blurry), smaller + * values will make them look more crisp (but possibly aliased). Initial + * value is @cpp 0.04f @ce. + * @see @ref DistanceFieldVectorGL::setSmoothness() + */ + Float smoothness; + + /* warning: Member __pad4__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad4 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief DistanceFieldVectorGL * @m_deprecated_since_latest Use @ref DistanceFieldVectorGL instead. */ @@ -61,10 +323,8 @@ typedef CORRADE_DEPRECATED("use DistanceFieldVectorGL2D instead") DistanceFieldV * @m_deprecated_since_latest Use @ref DistanceFieldVectorGL3D instead. */ typedef CORRADE_DEPRECATED("use DistanceFieldVectorGL3D instead") DistanceFieldVectorGL3D DistanceFieldVector3D; +#endif }} -#else -#error use Magnum/Shaders/DistanceFieldVectorGL.h, the DistanceFieldVectorGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 1ce614bc3..381a6b2f6 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -32,15 +32,71 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): _flags{flags} { +namespace { + enum: Int { TextureUnit = 6 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4 + }; + #endif +} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, + _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::DistanceFieldVectorGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::DistanceFieldVectorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -60,15 +116,35 @@ template DistanceFieldVectorGL::DistanceFiel GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); vert.addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) - .addSource(rs.get("AbstractVector.vert")); + .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + vert.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + vert.addSource(rs.get("generic.glsl")) + .addSource(rs.get("Vector.vert")); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define MATERIAL_COUNT {}\n" + "#define DRAW_COUNT {}\n", + materialCount, + drawCount)); + frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("DistanceFieldVector.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); - GL::AbstractShaderProgram::attachShaders({vert, frag}); + attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -76,74 +152,201 @@ template DistanceFieldVectorGL::DistanceFiel if(!context.isExtensionSupported(version)) #endif { - GL::AbstractShaderProgram::bindAttributeLocation(AbstractVectorGL::Position::Location, "position"); - GL::AbstractShaderProgram::bindAttributeLocation(AbstractVectorGL::TextureCoordinates::Location, "textureCoordinates"); + bindAttributeLocation(Position::Location, "position"); + bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::AbstractShaderProgram::link()); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = GL::AbstractShaderProgram::uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = GL::AbstractShaderProgram::uniformLocation("textureMatrix"); - _colorUniform = GL::AbstractShaderProgram::uniformLocation("color"); - _outlineColorUniform = GL::AbstractShaderProgram::uniformLocation("outlineColor"); - _outlineRangeUniform = GL::AbstractShaderProgram::uniformLocation("outlineRange"); - _smoothnessUniform = GL::AbstractShaderProgram::uniformLocation("smoothness"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _colorUniform = uniformLocation("color"); + _outlineColorUniform = uniformLocation("outlineColor"); + _outlineRangeUniform = uniformLocation("outlineRange"); + _smoothnessUniform = uniformLocation("smoothness"); + } } #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - GL::AbstractShaderProgram::setUniform(GL::AbstractShaderProgram::uniformLocation("vectorTexture"), - AbstractVectorGL::VectorTextureUnit); + setUniform(uniformLocation("vectorTexture"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Color4{1.0f}); /* Outline color is zero by default */ - setOutlineRange(0.5f, 1.0f); - setSmoothness(0.04f); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + setColor(Color4{1.0f}); + /* Outline color is zero by default */ + setOutlineRange(0.5f, 1.0f); + setSmoothness(0.04f); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{flags, 1, 1} {} +#endif + template DistanceFieldVectorGL& DistanceFieldVectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { - GL::AbstractShaderProgram::setUniform(_transformationProjectionMatrixUniform, matrix); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); - GL::AbstractShaderProgram::setUniform(_textureMatrixUniform, matrix); + setUniform(_textureMatrixUniform, matrix); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setColor(const Color4& color) { - GL::AbstractShaderProgram::setUniform(_colorUniform, color); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_colorUniform, color); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setOutlineColor(const Color4& color) { - GL::AbstractShaderProgram::setUniform(_outlineColorUniform, color); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setOutlineColor(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_outlineColorUniform, color); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setOutlineRange(Float start, Float end) { - GL::AbstractShaderProgram::setUniform(_outlineRangeUniform, Vector2(start, end)); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setOutlineRange(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_outlineRangeUniform, Vector2(start, end)); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setSmoothness(Float value) { - GL::AbstractShaderProgram::setUniform(_smoothnessUniform, value); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_smoothnessUniform, value); + return *this; +} + +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL& DistanceFieldVectorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::DistanceFieldVectorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindVectorTexture(GL::Texture2D& texture) { + texture.bind(TextureUnit); return *this; } @@ -159,6 +362,10 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlag value) { /* LCOV_EXCL_START */ #define _c(v) case DistanceFieldVectorGLFlag::v: return debug << "::" #v; _c(TextureTransformation) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -168,8 +375,12 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlag value) { Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::DistanceFieldVectorGL::Flags{}", { - DistanceFieldVectorGLFlag::TextureTransformation - }); + DistanceFieldVectorGLFlag::TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + DistanceFieldVectorGLFlag::MultiDraw, /* Superset of UniformBuffers */ + DistanceFieldVectorGLFlag::UniformBuffers + #endif + }); } } diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index f4752b357..69290b331 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -31,14 +31,19 @@ */ #include "Magnum/DimensionTraits.h" -#include "Magnum/Shaders/AbstractVectorGL.h" +#include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { namespace Implementation { enum class DistanceFieldVectorGLFlag: UnsignedByte { - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 1, + MultiDraw = UniformBuffers|(1 << 2) + #endif }; typedef Containers::EnumSet DistanceFieldVectorGLFlags; } @@ -48,9 +53,10 @@ namespace Implementation { @m_since_latest Renders vector graphics in a form of signed distance field. See -@ref TextureTools::DistanceField for more information. Note that the final -rendered outlook will greatly depend on radius of input distance field and -value passed to @ref setSmoothness(). You need to provide @ref Position and +@ref TextureTools::DistanceField for more information and @ref VectorGL for a +simpler variant of this shader. Note that the final rendered outlook will +greatly depend on radius of input distance field and value passed to +@ref setSmoothness(). You need to provide @ref Position and @ref TextureCoordinates attributes in your triangle mesh and call at least @ref bindVectorTexture(). By default, the shader renders the distance field texture with a white color in an identity transformation, use @@ -74,13 +80,72 @@ Common rendering setup: @snippet MagnumShaders-gl.cpp DistanceFieldVectorGL-usage2 +@section Shaders-DistanceFieldVectorGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, because the shader doesn't need a separate +projection and transformation matrix, a combined one is supplied via a +@ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D +buffer. To maximize use of the limited uniform buffer memory, materials are +supplied separately in a @ref DistanceFieldVectorMaterialUniform buffer and +then referenced via @relativeref{DistanceFieldVectorDrawUniform,materialId} +from a @ref DistanceFieldVectorDrawUniform; for optional texture transformation +a per-draw @ref TextureTransformationUniform can be supplied as well. A uniform +buffer setup equivalent to the above would look like this: + +@snippet MagnumShaders-gl.cpp DistanceFieldVectorGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw, supply desired material +and draw count in the @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) +constructor and specify material references and texture offsets for every draw. +Texture arrays aren't currently supported for this shader. Besides that, the +usage is similar for all shaders, see @ref shaders-usage-multidraw for an +example. + +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. +@requires_webgl20 Uniform buffers are not available in WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders, @ref DistanceFieldVectorGL2D, @ref DistanceFieldVectorGL3D @todo Use fragment shader derivations to have proper smoothness in perspective/ large zoom levels, make it optional as it might have negative performance impact */ -template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL: public AbstractVectorGL { +template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL: public GL::AbstractShaderProgram { public: + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector2 "Vector2" in 2D, @ref Magnum::Vector3 "Vector3" + * in 3D. + */ + typedef typename GenericGL::Position Position; + + /** + * @brief 2D texture coordinates + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector2 "Vector2". + */ + typedef typename GenericGL::TextureCoordinates TextureCoordinates; + + enum: UnsignedInt { + /** + * Color shader output. @ref shaders-generic "Generic output", + * present always. Expects three- or four-component floating-point + * or normalized buffer attachment. + */ + ColorOutput = GenericGL::ColorOutput + }; + #ifdef DOXYGEN_GENERATING_OUTPUT /** * @brief Flag @@ -94,7 +159,48 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @see @ref setTextureMatrix() * @m_since{2020,06} */ - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer(), + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 1, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 2) + #endif }; /** @@ -114,9 +220,50 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit DistanceFieldVectorGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref DistanceFieldVectorMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / + * @ref DistanceFieldVectorDrawUniform / + * @ref TextureTransformationUniform buffer bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref DistanceFieldVectorDrawUniform::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref DistanceFieldVectorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit DistanceFieldVectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -129,12 +276,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * However note that this is a low-level and a potentially dangerous * API, see the documentation of @ref NoCreate for alternatives. */ - explicit DistanceFieldVectorGL(NoCreateT) noexcept - /** @todoc remove workaround when doxygen is sane */ - #ifndef DOXYGEN_GENERATING_OUTPUT - : AbstractVectorGL{NoCreate} - #endif - {} + explicit DistanceFieldVectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} /** @brief Copying is not allowed */ DistanceFieldVectorGL(const DistanceFieldVectorGL&) = delete; @@ -154,8 +296,40 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the + * @ref DistanceFieldVectorMaterialUniform uniform buffer. Has use only + * if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref DistanceFieldVectorDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -163,6 +337,11 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ DistanceFieldVectorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -174,6 +353,11 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an * identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ DistanceFieldVectorGL& setTextureMatrix(const Matrix3& matrix); @@ -182,6 +366,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @return Reference to self (for method chaining) * * Initial value is @cpp 0xffffffff_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineColor() */ DistanceFieldVectorGL& setColor(const Color4& color); @@ -192,6 +380,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * * Initial value is @cpp 0x00000000_rgbaf @ce and the outline is not * drawn --- see @ref setOutlineRange() for more information. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::outlineColor and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineRange(), @ref setColor() */ DistanceFieldVectorGL& setOutlineColor(const Color4& color); @@ -208,6 +400,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * larger than @p start, the outline is not drawn. Initial value is * @cpp 1.0f @ce. * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::outlineStart and + * @ref DistanceFieldVectorMaterialUniform::outlineEnd and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineColor() */ DistanceFieldVectorGL& setOutlineRange(Float start, Float end); @@ -219,6 +415,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * Larger values will make edges look less aliased (but blurry), * smaller values will make them look more crisp (but possibly * aliased). Initial value is @cpp 0.04f @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ DistanceFieldVectorGL& setSmoothness(Float value); @@ -226,11 +426,165 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @} */ - #ifndef DOXYGEN_GENERATING_OUTPUT + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref DistanceFieldVectorDrawUniform and + * @ref TextureTransformationUniform buffers bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindTextureTransformationBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce, if + * @ref drawCount() is @cpp 1 @ce, the function is a no-op as the + * shader assumes draw offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref DistanceFieldVectorDrawUniform. At the very least you need to + * call also @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref DistanceFieldVectorMaterialUniform. At the very least you need + * to call also @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + + /** @{ + * @name Texture binding + */ + + /** + * @brief Bind vector texture + * @return Reference to self (for method chaining) + * + * @see @ref DistanceFieldVectorGL::Flag::TextureTransformation, + * @ref VectorGL::Flag::TextureTransformation, + * @ref DistanceFieldVectorGL::setTextureMatrix(), + * @ref VectorGL::setTextureMatrix() + */ + DistanceFieldVectorGL& bindVectorTexture(GL::Texture2D& texture); + + /** + * @} + */ + /* Overloads to remove WTF-factor from method chaining order */ - DistanceFieldVectorGL& bindVectorTexture(GL::Texture2D& texture) { - AbstractVectorGL::bindVectorTexture(texture); - return *this; + #ifndef DOXYGEN_GENERATING_OUTPUT + DistanceFieldVectorGL& draw(GL::Mesh& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + DistanceFieldVectorGL& draw(GL::Mesh&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + DistanceFieldVectorGL& draw(GL::MeshView& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + DistanceFieldVectorGL& draw(GL::MeshView&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + DistanceFieldVectorGL& draw(Containers::ArrayView> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + DistanceFieldVectorGL& draw(std::initializer_list> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); } #endif @@ -244,12 +598,20 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, _colorUniform{2}, _outlineColorUniform{3}, _outlineRangeUniform{4}, _smoothnessUniform{5}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** @@ -266,10 +628,10 @@ typedef DistanceFieldVectorGL<3> DistanceFieldVectorGL3D; #ifdef DOXYGEN_GENERATING_OUTPUT /** @debugoperatorclassenum{DistanceFieldVectorGL,DistanceFieldVectorGL::Flag} */ -template Debug& operator<<(Debug& debug, DistanceFieldVector::Flag value); +template Debug& operator<<(Debug& debug, DistanceFieldVectorGL::Flag value); /** @debugoperatorclassenum{DistanceFieldVectorGL,DistanceFieldVectorGL::Flags} */ -template Debug& operator<<(Debug& debug, DistanceFieldVector::Flags value); +template Debug& operator<<(Debug& debug, DistanceFieldVectorGL::Flags value); #else namespace Implementation { MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, DistanceFieldVectorGLFlag value); diff --git a/src/Magnum/Shaders/Flat.frag b/src/Magnum/Shaders/Flat.frag index 21d3fbf45..dff034e23 100644 --- a/src/Magnum/Shaders/Flat.frag +++ b/src/Magnum/Shaders/Flat.frag @@ -33,10 +33,15 @@ #define in varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 2) +layout(location = 3) #endif uniform lowp vec4 color #ifndef GL_ES @@ -46,7 +51,7 @@ uniform lowp vec4 color #ifdef ALPHA_MASK #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 3) +layout(location = 4) #endif uniform lowp float alphaMask #ifndef GL_ES @@ -57,25 +62,85 @@ uniform lowp float alphaMask #ifdef OBJECT_ID #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 4) +layout(location = 5) #endif /* mediump is just 2^10, which might not be enough, this is 2^16 */ uniform highp uint objectId; /* defaults to zero */ #endif +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +struct DrawUniform { + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 color; + highp vec4 alphaMaskReservedReservedReserved; + #define material_alphaMask alphaMaskReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Textures */ #ifdef TEXTURED -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 0) #endif -uniform lowp sampler2D textureData; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + textureData; #endif /* Inputs */ #ifdef TEXTURED -in mediump vec2 interpolatedTextureCoordinates; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #endif #ifdef VERTEX_COLOR @@ -102,7 +167,26 @@ layout(location = OBJECT_ID_OUTPUT_ATTRIBUTE_LOCATION) out highp uint fragmentObjectId; #endif +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + void main() { + #ifdef UNIFORM_BUFFERS + #ifdef OBJECT_ID + highp const uint objectId = draws[drawId].draw_objectId; + #endif + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + lowp const vec4 color = materials[materialId].color; + #ifdef ALPHA_MASK + lowp const float alphaMask = materials[materialId].material_alphaMask; + #endif + #endif + fragmentColor = #ifdef TEXTURED texture(textureData, interpolatedTextureCoordinates)* diff --git a/src/Magnum/Shaders/Flat.h b/src/Magnum/Shaders/Flat.h index 70311e465..8e22d887d 100644 --- a/src/Magnum/Shaders/Flat.h +++ b/src/Magnum/Shaders/Flat.h @@ -25,26 +25,241 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Flat, alias @ref Magnum::Shaders::Flat2D, @ref Magnum::Shaders::Flat3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/FlatGL.h, the - * @ref Magnum::Shaders::FlatGL "FlatGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::FlatDrawUniform, @ref Magnum::Shaders::FlatMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/FlatGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/FlatGL.h, the FlatGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for flat shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform; material-related properties are expected to +be shared among multiple draw calls and thus are provided in a separate +@ref FlatMaterialUniform structure, referenced by @ref materialId. +@see @ref FlatGL::bindDrawBuffer() +*/ +struct FlatDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) && !defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, + #endif + objectId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad1{}, _pad2{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit FlatDrawUniform(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a @ref FlatMaterialUniform array. + * Useful when an UBO with more than one material is supplied or in a + * multi-draw scenario. Should be less than the material count passed to + * the @ref FlatGL::FlatGL(Flags, UnsignedInt, UnsignedInt) constructor, if + * material count is @cpp 1 @ce, this field is assumed to be @cpp 0 @ce and + * isn't even read by the shader. Default value is @cpp 0 @ce, meaning the + * first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /** + * @brief Object ID + * + * Used only for the object ID framebuffer output, not to access any other + * uniform data. Default value is @cpp 0 @ce. + * + * Used only if @ref FlatGL::Flag::ObjectId is enabled, ignored otherwise. + * If @ref FlatGL::Flag::InstancedObjectId is enabled as well, this value + * is added to the ID coming from the @ref FlatGL::ObjectId attribute. + * @see @ref FlatGL::setObjectId() + */ + UnsignedInt objectId; + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Material uniform for flat shaders +@m_since_latest + +Describes material properties referenced from +@ref FlatDrawUniform::materialId. +@see @ref FlatGL::bindMaterialBuffer() +*/ +struct FlatMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit FlatMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, alphaMask{0.5f} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad0{}, _pad1{}, _pad2{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit FlatMaterialUniform(NoInitT) noexcept: color{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref alphaMask field + * @return Reference to self (for method chaining) + */ + FlatMaterialUniform& setAlphaMask(Float alphaMask) { + this->alphaMask = alphaMask; + return *this; + } + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + FlatMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @} + */ + + /** + * @brief Color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * If @ref FlatGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref FlatGL::Color3 / @ref FlatGL::Color4 + * attribute. + * @see @ref FlatGL::setColor() + */ + Color4 color; + + /** + * @brief Alpha mask value + * + * Fragments with alpha values smaller than the mask value will be + * discarded. Default value is @cpp 0.5f @ce. + * + * Used only if @ref FlatGL::Flag::AlphaMask is enabled, ignored otherwise. + * @see @ref FlatGL::setAlphaMask() + */ + Float alphaMask; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief FlatGL * @m_deprecated_since_latest Use @ref FlatGL instead. */ @@ -61,10 +276,8 @@ typedef CORRADE_DEPRECATED("use FlatGL2D instead") FlatGL2D Flat2D; * @m_deprecated_since_latest Use @ref FlatGL3D instead. */ typedef CORRADE_DEPRECATED("use FlatGL3D instead") FlatGL3D Flat3D; +#endif }} -#else -#error use Magnum/Shaders/FlatGL.h, the FlatGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/Flat.vert b/src/Magnum/Shaders/Flat.vert index 1c4305fc6..eb71c6730 100644 --- a/src/Magnum/Shaders/Flat.vert +++ b/src/Magnum/Shaders/Flat.vert @@ -27,13 +27,30 @@ #extension GL_EXT_gpu_shader4: require #endif +#if defined(UNIFORM_BUFFERS) && defined(TEXTURE_ARRAYS) && !defined(GL_ES) +#extension GL_ARB_shader_bit_encoding: require +#endif + +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + #ifndef NEW_GLSL #define in attribute #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -64,6 +81,66 @@ uniform mediump mat3 textureMatrix ; #endif +#ifdef TEXTURE_ARRAYS +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 2) +#endif +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +uniform highp uint textureLayer; /* defaults to zero */ +#endif + +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetLayerReserved; + #define textureTransformation_offset offsetLayerReserved.xy + #define textureTransformation_layer offsetLayerReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -115,13 +192,25 @@ in highp mat4 instancedTransformationMatrix; #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = TEXTURE_OFFSET_ATTRIBUTE_LOCATION) #endif -in mediump vec2 instancedTextureOffset; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + instancedTextureOffset; #endif /* Outputs */ #ifdef TEXTURED -out mediump vec2 interpolatedTextureCoordinates; +out mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #endif #ifdef VERTEX_COLOR @@ -132,7 +221,39 @@ out lowp vec4 interpolatedVertexColor; flat out highp uint interpolatedInstanceObjectId; #endif +#ifdef MULTI_DRAW +flat out highp uint drawId; +#endif + void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + drawId = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = mat3(transformationProjectionMatrices[drawId]); + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationProjectionMatrix = transformationProjectionMatrices[drawId]; + #else + #error + #endif + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawId].rotationScaling.xy, 0.0, textureTransformations[drawId].rotationScaling.zw, 0.0, textureTransformations[drawId].textureTransformation_offset, 1.0); + #ifdef TEXTURE_ARRAYS + highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); + #endif + #endif + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix* #ifdef INSTANCED_TRANSFORMATION @@ -151,17 +272,25 @@ void main() { #ifdef TEXTURED /* Texture coordinates, if needed */ - interpolatedTextureCoordinates = + interpolatedTextureCoordinates.xy = #ifdef TEXTURE_TRANSFORMATION (textureMatrix*vec3( #ifdef INSTANCED_TEXTURE_OFFSET - instancedTextureOffset + + instancedTextureOffset.xy + #endif textureCoordinates, 1.0)).xy #else textureCoordinates #endif ; + #ifdef TEXTURE_ARRAYS + interpolatedTextureCoordinates.z = float( + #ifdef INSTANCED_TEXTURE_OFFSET + uint(instancedTextureOffset.z) + + #endif + textureLayer + ); + #endif #endif #ifdef VERTEX_COLOR diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index cb054eaf9..cf281c50d 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -39,16 +39,78 @@ #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#include "Magnum/GL/TextureArray.h" +#endif + namespace Magnum { namespace Shaders { namespace { enum: Int { TextureUnit = 0 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4 + }; + #endif } -template FlatGL::FlatGL(const Flags flags): _flags(flags) { +template FlatGL::FlatGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, _drawCount{drawCount} + #endif +{ CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & Flag::Textured), "Shaders::FlatGL: texture transformation enabled but the shader is not textured", ); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::FlatGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::FlatGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags & Flag::TextureArrays) || (flags & Flag::Textured), + "Shaders::FlatGL: texture arrays enabled but the shader is not textured", ); + CORRADE_ASSERT(!(flags & Flag::UniformBuffers) || !(flags & Flag::TextureArrays) || flags >= (Flag::TextureArrays|Flag::TextureTransformation), + "Shaders::FlatGL: texture arrays require texture transformation enabled as well if uniform buffers are used", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::TextureArrays) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::EXT::texture_array); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -70,22 +132,49 @@ template FlatGL::FlatGL(const Flags flags): vert.addSource(flags & Flag::Textured ? "#define TEXTURED\n" : "") .addSource(flags & Flag::VertexColor ? "#define VERTEX_COLOR\n" : "") .addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") + #ifndef MAGNUM_TARGET_GLES2 + .addSource(flags & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + #endif .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") #ifndef MAGNUM_TARGET_GLES2 .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif .addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") - .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") - .addSource(rs.get("generic.glsl")) + .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + vert.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Flat.vert")); frag.addSource(flags & Flag::Textured ? "#define TEXTURED\n" : "") + #ifndef MAGNUM_TARGET_GLES2 + .addSource(flags & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + #endif .addSource(flags & Flag::AlphaMask ? "#define ALPHA_MASK\n" : "") .addSource(flags & Flag::VertexColor ? "#define VERTEX_COLOR\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(flags & Flag::ObjectId ? "#define OBJECT_ID\n" : "") .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + drawCount, + materialCount)); + frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Flat.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); @@ -125,14 +214,25 @@ template FlatGL::FlatGL(const Flags flags): if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _colorUniform = uniformLocation("color"); - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::TextureArrays) + _textureLayerUniform = uniformLocation("textureLayer"); + #endif + _colorUniform = uniformLocation("color"); + if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + #endif + } } #ifndef MAGNUM_TARGET_GLES @@ -140,37 +240,85 @@ template FlatGL::FlatGL(const Flags flags): #endif { if(flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Magnum::Color4{1.0f}); - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); - /* Object ID is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Texture layer is zero by default */ + setColor(Magnum::Color4{1.0f}); + if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + /* Object ID is zero by default */ + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1, 1} {} +#endif + template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template FlatGL& FlatGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::FlatGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL& FlatGL::setTextureLayer(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setTextureLayer(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::FlatGL::setTextureLayer(): the shader was not created with texture arrays enabled", *this); + setUniform(_textureLayerUniform, id); + return *this; +} +#endif + template FlatGL& FlatGL::setColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_colorUniform, color); return *this; } template FlatGL& FlatGL::setAlphaMask(Float mask) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setAlphaMask(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::AlphaMask, "Shaders::FlatGL::setAlphaMask(): the shader was not created with alpha mask enabled", *this); setUniform(_alphaMaskUniform, mask); @@ -179,6 +327,8 @@ template FlatGL& FlatGL::setAlph #ifndef MAGNUM_TARGET_GLES2 template FlatGL& FlatGL::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(_flags & Flag::ObjectId, "Shaders::FlatGL::setObjectId(): the shader was not created with object ID enabled", *this); setUniform(_objectIdUniform, id); @@ -186,13 +336,99 @@ template FlatGL& FlatGL::setObje } #endif +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL& FlatGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::FlatGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +template FlatGL& FlatGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template FlatGL& FlatGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template FlatGL& FlatGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +template FlatGL& FlatGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + template FlatGL& FlatGL::bindTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::Textured, "Shaders::FlatGL::bindTexture(): the shader was not created with texturing enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::FlatGL::bindTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif texture.bind(TextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL& FlatGL::bindTexture(GL::Texture2DArray& texture) { + CORRADE_ASSERT(_flags & Flag::Textured, + "Shaders::FlatGL::bindTexture(): the shader was not created with texturing enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::FlatGL::bindTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + texture.bind(TextureUnit); + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT FlatGL<2>; template class MAGNUM_SHADERS_EXPORT FlatGL<3>; @@ -214,11 +450,16 @@ Debug& operator<<(Debug& debug, const FlatGLFlag value) { #endif _c(InstancedTransformation) _c(InstancedTextureOffset) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + _c(TextureArrays) + #endif #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const FlatGLFlags value) { @@ -232,7 +473,13 @@ Debug& operator<<(Debug& debug, const FlatGLFlags value) { FlatGLFlag::InstancedObjectId, /* Superset of ObjectId */ FlatGLFlag::ObjectId, #endif - FlatGLFlag::InstancedTransformation}); + FlatGLFlag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 + FlatGLFlag::MultiDraw, /* Superset of UniformBuffers */ + FlatGLFlag::UniformBuffers, + FlatGLFlag::TextureArrays + #endif + }); } } diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index cf08d8362..d0bcc08fb 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -38,7 +38,7 @@ namespace Magnum { namespace Shaders { namespace Implementation { - enum class FlatGLFlag: UnsignedByte { + enum class FlatGLFlag: UnsignedShort { Textured = 1 << 0, AlphaMask = 1 << 1, VertexColor = 1 << 2, @@ -48,7 +48,12 @@ namespace Implementation { InstancedObjectId = (1 << 5)|ObjectId, #endif InstancedTransformation = 1 << 6, - InstancedTextureOffset = (1 << 7)|TextureTransformation + InstancedTextureOffset = (1 << 7)|TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 8, + MultiDraw = UniformBuffers|(1 << 9), + TextureArrays = 1 << 10 + #endif }; typedef Containers::EnumSet FlatGLFlags; } @@ -80,7 +85,7 @@ Common rendering setup: If you want to use a texture, you need to provide also the @ref TextureCoordinates attribute. Pass @ref Flag::Textured to the constructor and then at render time don't forget to bind also the texture via -@ref bindTexture(). The texture is multipled by the color, which is by default +@ref bindTexture(). The texture is multiplied by the color, which is by default set to @cpp 0xffffffff_rgbaf @ce. Common mesh setup: @snippet MagnumShaders-gl.cpp FlatGL-usage-textured1 @@ -151,6 +156,41 @@ color to a mesh: @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} in WebGL 1.0. +@section Shaders-FlatGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, because the shader doesn't need a separate +projection and transformation matrix, a combined one is supplied via a +@ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D +buffer. To maximize use of the limited uniform buffer memory, materials are +supplied separately in a @ref FlatMaterialUniform buffer and then referenced +via @relativeref{FlatDrawUniform,materialId} from a @ref FlatDrawUniform; for +optional texture transformation a per-draw @ref TextureTransformationUniform +can be supplied as well. A uniform buffer setup equivalent to the +@ref Shaders-FlatGL-colored "colored case at the top" would look like this: + +@snippet MagnumShaders-gl.cpp FlatGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw (and possibly +@ref Flag::TextureArrays), supply desired material and draw count in the +@ref FlatGL(Flags, UnsignedInt, UnsignedInt) constructor and specify material +references and texture offsets/layers for every draw. The usage is similar for +all shaders, see @ref shaders-usage-multidraw for an example. + +@requires_gl30 Extension @gl_extension{EXT,texture_array} for texture arrays. +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Neither texture arrays nor uniform buffers are available in + OpenGL ES 2.0. +@requires_webgl20 Neither texture arrays nor uniform buffers are available in + WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders, @ref FlatGL2D, @ref FlatGL3D */ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL::AbstractShaderProgram { @@ -227,8 +267,9 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @brief (Instanced) texture offset * @m_since{2020,06} * - * @ref shaders-generic "Generic attribute", @ref Magnum::Vector2. Used - * only if @ref Flag::InstancedTextureOffset is set. + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector2. Use + * either this or the @ref TextureOffsetLayer attribute. Used only if + * @ref Flag::InstancedTextureOffset is set. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -238,6 +279,24 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ typedef typename GenericGL::TextureOffset TextureOffset; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief (Instanced) texture offset and layer + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector3, with + * the last component interpreted as an integer. Use either this or the + * @ref TextureOffset attribute. First two components used only if + * @ref Flag::InstancedTextureOffset is set, third component only if + * @ref Flag::TextureArrays is set. + * @requires_gl33 Extension @gl_extension{EXT,texture_array} and + * @gl_extension{ARB,instanced_arrays} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + typedef typename GenericGL::TextureOffsetLayer TextureOffsetLayer; + #endif + enum: UnsignedInt { /** * Color shader output. Present always, expects three- or @@ -269,7 +328,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * @see @ref Flags, @ref flags() */ - enum class Flag: UnsignedByte { + enum class Flag: UnsignedShort { /** * Multiply color with a texture. * @see @ref setColor(), @ref bindTexture() @@ -290,8 +349,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: AlphaMask = 1 << 1, /** - * Multiply diffuse color with a vertex color. Requires either - * the @ref Color3 or @ref Color4 attribute to be present. + * Multiply the color with a vertex color. Requires either the + * @ref Color3 or @ref Color4 attribute to be present. * @m_since{2019,10} */ VertexColor = 1 << 2, @@ -319,9 +378,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * Instanced object ID. Retrieves a per-instance / per-vertex * object ID from the @ref ObjectId attribute, outputting a sum of - * the per-vertex ID and ID coming from @ref setObjectId(). - * Implicitly enables @ref Flag::ObjectId. See - * @ref Shaders-FlatGL-object-id for more information. + * the per-vertex ID and ID coming from @ref setObjectId() or + * @ref FlatDrawUniform::objectId. Implicitly enables + * @ref Flag::ObjectId. See @ref Shaders-FlatGL-object-id for more + * information. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL @@ -335,8 +395,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * Instanced transformation. Retrieves a per-instance * transformation matrix from the @ref TransformationMatrix * attribute and uses it together with the matrix coming from - * @ref setTransformationProjectionMatrix() (first the - * per-instance, then the uniform matrix). See + * @ref setTransformationProjectionMatrix() or + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * / @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * (first the per-instance, then the uniform matrix). See * @ref Shaders-FlatGL-instancing for more information. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, @@ -351,12 +413,21 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * Instanced texture offset. Retrieves a per-instance offset vector * from the @ref TextureOffset attribute and uses it together with - * the matrix coming from @ref setTextureMatrix() (first the + * the matrix coming from @ref setTextureMatrix() or + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset (first the * per-instance vector, then the uniform matrix). Instanced texture * scaling and rotation is not supported at the moment, you can * specify that only via the uniform @ref setTextureMatrix(). * Implicitly enables @ref Flag::TextureTransformation. See * @ref Shaders-FlatGL-instancing for more information. + * + * If @ref Flag::TextureArrays is set as well, a three-component + * @ref TextureOffsetLayer attribute can be used instead of + * @ref TextureOffset to specify per-instance texture layer, which + * gets added to the uniform layer numbers set by + * @ref setTextureLayer() or + * @ref TextureTransformationUniform::layer. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -365,7 +436,66 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * in WebGL 1.0. * @m_since{2020,06} */ - InstancedTextureOffset = (1 << 7)|TextureTransformation + InstancedTextureOffset = (1 << 7)|TextureTransformation, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer() + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 8, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 9), + + /** + * Use 2D texture arrays. Expects that the texture is supplied via + * @ref bindTexture(GL::Texture2DArray&) instead of + * @ref bindTexture(GL::Texture2D&) and the layer is set via + * @ref setTextureLayer() or + * @ref TextureTransformationUniform::layer. If + * @ref Flag::InstancedTextureOffset is set as well and a + * three-component @ref TextureOffsetLayer attribute is used + * instead of @ref TextureOffset, the per-instance and uniform + * layer numbers are added together. + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + * @m_since_latest + */ + TextureArrays = 1 << 10 + #endif }; /** @@ -384,9 +514,49 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref FlatGL(Flags, UnsignedInt, UnsignedInt) with @p materialCount + * and @p drawCount set to @cpp 1 @ce. */ explicit FlatGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref FlatMaterialUniform buffer + * bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / @ref FlatDrawUniform + * / @ref TextureTransformationUniform buffer bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials specified via + * @ref FlatDrawUniform::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref FlatGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -416,15 +586,53 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** @brief Flags */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref FlatMaterialUniform uniform + * buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref FlatDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** * @brief Set transformation and projection matrix * @return Reference to self (for method chaining) * - * Initial value is an identity matrix. + * Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance + * transformation matrix coming from the @ref TransformationMatrix + * attribute is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ FlatGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -435,16 +643,52 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an - * identity matrix. + * identity matrix. If @ref Flag::InstancedTextureOffset is set, the + * per-instance offset coming from the @ref TextureOffset attribute is + * applied first, before this matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ FlatGL& setTextureMatrix(const Matrix3& matrix); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set texture array layer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. Initial value is @cpp 0 @ce. If + * @ref Flag::InstancedTextureOffset is set and a three-component + * @ref TextureOffsetLayer attribute is used instead of + * @ref TextureOffset, this value is added to the layer coming from the + * third component. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::layer and call + * @ref bindTextureTransformationBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + FlatGL& setTextureLayer(UnsignedInt layer); + #endif + /** * @brief Set color * @return Reference to self (for method chaining) * * Initial value is @cpp 0xffffffff_rgbaf @ce. If @ref Flag::Textured - * is set, the color will be multiplied with the texture. + * is set, the color is multiplied with the texture. If + * @ref Flag::VertexColor is set, the color is multiplied with a color + * coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatMaterialUniform::color and call @ref bindMaterialBuffer() + * instead. * @see @ref bindTexture() */ FlatGL& setColor(const Magnum::Color4& color); @@ -460,6 +704,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * This corresponds to @m_class{m-doc-external} [glAlphaFunc()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glAlphaFunc.xml) * in classic OpenGL. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatMaterialUniform::alphaMask and call + * @ref bindMaterialBuffer() instead. * @m_keywords{glAlphaFunc()} */ FlatGL& setAlphaMask(Float mask); @@ -473,7 +721,11 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * enabled. Value set here is written to the @ref ObjectIdOutput, see * @ref Shaders-FlatGL-object-id for more information. Default is * @cpp 0 @ce. If @ref Flag::InstancedObjectId is enabled as well, this - * value is combined with ID coming from the @ref ObjectId attribute. + * value is added to the ID coming from the @ref ObjectId attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatDrawUniform::objectId and call @ref bindDrawBuffer() + * instead. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL 1.0. @@ -485,6 +737,125 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref FlatDrawUniform and + * @ref TextureTransformationUniform buffers bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindTextureTransformationBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce, if + * @ref drawCount() is @cpp 1 @ce, the function is a no-op as the + * shader assumes draw offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref FlatDrawUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref FlatMaterialUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -494,16 +865,60 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @return Reference to self (for method chaining) * * Expects that the shader was created with @ref Flag::Textured - * enabled. + * enabled. If @ref Flag::TextureArrays is enabled as well, use + * @ref bindTexture(GL::Texture2DArray&) instead. * @see @ref setColor(), @ref Flag::TextureTransformation, * @ref setTextureMatrix() */ FlatGL& bindTexture(GL::Texture2D& texture); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind a color array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with both @ref Flag::Textured + * and @ref Flag::TextureArrays enabled. If @ref Flag::UniformBuffers + * is not enabled, the layer is set via @ref setTextureLayer(); if + * @ref Flag::UniformBuffers is enabled, + * @ref Flag::TextureTransformation has to be enabled as well and the + * layer is set via @ref TextureTransformationUniform::layer. + * @see @ref setColor(), @ref Flag::TextureTransformation, + * @ref setTextureLayer() + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + FlatGL& bindTexture(GL::Texture2DArray& texture); + #endif + /** * @} */ + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + FlatGL& draw(GL::Mesh& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + FlatGL& draw(GL::Mesh&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + FlatGL& draw(GL::MeshView& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + FlatGL& draw(GL::MeshView&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + FlatGL& draw(Containers::ArrayView> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + FlatGL& draw(std::initializer_list> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + #endif + private: /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -514,12 +929,21 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, - _colorUniform{2}, - _alphaMaskUniform{3}; + #ifndef MAGNUM_TARGET_GLES2 + _textureLayerUniform{2}, + #endif + _colorUniform{3}, + _alphaMaskUniform{4}; #ifndef MAGNUM_TARGET_GLES2 - Int _objectIdUniform{4}; + Int _objectIdUniform{5}; + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; #endif }; diff --git a/src/Magnum/Shaders/Generic.h b/src/Magnum/Shaders/Generic.h index e588298ec..07408548a 100644 --- a/src/Magnum/Shaders/Generic.h +++ b/src/Magnum/Shaders/Generic.h @@ -25,32 +25,474 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Generic, alias @ref Magnum::Shaders::Generic2D, @ref Magnum::Shaders::Generic3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/GenericGL.h, the - * @ref Magnum::Shaders::GenericGL "GenericGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::ProjectionUniform2D, @ref Magnum::Shaders::ProjectionUniform3D, @ref Magnum::Shaders::TransformationUniform2D, @ref Magnum::Shaders::TransformationUniform3D, @ref Magnum::Shaders::TextureTransformationUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/GenericGL.h" +#endif + +namespace Magnum { namespace Shaders { + +/** +@brief 2D projection uniform common for all shaders +@m_since_latest + +Contains the per-view projection matrix. +*/ +struct ProjectionUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit ProjectionUniform2D(DefaultInitT = DefaultInit) noexcept: projectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit ProjectionUniform2D(NoInitT) noexcept: projectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref projectionMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + ProjectionUniform2D& setProjectionMatrix(const Matrix3& matrix) { + projectionMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Projection matrix + * + * Default value is an identity matrix (i.e., an orthographic projection of + * the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ cube). The + * bottom row is unused and acts only as a padding to match uniform buffer + * packing rules. + */ + Matrix3x4 projectionMatrix; +}; + +/** +@brief 3D projection uniform common for all shaders +@m_since_latest + +Contains the per-view projection matrix used by the @ref MeshVisualizerGL3D +and @ref PhongGL shaders that need a separate projection and transformation +matrix. +@see @ref MeshVisualizerGL3D::bindProjectionBuffer(), + @ref PhongGL::bindProjectionBuffer() +*/ +struct ProjectionUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit ProjectionUniform3D(DefaultInitT = DefaultInit) noexcept: projectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit ProjectionUniform3D(NoInitT) noexcept: projectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref projectionMatrix field + * @return Reference to self (for method chaining) + */ + ProjectionUniform3D& setProjectionMatrix(const Matrix4& matrix) { + projectionMatrix = matrix; + return *this; + } + + /** + * @} + */ + + /** + * @brief Projection matrix + * + * Default value is an identity matrix (i.e., an orthographic projection of + * the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ cube). + * @see @ref PhongGL::setProjectionMatrix() + */ + Matrix4 projectionMatrix; +}; + +/** +@brief 2D transformation uniform common for all shaders +@m_since_latest + +Contains the per-draw transformation matrix. +*/ +struct TransformationUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationUniform2D(DefaultInitT = DefaultInit) noexcept: transformationMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationUniform2D(NoInitT) noexcept: transformationMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + TransformationUniform2D& setTransformationMatrix(const Matrix3& matrix) { + transformationMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + */ + Matrix3x4 transformationMatrix; +}; + +/** +@brief 3D transformation uniform common for all shaders +@m_since_latest + +Contains the per-draw transformation matrix used by the @ref MeshVisualizerGL3D +and @ref PhongGL shaders that need a separate projection and transformation +matrix. +@see @ref MeshVisualizerGL3D::bindTransformationBuffer(), + @ref PhongGL::bindTransformationBuffer() +*/ +struct TransformationUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationUniform3D(DefaultInitT = DefaultInit) noexcept: transformationMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationUniform3D(NoInitT) noexcept: transformationMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationMatrix field + * @return Reference to self (for method chaining) + */ + TransformationUniform3D& setTransformationMatrix(const Matrix4& matrix) { + transformationMatrix = matrix; + return *this; + } -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/GenericGL.h, the GenericGL class and related typedefs instead") + /** + * @} + */ + /** + * @brief Transformation matrix + * + * Default value is an identity matrix. + * + * If @ref PhongGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref PhongGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref PhongGL::setTransformationMatrix(), + * @ref MeshVisualizerGL3D::setTransformationMatrix() + */ + Matrix4 transformationMatrix; +}; + +/** +@brief Combined 2D projection and transformation uniform common for all shaders +@m_since_latest + +Used by @ref FlatGL, @ref MeshVisualizerGL2D, @ref DistanceFieldVectorGL, +@ref VectorGL and @ref VertexColorGL that don't need to have a separate +projection matrix supplied. +@see @ref DistanceFieldVectorGL2D::bindTransformationProjectionBuffer(), + @ref FlatGL2D::bindTransformationProjectionBuffer(), + @ref MeshVisualizerGL2D::bindTransformationProjectionBuffer(), + @ref VectorGL2D::bindTransformationProjectionBuffer(), + @ref VertexColorGL2D::bindTransformationProjectionBuffer() +*/ +struct TransformationProjectionUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationProjectionUniform2D(DefaultInitT = DefaultInit) noexcept: transformationProjectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationProjectionUniform2D(NoInitT) noexcept: transformationProjectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationProjectionMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + TransformationProjectionUniform2D& setTransformationProjectionMatrix(const Matrix3& matrix) { + transformationProjectionMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation and projection matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * + * If @ref FlatGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref FlatGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref DistanceFieldVectorGL2D::setTransformationProjectionMatrix(), + * @ref FlatGL2D::setTransformationProjectionMatrix(), + * @ref MeshVisualizerGL2D::setTransformationProjectionMatrix(), + * @ref VectorGL2D::setTransformationProjectionMatrix(), + * @ref VertexColorGL2D::setTransformationProjectionMatrix() + */ + Matrix3x4 transformationProjectionMatrix; +}; + +/** +@brief Combined 3D projection and transformation uniform common for all shaders +@m_since_latest + +Used by @ref FlatGL, @ref DistanceFieldVectorGL, @ref VectorGL and +@ref VertexColorGL that don't need to have a separate projection matrix +supplied. +@see @ref DistanceFieldVectorGL3D::bindTransformationProjectionBuffer(), + @ref FlatGL3D::bindTransformationProjectionBuffer(), + @ref VectorGL3D::bindTransformationProjectionBuffer(), + @ref VertexColorGL3D::bindTransformationProjectionBuffer() +*/ +struct TransformationProjectionUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationProjectionUniform3D(DefaultInitT = DefaultInit) noexcept: transformationProjectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationProjectionUniform3D(NoInitT) noexcept: transformationProjectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationProjectionMatrix field + * @return Reference to self (for method chaining) + */ + TransformationProjectionUniform3D& setTransformationProjectionMatrix(const Matrix4& matrix) { + transformationProjectionMatrix = matrix; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation and projection matrix + * + * Default value is an identity matrix. + * + * If @ref FlatGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref FlatGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref DistanceFieldVectorGL3D::setTransformationProjectionMatrix(), + * @ref FlatGL3D::setTransformationProjectionMatrix(), + * @ref VectorGL3D::setTransformationProjectionMatrix(), + * @ref VertexColorGL3D::setTransformationProjectionMatrix() + */ + Matrix4 transformationProjectionMatrix; +}; + +/** +@brief Texture transformation uniform common for all shaders +@m_since_latest + +Expands upon @ref TransformationUniform2D / @ref TransformationUniform3D with +texture-related parameters. + +Used only if @ref DistanceFieldVectorGL::Flag::TextureTransformation, +@ref FlatGL::Flag::TextureTransformation, +@ref PhongGL::Flag::TextureTransformation or +@ref VectorGL::Flag::TextureTransformation is enabled. +@see @ref DistanceFieldVectorGL::bindTextureTransformationBuffer(), + @ref FlatGL::bindTextureTransformationBuffer(), + @ref PhongGL::bindTextureTransformationBuffer(), + @ref VectorGL::bindTextureTransformationBuffer() +*/ +struct TextureTransformationUniform { + /** @brief Construct with default parameters */ + constexpr explicit TextureTransformationUniform(DefaultInitT = DefaultInit) noexcept: rotationScaling{Math::IdentityInit}, offset{0.0f, 0.0f}, layer{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad0{} /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + {} + /** @brief Construct without initializing the contents */ + explicit TextureTransformationUniform(NoInitT) noexcept: rotationScaling{NoInit}, offset{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref rotationScaling and @ref offset fields + * @return Reference to self (for method chaining) + * + * The @ref rotationScaling field is set to the upper left 2x2 corner of + * @p transformation, @ref offset to the two upper elements of the + * rightmost column of @p transformation. Bottom row is ignored, as it's + * expected to be always @cpp {0.0f, 0.0f, 1.0f} @ce. + */ + TextureTransformationUniform& setTextureMatrix(const Matrix3& transformation) { + rotationScaling = transformation.rotationScaling(); + offset = transformation.translation(); + return *this; + } + + /** + * @brief Set the @ref layer field + * @return Reference to self (for method chaining) + */ + TextureTransformationUniform& setLayer(UnsignedInt layer) { + this->layer = layer; + return *this; + } + + /** + * @} + */ + + /** + * @brief Texture rotation and scaling + * + * The top left part of a 3x3 texture transformation matrix. The + * transformation is split between @ref rotationScaling and @ref offset to + * make it occupy just two @cb{.glsl} vec4 @ce slots in the uniform buffer + * instead of three. Default value is an identity matrix. + * + * If @ref FlatGL::Flag::InstancedTextureOffset / + * @ref PhongGL::Flag::InstancedTextureOffset is enabled, the + * per-instance offset coming from the @ref FlatGL::TextureOffset / + * @ref PhongGL::TextureOffset attribute is applied first, before + * this transformation. + * @see @ref DistanceFieldVectorGL::setTextureMatrix(), + * @ref FlatGL::setTextureMatrix(), @ref PhongGL::setTextureMatrix(), + * @ref VectorGL::setTextureMatrix() + */ + Matrix2x2 rotationScaling; + + /** + * @brief Texture offset + * + * Top two elements of the rightmost column of a 3x3 texture transformation + * matrix. The transformation is split between @ref rotationScaling and + * @ref offset to make it occupy just two @cb{.glsl} vec4 @ce slots in the + * uniform buffer instead of three. Default value is a zero vector. + * + * If @ref FlatGL::Flag::InstancedTextureOffset / + * @ref PhongGL::Flag::InstancedTextureOffset is enabled, the + * per-instance offset coming from the @ref FlatGL::TextureOffset / + * @ref PhongGL::TextureOffset attribute is applied first, before + * this transformation. + * @see @ref DistanceFieldVectorGL::setTextureMatrix(), + * @ref FlatGL::setTextureMatrix(), @ref PhongGL::setTextureMatrix(), + * @ref VectorGL::setTextureMatrix() + */ + Vector2 offset; + + /** + * @brief Texture layer + * + * Descibes which layer of a texture array to use. Default value is + * @cpp 0.5f @ce. + * + * Used only if @ref FlatGL::Flag::TextureArrays / + * @ref PhongGL::Flag::TextureArrays is enabled, ignored otherwise. If + * @ref FlatGL::Flag::InstancedTextureOffset / + * @ref PhongGL::Flag::InstancedTextureOffset is enabled as well, the + * per-instance layer coming from the @ref FlatGL::TextureOffsetLayer / + * @ref PhongGL::TextureOffsetLayer attribute is added to this value. + * @see @ref FlatGL::setTextureLayer(), @ref PhongGL::setTextureLayer() + */ + UnsignedInt layer; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for coordinateSet */ + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /* Deprecated aliases not present here but in GenericGL.h instead, as a lot of existing code relies on these being transitively included from Phong.h etc., and there are no forward declarations in Shaders.h as the type is never used like that. While we *could* include Generic.h from Phong.h, we'd have to also temporarily disable the CORRADE_DEPRECATED_FILE() macro there and it's more pain than it's worth. */ -#else -#error use Magnum/Shaders/GenericGL.h, the GenericGL class and related typedefs instead #endif +}} + #endif diff --git a/src/Magnum/Shaders/GenericGL.h b/src/Magnum/Shaders/GenericGL.h index d40318ae3..eb748f994 100644 --- a/src/Magnum/Shaders/GenericGL.h +++ b/src/Magnum/Shaders/GenericGL.h @@ -186,9 +186,7 @@ rotation and scale 15 -@ref TextureOffset (instanced) - -* *Reserved* --- third component for a layer +@ref TextureOffset / @ref TextureOffsetLayer (instanced) * *Reserved* --- a single component \n @@ -420,8 +418,9 @@ template struct GenericGL { * @brief (Instanced) texture offset * @m_since{2020,06} * - * @ref Magnum::Vector2 "Vector2". Currently doesn't have a corresponding - * @ref Trade::MeshAttribute. + * @ref Magnum::Vector2 "Vector2". Use either this or the + * @ref TextureOffsetLayer attribute. Currently doesn't have a + * corresponding @ref Trade::MeshAttribute. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -430,6 +429,22 @@ template struct GenericGL { * in WebGL 1.0. */ typedef GL::Attribute<15, Vector2> TextureOffset; + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief (Instanced) texture offset and layer + * @m_since_latest + * + * @ref Magnum::Vector3 "Vector3", with the last component interpreted as + * an integer. Use either this or the @ref TextureOffset attribute. + * Currently doesn't have a corresponding @ref Trade::MeshAttribute. + * @requires_gl33 Extension @gl_extension{EXT,texture_array} and + * @gl_extension{ARB,instanced_arrays} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + typedef GL::Attribute<15, Vector3> TextureOffsetLayer; + #endif }; #endif @@ -462,6 +477,9 @@ struct BaseGenericGL { #endif typedef GL::Attribute<15, Vector2> TextureOffset; + #ifndef MAGNUM_TARGET_GLES2 + typedef GL::Attribute<15, Vector3> TextureOffsetLayer; + #endif }; template<> struct GenericGL<2>: BaseGenericGL { diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index c206409bd..db16422a8 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -32,10 +32,6 @@ #define const #endif -#ifndef EXPLICIT_UNIFORM_LOCATION -#define layout(arg) -#endif - #if defined(WIREFRAME_RENDERING) && defined(GL_ES) && __VERSION__ < 300 #extension GL_OES_standard_derivatives : enable #endif @@ -46,6 +42,7 @@ /* Uniforms */ +#ifndef UNIFORM_BUFFERS #if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) @@ -99,7 +96,7 @@ uniform lowp float smoothness #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 5) #endif uniform lowp vec2 colorMapOffsetScale #ifndef GL_ES @@ -110,10 +107,76 @@ uniform lowp vec2 colorMapOffsetScale #define colorMapScale colorMapOffsetScale.y #endif +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.geom. Can't + "outsource" to a common file because the extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see Phong.vert for details */ + highp mat3x4 normalMatrix; + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.geom. Can't + "outsource" to a common file because the extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Textures */ #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 4) #endif uniform lowp sampler2D colorMapTexture; @@ -147,6 +210,10 @@ in lowp vec4 backgroundColor; in lowp vec4 lineColor; #endif +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + /* Outputs */ #ifdef NEW_GLSL @@ -157,6 +224,30 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + #if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) + lowp const vec4 color = materials[materialId].color; + lowp const vec4 wireframeColor = materials[materialId].wireframeColor; + #endif + #ifdef WIREFRAME_RENDERING + lowp const float wireframeWidth = materials[materialId].material_wireframeWidth; + #elif defined(TBN_DIRECTION) + lowp const float lineWidth = materials[materialId].material_lineWidth; + #endif + #if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) + lowp const float smoothness = materials[materialId].material_smoothness; + #endif + #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + lowp const float colorMapOffset = materials[materialId].material_colorMapOffset; + lowp const float colorMapScale = materials[materialId].material_colorMapScale; + #endif + #endif + /* Map object/vertex/primitive ID to a color. Will be either combined with the wireframe background color (if wireframe is enabled), ignored (if rendering TBN direction) or used as-is if nothing else is enabled */ diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index e5b65c426..ac662e2ad 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -36,13 +36,14 @@ /* Uniforms */ +/* This one is for both classic and UBOs, as it's usually set globally instead + of changing per-draw */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 5) +layout(location = 0) #endif uniform lowp vec2 viewportSize; /* defaults to zero */ -layout(triangles) in; - +#ifndef UNIFORM_BUFFERS #if (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) && (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) @@ -83,8 +84,71 @@ uniform lowp float smoothness ; #endif +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#endif + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see Phong.vert for details */ + highp mat3x4 normalMatrix; + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Inputs */ +layout(triangles) in; + #ifdef TANGENT_DIRECTION in highp vec4 tangentEndpoint[]; #endif @@ -105,6 +169,10 @@ in highp float interpolatedVsMappedVertexId[]; flat in highp uint interpolatedVsPrimitiveId[]; #endif +#ifdef MULTI_DRAW +flat in highp uint vsDrawId[]; +#endif + /* Outputs */ layout(triangle_strip, max_vertices = MAX_VERTICES) out; @@ -125,11 +193,25 @@ out highp float interpolatedMappedVertexId; flat out highp uint interpolatedPrimitiveId; #endif +#ifdef MULTI_DRAW +flat out highp uint drawId; +#endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) out lowp vec4 backgroundColor; out lowp vec4 lineColor; -void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointScreen) { +void emitQuad( + #ifdef UNIFORM_BUFFERS + uint materialId, + #endif + vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointScreen +) { + #if defined(UNIFORM_BUFFERS) && (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) + lowp const float lineWidth = materials[materialId].material_lineWidth; + lowp const float smoothness = materials[materialId].material_smoothness; + #endif + /* Calculate screen-space locations for the bar vertices and form two triangles out of them. In case TBN is rendered alone, half bar width is lineWidth + smoothness to allow for antialiasing, in case it's rendered @@ -183,6 +265,24 @@ void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointSc #endif void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + drawId = vsDrawId[0]; + #else + #define drawId drawOffset + #endif + + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #if (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) && (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) + lowp const vec4 color = materials[materialId].color; + lowp const vec4 wireframeColor = materials[materialId].wireframeColor; + #endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + lowp const float lineWidth = materials[materialId].material_lineWidth; + lowp const float smoothness = materials[materialId].material_smoothness; + #endif + #endif + /* Passthrough for unchanged variables */ #ifdef INSTANCED_OBJECT_ID interpolatedInstanceObjectId = interpolatedVsInstanceObjectId[0]; @@ -261,17 +361,32 @@ void main() { for(int i = 0; i != 3; ++i) { #ifdef TANGENT_DIRECTION lineColor = vec4(1.0, 0.0, 0.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], tangentEndpoint[i], t[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], tangentEndpoint[i], t[i] + ); #endif #ifdef BITANGENT_DIRECTION lineColor = vec4(0.0, 1.0, 0.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], bitangentEndpoint[i], b[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], bitangentEndpoint[i], b[i] + ); #endif #ifdef NORMAL_DIRECTION lineColor = vec4(0.0, 0.0, 1.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], normalEndpoint[i], n[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], normalEndpoint[i], n[i] + ); #endif } #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index 3a725bdef..9b0a540c4 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -25,27 +25,495 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::MeshVisualizer2D, @ref Magnum::Shaders::MeshVisualizer3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/MeshVisualizerGL.h and - * the @ref Magnum::Shaders::MeshVisualizerGL2D "MeshVisualizerGL2D" / - * @ref Magnum::Shaders::MeshVisualizerGL3D "MeshVisualizerGL3D" class - * instead + * @brief Struct @ref Magnum::Shaders::MeshVisualizerDrawUniform2D, @ref Magnum::Shaders::MeshVisualizerDrawUniform3D, @ref Magnum::Shaders::MeshVisualizerMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/MeshVisualizerGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/MeshVisualizerGL.h and the MeshVisualizerGL2D / MeshVisualizer3D class instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for 2D mesh visualizer shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D contains +parameters that are specific to each draw call. Material-related properties are +expected to be shared among multiple draw calls and thus are provided in a +separate @ref MeshVisualizerMaterialUniform structure, referenced by +@ref materialId. +@see @ref MeshVisualizerGL2D::bindDrawBuffer() +*/ +struct MeshVisualizerDrawUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerDrawUniform2D(DefaultInitT = DefaultInit) noexcept: + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + #ifndef CORRADE_TARGET_BIG_ENDIAN + , _pad0{} + #endif + , _pad1{}, _pad2{}, _pad3{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerDrawUniform2D(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform2D& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref MeshVisualizerMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to the @ref MeshVisualizerGL2D::MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) + * / @ref MeshVisualizerGL3D::MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) + * constructor, if material count is @cpp 1 @ce, this field is assumed to + * be @cpp 0 @ce and isn't even read by the shader. Default value is + * @cpp 0 @ce, meaning the first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + alignas(4) UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + alignas(4) UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Per-draw uniform for 3D mesh visualizer shaders +@m_since_latest + +Together with the generic @ref TransformationUniform3D contains parameters that +are specific to each draw call. Material-related properties are expected to be +shared among multiple draw calls and thus are provided in a separate +@ref MeshVisualizerMaterialUniform structure, referenced by @ref materialId. +@see @ref MeshVisualizerGL3D::bindDrawBuffer() +*/ +struct MeshVisualizerDrawUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerDrawUniform3D(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit} + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + , _pad0{} /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + , materialId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + #ifndef CORRADE_TARGET_BIG_ENDIAN + , _pad0{} + #endif + , _pad1{}, _pad2{}, _pad3{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerDrawUniform3D(NoInitT) noexcept: normalMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref normalMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + MeshVisualizerDrawUniform3D& setNormalMatrix(const Matrix3x3& matrix) { + normalMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform3D& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** + * @brief Normal matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * @see @ref MeshVisualizerGL3D::setNormalMatrix() + */ + Matrix3x4 normalMatrix; + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref MeshVisualizerMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to @ref MeshVisualizerGL2D / + * @ref MeshVisualizerGL3D constructor. Default value is @cpp 0 @ce, + * meaning the first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Material uniform for mesh visualizer shaders +@m_since_latest + +Describes material properties referenced from +@ref MeshVisualizerDrawUniform2D::materialId and +@ref MeshVisualizerDrawUniform3D::materialId. +*/ +struct MeshVisualizerMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, wireframeColor{0.0f, 0.0f, 0.0f, 1.0f}, wireframeWidth{1.0f}, colorMapOffset{1.0f/512.0f}, colorMapScale{1.0f/256.0f}, lineWidth{1.0f}, lineLength{1.0f}, smoothness{2.0f} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + /* Otherwise it refuses to constexpr, on 3.8 at least */ + , _pad0{}, _pad1{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerMaterialUniform(NoInitT) noexcept: color{NoInit}, wireframeColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref wireframeColor field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setWireframeColor(const Color4& color) { + wireframeColor = color; + return *this; + } + + /** + * @brief Set the @ref wireframeWidth field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setWireframeWidth(Float width) { + wireframeWidth = width; + return *this; + } + + /** + * @brief Set the @ref colorMapOffset and @ref colorMapScale fields + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setColorMapTransformation(Float offset, Float scale) { + colorMapOffset = offset; + colorMapScale = scale; + return *this; + } + + /** + * @brief Set the @ref lineWidth field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setLineWidth(Float width) { + lineWidth = width; + return *this; + } + + /** + * @brief Set the @ref lineLength field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setLineLength(Float length) { + lineLength = length; + return *this; + } + + /** + * @brief Set the @ref smoothness field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setSmoothness(Float smoothness) { + this->smoothness = smoothness; + return *this; + } + + /** + * @} + */ + + /** + * @brief Base object color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * or @relativeref{MeshVisualizerGL3D,Flag::InstancedObjectId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. In case of the latter, the color is multiplied with the color + * map coming from @ref MeshVisualizerGL3D::bindColorMapTexture() "MeshVisualizerGL*D::bindColorMapTexture()". + * @see @ref MeshVisualizerGL2D::setColor(), + * @ref MeshVisualizerGL3D::setColor() + */ + Color4 color; + + /** + * @brief Wireframe color + * + * Default value is @cpp 0x000000ff_rgbaf @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * is enabled. + * @see @ref MeshVisualizerGL2D::setWireframeColor(), + * @ref MeshVisualizerGL3D::setWireframeColor() + */ + Color4 wireframeColor; + + /** + * @brief Wireframe width + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * default value is @cpp 1.0f @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * is enabled. + * @see @ref MeshVisualizerGL2D::setWireframeWidth(), + * @ref MeshVisualizerGL3D::setWireframeWidth() + */ + Float wireframeWidth; + + /** + * @brief Color map offset + * + * Together with @ref colorMapScale forms an offset and scale applied to + * the input value coming either from the + * @ref MeshVisualizerGL3D::ObjectId "MeshVisualizerGL*D::ObjectId" + * attribute or @glsl gl_PrimitiveID @ce, resulting value is then used to + * fetch a color from a color map bound with @ref MeshVisualizerGL3D::bindColorMapTexture() "MeshVisualizerGL*D::bindColorMapTexture()". + * Default offset and scale values are @cpp 1.0f/512.0f @ce and + * @cpp 1.0/256.0f @ce, meaning that for a 256-entry colormap the first 256 + * values get an exact color from it and the next values will be either + * clamped to last color or repeated depending on the color map texture + * wrapping mode. + * + * Used only if @ref MeshVisualizerGL3D::Flag::InstancedObjectId "MeshVisualizerGL*D::Flag::InstancedObjectId" or + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. + * @see @ref MeshVisualizerGL2D::setColorMapTransformation(), + * @ref MeshVisualizerGL3D::setColorMapTransformation() + */ + Float colorMapOffset; + + /** + * @brief Color map offset + * + * See @ref colorMapOffset for more information. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::InstancedObjectId "MeshVisualizerGL*D::Flag::InstancedObjectId" or + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. + * @see @ref MeshVisualizerGL2D::setColorMapTransformation(), + * @ref MeshVisualizerGL3D::setColorMapTransformation() + */ + Float colorMapScale; + + /** + * @brief Line width + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * default value is @cpp 1.0f @ce. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL3D::setLineWidth() + */ + Float lineWidth; + + /** + * @brief Line length + * + * The value is in object space, default value is @cpp 1.0f @ce. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL3D::setLineLength() + */ + Float lineLength; + + /** + * @brief Line smoothness + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * initial value is @cpp 2.0f @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe", + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL2D::setSmoothness(), + * @ref MeshVisualizerGL3D::setSmoothness() + */ + Float smoothness; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief MeshVisualizerGL2D * @m_deprecated_since_latest Use @ref MeshVisualizerGL2D instead. */ @@ -60,10 +528,8 @@ typedef CORRADE_DEPRECATED("use MeshVisualizerGL3D instead") MeshVisualizerGL3D * @m_deprecated_since{2020,06} Use @ref MeshVisualizerGL3D instead. */ typedef CORRADE_DEPRECATED("use MeshVisualizerGL3D instead") MeshVisualizerGL3D MeshVisualizer; +#endif }} -#else -#error use Magnum/Shaders/MeshVisualizerGL.h and the MeshVisualizerGL2D / MeshVisualizer3D class instead -#endif #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index a67ebc024..43359b178 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -27,16 +27,29 @@ #extension GL_EXT_gpu_shader4: require #endif +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + #ifndef NEW_GLSL #define in attribute #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef TWO_DIMENSIONS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 0) +layout(location = 6) #endif uniform highp mat3 transformationProjectionMatrix #ifndef GL_ES @@ -45,7 +58,7 @@ uniform highp mat3 transformationProjectionMatrix ; #elif defined(THREE_DIMENSIONS) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 0) +layout(location = 6) #endif uniform highp mat4 transformationMatrix #ifndef GL_ES @@ -66,7 +79,7 @@ uniform highp mat4 projectionMatrix #ifdef VERTEX_ID #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 5) #endif uniform lowp vec2 colorMapOffsetScale #ifndef GL_ES @@ -97,6 +110,99 @@ uniform highp float lineLength ; #endif +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif + +#ifdef TWO_DIMENSIONS +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + highp mat3x4 transformationProjectionMatrices[DRAW_COUNT]; +}; +#elif defined(THREE_DIMENSIONS) +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 0 + #endif +) uniform Projection { + highp mat4 projectionMatrix; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform Transformation { + highp mat4 transformationMatrices[DRAW_COUNT]; +}; +#else +#error +#endif + +/* Keep in sync with MeshVisualizer.geom and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see Phong.vert for details */ + highp mat3x4 normalMatrix; + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.geom and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -188,7 +294,52 @@ out highp vec4 bitangentEndpoint; out highp vec4 normalEndpoint; #endif +#ifdef MULTI_DRAW +flat out highp uint + #ifdef NO_GEOMETRY_SHADER + drawId + #else + vsDrawId + #endif + ; +#endif + void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + #ifdef NO_GEOMETRY_SHADER + drawId + #else + vsDrawId + #define drawId vsDrawId + #endif + = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = mat3(transformationProjectionMatrices[drawId]); + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationMatrix = transformationMatrices[drawId]; + #else + #error + #endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + mediump const mat3 normalMatrix = mat3(draws[drawId].normalMatrix); + #endif + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + lowp float colorMapOffset = materials[materialId].material_colorMapOffset; + lowp float colorMapScale = materials[materialId].material_colorMapScale; + highp float lineLength = materials[materialId].material_lineLength; + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index ef34527af..1fc40b8ec 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -39,6 +39,10 @@ #include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { @@ -48,11 +52,36 @@ namespace { /* First four taken by Phong (A/D/S/N) */ ColorMapTextureUnit = 4 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + ProjectionBufferBinding = 0, + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from the 3D variant which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + TransformationBufferBinding = 1, + DrawBufferBinding = 2, + /* Binding 3 is commonly used by TextureTransformationBufferBinding, + leave it reserved */ + MaterialBufferBinding = 4, + }; + #endif } namespace Implementation { -MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags): _flags{flags} { +MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, + _drawCount{drawCount} + #endif +{ #ifndef MAGNUM_TARGET_GLES2 #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; @@ -64,6 +93,22 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags): _flags{flags} { "Shaders::MeshVisualizerGL: Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive", ); #endif + #ifndef MAGNUM_TARGET_GLES + if(flags >= FlagBase::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= FlagBase::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES2 if(_flags & FlagBase::Wireframe && !(_flags & FlagBase::NoGeometryShader)) { #ifndef MAGNUM_TARGET_GLES @@ -100,11 +145,12 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra #ifndef MAGNUM_TARGET_GLES const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); - /* Extended in MeshVisualizer3D for TBN visualization */ + /* Extended in MeshVisualizerGL3D for TBN visualization */ CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); #elif !defined(MAGNUM_TARGET_WEBGL) - const GL::Version version = context.supportedVersion({GL::Version::GLES310, GL::Version::GLES300, GL::Version::GLES200}); - /* Extended in MeshVisualizer3D for TBN visualization */ + /* ES 3.2 needed for gl_PrimitiveID */ + const GL::Version version = context.supportedVersion({GL::Version::GLES320, GL::Version::GLES310, GL::Version::GLES300, GL::Version::GLES200}); + /* Extended in MeshVisualizerGL3D for TBN visualization */ CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GLES310); #else const GL::Version version = context.supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); @@ -126,6 +172,17 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra "#define SUBSCRIPTING_WORKAROUND\n" : "") #endif ; + #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= FlagBase::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + vert.addSource(_flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") @@ -136,11 +193,26 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra "#define PRIMITIVE_ID\n") : "") #endif ; + #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= FlagBase::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + frag.addSource(_flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif return version; } MeshVisualizerGLBase& MeshVisualizerGLBase::setColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled", *this); @@ -153,6 +225,10 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setColor(const Color4& color) { } MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was not created with wireframe enabled", *this); setUniform(_wireframeColorUniform, color); @@ -160,6 +236,10 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeColor(const Color4& colo } MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeWidth(const Float width) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was not created with wireframe enabled", *this); setUniform(_wireframeWidthUniform, width); @@ -168,12 +248,41 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeWidth(const Float width) #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& MeshVisualizerGLBase::setColorMapTransformation(const Float offset, const Float scale) { + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled", *this); setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); return *this; } +#endif + +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGLBase& MeshVisualizerGLBase::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} +MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& MeshVisualizerGLBase::bindColorMapTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled", *this); @@ -184,7 +293,15 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindColorMapTexture(GL::Texture2D& t } -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags))} { +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags)) + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif +} { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", ); @@ -193,6 +310,16 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", ); #endif + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::MeshVisualizerGL2D: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::MeshVisualizerGL2D: draw count can't be zero", ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Context& context = GL::Context::current(); #endif @@ -215,8 +342,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV nothing actually needs it, as that makes checks much simpler in the shader code */ .addSource((flags & Flag::NoGeometryShader) || !(flags & Flag::Wireframe) ? - "#define NO_GEOMETRY_SHADER\n" : "") - .addSource(rs.get("generic.glsl")) + "#define NO_GEOMETRY_SHADER\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) + frag.addSource("#define TWO_DIMENSIONS\n"); + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -230,8 +361,20 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : - "#define PRIMITIVE_ID\n") : "") - .addSource(rs.get("MeshVisualizer.geom")); + "#define PRIMITIVE_ID\n") : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + geom->addSource(Utility::formatString( + "#define TWO_DIMENSIONS\n" + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + geom->addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -276,50 +419,89 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & (Flag::Wireframe + /* This one is used also in the UBO case as it's usually a global + setting */ + if((flags & Flag::Wireframe) && !(flags & Flag::NoGeometryShader)) + _viewportSizeUniform = uniformLocation("viewportSize"); + + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { + _wireframeColorUniform = uniformLocation("wireframeColor"); + _wireframeWidthUniform = uniformLocation("wireframeWidth"); + _smoothnessUniform = uniformLocation("smoothness"); + } #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + } #endif - )) - _colorUniform = uniformLocation("color"); - if(flags & Flag::Wireframe) { - _wireframeColorUniform = uniformLocation("wireframeColor"); - _wireframeWidthUniform = uniformLocation("wireframeWidth"); - _smoothnessUniform = uniformLocation("smoothness"); - if(!(flags & Flag::NoGeometryShader)) - _viewportSizeUniform = uniformLocation("viewportSize"); } - #ifndef MAGNUM_TARGET_GLES2 + } + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(flags && !context.isExtensionSupported(version)) + #endif + { if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { - _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } #endif } + #endif /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(Matrix3{Math::IdentityInit}); - if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Viewport size is zero by default */ + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(Matrix3{Math::IdentityInit}); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + setColor(Color3(1.0f)); + if(flags & Flag::Wireframe) { + /* Viewport size is zero by default */ + setWireframeColor(Color3{0.0f}); + setWireframeWidth(1.0f); + setSmoothness(2.0f); + } #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif - )) - setColor(Color3(1.0f)); - if(flags & Flag::Wireframe) { - /* Viewport size is zero by default */ - setWireframeColor(Color3{0.0f}); - setWireframeWidth(1.0f); - setSmoothness(2.0f); } - #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) - setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); - #endif #endif } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{flags, 1, 1} {} +#endif + MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { /* Not asserting here, since the relation to wireframe is a bit vague. Also it's an ugly hack that should be removed, ideally. */ @@ -329,11 +511,19 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { } MeshVisualizerGL2D& MeshVisualizerGL2D::setTransformationProjectionMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } MeshVisualizerGL2D& MeshVisualizerGL2D::setSmoothness(const Float smoothness) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif /* This is a bit vaguely related but less vague than setViewportSize() so asserting in this case. */ CORRADE_ASSERT(flags() & Flag::Wireframe, @@ -342,7 +532,45 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::setSmoothness(const Float smoothness) { return *this; } -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags))} { +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D& MeshVisualizerGL2D::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} +#endif + +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags)) + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif +} { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); @@ -358,6 +586,21 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", ); #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + CORRADE_ASSERT(!(flags & Flag::InstancedObjectId) || !(flags & Flag::BitangentDirection), + "Shaders::MeshVisualizerGL3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", ); + #endif + + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::MeshVisualizerGL3D: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::MeshVisualizerGL3D: draw count can't be zero", ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Context& context = GL::Context::current(); #endif @@ -389,7 +632,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV .addSource(flags & Flag::BitangentDirection ? "#define BITANGENT_DIRECTION\n" : "") .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.vert")); frag /* Pass NO_GEOMETRY_SHADER not only when NoGeometryShader but also when @@ -403,7 +647,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) .addSource(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) ? "#define TBN_DIRECTION\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) + frag.addSource("#define THREE_DIMENSIONS\n"); + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -428,8 +677,20 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV "#define PRIMITIVE_ID\n") : "") .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") .addSource(flags & (Flag::BitangentDirection|Flag::BitangentFromTangentDirection) ? "#define BITANGENT_DIRECTION\n" : "") - .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") - .addSource(rs.get("MeshVisualizer.geom")); + .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + geom->addSource(Utility::formatString( + "#define THREE_DIMENSIONS\n" + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + geom->addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -485,90 +746,144 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV if(!context.isExtensionSupported(version)) #endif { - _transformationMatrixUniform = uniformLocation("transformationMatrix"); - _projectionMatrixUniform = uniformLocation("projectionMatrix"); + /* This one is used also in the UBO case as it's usually a global + setting */ + if(((flags & Flag::Wireframe) && !(flags & Flag::NoGeometryShader)) + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + || (flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) + #endif + ) + _viewportSizeUniform = uniformLocation("viewportSize"); + + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationMatrixUniform = uniformLocation("transformationMatrix"); + _projectionMatrixUniform = uniformLocation("projectionMatrix"); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { + _wireframeColorUniform = uniformLocation("wireframeColor"); + _wireframeWidthUniform = uniformLocation("wireframeWidth"); + } + if(flags & (Flag::Wireframe + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + #endif + )) { + _smoothnessUniform = uniformLocation("smoothness"); + } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { + _normalMatrixUniform = uniformLocation("normalMatrix"); + _lineWidthUniform = uniformLocation("lineWidth"); + _lineLengthUniform = uniformLocation("lineLength"); + } + #endif + } + } + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(flags && !context.isExtensionSupported(version)) + #endif + { + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); + } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } + #endif + } + #endif + + /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Viewport size is zero by default */ + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationMatrix(Matrix4{Math::IdentityInit}); + setProjectionMatrix(Matrix4{Math::IdentityInit}); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) - _colorUniform = uniformLocation("color"); + setColor(Color3(1.0f)); if(flags & Flag::Wireframe) { - _wireframeColorUniform = uniformLocation("wireframeColor"); - _wireframeWidthUniform = uniformLocation("wireframeWidth"); + /* Viewport size is zero by default */ + setWireframeColor(Color3{0.0f}); + setWireframeWidth(1.0f); } if(flags & (Flag::Wireframe #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection #endif )) { - _smoothnessUniform = uniformLocation("smoothness"); - if(!(flags & Flag::NoGeometryShader)) - _viewportSizeUniform = uniformLocation("viewportSize"); + setSmoothness(2.0f); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { - _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); - setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); - } + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { - _normalMatrixUniform = uniformLocation("normalMatrix"); - _lineWidthUniform = uniformLocation("lineWidth"); - _lineLengthUniform = uniformLocation("lineLength"); + setNormalMatrix(Matrix3x3{Math::IdentityInit}); + setLineWidth(1.0f); + setLineLength(1.0f); } #endif } - - /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ - #ifdef MAGNUM_TARGET_GLES - setTransformationMatrix(Matrix4{Math::IdentityInit}); - setProjectionMatrix(Matrix4{Math::IdentityInit}); - if(flags & (Flag::Wireframe - #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId - #endif - )) - setColor(Color3(1.0f)); - if(flags & Flag::Wireframe) { - /* Viewport size is zero by default */ - setWireframeColor(Color3{0.0f}); - setWireframeWidth(1.0f); - } - if(flags & (Flag::Wireframe - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection - #endif - )) { - setSmoothness(2.0f); - } - #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) - setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); - #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { - setNormalMatrix(Matrix3x3{Math::IdentityInit}); - setLineWidth(1.0f); - setLineLength(1.0f); - } - #endif #endif } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{flags, 1, 1} {} +#endif + MeshVisualizerGL3D& MeshVisualizerGL3D::setTransformationMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setTransformationMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationMatrixUniform, matrix); return *this; } MeshVisualizerGL3D& MeshVisualizerGL3D::setProjectionMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_projectionMatrixUniform, matrix); return *this; } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) MeshVisualizerGL3D& MeshVisualizerGL3D::setNormalMatrix(const Matrix3x3& matrix) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was not created with TBN direction enabled", *this); setUniform(_normalMatrixUniform, matrix); @@ -590,6 +905,8 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setViewportSize(const Vector2& size) { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) MeshVisualizerGL3D& MeshVisualizerGL3D::setLineWidth(const Float width) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was not created with TBN direction enabled", *this); setUniform(_lineWidthUniform, width); @@ -597,6 +914,8 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setLineWidth(const Float width) { } MeshVisualizerGL3D& MeshVisualizerGL3D::setLineLength(const Float length) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was not created with TBN direction enabled", *this); setUniform(_lineLengthUniform, length); @@ -605,6 +924,10 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setLineLength(const Float length) { #endif MeshVisualizerGL3D& MeshVisualizerGL3D::setSmoothness(const Float smoothness) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif #ifndef CORRADE_NO_ASSERT /* This is a bit vaguely related but less vague than setViewportSize() so asserting in this case. */ @@ -620,6 +943,50 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setSmoothness(const Float smoothness) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D& MeshVisualizerGL3D::bindProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} +#endif + Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { debug << "Shaders::MeshVisualizerGL2D::Flag" << Debug::nospace; @@ -636,6 +1003,10 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { #endif _c(PrimitiveIdFromVertexId) #endif + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -665,6 +1036,10 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flag value) { #endif _c(PrimitiveIdFromVertexId) #endif + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -683,7 +1058,11 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flags value) { MeshVisualizerGL2D::Flag::VertexId, MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL - MeshVisualizerGL2D::Flag::PrimitiveId + MeshVisualizerGL2D::Flag::PrimitiveId, + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL2D::Flag::MultiDraw, /* Superset of UniformBuffers */ + MeshVisualizerGL2D::Flag::UniformBuffers #endif #endif }); @@ -706,7 +1085,11 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flags value) { MeshVisualizerGL3D::Flag::VertexId, MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL - MeshVisualizerGL3D::Flag::PrimitiveId + MeshVisualizerGL3D::Flag::PrimitiveId, + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL3D::Flag::MultiDraw, /* Superset of UniformBuffers */ + MeshVisualizerGL3D::Flag::UniformBuffers #endif #endif }); diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 949954a05..ca7197f55 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -53,14 +53,21 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr InstancedObjectId = 1 << 2, VertexId = 1 << 3, PrimitiveId = 1 << 4, - PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, + /* bit 6, 7, 8, 9 used by 3D-specific TBN visualization */ + UniformBuffers = 1 << 10, + MultiDraw = UniformBuffers|(1 << 11) #endif }; typedef Containers::EnumSet FlagsBase; CORRADE_ENUMSET_FRIEND_OPERATORS(FlagsBase) - explicit MeshVisualizerGLBase(FlagsBase flags); + explicit MeshVisualizerGLBase(FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const; @@ -73,6 +80,12 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr MeshVisualizerGLBase& bindColorMapTexture(GL::Texture2D& texture); #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGLBase& setDrawOffset(UnsignedInt offset); + MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer); + MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + #endif + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -82,13 +95,19 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr #endif FlagsBase _flags; - Int _colorUniform{1}, + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif + Int _viewportSizeUniform{0}, + _colorUniform{1}, _wireframeColorUniform{2}, _wireframeWidthUniform{3}, - _smoothnessUniform{4}, - _viewportSizeUniform{5}; + _smoothnessUniform{4}; #ifndef MAGNUM_TARGET_GLES2 - Int _colorMapOffsetScaleUniform{6}; + Int _colorMapOffsetScaleUniform{5}; + /* Used instead of all other uniforms except viewportSize when + Flag::UniformBuffers is set, so it can alias them */ + Int _drawOffsetUniform{1}; #endif }; @@ -200,10 +219,50 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua /** @copydoc MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId */ #ifndef MAGNUM_TARGET_WEBGL - PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, #else - PrimitiveIdFromVertexId = (1 << 5)|(1 << 4) + PrimitiveIdFromVertexId = (1 << 5)|(1 << 4), + #endif #endif + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 10, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and combines the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 11) #endif }; @@ -215,9 +274,50 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @param flags Flags * * At least @ref Flag::Wireframe is expected to be enabled. + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit MeshVisualizerGL2D(Flags flags); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref MeshVisualizerMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref MeshVisualizerMaterialUniform buffer bound with + * @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer() + * + * At least @ref Flag::Wireframe is expected to be enabled. + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref MeshVisualizerDrawUniform2D::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref MeshVisualizerGL2D(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -249,8 +349,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua return Flag(UnsignedShort(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref MeshVisualizerMaterialUniform + * uniform buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D and + * @ref MeshVisualizerDrawUniform2D uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -258,6 +387,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ MeshVisualizerGL2D& setTransformationProjectionMatrix(const Matrix3& matrix); @@ -280,6 +413,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @ref Flag::PrimitiveId / @ref Flag::PrimitiveIdFromVertexId is * enabled. In case of the latter, the color is multiplied with the * color map coming from @ref bindColorMapTexture(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setColor(color)); @@ -291,6 +428,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * * Initial value is @cpp 0x000000ff_rgbaf @ce. Expects that * @ref Flag::Wireframe is enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeColor and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setWireframeColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeColor(color)); @@ -303,6 +444,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * The value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 1.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeWidth and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setWireframeWidth(Float width) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeWidth(width)); @@ -322,6 +467,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * Value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 2.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setSmoothness(Float smoothness); @@ -329,6 +478,110 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D + * and @ref MeshVisualizerDrawUniform2D buffers bound with + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer() + * should be used for current draw. Expects that + * @ref Flag::UniformBuffers is set and @p offset is less than + * @ref drawCount(). Initial value is @cpp 0 @ce, if @ref drawCount() + * is @cpp 1 @ce, the function is a no-op as the shader assumes draw + * offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setDrawOffset(UnsignedInt offset) { + return static_cast(Implementation::MeshVisualizerGLBase::setDrawOffset(offset)); + } + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref MeshVisualizerDrawUniform2D. At the very least you need to call + * also @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref MeshVisualizerMaterialUniform. At the very least you need to + * call also @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindMaterialBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); + } + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -344,8 +597,30 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @} */ + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + MeshVisualizerGL2D& draw(GL::Mesh& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL2D& draw(GL::Mesh&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL2D& draw(GL::MeshView& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL2D& draw(GL::MeshView&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL2D& draw(Containers::ArrayView> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + MeshVisualizerGL2D& draw(std::initializer_list> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + #endif + private: - Int _transformationProjectionMatrixUniform{0}; + Int _transformationProjectionMatrixUniform{6}; }; /** @@ -394,7 +669,7 @@ this, the mesh will be rendered in a single color. If you don't have geometry shaders, you need to enable @ref Flag::NoGeometryShader (done by default in OpenGL ES 2.0) and use only **non-indexed** triangle meshes -(see @ref MeshTools::duplicate() for a possible solution). Additionaly, if you +(see @ref MeshTools::duplicate() for a possible solution). Additionally, if you have OpenGL < 3.1 or OpenGL ES 2.0, you need to provide also the @ref VertexIndex attribute. @@ -508,6 +783,37 @@ non-indexed @ref MeshPrimitive::Triangles. @requires_gles `gl_PrimitiveID` is not available in WebGL. @requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. +@section Shaders-MeshVisualizerGL3D-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, the shader needs a separate +@ref ProjectionUniform3D and @ref TransformationUniform3D buffer. To maximize +use of the limited uniform buffer memory, materials are supplied separately in +a @ref MeshVisualizerMaterialUniform and then referenced via +@relativeref{MeshVisualizerDrawUniform3D,materialId} from a +@ref MeshVisualizerDrawUniform3D. A uniform buffer setup equivalent to the +@ref Shaders-MeshVisualizerGL3D-wireframe "wireframe case at the top" would +look like this --- note that @ref setViewportSize() is an immediate uniform +here as well, as it's assumed to be set globally and rarely changed: + +@snippet MagnumShaders-gl.cpp MeshVisualizerGL3D-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw, supply desired material +and draw count in the @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) +constructor and specify material references for every draw. The usage is +similar for all shaders, see @ref shaders-usage-multidraw for an example. + +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. +@requires_webgl20 Uniform buffers are not available in WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders, @ref MeshVisualizerGL2D @todo Understand and add support wireframe width/smoothness without GS */ @@ -761,7 +1067,48 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @requires_gles Geometry shaders are not available in WebGL. * @m_since{2020,06} */ - NormalDirection = 1 << 9 + NormalDirection = 1 << 9, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer() and @ref bindMaterialBuffer() instead of + * direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 10, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and combines the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 11) #endif }; @@ -776,6 +1123,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection, @ref Flag::NormalDirection is * expected to be enabled. + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit MeshVisualizerGL3D(Flags flags); @@ -788,6 +1140,46 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{{}} {} #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref MeshVisualizerMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref ProjectionUniform3D / + * @ref TransformationUniform3D / + * @ref MeshVisualizerMaterialUniform buffer bound with + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() + * and @ref bindDrawBuffer() + * + * At least @ref Flag::Wireframe or one of @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection, @ref Flag::NormalDirection is + * expected to be enabled. + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref MeshVisualizerDrawUniform3D::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref MeshVisualizerGL3D(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit MeshVisualizerGL3D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -819,8 +1211,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua return Flag(UnsignedShort(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref MeshVisualizerMaterialUniform + * uniform buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform3D and + * @ref MeshVisualizerDrawUniform3D uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ #ifdef MAGNUM_BUILD_DEPRECATED @@ -841,6 +1262,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindTransformationBuffer() instead. */ MeshVisualizerGL3D& setTransformationMatrix(const Matrix4& matrix); @@ -851,6 +1276,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * Initial value is an identity matrix. (i.e., an orthographic * projection of the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ * cube). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref ProjectionUniform3D::projectionMatrix and call + * @ref bindProjectionBuffer() instead. */ MeshVisualizerGL3D& setProjectionMatrix(const Matrix4& matrix); @@ -865,6 +1294,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * enabled. The matrix doesn't need to be normalized, as * renormalization is done per-fragment anyway. * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform3D::normalMatrix and call + * @ref bindDrawBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -894,6 +1327,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::PrimitiveId / @ref Flag::PrimitiveIdFromVertexId is * enabled. In case of the latter, the color is multiplied with the * color map coming from @ref bindColorMapTexture(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setColor(color)); @@ -905,6 +1342,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * * Initial value is @cpp 0x000000ff_rgbaf @ce. Expects that * @ref Flag::Wireframe is enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeColor and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setWireframeColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeColor(color)); @@ -917,6 +1358,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * The value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 1.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeWidth and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setWireframeWidth(Float width) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeWidth(width)); @@ -943,6 +1388,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref FlatGL::setObjectId() "setObjectId()" uniform that's used to * offset the per-vertex / per-instance ID. Instead, you need to encode * the base offset into the @p offset parameter. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::colorMapOffset and + * @ref MeshVisualizerMaterialUniform::colorMapScale and call + * @ref bindMaterialBuffer() instead. * @requires_gles30 Object ID visualization requires integer attributes * while primitive ID visualization requires the `gl_VertexID` / * `gl_PrimitiveID` builtins, neither of which is available in @@ -969,6 +1419,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::lineWidth and call + * @ref bindMaterialBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -987,6 +1441,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::lineLength and call + * @ref bindMaterialBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -1006,6 +1464,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setSmoothness(Float smoothness); @@ -1013,6 +1475,130 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationUniform3D and + * @ref MeshVisualizerDrawUniform3D buffers bound with + * @ref bindTransformationBuffer() and @ref bindDrawBuffer() should be + * used for current draw. Expects that @ref Flag::UniformBuffers is set + * and @p offset is less than @ref drawCount(). Initial value is + * @cpp 0 @ce, if @ref drawCount() is @cpp 1 @ce, the function is a + * no-op as the shader assumes draw offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setDrawOffset(UnsignedInt offset) { + return static_cast(Implementation::MeshVisualizerGLBase::setDrawOffset(offset)); + } + + /** + * @brief Set a projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain at least one instance of + * @ref ProjectionUniform3D. At the very least you need to call also + * @ref bindTransformationBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref MeshVisualizerDrawUniform3D. At the very least you need to call + * also @ref bindProjectionBuffer(), @ref bindTransformationBuffer() + * and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref MeshVisualizerMaterialUniform. At the very least you need to + * call also @ref bindProjectionBuffer(), + * @ref bindTransformationBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindMaterialBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); + } + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -1044,8 +1630,30 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @} */ + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + MeshVisualizerGL3D& draw(GL::Mesh& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL3D& draw(GL::Mesh&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL3D& draw(GL::MeshView& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL3D& draw(GL::MeshView&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + MeshVisualizerGL3D& draw(Containers::ArrayView> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + MeshVisualizerGL3D& draw(std::initializer_list> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + #endif + private: - Int _transformationMatrixUniform{0}, + Int _transformationMatrixUniform{6}, _projectionMatrixUniform{7}; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) Int _normalMatrixUniform{8}, diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 7f276f0a7..1c21e5b0c 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -39,8 +39,9 @@ /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 4) +layout(location = 5) #endif uniform lowp vec4 ambientColor #ifndef GL_ES @@ -53,9 +54,8 @@ uniform lowp vec4 ambientColor ; #if LIGHT_COUNT - #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 5) +layout(location = 6) #endif uniform lowp vec4 diffuseColor #ifndef GL_ES @@ -63,8 +63,9 @@ uniform lowp vec4 diffuseColor #endif ; +#ifndef NO_SPECULAR #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 7) #endif uniform lowp vec4 specularColor #ifndef GL_ES @@ -73,7 +74,7 @@ uniform lowp vec4 specularColor ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 7) +layout(location = 8) #endif uniform mediump float shininess #ifndef GL_ES @@ -81,10 +82,11 @@ uniform mediump float shininess #endif ; #endif +#endif #ifdef NORMAL_TEXTURE #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 8) +layout(location = 9) #endif uniform mediump float normalTextureScale #ifndef GL_ES @@ -95,7 +97,7 @@ uniform mediump float normalTextureScale #ifdef ALPHA_MASK #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = 10) #endif uniform lowp float alphaMask #ifndef GL_ES @@ -106,16 +108,26 @@ uniform lowp float alphaMask #ifdef OBJECT_ID #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 10) +layout(location = 11) #endif /* mediump is just 2^10, which might not be enough, this is 2^16 */ uniform highp uint objectId; /* defaults to zero */ #endif #if LIGHT_COUNT -/* Needs to be last because it uses locations 11 + LIGHT_COUNT to - 11 + 2*LIGHT_COUNT - 1. Location 11 is lightPositions. Also it can't be - specified as 11 + LIGHT_COUNT because that requires ARB_enhanced_layouts. +/* Needs to be last because it uses locations 12 to 12 + LIGHT_COUNT - 1 */ +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 12) +#endif +uniform highp vec4 lightPositions[LIGHT_COUNT] + #ifndef GL_ES + = vec4[](LIGHT_POSITION_INITIALIZER) + #endif + ; + +/* Needs to be last because it uses locations 12 + LIGHT_COUNT to + 12 + 2*LIGHT_COUNT - 1. Location 12 is lightPositions. Also it can't be + specified as 12 + LIGHT_COUNT because that requires ARB_enhanced_layouts. Same for lightSpecularColors and lightRanges below. */ #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = LIGHT_COLORS_LOCATION) /* I fear this will blow up some drivers */ @@ -126,6 +138,7 @@ uniform lowp vec3 lightColors[LIGHT_COUNT] #endif ; +#ifndef NO_SPECULAR #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = LIGHT_SPECULAR_COLORS_LOCATION) #endif @@ -134,6 +147,7 @@ uniform lowp vec3 lightSpecularColors[LIGHT_COUNT] = vec3[](LIGHT_COLOR_INITIALIZER) #endif ; +#endif #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = LIGHT_RANGES_LOCATION) @@ -145,35 +159,137 @@ uniform lowp float lightRanges[LIGHT_COUNT] ; #endif +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +/* Keep in sync with Phong.vert. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct DrawUniform { + /* Can't be a mat3 because of ANGLE, see Phong.vert for details */ + mediump mat3x4 normalMatrix; + highp uvec4 materialIdReservedObjectIdLightOffsetLightCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCount.y + #define draw_lightOffset materialIdReservedObjectIdLightOffsetLightCount.z + #define draw_lightCount materialIdReservedObjectIdLightOffsetLightCount.w +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 ambientColor; + lowp vec4 diffuseColor; + lowp vec4 specularColor; + mediump vec4 normalTextureScaleShininessAlphaMaskReserved; + #define material_normalTextureScale normalTextureScaleShininessAlphaMaskReserved.x + #define material_shininess normalTextureScaleShininessAlphaMaskReserved.y + #define material_alphaMask normalTextureScaleShininessAlphaMaskReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; + +#if LIGHT_COUNT +struct LightUniform { + highp vec4 position; + lowp vec3 colorReserved; + #define light_color colorReserved.xyz + lowp vec4 specularColorReserved; + #define light_specularColor specularColorReserved.xyz + lowp vec4 rangeReservedReservedReserved; + #define light_range rangeReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 5 + #endif +) uniform Light { + LightUniform lights[LIGHT_COUNT]; +}; +#endif +#endif + /* Textures */ #ifdef AMBIENT_TEXTURE -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 0) #endif -uniform lowp sampler2D ambientTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + ambientTexture; #endif #if LIGHT_COUNT #ifdef DIFFUSE_TEXTURE -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 1) #endif -uniform lowp sampler2D diffuseTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + diffuseTexture; #endif #ifdef SPECULAR_TEXTURE -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 2) #endif -uniform lowp sampler2D specularTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + specularTexture; #endif #ifdef NORMAL_TEXTURE -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 3) #endif -uniform lowp sampler2D normalTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + normalTexture; #endif #endif @@ -189,12 +305,17 @@ in mediump vec3 transformedTangent; in mediump vec3 transformedBitangent; #endif #endif -in highp vec4 lightDirections[LIGHT_COUNT]; -in highp vec3 cameraDirection; +in highp vec3 transformedPosition; #endif #if defined(AMBIENT_TEXTURE) || defined(DIFFUSE_TEXTURE) || defined(SPECULAR_TEXTURE) || defined(NORMAL_TEXTURE) -in mediump vec2 interpolatedTextureCoordinates; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #endif #ifdef VERTEX_COLOR @@ -205,6 +326,10 @@ in lowp vec4 interpolatedVertexColor; flat in highp uint interpolatedInstanceObjectId; #endif +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + /* Outputs */ #ifdef NEW_GLSL @@ -222,6 +347,32 @@ out highp uint fragmentObjectId; #endif void main() { + #ifdef UNIFORM_BUFFERS + #ifdef OBJECT_ID + highp const uint objectId = draws[drawId].draw_objectId; + #endif + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + lowp const vec4 ambientColor = materials[materialId].ambientColor; + #if LIGHT_COUNT + lowp const vec4 diffuseColor = materials[materialId].diffuseColor; + lowp const vec4 specularColor = materials[materialId].specularColor; + mediump const float shininess = materials[materialId].material_shininess; + #endif + #ifdef NORMAL_TEXTURE + mediump float normalTextureScale = materials[materialId].material_normalTextureScale; + #endif + #ifdef ALPHA_MASK + lowp const float alphaMask = materials[materialId].material_alphaMask; + #endif + #if LIGHT_COUNT + mediump const uint lightOffset = draws[drawId].draw_lightOffset; + #endif + #endif + lowp const vec4 finalAmbientColor = #ifdef AMBIENT_TEXTURE texture(ambientTexture, interpolatedTextureCoordinates)* @@ -239,12 +390,14 @@ void main() { interpolatedVertexColor* #endif diffuseColor; + #ifndef NO_SPECULAR lowp const vec4 finalSpecularColor = #ifdef SPECULAR_TEXTURE texture(specularTexture, interpolatedTextureCoordinates)* #endif specularColor; #endif + #endif /* Ambient color */ fragmentColor = finalAmbientColor; @@ -272,30 +425,91 @@ void main() { normalizedTransformedNormal = tbn*(normalize((texture(normalTexture, interpolatedTextureCoordinates).rgb*2.0 - vec3(1.0))*vec3(normalTextureScale, normalTextureScale, 1.0))); #endif + highp const vec3 cameraDirection = normalize(-transformedPosition); + /* Add diffuse color for each light */ - for(int i = 0; i < LIGHT_COUNT; ++i) { + #ifndef LIGHT_CULLING + for(int i = 0; i < LIGHT_COUNT; ++i) + #else + for(uint i = 0u, actualLightCount = min(uint(LIGHT_COUNT), draws[drawId].draw_lightCount); i < actualLightCount; ++i) + #endif + { + lowp const vec3 lightColor = + #ifndef UNIFORM_BUFFERS + lightColors[i] + #else + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_color + #endif + ; + #ifndef NO_SPECULAR + lowp const vec3 lightSpecularColor = + #ifndef UNIFORM_BUFFERS + lightSpecularColors[i] + #else + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_specularColor + #endif + ; + #endif + lowp const float lightRange = + #ifndef UNIFORM_BUFFERS + lightRanges[i] + #else + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_range + #endif + ; + + highp const vec4 lightPosition = + #ifndef UNIFORM_BUFFERS + lightPositions[i] + #else + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].position + #endif + ; + highp const vec4 lightDirection = vec4(lightPosition.xyz - transformedPosition*lightPosition.w, lightPosition.w); + /* Attenuation. Directional lights have the .w component set to 0, use that to make the distance zero -- which will then ensure the attenuation is always 1.0 */ - highp float dist = length(lightDirections[i].xyz)*lightDirections[i].w; + highp const float len = length(lightDirection.xyz); + highp const float dist = len*lightDirection.w; /* If range is 0 for whatever reason, clamp it to a small value to avoid a NaN when dist is 0 as well (which is the case for directional lights). */ - highp float attenuation = clamp(1.0 - pow(dist/max(lightRanges[i], 0.0001), 4.0), 0.0, 1.0); + highp float attenuation = clamp(1.0 - pow(dist/max(lightRange, 0.0001), 4.0), 0.0, 1.0); attenuation = attenuation*attenuation/(1.0 + dist*dist); - highp vec3 normalizedLightDirection = normalize(lightDirections[i].xyz); + highp vec3 normalizedLightDirection = lightDirection.xyz/len; lowp float intensity = max(0.0, dot(normalizedTransformedNormal, normalizedLightDirection))*attenuation; - fragmentColor += vec4(finalDiffuseColor.rgb*lightColors[i]*intensity, finalDiffuseColor.a/float(LIGHT_COUNT)); + fragmentColor.rgb += finalDiffuseColor.rgb*lightColor*intensity; + #ifndef NO_SPECULAR /* Add specular color, if needed */ if(intensity > 0.001) { highp vec3 reflection = reflect(-normalizedLightDirection, normalizedTransformedNormal); /* Use attenuation for the specularity as well */ - mediump float specularity = clamp(pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess), 0.0, 1.0)*attenuation; - fragmentColor += vec4(finalSpecularColor.rgb*lightSpecularColors[i].rgb*specularity, finalSpecularColor.a); + mediump float specularity = clamp(pow(max(0.0, dot(cameraDirection, reflection)), shininess), 0.0, 1.0)*attenuation; + fragmentColor += vec4(finalSpecularColor.rgb*lightSpecularColor.rgb*specularity, finalSpecularColor.a); } + #endif } + + fragmentColor.a += finalDiffuseColor.a; #endif #ifdef ALPHA_MASK diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index 24a5cd9af..b9b76ce93 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -25,33 +25,525 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Phong - * @m_deprecated_since_latest Use @ref Magnum/Shaders/PhongGL.h and the - * @ref Magnum::Shaders::PhongGL "PhongGL" class instead + * @brief Struct @ref Magnum::Shaders::PhongDrawUniform, @ref Magnum::Shaders::PhongMaterialUniform, @ref Magnum::Shaders::PhongLightUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Tags.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/PhongGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/PhongGL.h and the PhongGL class instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for Phong shaders +@m_since_latest + +Together with the generic @ref TransformationUniform3D contains parameters that +are specific to each draw call. Texture transformation, if needed, is supplied +separately in a @ref TextureTransformationUniform; material-related properties +are expected to be shared among multiple draw calls and thus are provided in a +separate @ref PhongMaterialUniform structure, referenced by @ref materialId. +@see @ref PhongGL::bindDrawBuffer() +*/ +struct PhongDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongDrawUniform(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) && !defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, + #endif + objectId{0}, lightOffset{0}, lightCount{0xffffffffu} {} + + /** @brief Construct without initializing the contents */ + explicit PhongDrawUniform(NoInitT) noexcept: normalMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref normalMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + PhongDrawUniform& setNormalMatrix(const Matrix3x3& matrix) { + normalMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + + /** + * @brief Set the @ref lightOffset and @ref lightCount fields + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setLightOffsetCount(UnsignedInt offset, UnsignedInt count) { + lightOffset = offset; + lightCount = count; + return *this; + } + + /** + * @} + */ + + /** + * @brief Normal matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * + * If @ref PhongGL::Flag::InstancedTransformation is enabled, the + * per-instance normal matrix coming from the @ref PhongGL::NormalMatrix + * attribute is applied first, before this one. + * @see @ref PhongGL::setNormalMatrix() + */ + Matrix3x4 normalMatrix; + + /** @var materialId + * @brief Material ID + * + * References a particular material from a @ref PhongMaterialUniform array. + * Useful when a UBO with more than one material is supplied or in a + * multi-draw scenario. Should be less than the material count passed to + * the @ref PhongGL::PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + * constructor, if material count is @cpp 1 @ce, this field is assumed to + * be @cpp 0 @ce and isn't even read by the shader. Default value is + * @cpp 0 @ce, meaning the first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /** + * @brief Object ID + * + * Unlike @ref materialId, this index is used only for the object ID + * framebuffer output, not to access any other uniform data. Default value + * is @cpp 0 @ce. + * + * Used only if @ref PhongGL::Flag::ObjectId is enabled, ignored otherwise. + * If @ref PhongGL::Flag::InstancedObjectId is enabled as well, this value + * is added to the ID coming from the @ref PhongGL::ObjectId attribute. + * @see @ref PhongGL::setObjectId() + */ + UnsignedInt objectId; + + /** + * @brief Light offset + * + * References the first light in the @ref PhongLightUniform array. Should + * be less than the light count passed to @ref PhongGL constructor. Default + * value is @cpp 0 @ce. + * + * Used only if @ref PhongGL::Flag::LightCulling is enabled, otherwise + * light offset is implicitly @cpp 0 @ce. + */ + UnsignedInt lightOffset; + + /** + * @brief Light count + * + * Specifies how many lights after the @p lightOffset are used from the + * @ref PhongLightUniform array. Gets clamped by the shader so it's + * together with @ref lightOffset not larger than the light count passed to + * @ref PhongGL constructor. Default value is @cpp 0xffffffffu @ce. + * + * Used only if @ref PhongGL::Flag::LightCulling is enabled, otherwise + * light count is implicitly @ref PhongGL::lightCount(). + */ + UnsignedInt lightCount; +}; + +/** +@brief Material uniform for Phong shaders +@m_since_latest + +Describes material properties referenced from +@ref PhongDrawUniform::materialId. +@see @ref PhongGL::bindMaterialBuffer() +*/ +struct PhongMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongMaterialUniform(DefaultInitT = DefaultInit) noexcept: ambientColor{0.0f, 0.0f, 0.0f, 0.0f}, diffuseColor{1.0f, 1.0f, 1.0f, 1.0f}, specularColor{1.0f, 1.0f, 1.0f, 0.0f}, normalTextureScale{1.0f}, shininess{80.0f}, alphaMask{0.5f} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad0{} /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit PhongMaterialUniform(NoInitT) noexcept: ambientColor{NoInit}, diffuseColor{NoInit}, specularColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref ambientColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setAmbientColor(const Color4& color) { + ambientColor = color; + return *this; + } + + /** + * @brief Set the @ref diffuseColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setDiffuseColor(const Color4& color) { + diffuseColor = color; + return *this; + } + + /** + * @brief Set the @ref specularColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setSpecularColor(const Color4& color) { + specularColor = color; + return *this; + } + + /** + * @brief Set the @ref normalTextureScale field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setNormalTextureScale(Float scale) { + normalTextureScale = scale; + return *this; + } + + /** + * @brief Set the @ref shininess field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setShininess(Float shininess) { + this->shininess = shininess; + return *this; + } + + /** + * @brief Set the @ref alphaMask field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setAlphaMask(Float alphaMask) { + this->alphaMask = alphaMask; + return *this; + } + + /** + * @} + */ + + /** + * @brief Ambient color + * + * Default value is @cpp 0x00000000_rgbaf @ce. If + * @ref PhongGL::Flag::AmbientTexture is enabled, be sure to set this field + * to @cpp 0xffffffff_rgbaf @ce, otherwise the texture will be ignored. + * + * If @ref PhongGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref PhongGL::Color3 / @ref PhongGL::Color4 + * attribute. + * @see @ref PhongGL::setAmbientColor() + */ + Color4 ambientColor; + + /** + * @brief Diffuse color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * Used only if the effective light count for given draw is not zero, + * ignored otherwise. If @ref PhongGL::Flag::VertexColor is enabled, the + * color is multiplied with a color coming from the @ref PhongGL::Color3 / + * @ref PhongGL::Color4 attribute. + * @see @ref PhongGL::setDiffuseColor() + */ + Color4 diffuseColor; + + /** + * @brief Specular color + * + * Default value is @cpp 0xffffff00_rgbaf @ce. + * + * Used only if the effective light count for given draw is not zero and + * @ref PhongGL::Flag::NoSpecular is not set, ignored otherwise. + * @see @ref PhongGL::setSpecularColor() + */ + Color4 specularColor; + + /** + * @brief Normal texture scale + * + * Affects strength of the normal mapping. Default value is @cpp 1.0f @ce, + * meaning the normal texture is not changed in any way; a value of + * @cpp 0.0f @ce disables the normal texture effect altogether. + * + * Used only if @ref PhongGL::Flag::NormalTexture is enabled and the + * effective light count for given draw is not zero, ignored otherwise. + * @see @ref PhongGL::setNormalTextureScale() + */ + Float normalTextureScale; + + /** + * @brief Shininess + * + * The larger value, the harder surface (smaller specular highlight). + * Default value is @cpp 80.0f @ce. + * + * Used only if the effective light count for given draw is not zero and + * @ref PhongGL::Flag::NoSpecular is not set, ignored otherwise. + * @see @ref PhongGL::setShininess() + */ + Float shininess; + + /** + * @brief Alpha mask value + * + * Fragments with alpha values smaller than the mask value will be + * discarded. Default value is @cpp 0.5f @ce. + * + * Used only if @ref PhongGL::Flag::AlphaMask is enabled, ignored + * otherwise. + * @see @ref PhongGL::setAlphaMask() + */ + Float alphaMask; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Light parameters for Phong shaders +@m_since_latest + +Describes light properties for each light used by the shader, either all +@ref PhongGL::lightCount() or the subrange referenced by the +@ref PhongDrawUniform::lightOffset and @ref PhongDrawUniform::lightCount range +if @ref PhongGL::Flag::LightCulling is enabled. +@see @ref PhongGL::bindLightBuffer() +*/ +struct PhongLightUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongLightUniform(DefaultInitT = DefaultInit) noexcept: position{0.0f, 0.0f, 1.0f, 0.0f}, color{1.0f, 1.0f, 1.0f}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + specularColor{1.0f, 1.0f, 1.0f}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1{}, + #endif + range{Constants::inf()} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad2{}, _pad3{}, _pad4{} + #endif + {} + /** @brief Construct without initializing the contents */ + explicit PhongLightUniform(NoInitT) noexcept: position{NoInit}, color{NoInit}, specularColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref position field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setPosition(const Vector4& position) { + this->position = position; + return *this; + } + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setColor(const Color3& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref specularColor field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setSpecularColor(const Color3& specularColor) { + this->specularColor = specularColor; + return *this; + } + + /** + * @brief Set the @ref range field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setRange(Float range) { + this->range = range; + return *this; + } + + /** + * @} + */ + + /** + * @brief Position + * + * Depending on the fourth component, the value is treated as either a + * camera-relative position of a point light, if the fourth component is + * @cpp 1.0f @ce; or a direction *to* a directional light, if the fourth + * component is @cpp 0.0f @ce. Default value is + * @cpp {0.0f, 0.0f, 1.0f, 0.0f} @ce --- a directional "fill" light coming + * from the camera. + * @see @ref PhongGL::setLightPositions() + */ + Vector4 position; + + /** + * @brief Color + * + * Default value is @cpp 0xffffff_rgbf @ce. + * @see @ref PhongGL::setLightColors() + */ + Color3 color; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for cone inner angle */ + #endif + + /** + * @brief Specular color + * + * Usually you'd set this value to the same as @ref color, but it allows + * for greater flexibility such as disabling specular highlights on certain + * lights. Default value is @cpp 0xffffff_rgbf @ce. + * @see @ref PhongGL::setLightColors() + */ + Color3 specularColor; + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for cone outer angle */ + #endif + + /** + * @brief Attenuation range + * + * Default value is @ref Constants::inf(). + * @see @ref PhongGL::setLightRanges() + */ + Float range; + + /* warning: Member __pad2__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for cone direction */ + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad4 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief PhongGL * @m_deprecated_since_latest Use @ref PhongGL instead. */ typedef CORRADE_DEPRECATED("use PhongGL instead") PhongGL Phong; +#endif }} -#else -#error use Magnum/Shaders/PhongGL.h and the PhongGL class instead -#endif #endif diff --git a/src/Magnum/Shaders/Phong.vert b/src/Magnum/Shaders/Phong.vert index 80f48ce04..4578d03e2 100644 --- a/src/Magnum/Shaders/Phong.vert +++ b/src/Magnum/Shaders/Phong.vert @@ -27,13 +27,30 @@ #extension GL_EXT_gpu_shader4: require #endif +#if defined(UNIFORM_BUFFERS) && defined(TEXTURE_ARRAYS) && !defined(GL_ES) +#extension GL_ARB_shader_bit_encoding: require +#endif + +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + #ifndef NEW_GLSL #define in attribute #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -52,7 +69,7 @@ uniform highp mat4 projectionMatrix #endif ; -#if LIGHT_COUNT +#ifdef HAS_LIGHTS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -74,16 +91,94 @@ uniform mediump mat3 textureMatrix ; #endif -#if LIGHT_COUNT -/* Needs to be last because it uses locations 11 to 11 + LIGHT_COUNT - 1 */ +#ifdef TEXTURE_ARRAYS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 11) +layout(location = 4) #endif -uniform highp vec4 lightPositions[LIGHT_COUNT] +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +uniform highp uint textureLayer; /* defaults to zero */ +#endif + +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset #ifndef GL_ES - = vec4[](LIGHT_POSITION_INITIALIZER) + = 0u #endif ; +#else +#define drawOffset 0u +#endif + +/* Keep in sync with Phong.frag. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct DrawUniform { + /* Of all drivers, I made the crucial mistake of expecting ANGLE to have + non-broken uniform packing. Of course everything including random phone + drivers worked, except ANGLE with a D3D backend, which blew up when + seeing `mat3` here. With all the coding guidelines, rules and automated + bullying from Clang Tidy in place over at Google, it seems the false + sense of security is so strong that they don't even bother testing what + they wrote. Or, how to code, the Google way: + + 1. Thoroughly document the packing rules and how the translation of + every GLSL type to the D3D equivalent is performed: + https://chromium.googlesource.com/angle/angle/+/refs/heads/main/src/libANGLE/renderer/d3d/d3d11/UniformBlockToStructuredBufferTranslation.md#std140-limitation + 2. Forget to actually implement and test the damn thing. + */ + mediump mat3x4 normalMatrix; + highp uvec4 materialIdReservedObjectIdLightOffsetLightCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCount.y + #define draw_lightOffset materialIdReservedObjectIdLightOffsetLightCount.z + #define draw_lightCount materialIdReservedObjectIdLightOffsetLightCount.w +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 0 + #endif +) uniform Projection { + highp mat4 projectionMatrix; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform Transformation { + highp mat4 transformationMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetLayerReserved; + #define textureTransformation_offset offsetLayerReserved.xy + #define textureTransformation_layer offsetLayerReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif #endif /* Inputs */ @@ -93,7 +188,7 @@ layout(location = POSITION_ATTRIBUTE_LOCATION) #endif in highp vec4 position; -#if LIGHT_COUNT +#ifdef HAS_LIGHTS #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = NORMAL_ATTRIBUTE_LOCATION) #endif @@ -125,8 +220,6 @@ in mediump vec3 bitangent; layout(location = TEXTURECOORDINATES_ATTRIBUTE_LOCATION) #endif in mediump vec2 textureCoordinates; - -out mediump vec2 interpolatedTextureCoordinates; #endif #ifdef VERTEX_COLOR @@ -134,8 +227,6 @@ out mediump vec2 interpolatedTextureCoordinates; layout(location = COLOR_ATTRIBUTE_LOCATION) #endif in lowp vec4 vertexColor; - -out lowp vec4 interpolatedVertexColor; #endif #ifdef INSTANCED_OBJECT_ID @@ -143,8 +234,6 @@ out lowp vec4 interpolatedVertexColor; layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) #endif in highp uint instanceObjectId; - -flat out highp uint interpolatedInstanceObjectId; #endif #ifdef INSTANCED_TRANSFORMATION @@ -163,12 +252,36 @@ in highp mat3 instancedNormalMatrix; #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = TEXTURE_OFFSET_ATTRIBUTE_LOCATION) #endif -in mediump vec2 instancedTextureOffset; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + instancedTextureOffset; #endif /* Outputs */ -#if LIGHT_COUNT +#ifdef TEXTURED +out mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; +#endif + +#ifdef VERTEX_COLOR +out lowp vec4 interpolatedVertexColor; +#endif + +#ifdef INSTANCED_OBJECT_ID +flat out highp uint interpolatedInstanceObjectId; +#endif + +#ifdef HAS_LIGHTS out mediump vec3 transformedNormal; #ifdef NORMAL_TEXTURE #ifndef BITANGENT @@ -178,20 +291,51 @@ out mediump vec3 transformedTangent; out mediump vec3 transformedBitangent; #endif #endif -out highp vec4 lightDirections[LIGHT_COUNT]; -out highp vec3 cameraDirection; +out highp vec3 transformedPosition; +#endif + +#ifdef MULTI_DRAW +flat out highp uint drawId; #endif void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + drawId = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + highp const mat4 transformationMatrix = transformationMatrices[drawId]; + #ifdef HAS_LIGHTS + mediump const mat3 normalMatrix = mat3(draws[drawId].normalMatrix); + #endif + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawId].rotationScaling.xy, 0.0, textureTransformations[drawId].rotationScaling.zw, 0.0, textureTransformations[drawId].textureTransformation_offset, 1.0); + #ifdef TEXTURE_ARRAYS + highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); + #endif + #endif + #endif + /* Transformed vertex position */ highp vec4 transformedPosition4 = transformationMatrix* #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif position; - highp vec3 transformedPosition = transformedPosition4.xyz/transformedPosition4.w; + #ifndef HAS_LIGHTS + highp vec3 + #endif + transformedPosition = transformedPosition4.xyz/transformedPosition4.w; - #if LIGHT_COUNT + #ifdef HAS_LIGHTS /* Transformed normal and tangent vector */ transformedNormal = normalMatrix* #ifdef INSTANCED_TRANSFORMATION @@ -218,14 +362,6 @@ void main() { bitangent; #endif #endif - - /* Direction to the light. Directional lights have the last component set - to 0, which gets used to ignore the transformed position. */ - for(int i = 0; i < LIGHT_COUNT; ++i) - lightDirections[i] = vec4(lightPositions[i].xyz - transformedPosition*lightPositions[i].w, lightPositions[i].w); - - /* Direction to the camera */ - cameraDirection = -transformedPosition; #endif /* Transform the position */ @@ -233,17 +369,25 @@ void main() { #ifdef TEXTURED /* Texture coordinates, if needed */ - interpolatedTextureCoordinates = + interpolatedTextureCoordinates.xy = #ifdef TEXTURE_TRANSFORMATION (textureMatrix*vec3( #ifdef INSTANCED_TEXTURE_OFFSET - instancedTextureOffset + + instancedTextureOffset.xy + #endif textureCoordinates, 1.0)).xy #else textureCoordinates #endif ; + #ifdef TEXTURE_ARRAYS + interpolatedTextureCoordinates.z = float( + #ifdef INSTANCED_TEXTURE_OFFSET + uint(instancedTextureOffset.z) + + #endif + textureLayer + ); + #endif #endif #ifdef VERTEX_COLOR diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 750c6c2ad..3cd3a1aa0 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -43,6 +43,11 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Buffer.h" +#include "Magnum/GL/TextureArray.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { @@ -54,12 +59,81 @@ namespace { SpecularTextureUnit = 2, NormalTextureUnit = 3 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + ProjectionBufferBinding = 0, + TransformationBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4, + LightBufferBinding = 5 + }; + #endif } -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _lightCount{lightCount}, _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, _lightSpecularColorsUniform{_lightPositionsUniform + 2*Int(lightCount)}, _lightRangesUniform{_lightPositionsUniform + 3*Int(lightCount)} { +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags}, + _lightCount{lightCount}, + #ifndef MAGNUM_TARGET_GLES2 + _materialCount{materialCount}, + _drawCount{drawCount}, + #endif + _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, + _lightSpecularColorsUniform{_lightPositionsUniform + 2*Int(lightCount)}, + _lightRangesUniform{_lightPositionsUniform + 3*Int(lightCount)} +{ CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)), "Shaders::PhongGL: texture transformation enabled but the shader is not textured", ); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::InstancedObjectId) || !(flags & Flag::Bitangent), + "Shaders::PhongGL: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", ); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::PhongGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::PhongGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags & Flag::TextureArrays) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)), + "Shaders::PhongGL: texture arrays enabled but the shader is not textured", ); + CORRADE_ASSERT(!(flags & Flag::UniformBuffers) || !(flags & Flag::TextureArrays) || flags >= (Flag::TextureArrays|Flag::TextureTransformation), + "Shaders::PhongGL: texture arrays require texture transformation enabled as well if uniform buffers are used", ); + CORRADE_ASSERT(!(flags & Flag::LightCulling) || (flags & Flag::UniformBuffers), + "Shaders::PhongGL: light culling requires uniform buffers to be enabled", ); + #endif + + CORRADE_ASSERT(!(flags & Flag::SpecularTexture) || !(flags & (Flag::NoSpecular)), + "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", ); + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::TextureArrays) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::EXT::texture_array); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -69,6 +143,11 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} const GL::Context& context = GL::Context::current(); + #ifndef MAGNUM_TARGET_GLES + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || context.isExtensionSupported(), + "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); #else @@ -79,8 +158,8 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); #ifndef MAGNUM_TARGET_GLES - std::string lightInitializerVertex, lightInitializerFragment; - if(lightCount) { + std::string lightInitializer; + if(!(flags >= Flag::UniformBuffers) && lightCount) { using namespace Containers::Literals; /* Initializer for the light color / position / range arrays -- we need @@ -93,39 +172,37 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} constexpr Containers::StringView lightColorInitializerItem = "vec3(1.0), "_s; constexpr Containers::StringView lightRangeInitializerItem = "1.0/0.0, "_s; - lightInitializerVertex.reserve( + lightInitializer.reserve( lightPositionInitializerPreamble.size() + - lightCount*(lightPositionInitializerItem.size())); + lightColorInitializerPreamble.size() + + lightRangeInitializerPreamble.size() + + lightCount*(lightPositionInitializerItem.size() + + lightColorInitializerItem.size() + + lightRangeInitializerItem.size())); - lightInitializerVertex.append(lightPositionInitializerPreamble); + lightInitializer.append(lightPositionInitializerPreamble); for(std::size_t i = 0; i != lightCount; ++i) - lightInitializerVertex.append(lightPositionInitializerItem); + lightInitializer.append(lightPositionInitializerItem); /* Drop the last comma and add a newline at the end */ - lightInitializerVertex[lightInitializerVertex.size() - 2] = '\n'; - lightInitializerVertex.resize(lightInitializerVertex.size() - 1); + lightInitializer[lightInitializer.size() - 2] = '\n'; + lightInitializer.resize(lightInitializer.size() - 1); - lightInitializerFragment.reserve( - lightColorInitializerPreamble.size() + - lightRangeInitializerPreamble.size() + - lightCount*(lightColorInitializerItem.size() + - lightRangeInitializerItem.size())); - - lightInitializerFragment.append(lightColorInitializerPreamble); + lightInitializer.append(lightColorInitializerPreamble); for(std::size_t i = 0; i != lightCount; ++i) - lightInitializerFragment.append(lightColorInitializerItem); + lightInitializer.append(lightColorInitializerItem); /* Drop the last comma and add a newline at the end */ - lightInitializerFragment[lightInitializerFragment.size() - 2] = '\n'; - lightInitializerFragment.resize(lightInitializerFragment.size() - 1); + lightInitializer[lightInitializer.size() - 2] = '\n'; + lightInitializer.resize(lightInitializer.size() - 1); - lightInitializerFragment.append(lightRangeInitializerPreamble); + lightInitializer.append(lightRangeInitializerPreamble); for(std::size_t i = 0; i != lightCount; ++i) - lightInitializerFragment.append(lightRangeInitializerItem); + lightInitializer.append(lightRangeInitializerItem); /* Drop the last comma and add a newline at the end */ - lightInitializerFragment[lightInitializerFragment.size() - 2] = '\n'; - lightInitializerFragment.resize(lightInitializerFragment.size() - 1); + lightInitializer[lightInitializer.size() - 2] = '\n'; + lightInitializer.resize(lightInitializer.size() - 1); } #endif @@ -134,14 +211,24 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} .addSource(flags & Flag::Bitangent ? "#define BITANGENT\n" : "") .addSource(flags & Flag::VertexColor ? "#define VERTEX_COLOR\n" : "") .addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(Utility::formatString("#define LIGHT_COUNT {}\n", lightCount)) + #ifndef MAGNUM_TARGET_GLES2 + .addSource(flags & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + #endif + .addSource(lightCount ? "#define HAS_LIGHTS\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif .addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); - #ifndef MAGNUM_TARGET_GLES - if(lightCount) vert.addSource(std::move(lightInitializerVertex)); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount, + lightCount)); + vert.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } #endif vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Phong.vert")); @@ -149,6 +236,9 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} .addSource(flags & Flag::DiffuseTexture ? "#define DIFFUSE_TEXTURE\n" : "") .addSource(flags & Flag::SpecularTexture ? "#define SPECULAR_TEXTURE\n" : "") .addSource(flags & Flag::NormalTexture ? "#define NORMAL_TEXTURE\n" : "") + #ifndef MAGNUM_TARGET_GLES2 + .addSource(flags & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + #endif .addSource(flags & Flag::Bitangent ? "#define BITANGENT\n" : "") .addSource(flags & Flag::VertexColor ? "#define VERTEX_COLOR\n" : "") .addSource(flags & Flag::AlphaMask ? "#define ALPHA_MASK\n" : "") @@ -156,7 +246,24 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} .addSource(flags & Flag::ObjectId ? "#define OBJECT_ID\n" : "") .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(Utility::formatString( + .addSource(flags & Flag::NoSpecular ? "#define NO_SPECULAR\n" : "") + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n" + "#define LIGHT_COUNT {}\n", + drawCount, + materialCount, + lightCount)); + frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : "") + .addSource(flags >= Flag::LightCulling ? "#define LIGHT_CULLING\n" : ""); + } else + #endif + { + frag.addSource(Utility::formatString( "#define LIGHT_COUNT {}\n" "#define LIGHT_COLORS_LOCATION {}\n" "#define LIGHT_SPECULAR_COLORS_LOCATION {}\n" @@ -165,8 +272,10 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} _lightPositionsUniform + lightCount, _lightPositionsUniform + 2*lightCount, _lightPositionsUniform + 3*lightCount)); + } #ifndef MAGNUM_TARGET_GLES - if(lightCount) frag.addSource(std::move(lightInitializerFragment)); + if(!(flags >= Flag::UniformBuffers) && lightCount) + frag.addSource(std::move(lightInitializer)); #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Phong.frag")); @@ -215,27 +324,41 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} if(!context.isExtensionSupported(version)) #endif { - _transformationMatrixUniform = uniformLocation("transformationMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _projectionMatrixUniform = uniformLocation("projectionMatrix"); - _ambientColorUniform = uniformLocation("ambientColor"); - if(lightCount) { - _normalMatrixUniform = uniformLocation("normalMatrix"); - _diffuseColorUniform = uniformLocation("diffuseColor"); - _specularColorUniform = uniformLocation("specularColor"); - _shininessUniform = uniformLocation("shininess"); - if(flags & Flag::NormalTexture) - _normalTextureScaleUniform = uniformLocation("normalTextureScale"); - _lightPositionsUniform = uniformLocation("lightPositions"); - _lightColorsUniform = uniformLocation("lightColors"); - _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); - _lightRangesUniform = uniformLocation("lightRanges"); - } - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else #endif + { + _transformationMatrixUniform = uniformLocation("transformationMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::TextureArrays) + _textureLayerUniform = uniformLocation("textureLayer"); + #endif + _projectionMatrixUniform = uniformLocation("projectionMatrix"); + _ambientColorUniform = uniformLocation("ambientColor"); + if(lightCount) { + _normalMatrixUniform = uniformLocation("normalMatrix"); + _diffuseColorUniform = uniformLocation("diffuseColor"); + if(!(flags & Flag::NoSpecular)) { + _specularColorUniform = uniformLocation("specularColor"); + _shininessUniform = uniformLocation("shininess"); + } + if(flags & Flag::NormalTexture) + _normalTextureScaleUniform = uniformLocation("normalTextureScale"); + _lightPositionsUniform = uniformLocation("lightPositions"); + _lightColorsUniform = uniformLocation("lightColors"); + if(!(flags & Flag::NoSpecular)) + _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); + _lightRangesUniform = uniformLocation("lightRanges"); + } + if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + #endif + } } #ifndef MAGNUM_TARGET_GLES @@ -248,57 +371,108 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} if(flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); if(flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + if(lightCount) + setUniformBlockBinding(uniformBlockIndex("Light"), LightBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - /* Default to fully opaque white so we can see the textures */ - if(flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); - else setAmbientColor(Magnum::Color4{0.0f}); - setTransformationMatrix(Matrix4{Math::IdentityInit}); - setProjectionMatrix(Matrix4{Math::IdentityInit}); - if(lightCount) { - setDiffuseColor(Magnum::Color4{1.0f}); - setSpecularColor(Magnum::Color4{1.0f, 0.0f}); - setShininess(80.0f); - if(flags & Flag::NormalTexture) - setNormalTextureScale(1.0f); - setLightPositions(Containers::Array{DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); - Containers::Array colors{DirectInit, lightCount, Magnum::Color3{1.0f}}; - setLightColors(colors); - setLightSpecularColors(colors); - setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); - /* Light position is zero by default */ - setNormalMatrix(Matrix3x3{Math::IdentityInit}); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + /* Default to fully opaque white so we can see the textures */ + if(flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); + else setAmbientColor(Magnum::Color4{0.0f}); + setTransformationMatrix(Matrix4{Math::IdentityInit}); + setProjectionMatrix(Matrix4{Math::IdentityInit}); + if(lightCount) { + setDiffuseColor(Magnum::Color4{1.0f}); + if(!(flags & Flag::NoSpecular)) { + setSpecularColor(Magnum::Color4{1.0f, 0.0f}); + setShininess(80.0f); + } + if(flags & Flag::NormalTexture) + setNormalTextureScale(1.0f); + setLightPositions(Containers::Array{DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); + Containers::Array colors{DirectInit, lightCount, Magnum::Color3{1.0f}}; + setLightColors(colors); + if(!(flags & Flag::NoSpecular)) + setLightSpecularColors(colors); + setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); + /* Light position is zero by default */ + setNormalMatrix(Matrix3x3{Math::IdentityInit}); + } + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Texture layer is zero by default */ + if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + /* Object ID is zero by default */ } - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); - /* Object ID is zero by default */ #endif } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{flags, lightCount, 1, 1} {} +#endif + PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setAmbientColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_ambientColorUniform, color); return *this; } PhongGL& PhongGL::setDiffuseColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setDiffuseColor(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_diffuseColorUniform, color); return *this; } PhongGL& PhongGL::setSpecularColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setSpecularColor(): the shader was created with uniform buffers enabled", *this); + #endif + CORRADE_ASSERT(!(_flags >= Flag::NoSpecular), + "Shaders::PhongGL::setSpecularColor(): the shader was created with specular disabled", *this); if(_lightCount) setUniform(_specularColorUniform, color); return *this; } PhongGL& PhongGL::setShininess(Float shininess) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setShininess(): the shader was created with uniform buffers enabled", *this); + #endif + CORRADE_ASSERT(!(_flags >= Flag::NoSpecular), + "Shaders::PhongGL::setShininess(): the shader was created with specular disabled", *this); if(_lightCount) setUniform(_shininessUniform, shininess); return *this; } PhongGL& PhongGL::setNormalTextureScale(const Float scale) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setNormalTextureScale(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::NormalTexture, "Shaders::PhongGL::setNormalTextureScale(): the shader was not created with normal texture enabled", *this); if(_lightCount) setUniform(_normalTextureScaleUniform, scale); @@ -306,6 +480,10 @@ PhongGL& PhongGL::setNormalTextureScale(const Float scale) { } PhongGL& PhongGL::setAlphaMask(Float mask) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setAlphaMask(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::AlphaMask, "Shaders::PhongGL::setAlphaMask(): the shader was not created with alpha mask enabled", *this); setUniform(_alphaMaskUniform, mask); @@ -314,6 +492,8 @@ PhongGL& PhongGL::setAlphaMask(Float mask) { #ifndef MAGNUM_TARGET_GLES2 PhongGL& PhongGL::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setObjectId(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(_flags & Flag::ObjectId, "Shaders::PhongGL::setObjectId(): the shader was not created with object ID enabled", *this); setUniform(_objectIdUniform, id); @@ -322,28 +502,59 @@ PhongGL& PhongGL::setObjectId(UnsignedInt id) { #endif PhongGL& PhongGL::setTransformationMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setTransformationMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setNormalMatrix(const Matrix3x3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setNormalMatrix(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_normalMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setProjectionMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_projectionMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::PhongGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::setTextureLayer(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setTextureLayer(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::PhongGL::setTextureLayer(): the shader was not created with texture arrays enabled", *this); + setUniform(_textureLayerUniform, id); + return *this; +} +#endif + PhongGL& PhongGL::setLightPositions(const Containers::ArrayView positions) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightPositions(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == positions.size(), "Shaders::PhongGL::setLightPositions(): expected" << _lightCount << "items but got" << positions.size(), *this); if(_lightCount) setUniform(_lightPositionsUniform, positions); @@ -373,6 +584,10 @@ PhongGL& PhongGL::setLightPositions(const std::initializer_list positio #endif PhongGL& PhongGL::setLightPosition(const UnsignedInt id, const Vector4& position) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightPosition(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightPosition(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightPositionsUniform + id, position); @@ -391,6 +606,10 @@ PhongGL& PhongGL::setLightPosition(const Vector3& position) { #endif PhongGL& PhongGL::setLightColors(const Containers::ArrayView colors) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightColors(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == colors.size(), "Shaders::PhongGL::setLightColors(): expected" << _lightCount << "items but got" << colors.size(), *this); if(_lightCount) setUniform(_lightColorsUniform, colors); @@ -418,6 +637,10 @@ PhongGL& PhongGL::setLightColors(const std::initializer_list col } PhongGL& PhongGL::setLightColor(const UnsignedInt id, const Magnum::Color3& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightColor(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightColorsUniform + id, color); @@ -436,8 +659,14 @@ PhongGL& PhongGL::setLightColor(const Magnum::Color4& color) { #endif PhongGL& PhongGL::setLightSpecularColors(const Containers::ArrayView colors) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == colors.size(), "Shaders::PhongGL::setLightSpecularColors(): expected" << _lightCount << "items but got" << colors.size(), *this); + CORRADE_ASSERT(!(_flags >= Flag::NoSpecular), + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with specular disabled", *this); if(_lightCount) setUniform(_lightSpecularColorsUniform, colors); return *this; } @@ -447,13 +676,23 @@ PhongGL& PhongGL::setLightSpecularColors(const std::initializer_list= Flag::UniformBuffers), + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightSpecularColor(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); + CORRADE_ASSERT(!(_flags >= Flag::NoSpecular), + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with specular disabled", *this); setUniform(_lightSpecularColorsUniform + id, color); return *this; } PhongGL& PhongGL::setLightRanges(const Containers::ArrayView ranges) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightRanges(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == ranges.size(), "Shaders::PhongGL::setLightRanges(): expected" << _lightCount << "items but got" << ranges.size(), *this); if(_lightCount) setUniform(_lightRangesUniform, ranges); @@ -465,43 +704,210 @@ PhongGL& PhongGL::setLightRanges(const std::initializer_list ranges) { } PhongGL& PhongGL::setLightRange(const UnsignedInt id, const Float range) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightRange(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightRangesUniform + id, range); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::PhongGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +PhongGL& PhongGL::bindProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindLightBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, LightBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindLightBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, LightBufferBinding, offset, size); + return *this; +} +#endif + PhongGL& PhongGL::bindAmbientTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::AmbientTexture, "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::PhongGL::bindAmbientTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif texture.bind(AmbientTextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::bindAmbientTexture(GL::Texture2DArray& texture) { + CORRADE_ASSERT(_flags & Flag::AmbientTexture, + "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + texture.bind(AmbientTextureUnit); + return *this; +} +#endif + PhongGL& PhongGL::bindDiffuseTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::DiffuseTexture, "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with diffuse texture enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::PhongGL::bindDiffuseTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif if(_lightCount) texture.bind(DiffuseTextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::bindDiffuseTexture(GL::Texture2DArray& texture) { + CORRADE_ASSERT(_flags & Flag::DiffuseTexture, + "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with diffuse texture enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + if(_lightCount) texture.bind(DiffuseTextureUnit); + return *this; +} +#endif + PhongGL& PhongGL::bindSpecularTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::SpecularTexture, "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with specular texture enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::PhongGL::bindSpecularTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif if(_lightCount) texture.bind(SpecularTextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::bindSpecularTexture(GL::Texture2DArray& texture) { + CORRADE_ASSERT(_flags & Flag::SpecularTexture, + "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with specular texture enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + if(_lightCount) texture.bind(SpecularTextureUnit); + return *this; +} +#endif + PhongGL& PhongGL::bindNormalTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::NormalTexture, "Shaders::PhongGL::bindNormalTexture(): the shader was not created with normal texture enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::PhongGL::bindNormalTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif + if(_lightCount) texture.bind(NormalTextureUnit); + return *this; +} + +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::bindNormalTexture(GL::Texture2DArray& texture) { + CORRADE_ASSERT(_flags & Flag::NormalTexture, + "Shaders::PhongGL::bindNormalTexture(): the shader was not created with normal texture enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::PhongGL::bindNormalTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); if(_lightCount) texture.bind(NormalTextureUnit); return *this; } +#endif PhongGL& PhongGL::bindTextures(GL::Texture2D* ambient, GL::Texture2D* diffuse, GL::Texture2D* specular, GL::Texture2D* normal) { CORRADE_ASSERT(_flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture), "Shaders::PhongGL::bindTextures(): the shader was not created with any textures enabled", *this); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::PhongGL::bindTextures(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif GL::AbstractTexture::bind(AmbientTextureUnit, {ambient, diffuse, specular, normal}); return *this; } @@ -526,11 +932,18 @@ Debug& operator<<(Debug& debug, const PhongGL::Flag value) { #endif _c(InstancedTransformation) _c(InstancedTextureOffset) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + _c(TextureArrays) + _c(LightCulling) + #endif + _c(NoSpecular) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const PhongGL::Flags value) { @@ -548,7 +961,15 @@ Debug& operator<<(Debug& debug, const PhongGL::Flags value) { PhongGL::Flag::InstancedObjectId, /* Superset of ObjectId */ PhongGL::Flag::ObjectId, #endif - PhongGL::Flag::InstancedTransformation}); + PhongGL::Flag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 + PhongGL::Flag::MultiDraw, /* Superset of UniformBuffers */ + PhongGL::Flag::UniformBuffers, + PhongGL::Flag::TextureArrays, + PhongGL::Flag::LightCulling, + #endif + PhongGL::Flag::NoSpecular + }); } }} diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 177aeed60..37a06aa0f 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -66,7 +66,7 @@ If you want to use textures, you need to provide also the the constructor and then at render time don't forget to also call appropriate subset of @ref bindAmbientTexture(), @ref bindDiffuseTexture() and @ref bindSpecularTexture() (or the combined @ref bindTextures()). The texture -is multipled by the color, which is by default set to fully opaque white for +is multiplied by the color, which is by default set to fully opaque white for enabled textures. Mesh setup with a diffuse and a specular texture: @snippet MagnumShaders-gl.cpp PhongGL-usage-texture1 @@ -248,6 +248,45 @@ well to ensure lighting works: @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} in WebGL 1.0. +@section Shaders-PhongGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, the shader needs a separate +@ref ProjectionUniform3D and @ref TransformationUniform3D buffer, lights are +supplied via a @ref PhongLightUniform. To maximize use of the limited uniform +buffer memory, materials are supplied separately in a @ref PhongMaterialUniform +buffer and then referenced via @relativeref{PhongDrawUniform,materialId} from a +@ref PhongDrawUniform; for optional texture transformation a per-draw +@ref TextureTransformationUniform can be supplied as well. A uniform buffer +setup equivalent to the @ref Shaders-PhongGL-colored "colored case at the top", +with one default light, would look like this: + +@snippet MagnumShaders-gl.cpp PhongGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw (and possibly +@ref Flag::TextureArrays) and supply desired light, material and draw count in +the @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) constructor. For +every draw then specify material references and texture offsets/layers. With +@ref Flag::LightCulling it's also possible to perform per-draw light culling by +supplying a subrange into the @ref PhongLightUniform array using +@ref PhongDrawUniform::lightOffset and @relativeref{PhongDrawUniform,lightCount}. +Besides that, the usage is similar for all shaders, see +@ref shaders-usage-multidraw for an example. + +@requires_gl30 Extension @gl_extension{EXT,texture_array} for texture arrays. +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Neither texture arrays nor uniform buffers are available in + OpenGL ES 2.0. +@requires_webgl20 Neither texture arrays nor uniform buffers are available in + WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders */ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { @@ -396,6 +435,24 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ typedef typename GenericGL3D::TextureOffset TextureOffset; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief (Instanced) texture offset and layer + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector3, with + * the last component interpreted as an integer. Use either this or the + * @ref TextureOffset attribute. First two components used only if + * @ref Flag::InstancedTextureOffset is set, third component only if + * @ref Flag::TextureArrays is set. + * @requires_gl33 Extension @gl_extension{EXT,texture_array} and + * @gl_extension{ARB,instanced_arrays} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + typedef typename GenericGL3D::TextureOffsetLayer TextureOffsetLayer; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -427,7 +484,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * @see @ref Flags, @ref flags() */ - enum class Flag: UnsignedShort { + enum class Flag: UnsignedInt { /** * Multiply ambient color with a texture. * @see @ref setAmbientColor(), @ref bindAmbientTexture() @@ -467,8 +524,9 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { AlphaMask = 1 << 3, /** - * Multiply diffuse color with a vertex color. Requires either - * the @ref Color3 or @ref Color4 attribute to be present. + * Multiply the diffuse and ambient color with a vertex color. + * Requires either the @ref Color3 or @ref Color4 attribute to be + * present. * @m_since{2019,10} */ VertexColor = 1 << 5, @@ -508,9 +566,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** * Instanced object ID. Retrieves a per-instance / per-vertex * object ID from the @ref ObjectId attribute, outputting a sum of - * the per-vertex ID and ID coming from @ref setObjectId(). - * Implicitly enables @ref Flag::ObjectId. See - * @ref Shaders-PhongGL-object-id for more information. + * the per-vertex ID and ID coming from @ref setObjectId() or + * @ref PhongDrawUniform::objectId. Implicitly enables + * @ref Flag::ObjectId. See @ref Shaders-PhongGL-object-id for more + * information. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL @@ -525,9 +584,11 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * transformation and normal matrix from the * @ref TransformationMatrix / @ref NormalMatrix attributes and * uses them together with matrices coming from - * @ref setTransformationMatrix() and @ref setNormalMatrix() (first - * the per-instance, then the uniform matrix). See - * @ref Shaders-PhongGL-instancing for more information. + * @ref setTransformationMatrix() and @ref setNormalMatrix() or + * @ref TransformationUniform3D::transformationMatrix and + * @ref PhongDrawUniform::normalMatrix (first the per-instance, + * then the uniform matrix). See @ref Shaders-PhongGL-instancing + * for more information. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -541,12 +602,21 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** * Instanced texture offset. Retrieves a per-instance offset vector * from the @ref TextureOffset attribute and uses it together with - * the matrix coming from @ref setTextureMatrix() (first the + * the matrix coming from @ref setTextureMatrix() or + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset (first the * per-instance vector, then the uniform matrix). Instanced texture * scaling and rotation is not supported at the moment, you can * specify that only via the uniform @ref setTextureMatrix(). * Implicitly enables @ref Flag::TextureTransformation. See * @ref Shaders-PhongGL-instancing for more information. + * + * If @ref Flag::TextureArrays is set as well, a three-component + * @ref TextureOffsetLayer attribute can be used instead of + * @ref TextureOffset to specify per-instance texture layer, which + * gets added to the uniform layer numbers set by + * @ref setTextureLayer() or + * @ref TextureTransformationUniform::layer. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -555,7 +625,97 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * in WebGL 1.0. * @m_since{2020,06} */ - InstancedTextureOffset = (1 << 10)|TextureTransformation + InstancedTextureOffset = (1 << 10)|TextureTransformation, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer(), + * @ref bindMaterialBuffer() and @ref bindLightBuffer() instead of + * direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 12, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 13), + + /** + * Use 2D texture arrays. Expects that the texture is supplied via + * @ref bindAmbientTexture(GL::Texture2DArray&) / + * @ref bindDiffuseTexture(GL::Texture2DArray&) / + * @ref bindSpecularTexture(GL::Texture2DArray&) / + * @ref bindNormalTexture(GL::Texture2DArray&) instead of + * @ref bindAmbientTexture(GL::Texture2D&) / + * @ref bindDiffuseTexture(GL::Texture2D&) / + * @ref bindSpecularTexture(GL::Texture2D&) / + * @ref bindNormalTexture(GL::Texture2D&) and the layer shared by + * all textures is set via @ref setTextureLayer() or + * @ref TextureTransformationUniform::layer. If + * @ref Flag::InstancedTextureOffset is set as well and a + * three-component @ref TextureOffsetLayer attribute is used + * instead of @ref TextureOffset, the per-instance and uniform + * layer numbers are added together. + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + * @m_since_latest + */ + TextureArrays = 1 << 14, + + /** + * Enable light culling in uniform buffer workflows using the + * @ref PhongDrawUniform::lightOffset and + * @ref PhongDrawUniform::lightCount fields. If not enabled, all + * @ref lightCount() lights are used for every draw. Expects that + * @ref Flag::UniformBuffers is enabled as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + LightCulling = 1 << 15, + #endif + + /** + * Disable specular contribution in light calculation. Can result + * in a significant performance improvement compared to calling + * @ref setSpecularColor() with @cpp 0x00000000_rgbaf @ce when + * specular highlights are not desired. + * @m_since_latest + */ + NoSpecular = 1 << 16 }; /** @@ -569,9 +729,54 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @brief Constructor * @param flags Flags * @param lightCount Count of light sources + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit PhongGL(Flags flags = {}, UnsignedInt lightCount = 1); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param lightCount Size of a @ref PhongLightUniform buffer bound + * with @ref bindLightBuffer() + * @param materialCount Size of a @ref PhongMaterialUniform buffer + * bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref ProjectionUniform3D / + * @ref TransformationUniform3D / @ref PhongDrawUniform / + * @ref TextureTransformationUniform buffer bound with + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p lightCount, + * @p materialCount and @p drawCount describe the uniform buffer sizes + * as these are required to have a statically defined size. The draw + * offset is then set via @ref setDrawOffset() and the per-draw + * materials and lights are specified via + * @ref PhongDrawUniform::materialId, + * @ref PhongDrawUniform::lightOffset and + * @ref PhongDrawUniform::lightCount. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref PhongGL(Flags, UnsignedInt). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -601,11 +806,46 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** @brief Flags */ Flags flags() const { return _flags; } - /** @brief Light count */ + /** + * @brief Light count + * + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref PhongLightUniform uniform buffer. + * @see @ref bindLightBuffer() + */ UnsignedInt lightCount() const { return _lightCount; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref PhongMaterialUniform uniform + * buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the @ref ProjectionUniform3D, + * @ref TransformationUniform3D, @ref PhongDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -615,6 +855,12 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * If @ref Flag::AmbientTexture is set, default value is * @cpp 0xffffffff_rgbaf @ce and the color will be multiplied with * ambient texture, otherwise default value is @cpp 0x00000000_rgbaf @ce. + * If @ref Flag::VertexColor is set, the color is multiplied with a + * color coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::ambientColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindAmbientTexture(), @ref Shaders-PhongGL-lights-ambient */ PhongGL& setAmbientColor(const Magnum::Color4& color); @@ -623,9 +869,16 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @brief Set diffuse color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffffff_rgbaf @ce. If @ref lightCount() is - * zero, this function is a no-op, as diffuse color doesn't contribute - * to the output in that case. + * Initial value is @cpp 0xffffffff_rgbaf @ce. If + * @ref Flag::DiffuseTexture is set, the color will be multiplied with + * the texture. If @ref lightCount() is zero, this function is a no-op, + * as diffuse color doesn't contribute to the output in that case. + * If @ref Flag::VertexColor is set, the color is multiplied with a + * color coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::diffuseColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindDiffuseTexture() */ PhongGL& setDiffuseColor(const Magnum::Color4& color); @@ -643,6 +896,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Expects that the shader was created with @ref Flag::NormalTexture * enabled. If @ref lightCount() is zero, this function is a no-op, as * normals don't contribute to the output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::normalTextureScale and call + * @ref bindDrawBuffer() instead. * @see @ref Shaders-PhongGL-normal-mapping, @ref bindNormalTexture(), * @ref Trade::MaterialAttribute::NormalTextureScale */ @@ -652,12 +909,19 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @brief Set specular color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffff00_rgbaf @ce. Color will be multiplied - * with specular texture if @ref Flag::SpecularTexture is set. If you - * want to have a fully diffuse material, set specular color to - * @cpp 0x00000000_rgbaf @ce. If @ref lightCount() is zero, this - * function is a no-op, as specular color doesn't contribute to the - * output in that case. + * Initial value is @cpp 0xffffff00_rgbaf @ce. Expects that the shader + * was not created with @ref Flag::NoSpecular. If + * @ref Flag::SpecularTexture is set, the color will be multiplied with + * the texture. If you want to have a fully diffuse material, it's + * recommended to disable the specular contribution altogether with + * @ref Flag::NoSpecular. If having a dedicated shader variant is not + * possible, set the specular color to @cpp 0x00000000_rgbaf @ce. If + * @ref lightCount() is zero, this function is a no-op, as specular + * color doesn't contribute to the output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::specularColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindSpecularTexture() */ PhongGL& setSpecularColor(const Magnum::Color4& color); @@ -670,6 +934,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Initial value is @cpp 80.0f @ce. If @ref lightCount() is zero, this * function is a no-op, as specular color doesn't contribute to the * output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::shininess and call + * @ref bindMaterialBuffer() instead. */ PhongGL& setShininess(Float shininess); @@ -684,6 +952,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * This corresponds to @m_class{m-doc-external} [glAlphaFunc()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glAlphaFunc.xml) * in classic OpenGL. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::alphaMask and call + * @ref bindMaterialBuffer() instead. * @m_keywords{glAlphaFunc()} */ PhongGL& setAlphaMask(Float mask); @@ -696,7 +968,12 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Expects that the shader was created with @ref Flag::ObjectId * enabled. Value set here is written to the @ref ObjectIdOutput, see * @ref Shaders-PhongGL-object-id for more information. Default is - * @cpp 0 @ce. + * @cpp 0 @ce. If @ref Flag::InstancedObjectId is enabled as well, this + * value is added to the ID coming from the @ref ObjectId attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongDrawUniform::objectId and call @ref bindDrawBuffer() + * instead. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL 1.0. @@ -709,7 +986,14 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @return Reference to self (for method chaining) * * You need to set also @ref setNormalMatrix() with a corresponding - * value. Initial value is an identity matrix. + * value. Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance + * transformation coming from the @ref TransformationMatrix attribute + * is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindTransformationBuffer() instead. */ PhongGL& setTransformationMatrix(const Matrix4& matrix); @@ -722,7 +1006,13 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref setTransformationMatrix() with a corresponding value. Initial * value is an identity matrix. If @ref lightCount() is zero, this * function is a no-op, as normals don't contribute to the output in - * that case. + * that case. If @ref Flag::InstancedTransformation is set, the + * per-instance normal matrix coming from the @ref NormalMatrix + * attribute is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongDrawUniform::normalMatrix and call + * @ref bindDrawBuffer() instead. * @see @ref Math::Matrix4::normalMatrix() */ PhongGL& setNormalMatrix(const Matrix3x3& matrix); @@ -734,6 +1024,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Initial value is an identity matrix (i.e., an orthographic * projection of the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ * cube). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref ProjectionUniform3D::projectionMatrix and call + * @ref bindProjectionBuffer() instead. */ PhongGL& setProjectionMatrix(const Matrix4& matrix); @@ -744,22 +1038,56 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an - * identity matrix. + * identity matrix. If @ref Flag::InstancedTextureOffset is set, the + * per-instance offset coming from the @ref TextureOffset atttribute is + * applied first, before this matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ PhongGL& setTextureMatrix(const Matrix3& matrix); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set texture array layer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. Initial value is @cpp 0 @ce. If + * @ref Flag::InstancedTextureOffset is set and a three-component + * @ref TextureOffsetLayer attribute is used instead of + * @ref TextureOffset, this value is added to the layer coming from the + * third component. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::layer and call + * @ref bindTextureTransformationBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + PhongGL& setTextureLayer(UnsignedInt layer); + #endif + /** * @brief Set light positions * @return Reference to self (for method chaining) * @m_since_latest * * Depending on the fourth component, the value is treated as either a - *camera-relative position of a point light, if the fourth component is - * @cpp 1.0f @ce; or a direction *to* a directional light, if the + * camera-relative position of a point light, if the fourth component + * is @cpp 1.0f @ce; or a direction *to* a directional light, if the * fourth component is @cpp 0.0f @ce. Expects that the size of the * @p positions array is the same as @ref lightCount(). Initial values * are @cpp {0.0f, 0.0f, 1.0f, 0.0f} @ce --- a directional "fill" light * coming from the camera. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::position and call + * @ref bindLightBuffer() instead * @see @ref Shaders-PhongGL-lights, @ref setLightPosition() */ PhongGL& setLightPositions(Containers::ArrayView positions); @@ -799,6 +1127,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * position. If updating more than one light, prefer the batch function * instead to reduce the count of GL API calls. Expects that @p id is * less than @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::position and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightPosition(UnsignedInt id, const Vector4& position); @@ -830,6 +1162,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Initial values are @cpp 0xffffff_rgbf @ce. Expects that the size * of the @p colors array is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::color and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightColor() */ PhongGL& setLightColors(Containers::ArrayView colors); @@ -865,6 +1201,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * updating more than one light, prefer the batch function instead to * reduce the count of GL API calls. Expects that @p id is less than * @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::color and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightColor(UnsignedInt id, const Magnum::Color3& color); @@ -896,6 +1236,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * highlights on certain lights. Initial values are * @cpp 0xffffff_rgbf @ce. Expects that the size of the @p colors array * is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::specularColor and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightColor() */ PhongGL& setLightSpecularColors(Containers::ArrayView colors); @@ -915,6 +1259,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * color. If updating more than one light, prefer the batch function * instead to reduce the count of GL API calls. Expects that @p id is * less than @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::specularColor and call + * @ref bindLightBuffer() instead. */ PhongGL& setLightSpecularColor(UnsignedInt id, const Magnum::Color3& color); @@ -925,6 +1273,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Initial values are @ref Constants::inf(). Expects that the size of * the @p ranges array is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::range and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightRange() */ PhongGL& setLightRanges(Containers::ArrayView ranges); @@ -944,6 +1296,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * updating more than one light, prefer the batch function instead to * reduce the count of GL API calls. Expects that @p id is less than * @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::range and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightRange(UnsignedInt id, Float range); @@ -951,32 +1307,273 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @} */ + #ifndef MAGNUM_TARGET_GLES2 /** @{ - * @name Texture binding + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. */ /** - * @brief Bind a diffuse texture + * @brief Set a draw offset * @return Reference to self (for method chaining) + * @m_since_latest * - * Expects that the shader was created with @ref Flag::DiffuseTexture - * enabled. If @ref lightCount() is zero, this function is a no-op, as - * diffuse color doesn't contribute to the output in that case. - * @see @ref bindTextures(), @ref setDiffuseColor() + * Specifies which item in the @ref TransformationUniform3D, + * @ref PhongDrawUniform and @ref TextureTransformationUniform buffers + * bound with @ref bindTransformationBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() should be used for + * current draw. Expects that @ref Flag::UniformBuffers is set and + * @p offset is less than @ref drawCount(). Initial value is + * @cpp 0 @ce, if @ref drawCount() is @cpp 1 @ce, the function is a + * no-op as the shader assumes draw offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain at least one instance of + * @ref ProjectionUniform3D. At the very least you need to call also + * @ref bindTransformationBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref PhongDrawUniform. At the very least you need to call also + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref PhongMaterialUniform. At the very least you need to call also + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() and + * @ref bindDrawBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a light uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref lightCount() instances of + * @ref PhongLightUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindLightBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindLightBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + + /** @{ + * @name Texture binding */ - PhongGL& bindDiffuseTexture(GL::Texture2D& texture); /** * @brief Bind an ambient texture * @return Reference to self (for method chaining) * * Expects that the shader was created with @ref Flag::AmbientTexture - * enabled. + * enabled. If @ref Flag::TextureArrays is enabled as well, use + * @ref bindAmbientTexture(GL::Texture2DArray&) instead. * @see @ref bindTextures(), @ref setAmbientColor(), * @ref Shaders-PhongGL-lights-ambient */ PhongGL& bindAmbientTexture(GL::Texture2D& texture); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind an ambient array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with both + * @ref Flag::AmbientTexture and @ref Flag::TextureArrays enabled. If + * @ref Flag::UniformBuffers is not enabled, the layer is set via + * @ref setTextureLayer(); if @ref Flag::UniformBuffers is enabled, + * @ref Flag::TextureTransformation has to be enabled as well and the + * layer is set via @ref TextureTransformationUniform::layer. + * @see @ref setAmbientColor(), @ref Shaders-PhongGL-lights-ambient + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + PhongGL& bindAmbientTexture(GL::Texture2DArray& texture); + #endif + + /** + * @brief Bind a diffuse texture + * @return Reference to self (for method chaining) + * + * Expects that the shader was created with @ref Flag::DiffuseTexture + * enabled. If @ref Flag::TextureArrays is enabled as well, use + * @ref bindDiffuseTexture(GL::Texture2DArray&) instead. If + * @ref lightCount() is zero, this function is a no-op, as diffuse + * color doesn't contribute to the output in that case. + * @see @ref bindTextures(), @ref setDiffuseColor() + */ + PhongGL& bindDiffuseTexture(GL::Texture2D& texture); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind a diffuse array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with both + * @ref Flag::DiffuseTexture and @ref Flag::TextureArrays + * enabled. If @ref Flag::UniformBuffers is not enabled, the layer is + * set via @ref setTextureLayer(); if @ref Flag::UniformBuffers is + * enabled, @ref Flag::TextureTransformation has to be enabled as well + * and the layer is set via @ref TextureTransformationUniform::layer. + * If @ref lightCount() is zero, this function is a no-op, as diffuse + * color doesn't contribute to the output in that case. + * @see @ref setDiffuseColor() + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + PhongGL& bindDiffuseTexture(GL::Texture2DArray& texture); + #endif + + /** + * @brief Bind a specular texture + * @return Reference to self (for method chaining) + * + * Expects that the shader was created with @ref Flag::SpecularTexture + * enabled and that @ref Flag::NoSpecular is not set. If + * @ref Flag::TextureArrays is enabled as well, use + * @ref bindSpecularTexture(GL::Texture2DArray&) instead. If + * @ref lightCount() is zero, this function is a no-op, as specular + * color doesn't contribute to the output in that case. + * @see @ref bindTextures(), @ref setSpecularColor() + */ + PhongGL& bindSpecularTexture(GL::Texture2D& texture); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind a specular array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with both + * @ref Flag::SpecularTexture and @ref Flag::TextureArrays enabled and + * that @ref Flag::NoSpecular is not set. If @ref Flag::UniformBuffers + * is not enabled, the layer is set via @ref setTextureLayer(); if + * @ref Flag::UniformBuffers is enabled, + * @ref Flag::TextureTransformation has to be enabled as well and the + * layer is set via @ref TextureTransformationUniform::layer. If + * @ref lightCount() is zero, this function is a no-op, as specular + * color doesn't contribute to the output in that case. + * @see @ref setSpecularColor() + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + PhongGL& bindSpecularTexture(GL::Texture2DArray& texture); + #endif + /** * @brief Bind a normal texture * @return Reference to self (for method chaining) @@ -984,6 +1581,8 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Expects that the shader was created with @ref Flag::NormalTexture * enabled and the @ref Tangent attribute was supplied. If + * @ref Flag::TextureArrays is enabled as well, use + * @ref bindNormalTexture(GL::Texture2DArray&) instead. If * @ref lightCount() is zero, this function is a no-op, as normals * don't contribute to the output in that case. * @see @ref Shaders-PhongGL-normal-mapping, @@ -991,16 +1590,22 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ PhongGL& bindNormalTexture(GL::Texture2D& texture); + #ifndef MAGNUM_TARGET_GLES2 /** - * @brief Bind a specular texture + * @brief Bind a normal array texture * @return Reference to self (for method chaining) + * @m_since_latest * - * Expects that the shader was created with @ref Flag::SpecularTexture - * enabled. If @ref lightCount() is zero, this function is a no-op, as - * specular color doesn't contribute to the output in that case. - * @see @ref bindTextures(), @ref setSpecularColor() + * Expects that the shader was created with both + * @ref Flag::NormalTexture and @ref Flag::TextureArrays enabled and + * the @ref Tangent attribute was supplied. If @ref lightCount() is + * zero, this function is a no-op, as normals don't contribute to the + * output in that case. + * @see @ref Shaders-PhongGL-normal-mapping, + * @ref setNormalTextureScale() */ - PhongGL& bindSpecularTexture(GL::Texture2D& texture); + PhongGL& bindNormalTexture(GL::Texture2DArray& texture); + #endif /** * @brief Bind textures @@ -1010,8 +1615,9 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref PhongGL::Flag "Flag" is set, you can use @cpp nullptr @ce for * the rest. Expects that the shader was created with at least one of * @ref Flag::AmbientTexture, @ref Flag::DiffuseTexture, - * @ref Flag::SpecularTexture or @ref Flag::NormalTexture enabled. More - * efficient than setting each texture separately. + * @ref Flag::SpecularTexture or @ref Flag::NormalTexture enabled and + * @ref Flag::TextureArrays is not set. More efficient than setting + * each texture separately. * @see @ref bindAmbientTexture(), @ref bindDiffuseTexture(), * @ref bindSpecularTexture(), @ref bindNormalTexture() */ @@ -1025,6 +1631,28 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @} */ + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + PhongGL& draw(GL::Mesh& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + PhongGL& draw(GL::Mesh&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + PhongGL& draw(GL::MeshView& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + PhongGL& draw(GL::MeshView&& mesh) { + return static_cast(GL::AbstractShaderProgram::draw(mesh)); + } + PhongGL& draw(Containers::ArrayView> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + PhongGL& draw(std::initializer_list> meshes) { + return static_cast(GL::AbstractShaderProgram::draw(meshes)); + } + #endif + private: /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -1036,23 +1664,34 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { Flags _flags; UnsignedInt _lightCount{}; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationMatrixUniform{0}, _projectionMatrixUniform{1}, _normalMatrixUniform{2}, _textureMatrixUniform{3}, - _ambientColorUniform{4}, - _diffuseColorUniform{5}, - _specularColorUniform{6}, - _shininessUniform{7}, - _normalTextureScaleUniform{8}, - _alphaMaskUniform{9}; #ifndef MAGNUM_TARGET_GLES2 - Int _objectIdUniform{10}; + _textureLayerUniform{4}, #endif - Int _lightPositionsUniform{11}, - _lightColorsUniform, /* 11 + lightCount, set in the constructor */ - _lightSpecularColorsUniform, /* 11 + 2*lightCount */ - _lightRangesUniform; /* 11 + 3*lightCount */ + _ambientColorUniform{5}, + _diffuseColorUniform{6}, + _specularColorUniform{7}, + _shininessUniform{8}, + _normalTextureScaleUniform{9}, + _alphaMaskUniform{10}; + #ifndef MAGNUM_TARGET_GLES2 + Int _objectIdUniform{11}; + #endif + Int _lightPositionsUniform{12}, + _lightColorsUniform, /* 12 + lightCount, set in the constructor */ + _lightSpecularColorsUniform, /* 12 + 2*lightCount */ + _lightRangesUniform; /* 12 + 3*lightCount */ + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** @debugoperatorclassenum{PhongGL,PhongGL::Flag} */ diff --git a/src/Magnum/Shaders/Shaders.h b/src/Magnum/Shaders/Shaders.h index 5e353add8..2e2e31392 100644 --- a/src/Magnum/Shaders/Shaders.h +++ b/src/Magnum/Shaders/Shaders.h @@ -38,15 +38,6 @@ namespace Magnum { namespace Shaders { #ifndef DOXYGEN_GENERATING_OUTPUT -template class AbstractVectorGL; -typedef AbstractVectorGL<2> AbstractVectorGL2D; -typedef AbstractVectorGL<3> AbstractVectorGL3D; -#ifdef MAGNUM_BUILD_DEPRECATED -template using AbstractVector CORRADE_DEPRECATED_ALIAS("use AbstractVectorGL instead") = AbstractVectorGL; -typedef CORRADE_DEPRECATED("use AbstractVectorGL2D instead") AbstractVectorGL2D AbstractVector2D; -typedef CORRADE_DEPRECATED("use AbstractVectorGL3D instead") AbstractVectorGL3D AbstractVector3D; -#endif - template class DistanceFieldVectorGL; typedef DistanceFieldVectorGL<2> DistanceFieldVectorGL2D; typedef DistanceFieldVectorGL<3> DistanceFieldVectorGL3D; diff --git a/src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga b/src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga new file mode 100644 index 000000000..5042eb5fc Binary files /dev/null and b/src/Magnum/Shaders/Test/BenchmarkFiles/trivial.tga differ diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 214681e51..021acb252 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -23,6 +23,22 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(ShadersDistanceFieldVectorTest DistanceFieldVectorTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersFlatTest FlatTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersGenericTest GenericTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersMeshVisualizerTest MeshVisualizerTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersPhongTest PhongTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersVectorTest VectorTest.cpp LIBRARIES MagnumShaders) + +set_target_properties( + ShadersDistanceFieldVectorTest + ShadersFlatTest + ShadersGenericTest + ShadersMeshVisualizerTest + ShadersPhongTest + ShadersVectorTest + PROPERTIES FOLDER "Magnum/Shaders/Test") + # There's an underscore between GL and Test to disambiguate from GLTest, which # is a common suffix used to mark tests that need a GL context. Ugly, I know. corrade_add_test(ShadersDistanceFieldVectorGL_Test DistanceFieldVectorGL_Test.cpp LIBRARIES MagnumShaders) @@ -94,7 +110,9 @@ if(BUILD_GL_TESTS) VectorTestFiles/smooth0.2-2D.tga VectorTestFiles/smooth0.2-3D.tga VectorTestFiles/outline2D.tga - VectorTestFiles/outline3D.tga) + VectorTestFiles/outline3D.tga + VectorTestFiles/multidraw2D-distancefield.tga + VectorTestFiles/multidraw3D-distancefield.tga) target_include_directories(ShadersDistanceFieldVectorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -103,6 +121,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersDistanceFieldVectorGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersDistanceFieldVectorGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersDistanceFieldVectorGLTest TgaImporter) + endif() endif() set(ShadersFlatGLTest_SRCS FlatGLTest.cpp) @@ -126,6 +152,8 @@ if(BUILD_GL_TESTS) FlatTestFiles/defaults.tga FlatTestFiles/instanced2D.tga FlatTestFiles/instanced3D.tga + FlatTestFiles/instanced-textured2D.tga + FlatTestFiles/instanced-textured3D.tga FlatTestFiles/textured2D.tga FlatTestFiles/textured3D.tga FlatTestFiles/textured2D-alpha.tga @@ -133,7 +161,11 @@ if(BUILD_GL_TESTS) FlatTestFiles/textured2D-alpha-mask0.5.tga FlatTestFiles/textured3D-alpha-mask0.5.tga FlatTestFiles/vertexColor2D.tga - FlatTestFiles/vertexColor3D.tga) + FlatTestFiles/vertexColor3D.tga + FlatTestFiles/multidraw2D.tga + FlatTestFiles/multidraw3D.tga + FlatTestFiles/multidraw-textured2D.tga + FlatTestFiles/multidraw-textured3D.tga) target_include_directories(ShadersFlatGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -142,6 +174,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersFlatGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersFlatGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersFlatGLTest TgaImporter) + endif() endif() set(ShadersMeshVisualizerGLTest_SRCS MeshVisualizerGLTest.cpp) @@ -189,7 +229,14 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/wireframe-wide2D.tga MeshVisualizerTestFiles/wireframe-wide3D.tga MeshVisualizerTestFiles/wireframe2D.tga - MeshVisualizerTestFiles/wireframe3D.tga) + MeshVisualizerTestFiles/wireframe3D.tga + MeshVisualizerTestFiles/multidraw-wireframe2D.tga + MeshVisualizerTestFiles/multidraw-wireframe3D.tga + MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga + MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga + MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga + MeshVisualizerTestFiles/multidraw-vertexid2D.tga + MeshVisualizerTestFiles/multidraw-vertexid3D.tga) target_include_directories(ShadersMeshVisualizerGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -198,6 +245,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersMeshVisualizerGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersMeshVisualizerGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersMeshVisualizerGLTest TgaImporter) + endif() endif() set(ShadersPhongGLTest_SRCS PhongGLTest.cpp) @@ -225,7 +280,7 @@ if(BUILD_GL_TESTS) PhongTestFiles/instanced.tga PhongTestFiles/instanced-normal.tga PhongTestFiles/low-light-angle.tga - PhongTestFiles/shininess-black-specular.tga + PhongTestFiles/shininess-no-specular.tga PhongTestFiles/shininess0-overflow.tga PhongTestFiles/shininess0.tga PhongTestFiles/shininess10.tga @@ -251,6 +306,8 @@ if(BUILD_GL_TESTS) PhongTestFiles/light-point-range1.5.tga PhongTestFiles/light-point-specular-color.tga PhongTestFiles/light-point.tga + PhongTestFiles/multidraw.tga + PhongTestFiles/multidraw-textured.tga # For zero lights test (equivalency to Flat3D) FlatTestFiles/textured3D-alpha-mask0.5.tga) @@ -262,6 +319,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersPhongGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersPhongGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersPhongGLTest TgaImporter) + endif() endif() set(ShadersVectorGLTest_SRCS VectorGLTest.cpp) @@ -280,7 +345,9 @@ if(BUILD_GL_TESTS) VectorTestFiles/defaults.tga VectorTestFiles/vector2D.tga - VectorTestFiles/vector3D.tga) + VectorTestFiles/vector3D.tga + VectorTestFiles/multidraw2D.tga + VectorTestFiles/multidraw3D.tga) target_include_directories(ShadersVectorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -289,6 +356,14 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersVectorGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersVectorGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersVectorGLTest TgaImporter) + endif() endif() set(ShadersVertexColorGLTest_SRCS VertexColorGLTest.cpp) @@ -306,7 +381,9 @@ if(BUILD_GL_TESTS) FlatTestFiles/defaults.tga VertexColorTestFiles/vertexColor2D.tga - VertexColorTestFiles/vertexColor3D.tga) + VertexColorTestFiles/vertexColor3D.tga + VertexColorTestFiles/multidraw2D.tga + VertexColorTestFiles/multidraw3D.tga) target_include_directories(ShadersVertexColorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -315,6 +392,33 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(ShadersVertexColorGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersVertexColorGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(ShadersVertexColorGLTest TgaImporter) + endif() + endif() + + corrade_add_test(ShadersGLBenchmark ShadersGLBenchmark.cpp + LIBRARIES + MagnumDebugTools + MagnumMeshTools + MagnumPrimitives + MagnumShaders + MagnumOpenGLTester + FILES + BenchmarkFiles/trivial.tga) + target_include_directories(ShadersGLBenchmark PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) + if(BUILD_PLUGINS_STATIC) + if(WITH_ANYIMAGEIMPORTER) + target_link_libraries(ShadersGLBenchmark PRIVATE AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + target_link_libraries(ShadersGLBenchmark PRIVATE TgaImporter) + endif() endif() if(CORRADE_TARGET_IOS) @@ -336,5 +440,6 @@ if(BUILD_GL_TESTS) ShadersPhongGLTest ShadersVectorGLTest ShadersVertexColorGLTest + ShadersGLBenchmark PROPERTIES FOLDER "Magnum/Shaders/Test") endif() diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index e9aeaee0a..70ecf2053 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -52,6 +52,22 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Circle.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Primitives/UVSphere.h" +#include "Magnum/Shaders/DistanceFieldVector.h" +#include "Magnum/Shaders/Generic.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -60,17 +76,43 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { explicit DistanceFieldVectorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersInvalid(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif template void setTextureMatrixNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void render2D(); - void render3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void render2D(); + template void render3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -84,15 +126,30 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - - Mesa llvmpipe - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 - - WebGL 1 / 2 (on Mesa Intel) - - iPhone 6 w/ iOS 12.4 + Rendering tests done: + + [B] base + [O] UBOs + draw offset + [M] multidraw + + Mesa Intel BOM + ES2 xx + ES3 BOx + Mesa AMD B + Mesa llvmpipe B + SwiftShader ES2 Bxx + ES3 B + ANGLE ES2 xx + ES3 BOM + ARM Mali (Huawei P10) ES2 Bxx + ES3 BOx + WebGL (on Mesa Intel) 1.0 Bxx + 2.0 BOM + NVidia + Intel Windows + AMD macOS x + Intel macOS BOx + iPhone 6 w/ iOS 12.4 ES3 B x */ using namespace Math::Literals; @@ -105,6 +162,34 @@ constexpr struct { {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1}, + {"", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 1}, + {"texture transformation", DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case + and 3+1 in 2D, per-material 4 */ + {"multiple materials, draws", DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 48}, + {"multidraw with all the things", DistanceFieldVectorGL2D::Flag::MultiDraw|DistanceFieldVectorGL2D::Flag::TextureTransformation, 16, 48} +}; + +constexpr struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", DistanceFieldVectorGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"}, +}; +#endif + const struct { const char* name; DistanceFieldVectorGL2D::Flags flags; @@ -127,30 +212,117 @@ const struct { "outline2D.tga", "outline3D.tga", false} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + {}, 1, 1, 16, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, + {"draw offset", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + {}, 2, 3, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, + {"multidraw", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::MultiDraw, 2, 3, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, +}; +#endif + DistanceFieldVectorGLTest::DistanceFieldVectorGLTest() { addInstancedTests({ &DistanceFieldVectorGLTest::construct<2>, &DistanceFieldVectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &DistanceFieldVectorGLTest::constructUniformBuffers<2>, + &DistanceFieldVectorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &DistanceFieldVectorGLTest::constructMove<2>, &DistanceFieldVectorGLTest::constructMove<3>, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::constructMoveUniformBuffers<2>, + &DistanceFieldVectorGLTest::constructMoveUniformBuffers<3>, + #endif + }); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &DistanceFieldVectorGLTest::constructUniformBuffersInvalid<2>, + &DistanceFieldVectorGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled<2>, + &DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled<3>, + &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<2>, - &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<3>}); + &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<3>, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<2>, + &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::setWrongDrawOffset<2>, + &DistanceFieldVectorGLTest::setWrongDrawOffset<3> + #endif + }); - addTests({&DistanceFieldVectorGLTest::renderDefaults2D, - &DistanceFieldVectorGLTest::renderDefaults3D}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &DistanceFieldVectorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::renderDefaults2D, + #endif + &DistanceFieldVectorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::renderDefaults3D, + #endif + }, &DistanceFieldVectorGLTest::renderSetup, &DistanceFieldVectorGLTest::renderTeardown); - addInstancedTests({&DistanceFieldVectorGLTest::render2D, - &DistanceFieldVectorGLTest::render3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &DistanceFieldVectorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::render2D, + #endif + &DistanceFieldVectorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::render3D, + #endif + }, Containers::arraySize(RenderData), &DistanceFieldVectorGLTest::renderSetup, &DistanceFieldVectorGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&DistanceFieldVectorGLTest::renderMulti2D, + &DistanceFieldVectorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &DistanceFieldVectorGLTest::renderSetup, + &DistanceFieldVectorGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -194,6 +366,47 @@ template void DistanceFieldVectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & DistanceFieldVectorGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= DistanceFieldVectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + DistanceFieldVectorGL shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void DistanceFieldVectorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -215,6 +428,128 @@ template void DistanceFieldVectorGLTest::constructMove() CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + DistanceFieldVectorGL a{DistanceFieldVectorGL::Flag::UniformBuffers, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + DistanceFieldVectorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), DistanceFieldVectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + DistanceFieldVectorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), DistanceFieldVectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + DistanceFieldVectorGL{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::DistanceFieldVectorGL: {}\n", data.message)); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + DistanceFieldVectorGL shader{DistanceFieldVectorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setColor({}) + .setOutlineColor({}) + .setOutlineRange({}, {}) + .setSmoothness({}); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setOutlineColor(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setOutlineRange(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setSmoothness(): the shader was created with uniform buffers enabled\n"); +} + +template void DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + DistanceFieldVectorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + template void DistanceFieldVectorGLTest::setTextureMatrixNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -232,6 +567,54 @@ template void DistanceFieldVectorGLTest::setTextureMatri "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + DistanceFieldVectorGL shader{DistanceFieldVectorGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + DistanceFieldVectorGL{DistanceFieldVectorGL::Flag::UniformBuffers, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void DistanceFieldVectorGLTest::renderSetup() { @@ -267,7 +650,18 @@ constexpr GL::TextureFormat TextureFormatR = #endif ; -void DistanceFieldVectorGLTest::renderDefaults2D() { +template void DistanceFieldVectorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -293,9 +687,30 @@ void DistanceFieldVectorGLTest::renderDefaults2D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL2D{} - .bindVectorTexture(texture) - .draw(square); + DistanceFieldVectorGL2D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL2D::Flag{}) { + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -323,7 +738,18 @@ void DistanceFieldVectorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::renderDefaults3D() { +template void DistanceFieldVectorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -349,9 +775,30 @@ void DistanceFieldVectorGLTest::renderDefaults3D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL2D{} - .bindVectorTexture(texture) - .draw(plane); + DistanceFieldVectorGL3D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL3D::Flag{}) { + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -379,10 +826,21 @@ void DistanceFieldVectorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::render2D() { +template void DistanceFieldVectorGLTest::render2D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -408,21 +866,52 @@ void DistanceFieldVectorGLTest::render2D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL2D shader{data.flags}; - shader - /** @todo implement background color */ - .setColor(data.color) - .setOutlineColor(data.outlineColor) - .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) - .setSmoothness(data.smoothness) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})); - - shader.draw(square); + DistanceFieldVectorGL2D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL2D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})); + shader.setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + .draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix3::projection({2.1f, 2.1f}) : Matrix3{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + .setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + }}; + GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationlUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -445,10 +934,21 @@ void DistanceFieldVectorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::render3D() { +template void DistanceFieldVectorGLTest::render3D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -474,24 +974,58 @@ void DistanceFieldVectorGLTest::render3D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL3D shader{data.flags}; - shader - /** @todo implement background color */ - .setColor(data.color) - .setOutlineColor(data.outlineColor) - .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) - .setSmoothness(data.smoothness) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationZ(15.0_degf)); - - shader.draw(plane); + DistanceFieldVectorGL3D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL3D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf)); + shader.setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf) : Matrix4{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + .setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + }}; + GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationlUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -514,6 +1048,415 @@ void DistanceFieldVectorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldVectorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= DistanceFieldVectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x00ff00_rgbf); + materialData[1*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x990000_rgbf) + .setOutlineColor(0xff0000_rgbf) + .setOutlineRange(0.6f, 0.4f); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation|data.flags, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + + if(data.flags >= DistanceFieldVectorGL2D::Flag::MultiDraw) + shader.draw({circle, square, triangle}); + else { + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + } + + /* + - Circle lower left, green, upside down + - Square lower right, dark red with red outline, mirrored + - Triangle up center, green + */ + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VectorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void DistanceFieldVectorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= DistanceFieldVectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x00ff00_rgbf); + materialData[1*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x990000_rgbf) + .setOutlineColor(0xff0000_rgbf) + .setOutlineRange(0.6f, 0.4f); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Flag::UniformBuffers|DistanceFieldVectorGL3D::Flag::TextureTransformation|data.flags, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + + if(data.flags >= DistanceFieldVectorGL3D::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + } + + /* + - Sphere lower left, green, upside down + - Plane lower right, dark red with red outline, mirrored + - Cone up center, green + */ + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VectorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::DistanceFieldVectorGLTest) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp index b337b24c5..c9e206ae4 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp @@ -41,17 +41,25 @@ struct DistanceFieldVectorGL_Test: TestSuite::Tester { void debugFlag(); void debugFlags(); + #ifndef MAGNUM_TARGET_GLES2 + void debugFlagsSupersets(); + #endif }; DistanceFieldVectorGL_Test::DistanceFieldVectorGL_Test() { - addTests({&DistanceFieldVectorGL_Test::constructNoCreate<2>, - &DistanceFieldVectorGL_Test::constructNoCreate<3>, - - &DistanceFieldVectorGL_Test::constructCopy<2>, - &DistanceFieldVectorGL_Test::constructCopy<3>, - - &DistanceFieldVectorGL_Test::debugFlag, - &DistanceFieldVectorGL_Test::debugFlags}); + addTests({ + &DistanceFieldVectorGL_Test::constructNoCreate<2>, + &DistanceFieldVectorGL_Test::constructNoCreate<3>, + + &DistanceFieldVectorGL_Test::constructCopy<2>, + &DistanceFieldVectorGL_Test::constructCopy<3>, + + &DistanceFieldVectorGL_Test::debugFlag, + &DistanceFieldVectorGL_Test::debugFlags, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGL_Test::debugFlagsSupersets + #endif + }); } template void DistanceFieldVectorGL_Test::constructNoCreate() { @@ -87,6 +95,15 @@ void DistanceFieldVectorGL_Test::debugFlags() { CORRADE_COMPARE(out.str(), "Shaders::DistanceFieldVectorGL::Flag::TextureTransformation|Shaders::DistanceFieldVectorGL::Flag(0xf0) Shaders::DistanceFieldVectorGL::Flags{}\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldVectorGL_Test::debugFlagsSupersets() { + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + std::ostringstream out; + Debug{&out} << (DistanceFieldVectorGL3D::Flag::MultiDraw|DistanceFieldVectorGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::DistanceFieldVectorGL::Flag::MultiDraw\n"); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::DistanceFieldVectorGL_Test) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp new file mode 100644 index 000000000..768591731 --- /dev/null +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp @@ -0,0 +1,207 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/DistanceFieldVector.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct DistanceFieldVectorTest: TestSuite::Tester { + explicit DistanceFieldVectorTest(); + + template void uniformSizeAlignment(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +DistanceFieldVectorTest::DistanceFieldVectorTest() { + addTests({&DistanceFieldVectorTest::uniformSizeAlignment, + &DistanceFieldVectorTest::uniformSizeAlignment, + + &DistanceFieldVectorTest::drawUniformConstructDefault, + &DistanceFieldVectorTest::drawUniformConstructNoInit, + &DistanceFieldVectorTest::drawUniformSetters, + &DistanceFieldVectorTest::drawUniformMaterialIdPacking, + + &DistanceFieldVectorTest::materialUniformConstructDefault, + &DistanceFieldVectorTest::materialUniformConstructNoInit, + &DistanceFieldVectorTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "DistanceFieldVectorDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "DistanceFieldVectorMaterialUniform"; } +}; + +template void DistanceFieldVectorTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void DistanceFieldVectorTest::drawUniformConstructDefault() { + DistanceFieldVectorDrawUniform a; + DistanceFieldVectorDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr DistanceFieldVectorDrawUniform ca; + constexpr DistanceFieldVectorDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + DistanceFieldVectorDrawUniform a; + a.materialId = 76; + + new(&a) DistanceFieldVectorDrawUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.materialId, 76); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::drawUniformSetters() { + DistanceFieldVectorDrawUniform a; + a.setMaterialId(76); + CORRADE_COMPARE(a.materialId, 76); +} + +void DistanceFieldVectorTest::drawUniformMaterialIdPacking() { + DistanceFieldVectorDrawUniform a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void DistanceFieldVectorTest::materialUniformConstructDefault() { + DistanceFieldVectorMaterialUniform a; + DistanceFieldVectorMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(a.outlineStart, 0.5f); + CORRADE_COMPARE(b.outlineStart, 0.5f); + CORRADE_COMPARE(a.outlineEnd, 1.0f); + CORRADE_COMPARE(b.outlineEnd, 1.0f); + CORRADE_COMPARE(a.smoothness, 0.04f); + CORRADE_COMPARE(b.smoothness, 0.04f); + + constexpr DistanceFieldVectorMaterialUniform ca; + constexpr DistanceFieldVectorMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(ca.outlineStart, 0.5f); + CORRADE_COMPARE(cb.outlineStart, 0.5f); + CORRADE_COMPARE(ca.outlineEnd, 1.0f); + CORRADE_COMPARE(cb.outlineEnd, 1.0f); + CORRADE_COMPARE(ca.smoothness, 0.04f); + CORRADE_COMPARE(cb.smoothness, 0.04f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + DistanceFieldVectorMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.outlineEnd = 0.37f; + + new(&a) DistanceFieldVectorMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.outlineEnd, 0.37f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::materialUniformSetters() { + DistanceFieldVectorMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setOutlineColor(0x9876facd_rgbaf) + .setOutlineRange(0.6f, 0.1f) + .setSmoothness(0.37f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.outlineColor, 0x9876facd_rgbaf); + CORRADE_COMPARE(a.outlineStart, 0.6f); + CORRADE_COMPARE(a.outlineEnd, 0.1f); + CORRADE_COMPARE(a.smoothness, 0.37f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::DistanceFieldVectorTest) diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 34f2ff8f2..b5a8beedc 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -56,6 +56,18 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/MeshView.h" +#include "Magnum/GL/TextureArray.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Flat.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -64,49 +76,77 @@ struct FlatGLTest: GL::OpenGLTester { explicit FlatGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif - template void constructTextureTransformationNotTextured(); + template void constructInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersInvalid(); + #endif - template void bindTextureNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif + template void bindTextureInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureArrayInvalid(); + #endif template void setAlphaMaskNotEnabled(); template void setTextureMatrixNotEnabled(); #ifndef MAGNUM_TARGET_GLES2 + template void setTextureLayerNotArray(); + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 template void setObjectIdNotEnabled(); #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void renderColored2D(); - void renderColored3D(); - void renderSinglePixelTextured2D(); - void renderSinglePixelTextured3D(); - void renderTextured2D(); - void renderTextured3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void renderColored2D(); + template void renderColored3D(); + template void renderSinglePixelTextured2D(); + template void renderSinglePixelTextured3D(); + template void renderTextured2D(); + template void renderTextured3D(); - template void renderVertexColor2D(); - template void renderVertexColor3D(); + template void renderVertexColor2D(); + template void renderVertexColor3D(); void renderAlphaSetup(); void renderAlphaTeardown(); - void renderAlpha2D(); - void renderAlpha3D(); + template void renderAlpha2D(); + template void renderAlpha3D(); #ifndef MAGNUM_TARGET_GLES2 void renderObjectIdSetup(); void renderObjectIdTeardown(); - void renderObjectId2D(); - void renderObjectId3D(); + template void renderObjectId2D(); + template void renderObjectId3D(); #endif - void renderInstanced2D(); - void renderInstanced3D(); + template void renderInstanced2D(); + template void renderInstanced3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -120,18 +160,34 @@ struct FlatGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - - Mesa llvmpipe - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 (except instancing) - - WebGL 1 / 2 (on Mesa Intel) (except instancing) - - NVidia Windows (except instancing) - - Intel Windows (except instancing) - - AMD on macOS (except instancing) - - iPhone 6 w/ iOS 12.4 (except instancing) + Rendering tests done: + + [B] base + [A] alpha mask + [D] object ID + [I] instancing + [O] UBOs + draw offset + [M] multidraw + [L] texture arrays + + Mesa Intel BADIOML + ES2 xxx + ES3 BAD Ox + Mesa AMD BAD + Mesa llvmpipe BAD + SwiftShader ES2 BAD xxx + ES3 BAD + ANGLE ES2 xxx + ES3 BAD OM + ARM Mali (Huawei P10) ES2 BAD xxx + ES3 BAD Ox + WebGL (on Mesa Intel) 1.0 BAD xxx + 2.0 BAD OM + NVidia BAD + Intel Windows BAD + AMD macOS BAD + Intel macOS BAD Ox + iPhone 6 w/ iOS 12.4 ES3 BAD x */ using namespace Math::Literals; @@ -143,6 +199,10 @@ constexpr struct { {"", {}}, {"textured", FlatGL2D::Flag::Textured}, {"textured + texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays}, + {"texture arrays + texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::TextureTransformation}, + #endif {"alpha mask", FlatGL2D::Flag::AlphaMask}, {"alpha mask + textured", FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::Textured}, {"vertex colors", FlatGL2D::Flag::VertexColor}, @@ -153,20 +213,126 @@ constexpr struct { {"object ID + alpha mask + textured", FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::Textured}, #endif {"instanced transformation", FlatGL2D::Flag::InstancedTransformation}, - {"instanced texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTextureOffset} + {"instanced texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTextureOffset}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced texture array offset + layer", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::InstancedTextureOffset}, + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1}, + {"", FlatGL2D::Flag::UniformBuffers, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case + and 3+1 in 2D, per-material 2 */ + {"multiple materials, draws", FlatGL2D::Flag::UniformBuffers, 8, 48}, + {"textured", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured, 1, 1}, + {"textured + texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"texture arrays + texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"alpha mask", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1, 1}, + {"object ID", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId, 1, 1}, + {"instanced texture array offset + layer", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::InstancedTextureOffset, 1, 1}, + {"multidraw with all the things", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 8, 48} +}; +#endif + +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + const char* message; +} ConstructInvalidData[]{ + {"texture transformation but not textured", FlatGL2D::Flag::TextureTransformation, + "texture transformation enabled but the shader is not textured"}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays but not textured", FlatGL2D::Flag::TextureArrays, + "texture arrays enabled but the shader is not textured"} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", FlatGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", FlatGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"}, + {"texture arrays but no transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, 1, 1, + "texture arrays require texture transformation enabled as well if uniform buffers are used"} +}; +#endif + +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + const char* message; +} BindTextureInvalidData[]{ + {"not textured", {}, + "the shader was not created with texturing enabled"}, + #ifndef MAGNUM_TARGET_GLES2 + {"array", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + "the shader was created with texture arrays enabled, use a Texture2DArray instead"} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + const char* message; +} BindTextureArrayInvalidData[]{ + {"not textured", {}, + "the shader was not created with texturing enabled"}, + {"not array", FlatGL2D::Flag::Textured, + "the shader was not created with texture arrays enabled, use a Texture2D instead"} +}; +#endif + +const struct { + const char* name; + FlatGL2D::Flags flags; + Int layer; +} RenderSinglePixelTexturedData[]{ + {"", {}, 0}, + #ifndef MAGNUM_TARGET_GLES2 + {"array, first layer", FlatGL2D::Flag::TextureArrays, 0}, + {"array, arbitrary layer", FlatGL2D::Flag::TextureArrays, 6}, + #endif }; const struct { const char* name; FlatGL2D::Flags flags; Matrix3 textureTransformation; + Int layer; bool flip; } RenderTexturedData[]{ - {"", FlatGL2D::Flag::Textured, {}, false}, + {"", + FlatGL2D::Flag::Textured, + {}, 0, false}, {"texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), - true}, + 0, true}, + #ifndef MAGNUM_TARGET_GLES2 + {"array, first layer", + FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + {}, 0, false}, + {"array, arbitrary layer", + FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + {}, 6, false}, + {"array, texture transformation, arbitrary layer", + FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + 6, true}, + #endif }; const struct { @@ -184,27 +350,89 @@ const struct { {"blending", "FlatTestFiles/textured2D-alpha.tga", "FlatTestFiles/textured3D-alpha.tga", true, FlatGL2D::Flag::Textured, 0.0f}, {"masking 0.0", "FlatTestFiles/textured2D.tga", "FlatTestFiles/textured3D.tga", false, - FlatGL2D::Flag::Textured, 0.0f}, + FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 0.0f}, {"masking 0.5", "FlatTestFiles/textured2D-alpha-mask0.5.tga", "FlatTestFiles/textured3D-alpha-mask0.5.tga", false, FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 0.5f}, {"masking 1.0", "TestFiles/alpha-mask1.0.tga", "TestFiles/alpha-mask1.0.tga", false, FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 1.0f} + /* texture arrays are orthogonal to this, no need to be tested here */ +}; + +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + FlatGL2D::Flags flags; + Float maxThreshold, meanThreshold; +} RenderInstancedData[] { + {"colored", "instanced2D.tga", "instanced3D.tga", + {}, + /* Minor differences on SwiftShader */ + 164.4f, 0.094f}, + {"textured", "instanced-textured2D.tga", "instanced-textured3D.tga", + FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::Textured, + /* Minor differences on SwiftShader */ + 192.67f, 0.140f}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture array", "instanced-textured2D.tga", "instanced-textured3D.tga", + FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around); minor differences on + SwiftShader */ + 192.67f, 0.398f}, + #endif }; #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; + const char* expected2D; + const char* expected3D; FlatGL2D::Flags flags; - UnsignedInt uniformId; - UnsignedInt instanceCount; - UnsignedInt expected; -} RenderObjectIdData[] { - {"", /* Verify that it can hold 16 bits at least */ - FlatGL2D::Flag::ObjectId, 48526, 0, 48526}, - {"instanced, first instance", - FlatGL2D::Flag::InstancedObjectId, 13524, 1, 24526}, - {"instanced, second instance", - FlatGL2D::Flag::InstancedObjectId, 13524, 2, 62347} + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset, colored", "multidraw2D.tga", "multidraw3D.tga", + {}, 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 2.34f, 0.01f}, + {"bind with offset, texture array", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + 1, 1, 16, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 65.0f, 0.15f}, + {"draw offset, colored", "multidraw2D.tga", "multidraw3D.tga", + {}, + 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 2.34f, 0.01f}, + {"draw offset, texture array", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + 2, 3, 1, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 65.0f, 0.15f}, + {"multidraw, colored", "multidraw2D.tga", "multidraw3D.tga", + FlatGL2D::Flag::MultiDraw, 2, 3, 1, 0.0f, 0.0f}, + {"multidraw, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 2.34f, 0.01f}, + {"multidraw, texture array", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, + 2, 3, 1, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 65.0f, 0.15f} }; #endif @@ -214,65 +442,207 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::constructUniformBuffers<2>, + &FlatGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &FlatGLTest::constructMove<2>, &FlatGLTest::constructMove<3>, - &FlatGLTest::constructTextureTransformationNotTextured<2>, - &FlatGLTest::constructTextureTransformationNotTextured<3>, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::constructMoveUniformBuffers<2>, + &FlatGLTest::constructMoveUniformBuffers<3>, + #endif + }); + + addInstancedTests({ + &FlatGLTest::constructInvalid<2>, + &FlatGLTest::constructInvalid<3>}, + Containers::arraySize(ConstructInvalidData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::constructUniformBuffersInvalid<2>, + &FlatGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setUniformUniformBuffersEnabled<2>, + &FlatGLTest::setUniformUniformBuffersEnabled<3>, + &FlatGLTest::bindBufferUniformBuffersNotEnabled<2>, + &FlatGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif + }); + + addInstancedTests({ + &FlatGLTest::bindTextureInvalid<2>, + &FlatGLTest::bindTextureInvalid<3>}, + Containers::arraySize(BindTextureInvalidData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::bindTextureArrayInvalid<2>, + &FlatGLTest::bindTextureArrayInvalid<3>}, + Containers::arraySize(BindTextureArrayInvalidData)); + #endif - &FlatGLTest::bindTextureNotEnabled<2>, - &FlatGLTest::bindTextureNotEnabled<3>, + addTests({ &FlatGLTest::setAlphaMaskNotEnabled<2>, &FlatGLTest::setAlphaMaskNotEnabled<3>, &FlatGLTest::setTextureMatrixNotEnabled<2>, &FlatGLTest::setTextureMatrixNotEnabled<3>, #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setTextureLayerNotArray<2>, + &FlatGLTest::setTextureLayerNotArray<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::bindTextureTransformBufferNotEnabled<2>, + &FlatGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::setObjectIdNotEnabled<2>, - &FlatGLTest::setObjectIdNotEnabled<3> + &FlatGLTest::setObjectIdNotEnabled<3>, #endif - }); + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setWrongDrawOffset<2>, + &FlatGLTest::setWrongDrawOffset<3>, + #endif + }); - addTests({&FlatGLTest::renderDefaults2D, - &FlatGLTest::renderDefaults3D, - &FlatGLTest::renderColored2D, - &FlatGLTest::renderColored3D, - &FlatGLTest::renderSinglePixelTextured2D, - &FlatGLTest::renderSinglePixelTextured3D}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &FlatGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderDefaults2D, + #endif + &FlatGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderDefaults3D, + #endif + &FlatGLTest::renderColored2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderColored2D, + #endif + &FlatGLTest::renderColored3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderColored3D, + #endif + }, + &FlatGLTest::renderSetup, + &FlatGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &FlatGLTest::renderSinglePixelTextured2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderSinglePixelTextured2D, + #endif + &FlatGLTest::renderSinglePixelTextured3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderSinglePixelTextured3D + #endif + }, + Containers::arraySize(RenderSinglePixelTexturedData), &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addInstancedTests({&FlatGLTest::renderTextured2D, - &FlatGLTest::renderTextured3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &FlatGLTest::renderTextured2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderTextured2D, + #endif + &FlatGLTest::renderTextured3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderTextured3D + #endif + }, Containers::arraySize(RenderTexturedData), &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addTests({&FlatGLTest::renderVertexColor2D, - &FlatGLTest::renderVertexColor2D, - &FlatGLTest::renderVertexColor3D, - &FlatGLTest::renderVertexColor3D}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &FlatGLTest::renderVertexColor2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor2D, + #endif + &FlatGLTest::renderVertexColor2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor2D, + #endif + &FlatGLTest::renderVertexColor3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor3D, + #endif + &FlatGLTest::renderVertexColor3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor3D + #endif + }, &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addInstancedTests({&FlatGLTest::renderAlpha2D, - &FlatGLTest::renderAlpha3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &FlatGLTest::renderAlpha2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderAlpha2D, + #endif + &FlatGLTest::renderAlpha3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderAlpha3D + #endif + }, Containers::arraySize(RenderAlphaData), &FlatGLTest::renderAlphaSetup, &FlatGLTest::renderAlphaTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&FlatGLTest::renderObjectId2D, - &FlatGLTest::renderObjectId3D}, - Containers::arraySize(RenderObjectIdData), + /* MSVC needs explicit type due to default template args */ + addTests({ + &FlatGLTest::renderObjectId2D, + &FlatGLTest::renderObjectId2D, + &FlatGLTest::renderObjectId3D, + &FlatGLTest::renderObjectId3D}, &FlatGLTest::renderObjectIdSetup, &FlatGLTest::renderObjectIdTeardown); #endif - addTests({&FlatGLTest::renderInstanced2D, - &FlatGLTest::renderInstanced3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &FlatGLTest::renderInstanced2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderInstanced2D, + #endif + &FlatGLTest::renderInstanced3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderInstanced3D + #endif + }, + Containers::arraySize(RenderInstancedData), + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderObjectIdSetup, + &FlatGLTest::renderObjectIdTeardown + #else &FlatGLTest::renderSetup, - &FlatGLTest::renderTeardown); + &FlatGLTest::renderTeardown + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&FlatGLTest::renderMulti2D, + &FlatGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &FlatGLTest::renderObjectIdSetup, + &FlatGLTest::renderObjectIdTeardown); + #endif /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -307,6 +677,8 @@ template void FlatGLTest::construct() { #ifndef MAGNUM_TARGET_GLES if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); #endif FlatGL shader{data.flags}; @@ -322,6 +694,51 @@ template void FlatGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + FlatGL shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void FlatGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -343,8 +760,42 @@ template void FlatGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } -template void FlatGLTest::constructTextureTransformationNotTextured() { +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + FlatGL a{FlatGL::Flag::UniformBuffers, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + FlatGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + FlatGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +template void FlatGLTest::constructInvalid() { + auto&& data = ConstructInvalidData[testCaseInstanceId()]; setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -352,46 +803,67 @@ template void FlatGLTest::constructTextureTransformation std::ostringstream out; Error redirectError{&out}; - FlatGL{FlatGL::Flag::TextureTransformation}; - CORRADE_COMPARE(out.str(), - "Shaders::FlatGL: texture transformation enabled but the shader is not textured\n"); + FlatGL{data.flags}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::FlatGL: {}\n", data.message)); } -template void FlatGLTest::bindTextureNotEnabled() { +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + std::ostringstream out; Error redirectError{&out}; - - GL::Texture2D texture; - FlatGL shader; - shader.bindTexture(texture); - - CORRADE_COMPARE(out.str(), "Shaders::FlatGL::bindTexture(): the shader was not created with texturing enabled\n"); + FlatGL{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::FlatGL: {}\n", data.message)); } +#endif -template void FlatGLTest::setAlphaMaskNotEnabled() { +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setUniformUniformBuffersEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + std::ostringstream out; Error redirectError{&out}; - FlatGL shader; - shader.setAlphaMask(0.75f); - + FlatGL shader{FlatGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setTextureLayer({}) + .setColor({}) + .setAlphaMask({}) + .setObjectId({}); CORRADE_COMPARE(out.str(), - "Shaders::FlatGL::setAlphaMask(): the shader was not created with alpha mask enabled\n"); + "Shaders::FlatGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setTextureLayer(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setAlphaMask(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled\n"); } -template void FlatGLTest::setTextureMatrixNotEnabled() { +template void FlatGLTest::bindBufferUniformBuffersNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); #ifdef CORRADE_NO_ASSERT @@ -401,33 +873,201 @@ template void FlatGLTest::setTextureMatrixNotEnabled() { std::ostringstream out; Error redirectError{&out}; + GL::Buffer buffer; FlatGL shader; - shader.setTextureMatrix({}); - + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); CORRADE_COMPARE(out.str(), - "Shaders::FlatGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } +#endif -#ifndef MAGNUM_TARGET_GLES2 -template void FlatGLTest::setObjectIdNotEnabled() { +template void FlatGLTest::bindTextureInvalid() { + auto&& data = BindTextureInvalidData[testCaseInstanceId()]; setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + std::ostringstream out; Error redirectError{&out}; - FlatGL shader; - shader.setObjectId(33376); - - CORRADE_COMPARE(out.str(), - "Shaders::FlatGL::setObjectId(): the shader was not created with object ID enabled\n"); + FlatGL shader{data.flags}; + GL::Texture2D texture; + shader.bindTexture(texture); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::FlatGL::bindTexture(): {}\n", data.message)); } -#endif -constexpr Vector2i RenderSize{80, 80}; +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::bindTextureArrayInvalid() { + auto&& data = BindTextureArrayInvalidData[testCaseInstanceId()]; + setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader{data.flags}; + GL::Texture2DArray textureArray; + shader.bindTexture(textureArray); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::FlatGL::bindTexture(): {}\n", data.message)); +} +#endif + +template void FlatGLTest::setAlphaMaskNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader; + shader.setAlphaMask(0.75f); + + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setAlphaMask(): the shader was not created with alpha mask enabled\n"); +} + +template void FlatGLTest::setTextureMatrixNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader; + shader.setTextureMatrix({}); + + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); +} + +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setTextureLayerNotArray() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader; + shader.setTextureLayer(37); + + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setTextureLayer(): the shader was not created with texture arrays enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + FlatGL shader{FlatGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setObjectIdNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader; + shader.setObjectId(33376); + + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setObjectId(): the shader was not created with object ID enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + FlatGL{FlatGL::Flag::UniformBuffers, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + +constexpr Vector2i RenderSize{80, 80}; void FlatGLTest::renderSetup() { /* Pick a color that's directly representable on RGBA4 as well to reduce @@ -454,11 +1094,44 @@ void FlatGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -void FlatGLTest::renderDefaults2D() { +template void FlatGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32)); - FlatGL2D{} - .draw(circle); + FlatGL2D shader{flag}; + + if(flag == FlatGL2D::Flag{}) { + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -474,11 +1147,44 @@ void FlatGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, 238.0f, 0.2975f})); } -void FlatGLTest::renderDefaults3D() { +template void FlatGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - FlatGL3D{} - .draw(sphere); + FlatGL3D shader{flag}; + + if(flag == FlatGL3D::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -494,13 +1200,49 @@ void FlatGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, 238.0f, 0.2975f})); } -void FlatGLTest::renderColored2D() { +template void FlatGLTest::renderColored2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32)); - FlatGL2D{} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .draw(circle); + FlatGL2D shader{flag}; + + if(flag == FlatGL2D::Flag{}) { + shader + .setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -521,17 +1263,58 @@ void FlatGLTest::renderColored2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderColored3D() { +template void FlatGLTest::renderColored3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - FlatGL3D{} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .draw(sphere); + FlatGL3D shader{flag}; + + if(flag == FlatGL3D::Flag{}) { + shader + .setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -568,23 +1351,97 @@ constexpr GL::TextureFormat TextureFormatRGBA = #endif ; -void FlatGLTest::renderSinglePixelTextured2D() { +template void FlatGLTest::renderSinglePixelTextured2D() { + auto&& data = RenderSinglePixelTexturedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); - const Color4ub diffuseData[]{ 0x9999ff_rgb }; - ImageView2D diffuseImage{PixelFormat::RGBA8Unorm, Vector2i{1}, diffuseData}; - GL::Texture2D texture; - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGBA, Vector2i{1}) - .setSubImage(0, {}, diffuseImage); + FlatGL2D::Flags flags = FlatGL2D::Flag::Textured|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers && (data.flags & FlatGL2D::Flag::TextureArrays) && !(data.flags & FlatGL2D::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= FlatGL2D::Flag::TextureTransformation; + } + #endif + FlatGL2D shader{flags}; + + const Color4ub imageData[]{ 0x9999ff_rgb }; + ImageView2D image{PixelFormat::RGBA8Unorm, Vector2i{1}, imageData}; + + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + if(data.flags & FlatGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector3i{1, 1, data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, image); + shader.bindTexture(textureArray); + if(flag != FlatGL2D::Flag::UniformBuffers && data.layer != 0) + shader.setTextureLayer(data.layer); /* to verify the default */ + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector2i{1}) + .setSubImage(0, {}, image); + shader.bindTexture(texture); + } - FlatGL2D{FlatGL3D::Flag::Textured} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .bindTexture(texture) - .draw(circle); + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setLayer(data.layer) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -606,27 +1463,107 @@ void FlatGLTest::renderSinglePixelTextured2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderSinglePixelTextured3D() { +template void FlatGLTest::renderSinglePixelTextured3D() { + auto&& data = RenderSinglePixelTexturedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - const Color4ub diffuseData[]{ 0x9999ff_rgb }; - ImageView2D diffuseImage{PixelFormat::RGBA8Unorm, Vector2i{1}, diffuseData}; - GL::Texture2D texture; - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGBA, Vector2i{1}) - .setSubImage(0, {}, diffuseImage); + FlatGL3D::Flags flags = FlatGL2D::Flag::Textured|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers && (data.flags & FlatGL3D::Flag::TextureArrays) && !(data.flags & FlatGL3D::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= FlatGL3D::Flag::TextureTransformation; + } + #endif + FlatGL3D shader{flags}; - FlatGL3D{FlatGL3D::Flag::Textured} - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .bindTexture(texture) - .draw(sphere); + const Color4ub imageData[]{ 0x9999ff_rgb }; + ImageView2D image{PixelFormat::RGBA8Unorm, Vector2i{1}, imageData}; + + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + if(data.flags & FlatGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector3i{1, 1, data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, image); + shader.bindTexture(textureArray); + if(flag != FlatGL2D::Flag::UniformBuffers && data.layer != 0) + shader.setTextureLayer(data.layer); /* to verify the default */ + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector2i{1}) + .setSubImage(0, {}, image); + shader.bindTexture(texture); + } + + if(flag == FlatGL3D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setLayer(data.layer) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -648,10 +1585,26 @@ void FlatGLTest::renderSinglePixelTextured3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderTextured2D() { +template void FlatGLTest::renderTextured2D() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -662,27 +1615,81 @@ void FlatGLTest::renderTextured2D() { Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - FlatGL2D shader{data.flags}; - shader - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - /* Colorized. Case without a color (where it should be white) is tested - in renderSinglePixelTextured() */ - .setColor(0x9999ff_rgbf) - .bindTexture(texture); + FlatGL2D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers && (data.flags & FlatGL2D::Flag::TextureArrays) && !(data.flags & FlatGL2D::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= FlatGL2D::Flag::TextureTransformation; + } + #endif + FlatGL2D shader{flags}; - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + if(data.flags & FlatGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindTexture(textureArray); + if(flag != FlatGL2D::Flag::UniformBuffers && data.layer != 0) + shader.setTextureLayer(data.layer); /* to verify the default */ + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } - shader.draw(circle); + if(flag == FlatGL2D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured2D() */ + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + .setLayer(data.layer) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -704,11 +1711,27 @@ void FlatGLTest::renderTextured2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderTextured3D() { +template void FlatGLTest::renderTextured3D() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -718,31 +1741,91 @@ void FlatGLTest::renderTextured3D() { Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - FlatGL3D shader{data.flags}; - shader - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* - Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf)) - /* Colorized. Case without a color (where it should be white) is tested - in renderSinglePixelTextured() */ - .setColor(0x9999ff_rgbf) - .bindTexture(texture); + FlatGL3D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers && (data.flags & FlatGL3D::Flag::TextureArrays) && !(data.flags & FlatGL3D::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= FlatGL3D::Flag::TextureTransformation; + } + #endif + FlatGL3D shader{flags}; - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + if(data.flags & FlatGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindTexture(textureArray); + if(flag != FlatGL3D::Flag::UniformBuffers && data.layer != 0) + shader.setTextureLayer(data.layer); /* to verify the default */ + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } - shader.draw(sphere); + if(flag == FlatGL3D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + shader + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* + Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf)) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured3D() */ + .setColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* + Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + .setLayer(data.layer) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays */ + if(flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -764,8 +1847,20 @@ void FlatGLTest::renderTextured3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void FlatGLTest::renderVertexColor2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void FlatGLTest::renderVertexColor2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -796,11 +1891,34 @@ template void FlatGLTest::renderVertexColor2D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL2D{FlatGL2D::Flag::Textured|FlatGL2D::Flag::VertexColor} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setColor(0x9999ff_rgbf) - .bindTexture(texture) - .draw(circle); + FlatGL2D shader{FlatGL2D::Flag::Textured|FlatGL2D::Flag::VertexColor|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -818,8 +1936,20 @@ template void FlatGLTest::renderVertexColor2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void FlatGLTest::renderVertexColor3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void FlatGLTest::renderVertexColor3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -850,15 +1980,43 @@ template void FlatGLTest::renderVertexColor3D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL3D{FlatGL3D::Flag::Textured|FlatGL3D::Flag::VertexColor} - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setColor(0x9999ff_rgbf) - .bindTexture(texture) - .draw(sphere); + FlatGL3D shader{FlatGL3D::Flag::Textured|FlatGL3D::Flag::VertexColor|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -890,10 +2048,21 @@ void FlatGLTest::renderAlphaTeardown() { renderTeardown(); } -void FlatGLTest::renderAlpha2D() { +template void FlatGLTest::renderAlpha2D() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -915,15 +2084,39 @@ void FlatGLTest::renderAlpha2D() { GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); - FlatGL2D shader{data.flags}; - shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.flags & FlatGL3D::Flag::AlphaMask) - shader.setAlphaMask(data.threshold); + FlatGL2D shader{data.flags|flag}; + shader.bindTexture(texture); - shader.draw(circle); + if(flag == FlatGL2D::Flag{}) { + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & FlatGL2D::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + .setAlphaMask(data.threshold) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -941,10 +2134,21 @@ void FlatGLTest::renderAlpha2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderAlpha3D() { +template void FlatGLTest::renderAlpha3D() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -966,23 +2170,59 @@ void FlatGLTest::renderAlpha3D() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - FlatGL3D shader{data.flags}; - shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.flags & FlatGL3D::Flag::AlphaMask) - shader.setAlphaMask(data.threshold); + FlatGL3D shader{data.flags|flag}; + shader.bindTexture(texture); - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setColor(0x9999ff_rgbf); + + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & FlatGL3D::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + .setAlphaMask(data.threshold) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1039,9 +2279,17 @@ void FlatGLTest::renderObjectIdTeardown() { _framebuffer = GL::Framebuffer{NoCreate}; } -void FlatGLTest::renderObjectId2D() { - auto&& data = RenderObjectIdData[testCaseInstanceId()]; - setTestCaseDescription(data.name); +template void FlatGLTest::renderObjectId2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) @@ -1052,17 +2300,31 @@ void FlatGLTest::renderObjectId2D() { GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32)); - if(data.instanceCount) circle - .setInstanceCount(data.instanceCount) - .addVertexBufferInstanced( - GL::Buffer{Containers::arrayView({11002u, 48823u})}, - 1, 0, FlatGL2D::ObjectId{}); - - FlatGL2D{data.flags} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setObjectId(data.uniformId) - .draw(circle); + FlatGL2D shader{FlatGL2D::Flag::ObjectId|flag}; + + if(flag == FlatGL2D::Flag{}) { + shader.setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setObjectId(48526) + .draw(circle); + } else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setObjectId(48526) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1094,12 +2356,20 @@ void FlatGLTest::renderObjectId2D() { /* Outside of the object, cleared to 27 */ CORRADE_COMPARE(image.pixels()[10][10], 27); /* Inside of the object */ - CORRADE_COMPARE(image.pixels()[40][46], data.expected); + CORRADE_COMPARE(image.pixels()[40][46], 48526); } -void FlatGLTest::renderObjectId3D() { - auto&& data = RenderObjectIdData[testCaseInstanceId()]; - setTestCaseDescription(data.name); +template void FlatGLTest::renderObjectId3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) @@ -1110,21 +2380,40 @@ void FlatGLTest::renderObjectId3D() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - if(data.instanceCount) sphere - .setInstanceCount(data.instanceCount) - .addVertexBufferInstanced( - GL::Buffer{Containers::arrayView({11002u, 48823u})}, - 1, 0, FlatGL2D::ObjectId{}); - - FlatGL3D{data.flags} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setObjectId(data.uniformId) - .draw(sphere); + FlatGL3D shader{FlatGL3D::Flag::ObjectId|flag}; + + if(flag == FlatGL3D::Flag{}) { + shader.setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setObjectId(48526) + .draw(sphere); + } else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setObjectId(48526) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1159,11 +2448,30 @@ void FlatGLTest::renderObjectId3D() { /* Outside of the object, cleared to 27 */ CORRADE_COMPARE(image.pixels()[10][10], 27); /* Inside of the object */ - CORRADE_COMPARE(image.pixels()[40][46], data.expected); + CORRADE_COMPARE(image.pixels()[40][46], 48526); } #endif -void FlatGLTest::renderInstanced2D() { +template void FlatGLTest::renderInstanced2D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1179,10 +2487,6 @@ void FlatGLTest::renderInstanced2D() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); @@ -1190,64 +2494,250 @@ void FlatGLTest::renderInstanced2D() { struct { Matrix3 transformation; Color3 color; - Vector2 textureOffset; + Vector3 textureOffsetLayer; + UnsignedInt objectId; } instanceData[] { - {Matrix3::translation({-1.25f, -1.25f}), 0xff3333_rgbf, - {0.0f, 0.0f}}, - {Matrix3::translation({ 1.25f, -1.25f}), 0x33ff33_rgbf, - {1.0f, 0.0f}}, - {Matrix3::translation({ 0.00f, 1.25f}), 0x9999ff_rgbf, - {0.5f, 1.0f}} + {Matrix3::translation({-1.25f, -1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf, + {0.0f, 0.0f, 0.0f}, 211}, + {Matrix3::translation({ 1.25f, -1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0x00ffff_rgbf, + {1.0f, 0.0f, 1.0f}, 4627}, + {Matrix3::translation({ 0.00f, 1.25f}), + data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xff00ff_rgbf, + #ifndef MAGNUM_TARGET_GLES2 + data.flags & FlatGL2D::Flag::TextureArrays ? Vector3{0.0f, 0.0f, 2.0f} : + #endif + Vector3{0.5f, 1.0f, 2.0f}, 35363}, }; circle .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, FlatGL2D::TransformationMatrix{}, FlatGL2D::Color3{}, - FlatGL2D::TextureOffset{}) + #ifndef MAGNUM_TARGET_GLES2 + FlatGL2D::TextureOffsetLayer{}, + #else + FlatGL2D::TextureOffset{}, + 4, + #endif + #ifndef MAGNUM_TARGET_GLES2 + FlatGL2D::ObjectId{} + #else + 4 + #endif + ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); + /* Enable also Object ID, if supported */ + FlatGL2D::Flags flags = FlatGL2D::Flag::VertexColor| + FlatGL2D::Flag::InstancedTransformation|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + flags |= FlatGL2D::Flag::InstancedObjectId; + } + #endif + FlatGL2D shader{flags}; - GL::Texture2D texture; - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif + if(data.flags & FlatGL3D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - FlatGL2D{FlatGL2D::Flag::Textured| - FlatGL2D::Flag::VertexColor| - FlatGL2D::Flag::InstancedTransformation| - FlatGL2D::Flag::InstancedTextureOffset} - .setColor(0xffff99_rgbf) - .setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})* - Matrix3::scaling(Vector2{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindTexture(texture) - .draw(circle); + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); - MAGNUM_VERIFY_NO_GL_ERROR(); + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* Minor differences on AMD, SwiftShader a bit more */ - const Float maxThreshold = 3.0f, meanThreshold = 0.018f; - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - const Float maxThreshold = 3.0f, meanThreshold = 0.018f; + #ifndef MAGNUM_TARGET_GLES2 + /* For arrays we upload three slices of the original image to half-high + slices */ + if(data.flags & FlatGL2D::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Three slices with 2 extra as a base offset, each slice has + half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 2 + 3}) + .setSubImage(0, {0, 0, 2}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 3}, second) + .setSubImage(0, {0, 0, 4}, third); + shader.bindTexture(textureArray); + if(flag != FlatGL2D::Flag::UniformBuffers) + shader.setTextureLayer(2); /* base offset */ + + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } + } + + if(flag == FlatGL2D::Flag{}) { + shader + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})); + + if(data.flags & FlatGL3D::Flag::Textured) + shader.setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & FlatGL2D::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f} + )); + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + shader.setObjectId(1000); /* gets added to the per-instance ID */ + } + #endif + + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setObjectId(1000) /* gets added to the per-instance ID */ + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & FlatGL2D::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f})) + .setLayer(2) /* base offset */ + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) + }}; + if(data.flags & FlatGL3D::Flag::Textured) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - First should be lower left, yellow with a yellow base color, so + yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red + + Textured case: + + - Lower left has bottom left numbers, so light 7881 + - Lower light has bottom right, 1223 + - Up center has 6778 + */ CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), - Utility::Directory::join(_testDir, "FlatTestFiles/instanced2D.tga"), - (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + #ifndef MAGNUM_TARGET_GLES2 + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); + CORRADE_COMPARE(image.pixels()[24][56], 5627); + CORRADE_COMPARE(image.pixels()[56][40], 36363); + } + #endif } -void FlatGLTest::renderInstanced3D() { +template void FlatGLTest::renderInstanced3D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1263,10 +2753,6 @@ void FlatGLTest::renderInstanced3D() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -1274,63 +2760,833 @@ void FlatGLTest::renderInstanced3D() { struct { Matrix4 transformation; Color3 color; - Vector2 textureOffset; + Vector3 textureOffsetLayer; + UnsignedInt objectId; } instanceData[] { - {Matrix4::translation({-1.25f, -1.25f, 0.0f}), 0xff3333_rgbf, - {0.0f, 0.0f}}, - {Matrix4::translation({ 1.25f, -1.25f, 0.0f}), 0x33ff33_rgbf, - {1.0f, 0.0f}}, - {Matrix4::translation({ 0.0f, 1.0f, 1.0f}), 0x9999ff_rgbf, - {0.5f, 1.0f}} + {Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* To be consistent with Phong's output where it tests that the + normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf, + {0.0f, 0.0f, 0.0f}, 211}, + {Matrix4::translation({ 1.25f, -1.25f, 0.0f}), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0x00ffff_rgbf, + {1.0f, 0.0f, 1.0f}, 4627}, + {Matrix4::translation({ 0.0f, 1.0f, 1.0f}), + data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xff00ff_rgbf, + #ifndef MAGNUM_TARGET_GLES2 + data.flags & FlatGL2D::Flag::TextureArrays ? Vector3{0.0f, 0.0f, 2.0f} : + #endif + Vector3{0.5f, 1.0f, 2.0f}, 35363} }; sphere .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, FlatGL3D::TransformationMatrix{}, FlatGL3D::Color3{}, - FlatGL3D::TextureOffset{}) + #ifndef MAGNUM_TARGET_GLES2 + FlatGL2D::TextureOffsetLayer{}, + #else + FlatGL2D::TextureOffset{}, + 4, + #endif + #ifndef MAGNUM_TARGET_GLES2 + FlatGL2D::ObjectId{} + #else + 4 + #endif + ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); + /* Enable also Object ID, if supported */ + FlatGL3D::Flags flags = FlatGL3D::Flag::VertexColor| + FlatGL3D::Flag::InstancedTransformation|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + flags |= FlatGL2D::Flag::InstancedObjectId; + } + #endif + FlatGL3D shader{flags}; - GL::Texture2D texture; - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif + if(data.flags & FlatGL2D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + #ifndef MAGNUM_TARGET_GLES2 + /* For arrays we upload three slices of the original image to half-high + slices */ + if(data.flags & FlatGL2D::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Three slices with 2 extra as a base offset, each slice has + half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 2 + 3}) + .setSubImage(0, {0, 0, 2}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 3}, second) + .setSubImage(0, {0, 0, 4}, third); + shader.bindTexture(textureArray); + if(flag != FlatGL2D::Flag::UniformBuffers) + shader.setTextureLayer(2); /* base offset */ + + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } + } + + if(flag == FlatGL3D::Flag{}) { + shader + .setColor(data.flags & FlatGL2D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})); + + if(data.flags & FlatGL3D::Flag::Textured) + shader.setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & FlatGL2D::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f} + )); + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + shader.setObjectId(1000); /* gets added to the per-instance ID */ + } + #endif + + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setObjectId(1000) /* gets added to the per-instance ID */ + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & FlatGL2D::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f})) + .setLayer(2) /* base offset */ + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(data.flags & FlatGL3D::Flag::Textured ? 0xffffff_rgbf : 0xffff00_rgbf) + }}; + if(data.flags & FlatGL3D::Flag::Textured) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - First should be lower left, yellow with a yellow base color, so + yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red + + Textured case: + + - Lower left has bottom left numbers, so light 7881, rotated (78 + visible) + - Lower light has bottom right, 1223 + - Up center has 6778 + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + #ifndef MAGNUM_TARGET_GLES2 + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); + CORRADE_COMPARE(image.pixels()[24][56], 5627); + CORRADE_COMPARE(image.pixels()[56][40], 36363); + } + #endif +} + +#ifndef MAGNUM_TARGET_GLES2 +void FlatGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif - FlatGL3D{FlatGL3D::Flag::Textured| - FlatGL3D::Flag::VertexColor| - FlatGL3D::Flag::InstancedTransformation| - FlatGL3D::Flag::InstancedTextureOffset} - .setColor(0xffff99_rgbf) + FlatGL2D shader{FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId|data.flags, data.materialCount, data.drawCount}; + + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif + if(data.flags & FlatGL3D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + #ifndef MAGNUM_TARGET_GLES2 + /* For arrays we upload three slices of the original image to half-high + slices */ + if(data.flags & FlatGL2D::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Each slice has half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 3}) + .setSubImage(0, {0, 0, 0}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 1}, second) + .setSubImage(0, {0, 0, 2}, third); + shader.bindTexture(textureArray); + + } else + #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } + } + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f})) + .setLayer(0); /* ignored if not array */ + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({1.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f})) + .setLayer(1); /* ignored if not array */ + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f})) + .setLayer(2); /* ignored if not array */ + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are + done with UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + + if(data.flags >= FlatGL2D::Flag::MultiDraw) + shader.draw({circle, square, triangle}); + else { + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - Circle should be lower left, red + - Square lower right, blue + - Triangle up center, red + + Textured case: + + - Circle should have bottom left numbers, so light 7881 + - Square bottom right, 1223 + - Triangle 6778 + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Circle */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Square */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Triangle */ + } +} + +void FlatGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + FlatGL3D shader{FlatGL3D::Flag::UniformBuffers|FlatGL3D::Flag::ObjectId|data.flags, data.materialCount, data.drawCount}; + + GL::Texture2D texture{NoCreate}; + GL::Texture2DArray textureArray{NoCreate}; + if(data.flags & FlatGL3D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + /* For arrays we upload three slices of the original image to half-high + slices */ + if(data.flags & FlatGL2D::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Each slice has half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 3}) + .setSubImage(0, {0, 0, 0}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 1}, second) + .setSubImage(0, {0, 0, 2}, third); + shader.bindTexture(textureArray); + + } else { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindTexture(texture); + } + } + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} .setTransformationProjectionMatrix( Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::scaling(Vector3{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindTexture(texture) - .draw(sphere); + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* To be consistent with Phong's output where it tests that the + normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f})) + .setLayer(0); /* ignored if not array */ + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({1.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f})) + .setLayer(1); /* ignored if not array */ + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & FlatGL2D::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f})) + .setLayer(2); /* ignored if not array */ + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + + if(data.flags >= FlatGL3D::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + } MAGNUM_VERIFY_NO_GL_ERROR(); - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* Minor differences on AMD, SwiftShader a bit more */ - const Float maxThreshold = 67.67f, meanThreshold = 0.062f; - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - const Float maxThreshold = 67.67f, meanThreshold = 0.062f; - #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Colored case: + + - Sphere should be lower left, red + - Plane lower right, blue + - Cone up center, red + + Textured case: + + - Sphere should have bottom left numbers, so light 7881, rotated (78 + visible) + - Plane bottom right, 1223 + - Cone 6778 + */ CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), - Utility::Directory::join(_testDir, "FlatTestFiles/instanced3D.tga"), - (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); + Utility::Directory::join({_testDir, "FlatTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Sphere */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Plane */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Circle */ + } } +#endif }}}} diff --git a/src/Magnum/Shaders/Test/FlatGL_Test.cpp b/src/Magnum/Shaders/Test/FlatGL_Test.cpp index 566ac3eb3..f4e9c183c 100644 --- a/src/Magnum/Shaders/Test/FlatGL_Test.cpp +++ b/src/Magnum/Shaders/Test/FlatGL_Test.cpp @@ -78,8 +78,8 @@ template void FlatGL_Test::constructCopy() { void FlatGL_Test::debugFlag() { std::ostringstream out; - Debug{&out} << FlatGL3D::Flag::Textured << FlatGL3D::Flag(0xf0); - CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::Textured Shaders::FlatGL::Flag(0xf0)\n"); + Debug{&out} << FlatGL3D::Flag::Textured << FlatGL3D::Flag(0xf00d); + CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::Textured Shaders::FlatGL::Flag(0xf00d)\n"); } void FlatGL_Test::debugFlags() { @@ -102,9 +102,20 @@ void FlatGL_Test::debugFlagsSupersets() { /* InstancedTextureOffset is a superset of TextureTransformation so only one should be printed */ - std::ostringstream out; - Debug{&out} << (FlatGL3D::Flag::InstancedTextureOffset|FlatGL3D::Flag::TextureTransformation); - CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::InstancedTextureOffset\n"); + { + std::ostringstream out; + Debug{&out} << (FlatGL3D::Flag::InstancedTextureOffset|FlatGL3D::Flag::TextureTransformation); + CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::InstancedTextureOffset\n"); + } + + #ifndef MAGNUM_TARGET_GLES2 + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + { + std::ostringstream out; + Debug{&out} << (FlatGL3D::Flag::MultiDraw|FlatGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::MultiDraw\n"); + } + #endif } }}}} diff --git a/src/Magnum/Shaders/Test/FlatTest.cpp b/src/Magnum/Shaders/Test/FlatTest.cpp new file mode 100644 index 000000000..206c04f7d --- /dev/null +++ b/src/Magnum/Shaders/Test/FlatTest.cpp @@ -0,0 +1,198 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Flat.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct FlatTest: TestSuite::Tester { + explicit FlatTest(); + + template void uniformSizeAlignment(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +FlatTest::FlatTest() { + addTests({&FlatTest::uniformSizeAlignment, + &FlatTest::uniformSizeAlignment, + + &FlatTest::drawUniformConstructDefault, + &FlatTest::drawUniformConstructNoInit, + &FlatTest::drawUniformSetters, + &FlatTest::drawUniformMaterialIdPacking, + + &FlatTest::materialUniformConstructDefault, + &FlatTest::materialUniformConstructNoInit, + &FlatTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "FlatDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "FlatMaterialUniform"; } +}; + +template void FlatTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void FlatTest::drawUniformConstructDefault() { + FlatDrawUniform a; + FlatDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + CORRADE_COMPARE(a.objectId, 0); + CORRADE_COMPARE(b.objectId, 0); + + constexpr FlatDrawUniform ca; + constexpr FlatDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + CORRADE_COMPARE(ca.objectId, 0); + CORRADE_COMPARE(cb.objectId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + FlatDrawUniform a; + a.materialId = 5; + a.objectId = 7; + + new(&a) FlatDrawUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::drawUniformSetters() { + FlatDrawUniform a; + a.setMaterialId(5) + .setObjectId(7); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); +} + +void FlatTest::drawUniformMaterialIdPacking() { + FlatDrawUniform a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void FlatTest::materialUniformConstructDefault() { + FlatMaterialUniform a; + FlatMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.5f); + CORRADE_COMPARE(b.alphaMask, 0.5f); + + constexpr FlatMaterialUniform ca; + constexpr FlatMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.alphaMask, 0.5f); + CORRADE_COMPARE(cb.alphaMask, 0.5f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + FlatMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.alphaMask = 0.7f; + + new(&a) FlatMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.7f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::materialUniformSetters() { + FlatMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setAlphaMask(0.7f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.7f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::FlatTest) diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga new file mode 100644 index 000000000..01359db39 Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured2D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga new file mode 100644 index 000000000..9ec1a93c8 Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/instanced-textured3D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga index a130de40e..11dec3675 100644 Binary files a/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga and b/src/Magnum/Shaders/Test/FlatTestFiles/instanced2D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga index c1c4d9640..0aa0bb4d5 100644 Binary files a/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga and b/src/Magnum/Shaders/Test/FlatTestFiles/instanced3D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga new file mode 100644 index 000000000..31d45ce3e Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured3D.tga new file mode 100644 index 000000000..a7925377c Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured3D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/multidraw2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw2D.tga new file mode 100644 index 000000000..31fed96b9 Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw2D.tga differ diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/multidraw3D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw3D.tga new file mode 100644 index 000000000..030916e7d Binary files /dev/null and b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw3D.tga differ diff --git a/src/Magnum/Shaders/Test/GenericTest.cpp b/src/Magnum/Shaders/Test/GenericTest.cpp index e69de29bb..cf0782601 100644 --- a/src/Magnum/Shaders/Test/GenericTest.cpp +++ b/src/Magnum/Shaders/Test/GenericTest.cpp @@ -0,0 +1,443 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Generic.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GenericTest: TestSuite::Tester { + explicit GenericTest(); + + template void uniformSizeAlignment(); + + void projectionUniform2DConstructDefault(); + void projectionUniform2DConstructNoInit(); + void projectionUniform2DSetters(); + + void projectionUniform3DConstructDefault(); + void projectionUniform3DConstructNoInit(); + void projectionUniform3DSetters(); + + void transformationUniform2DConstructDefault(); + void transformationUniform2DConstructNoInit(); + void transformationUniform2DSetters(); + + void transformationUniform3DConstructDefault(); + void transformationUniform3DConstructNoInit(); + void transformationUniform3DSetters(); + + void textureTransformationUniformConstructDefault(); + void textureTransformationUniformConstructNoInit(); + void textureTransformationUniformSetters(); +}; + +GenericTest::GenericTest() { + addTests({&GenericTest::uniformSizeAlignment, + &GenericTest::uniformSizeAlignment, + &GenericTest::uniformSizeAlignment, + &GenericTest::uniformSizeAlignment, + &GenericTest::uniformSizeAlignment, + + &GenericTest::projectionUniform2DConstructDefault, + &GenericTest::projectionUniform2DConstructNoInit, + &GenericTest::projectionUniform2DSetters, + + &GenericTest::projectionUniform3DConstructDefault, + &GenericTest::projectionUniform3DConstructNoInit, + &GenericTest::projectionUniform3DSetters, + + &GenericTest::transformationUniform2DConstructDefault, + &GenericTest::transformationUniform2DConstructNoInit, + &GenericTest::transformationUniform2DSetters, + + &GenericTest::transformationUniform3DConstructDefault, + &GenericTest::transformationUniform3DConstructNoInit, + &GenericTest::transformationUniform3DSetters, + + &GenericTest::textureTransformationUniformConstructDefault, + &GenericTest::textureTransformationUniformConstructNoInit, + &GenericTest::textureTransformationUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "ProjectionUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "ProjectionUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TransformationUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TransformationUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TextureTransformationUniform"; } +}; + +template void GenericTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void GenericTest::projectionUniform2DConstructDefault() { + ProjectionUniform2D a; + ProjectionUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + constexpr ProjectionUniform2D ca; + constexpr ProjectionUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform2DConstructNoInit() { + ProjectionUniform2D a; + a.projectionMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) ProjectionUniform2D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.projectionMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform2DSetters() { + ProjectionUniform2D a; + a.setProjectionMatrix(Matrix3::projection({2.5f, 3.0f})); + CORRADE_COMPARE(a.projectionMatrix, (Matrix3x4{ + Vector4{0.8f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.666667f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); +} + +void GenericTest::projectionUniform3DConstructDefault() { + ProjectionUniform3D a; + ProjectionUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(b.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + constexpr ProjectionUniform3D ca; + constexpr ProjectionUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform3DConstructNoInit() { + ProjectionUniform3D a; + a.projectionMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) ProjectionUniform3D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.projectionMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform3DSetters() { + ProjectionUniform3D a; + a.setProjectionMatrix(Matrix4::perspectiveProjection(35.0_degf, 1.5f, 0.1f, 100.0f)); + CORRADE_COMPARE(a.projectionMatrix, Matrix4::perspectiveProjection(35.0_degf, 1.5f, 0.1f, 100.0f)); +} + +void GenericTest::transformationUniform2DConstructDefault() { + TransformationUniform2D a; + TransformationUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + constexpr TransformationUniform2D ca; + constexpr TransformationUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform2DConstructNoInit() { + TransformationUniform2D a; + a.transformationMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) TransformationUniform2D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.transformationMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform2DSetters() { + TransformationUniform2D a; + a.setTransformationMatrix(Matrix3::translation({2.5f, 3.0f})); + CORRADE_COMPARE(a.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{2.5f, 3.0f, 1.0f, 0.0f} + })); +} + +void GenericTest::transformationUniform3DConstructDefault() { + TransformationUniform3D a; + TransformationUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(b.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + constexpr TransformationUniform3D ca; + constexpr TransformationUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform3DConstructNoInit() { + TransformationUniform3D a; + a.transformationMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) TransformationUniform3D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.transformationMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform3DSetters() { + TransformationUniform3D a; + a.setTransformationMatrix(Matrix4::translation({3.5f, 2.0f, -1.0f})); + CORRADE_COMPARE(a.transformationMatrix, Matrix4::translation({3.5f, 2.0f, -1.0f})); +} + +void GenericTest::textureTransformationUniformConstructDefault() { + TextureTransformationUniform a; + TextureTransformationUniform b{DefaultInit}; + CORRADE_COMPARE(a.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(b.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(a.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(b.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(a.layer, 0); + CORRADE_COMPARE(b.layer, 0); + + constexpr TextureTransformationUniform ca; + constexpr TextureTransformationUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(ca.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(cb.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(ca.layer, 0); + CORRADE_COMPARE(cb.layer, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::textureTransformationUniformConstructNoInit() { + TextureTransformationUniform a; + a.rotationScaling[1] = {2.5f, -3.0f}; + a.offset = {2.7f, 0.3f}; + a.layer = 37; + + new(&a) TextureTransformationUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.rotationScaling[1], (Vector2{2.5f, -3.0f})); + CORRADE_COMPARE(a.offset, (Vector2{2.7f, 0.3f})); + CORRADE_COMPARE(a.layer, 37); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::textureTransformationUniformSetters() { + TextureTransformationUniform a; + a.setTextureMatrix(Matrix3::translation({2.6f, 0.3f})* + Matrix3::rotation(90.0_degf)) + .setLayer(37); + CORRADE_COMPARE(a.rotationScaling, (Matrix2x2{ + Vector2{ 0.0f, 1.0f}, + Vector2{-1.0f, 0.0f} + })); + CORRADE_COMPARE(a.offset, (Vector2{2.6f, 0.3f})); + CORRADE_COMPARE(a.layer, 37); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GenericTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 183d504f4..eafd8f0f7 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -61,6 +61,17 @@ #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/MeshVisualizer.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -69,23 +80,38 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { explicit MeshVisualizerGLTest(); void construct2D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers2D(); + #endif void construct3D(); - - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void constructWireframeGeometryShader2D(); - void constructGeometryShader3D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers3D(); #endif void construct2DInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers2DInvalid(); + #endif void construct3DInvalid(); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void construct3DGeometryShaderDisabledButNeeded(); - void construct3DConflictingBitangentInput(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers3DInvalid(); #endif void constructMove2D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers2D(); + #endif void constructMove3D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers3D(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + void setUniformUniformBuffersEnabled2D(); + void setUniformUniformBuffersEnabled3D(); + void bindBufferUniformBuffersNotEnabled2D(); + void bindBufferUniformBuffersNotEnabled3D(); + #endif void setWireframeNotEnabled2D(); void setWireframeNotEnabled3D(); #ifndef MAGNUM_TARGET_GLES2 @@ -95,34 +121,44 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void setTangentBitangentNormalNotEnabled3D(); #endif + #ifndef MAGNUM_TARGET_GLES2 + void setWrongDrawOffset2D(); + void setWrongDrawOffset3D(); + #endif void renderSetup(); void renderTeardown(); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void renderDefaultsWireframe2D(); - void renderDefaultsWireframe3D(); + template void renderDefaultsWireframe2D(); + template void renderDefaultsWireframe3D(); #endif #ifndef MAGNUM_TARGET_GLES2 - void renderDefaultsObjectId2D(); - void renderDefaultsObjectId3D(); - void renderDefaultsVertexId2D(); - void renderDefaultsVertexId3D(); - void renderDefaultsPrimitiveId2D(); - void renderDefaultsPrimitiveId3D(); + template void renderDefaultsObjectId2D(); + template void renderDefaultsObjectId3D(); + template void renderDefaultsVertexId2D(); + template void renderDefaultsVertexId3D(); + template void renderDefaultsPrimitiveId2D(); + template void renderDefaultsPrimitiveId3D(); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void renderDefaultsTangentBitangentNormal(); + template void renderDefaultsTangentBitangentNormal(); #endif - void renderWireframe2D(); - void renderWireframe3D(); + template void renderWireframe2D(); + template void renderWireframe3D(); #ifndef MAGNUM_TARGET_GLES2 - void renderObjectVertexPrimitiveId2D(); - void renderObjectVertexPrimitiveId3D(); + template void renderObjectVertexPrimitiveId2D(); + template void renderObjectVertexPrimitiveId3D(); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /* This tests something that's irrelevant to UBOs */ void renderWireframe3DPerspective(); - void renderTangentBitangentNormal(); + template void renderTangentBitangentNormal(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); #endif private: @@ -138,15 +174,32 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - . Mesa llvmpipe - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 (except TBN visualization) - - WebGL 1 / 2 (on Mesa Intel) (except primitive/vertex/object ID) - - iPhone 6 w/ iOS 12.4 (except primitive/vertex/object ID) + Rendering tests done: + + [W] wireframe + [D] primitive/vertex/object ID + [T] TBN visualization + [O] draw offset + [M] multidraw + + Mesa Intel WDTOM + ES2 xx + ES3 x + Mesa AMD WDT + Mesa llvmpipe WDT + SwiftShader ES2 WDxxx + ES3 WDx + ANGLE ES2 xx + ES3 WDxOM + ARM Mali (Huawei P10) ES2 W xxx + ES3 W Ox (WDT big diffs, needs investigation) + WebGL (on Mesa Intel) 1.0 W xxx + 2.0 W x M + NVidia + Intel Windows + AMD macOS + Intel macOS WDTOx + iPhone 6 w/ iOS 12.4 ES3 W x x */ using namespace Math::Literals; @@ -155,6 +208,11 @@ constexpr struct { const char* name; MeshVisualizerGL2D::Flags flags; } ConstructData2D[] { + /* Whatever is added here should probably go also into + ConstructUniformBuffersData2D */ + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL2D::Flag::Wireframe}, + #endif {"wireframe w/o GS", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 {"object ID", MeshVisualizerGL2D::Flag::InstancedObjectId}, @@ -166,10 +224,45 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData2D[] { + {"classic fallback", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-2D-draw is 4, + per-material 4, two need to be left for drawOffset + viewportSize */ + {"multiple materials, draws", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 8, 55}, + {"multidraw with wireframe w/o GS and vertex ID", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader|MeshVisualizerGL2D::Flag::VertexId, 8, 55}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw with wireframe and primitive ID", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::PrimitiveId, 8, 55}, + #endif + /* The rest is basically a copy of ConstructData2D with UniformBuffers + added */ + #ifndef MAGNUM_TARGET_WEBGL + {"wireframe", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, 1, 1}, + #endif + {"wireframe w/o GS", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"object ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveId, 1, 1}, + #endif + {"primitive ID from vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, 1, 1} +}; +#endif + constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; } ConstructData3D[] { + /* Whatever is added here should probably go also into + ConstructUniformBuffersData3D */ + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL3D::Flag::Wireframe}, + #endif {"wireframe w/o GS", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 {"object ID", MeshVisualizerGL3D::Flag::InstancedObjectId}, @@ -177,16 +270,9 @@ constexpr struct { #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizerGL3D::Flag::PrimitiveId}, #endif - {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId} + {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId}, #endif -}; - -#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -constexpr struct { - const char* name; - MeshVisualizerGL3D::Flags flags; -} ConstructGeometryShaderData3D[] { - {"wireframe", MeshVisualizerGL3D::Flag::Wireframe}, + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) {"tangent direction", MeshVisualizerGL3D::Flag::TangentDirection}, {"bitangent direction from tangent", MeshVisualizerGL3D::Flag::BitangentFromTangentDirection}, {"bitangent direction", MeshVisualizerGL3D::Flag::BitangentDirection}, @@ -196,7 +282,52 @@ constexpr struct { {"wireframe + vertex id", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId}, {"wireframe + t/n direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection}, {"wireframe + object id + t/n direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection}, - {"wireframe + vertex id + t/b direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection} + {"wireframe + vertex id + t/b direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection}, + /* InstancedObjectId|BitangentDirection is disallowed (checked in + ConstructInvalidData3D), but this should work */ + {"object id + bitangent from tangent direction", MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection}, + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData3D[] { + {"classic fallback", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, + per-material 4, plus 4 for projection */ + {"multiple materials, draws", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 6, 28}, + {"multidraw with wireframe w/o GS and vertex ID", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::VertexId, 6, 28}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw with wireframe, primitive ID and TBN", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::PrimitiveId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 6, 28}, + #endif + /* The rest is basically a copy of ConstructData2D with UniformBuffers + added */ + #ifndef MAGNUM_TARGET_WEBGL + {"wireframe", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, 1, 1}, + #endif + {"wireframe w/o GS", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"object ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveId, 1, 1}, + #endif + {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"tangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection, 1, 1}, + {"bitangent direction from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection, 1, 1}, + {"bitangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentDirection, 1, 1}, + {"normal direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"tbn direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"tbn direction with bitangent from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + vertex id", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + {"wireframe + t/n direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + object id + t/n direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + vertex id + t/b direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection, 1, 1} + #endif }; #endif @@ -223,6 +354,20 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData2D[] { + {"zero draws", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 0, + "draw count can't be zero"}, + {"zero materials", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 0, 1, + "material count can't be zero"} +}; +#endif + constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; @@ -242,9 +387,34 @@ constexpr struct { ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both vertex and primitive id", MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"} + ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"geometry shader disabled but needed", + MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::NormalDirection, + "3D: geometry shader has to be enabled when rendering TBN direction"}, + {"conflicting bitangent input", + MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection, + "3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive"}, + {"conflicting bitangent and instanced object id attribute", + MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::InstancedObjectId, + "3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead"}, + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData3D[] { + {"zero draws", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 0, + "draw count can't be zero"}, + {"zero materials", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 0, 1, + "material count can't be zero"}, }; +#endif #ifndef MAGNUM_TARGET_GLES2 constexpr struct { @@ -403,87 +573,294 @@ constexpr struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData2D[] { + #ifndef MAGNUM_TARGET_WEBGL + {"bind with offset, wireframe", "multidraw-wireframe2D.tga", + MeshVisualizerGL2D::Flag::Wireframe, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #endif + {"bind with offset, w/o GS", "multidraw-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 0.67f, 0.02f}, + {"bind with offset, vertex ID", "multidraw-vertexid2D.tga", + MeshVisualizerGL2D::Flag::VertexId, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #ifndef MAGNUM_TARGET_WEBGL + {"draw offset, wireframe", "multidraw-wireframe2D.tga", + MeshVisualizerGL2D::Flag::Wireframe, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #endif + {"draw offset, wireframe w/o GS", "multidraw-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.02f}, + {"draw offset, vertex ID", "multidraw-vertexid2D.tga", + MeshVisualizerGL2D::Flag::VertexId, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw, wireframe", "multidraw-wireframe2D.tga", + MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #endif + {"multidraw, wireframe w/o GS", "multidraw-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.02f}, + {"multidraw, vertex ID", "multidraw-vertexid2D.tga", + MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::VertexId, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, +}; + +constexpr struct { + const char* name; + const char* expected; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData3D[] { + #ifndef MAGNUM_TARGET_WEBGL + {"bind with offset, wireframe", "multidraw-wireframe3D.tga", + MeshVisualizerGL3D::Flag::Wireframe, + 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, wireframe + TBN", "multidraw-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 1, 1, 16, 0.0f, 0.0f}, + #endif + {"bind with offset, wireframe w/o GS", "multidraw-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 6.0f, 0.04f}, + {"bind with offset, vertex ID", "multidraw-vertexid3D.tga", + MeshVisualizerGL3D::Flag::VertexId, + 1, 1, 16, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #ifndef MAGNUM_TARGET_WEBGL + {"draw offset, wireframe", "multidraw-wireframe3D.tga", + MeshVisualizerGL3D::Flag::Wireframe, + 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, wireframe + TBN", "multidraw-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 2, 3, 1, 0.0f, 0.0f}, + #endif + {"draw offset, wireframe w/o GS", "multidraw-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 6.0f, 0.04f}, + {"draw offset, vertex ID", "multidraw-vertexid3D.tga", + MeshVisualizerGL3D::Flag::VertexId, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw, wireframe", "multidraw-wireframe3D.tga", + MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe, + 2, 3, 1, 0.0f, 0.0f}, + {"multidraw, wireframe + TBN", "multidraw-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 2, 3, 1, 0.0f, 0.0f}, + #endif + {"multidraw, wireframe w/o GS", "multidraw-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 6.0f, 0.04f}, + {"multidraw, vertex ID", "multidraw-vertexid3D.tga", + MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::VertexId, + 2, 3, 1, + /* Minor differences on ARM Mali */ + 0.67f, 0.01f}, +}; +#endif + MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct2D}, Containers::arraySize(ConstructData2D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2D}, + Containers::arraySize(ConstructUniformBuffersData2D)); + #endif + addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - addTests({&MeshVisualizerGLTest::constructWireframeGeometryShader2D}); - - addInstancedTests({&MeshVisualizerGLTest::constructGeometryShader3D}, - Containers::arraySize(ConstructGeometryShaderData3D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3D}, + Containers::arraySize(ConstructUniformBuffersData3D)); #endif addInstancedTests({&MeshVisualizerGLTest::construct2DInvalid}, Containers::arraySize(ConstructInvalidData2D)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2DInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData2D)); + #endif + addInstancedTests({&MeshVisualizerGLTest::construct3DInvalid}, Containers::arraySize(ConstructInvalidData3D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3DInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData3D)); + #endif + addTests({ - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - &MeshVisualizerGLTest::construct3DGeometryShaderDisabledButNeeded, - &MeshVisualizerGLTest::construct3DConflictingBitangentInput, - #endif - - &MeshVisualizerGLTest::constructMove2D, - &MeshVisualizerGLTest::constructMove3D, - - &MeshVisualizerGLTest::setWireframeNotEnabled2D, - &MeshVisualizerGLTest::setWireframeNotEnabled3D, - #ifndef MAGNUM_TARGET_GLES2 - &MeshVisualizerGLTest::setColorMapNotEnabled2D, - &MeshVisualizerGLTest::setColorMapNotEnabled3D, - #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - &MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D, - #endif - }); + &MeshVisualizerGLTest::constructMove2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::constructMoveUniformBuffers2D, + #endif + &MeshVisualizerGLTest::constructMove3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::constructMoveUniformBuffers3D, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D, + &MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D, + &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D, + &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D, + #endif + &MeshVisualizerGLTest::setWireframeNotEnabled2D, + &MeshVisualizerGLTest::setWireframeNotEnabled3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setColorMapNotEnabled2D, + &MeshVisualizerGLTest::setColorMapNotEnabled3D, + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + &MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setWrongDrawOffset2D, + &MeshVisualizerGLTest::setWrongDrawOffset3D + #endif + }); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - addTests({&MeshVisualizerGLTest::renderDefaultsWireframe2D, - &MeshVisualizerGLTest::renderDefaultsWireframe3D}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &MeshVisualizerGLTest::renderDefaultsWireframe2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsWireframe2D, + #endif + &MeshVisualizerGLTest::renderDefaultsWireframe3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsWireframe3D, + #endif + }, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&MeshVisualizerGLTest::renderDefaultsObjectId2D, - &MeshVisualizerGLTest::renderDefaultsObjectId3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #endif + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #endif + }, Containers::arraySize(ObjectIdDefaultsData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif #ifndef MAGNUM_TARGET_GLES2 - addTests({ + /* MSVC needs explicit type due to default template args */ + addTests({ &MeshVisualizerGLTest::renderDefaultsVertexId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsVertexId2D, + #endif &MeshVisualizerGLTest::renderDefaultsVertexId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsVertexId3D, + #endif &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, + #endif &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, + #endif #ifndef MAGNUM_TARGET_WEBGL - &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal, + #endif #endif }, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif - addInstancedTests({&MeshVisualizerGLTest::renderWireframe2D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderWireframe2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderWireframe2D, + #endif + }, Containers::arraySize(WireframeData2D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - addInstancedTests({&MeshVisualizerGLTest::renderWireframe3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderWireframe3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderWireframe3D, + #endif + }, Containers::arraySize(WireframeData3D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, - &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, + #endif + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D, + #endif + }, Containers::arraySize(ObjectVertexPrimitiveIdData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); @@ -494,12 +871,30 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - addInstancedTests({&MeshVisualizerGLTest::renderTangentBitangentNormal}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderTangentBitangentNormal, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderTangentBitangentNormal, + #endif + }, Containers::arraySize(TangentBitangentNormalData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::renderMulti2D}, + Containers::arraySize(RenderMultiData2D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + + addInstancedTests({&MeshVisualizerGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData3D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -567,6 +962,23 @@ void MeshVisualizerGLTest::construct2D() { ) CORRADE_SKIP("gl_PrimitiveID not supported."); #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + MeshVisualizerGL2D shader{data.flags}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_VERIFY(shader.id()); @@ -580,17 +992,18 @@ void MeshVisualizerGLTest::construct2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } -void MeshVisualizerGLTest::construct3D() { - auto&& data = ConstructData3D[testCaseInstanceId()]; +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers2D() { + auto&& data = ConstructUniformBuffersData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES - if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId && #ifndef MAGNUM_TARGET_GLES !GL::Context::current().isVersionSupported(GL::Version::GL300) #else @@ -599,8 +1012,8 @@ void MeshVisualizerGLTest::construct3D() { ) CORRADE_SKIP("gl_VertexID not supported."); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags & MeshVisualizerGL2D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) && #ifndef MAGNUM_TARGET_GLES !GL::Context::current().isVersionSupported(GL::Version::GL320) #else @@ -609,7 +1022,42 @@ void MeshVisualizerGLTest::construct3D() { ) CORRADE_SKIP("gl_PrimitiveID not supported."); #endif - MeshVisualizerGL3D shader{data.flags}; + #ifndef MAGNUM_TARGET_WEBGL + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if(data.flags & MeshVisualizerGL2D::Flag::UniformBuffers && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + MeshVisualizerGL2D shader{data.flags, data.materialCount, data.drawCount}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_VERIFY(shader.id()); { @@ -621,59 +1069,143 @@ void MeshVisualizerGLTest::construct3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#endif + +void MeshVisualizerGLTest::construct3D() { + auto&& data = ConstructData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); -#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::constructWireframeGeometryShader2D() { #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif - #ifdef MAGNUM_TARGET_GLES - if(GL::Context::current().isExtensionSupported()) - Debug() << "Using" << GL::Extensions::NV::shader_noperspective_interpolation::string(); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); #endif - MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe}; - CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::Wireframe); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + MeshVisualizerGL3D shader{data.flags}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_VERIFY(shader.id()); { #ifdef CORRADE_TARGET_APPLE CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); #endif - CORRADE_VERIFY(shader.id()); CORRADE_VERIFY(shader.validate().first); } + + MAGNUM_VERIFY_NO_GL_ERROR(); } -void MeshVisualizerGLTest::constructGeometryShader3D() { - auto&& data = ConstructGeometryShaderData3D[testCaseInstanceId()]; +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers3D() { + auto&& data = ConstructUniformBuffersData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif - #ifdef MAGNUM_TARGET_GLES - if(GL::Context::current().isExtensionSupported()) - Debug() << "Using" << GL::Extensions::NV::shader_noperspective_interpolation::string(); + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); #endif - MeshVisualizerGL3D shader{data.flags}; + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if(data.flags & MeshVisualizerGL3D::Flag::UniformBuffers && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= MeshVisualizerGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + MeshVisualizerGL3D shader{data.flags, data.materialCount, data.drawCount}; CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_VERIFY(shader.id()); { #ifdef CORRADE_TARGET_APPLE CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); #endif - CORRADE_VERIFY(shader.id()); CORRADE_VERIFY(shader.validate().first); } + + MAGNUM_VERIFY_NO_GL_ERROR(); } #endif @@ -691,59 +1223,59 @@ void MeshVisualizerGLTest::construct2DInvalid() { CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } -void MeshVisualizerGLTest::construct3DInvalid() { - auto&& data = ConstructInvalidData3D[testCaseInstanceId()]; +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers2DInvalid() { + auto&& data = ConstructUniformBuffersInvalidData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + std::ostringstream out; Error redirectError{&out}; - MeshVisualizerGL3D{data.flags}; - CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); + MeshVisualizerGL2D{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL2D: {}\n", data.message)); } +#endif + +void MeshVisualizerGLTest::construct3DInvalid() { + auto&& data = ConstructInvalidData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); -#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::construct3DGeometryShaderDisabledButNeeded() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); - #endif - std::ostringstream out; Error redirectError{&out}; - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::NormalDirection}; - CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction\n"); + MeshVisualizerGL3D{data.flags}; + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } -void MeshVisualizerGLTest::construct3DConflictingBitangentInput() { +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers3DInvalid() { + auto&& data = ConstructUniformBuffersInvalidData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif std::ostringstream out; Error redirectError{&out}; - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection}; - CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive\n"); + MeshVisualizerGL3D{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL3D: {}\n", data.message)); } #endif @@ -766,6 +1298,36 @@ void MeshVisualizerGLTest::constructMove2D() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructMoveUniformBuffers2D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL2D a{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MeshVisualizerGL2D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL2D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + void MeshVisualizerGLTest::constructMove3D() { MeshVisualizerGL3D a{MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; const GLuint id = a.id(); @@ -773,17 +1335,182 @@ void MeshVisualizerGLTest::constructMove3D() { MAGNUM_VERIFY_NO_GL_ERROR(); - MeshVisualizerGL3D b{std::move(a)}; - CORRADE_COMPARE(b.id(), id); - CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_VERIFY(!a.id()); + MeshVisualizerGL3D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL3D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(!b.id()); +} + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructMoveUniformBuffers3D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL3D a{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MeshVisualizerGL3D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL3D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; + shader.setTransformationProjectionMatrix({}) + /* setViewportSize() works on both UBOs and classic */ + .setColor({}) + .setWireframeColor({}) + .setWireframeWidth({}) + .setColorMapTransformation({}, {}) + .setSmoothness({}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled\n"); +} + +void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; + shader.setProjectionMatrix({}) + .setTransformationMatrix({}) + /* setViewportSize() works on both UBOs and classic */ + .setColor({}) + .setWireframeColor({}) + .setWireframeWidth({}) + .setColorMapTransformation({}, {}) + .setSmoothness({}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::setProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setTransformationMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled\n"); + + out.str({}); + + #ifndef MAGNUM_TARGET_WEBGL + shader + .setNormalMatrix({}) + .setLineWidth({}) + .setLineLength({}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was created with uniform buffers enabled\n"); + #endif +} + +void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} + +void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; - MeshVisualizerGL3D c{NoCreate}; - c = std::move(b); - CORRADE_COMPARE(c.id(), id); - CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_VERIFY(!b.id()); + GL::Buffer buffer; + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; + shader.bindProjectionBuffer(buffer) + .bindProjectionBuffer(buffer, 0, 16) + .bindTransformationBuffer(buffer) + .bindTransformationBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } +#endif void MeshVisualizerGLTest::setWireframeNotEnabled2D() { #ifdef CORRADE_NO_ASSERT @@ -921,6 +1648,44 @@ void MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setWrongDrawOffset2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} + +void MeshVisualizerGLTest::setWrongDrawOffset3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void MeshVisualizerGLTest::renderSetup() { @@ -958,7 +1723,16 @@ void MeshVisualizerGLTest::renderTeardown() { } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::renderDefaultsWireframe2D() { +template void MeshVisualizerGLTest::renderDefaultsWireframe2D() { + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); @@ -974,8 +1748,26 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(16)); - MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe}; - shader.draw(circle); + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe|flag}; + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -993,9 +1785,26 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { } /** @todo make this unnecessary */ - shader - .setViewportSize({80, 80}) - .draw(circle); + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1007,7 +1816,16 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.082f})); } -void MeshVisualizerGLTest::renderDefaultsWireframe3D() { +template void MeshVisualizerGLTest::renderDefaultsWireframe3D() { + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); @@ -1023,8 +1841,30 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { GL::Mesh sphere = MeshTools::compile(Primitives::icosphereSolid(1)); - MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe}; - shader.draw(sphere); + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe|flag}; + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1042,9 +1882,30 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { } /** @todo make this unnecessary */ - shader - .setViewportSize({80, 80}) - .draw(sphere); + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1058,10 +1919,19 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { #endif #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderDefaultsObjectId2D() { +template void MeshVisualizerGLTest::renderDefaultsObjectId2D() { auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1089,9 +1959,27 @@ void MeshVisualizerGLTest::renderDefaultsObjectId2D() { Containers::arrayView(ids)} })); - MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::InstancedObjectId} - .bindColorMapTexture(colorMapTexture) - .draw(circle); + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::InstancedObjectId|flag}; + shader.bindColorMapTexture(colorMapTexture); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1107,10 +1995,19 @@ void MeshVisualizerGLTest::renderDefaultsObjectId2D() { (DebugTools::CompareImageToFile{_manager, 150.67f, 0.45f})); } -void MeshVisualizerGLTest::renderDefaultsObjectId3D() { +template void MeshVisualizerGLTest::renderDefaultsObjectId3D() { auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1138,9 +2035,31 @@ void MeshVisualizerGLTest::renderDefaultsObjectId3D() { Containers::arrayView(ids)} })); - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::InstancedObjectId} - .bindColorMapTexture(colorMapTexture) - .draw(icosphere); + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::InstancedObjectId|flag}; + shader.bindColorMapTexture(colorMapTexture); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1156,7 +2075,16 @@ void MeshVisualizerGLTest::renderDefaultsObjectId3D() { (DebugTools::CompareImageToFile{_manager, 150.67f, 0.165f})); } -void MeshVisualizerGLTest::renderDefaultsVertexId2D() { +template void MeshVisualizerGLTest::renderDefaultsVertexId2D() { + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1165,9 +2093,29 @@ void MeshVisualizerGLTest::renderDefaultsVertexId2D() { if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP("gl_VertexID not supported"); - MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::VertexId} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(Primitives::circle2DSolid(16))); + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(16)); + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::VertexId|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1178,7 +2126,16 @@ void MeshVisualizerGLTest::renderDefaultsVertexId2D() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.017f})); } -void MeshVisualizerGLTest::renderDefaultsVertexId3D() { +template void MeshVisualizerGLTest::renderDefaultsVertexId3D() { + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1187,9 +2144,33 @@ void MeshVisualizerGLTest::renderDefaultsVertexId3D() { if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP("gl_VertexID not supported"); - MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::VertexId} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(Primitives::icosphereSolid(0))); + GL::Mesh icosphere = MeshTools::compile(Primitives::icosphereSolid(0)); + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::VertexId|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1199,7 +2180,17 @@ void MeshVisualizerGLTest::renderDefaultsVertexId3D() { Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/defaults-vertexid3D.tga"), (DebugTools::CompareImageToFile{_manager, 1.0f, 0.012f})); } -void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { + +template void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1237,9 +2228,29 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { if(flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) circleData = MeshTools::duplicate(MeshTools::generateIndices(circleData)); - MeshVisualizerGL2D{flags} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(circleData)); + GL::Mesh circle = MeshTools::compile(circleData); + + MeshVisualizerGL2D shader{flags|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1251,7 +2262,16 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { (DebugTools::CompareImageToFile{_manager, 76.67f, 0.23f})); } -void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { +template void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1269,9 +2289,9 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { ) CORRADE_SKIP("gl_VertexID not supported."); #endif - MeshVisualizerGL2D::Flags flags; + MeshVisualizerGL3D::Flags flags; #ifdef MAGNUM_TARGET_WEBGL - flags = MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId; + flags = MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId; #else #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isVersionSupported(GL::Version::GL320)) @@ -1280,18 +2300,42 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { #endif { Debug{} << "Using primitive ID from vertex ID"; - flags = MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId; + flags = MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId; } - else flags = MeshVisualizerGL2D::Flag::PrimitiveId; + else flags = MeshVisualizerGL3D::Flag::PrimitiveId; #endif Trade::MeshData icosphereData = Primitives::icosphereSolid(0); - if(flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) + if(flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) icosphereData = MeshTools::duplicate(icosphereData); - MeshVisualizerGL2D{flags} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(icosphereData)); + GL::Mesh icosphere = MeshTools::compile(icosphereData); + + MeshVisualizerGL3D shader{flags|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1305,7 +2349,16 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { +template void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); @@ -1317,11 +2370,34 @@ void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(4, 8, Primitives::UVSphereFlag::Tangents)); - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::TangentDirection| + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::TangentDirection| MeshVisualizerGL3D::Flag::BitangentFromTangentDirection| - MeshVisualizerGL3D::Flag::NormalDirection} - .setViewportSize({80, 80}) /** @todo make this unnecessary */ - .draw(sphere); + MeshVisualizerGL3D::Flag::NormalDirection|flag}; + /** @todo make this unnecessary */ + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1340,10 +2416,21 @@ void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { } #endif -void MeshVisualizerGLTest::renderWireframe2D() { +template void MeshVisualizerGLTest::renderWireframe2D() { auto&& data = WireframeData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #ifndef MAGNUM_TARGET_GLES if(!(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) @@ -1382,14 +2469,42 @@ void MeshVisualizerGLTest::renderWireframe2D() { } } else circle = MeshTools::compile(circleData); - MeshVisualizerGL2D{data.flags|MeshVisualizerGL2D::Flag::Wireframe} - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf) - .setWireframeWidth(data.width) - .setSmoothness(data.smoothness) - .setViewportSize({80, 80}) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .draw(circle); + MeshVisualizerGL2D shader{data.flags|MeshVisualizerGL2D::Flag::Wireframe|flag}; + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1432,10 +2547,21 @@ void MeshVisualizerGLTest::renderWireframe2D() { } } -void MeshVisualizerGLTest::renderWireframe3D() { +template void MeshVisualizerGLTest::renderWireframe3D() { auto&& data = WireframeData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #ifndef MAGNUM_TARGET_GLES if(!(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) @@ -1472,18 +2598,55 @@ void MeshVisualizerGLTest::renderWireframe3D() { } } else sphere = MeshTools::compile(sphereData); - MeshVisualizerGL3D{data.flags|MeshVisualizerGL3D::Flag::Wireframe} - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf) - .setWireframeWidth(data.width) - .setSmoothness(data.smoothness) - .setViewportSize({80, 80}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + MeshVisualizerGL3D shader{data.flags|MeshVisualizerGL3D::Flag::Wireframe|flag}; + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1533,10 +2696,19 @@ void MeshVisualizerGLTest::renderWireframe3D() { } #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { +template void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if((data.flags2D & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1591,32 +2763,55 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { GL::Mesh circle = MeshTools::compile(circleData); - MeshVisualizerGL2D shader{data.flags2D}; + MeshVisualizerGL2D shader{data.flags2D|flag}; shader - /* Remove blue so it's clear the (wireframe) background and mapped ID - colors got mixed */ - .setColor(0xffff00_rgbf) /* Shouldn't assert (nor warn) when wireframe is not enabled */ .setViewportSize({80, 80}) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) .bindColorMapTexture(_colorMapTexture); - /* OTOH the wireframe color should stay at full channels, not mixed */ - if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) - shader.setWireframeColor(0xffffff_rgbf); - - /* For vertex ID we don't want any repeat/wraparound as that causes - disruptions in the gradient and test failures. There's 17 vertices - also. */ - if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) - shader.setColorMapTransformation(1.0f, -1.0f/17.0f); - /* For object/primitive ID there's no gradient so a wraparound is okay. - This should cover the first half of the colormap, in reverse order; for - primitive ID the whole colormap due to the repeat wrapping */ - else - shader.setColorMapTransformation(0.5f, -1.0f/16.0f); - - shader.draw(circle); + if(flag == MeshVisualizerGL2D::Flag{}) { + /* Remove blue so it's clear the (wireframe) background and mapped ID + colors got mixed */ + shader.setColor(0xffff00_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})); + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + /* For vertex ID we don't want any repeat/wraparound as that causes + disruptions in the gradient and test failures. There's 17 vertices + also. */ + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + shader.setColorMapTransformation(1.0f, -1.0f/17.0f); + /* For object/primitive ID there's no gradient so a wraparound is okay. + This should cover the first half of the colormap, in reverse order; + for primitive ID the whole colormap due to the repeat wrapping */ + else + shader.setColorMapTransformation(0.5f, -1.0f/16.0f); + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + /* See above for comments */ + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + materialUniformData->setColor(0xffff00_rgbf); + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + materialUniformData->setWireframeColor(0xffffff_rgbf); + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + materialUniformData->setColorMapTransformation(1.0f, -1.0f/17.0f); + else + materialUniformData->setColorMapTransformation(0.5f, -1.0f/16.0f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1633,10 +2828,19 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { (DebugTools::CompareImageToFile{_manager, 4.0f, 0.141f})); } -void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { +template void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if((data.flags3D & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1684,37 +2888,70 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { data.flags3D & MeshVisualizerGL3D::Flag::NoGeometryShader) icosphereData = MeshTools::duplicate(icosphereData); - GL::Mesh circle = MeshTools::compile(icosphereData); + GL::Mesh icosphere = MeshTools::compile(icosphereData); - MeshVisualizerGL3D shader{data.flags3D}; + MeshVisualizerGL3D shader{data.flags3D|flag}; shader - /* Remove blue so it's clear the wireframe background and mapped ID - colors got mixed */ - .setColor(0xffff00_rgbf) /* Shouldn't assert (nor warn) when wireframe is not enabled */ .setViewportSize({80, 80}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) .bindColorMapTexture(_colorMapTexture); - /* OTOH the wireframe color should stay at full channels, not mixed */ - if(data.flags2D & MeshVisualizerGL2D::Flag::Wireframe) - shader.setWireframeColor(0xffffff_rgbf); - - /* For vertex ID we don't want any repeat/wraparound as that causes - disruptions in the gradient and test failures. There's 42 vertices also. */ - if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) - shader.setColorMapTransformation(1.0f, -1.0f/42.0f); - /* For object/primitive ID there's no gradient so a wraparound is okay. - This should cover the first half of the colormap, in reverse order; for - primitive ID the whole colormap due to the repeat wrapping */ - else - shader.setColorMapTransformation(0.5f, -1.0f/40.0f); - - shader.draw(circle); + if(flag == MeshVisualizerGL3D::Flag{}) { + /* Remove blue so it's clear the wireframe background and mapped ID + colors got mixed */ + shader.setColor(0xffff00_rgbf) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)); + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags2D & MeshVisualizerGL2D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + /* For vertex ID we don't want any repeat/wraparound as that causes + disruptions in the gradient and test failures. There's 42 vertices + also. */ + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + shader.setColorMapTransformation(1.0f, -1.0f/42.0f); + /* For object/primitive ID there's no gradient so a wraparound is okay. + This should cover the first half of the colormap, in reverse order; + for primitive ID the whole colormap due to the repeat wrapping */ + else + shader.setColorMapTransformation(0.5f, -1.0f/40.0f); + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + /* See above for comments */ + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + materialUniformData->setColor(0xffff00_rgbf); + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + materialUniformData->setWireframeColor(0xffffff_rgbf); + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + materialUniformData->setColorMapTransformation(1.0f, -1.0f/42.0f); + else + materialUniformData->setColorMapTransformation(0.5f, -1.0f/40.0f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1784,10 +3021,19 @@ void MeshVisualizerGLTest::renderWireframe3DPerspective() { (DebugTools::CompareImageToFile{_manager, 0.667f, 0.002f})); } -void MeshVisualizerGLTest::renderTangentBitangentNormal() { +template void MeshVisualizerGLTest::renderTangentBitangentNormal() { auto&& data = TangentBitangentNormalData[testCaseInstanceId()]; setTestCaseDescription(data.name); + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); @@ -1885,25 +3131,58 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { .draw(mesh); } - MeshVisualizerGL3D shader{data.flags}; - shader - /** @todo make this unnecessary */ - .setViewportSize({80, 80}) - .setTransformationMatrix(transformation) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setNormalMatrix(transformation.normalMatrix()*data.multiply) - .setSmoothness(data.smoothness) - .setLineLength(data.lineLength) - .setLineWidth(data.lineWidth); - - if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) shader - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf); - if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) shader - .bindColorMapTexture(_colorMapTexture) - .setColorMapTransformation(1.0f/512.0f, 0.5f); + MeshVisualizerGL3D shader{data.flags|flag}; + /** @todo make this unnecessary */ + shader.setViewportSize({80, 80}); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + shader.bindColorMapTexture(_colorMapTexture); - shader.draw(mesh); + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.setTransformationMatrix(transformation) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setNormalMatrix(transformation.normalMatrix()*data.multiply) + .setSmoothness(data.smoothness) + .setLineLength(data.lineLength) + .setLineWidth(data.lineWidth); + if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + shader.setColorMapTransformation(1.0f/512.0f, 0.5f); + shader.draw(mesh); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(transformation) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + .setNormalMatrix(transformation.normalMatrix()*data.multiply) + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setSmoothness(data.smoothness) + .setLineLength(data.lineLength) + .setLineWidth(data.lineWidth); + if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) + (*materialUniformData) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + materialUniformData->setColorMapTransformation(1.0f/512.0f, 0.5f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1917,8 +3196,8 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { Float maxThreshold = 1.334f, meanThreshold = 0.018f; #ifdef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) { - maxThreshold = 39.0f; - meanThreshold = 1.207f; + maxThreshold = 58.0f; + meanThreshold = 1.547f; } #endif CORRADE_COMPARE_WITH( @@ -1929,6 +3208,418 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::renderMulti2D() { + auto&& data = RenderMultiData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(8)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid()); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3)); + /* For a GS-less wireframe we have to deindex the meshes */ + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + for(Trade::MeshData* i: {&circleData, &squareData, &triangleData}) + *i = MeshTools::duplicate(*i); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + circleData.vertexCount() : circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + squareData.vertexCount() : squareData.indexCount()); + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + square.setBaseVertex(circleData.vertexCount()); + else + square.setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + triangleData.vertexCount() : triangleData.indexCount()); + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + triangle.setBaseVertex(circleData.vertexCount() + squareData.vertexCount()); + else triangle.setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf) + .setColorMapTransformation(0.5f/circleData.vertexCount(), 1.0f/circleData.vertexCount()); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf) + .setWireframeWidth(2.5f) + .setColorMapTransformation(0.5f/triangleData.vertexCount(), 1.0f/triangleData.vertexCount()); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::UniformBuffers|data.flags, data.materialCount, data.drawCount}; + shader.setViewportSize(Vector2{RenderSize}); + if(data.flags & MeshVisualizerGL2D::Flag::VertexId) + shader.bindColorMapTexture(_colorMapTexture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(triangle); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) + shader.draw({circle, square, triangle}); + else { + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + }; + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Wireframe case: + + - Circle should be lower left, pink with red wireframe + - Square lower right, cyan with thick blue wireframe + - Triangle up center, cyan with thick blue wireframe + + Vertex ID case: + + - Circle and triangle should have both almost the full color map + range, one tinted pink, one cyan + - Square tinted cyan, with just two colors + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void MeshVisualizerGLTest::renderMulti3D() { + auto&& data = RenderMultiData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + if(data.flags >= MeshVisualizerGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + Trade::MeshData sphereData = MeshTools::interleave(Primitives::icosphereSolid(0), { + /* The icosphere doesn't have tangents and we don't use them, but + concatenate() will ignore the tangents of others if the first mesh + doesn't have them, so add a bogus data at least */ + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, VertexFormat::Vector4, nullptr} + }); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid(Primitives::PlaneFlag::Tangents)); + Trade::MeshData coneData = Primitives::coneSolid(1, 8, 1.0f, Primitives::ConeFlag::Tangents); + /* For a GS-less wireframe we have to deindex the meshes */ + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + for(Trade::MeshData* i: {&sphereData, &planeData, &coneData}) + *i = MeshTools::duplicate(*i); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + sphereData.vertexCount() : sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + planeData.vertexCount() : planeData.indexCount()); + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + plane.setBaseVertex(sphereData.vertexCount()); + else + plane.setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + coneData.vertexCount() : coneData.indexCount()); + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + cone.setBaseVertex(sphereData.vertexCount() + planeData.vertexCount()); + else cone.setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf) + .setLineLength(0.0f) /* no TBN */ + .setColorMapTransformation(0.5f/sphereData.vertexCount(), 1.0f/sphereData.vertexCount()); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf) + .setLineLength(0.25f) + .setWireframeWidth(2.5f) + .setColorMapTransformation(0.5f/coneData.vertexCount(), 1.0f/coneData.vertexCount()); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f}) + ); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. Also no need to supply a normal matrix. */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::UniformBuffers|data.flags, data.materialCount, data.drawCount}; + shader.setViewportSize(Vector2{RenderSize}) + .bindProjectionBuffer(projectionUniform); + if(data.flags & MeshVisualizerGL3D::Flag::VertexId) + shader.bindColorMapTexture(_colorMapTexture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags >= MeshVisualizerGL3D::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + }; + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + Wireframe case: + + - Sphere should be lower left, pink with red wireframe (and no TBN) + - Plane lower right, cyan with thick blue wireframe and TBN + - Cone up center, cyan with thick blue wireframe and TBN + + Vertex ID case: + + - Sphere and cone should have both almost the full color map + range, one tinted pink, one cyan + - Plane tinted cyan, with just two colors + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::MeshVisualizerGLTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp index acee35225..10e7ffc53 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp @@ -48,21 +48,31 @@ struct MeshVisualizerGL_Test: TestSuite::Tester { void debugFlag3D(); void debugFlags2D(); void debugFlags3D(); + #ifndef MAGNUM_TARGET_GLES2 + void debugFlagsSupersets2D(); + void debugFlagsSupersets3D(); + #endif }; MeshVisualizerGL_Test::MeshVisualizerGL_Test() { - addTests({&MeshVisualizerGL_Test::constructNoCreate2D, - &MeshVisualizerGL_Test::constructNoCreate3D, - - &MeshVisualizerGL_Test::constructCopy2D, - &MeshVisualizerGL_Test::constructCopy3D, - - &MeshVisualizerGL_Test::vertexIndexSameAsObjectId, - - &MeshVisualizerGL_Test::debugFlag2D, - &MeshVisualizerGL_Test::debugFlag3D, - &MeshVisualizerGL_Test::debugFlags2D, - &MeshVisualizerGL_Test::debugFlags3D}); + addTests({ + &MeshVisualizerGL_Test::constructNoCreate2D, + &MeshVisualizerGL_Test::constructNoCreate3D, + + &MeshVisualizerGL_Test::constructCopy2D, + &MeshVisualizerGL_Test::constructCopy3D, + + &MeshVisualizerGL_Test::vertexIndexSameAsObjectId, + + &MeshVisualizerGL_Test::debugFlag2D, + &MeshVisualizerGL_Test::debugFlag3D, + &MeshVisualizerGL_Test::debugFlags2D, + &MeshVisualizerGL_Test::debugFlags3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGL_Test::debugFlagsSupersets2D, + &MeshVisualizerGL_Test::debugFlagsSupersets3D, + #endif + }); } void MeshVisualizerGL_Test::constructNoCreate2D() { @@ -140,6 +150,22 @@ void MeshVisualizerGL_Test::debugFlags3D() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGL_Test::debugFlagsSupersets2D() { + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::Flag::MultiDraw\n"); +} + +void MeshVisualizerGL_Test::debugFlagsSupersets3D() { + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::Flag::MultiDraw\n"); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::MeshVisualizerGL_Test) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp new file mode 100644 index 000000000..178c261e5 --- /dev/null +++ b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp @@ -0,0 +1,319 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Shaders/MeshVisualizer.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct MeshVisualizerTest: TestSuite::Tester { + explicit MeshVisualizerTest(); + + template void uniformSizeAlignment(); + + void drawUniform2DConstructDefault(); + void drawUniform2DConstructNoInit(); + void drawUniform2DSetters(); + void drawUniform2DMaterialIdPacking(); + + void drawUniform3DConstructDefault(); + void drawUniform3DConstructNoInit(); + void drawUniform3DSetters(); + void drawUniform3DMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +MeshVisualizerTest::MeshVisualizerTest() { + addTests({&MeshVisualizerTest::uniformSizeAlignment, + &MeshVisualizerTest::uniformSizeAlignment, + &MeshVisualizerTest::uniformSizeAlignment, + + &MeshVisualizerTest::drawUniform2DConstructDefault, + &MeshVisualizerTest::drawUniform2DConstructNoInit, + &MeshVisualizerTest::drawUniform2DSetters, + &MeshVisualizerTest::drawUniform2DMaterialIdPacking, + + &MeshVisualizerTest::drawUniform3DConstructDefault, + &MeshVisualizerTest::drawUniform3DConstructNoInit, + &MeshVisualizerTest::drawUniform3DSetters, + &MeshVisualizerTest::drawUniform3DMaterialIdPacking, + + &MeshVisualizerTest::materialUniformConstructDefault, + &MeshVisualizerTest::materialUniformConstructNoInit, + &MeshVisualizerTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerDrawUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerDrawUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerMaterialUniform"; } +}; + +template void MeshVisualizerTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment"); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment"); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768"); + + CORRADE_COMPARE(alignof(T), 4); +} + +void MeshVisualizerTest::drawUniform2DConstructDefault() { + MeshVisualizerDrawUniform2D a; + MeshVisualizerDrawUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr MeshVisualizerDrawUniform2D ca; + constexpr MeshVisualizerDrawUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform2DConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerDrawUniform2D a; + a.materialId = 73; + + new(&a) MeshVisualizerDrawUniform2D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.materialId, 73); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform2DSetters() { + MeshVisualizerDrawUniform2D a; + a.setMaterialId(73); + CORRADE_COMPARE(a.materialId, 73); +} + +void MeshVisualizerTest::drawUniform2DMaterialIdPacking() { + MeshVisualizerDrawUniform2D a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void MeshVisualizerTest::drawUniform3DConstructDefault() { + MeshVisualizerDrawUniform3D a; + MeshVisualizerDrawUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr MeshVisualizerDrawUniform3D ca; + constexpr MeshVisualizerDrawUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform3DConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerDrawUniform3D a; + a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + a.materialId = 5; + + new(&a) MeshVisualizerDrawUniform3D{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + CORRADE_COMPARE(a.materialId, 5); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform3DSetters() { + MeshVisualizerDrawUniform3D a; + a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) + .setMaterialId(5); + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f}, + Vector4{0.0f, -1.0f, 0.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 5); +} + +void MeshVisualizerTest::drawUniform3DMaterialIdPacking() { + MeshVisualizerDrawUniform3D a; + a.setMaterialId(13765); + /* The normalMatrix field is 3x4 floats, materialId should be right after + in the low 16 bits on both LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); +} + +void MeshVisualizerTest::materialUniformConstructDefault() { + MeshVisualizerMaterialUniform a; + MeshVisualizerMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(b.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(a.wireframeWidth, 1.0f); + CORRADE_COMPARE(b.wireframeWidth, 1.0f); + CORRADE_COMPARE(a.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(b.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(a.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(b.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(a.lineWidth, 1.0f); + CORRADE_COMPARE(b.lineWidth, 1.0f); + CORRADE_COMPARE(a.lineLength, 1.0f); + CORRADE_COMPARE(b.lineLength, 1.0f); + CORRADE_COMPARE(a.smoothness, 2.0f); + CORRADE_COMPARE(b.smoothness, 2.0f); + + constexpr MeshVisualizerMaterialUniform ca; + constexpr MeshVisualizerMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(cb.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(ca.wireframeWidth, 1.0f); + CORRADE_COMPARE(cb.wireframeWidth, 1.0f); + CORRADE_COMPARE(ca.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(cb.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(ca.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(cb.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(ca.lineWidth, 1.0f); + CORRADE_COMPARE(cb.lineWidth, 1.0f); + CORRADE_COMPARE(ca.lineLength, 1.0f); + CORRADE_COMPARE(cb.lineLength, 1.0f); + CORRADE_COMPARE(ca.smoothness, 2.0f); + CORRADE_COMPARE(cb.smoothness, 2.0f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.lineWidth = 0.765f; + + new(&a) MeshVisualizerMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.lineWidth, 0.765f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::materialUniformSetters() { + MeshVisualizerMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setWireframeColor(0x9876fadc_rgbaf) + .setWireframeWidth(3.5f) + .setColorMapTransformation(35.5f, 0.5f) + .setLineWidth(3.0f) + .setLineLength(4.0f) + .setSmoothness(5.0f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.wireframeColor, 0x9876fadc_rgbaf); + CORRADE_COMPARE(a.wireframeWidth, 3.5f); + CORRADE_COMPARE(a.colorMapOffset, 35.5f); + CORRADE_COMPARE(a.colorMapScale, 0.5f); + CORRADE_COMPARE(a.lineWidth, 3.0f); + CORRADE_COMPARE(a.lineLength, 4.0f); + CORRADE_COMPARE(a.smoothness, 5.0f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::MeshVisualizerTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga new file mode 100644 index 000000000..fc8195fd8 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga new file mode 100644 index 000000000..e225aca56 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga new file mode 100644 index 000000000..628b7849d Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga new file mode 100644 index 000000000..00291cd9f Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga new file mode 100644 index 000000000..c5c208c05 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga new file mode 100644 index 000000000..cbc3a6d91 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe3D.tga new file mode 100644 index 000000000..e5d6adce0 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe3D.tga differ diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 1d0d031cb..c70e5d332 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -48,6 +49,7 @@ #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/Swizzle.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Primitives/Plane.h" @@ -57,6 +59,16 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/MeshView.h" +#include "Magnum/GL/TextureArray.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Phong.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -65,52 +77,88 @@ struct PhongGLTest: GL::OpenGLTester { explicit PhongGLTest(); void construct(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers(); + #endif void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers(); + #endif - void constructTextureTransformationNotTextured(); + void constructInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffersInvalid(); + #endif - void bindTexturesNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + void setUniformUniformBuffersEnabled(); + void bindBufferUniformBuffersNotEnabled(); + #endif + void bindTexturesInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + void bindTextureArraysInvalid(); + #endif void setAlphaMaskNotEnabled(); + void setSpecularDisabled(); void setTextureMatrixNotEnabled(); + void setNormalTextureScaleNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + void setTextureLayerNotArray(); + void bindTextureTransformBufferNotEnabled(); + #endif #ifndef MAGNUM_TARGET_GLES2 void setObjectIdNotEnabled(); #endif void setWrongLightCount(); void setWrongLightId(); + #ifndef MAGNUM_TARGET_GLES2 + void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults(); - void renderColored(); - void renderSinglePixelTextured(); + template void renderDefaults(); + template void renderColored(); + template void renderSinglePixelTextured(); - void renderTextured(); - void renderTexturedNormal(); + template void renderTextured(); + template void renderTexturedNormal(); - template void renderVertexColor(); + template void renderVertexColor(); - void renderShininess(); + template void renderShininess(); void renderAlphaSetup(); void renderAlphaTeardown(); - void renderAlpha(); + template void renderAlpha(); #ifndef MAGNUM_TARGET_GLES2 void renderObjectIdSetup(); void renderObjectIdTeardown(); - void renderObjectId(); + template void renderObjectId(); #endif - void renderLights(); + template void renderLights(); + + /* This tests something that's irrelevant to UBOs */ void renderLightsSetOneByOne(); + /* This tests just the algorithm, not affected by UBOs */ void renderLowLightAngle(); - void renderZeroLights(); + #ifndef MAGNUM_TARGET_GLES2 + void renderLightCulling(); + #endif - void renderInstanced(); + template void renderZeroLights(); + + template void renderInstanced(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -124,18 +172,35 @@ struct PhongGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - . Mesa llvmpipe - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 (except instancing) - - WebGL 1 / 2 (on Mesa Intel) (except instancing) - - NVidia Windows (except instancing) - - Intel Windows (except instancing) - - AMD on macOS (except instancing) - - iPhone 6 w/ iOS 12.4 (except instancing) + Rendering tests done: + + [B] base + [A] alpha mask + [D] object ID + [L] point lights + [I] instancing + [O] UBOs + draw offset + [M] multidraw + [L] texture arrays + + Mesa Intel BADLIOML + ES2 xxx + ES3 BADL Ox + Mesa AMD BAD + Mesa llvmpipe BAD + SwiftShader ES2 BADL xxx + ES3 BADL + ANGLE ES2 xxx + ES3 BADL OM + ARM Mali (Huawei P10) ES2 BAD xxx + ES3 BADL Ox + WebGL (on Mesa Intel) 1.0 BAD xxx + 2.0 BADL OM + NVidia BAD + Intel Windows BAD + AMD macOS BAD + Intel macOS BADL Ox + iPhone 6 w/ iOS 12.4 ES3 BAD x */ constexpr struct { @@ -156,21 +221,136 @@ constexpr struct { {"diffuse + specular texture", PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1}, {"ambient + diffuse + specular texture", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1}, {"ambient + diffuse + specular + normal texture", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"ambient + diffuse + specular + normal texture arrays", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays, 1}, + {"ambient + diffuse + specular + normal texture arrays + texture transformation", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, 1}, + #endif {"alpha mask", PhongGL::Flag::AlphaMask, 1}, {"alpha mask + diffuse texture", PhongGL::Flag::AlphaMask|PhongGL::Flag::DiffuseTexture, 1}, {"vertex colors", PhongGL::Flag::VertexColor, 1}, {"vertex colors + diffuse texture", PhongGL::Flag::VertexColor|PhongGL::Flag::DiffuseTexture, 1}, #ifndef MAGNUM_TARGET_GLES2 {"object ID", PhongGL::Flag::ObjectId, 1}, + /* This is fine, InstancedObjectId isn't (check in ConstructInvalidData) */ + {"object ID + separate bitangent", PhongGL::Flag::ObjectId|PhongGL::Flag::Bitangent, 1}, {"instanced object ID", PhongGL::Flag::InstancedObjectId, 1}, {"object ID + alpha mask + specular texture", PhongGL::Flag::ObjectId|PhongGL::Flag::AlphaMask|PhongGL::Flag::SpecularTexture, 1}, #endif + {"no specular", PhongGL::Flag::NoSpecular, 1}, {"five lights", {}, 5}, {"zero lights", {}, 0}, {"instanced transformation", PhongGL::Flag::InstancedTransformation, 3}, {"instanced specular texture offset", PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset, 3}, - {"instanced normal texture offset", PhongGL::Flag::NormalTexture|PhongGL::Flag::InstancedTextureOffset, 3} + {"instanced normal texture offset", PhongGL::Flag::NormalTexture|PhongGL::Flag::InstancedTextureOffset, 3}, + #ifndef MAGNUM_TARGET_GLES2 + /* InstancedObjectId|Bitangent is disallowed (checked in + ConstructInvalidData), but this should work */ + {"object ID + normal texture with bitangent from tangent", PhongGL::Flag::InstancedObjectId|PhongGL::Flag::NormalTexture, 1} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1, 1}, + {"", PhongGL::Flag::UniformBuffers, 1, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, + per-material 4, per-light 4 plus 4 for projection */ + {"multiple lights, materials, draws", PhongGL::Flag::UniformBuffers, 8, 8, 24}, + {"multiple lights, materials, draws + light culling", PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24}, + {"zero lights", PhongGL::Flag::UniformBuffers, 0, 16, 24}, + {"ambient + diffuse + specular texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1, 1, 1}, + {"ambient + diffuse + specular texture + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1, 1, 1}, + {"ambient + diffuse + specular texture array + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, 1, 1, 1}, + {"normal texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture, 1, 1, 1}, + {"normal texture + separate bitangents", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, 1, 1, 1}, + {"alpha mask", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AlphaMask, 1, 1, 1}, + {"object ID", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId, 1, 1, 1}, + {"no specular", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NoSpecular, 1, 1, 1}, + {"multidraw with all the things", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId|PhongGL::Flag::LightCulling, 8, 16, 24} +}; +#endif + +constexpr struct { + const char* name; + PhongGL::Flags flags; + const char* message; +} ConstructInvalidData[] { + {"texture transformation but not textured", + PhongGL::Flag::TextureTransformation, + "texture transformation enabled but the shader is not textured"}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays but not textured", PhongGL::Flag::TextureArrays, + "texture arrays enabled but the shader is not textured"}, + {"conflicting bitangent and instanced object id attribute", + PhongGL::Flag::Bitangent|PhongGL::Flag::InstancedObjectId, + "Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead"}, + #endif + {"specular texture but no specular", + PhongGL::Flag::SpecularTexture|PhongGL::Flag::NoSpecular, + "specular texture requires the shader to not have specular disabled"} +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", PhongGL::Flag::UniformBuffers, 1, 1, 0, + "draw count can't be zero"}, + {"zero materials", PhongGL::Flag::UniformBuffers, 1, 0, 1, + "material count can't be zero"}, + {"texture arrays but no transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays, 1, 1, 1, + "texture arrays require texture transformation enabled as well if uniform buffers are used"}, + {"light culling but no UBOs", PhongGL::Flag::LightCulling, 1, 1, 1, + "light culling requires uniform buffers to be enabled"} +}; +#endif + +constexpr struct { + const char* name; + PhongGL::Flags flags; + const char* message; +} BindTexturesInvalidData[]{ + {"not textured", {}, + "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled\n" + "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with diffuse texture enabled\n" + "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with specular texture enabled\n" + "Shaders::PhongGL::bindNormalTexture(): the shader was not created with normal texture enabled\n" + "Shaders::PhongGL::bindTextures(): the shader was not created with any textures enabled\n"}, + #ifndef MAGNUM_TARGET_GLES2 + {"array", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays, + "Shaders::PhongGL::bindAmbientTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n" + "Shaders::PhongGL::bindDiffuseTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n" + "Shaders::PhongGL::bindSpecularTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n" + "Shaders::PhongGL::bindNormalTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n" + "Shaders::PhongGL::bindTextures(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n"} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + PhongGL::Flags flags; + const char* message; +} BindTextureArraysInvalidData[]{ + {"not textured", {}, + "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled\n" + "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with diffuse texture enabled\n" + "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with specular texture enabled\n" + "Shaders::PhongGL::bindNormalTexture(): the shader was not created with normal texture enabled\n"}, + {"not array", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture, + "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n" + "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n" + "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n" + "Shaders::PhongGL::bindNormalTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n"} }; +#endif using namespace Math::Literals; @@ -187,10 +367,16 @@ const struct { constexpr struct { const char* name; + PhongGL::Flags flags; + Int layer; bool multiBind; } RenderSinglePixelTexturedData[]{ - {"", false}, - {"multi bind", true} + {"", {}, 0, false}, + {"multi bind", {}, 0, true}, + #ifndef MAGNUM_TARGET_GLES2 + {"array, first layer", PhongGL::Flag::TextureArrays, 0, false}, + {"array, arbitrary layer", PhongGL::Flag::TextureArrays, 6, false}, + #endif }; const struct { @@ -198,15 +384,31 @@ const struct { const char* expected; PhongGL::Flags flags; Matrix3 textureTransformation; + Int layer; } RenderTexturedData[]{ - {"all", "textured.tga", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, {}}, - {"ambient", "textured-ambient.tga", PhongGL::Flag::AmbientTexture, {}}, - {"diffuse", "textured-diffuse.tga", PhongGL::Flag::DiffuseTexture, {}}, + {"all", "textured.tga", + PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, + {}, 0}, + {"ambient", "textured-ambient.tga", PhongGL::Flag::AmbientTexture, + {}, 0}, + {"diffuse", "textured-diffuse.tga", PhongGL::Flag::DiffuseTexture, + {}, 0}, {"diffuse transformed", "textured-diffuse-transformed.tga", PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureTransformation, - Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}) - }, - {"specular", "textured-specular.tga", PhongGL::Flag::SpecularTexture, {}} + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), 0}, + {"specular", "textured-specular.tga", PhongGL::Flag::SpecularTexture, + {}, 0}, + #ifndef MAGNUM_TARGET_GLES2 + {"all, array, first layer", "textured.tga", + PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays, + {}, 0}, + {"all, array, arbitrary layer", "textured.tga", + PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays, + {}, 6}, + {"diffuse, array, texture transformation, arbitrary layer", "textured-diffuse-transformed.tga", + PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), 6}, + #endif }; /* MSVC 2015 doesn't like constexpr here due to the angles */ @@ -221,60 +423,79 @@ const struct { PhongGL::Tangent4::Components tangentComponents; bool flipNormalY; PhongGL::Flags flags; + Int layer; } RenderTexturedNormalData[]{ {"", "textured-normal.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, {"multi bind", "textured-normal.tga", true, {}, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays, first layer", "textured-normal.tga", false, {}, 1.0f, + {1.0f, 0.0f, 0.0f, 1.0f}, {}, + PhongGL::Tangent4::Components::Four, false, + PhongGL::Flag::TextureArrays, 0}, + {"texture arrays, arbitrary layer", "textured-normal.tga", false, {}, 1.0f, + {1.0f, 0.0f, 0.0f, 1.0f}, {}, + PhongGL::Tangent4::Components::Four, false, + PhongGL::Flag::TextureArrays, 6}, + #endif {"rotated 90°", "textured-normal.tga", false, 90.0_degf, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, {"rotated -90°", "textured-normal.tga", false, -90.0_degf, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, {"0.5 scale", "textured-normal0.5.tga", false, {}, 0.5f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, {"0.0 scale", "textured-normal0.0.tga", false, {}, 0.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, /* The fourth component, if missing, gets automatically filled up to 1, so this should work */ {"implicit bitangent direction", "textured-normal.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, 0.0f}, {}, - PhongGL::Tangent4::Components::Three, false, {}}, + PhongGL::Tangent4::Components::Three, false, {}, 0}, {"separate bitangents", "textured-normal.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, PhongGL::Tangent4::Components::Three, false, - PhongGL::Flag::Bitangent}, + PhongGL::Flag::Bitangent, 0}, {"right-handed, flipped Y", "textured-normal-left.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, 1.0f}, {}, - PhongGL::Tangent4::Components::Four, true, {}}, + PhongGL::Tangent4::Components::Four, true, {}, 0}, {"left-handed", "textured-normal-left.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, -1.0f}, {}, - PhongGL::Tangent4::Components::Four, false, {}}, + PhongGL::Tangent4::Components::Four, false, {}, 0}, {"left-handed, separate bitangents", "textured-normal-left.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, PhongGL::Tangent4::Components::Three, false, - PhongGL::Flag::Bitangent}, + PhongGL::Flag::Bitangent, 0}, {"left-handed, flipped Y", "textured-normal.tga", false, {}, 1.0f, {1.0f, 0.0f, 0.0f, -1.0f}, {}, - PhongGL::Tangent4::Components::Four, true, {}} + PhongGL::Tangent4::Components::Four, true, {}, 0}, }; const struct { const char* name; const char* expected; + PhongGL::Flags flags; Float shininess; Color4 specular; } RenderShininessData[] { - {"80", "shininess80.tga", 80.0f, 0xffffff_rgbf}, - {"10", "shininess10.tga", 10.0f, 0xffffff_rgbf}, - {"0", "shininess0.tga", 0.0f, 0xffffff_rgbf}, - {"0.001", "shininess0.tga", 0.001f, 0xffffff_rgbf}, - {"black specular", "shininess-black-specular.tga", 80.0f, 0x000000_rgbf} + {"80", "shininess80.tga", + {}, 80.0f, 0xffffff_rgbf}, + {"10", "shininess10.tga", + {}, 10.0f, 0xffffff_rgbf}, + {"0", "shininess0.tga", + {}, 0.0f, 0xffffff_rgbf}, + {"0.001", "shininess0.tga", + {}, 0.001f, 0xffffff_rgbf}, + {"black specular", "shininess-no-specular.tga", + {}, 80.0f, 0x000000_rgbf}, + {"no specular", "shininess-no-specular.tga", + PhongGL::Flag::NoSpecular, 80.0f, 0xffffff_rgbf} }; const struct { @@ -322,25 +543,9 @@ const struct { PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AlphaMask, 1.0f, "alpha-texture.tga", "diffuse-texture.tga", 0xffffffff_rgbaf, 0x9999ff00_rgbaf} + /* texture arrays are orthogonal to this, no need to be tested here */ }; -#ifndef MAGNUM_TARGET_GLES2 -constexpr struct { - const char* name; - PhongGL::Flags flags; - UnsignedInt uniformId; - UnsignedInt instanceCount; - UnsignedInt expected; -} RenderObjectIdData[] { - {"", /* Verify that it can hold 16 bits at least */ - PhongGL::Flag::ObjectId, 48526, 0, 48526}, - {"instanced, first instance", - PhongGL::Flag::InstancedObjectId, 13524, 1, 24526}, - {"instanced, second instance", - PhongGL::Flag::InstancedObjectId, 13524, 2, 62347} -}; -#endif - const struct { const char* name; const char* file; @@ -450,7 +655,7 @@ const struct { {"point, range=0.0", "light-none.tga", {0.75f, -0.75f, -0.75f, 1.0f}, Color3{1.0f}, Color3{1.0f}, 1.0f, 0.0f, {}}, - /* Distance is 0, which means the direction is always prependicular and + /* Distance is 0, which means the direction is always perpendicular and thus contributes nothing */ {"point, distance=0", "light-none.tga", {0.75f, -0.75f, -0.75f, 1.0f}, Color3{1.0f}, Color3{1.0f}, @@ -463,99 +668,277 @@ constexpr struct { PhongGL::Flags flags; Float maxThreshold, meanThreshold; } RenderInstancedData[] { - {"diffuse", "instanced.tga", {}, - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* AMD has one off pixel; SwiftShader a bit more */ - 96.34f, 0.113f, - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - 96.34f, 0.113f, - #endif - }, - {"diffuse + normal", "instanced-normal.tga", PhongGL::Flag::NormalTexture, - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - /* AMD has one off pixel, llvmpipe more */ - 96.0f, 0.333f, - #else - /* WebGL 1 doesn't have 8bit renderbuffer storage */ - 96.0f, 0.333f, - #endif - } + {"diffuse color", "instanced.tga", {}, + /* Minor differences on SwiftShader */ + 81.0f, 0.06f}, + {"diffuse texture", "instanced-textured.tga", + PhongGL::Flag::DiffuseTexture|PhongGL::Flag::InstancedTextureOffset, + /* Minor differences on SwiftShader */ + 112.0f, 0.09f}, + /** @todo test normal when there's usable texture */ + #ifndef MAGNUM_TARGET_GLES2 + {"diffuse texture array", "instanced-textured.tga", + PhongGL::Flag::DiffuseTexture|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::TextureArrays, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around); minor differences on + SwiftShader */ + 112.0f, 0.099f} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset, colored", "multidraw.tga", + {}, + 2, 1, 1, 16, + /* Minor differences on ARM Mali */ + 3.34f, 0.01f}, + {"bind with offset, textured", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture, + 2, 1, 1, 16, + /* Minor differences on ARM Mali */ + 4.67f, 0.02f}, + #ifndef MAGNUM_TARGET_GLES2 + {"bind with offset, texture array", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays, + 2, 1, 1, 16, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 50.34f, 0.131f}, + #endif + {"draw offset, colored", "multidraw.tga", + {}, + 4, 2, 3, 1, + /* Minor differences on ARM Mali */ + 3.34f, 0.01f}, + {"draw offset, textured", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture, + 4, 2, 3, 1, + /* Minor differences on ARM Mali */ + 4.67f, 0.02f}, + #ifndef MAGNUM_TARGET_GLES2 + {"draw offset, texture array", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays, + 4, 2, 3, 1, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 50.34f, 0.131f}, + #endif + {"multidraw, colored", "multidraw.tga", + PhongGL::Flag::MultiDraw, + 4, 2, 3, 1, + /* Minor differences on ARM Mali */ + 3.34f, 0.01f}, + {"multidraw, textured", "multidraw-textured.tga", + PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture, + 4, 2, 3, 1, + /* Minor differences on ARM Mali */ + 4.67f, 0.02f}, + #ifndef MAGNUM_TARGET_GLES2 + {"multidraw, texture array", "multidraw-textured.tga", + PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays, + 4, 2, 3, 1, + /* Some difference at the UV edge (texture is wrapping in the 2D case + while the 2D array has a black area around) */ + 50.34f, 0.131f}, + #endif + /** @todo test normal and per-draw scaling when there's usable texture */ }; +#endif PhongGLTest::PhongGLTest() { - addInstancedTests({&PhongGLTest::construct}, Containers::arraySize(ConstructData)); + addInstancedTests({&PhongGLTest::construct}, + Containers::arraySize(ConstructData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::constructUniformBuffers}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + + addTests({ + &PhongGLTest::constructMove, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::constructMoveUniformBuffers, + #endif + }); + + addInstancedTests({&PhongGLTest::constructInvalid}, + Containers::arraySize(ConstructInvalidData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &PhongGLTest::constructUniformBuffersInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif - addTests({&PhongGLTest::constructMove, + #ifndef MAGNUM_TARGET_GLES2 + addTests({&PhongGLTest::setUniformUniformBuffersEnabled, + &PhongGLTest::bindBufferUniformBuffersNotEnabled}); + #endif - &PhongGLTest::constructTextureTransformationNotTextured, + addInstancedTests({&PhongGLTest::bindTexturesInvalid}, + Containers::arraySize(BindTexturesInvalidData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::bindTextureArraysInvalid}, + Containers::arraySize(BindTextureArraysInvalidData)); + #endif - &PhongGLTest::bindTexturesNotEnabled, - &PhongGLTest::setAlphaMaskNotEnabled, - &PhongGLTest::setTextureMatrixNotEnabled, - #ifndef MAGNUM_TARGET_GLES2 - &PhongGLTest::setObjectIdNotEnabled, - #endif - &PhongGLTest::setWrongLightCount, - &PhongGLTest::setWrongLightId}); + addTests({ + &PhongGLTest::setAlphaMaskNotEnabled, + &PhongGLTest::setSpecularDisabled, + &PhongGLTest::setTextureMatrixNotEnabled, + &PhongGLTest::setNormalTextureScaleNotEnabled, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setTextureLayerNotArray, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::bindTextureTransformBufferNotEnabled, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setObjectIdNotEnabled, + #endif + &PhongGLTest::setWrongLightCount, + &PhongGLTest::setWrongLightId, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setWrongDrawOffset + #endif + }); - addTests({&PhongGLTest::renderDefaults}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &PhongGLTest::renderDefaults, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderDefaults + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderColored}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderColored, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderColored + #endif + }, Containers::arraySize(RenderColoredData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderSinglePixelTextured}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderSinglePixelTextured, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderSinglePixelTextured + #endif + }, Containers::arraySize(RenderSinglePixelTexturedData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderTextured}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderTextured, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderTextured + #endif + }, Containers::arraySize(RenderTexturedData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderTexturedNormal}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderTexturedNormal, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderTexturedNormal + #endif + }, Containers::arraySize(RenderTexturedNormalData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderVertexColor, - &PhongGLTest::renderVertexColor}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &PhongGLTest::renderVertexColor, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderVertexColor, + #endif + &PhongGLTest::renderVertexColor, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderVertexColor, + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderShininess}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderShininess, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderShininess, + #endif + }, Containers::arraySize(RenderShininessData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderAlpha}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderAlpha, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderAlpha + #endif + }, Containers::arraySize(RenderAlphaData), &PhongGLTest::renderAlphaSetup, &PhongGLTest::renderAlphaTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&PhongGLTest::renderObjectId}, - Containers::arraySize(RenderObjectIdData), + /* MSVC needs explicit type due to default template args */ + addTests({ + &PhongGLTest::renderObjectId, + &PhongGLTest::renderObjectId}, &PhongGLTest::renderObjectIdSetup, &PhongGLTest::renderObjectIdTeardown); #endif - addInstancedTests({&PhongGLTest::renderLights}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderLights, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderLights, + #endif + }, Containers::arraySize(RenderLightsData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderLightsSetOneByOne, - &PhongGLTest::renderLowLightAngle}, + addTests({ + &PhongGLTest::renderLightsSetOneByOne, + &PhongGLTest::renderLowLightAngle, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderLightCulling + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderZeroLights}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &PhongGLTest::renderZeroLights, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderZeroLights + #endif + }, #ifndef MAGNUM_TARGET_GLES2 &PhongGLTest::renderObjectIdSetup, &PhongGLTest::renderObjectIdTeardown @@ -565,10 +948,29 @@ PhongGLTest::PhongGLTest() { #endif ); - addInstancedTests({&PhongGLTest::renderInstanced}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderInstanced, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderInstanced, + #endif + }, Containers::arraySize(RenderInstancedData), + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderObjectIdSetup, + &PhongGLTest::renderObjectIdTeardown + #else &PhongGLTest::renderSetup, - &PhongGLTest::renderTeardown); + &PhongGLTest::renderTeardown + #endif + ); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::renderMulti}, + Containers::arraySize(RenderMultiData), + &PhongGLTest::renderObjectIdSetup, + &PhongGLTest::renderObjectIdTeardown); + #endif /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -601,6 +1003,8 @@ void PhongGLTest::construct() { #ifndef MAGNUM_TARGET_GLES if((data.flags & PhongGL::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); #endif PhongGL shader{data.flags, data.lightCount}; @@ -617,6 +1021,50 @@ void PhongGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructUniformBuffers() { + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & PhongGL::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(data.flags >= PhongGL::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + PhongGL shader{data.flags, data.lightCount, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.lightCount(), data.lightCount); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void PhongGLTest::constructMove() { PhongGL a{PhongGL::Flag::AlphaMask, 3}; const GLuint id = a.id(); @@ -638,45 +1086,229 @@ void PhongGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } -void PhongGLTest::constructTextureTransformationNotTextured() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructMoveUniformBuffers() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - std::ostringstream out; - Error redirectError{&out}; - PhongGL{PhongGL::Flag::TextureTransformation}; - CORRADE_COMPARE(out.str(), - "Shaders::PhongGL: texture transformation enabled but the shader is not textured\n"); + PhongGL a{PhongGL::Flag::UniformBuffers, 3, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + PhongGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), PhongGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.lightCount(), 3); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + PhongGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), PhongGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.lightCount(), 3); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); } +#endif + +void PhongGLTest::constructInvalid() { + auto&& data = ConstructInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); -void PhongGLTest::bindTexturesNotEnabled() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif std::ostringstream out; Error redirectError{&out}; - - GL::Texture2D texture; - PhongGL shader; - shader.bindAmbientTexture(texture) - .bindDiffuseTexture(texture) - .bindSpecularTexture(texture) - .bindNormalTexture(texture) - .setNormalTextureScale(0.5f) - .bindTextures(&texture, &texture, &texture, &texture); - - CORRADE_COMPARE(out.str(), - "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled\n" - "Shaders::PhongGL::bindDiffuseTexture(): the shader was not created with diffuse texture enabled\n" - "Shaders::PhongGL::bindSpecularTexture(): the shader was not created with specular texture enabled\n" - "Shaders::PhongGL::bindNormalTexture(): the shader was not created with normal texture enabled\n" - "Shaders::PhongGL::setNormalTextureScale(): the shader was not created with normal texture enabled\n" - "Shaders::PhongGL::bindTextures(): the shader was not created with any textures enabled\n"); + PhongGL{data.flags}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::PhongGL: {}\n", data.message)); } -void PhongGLTest::setAlphaMaskNotEnabled() { +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + PhongGL{data.flags, data.lightCount, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::PhongGL: {}\n", data.message)); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setUniformUniformBuffersEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + PhongGL shader{PhongGL::Flag::UniformBuffers}; + shader.setAmbientColor({}) + .setDiffuseColor({}) + .setNormalTextureScale({}) + .setSpecularColor({}) + .setShininess({}) + .setAlphaMask({}) + .setObjectId({}) + .setTransformationMatrix({}) + .setNormalMatrix({}) + .setProjectionMatrix({}) + .setTextureMatrix({}) + .setTextureLayer({}) + .setLightPositions(std::initializer_list{}) + .setLightPosition(0, Vector4{}) + .setLightColors(std::initializer_list{}) + .setLightColor(0, Color3{}) + .setLightSpecularColors({}) + .setLightSpecularColor(0, {}) + .setLightRanges({}) + .setLightRange(0, {}); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setAmbientColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setDiffuseColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setNormalTextureScale(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setSpecularColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setShininess(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setAlphaMask(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setObjectId(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setTransformationMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setNormalMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setTextureLayer(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightPositions(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightPosition(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightColors(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightRanges(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled\n"); +} + +void PhongGLTest::bindBufferUniformBuffersNotEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + PhongGL shader; + shader.bindProjectionBuffer(buffer) + .bindProjectionBuffer(buffer, 0, 16) + .bindTransformationBuffer(buffer) + .bindTransformationBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .bindLightBuffer(buffer) + .bindLightBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + +void PhongGLTest::bindTexturesInvalid() { + auto&& data = BindTexturesInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Texture2D texture; + PhongGL shader{data.flags}; + shader.bindAmbientTexture(texture) + .bindDiffuseTexture(texture) + .bindSpecularTexture(texture) + .bindNormalTexture(texture) + .bindTextures(&texture, &texture, &texture, &texture); + + CORRADE_COMPARE(out.str(), data.message); +} + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::bindTextureArraysInvalid() { + auto&& data = BindTextureArraysInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Texture2DArray textureArray; + PhongGL shader{data.flags}; + shader.bindAmbientTexture(textureArray) + .bindDiffuseTexture(textureArray) + .bindSpecularTexture(textureArray) + .bindNormalTexture(textureArray); + + CORRADE_COMPARE(out.str(), data.message); +} +#endif + +void PhongGLTest::setAlphaMaskNotEnabled() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -691,6 +1323,27 @@ void PhongGLTest::setAlphaMaskNotEnabled() { "Shaders::PhongGL::setAlphaMask(): the shader was not created with alpha mask enabled\n"); } +void PhongGLTest::setSpecularDisabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Texture2D texture; + PhongGL shader{PhongGL::Flag::NoSpecular}; + shader.setSpecularColor({}) + .setShininess({}) + .setLightSpecularColors({{}}) + .setLightSpecularColor(0, {}); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setSpecularColor(): the shader was created with specular disabled\n" + "Shaders::PhongGL::setShininess(): the shader was created with specular disabled\n" + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with specular disabled\n" + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with specular disabled\n"); +} + void PhongGLTest::setTextureMatrixNotEnabled() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -706,6 +1359,62 @@ void PhongGLTest::setTextureMatrixNotEnabled() { "Shaders::PhongGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +void PhongGLTest::setNormalTextureScaleNotEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + PhongGL shader; + shader.setNormalTextureScale({}); + + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setNormalTextureScale(): the shader was not created with normal texture enabled\n"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setTextureLayerNotArray() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + PhongGL shader; + shader.setTextureLayer(37); + + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setTextureLayer(): the shader was not created with texture arrays enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::bindTextureTransformBufferNotEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + PhongGL shader{PhongGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::setObjectIdNotEnabled() { #ifdef CORRADE_NO_ASSERT @@ -757,6 +1466,26 @@ void PhongGLTest::setWrongLightId() { "Shaders::PhongGL::setLightRange(): light ID 3 is out of bounds for 3 lights\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setWrongDrawOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + PhongGL{PhongGL::Flag::UniformBuffers, 1, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void PhongGLTest::renderSetup() { @@ -785,11 +1514,57 @@ void PhongGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -void PhongGLTest::renderDefaults() { +template void PhongGLTest::renderDefaults() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{} - .draw(sphere); + PhongGL shader{flag}; + + if(flag == PhongGL::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -812,24 +1587,84 @@ void PhongGLTest::renderDefaults() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderColored() { +template void PhongGLTest::renderColored() { auto&& data = RenderColoredData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{{}, 2} - .setLightColors({data.lightColor1, data.lightColor2}) - .setLightPositions({{data.lightPosition1, -3.0f, 2.0f, 0.0f}, - {data.lightPosition2, -3.0f, 2.0f, 0.0f}}) - .setAmbientColor(0x330033_rgbf) - .setDiffuseColor(0xccffcc_rgbf) - .setSpecularColor(0x6666ff_rgbf) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(data.rotation)) - .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + PhongGL shader{flag, 2}; + + if(flag == PhongGL::Flag{}) { + shader + .setLightColors({data.lightColor1, data.lightColor2}) + .setLightPositions({{data.lightPosition1, -3.0f, 2.0f, 0.0f}, + {data.lightPosition2, -3.0f, 2.0f, 0.0f}}) + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.rotation)) + .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.rotation)) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({data.lightPosition1, -3.0f, 2.0f, 0.0f}) + .setColor(data.lightColor1), + PhongLightUniform{} + .setPosition({data.lightPosition2, -3.0f, 2.0f, 0.0f}) + .setColor(data.lightColor2) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -867,55 +1702,166 @@ constexpr GL::TextureFormat TextureFormatRGBA = #endif ; -void PhongGLTest::renderSinglePixelTextured() { +template void PhongGLTest::renderSinglePixelTextured() { auto&& data = RenderSinglePixelTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); + PhongGL::Flags flags = PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers && (data.flags & PhongGL::Flag::TextureArrays) && !(data.flags & PhongGL::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= PhongGL::Flag::TextureTransformation; + } + #endif + PhongGL shader{flags, 2}; + const Color4ub ambientData[]{ 0x330033_rgb }; ImageView2D ambientImage{PixelFormat::RGBA8Unorm, Vector2i{1}, ambientData}; - GL::Texture2D ambient; - ambient.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGBA, Vector2i{1}) - .setSubImage(0, {}, ambientImage); const Color4ub diffuseData[]{ 0xccffcc_rgb }; ImageView2D diffuseImage{PixelFormat::RGBA8Unorm, Vector2i{1}, diffuseData}; - GL::Texture2D diffuse; - diffuse.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGBA, Vector2i{1}) - .setSubImage(0, {}, diffuseImage); const Color4ub specularData[]{ 0x6666ff_rgb }; ImageView2D specularImage{PixelFormat::RGBA8Unorm, Vector2i{1}, specularData}; - GL::Texture2D specular; - specular.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGBA, Vector2i{1}) - .setSubImage(0, {}, specularImage); - PhongGL shader{PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 2}; - shader.setLightColors({0x993366_rgbf, 0x669933_rgbf}) - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)); - - if(data.multiBind) - shader.bindTextures(&ambient, &diffuse, &specular, nullptr); - else shader - .bindAmbientTexture(ambient) - .bindDiffuseTexture(diffuse) - .bindSpecularTexture(specular); + GL::Texture2D ambient; + GL::Texture2D diffuse; + GL::Texture2D specular; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray ambientArray{NoCreate}; + GL::Texture2DArray diffuseArray{NoCreate}; + GL::Texture2DArray specularArray{NoCreate}; + if(data.flags & PhongGL::Flag::TextureArrays) { + ambientArray = GL::Texture2DArray{}; + ambientArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector3i{1, 1, data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ambientImage); + diffuseArray = GL::Texture2DArray{}; + diffuseArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector3i{1, 1, data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, diffuseImage); + specularArray = GL::Texture2DArray{}; + specularArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector3i{1, 1, data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, specularImage); + shader + .bindAmbientTexture(ambientArray) + .bindDiffuseTexture(diffuseArray) + .bindSpecularTexture(specularArray); + if(flag != PhongGL::Flag::UniformBuffers && data.layer != 0) + shader.setTextureLayer(data.layer); /* to verify the default */ + } else + #endif + { + ambient = GL::Texture2D{}; + ambient.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector2i{1}) + .setSubImage(0, {}, ambientImage); + diffuse = GL::Texture2D{}; + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector2i{1}) + .setSubImage(0, {}, diffuseImage); + specular = GL::Texture2D{}; + specular.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGBA, Vector2i{1}) + .setSubImage(0, {}, specularImage); + if(data.multiBind) + shader.bindTextures(&ambient, &diffuse, &specular, nullptr); + else shader + .bindAmbientTexture(ambient) + .bindDiffuseTexture(diffuse) + .bindSpecularTexture(specular); + } - shader.draw(sphere); + if(flag == PhongGL::Flag{}) { + shader.setLightColors({0x993366_rgbf, 0x669933_rgbf}) + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setLayer(data.layer) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + /* Has to be set because the default is black regardless of + whether the texture is present or not (it has no way to + know) */ + .setAmbientColor(0xffffff_rgbf) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf), + PhongLightUniform{} + .setPosition({ 3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf) + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -938,10 +1884,31 @@ void PhongGLTest::renderSinglePixelTextured() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderTextured() { +template void PhongGLTest::renderTextured() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -949,76 +1916,194 @@ void PhongGLTest::renderTextured() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - PhongGL shader{data.flags, 2}; - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + PhongGL::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers && (data.flags & PhongGL::Flag::TextureArrays) && !(data.flags & PhongGL::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= PhongGL::Flag::TextureTransformation; + } + #endif + PhongGL shader{flags, 2}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D ambient; + GL::Texture2D ambient{NoCreate}; + GL::Texture2D diffuse{NoCreate}; + GL::Texture2D specular{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray ambientArray{NoCreate}; + GL::Texture2DArray diffuseArray{NoCreate}; + GL::Texture2DArray specularArray{NoCreate}; + #endif if(data.flags & PhongGL::Flag::AmbientTexture) { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/ambient-texture.tga")) && (image = importer->image2D(0))); - ambient.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - shader - .bindAmbientTexture(ambient) - /* Colorized. Case without a color (where it should be white) is - tested in renderSinglePixelTextured() */ - .setAmbientColor(0xff9999_rgbf); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + ambientArray = GL::Texture2DArray{}; + ambientArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindAmbientTexture(ambientArray); + } else + #endif + { + ambient = GL::Texture2D{}; + ambient.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindAmbientTexture(ambient); + } } /* If no diffuse texture is present, dial down the default diffuse color so ambient/specular is visible */ - GL::Texture2D diffuse; if(data.flags & PhongGL::Flag::DiffuseTexture) { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - diffuse.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - shader - .bindDiffuseTexture(diffuse) - /* Colorized. Case without a color (where it should be white) is - tested in renderSinglePixelTextured() */ - .setDiffuseColor(0x9999ff_rgbf); - } else shader.setDiffuseColor(0x333333_rgbf); - GL::Texture2D specular; + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + diffuseArray = GL::Texture2DArray{}; + diffuseArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindDiffuseTexture(diffuseArray); + } else + #endif + { + diffuse = GL::Texture2D{}; + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindDiffuseTexture(diffuse); + } + } + if(data.flags & PhongGL::Flag::SpecularTexture) { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/specular-texture.tga")) && (image = importer->image2D(0))); - specular.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); - shader - .bindSpecularTexture(specular) + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + specularArray = GL::Texture2DArray{}; + specularArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindSpecularTexture(specularArray); + } else + #endif + { + specular = GL::Texture2D{}; + specular.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindSpecularTexture(specular); + } + } + + if(flag == PhongGL::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + if(data.flags & PhongGL::Flag::AmbientTexture) /* Colorized. Case without a color (where it should be white) is tested in renderSinglePixelTextured() */ - .setSpecularColor(0x99ff99_rgbf); - } + shader.setAmbientColor(0xff9999_rgbf); + if(data.flags & PhongGL::Flag::DiffuseTexture) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured() */ + shader.setDiffuseColor(0x9999ff_rgbf); + else shader.setDiffuseColor(0x333333_rgbf); + if(data.flags & PhongGL::Flag::SpecularTexture) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured() */ + shader.setSpecularColor(0x99ff99_rgbf); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layer != 0) /* to verify the default */ + shader.setTextureLayer(data.layer); + #endif - /* Using default (white) light colors to have the texture data visible - better */ - shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + /* Using default (white) light colors to have the texture data visible + better */ + shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + .setLayer(data.layer) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + }}; + + PhongMaterialUniform materialUniformData[1]; + if(data.flags & PhongGL::Flag::AmbientTexture) + materialUniformData->setAmbientColor(0xff9999_rgbf); + if(data.flags & PhongGL::Flag::DiffuseTexture) + materialUniformData->setDiffuseColor(0x9999ff_rgbf); + else + materialUniformData->setDiffuseColor(0x333333_rgbf); + if(data.flags & PhongGL::Flag::SpecularTexture) + materialUniformData->setSpecularColor(0x99ff99_rgbf); + GL::Buffer materialUniform{materialUniformData}; + + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1037,10 +2122,31 @@ void PhongGLTest::renderTextured() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderTexturedNormal() { +template void PhongGLTest::renderTexturedNormal() { auto&& data = RenderTexturedNormalData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1055,12 +2161,40 @@ void PhongGLTest::renderTexturedNormal() { for(Color3ub& pixel: row) pixel.y() = 255 - pixel.y(); - GL::Texture2D normal; - normal.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); + PhongGL::Flags flags = PhongGL::Flag::NormalTexture|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers && (data.flags & PhongGL::Flag::TextureArrays) && !(data.flags & PhongGL::Flag::TextureTransformation)) { + CORRADE_INFO("Texture arrays currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= PhongGL::Flag::TextureTransformation; + } + #endif + PhongGL shader{flags, 2}; + + GL::Texture2D normal{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray normalArray{NoCreate}; + if(data.flags & PhongGL::Flag::TextureArrays) { + normalArray = GL::Texture2DArray{}; + normalArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, {image->size(), data.layer + 1}) + .setSubImage(0, {0, 0, data.layer}, ImageView2D{*image}); + shader.bindNormalTexture(normalArray); + } else + #endif + { + normal = GL::Texture2D{}; + normal.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + if(data.multiBind) + shader.bindTextures(nullptr, nullptr, nullptr, &normal); + else + shader.bindNormalTexture(normal); + } GL::Mesh plane = MeshTools::compile(Primitives::planeSolid( Primitives::PlaneFlag::TextureCoordinates)); @@ -1081,30 +2215,77 @@ void PhongGLTest::renderTexturedNormal() { /* Rotating the view a few times (together with light positions). If the tangent transformation in the shader is correct, it should result in exactly the same images. */ - PhongGL shader{PhongGL::Flag::NormalTexture|data.flags, 2}; - shader.setLightPositions({ - Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}, - Matrix4::rotationZ(data.rotation)*Vector4{ 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.35f))* - Matrix4::rotationZ(data.rotation)* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationZ(data.rotation)* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setDiffuseColor(0x999999_rgbf); - - /* Verify the default is working properly */ - if(data.scale != 1.0f) - shader.setNormalTextureScale(data.scale); - - if(data.multiBind) - shader.bindTextures(nullptr, nullptr, nullptr, &normal); - else - shader.bindNormalTexture(normal); - - shader.draw(plane); + if(flag == PhongGL::Flag{}) { + /* Verify the defaults are working properly */ + if(data.scale != 1.0f) + shader.setNormalTextureScale(data.scale); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layer != 0) + shader.setTextureLayer(data.layer); + #endif + + shader.setLightPositions({ + Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}, + Matrix4::rotationZ(data.rotation)*Vector4{ 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.35f))* + Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setDiffuseColor(0x999999_rgbf) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.35f))* + Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(0x999999_rgbf) + .setNormalTextureScale(data.scale) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setLayer(data.layer) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition(Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition(Matrix4::rotationZ(data.rotation)*Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + }}; + /* Also take into account the case when texture transform needs to be + enabled for texture arrays, so not data.flags but flags */ + if(flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1137,8 +2318,25 @@ void PhongGLTest::renderTexturedNormal() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void PhongGLTest::renderVertexColor() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void PhongGLTest::renderVertexColor() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -1171,20 +2369,62 @@ template void PhongGLTest::renderVertexColor() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - PhongGL{PhongGL::Flag::DiffuseTexture|PhongGL::Flag::VertexColor, 2} - .setLightPositions({{-3.0f, -3.0f, 0.0f, 0.0f}, - { 3.0f, -3.0f, 0.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setAmbientColor(0x111111_rgbf) - .setDiffuseColor(0x9999ff_rgbf) - .bindDiffuseTexture(diffuse) - .draw(sphere); + PhongGL shader{PhongGL::Flag::DiffuseTexture|PhongGL::Flag::VertexColor|flag, 2}; + shader.bindDiffuseTexture(diffuse); + + if(flag == PhongGL::Flag{}) { + shader + .setLightPositions({{-3.0f, -3.0f, 0.0f, 0.0f}, + { 3.0f, -3.0f, 0.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setAmbientColor(0x111111_rgbf) + .setDiffuseColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 0.0f, 0.0f}), + PhongLightUniform{}.setPosition({ 3.0f, -3.0f, 0.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x111111_rgbf) + .setDiffuseColor(0x9999ff_rgbf) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1203,20 +2443,74 @@ template void PhongGLTest::renderVertexColor() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderShininess() { +template void PhongGLTest::renderShininess() { auto&& data = RenderShininessData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{} - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}}) - .setDiffuseColor(0xff3333_rgbf) - .setSpecularColor(data.specular) - .setShininess(data.shininess) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + PhongGL shader{flag|data.flags}; + if(flag == PhongGL::Flag{}) { + if(!(data.flags & PhongGL::Flag::NoSpecular)) shader + .setSpecularColor(data.specular) + .setShininess(data.shininess); + shader + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}}) + .setDiffuseColor(0xff3333_rgbf) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f)) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(0xff3333_rgbf) + .setSpecularColor(data.specular) /* ignored if NoSpecular */ + .setShininess(data.shininess) /* ignored if NoSpecular */ + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1233,7 +2527,7 @@ void PhongGLTest::renderShininess() { #elif !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) /* SwiftShader has some minor rounding differences (max = 1.67). ARM Mali G71 has bigger rounding differences. */ - const Float maxThreshold = 12.0f, meanThreshold = 0.043f; + const Float maxThreshold = 221.0f, meanThreshold = 0.106f; #else /* WebGL 1 doesn't have 8bit renderbuffer storage, so it's way worse */ const Float maxThreshold = 16.667f, meanThreshold = 2.583f; @@ -1302,10 +2596,26 @@ void PhongGLTest::renderAlphaTeardown() { renderTeardown(); } -void PhongGLTest::renderAlpha() { +template void PhongGLTest::renderAlpha() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1343,31 +2653,79 @@ void PhongGLTest::renderAlpha() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - PhongGL shader{data.flags, 2}; - shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setAmbientColor(data.ambientColor) - .setDiffuseColor(data.diffuseColor) - .setSpecularColor(0xffffff00_rgbaf) - .bindTextures(&ambient, &diffuse, nullptr, nullptr); - - /* Test that the default is correct by not setting the threshold if it's - equal to the default */ - if(data.flags & PhongGL::Flag::AlphaMask && data.threshold != 0.5f) - shader.setAlphaMask(data.threshold); - - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + PhongGL shader{data.flags|flag, 2}; + shader.bindTextures(&ambient, &diffuse, nullptr, nullptr); + + if(flag == PhongGL::Flag{}) { + shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setAmbientColor(data.ambientColor) + .setDiffuseColor(data.diffuseColor) + .setSpecularColor(0xffffff00_rgbaf); + + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & PhongGL::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(data.ambientColor) + .setDiffuseColor(data.diffuseColor) + .setSpecularColor(0xffffff00_rgbaf) + .setAlphaMask(data.threshold) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1426,9 +2784,22 @@ void PhongGLTest::renderObjectIdTeardown() { _framebuffer = GL::Framebuffer{NoCreate}; } -void PhongGLTest::renderObjectId() { - auto&& data = RenderObjectIdData[testCaseInstanceId()]; - setTestCaseDescription(data.name); +template void PhongGLTest::renderObjectId() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) @@ -1439,23 +2810,56 @@ void PhongGLTest::renderObjectId() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - if(data.instanceCount) sphere - .setInstanceCount(data.instanceCount) - .addVertexBufferInstanced( - GL::Buffer{Containers::arrayView({11002u, 48823u})}, - 1, 0, PhongGL::ObjectId{}); - - PhongGL{data.flags, 2} - .setLightColors({0x993366_rgbf, 0x669933_rgbf}) - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setAmbientColor(0x330033_rgbf) - .setDiffuseColor(0xccffcc_rgbf) - .setSpecularColor(0x6666ff_rgbf) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setObjectId(data.uniformId) - .draw(sphere); + PhongGL shader{PhongGL::Flag::ObjectId|flag, 2}; + + if(flag == PhongGL::Flag{}) { + shader + .setLightColors({0x993366_rgbf, 0x669933_rgbf}) + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setObjectId(48526) + .draw(sphere); + } else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f)) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setObjectId(48526) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf), + PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1489,33 +2893,88 @@ void PhongGLTest::renderObjectId() { /* Outside of the object, cleared to 27 */ CORRADE_COMPARE(image.pixels()[10][10], 27); /* Inside of the object */ - CORRADE_COMPARE(image.pixels()[40][46], data.expected); + CORRADE_COMPARE(image.pixels()[40][46], 48526); } #endif -void PhongGLTest::renderLights() { +template void PhongGLTest::renderLights() { auto&& data = RenderLightsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + GL::Mesh plane = MeshTools::compile(Primitives::planeSolid()); Matrix4 transformation = Matrix4::translation({0.0f, 0.0f, -1.5f}); - PhongGL{{}, 1} - /* Set non-black ambient to catch accidental NaNs -- the render should - never be fully black */ - .setAmbientColor(0x222222_rgbf) - .setSpecularColor(data.specularColor) - .setLightPositions({data.position}) - .setLightColors({0xff8080_rgbf*data.intensity}) - .setLightSpecularColors({data.lightSpecularColor}) - .setLightRanges({data.range}) - .setShininess(60.0f) - .setTransformationMatrix(transformation) - .setNormalMatrix(transformation.normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f)) - .draw(plane); + PhongGL shader{flag, 1}; + if(flag == PhongGL::Flag{}) { + shader + /* Set non-black ambient to catch accidental NaNs -- the render + should never be fully black */ + .setAmbientColor(0x222222_rgbf) + .setSpecularColor(data.specularColor) + .setLightPositions({data.position}) + .setLightColors({0xff8080_rgbf*data.intensity}) + .setLightSpecularColors({data.lightSpecularColor}) + .setLightRanges({data.range}) + .setShininess(60.0f) + .setTransformationMatrix(transformation) + .setNormalMatrix(transformation.normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f)) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix(transformation) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix(transformation.normalMatrix()) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({data.position}) + .setColor(0xff8080_rgbf*data.intensity) + .setSpecularColor(data.lightSpecularColor) + .setRange(data.range), + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x222222_rgbf) + .setSpecularColor(data.specularColor) + .setShininess(60.0f) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1635,13 +3094,96 @@ void PhongGLTest::renderLowLightAngle() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderZeroLights() { - CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Draw), GL::Framebuffer::Status::Complete); +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::renderLightCulling() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setLightOffsetCount(57, 2) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + /* Put one light into the first 32-bit component, one into the second to + test that both halves are checked correctly */ + PhongLightUniform lights[64]; + lights[57] = PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf); + lights[58] = PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf); + GL::Buffer lightUniform{lights}; + + PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 64}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + /* SwiftShader has some minor rounding differences (max = 1). ARM Mali G71 + and Apple A8 has bigger rounding differences. */ + const Float maxThreshold = 8.34f, meanThreshold = 0.100f; + #else + /* WebGL 1 doesn't have 8bit renderbuffer storage, so it's way worse */ + const Float maxThreshold = 15.34f, meanThreshold = 3.33f; + #endif + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join(_testDir, "PhongTestFiles/colored.tga"), + (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); +} +#endif +template void PhongGLTest::renderZeroLights() { if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -1655,7 +3197,7 @@ void PhongGLTest::renderZeroLights() { flags |= PhongGL::Flag::ObjectId; } #endif - PhongGL shader{flags, 0}; + PhongGL shader{flags|flag, 0}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); @@ -1669,42 +3211,89 @@ void PhongGLTest::renderZeroLights() { .setStorage(1, TextureFormatRGBA, ambientImage->size()) .setSubImage(0, {}, *ambientImage); - GL::Texture2D bogus; + shader.bindAmbientTexture(ambient); - shader - .bindAmbientTexture(ambient) - .setAmbientColor(0x9999ff_rgbf) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - /* Keep alpha mask at the default 0.5 to test the default */ - /* Passing a zero-sized light position / color array, shouldn't assert */ - .setLightPositions(Containers::ArrayView{}) - .setLightColors(Containers::ArrayView{}) - /* Using a bogus normal matrix -- it's not used so it should be okay. - Same for all other unused values, they should get ignored. */ - .setNormalMatrix(Matrix3x3{Math::ZeroInit}) - .setDiffuseColor(0xfa9922_rgbf) - .setSpecularColor(0xfa9922_rgbf) - .setShininess(0.2f) - .setNormalTextureScale(-0.3f); + if(flag == PhongGL::Flag{}) { + shader + .setAmbientColor(0x9999ff_rgbf) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + /* Keep alpha mask at the default 0.5 to test the default */ + /* Passing a zero-sized light position / color array, shouldn't + assert */ + .setLightPositions(Containers::ArrayView{}) + .setLightColors(Containers::ArrayView{}) + /* Using a bogus normal matrix -- it's not used so it should be + okay. Same for all other unused values, they should get + ignored. */ + .setNormalMatrix(Matrix3x3{Math::ZeroInit}) + .setDiffuseColor(0xfa9922_rgbf) + .setSpecularColor(0xfa9922_rgbf) + .setShininess(0.2f) + .setNormalTextureScale(-0.3f); + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + shader.setObjectId(65534); + } + #endif + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } #ifndef MAGNUM_TARGET_GLES2 - #ifndef MAGNUM_TARGET_GLES - if(GL::Context::current().isExtensionSupported()) - #endif - { - shader.setObjectId(65534); + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + /* Using a bogus normal matrix -- it's not used so it should be + okay. */ + .setNormalMatrix(Matrix3x3{Math::ZeroInit}) + .setObjectId(65534) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x9999ff_rgbf) + /* Same for all other unused values, they should get ignored */ + .setDiffuseColor(0xfa9922_rgbf) + .setSpecularColor(0xfa9922_rgbf) + .setShininess(0.2f) + .setNormalTextureScale(-0.3f) + }}; + /* Not binding any light buffer as it's not needed */ + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); } #endif - - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1746,10 +3335,31 @@ void PhongGLTest::renderZeroLights() { #endif } -void PhongGLTest::renderInstanced() { +template void PhongGLTest::renderInstanced() { auto&& data = RenderInstancedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1765,95 +3375,681 @@ void PhongGLTest::renderInstanced() { #endif #endif - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates| Primitives::UVSphereFlag::Tangents)); - /* Three spheres, each in a different location, differently rotated to - ensure the normal matrix is properly used as well. */ + /* Three spheres, each in a different location. To test normal matrix + concatenation, everything is rotated 90° on Y, thus X is now -Z and Z is + now X. */ struct { Matrix4 transformation; Matrix3x3 normal; Color3 color; - Vector2 textureOffset; + Vector3 textureOffsetLayer; + UnsignedInt objectId; } instanceData[] { - {Matrix4::translation({-1.25f, -1.25f, 0.0f})* - Matrix4::rotationX(90.0_degf), - {}, 0xff3333_rgbf, {0.0f, 0.0f}}, - {Matrix4::translation({ 1.25f, -1.25f, 0.0f})* - Matrix4::rotationY(90.0_degf), - {}, 0x33ff33_rgbf, {1.0f, 0.0f}}, - {Matrix4::translation({ 0.0f, 1.0f, 1.0f})* - Matrix4::rotationZ(90.0_degf), - {}, 0x9999ff_rgbf, {0.5f, 1.0f}} + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{-1.25f, -1.25f, 0.0f}))*Matrix4::rotationY(-90.0_degf)*Matrix4::rotationX(90.0_degf), + /* to test also per-instance normal matrix is applied properly -- + the texture should look the same as in the case of Flat 3D + instanced textured */ + (Matrix4::rotationY(-90.0_degf)*Matrix4::rotationX(90.0_degf)).normalMatrix(), + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0xffff00_rgbf, + {0.0f, 0.0f, 0.0f}, 211}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 1.25f, -1.25f, 0.0f})), + {}, + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0x00ffff_rgbf, + {1.0f, 0.0f, 1.0f}, 4627}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 0.0f, 1.0f, -1.0f})), + {}, + data.flags & PhongGL::Flag::DiffuseTexture ? 0xffffff_rgbf : 0xff00ff_rgbf, + #ifndef MAGNUM_TARGET_GLES2 + data.flags & PhongGL::Flag::TextureArrays ? Vector3{0.0f, 0.0f, 2.0f} : + #endif + Vector3{0.5f, 1.0f, 2.0f}, 35363} }; - for(auto& instance: instanceData) - instance.normal = instance.transformation.normalMatrix(); sphere .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, PhongGL::TransformationMatrix{}, PhongGL::NormalMatrix{}, PhongGL::Color3{}, - PhongGL::TextureOffset{}) + #ifndef MAGNUM_TARGET_GLES2 + PhongGL::TextureOffsetLayer{}, + #else + PhongGL::TextureOffset{}, + 4, + #endif + #ifndef MAGNUM_TARGET_GLES2 + PhongGL::ObjectId{} + #else + 4 + #endif + ) .setInstanceCount(3); - Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); - CORRADE_VERIFY(importer); + /* Enable also Object ID, if supported */ + PhongGL::Flags flags = PhongGL::Flag::VertexColor| + PhongGL::Flag::InstancedTransformation|data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + flags |= PhongGL::Flag::InstancedObjectId; + } + #endif + PhongGL shader{flags, 2}; - Containers::Optional image; - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); - GL::Texture2D diffuse; - diffuse.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); + GL::Texture2D diffuse{NoCreate}; + GL::Texture2D normal{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray diffuseArray{NoCreate}; + GL::Texture2DArray normalArray{NoCreate}; + #endif + if(data.flags & (PhongGL::Flag::DiffuseTexture|PhongGL::Flag::NormalTexture)) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + if(data.flags & PhongGL::Flag::DiffuseTexture) { + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + diffuseArray = GL::Texture2DArray{}; + diffuseArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Three slices with 2 extra as a base offset, each slice + has half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 2 + 3}) + .setSubImage(0, {0, 0, 2}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 3}, second) + .setSubImage(0, {0, 0, 4}, third); + shader.bindDiffuseTexture(diffuseArray); + if(flag != PhongGL::Flag::UniformBuffers) + shader.setTextureLayer(2); /* base offset */ + + } else + #endif + { + diffuse = GL::Texture2D{}; + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindDiffuseTexture(diffuse); + } + } - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/normal-texture.tga")) && (image = importer->image2D(0))); - GL::Texture2D normal; - normal.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, TextureFormatRGB, image->size()) - .setSubImage(0, {}, *image); + if(data.flags & PhongGL::Flag::NormalTexture) { + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/normal-texture.tga")) && (image = importer->image2D(0))); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + normalArray = GL::Texture2DArray{}; + normalArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Three slices with 2 extra as a base offset, each slice + has half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 2 + 3}) + .setSubImage(0, {0, 0, 2}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 3}, second) + .setSubImage(0, {0, 0, 4}, third); + shader.bindNormalTexture(normalArray); + + } else + #endif + { + normal = GL::Texture2D{}; + normal.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindNormalTexture(normal); + } + + normal.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindNormalTexture(normal); + } + } + if(flag == PhongGL::Flag{}) { + shader + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setLightColors({0x999999_rgbf, 0x999999_rgbf}) + .setLightSpecularColors({0x0000ff_rgbf, 0x00ff00_rgbf}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* + Matrix4::scaling(Vector3{0.4f})) + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) + .setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf); + + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & PhongGL::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f} + )); + #ifndef MAGNUM_TARGET_GLES2 + if((data.flags & PhongGL::Flag::TextureArrays) && flag != PhongGL::Flag::UniformBuffers) + shader.setTextureLayer(2); /* base offset */ + #endif - PhongGL shader{PhongGL::Flag::DiffuseTexture| - PhongGL::Flag::VertexColor| - PhongGL::Flag::InstancedTransformation| - PhongGL::Flag::InstancedTextureOffset|data.flags, 2}; - shader - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-1.75f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)* - Matrix4::scaling(Vector3{0.4f})) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindDiffuseTexture(diffuse) - .setDiffuseColor(0xffff99_rgbf); - - if(data.flags & PhongGL::Flag::NormalTexture) - shader.bindNormalTexture(normal); - - shader.draw(sphere); + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + shader.setObjectId(1000); /* gets added to the per-instance ID */ + } + #endif + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) + .setObjectId(1000) /* gets added to the per-instance ID */ + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling( + #ifndef MAGNUM_TARGET_GLES2 + /* Slices of the texture array have half the height */ + data.flags & PhongGL::Flag::TextureArrays ? Vector2::xScale(0.5f) : + #endif + Vector2{0.5f})) + .setLayer(2) /* base offset */ + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x0000ff_rgbf), + PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x00ff00_rgbf) + }}; + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + /* + Colored case: + + - First should be lower left, yellow with a blue and green highlight + on bottom left and right part + - Second lower right, cyan with a yellow light, so green, the same + highlight at the same position + - Third up center, magenta with a yellow light, so red, the same + highlight at the same position + + Textured case: + + - Lower left has bottom left numbers, so light 7881, rotated (78 + visible, should look the same as the multidraw case or as Flat) + - Lower light has bottom right, 1223, rotated (23 visible, looking at + the left side of the sphere in the equivalent Flat test) + - Up center has 6778, rotated (78 visible, looking at the left side + of the sphere in the equivalent Flat test) + */ MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE_WITH( /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), Utility::Directory::join({_testDir, "PhongTestFiles", data.file}), (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + #ifndef MAGNUM_TARGET_GLES2 + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); + CORRADE_COMPARE(image.pixels()[24][56], 5627); + CORRADE_COMPARE(image.pixels()[56][40], 36363); + } + #endif +} + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::renderMulti() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + if(data.flags >= PhongGL::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId|PhongGL::Flag::LightCulling|data.flags, data.lightCount, data.materialCount, data.drawCount}; + + GL::Texture2D diffuse{NoCreate}; + GL::Texture2DArray diffuseArray{NoCreate}; + if(data.flags & PhongGL::Flag::DiffuseTexture) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + /* For arrays we upload three slices of the original image to half-high + slices */ + if(data.flags & PhongGL::Flag::TextureArrays) { + /** @todo implement image slicing, ffs */ + const ImageView2D first{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({0, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D second{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/2, 0, 0}), + image->format(), image->size()/2, image->data()}; + const ImageView2D third{ + image->storage().setRowLength(image->size().x()) + .setImageHeight(image->size().y()) + .setSkip({image->size().x()/4, image->size().y()/2, 0}), + image->format(), image->size()/2, image->data()}; + + diffuseArray = GL::Texture2DArray{}; + diffuseArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + /* Each slice has half the height */ + .setStorage(1, TextureFormatRGB, {image->size().x(), image->size().y()/2, 3}) + .setSubImage(0, {0, 0, 0}, first) + /* Put the second image on the right half to test that the + per-instance offset is used together with the layer */ + .setSubImage(0, {image->size().x()/2, 0, 1}, second) + .setSubImage(0, {0, 0, 2}, third); + shader.bindDiffuseTexture(diffuseArray); + + } else { + diffuse = GL::Texture2D{}; + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatRGB, image->size()) + .setSubImage(0, {}, *image); + shader.bindDiffuseTexture(diffuse); + } + } + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates| + Primitives::UVSphereFlag::Tangents); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates| + Primitives::PlaneFlag::Tangents)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates| + Primitives::ConeFlag::Tangents); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = PhongMaterialUniform{} + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0x00ffff_rgbf); + materialData[1*data.uniformIncrement] = PhongMaterialUniform{} + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + /* The shader has two lights hardcoded, so make sure the buffer can fit + 2 items enough even though the last draw needs just one light. Not a + problem on desktop, but WebGL complains. */ + Containers::Array lightData{2*data.uniformIncrement + 2}; + lightData[0*data.uniformIncrement] = PhongLightUniform{} + .setPosition(Vector4{0.0f, 0.0f, 1.0f, 0.0f}) + .setColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0x00ffff_rgbf); + lightData[1*data.uniformIncrement + 0] = PhongLightUniform{} + .setPosition(Vector4{-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0xff0000_rgbf); + lightData[1*data.uniformIncrement + 1] = PhongLightUniform{} + .setPosition(Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x00ff00_rgbf); + /* This will put the light to position 4 in case data.uniformIncrement is 1 + and to an offset aligned to 256 if it's higher */ + lightData[2*data.uniformIncrement + 1/data.uniformIncrement] = PhongLightUniform{} + .setPosition(Vector4{0.0f, 0.0f, 1.0f, 0.0f}) + .setColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xff00ff_rgbf); + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, lightData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* to test the normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf) + ); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & PhongGL::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f})) + .setLayer(0); /* ignored if not array */ + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & PhongGL::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({1.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f})) + .setLayer(1); /* ignored if not array */ + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + data.flags & PhongGL::Flag::TextureArrays ? + Matrix3::scaling(Vector2::xScale(0.5f))* + Matrix3::translation({0.0f, 0.0f}) : + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f})) + .setLayer(2); /* ignored if not array */ + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / light offsets are zero if we have single draw, as those are + done with UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 1, 2) + .setNormalMatrix(transformationData[0*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 3, 1) + .setNormalMatrix(transformationData[1*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 0, 1) + .setNormalMatrix(transformationData[2*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + shader.bindProjectionBuffer(projectionUniform); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 1*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 2*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 0*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + + if(data.flags >= PhongGL::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + } + + /* + Colored case: + + - Sphere should be lower left, yellow with a white light with red and + green highlight on bottom left and right part + - Plane lower right, cyan with a magenta light so blue + - Cone up center, yellow with a cyan light so green + + Textured case: + + - Sphere should have bottom left numbers, so light 7881, rotated (78 + visible) + - Plane bottom right, 1223 + - Cone 6778 + */ + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "PhongTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Sphere */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Plane */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Circle */ + } } +#endif }}}} diff --git a/src/Magnum/Shaders/Test/PhongGL_Test.cpp b/src/Magnum/Shaders/Test/PhongGL_Test.cpp index b76638f6c..07f1f9ea9 100644 --- a/src/Magnum/Shaders/Test/PhongGL_Test.cpp +++ b/src/Magnum/Shaders/Test/PhongGL_Test.cpp @@ -72,8 +72,8 @@ void PhongGL_Test::constructCopy() { void PhongGL_Test::debugFlag() { std::ostringstream out; - Debug{&out} << PhongGL::Flag::AmbientTexture << PhongGL::Flag(0xf0); - CORRADE_COMPARE(out.str(), "Shaders::PhongGL::Flag::AmbientTexture Shaders::PhongGL::Flag(0xf0)\n"); + Debug{&out} << PhongGL::Flag::AmbientTexture << PhongGL::Flag(0xcafedead); + CORRADE_COMPARE(out.str(), "Shaders::PhongGL::Flag::AmbientTexture Shaders::PhongGL::Flag(0xcafedead)\n"); } void PhongGL_Test::debugFlags() { @@ -96,9 +96,20 @@ void PhongGL_Test::debugFlagsSupersets() { /* InstancedTextureOffset is a superset of TextureTransformation so only one should be printed */ - std::ostringstream out; - Debug{&out} << (PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::TextureTransformation); - CORRADE_COMPARE(out.str(), "Shaders::PhongGL::Flag::InstancedTextureOffset\n"); + { + std::ostringstream out; + Debug{&out} << (PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::TextureTransformation); + CORRADE_COMPARE(out.str(), "Shaders::PhongGL::Flag::InstancedTextureOffset\n"); + } + + #ifndef MAGNUM_TARGET_GLES2 + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + { + std::ostringstream out; + Debug{&out} << (PhongGL::Flag::MultiDraw|PhongGL::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::PhongGL::Flag::MultiDraw\n"); + } + #endif } }}}} diff --git a/src/Magnum/Shaders/Test/PhongTest.cpp b/src/Magnum/Shaders/Test/PhongTest.cpp new file mode 100644 index 000000000..510da0a51 --- /dev/null +++ b/src/Magnum/Shaders/Test/PhongTest.cpp @@ -0,0 +1,339 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Shaders/Phong.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct PhongTest: TestSuite::Tester { + explicit PhongTest(); + + template void uniformSizeAlignment(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); + + void lightUniformConstructDefault(); + void lightUniformConstructNoInit(); + void lightUniformSetters(); +}; + +PhongTest::PhongTest() { + addTests({&PhongTest::uniformSizeAlignment, + &PhongTest::uniformSizeAlignment, + &PhongTest::uniformSizeAlignment, + + &PhongTest::drawUniformConstructDefault, + &PhongTest::drawUniformConstructNoInit, + &PhongTest::drawUniformSetters, + &PhongTest::drawUniformMaterialIdPacking, + + &PhongTest::materialUniformConstructDefault, + &PhongTest::materialUniformConstructNoInit, + &PhongTest::materialUniformSetters, + + &PhongTest::lightUniformConstructDefault, + &PhongTest::lightUniformConstructNoInit, + &PhongTest::lightUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "PhongDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "PhongMaterialUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "PhongLightUniform"; } +}; + +template void PhongTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void PhongTest::drawUniformConstructDefault() { + PhongDrawUniform a; + PhongDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + CORRADE_COMPARE(a.objectId, 0); + CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.lightOffset, 0); + CORRADE_COMPARE(b.lightOffset, 0); + CORRADE_COMPARE(a.lightCount, 0xffffffffu); + CORRADE_COMPARE(b.lightCount, 0xffffffffu); + + constexpr PhongDrawUniform ca; + constexpr PhongDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + CORRADE_COMPARE(ca.objectId, 0); + CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.lightOffset, 0); + CORRADE_COMPARE(cb.lightOffset, 0); + CORRADE_COMPARE(ca.lightCount, 0xffffffffu); + CORRADE_COMPARE(cb.lightCount, 0xffffffffu); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongDrawUniform a; + a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + a.materialId = 5; + a.lightCount = 7; + + new(&a) PhongDrawUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.lightCount, 7); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::drawUniformSetters() { + PhongDrawUniform a; + a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) + .setMaterialId(5) + .setObjectId(7) + .setLightOffsetCount(9, 13); + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f}, + Vector4{0.0f, -1.0f, 0.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.lightOffset, 9); + CORRADE_COMPARE(a.lightCount, 13); +} + +void PhongTest::drawUniformMaterialIdPacking() { + PhongDrawUniform a; + a.setMaterialId(13765); + /* The normalMatrix field is 3x4 floats, materialId should be right after + in the low 16 bits on both LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); +} + +void PhongTest::materialUniformConstructDefault() { + PhongMaterialUniform a; + PhongMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(a.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(b.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 1.0f); + CORRADE_COMPARE(b.normalTextureScale, 1.0f); + CORRADE_COMPARE(a.shininess, 80.0f); + CORRADE_COMPARE(b.shininess, 80.0f); + CORRADE_COMPARE(a.alphaMask, 0.5f); + CORRADE_COMPARE(b.alphaMask, 0.5f); + + constexpr PhongMaterialUniform ca; + constexpr PhongMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(ca.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(cb.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(ca.normalTextureScale, 1.0f); + CORRADE_COMPARE(cb.normalTextureScale, 1.0f); + CORRADE_COMPARE(ca.shininess, 80.0f); + CORRADE_COMPARE(cb.shininess, 80.0f); + CORRADE_COMPARE(ca.alphaMask, 0.5f); + CORRADE_COMPARE(cb.alphaMask, 0.5f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongMaterialUniform a; + a.diffuseColor = 0x354565fc_rgbaf; + a.normalTextureScale = 0.4f; + a.alphaMask = 7.0f; + + new(&a) PhongMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.diffuseColor, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 0.4f); + CORRADE_COMPARE(a.alphaMask, 7.0f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::materialUniformSetters() { + PhongMaterialUniform a; + a.setAmbientColor(0xff3366cc_rgbaf) + .setDiffuseColor(0x996600aa_rgbaf) + .setSpecularColor(0x0044ffdd_rgbaf) + .setNormalTextureScale(0.4f) + .setShininess(37.0f) + .setAlphaMask(2.5f); + CORRADE_COMPARE(a.ambientColor, 0xff3366cc_rgbaf); + CORRADE_COMPARE(a.diffuseColor, 0x996600aa_rgbaf); + CORRADE_COMPARE(a.specularColor, 0x0044ffdd_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 0.4f); + CORRADE_COMPARE(a.shininess, 37.0f); + CORRADE_COMPARE(a.alphaMask, 2.5f); +} + +void PhongTest::lightUniformConstructDefault() { + PhongLightUniform a; + PhongLightUniform b{DefaultInit}; + CORRADE_COMPARE(a.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(b.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(a.color, 0xffffff_rgbf); + CORRADE_COMPARE(b.color, 0xffffff_rgbf); + CORRADE_COMPARE(a.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(b.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(a.range, Constants::inf()); + CORRADE_COMPARE(b.range, Constants::inf()); + + constexpr PhongLightUniform ca; + constexpr PhongLightUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(cb.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(ca.color, 0xffffff_rgbf); + CORRADE_COMPARE(cb.color, 0xffffff_rgbf); + CORRADE_COMPARE(ca.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(cb.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(ca.range, Constants::inf()); + CORRADE_COMPARE(cb.range, Constants::inf()); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::lightUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongLightUniform a; + a.color = 0x354565_rgbf; + a.range = 7.0f; + + new(&a) PhongLightUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565_rgbf); + CORRADE_COMPARE(a.range, 7.0f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::lightUniformSetters() { + PhongLightUniform a; + a.setPosition({2.5f, 3.6f, 0.7f, 1.1f}) + .setColor(0x354565_rgbf) + .setSpecularColor(0x996600_rgbf) + .setRange(7.0f); + CORRADE_COMPARE(a.position, (Vector4{2.5f, 3.6f, 0.7f, 1.1f})); + CORRADE_COMPARE(a.color, 0x354565_rgbf); + CORRADE_COMPARE(a.specularColor, 0x996600_rgbf); + CORRADE_COMPARE(a.range, 7.0f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::PhongTest) diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga b/src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga new file mode 100644 index 000000000..53b29b425 Binary files /dev/null and b/src/Magnum/Shaders/Test/PhongTestFiles/instanced-textured.tga differ diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga b/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga index a761d9413..856356598 100644 Binary files a/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga and b/src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga differ diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga b/src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga new file mode 100644 index 000000000..3c7ce9617 Binary files /dev/null and b/src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga differ diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/multidraw.tga b/src/Magnum/Shaders/Test/PhongTestFiles/multidraw.tga new file mode 100644 index 000000000..40ac60ba4 Binary files /dev/null and b/src/Magnum/Shaders/Test/PhongTestFiles/multidraw.tga differ diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/shininess-black-specular.tga b/src/Magnum/Shaders/Test/PhongTestFiles/shininess-no-specular.tga similarity index 100% rename from src/Magnum/Shaders/Test/PhongTestFiles/shininess-black-specular.tga rename to src/Magnum/Shaders/Test/PhongTestFiles/shininess-no-specular.tga diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp new file mode 100644 index 000000000..da0791c81 --- /dev/null +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -0,0 +1,1295 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Image.h" +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Renderbuffer.h" +#include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/GL/Texture.h" +#include "Magnum/GL/TextureFormat.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Primitives/Grid.h" +#include "Magnum/Shaders/DistanceFieldVectorGL.h" +#include "Magnum/Shaders/FlatGL.h" +#include "Magnum/Shaders/MeshVisualizerGL.h" +#include "Magnum/Shaders/PhongGL.h" +#include "Magnum/Shaders/VertexColorGL.h" +#include "Magnum/Shaders/VectorGL.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#include "Magnum/Shaders/DistanceFieldVector.h" +#include "Magnum/Shaders/Flat.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/MeshVisualizer.h" +#include "Magnum/Shaders/Phong.h" +#include "Magnum/Shaders/Vector.h" +#endif + +#include "configure.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +/* + The goal of this is to not duplicate all testing work here, but instead + have a set of simple-to-setup benchmarks with trivial output that allow for + measuring cost of particular shader features or seeing performance + differences between different implementations of the same (e.g., using the + VertexColor shader vs Flat with vertex colors enabled). Thus: + + - all shaders render the same mesh, 2D shaders only ignore the Z + coordinate (so it should be possible to compare the perf between 2D and + 3D) + - the mesh contains all attributes the shader might ever need including + instanced ones, to avoid differences caused by different memory access + patterns + - transformation and projection is identity + - textures are always single-pixel to measure the sampler overhead, not + memory access overhead + - if texture transformation is enabled, it's identity + - if instancing features are enabled, there's exactly one instance + - if alpha mask is enabled, it's 0.0 + - uniforms / binding overhead is not included in the benchmark +*/ + +struct ShadersGLBenchmark: GL::OpenGLTester { + explicit ShadersGLBenchmark(); + + void renderSetup(); + void renderTeardown(); + + template void flat(); + void phong(); + template void vertexColor(); + template void vector(); + template void distanceFieldVector(); + void meshVisualizer2D(); + void meshVisualizer3D(); + /** @todo mesh visualizer TBN, how to verify output? */ + + private: + PluginManager::Manager _manager{"nonexistent"}; + + GL::Renderbuffer _color; + #ifndef MAGNUM_TARGET_GLES2 + GL::Renderbuffer _objectId{NoCreate}; + #endif + GL::Framebuffer _framebuffer; + + GL::Buffer _indices, _vertices; + GL::Mesh _mesh, _meshInstanced, _meshDuplicated; + + GL::Texture2D _textureWhite, _textureBlue; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray _textureWhiteArray, _textureBlueArray; + #endif +}; + +using namespace Math::Literals; + +constexpr Vector2i GridSubdivisions{64, 64}; +constexpr Vector2i RenderSize{512, 512}; +constexpr std::size_t WarmupIterations{100}; +constexpr std::size_t BenchmarkIterations{1000}; +constexpr std::size_t BenchmarkRepeats{4}; + +const struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} FlatData[] { + {"", {}, 1, 1}, + {"vertex color", FlatGL2D::Flag::VertexColor, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", FlatGL2D::Flag::ObjectId, 1, 1}, + #endif + {"textured", FlatGL2D::Flag::Textured, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture array", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays, 1, 1}, + #endif + {"textured + alpha mask", FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 1, 1}, + {"texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"instanced transformation", FlatGL2D::Flag::InstancedTransformation, 1, 1}, + {"instanced transformation + color", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::VertexColor, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced transformation + object ID", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 1, 1}, + #endif + {"instanced transformation + texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"UBO single", FlatGL2D::Flag::UniformBuffers, 1, 1}, + {"UBO single, texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"UBO single, texture array transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"UBO multi", FlatGL2D::Flag::UniformBuffers, 32, 128}, + {"multidraw", FlatGL2D::Flag::MultiDraw, 32, 128}, + #endif +}; + +const struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; + bool bufferStorage; +} PhongData[] { + {"", {}, 1, 1, 1, false}, + {"zero lights", {}, 0, 1, 1, false}, + {"five lights", {}, 5, 1, 1, false}, + {"no specular", PhongGL::Flag::NoSpecular, 1, 1, 1, false}, + {"vertex color", PhongGL::Flag::VertexColor, 1, 1, 1, false}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", PhongGL::Flag::ObjectId, 1, 1, 1, false}, + #endif + {"diffuse texture", PhongGL::Flag::DiffuseTexture, 1, 1, 1, false}, + {"ADS textures", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1, 1, 1, false}, + #ifndef MAGNUM_TARGET_GLES2 + {"ADS texture arrays", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays, 1, 1, 1, false}, + #endif + {"ADS textures + alpha mask", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::AlphaMask, 1, 1, 1, false}, + {"ADS textures + transformation", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1, 1, 1, false}, + {"normal texture", PhongGL::Flag::NormalTexture, 1, 1, 1, false}, + {"normal texture with separate bitangent", PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, 1, 1, 1, false}, + {"instanced transformation", PhongGL::Flag::InstancedTransformation, 1, 1, 1, false}, + {"instanced transformation + color", PhongGL::Flag::InstancedTransformation|PhongGL::Flag::VertexColor, 1, 1, 1, false}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced transformation + object ID", PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId, 1, 1, 1, false}, + #endif + {"instanced transformation + ADS texture offset", PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedTextureOffset, 1, 1, 1, false}, + #ifndef MAGNUM_TARGET_GLES2 + {"UBO single", PhongGL::Flag::UniformBuffers, 1, 1, 1, false}, + {"UBO single, zero lights", PhongGL::Flag::UniformBuffers, 0, 1, 1, false}, + {"UBO single five lights", PhongGL::Flag::UniformBuffers, 5, 1, 1, false}, + {"UBO single, ADS textures + transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1, 1, 1, false}, + {"UBO single, ADS texture arrays + transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, 1, 1, 1, false}, + {"UBO multi, one light", PhongGL::Flag::UniformBuffers, 1, 32, 128, false}, + {"multidraw, one light", PhongGL::Flag::MultiDraw, 1, 32, 128, false}, + #ifndef MAGNUM_TARGET_GLES + {"multidraw, one light, immutable buffer storage", PhongGL::Flag::MultiDraw, 1, 32, 128, true}, + #endif + {"multidraw, one light, light culling enabled", PhongGL::Flag::MultiDraw|PhongGL::Flag::LightCulling, 1, 32, 128, false}, + {"multidraw, 64 lights, light culling enabled, five used", PhongGL::Flag::MultiDraw|PhongGL::Flag::LightCulling, 64, 32, 128, false}, + #endif +}; + +const struct { + const char* name; + VertexColorGL2D::Flags flags; + UnsignedInt drawCount; +} VertexColorData[] { + {"", {}, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"UBO single", VertexColorGL2D::Flag::UniformBuffers, 1}, + {"UBO multi", VertexColorGL2D::Flag::UniformBuffers, 128}, + {"multidraw", VertexColorGL2D::Flag::MultiDraw, 128} + #endif +}; + +const struct { + const char* name; + VectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} VectorData[] { + {"", {}, 1, 1}, + {"texture transformation", VectorGL2D::Flag::TextureTransformation, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"UBO single", VectorGL2D::Flag::UniformBuffers, 1, 1}, + {"UBO single, texture transformation", VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1, 1}, + {"UBO multi", VectorGL2D::Flag::UniformBuffers, 32, 128}, + {"UBO multi, texture transformation", VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 32, 128}, + {"multidraw", VectorGL2D::Flag::MultiDraw, 32, 128}, + #endif +}; + +const struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} DistanceFieldVectorData[] { + {"", {}, 1, 1}, + {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"UBO single", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 1}, + {"UBO single, texture transformation", DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, 1, 1}, + {"UBO multi", DistanceFieldVectorGL2D::Flag::UniformBuffers, 32, 128}, + {"UBO multi, texture transformation", DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, 32, 128}, + {"multidraw", DistanceFieldVectorGL2D::Flag::MultiDraw, 32, 128}, + #endif +}; + +const struct { + const char* name; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} MeshVisualizer2DData[] { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL2D::Flag::Wireframe, 1, 1}, + #endif + {"wireframe w/o a GS", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced object ID", MeshVisualizerGL2D::Flag::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL2D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL2D::Flag::PrimitiveId, 1, 1}, + {"primitive ID from vertex ID", MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, 1, 1}, + #endif + #endif + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + {"UBO single, wireframe", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, 1, 1}, + #endif + {"UBO single, wireframe w/o a GS", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"UBO single, vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"UBO multi, wireframe", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, 32, 128}, + #endif + {"UBO multi, wireframe w/o a GS", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 32, 128}, + {"UBO multi, vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, 32, 128}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw, wireframe", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe, 32, 128}, + #endif + {"multidraw, wireframe w/o a GS", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 32, 128}, + {"multidraw, vertex ID", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::VertexId, 32, 128}, + #endif +}; + +const struct { + const char* name; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; +} MeshVisualizer3DData[] { + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe", MeshVisualizerGL3D::Flag::Wireframe, 1, 1}, + #endif + {"wireframe w/o a GS", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + #ifndef MAGNUM_TARGET_GLES2 + {"instanced object ID", MeshVisualizerGL3D::Flag::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL3D::Flag::PrimitiveId, 1, 1}, + {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, 1, 1}, + #endif + #endif + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + {"UBO single, wireframe", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, 1, 1}, + #endif + {"UBO single, wireframe w/o a GS", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"UBO single, vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"UBO multi, wireframe", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, 32, 128}, + #endif + {"UBO multi, wireframe w/o a GS", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 32, 128}, + {"UBO multi, vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, 32, 128}, + #ifndef MAGNUM_TARGET_WEBGL + {"multidraw, wireframe", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe, 32, 128}, + #endif + {"multidraw, wireframe w/o a GS", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 32, 128}, + {"multidraw, vertex ID", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::VertexId, 32, 128}, + #endif +}; + +ShadersGLBenchmark::ShadersGLBenchmark(): _framebuffer{{{}, RenderSize}} { + addInstancedBenchmarks({&ShadersGLBenchmark::flat<2>, + &ShadersGLBenchmark::flat<3>}, + BenchmarkRepeats, Containers::arraySize(FlatData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::phong}, + BenchmarkRepeats, Containers::arraySize(PhongData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::vertexColor<2>, + &ShadersGLBenchmark::vertexColor<3>}, + BenchmarkRepeats, Containers::arraySize(VertexColorData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::vector<2>, + &ShadersGLBenchmark::vector<3>}, + BenchmarkRepeats, Containers::arraySize(VectorData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::distanceFieldVector<2>, + &ShadersGLBenchmark::distanceFieldVector<3>}, + BenchmarkRepeats, Containers::arraySize(DistanceFieldVectorData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::meshVisualizer2D}, + BenchmarkRepeats, Containers::arraySize(MeshVisualizer2DData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + addInstancedBenchmarks({&ShadersGLBenchmark::meshVisualizer3D}, + BenchmarkRepeats, Containers::arraySize(MeshVisualizer3DData), + &ShadersGLBenchmark::renderSetup, + &ShadersGLBenchmark::renderTeardown, + BenchmarkType::GpuTime); + + /* Set up the framebuffer */ + _color.setStorage( + #if !defined(MAGNUM_TARGET_GLES2) || !defined(MAGNUM_TARGET_WEBGL) + GL::RenderbufferFormat::RGBA8, + #else + GL::RenderbufferFormat::RGBA4, + #endif + RenderSize); + _framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .bind(); + #ifndef MAGNUM_TARGET_GLES2 + /* If we don't have EXT_gpu_shader4, we likely don't have integer + framebuffers either (Mesa's Zink), so skip setting up integer + attachments to avoid GL errors */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _objectId = GL::Renderbuffer{}; + _objectId.setStorage(GL::RenderbufferFormat::R32UI, RenderSize); + _framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{1}, _objectId) + .mapForDraw({ + {FlatGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {FlatGL2D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }); + } + #endif + + /* Set up the mesh */ + { + Trade::MeshData data = Primitives::grid3DSolid(GridSubdivisions, + Primitives::GridFlag::TextureCoordinates| + Primitives::GridFlag::Normals| + Primitives::GridFlag::Tangents); + Containers::Array vertexColors{DirectInit, data.vertexCount(), 0xffffffff_rgbaf}; + Containers::Array bitangents{DirectInit, data.vertexCount(), Vector3{0.0f, 1.0f, 0.0f}}; + Trade::MeshData dataWithVertexColors = MeshTools::interleave(std::move(data), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, arrayView(vertexColors)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent, arrayView(bitangents)} + }); + _indices.setData(dataWithVertexColors.indexData()); + _vertices.setData(dataWithVertexColors.vertexData()); + _mesh = MeshTools::compile(dataWithVertexColors, _indices, _vertices); + + /* Instanced variant, if the divisor-related extension is supported */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(GL::Context::current().isExtensionSupported() || + GL::Context::current().isExtensionSupported() || + GL::Context::current().isExtensionSupported()) + #else + if(GL::Context::current().isExtensionSupported()) + #endif + #endif + { + _meshInstanced = MeshTools::compile(dataWithVertexColors, _indices, _vertices); + struct { + /* Given the way the matrix attribute is specified (column by + column), it should work for 2D as well */ + Matrix4 transformation{Math::IdentityInit}; + Matrix3x3 normalMatrix{Math::IdentityInit}; + Vector2 textureOffset{0.0f, 0.0f}; + Color4 color{0xffffffff_rgbaf}; + UnsignedInt objectId{0}; + } instanceData[1]; + _meshInstanced.addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + GenericGL3D::TransformationMatrix{}, + GenericGL3D::NormalMatrix{}, + GenericGL3D::TextureOffset{}, + #ifndef MAGNUM_TARGET_GLES2 + GenericGL3D::ObjectId{} + #else + sizeof(UnsignedInt) + #endif + ); + /** @todo hmm, this doesn't really issue an instanced draw call, does + that matter? */ + _meshInstanced.setInstanceCount(1); + } + + /* Non-indexed variant for GS-less wireframe drawing */ + _meshDuplicated = MeshTools::compile(MeshTools::duplicate(dataWithVertexColors)); + } + + /* Set up the textures */ + { + const Color4ub white[1] { 0xffffffff_rgba }; + _textureWhite.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + GL::TextureFormat::RGBA8 + #else + GL::TextureFormat::RGBA + #endif + , {1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, white}); + #ifndef MAGNUM_TARGET_GLES2 + _textureWhiteArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGBA8, {1, 1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, white}); + #endif + } { + const Color4ub blue[1] { 0x0000ffff_rgba }; + _textureBlue.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + GL::TextureFormat::RGBA8 + #else + GL::TextureFormat::RGBA + #endif + , {1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, blue}); + #ifndef MAGNUM_TARGET_GLES2 + _textureBlueArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGBA8, {1, 1, 1}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, blue}); + #endif + } + + /* Load the plugins directly from the build tree. Otherwise they're either + static and already loaded or not present in the build tree */ + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif +} + +void ShadersGLBenchmark::renderSetup() { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer + .clearColor(0, Color4{}) + .clearColor(1, Vector4ui{}); + } + #ifndef MAGNUM_TARGET_GLES + else + #endif + #endif + { + _framebuffer.clear(GL::FramebufferClear::Color); + } +} + +void ShadersGLBenchmark::renderTeardown() { + /* Nothing to do here */ +} + +#ifndef MAGNUM_TARGET_GLES2 +template struct UniformTraits; +template<> struct UniformTraits<2> { + typedef TransformationProjectionUniform2D TransformationProjection; +}; +template<> struct UniformTraits<3> { + typedef TransformationProjectionUniform3D TransformationProjection; +}; +#endif + +template void ShadersGLBenchmark::flat() { + auto&& data = FlatData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= FlatGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + FlatGL shader{data.flags + #ifndef MAGNUM_TARGET_GLES2 + , data.materialCount, data.drawCount + #endif + }; + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer transformationProjectionUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer textureTransformationUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + if(data.flags & FlatGL2D::Flag::UniformBuffers) { + transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array::TransformationProjection>{data.drawCount}}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + Containers::Array materialData{data.materialCount}; + materialData[0].setAlphaMask(0.0f); + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, materialData}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + if(data.flags & FlatGL2D::Flag::TextureTransformation) { + textureTransformationUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + shader.bindTextureTransformationBuffer(textureTransformationUniform); + } + } else + #endif + { + if(data.flags >= FlatGL2D::Flag::AlphaMask) + shader.setAlphaMask(0.0f); + } + if(data.flags >= FlatGL2D::Flag::Textured) { + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & FlatGL2D::Flag::TextureArrays) { + shader.bindTexture(_textureWhiteArray); + } else + #endif + { + shader.bindTexture(_textureWhite); + } + } + + GL::Mesh* mesh; + /* InstancedTextureOffset is a superset of TextureTransformation, so + remove those bits first when deciding if instanced */ + if((data.flags & ~FlatGL2D::Flag::TextureTransformation) & (FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset + #ifndef MAGNUM_TARGET_GLES2 + |FlatGL2D::Flag::InstancedObjectId + #endif + )) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("Required extension is not available."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); + #endif + #endif + mesh = &_meshInstanced; + } else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != 100; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::phong() { + auto&& data = PhongData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= PhongGL::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + PhongGL shader{data.flags, data.lightCount + #ifndef MAGNUM_TARGET_GLES2 + , data.materialCount, data.drawCount + #endif + }; + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer projectionUniform{NoCreate}; + GL::Buffer transformationUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + GL::Buffer lightUniform{NoCreate}; + GL::Buffer textureTransformationUniform{NoCreate}; + if(data.flags & PhongGL::Flag::UniformBuffers) { + projectionUniform = GL::Buffer{}; + transformationUniform = GL::Buffer{}; + drawUniform = GL::Buffer{}; + materialUniform = GL::Buffer{}; + lightUniform = GL::Buffer{}; + textureTransformationUniform = GL::Buffer{}; + + Containers::Array transformationData{data.drawCount}; + Containers::Array drawData{data.drawCount}; + drawData[0].lightCount = 5; /* Cap at 5 lights, even if more is set */ + Containers::Array materialData{data.materialCount}; + materialData[0] + /* White ambient so we always have a white output */ + .setAmbientColor(0xffffffff_rgbaf) + .setAlphaMask(0.0f); + Containers::Array lightData{data.lightCount}; + Containers::Array textureTransformationData{data.drawCount}; + + #ifndef MAGNUM_TARGET_GLES + if(data.bufferStorage) { + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::buffer_storage::string() << "is not supported."); + + projectionUniform.setStorage(Containers::arrayView({ProjectionUniform3D{}}), {}); + transformationUniform.setStorage(transformationData, {}); + drawUniform.setStorage(drawData, {}); + materialUniform.setStorage(materialData, {}); + lightUniform.setStorage(lightData, {}); + + if(data.flags & PhongGL::Flag::TextureTransformation) + textureTransformationUniform.setStorage(textureTransformationData, {}); + } else + #endif + { + projectionUniform.setData({ProjectionUniform3D{}}); + transformationUniform.setData(transformationData); + drawUniform.setData(drawData); + materialUniform.setData(materialData); + lightUniform.setData(lightData); + + if(data.flags & PhongGL::Flag::TextureTransformation) + textureTransformationUniform.setData(textureTransformationData); + } + + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + + } else + #endif + { + /* White ambient so we always have a white output */ + shader.setAmbientColor(0xffffffff_rgbaf); + if(data.flags >= PhongGL::Flag::AlphaMask) + shader.setAlphaMask(0.0f); + } + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & PhongGL::Flag::TextureArrays) { + if(data.flags >= PhongGL::Flag::AmbientTexture) + shader.bindAmbientTexture(_textureWhiteArray); + if(data.flags >= PhongGL::Flag::DiffuseTexture) + shader.bindDiffuseTexture(_textureWhiteArray); + if(data.flags >= PhongGL::Flag::SpecularTexture) + shader.bindSpecularTexture(_textureWhiteArray); + if(data.flags >= PhongGL::Flag::NormalTexture) + shader.bindNormalTexture(_textureBlueArray); + } else + #endif + { + if(data.flags >= PhongGL::Flag::AmbientTexture) + shader.bindAmbientTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::DiffuseTexture) + shader.bindDiffuseTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::SpecularTexture) + shader.bindSpecularTexture(_textureWhite); + if(data.flags >= PhongGL::Flag::NormalTexture) + shader.bindNormalTexture(_textureBlue); + } + + GL::Mesh* mesh; + /* InstancedTextureOffset is a superset of TextureTransformation, so + remove those bits first when deciding if instanced */ + if((data.flags & ~PhongGL::Flag::TextureTransformation) & (PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedTextureOffset + #ifndef MAGNUM_TARGET_GLES2 + |PhongGL::Flag::InstancedObjectId + #endif + )) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_instanced::string() << "is not supported."); + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("Required extension is not available."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); + #endif + #endif + mesh = &_meshInstanced; + } else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::vertexColor() { + auto&& data = VertexColorData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VertexColorGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= VertexColorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + VertexColorGL shader{data.flags + #ifndef MAGNUM_TARGET_GLES2 + , data.drawCount + #endif + }; + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer transformationProjectionUniform{NoCreate}; + GL::Buffer textureTransformationUniform{NoCreate}; + if(data.flags & VertexColorGL::Flag::UniformBuffers) { + transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array::TransformationProjection>{data.drawCount}}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform); + } + #endif + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::vector() { + auto&& data = VectorData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VectorGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= VectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + VectorGL shader{data.flags + #ifndef MAGNUM_TARGET_GLES2 + , data.materialCount, data.drawCount + #endif + }; + shader.bindVectorTexture(_textureWhite); + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer transformationProjectionUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer textureTransformationUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + if(data.flags & VectorGL2D::Flag::UniformBuffers) { + transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array::TransformationProjection>{data.drawCount}}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.materialCount}}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + if(data.flags & VectorGL2D::Flag::TextureTransformation) { + textureTransformationUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + }}; + shader.bindTextureTransformationBuffer(textureTransformationUniform); + } + } + #endif + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +template void ShadersGLBenchmark::distanceFieldVector() { + auto&& data = DistanceFieldVectorData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::formatString("{}", dimensions)); + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & DistanceFieldVectorGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= DistanceFieldVectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + DistanceFieldVectorGL shader{data.flags + #ifndef MAGNUM_TARGET_GLES2 + , data.materialCount, data.drawCount + #endif + }; + shader.bindVectorTexture(_textureWhite); + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer transformationProjectionUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + GL::Buffer textureTransformationUniform{NoCreate}; + if(data.flags & DistanceFieldVectorGL2D::Flag::UniformBuffers) { + transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array::TransformationProjection>{data.drawCount}}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.materialCount}}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) { + textureTransformationUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + shader.bindTextureTransformationBuffer(textureTransformationUniform); + } + } + #endif + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(_mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(_mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::meshVisualizer2D() { + auto&& data = MeshVisualizer2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + /* Checks verbatim copied from MeshVisualizerGLTest::construct2D() */ + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizerGL2D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + MeshVisualizerGL2D shader{data.flags}; + shader.setViewportSize(Vector2{RenderSize}); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::VertexId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId + #ifndef MAGNUM_TARGET_WEBGL + |MeshVisualizerGL2D::Flag::PrimitiveId + #endif + )) + shader.bindColorMapTexture(_textureWhite); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer transformationProjectionUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + if(data.flags & MeshVisualizerGL2D::Flag::UniformBuffers) { + transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + Containers::Array materialData{data.materialCount}; + materialData[0].setWireframeColor(0xffffffff_rgbaf); + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, materialData}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + } else + #endif + { + if(data.flags >= MeshVisualizerGL2D::Flag::Wireframe) + shader.setWireframeColor(0xffffffff_rgbaf); + } + + GL::Mesh* mesh; + if(data.flags >= MeshVisualizerGL2D::Flag::NoGeometryShader) { + mesh = &_meshDuplicated; + } + #ifndef MAGNUM_TARGET_GLES2 + else if(data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_instanced::string() << "is not supported."); + #endif + mesh = &_meshInstanced; + } + #endif + else { + mesh = &_mesh; + } + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +void ShadersGLBenchmark::meshVisualizer3D() { + auto&& data = MeshVisualizer3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL3D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + /* Checks verbatim copied from MeshVisualizerGLTest:.construct3D() */ + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags >= MeshVisualizerGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + #endif + + MeshVisualizerGL3D shader{data.flags}; + shader.setViewportSize(Vector2{RenderSize}); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId + #ifndef MAGNUM_TARGET_WEBGL + |MeshVisualizerGL3D::Flag::PrimitiveId + #endif + )) + shader.bindColorMapTexture(_textureWhite); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + GL::Buffer projectionUniform{NoCreate}; + GL::Buffer transformationUniform{NoCreate}; + GL::Buffer drawUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; + if(data.flags & MeshVisualizerGL3D::Flag::UniformBuffers) { + projectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + transformationUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + Containers::Array materialData{data.materialCount}; + materialData[0].setWireframeColor(0xffffffff_rgbaf); + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, materialData}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + } else + #endif + { + if(data.flags >= MeshVisualizerGL3D::Flag::Wireframe) + shader.setWireframeColor(0xffffffff_rgbaf); + } + + GL::Mesh* mesh; + if(data.flags >= MeshVisualizerGL3D::Flag::NoGeometryShader) + mesh = &_meshDuplicated; + #ifndef MAGNUM_TARGET_GLES2 + else if(data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) + mesh = &_meshInstanced; + #endif + else + mesh = &_mesh; + + /* Warmup run */ + /** @todo make this possible to do inside CORRADE_BENCHMARK() */ + for(std::size_t i = 0; i != WarmupIterations; ++i) + shader.draw(*mesh); + + CORRADE_BENCHMARK(BenchmarkIterations) + shader.draw(*mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(SHADERS_TEST_DIR, "BenchmarkFiles/trivial.tga"), + DebugTools::CompareImageToFile{_manager}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::ShadersGLBenchmark) diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 581281b6c..0e7d7a548 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -52,6 +53,21 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Circle.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Primitives/UVSphere.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Vector.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -60,17 +76,43 @@ struct VectorGLTest: GL::OpenGLTester { explicit VectorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersInvalid(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif template void setTextureMatrixNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void render2D(); - void render3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void render2D(); + template void render3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -84,15 +126,30 @@ struct VectorGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - - Mesa llvmpipe - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 - - WebGL 1 / 2 (on Mesa Intel) - - iPhone 6 w/ iOS 12.4 + Rendering tests done: + + [B] base + [O] UBOs + draw offset + [M] multidraw + + Mesa Intel BOM + ES2 xx + ES3 BOx + Mesa AMD B + Mesa llvmpipe B + SwiftShader ES2 Bxx + ES3 B + ANGLE ES2 xx + ES3 BOM + ARM Mali (Huawei P10) ES2 Bxx + ES3 BOx + WebGL (on Mesa Intel) 1.0 Bxx + 2.0 BOM + NVidia + Intel Windows + AMD macOS x + Intel macOS BOx + iPhone 6 w/ iOS 12.4 ES3 B x */ using namespace Math::Literals; @@ -105,6 +162,36 @@ constexpr struct { {"texture transformation", VectorGL2D::Flag::TextureTransformation} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + VectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1}, + {"", VectorGL2D::Flag::UniformBuffers, 1, 1}, + {"texture transformation", VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case + and 3+1 in 2D, per-material 3 */ + {"multiple materials, draws", VectorGL2D::Flag::UniformBuffers, 15, 42}, + {"multidraw with all the things", VectorGL2D::Flag::MultiDraw|VectorGL2D::Flag::TextureTransformation, 15, 42} +}; +#endif + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + VectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", VectorGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", VectorGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"}, +}; +#endif + const struct { const char* name; VectorGL2D::Flags flags; @@ -122,30 +209,117 @@ const struct { "vector2D.tga", "vector3D.tga", false} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + VectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D.tga", "multidraw3D.tga", + {}, 1, 1, 16, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, + {"draw offset", "multidraw2D.tga", "multidraw3D.tga", + {}, 2, 3, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, + {"multidraw", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::MultiDraw, 2, 3, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, +}; +#endif + VectorGLTest::VectorGLTest() { addInstancedTests({ &VectorGLTest::construct<2>, &VectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &VectorGLTest::constructUniformBuffers<2>, + &VectorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &VectorGLTest::constructMove<2>, &VectorGLTest::constructMove<3>, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::constructMoveUniformBuffers<2>, + &VectorGLTest::constructMoveUniformBuffers<3>, + #endif + }); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &VectorGLTest::constructUniformBuffersInvalid<2>, + &VectorGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::setUniformUniformBuffersEnabled<2>, + &VectorGLTest::setUniformUniformBuffersEnabled<3>, + &VectorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &VectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif &VectorGLTest::setTextureMatrixNotEnabled<2>, - &VectorGLTest::setTextureMatrixNotEnabled<3>}); + &VectorGLTest::setTextureMatrixNotEnabled<3>, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::bindTextureTransformBufferNotEnabled<2>, + &VectorGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::setWrongDrawOffset<2>, + &VectorGLTest::setWrongDrawOffset<3> + #endif + }); - addTests({&VectorGLTest::renderDefaults2D, - &VectorGLTest::renderDefaults3D}, + /* MSVC needs explicit type due to default template args */ + addTests({ + &VectorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::renderDefaults2D, + #endif + &VectorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::renderDefaults3D, + #endif + }, &VectorGLTest::renderSetup, &VectorGLTest::renderTeardown); - addInstancedTests({&VectorGLTest::render2D, - &VectorGLTest::render3D}, + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &VectorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::render2D, + #endif + &VectorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::render3D, + #endif + }, Containers::arraySize(RenderData), &VectorGLTest::renderSetup, &VectorGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&VectorGLTest::renderMulti2D, + &VectorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &VectorGLTest::renderSetup, + &VectorGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -189,6 +363,46 @@ template void VectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VectorGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + VectorGL shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void VectorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -210,6 +424,123 @@ template void VectorGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VectorGL a{VectorGL::Flag::UniformBuffers, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + VectorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), VectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + VectorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), VectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VectorGL{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::VectorGL: {}\n", data.message)); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + VectorGL shader{VectorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setBackgroundColor({}) + .setColor({}); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setBackgroundColor(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setColor(): the shader was created with uniform buffers enabled\n"); +} + +template void VectorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + VectorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + template void VectorGLTest::setTextureMatrixNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -227,6 +558,54 @@ template void VectorGLTest::setTextureMatrixNotEnabled() "Shaders::VectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + VectorGL shader{VectorGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VectorGL{VectorGL::Flag::UniformBuffers, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void VectorGLTest::renderSetup() { @@ -262,7 +641,18 @@ constexpr GL::TextureFormat TextureFormatR = #endif ; -void VectorGLTest::renderDefaults2D() { +template void VectorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -288,9 +678,30 @@ void VectorGLTest::renderDefaults2D() { .setSubImage(0, {}, *image); #endif - VectorGL2D{} - .bindVectorTexture(texture) - .draw(square); + VectorGL2D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL2D::Flag{}) { + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + VectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -309,7 +720,18 @@ void VectorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::renderDefaults3D() { +template void VectorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -335,9 +757,30 @@ void VectorGLTest::renderDefaults3D() { .setSubImage(0, {}, *image); #endif - VectorGL3D{} - .bindVectorTexture(texture) - .draw(plane); + VectorGL3D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL3D::Flag{}) { + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + VectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -356,10 +799,21 @@ void VectorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::render2D() { +template void VectorGLTest::render2D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -385,18 +839,50 @@ void VectorGLTest::render2D() { .setSubImage(0, {}, *image); #endif - VectorGL2D shader{data.flags}; - shader.setBackgroundColor(data.backgroundColor) - .setColor(data.color) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})* - Matrix3::rotation(5.0_degf)); - - shader.draw(square); + VectorGL2D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL2D::Flag{}) { + shader.setBackgroundColor(data.backgroundColor) + .setColor(data.color); + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::rotation(5.0_degf)); + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix3::projection({2.1f, 2.1f})* + Matrix3::rotation(5.0_degf) : Matrix3{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + VectorMaterialUniform{} + .setBackgroundColor(data.backgroundColor) + .setColor(data.color) + }}; + if(data.flags & VectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -418,10 +904,21 @@ void VectorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::render3D() { +template void VectorGLTest::render3D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -447,20 +944,54 @@ void VectorGLTest::render3D() { .setSubImage(0, {}, *image); #endif - VectorGL3D shader{data.flags}; - shader.setBackgroundColor(data.backgroundColor) - .setColor(data.color) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationZ(15.0_degf)); - - shader.draw(plane); + VectorGL3D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL3D::Flag{}) { + shader.setBackgroundColor(data.backgroundColor) + .setColor(data.color); + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf)); + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf) : Matrix4{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + VectorMaterialUniform{} + .setBackgroundColor(data.backgroundColor) + .setColor(data.color) + }}; + if(data.flags & VectorGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -482,6 +1013,415 @@ void VectorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void VectorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VectorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = VectorMaterialUniform{} + .setColor(0xff0000_rgbf) + .setBackgroundColor(0xffcccc_rgbf); + materialData[1*data.uniformIncrement] = VectorMaterialUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[1*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[2*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + VectorGL2D shader{VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation|data.flags, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform) + .bindMaterialBuffer(materialUniform); + + if(data.flags >= VectorGL2D::Flag::MultiDraw) + shader.draw({circle, square, triangle}); + else { + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + } + + /* + - Circle lower left, green, upside down + - Square lower right, red, mirrored + - Triangle up center, green + */ + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VectorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void VectorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VectorGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = VectorMaterialUniform{} + .setColor(0xff0000_rgbf) + .setBackgroundColor(0xffcccc_rgbf); + materialData[1*data.uniformIncrement] = VectorMaterialUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[1*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[2*data.uniformIncrement] = VectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + VectorGL3D shader{VectorGL3D::Flag::UniformBuffers|VectorGL3D::Flag::TextureTransformation|data.flags, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(VectorMaterialUniform), + sizeof(VectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform) + .bindMaterialBuffer(materialUniform); + + if(data.flags >= VectorGL3D::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + } + + /* + - Sphere lower left, green, upside down + - Plane lower right, red, mirrored + - Cone up center, green + */ + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VectorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VectorGLTest) diff --git a/src/Magnum/Shaders/Test/VectorGL_Test.cpp b/src/Magnum/Shaders/Test/VectorGL_Test.cpp index ff8e7f2e6..f050f270d 100644 --- a/src/Magnum/Shaders/Test/VectorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/VectorGL_Test.cpp @@ -41,17 +41,25 @@ struct VectorGL_Test: TestSuite::Tester { void debugFlag(); void debugFlags(); + #ifndef MAGNUM_TARGET_GLES2 + void debugFlagsSupersets(); + #endif }; VectorGL_Test::VectorGL_Test() { - addTests({&VectorGL_Test::constructNoCreate<2>, - &VectorGL_Test::constructNoCreate<3>, - - &VectorGL_Test::constructCopy<2>, - &VectorGL_Test::constructCopy<3>, - - &VectorGL_Test::debugFlag, - &VectorGL_Test::debugFlags}); + addTests({ + &VectorGL_Test::constructNoCreate<2>, + &VectorGL_Test::constructNoCreate<3>, + + &VectorGL_Test::constructCopy<2>, + &VectorGL_Test::constructCopy<3>, + + &VectorGL_Test::debugFlag, + &VectorGL_Test::debugFlags, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGL_Test::debugFlagsSupersets + #endif + }); } template void VectorGL_Test::constructNoCreate() { @@ -87,6 +95,15 @@ void VectorGL_Test::debugFlags() { CORRADE_COMPARE(out.str(), "Shaders::VectorGL::Flag::TextureTransformation|Shaders::VectorGL::Flag(0xf0) Shaders::VectorGL::Flags{}\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void VectorGL_Test::debugFlagsSupersets() { + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + std::ostringstream out; + Debug{&out} << (VectorGL3D::Flag::MultiDraw|VectorGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::VectorGL::Flag::MultiDraw\n"); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VectorGL_Test) diff --git a/src/Magnum/Shaders/Test/VectorTest.cpp b/src/Magnum/Shaders/Test/VectorTest.cpp new file mode 100644 index 000000000..12985d5ba --- /dev/null +++ b/src/Magnum/Shaders/Test/VectorTest.cpp @@ -0,0 +1,190 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Vector.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct VectorTest: TestSuite::Tester { + explicit VectorTest(); + + template void uniformSizeAlignment(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +VectorTest::VectorTest() { + addTests({&VectorTest::uniformSizeAlignment, + &VectorTest::uniformSizeAlignment, + + &VectorTest::drawUniformConstructDefault, + &VectorTest::drawUniformConstructNoInit, + &VectorTest::drawUniformSetters, + &VectorTest::drawUniformMaterialIdPacking, + + &VectorTest::materialUniformConstructDefault, + &VectorTest::materialUniformConstructNoInit, + &VectorTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "VectorDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "VectorMaterialUniform"; } +}; + +template void VectorTest::uniformSizeAlignment() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void VectorTest::drawUniformConstructDefault() { + VectorDrawUniform a; + VectorDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr VectorDrawUniform ca; + constexpr VectorDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + VectorDrawUniform a; + a.materialId = 5; + + new(&a) VectorDrawUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.materialId, 5); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::drawUniformSetters() { + VectorDrawUniform a; + a.setMaterialId(5); + CORRADE_COMPARE(a.materialId, 5); +} + +void VectorTest::drawUniformMaterialIdPacking() { + VectorDrawUniform a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void VectorTest::materialUniformConstructDefault() { + VectorMaterialUniform a; + VectorMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.backgroundColor, 0x00000000_rgbaf); + + constexpr VectorMaterialUniform ca; + constexpr VectorMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.backgroundColor, 0x00000000_rgbaf); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + VectorMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.backgroundColor = 0x98769facb_rgbaf; + + new(&a) VectorMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x98769facb_rgbaf); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::materialUniformSetters() { + VectorMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setBackgroundColor(0x98769facb_rgbaf); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x98769facb_rgbaf); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::VectorTest) diff --git a/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga new file mode 100644 index 000000000..d255dcae9 Binary files /dev/null and b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga differ diff --git a/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D.tga b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D.tga new file mode 100644 index 000000000..9e8deea4e Binary files /dev/null and b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D.tga differ diff --git a/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D-distancefield.tga b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D-distancefield.tga new file mode 100644 index 000000000..0e65129a8 Binary files /dev/null and b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D-distancefield.tga differ diff --git a/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D.tga b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D.tga new file mode 100644 index 000000000..998b3682a Binary files /dev/null and b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D.tga differ diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index d0e73cbd6..9bb6392fa 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -23,9 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include +#include #include "Magnum/DebugTools/CompareImage.h" #include "Magnum/Image.h" @@ -46,6 +48,17 @@ #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -54,16 +67,38 @@ struct VertexColorGLTest: GL::OpenGLTester { explicit VertexColorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersZeroDraws(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - template void renderDefaults2D(); - template void renderDefaults3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + + template void render2D(); + template void render3D(); - template void render2D(); - template void render3D(); + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -77,36 +112,156 @@ struct VertexColorGLTest: GL::OpenGLTester { }; /* - Rendering tests done on: - - - Mesa Intel - - Mesa AMD - - SwiftShader ES2/ES3 - - ARM Mali (Huawei P10) ES2/ES3 - - WebGL 1 / 2 (on Mesa Intel) - - iPhone 6 w/ iOS 12.4 + Rendering tests done: + + [B] base + [O] UBOs + draw offset + [M] multidraw + + Mesa Intel BOM + ES2 xx + ES3 BOx + Mesa AMD B + Mesa llvmpipe B + SwiftShader ES2 Bxx + ES3 B + ANGLE ES2 xx + ES3 BOM + ARM Mali (Huawei P10) ES2 Bxx + ES3 BOx + WebGL (on Mesa Intel) 1.0 Bxx + 2.0 BOM + NVidia + Intel Windows + AMD macOS x + Intel macOS BOx + iPhone 6 w/ iOS 12.4 ES3 B x */ using namespace Math::Literals; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + VertexColorGL2D::Flags flags; + UnsignedInt drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1}, + {"", VertexColorGL2D::Flag::UniformBuffers, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4 in 3D case + and 3 in 2D; one needs to be reserved for drawOffset */ + {"multiple draws", VertexColorGL2D::Flag::UniformBuffers, 63}, + {"multidraw with all the things", VertexColorGL2D::Flag::MultiDraw, 63} +}; +#endif + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + VertexColorGL2D::Flags flags; + UnsignedInt drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D.tga", "multidraw3D.tga", + {}, 1, 16, + /* Minor differences on ARM Mali */ + 0.34f, 0.01f}, + {"draw offset", "multidraw2D.tga", "multidraw3D.tga", + {}, 3, 1, + /* Minor differences on ARM Mali */ + 0.34f, 0.01f}, + {"multidraw", "multidraw2D.tga", "multidraw3D.tga", + VertexColorGL2D::Flag::MultiDraw, 3, 1, + /* Minor differences on ARM Mali */ + 0.34f, 0.01f} +}; +#endif + VertexColorGLTest::VertexColorGLTest() { addTests({ &VertexColorGLTest::construct<2>, - &VertexColorGLTest::construct<3>, + &VertexColorGLTest::construct<3>}); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &VertexColorGLTest::constructUniformBuffers<2>, + &VertexColorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + + addTests({ &VertexColorGLTest::constructMove<2>, - &VertexColorGLTest::constructMove<3>}); + &VertexColorGLTest::constructMove<3>, + + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::constructMoveUniformBuffers<2>, + &VertexColorGLTest::constructMoveUniformBuffers<3>, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::constructUniformBuffersZeroDraws<2>, + &VertexColorGLTest::constructUniformBuffersZeroDraws<3>, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::setUniformUniformBuffersEnabled<2>, + &VertexColorGLTest::setUniformUniformBuffersEnabled<3>, + &VertexColorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &VertexColorGLTest::bindBufferUniformBuffersNotEnabled<3>, + &VertexColorGLTest::setWrongDrawOffset<2>, + &VertexColorGLTest::setWrongDrawOffset<3> + #endif + }); + + /* MSVC needs explicit type due to default template args */ + addTests({ + &VertexColorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults2D, + #endif + &VertexColorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults2D, + #endif + &VertexColorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults3D, + #endif + &VertexColorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults3D, + #endif - addTests({&VertexColorGLTest::renderDefaults2D, - &VertexColorGLTest::renderDefaults2D, - &VertexColorGLTest::renderDefaults3D, - &VertexColorGLTest::renderDefaults3D, + &VertexColorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render2D, + #endif + &VertexColorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render2D, + #endif + &VertexColorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render3D, + #endif + &VertexColorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render3D, + #endif + }, + &VertexColorGLTest::renderSetup, + &VertexColorGLTest::renderTeardown); - &VertexColorGLTest::render2D, - &VertexColorGLTest::render2D, - &VertexColorGLTest::render3D, - &VertexColorGLTest::render3D}, + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&VertexColorGLTest::renderMulti2D, + &VertexColorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), &VertexColorGLTest::renderSetup, &VertexColorGLTest::renderTeardown); + #endif /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -147,6 +302,46 @@ template void VertexColorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VertexColorGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VertexColorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + VertexColorGL shader{data.flags, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void VertexColorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -166,6 +361,121 @@ template void VertexColorGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VertexColorGL a{VertexColorGL::Flag::UniformBuffers, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + VertexColorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), VertexColorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + VertexColorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), VertexColorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructUniformBuffersZeroDraws() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VertexColorGL{VertexColorGL::Flag::UniformBuffers, 0}; + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL: draw count can't be zero\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + VertexColorGL shader{VertexColorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n"); +} + +template void VertexColorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + VertexColorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VertexColorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} + +template void VertexColorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VertexColorGL{VertexColorGL::Flag::UniformBuffers, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void VertexColorGLTest::renderSetup() { @@ -193,8 +503,20 @@ void VertexColorGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -template void VertexColorGLTest::renderDefaults2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates); @@ -207,8 +529,21 @@ template void VertexColorGLTest::renderDefaults2D() { GL::Mesh circle = MeshTools::compile(circleData); circle.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL2D{} - .draw(circle); + VertexColorGL2D shader{flag}; + + if(flag == VertexColorGL2D::Flag{}) { + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -230,8 +565,20 @@ template void VertexColorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::renderDefaults3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -248,8 +595,21 @@ template void VertexColorGLTest::renderDefaults3D() { GL::Mesh sphere = MeshTools::compile(sphereData); sphere.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL3D{} - .draw(sphere); + VertexColorGL3D shader{flag}; + + if(flag == VertexColorGL3D::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -267,8 +627,20 @@ template void VertexColorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::render2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::render2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates); @@ -283,9 +655,23 @@ template void VertexColorGLTest::render2D() { GL::Mesh circle = MeshTools::compile(circleData); circle.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL2D{} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + VertexColorGL2D shader{flag}; + + if(flag == VertexColorGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -308,12 +694,20 @@ template void VertexColorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::render3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::render3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); - if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || - !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates); @@ -328,16 +722,39 @@ template void VertexColorGLTest::render3D() { GL::Mesh sphere = MeshTools::compile(sphereData); sphere.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL3D{} - .setTransformationProjectionMatrix( + VertexColorGL3D shader{flag}; + + if(flag == VertexColorGL3D::Flag{}) { + shader.setTransformationProjectionMatrix( Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationX(15.0_degf)) .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) /* AMD has one different pixel compared to Intel, SwiftShader has differently rasterized edges on five pixels. Apple A8 some more. */ @@ -353,6 +770,267 @@ template void VertexColorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void VertexColorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VertexColorGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid()); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3)); + /* Concatenate the meshes, reserve a vertex color attribute and fill it + with a ... RAINBOW! */ + Trade::MeshData colored = MeshTools::interleave(MeshTools::concatenate({circleData, squareData, triangleData}), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} + }); + Deg angle = 0.0_degf; + for(Vector3& i: colored.mutableAttribute(Trade::MeshAttribute::Color)) + i = Color3::fromHsv({angle += 360.0_degf/colored.vertexCount(), 1.0f, 1.0f}); + GL::Mesh mesh = MeshTools::compile(colored); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + VertexColorGL2D shader{VertexColorGL2D::Flag::UniformBuffers|data.flags, data.drawCount}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(circle); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(square); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(triangle); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform); + + if(data.flags >= VertexColorGL2D::Flag::MultiDraw) + shader.draw({circle, square, triangle}); + else { + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + - Circle should be lower left + - Square lower right + - Triangle up center + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VertexColorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void VertexColorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= VertexColorGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid()); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f); + /* Concatenate the meshes, reserve a vertex color attribute and fill it + with a ... RAINBOW! */ + Trade::MeshData colored = MeshTools::interleave(MeshTools::concatenate({sphereData, planeData, coneData}), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} + }); + Deg angle = 0.0_degf; + for(Vector3& i: colored.mutableAttribute(Trade::MeshAttribute::Color)) + i = Color3::fromHsv({angle += 360.0_degf/colored.vertexCount(), 1.0f, 1.0f}); + GL::Mesh mesh = MeshTools::compile(colored); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + VertexColorGL3D shader{VertexColorGL3D::Flag::UniformBuffers|data.flags, data.drawCount}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(sphere); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(plane); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(cone); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform); + + if(data.flags >= VertexColorGL3D::Flag::MultiDraw) + shader.draw({sphere, plane, cone}); + else { + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* + - Sphere should be lower left + - Plane lower right + - Cone up center + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "VertexColorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VertexColorGLTest) diff --git a/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp b/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp index 34a8b27d9..949670a1c 100644 --- a/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp @@ -23,7 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Shaders/VertexColorGL.h" @@ -36,6 +38,12 @@ struct VertexColorGL_Test: TestSuite::Tester { template void constructNoCreate(); template void constructCopy(); + + void debugFlag(); + void debugFlags(); + #ifndef MAGNUM_TARGET_GLES2 + void debugFlagsSupersets(); + #endif }; VertexColorGL_Test::VertexColorGL_Test() { @@ -44,7 +52,14 @@ VertexColorGL_Test::VertexColorGL_Test() { &VertexColorGL_Test::constructNoCreate<3>, &VertexColorGL_Test::constructCopy<2>, - &VertexColorGL_Test::constructCopy<3>}); + &VertexColorGL_Test::constructCopy<3>, + + &VertexColorGL_Test::debugFlag, + &VertexColorGL_Test::debugFlags, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGL_Test::debugFlagsSupersets + #endif + }); } template void VertexColorGL_Test::constructNoCreate() { @@ -65,6 +80,39 @@ template void VertexColorGL_Test::constructCopy() { CORRADE_VERIFY(!std::is_copy_assignable>{}); } +void VertexColorGL_Test::debugFlag() { + std::ostringstream out; + + #ifndef MAGNUM_TARGET_GLES2 + Debug{&out} << VertexColorGL3D::Flag::UniformBuffers << VertexColorGL3D::Flag(0xf0); + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag::UniformBuffers Shaders::VertexColorGL::Flag(0xf0)\n"); + #else + Debug{&out} << VertexColorGL3D::Flag(0xf0); + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag(0xf0)\n"); + #endif +} + +void VertexColorGL_Test::debugFlags() { + std::ostringstream out; + + #ifndef MAGNUM_TARGET_GLES2 + Debug{&out} << (VertexColorGL3D::Flag::UniformBuffers|VertexColorGL3D::Flag(0xf0)) << VertexColorGL3D::Flags{}; + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag::UniformBuffers|Shaders::VertexColorGL::Flag(0xf0) Shaders::VertexColorGL::Flags{}\n"); + #else + Debug{&out} << VertexColorGL3D::Flag(0xf0) << VertexColorGL3D::Flags{}; + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag(0xf0) Shaders::VertexColorGL::Flags{}\n"); + #endif +} + +#ifndef MAGNUM_TARGET_GLES2 +void VertexColorGL_Test::debugFlagsSupersets() { + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + std::ostringstream out; + Debug{&out} << (VertexColorGL3D::Flag::MultiDraw|VertexColorGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag::MultiDraw\n"); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VertexColorGL_Test) diff --git a/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga new file mode 100644 index 000000000..b1bb643d5 Binary files /dev/null and b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga differ diff --git a/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga new file mode 100644 index 000000000..18c5b661d Binary files /dev/null and b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga differ diff --git a/src/Magnum/Shaders/Vector.frag b/src/Magnum/Shaders/Vector.frag index a10ac4931..50477a9b2 100644 --- a/src/Magnum/Shaders/Vector.frag +++ b/src/Magnum/Shaders/Vector.frag @@ -29,8 +29,13 @@ #define texture texture2D #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -45,8 +50,56 @@ uniform lowp vec4 color #endif ; -#ifdef EXPLICIT_TEXTURE_LAYER -/* See AbstractVector.h for details about the ID */ +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +struct DrawUniform { + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 color; + lowp vec4 backgroundColor; + lowp uvec4 reserved; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + +/* Textures */ + +#ifdef EXPLICIT_BINDING layout(binding = 6) #endif uniform lowp sampler2D vectorTexture; @@ -55,6 +108,10 @@ uniform lowp sampler2D vectorTexture; in mediump vec2 interpolatedTextureCoordinates; +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + /* Outputs */ #ifdef NEW_GLSL @@ -65,6 +122,16 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + lowp const vec4 color = materials[materialId].color; + lowp const vec4 backgroundColor = materials[materialId].backgroundColor; + #endif + lowp float intensity = texture(vectorTexture, interpolatedTextureCoordinates).r; fragmentColor = mix(backgroundColor, color, intensity); } diff --git a/src/Magnum/Shaders/Vector.h b/src/Magnum/Shaders/Vector.h index e73d13435..daaf3d70a 100644 --- a/src/Magnum/Shaders/Vector.h +++ b/src/Magnum/Shaders/Vector.h @@ -25,26 +25,219 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Vector, alias @ref Magnum::Shaders::Vector2D, @ref Magnum::Shaders::Vector3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/VectorGL.h, the - * @ref Magnum::Shaders::VectorGL "VectorGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::VectorDrawUniform, @ref Magnum::Shaders::VectorMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/VectorGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/VectorGL.h, the VectorGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for vector shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform; material-related properties are expected to +be shared among multiple draw calls and thus are provided in a separate +@ref VectorMaterialUniform structure, referenced by @ref materialId. +@see @ref VectorGL::bindDrawBuffer() +*/ +struct VectorDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit VectorDrawUniform(DefaultInitT = DefaultInit) noexcept: + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) && !defined(CORRADE_TARGET_BIG_ENDIAN) + , _pad0{}, _pad1{}, _pad2{}, _pad3{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit VectorDrawUniform(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + VectorDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a @ref VectorMaterialUniform + * array. Useful when an UBO with more than one material is supplied or in + * a multi-draw scenario. Should be less than the material count passed to + * the @ref VectorGL::VectorGL(Flags, UnsignedInt, UnsignedInt) + * constructor, if material count is @cpp 1 @ce, this field is assumed to + * be @cpp 0 @ce and isn't even read by the shader. Default value is + * @cpp 0 @ce, meaning the first material gets used. + */ + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + alignas(4) UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + alignas(4) UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for objectId */ + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +/** +@brief Material uniform for vector shaders +@m_since_latest + +Describes material properties referenced from +@ref VectorDrawUniform::materialId. +@see @ref VectorGL::bindMaterialBuffer() +*/ +struct VectorMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit VectorMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, backgroundColor{0.0f, 0.0f, 0.0f, 0.0f} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + /* Otherwise it refuses to constexpr, on 3.8 at least */ + , _pad0{}, _pad1{}, _pad2{}, _pad3{} + #endif + {} + + /** @brief Construct without initializing the contents */ + explicit VectorMaterialUniform(NoInitT) noexcept: color{NoInit}, backgroundColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + VectorMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref backgroundColor field + * @return Reference to self (for method chaining) + */ + VectorMaterialUniform& setBackgroundColor(const Color4& color) { + backgroundColor = color; + return *this; + } + + /** + * @} + */ + + /** + * @brief Fill color + * + * Default is @cpp 0xffffffff_rgbaf @ce. + * @see @ref VectorGL::setColor() + */ + Color4 color; + + /** + * @brief Background color + * + * Default is @cpp 0x00000000_rgbaf @ce. + * @see @ref VectorGL::setBackgroundColor() + */ + Color4 backgroundColor; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; /* reserved for alpha mask */ + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief Shaders::VectorGL * @m_deprecated_since_latest Use @ref Shaders::VectorGL "VectorGL" instead. */ @@ -61,10 +254,8 @@ typedef CORRADE_DEPRECATED("use VectorGL2D instead") VectorGL2D Vector2D; * @m_deprecated_since_latest Use @ref VectorGL3D instead. */ typedef CORRADE_DEPRECATED("use VectorGL3D instead") VectorGL3D Vector3D; +#endif }} -#else -#error use Magnum/Shaders/VectorGL.h, the VectorGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/AbstractVector.vert b/src/Magnum/Shaders/Vector.vert similarity index 55% rename from src/Magnum/Shaders/AbstractVector.vert rename to src/Magnum/Shaders/Vector.vert index 17cb41708..c0acfe667 100644 --- a/src/Magnum/Shaders/AbstractVector.vert +++ b/src/Magnum/Shaders/Vector.vert @@ -23,13 +23,26 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + #ifndef NEW_GLSL #define in attribute #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -60,6 +73,57 @@ uniform mediump mat3 textureMatrix ; #endif +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetReservedReserved; + #define textureTransformation_offset offsetReservedReserved.xy +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -82,7 +146,36 @@ in mediump vec2 textureCoordinates; out mediump vec2 interpolatedTextureCoordinates; +#ifdef MULTI_DRAW +flat out highp uint drawId; +#endif + void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + drawId = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = mat3(transformationProjectionMatrices[drawId]); + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationProjectionMatrix = transformationProjectionMatrices[drawId]; + #else + #error + #endif + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawId].rotationScaling.xy, 0.0, textureTransformations[drawId].rotationScaling.zw, 0.0, textureTransformations[drawId].textureTransformation_offset, 1.0); + #endif + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index db401d3f4..baeb51075 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -32,15 +32,70 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Texture.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { -template VectorGL::VectorGL(const Flags flags): _flags{flags} { +namespace { + enum: Int { TextureUnit = 6 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4 + }; + #endif +} + +template VectorGL::VectorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::VectorGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::VectorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -60,15 +115,35 @@ template VectorGL::VectorGL(const Flags flag GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); vert.addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) - .addSource(rs.get("AbstractVector.vert")); + .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + vert.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + vert.addSource(rs.get("generic.glsl")) + .addSource(rs.get("Vector.vert")); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + drawCount, + materialCount)); + frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Vector.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); - GL::AbstractShaderProgram::attachShaders({vert, frag}); + attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -76,59 +151,179 @@ template VectorGL::VectorGL(const Flags flag if(!context.isExtensionSupported(version)) #endif { - GL::AbstractShaderProgram::bindAttributeLocation(AbstractVectorGL::Position::Location, "position"); - GL::AbstractShaderProgram::bindAttributeLocation(AbstractVectorGL::TextureCoordinates::Location, "textureCoordinates"); + bindAttributeLocation(Position::Location, "position"); + bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::AbstractShaderProgram::link()); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = GL::AbstractShaderProgram::uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = GL::AbstractShaderProgram::uniformLocation("textureMatrix"); - _backgroundColorUniform = GL::AbstractShaderProgram::uniformLocation("backgroundColor"); - _colorUniform = GL::AbstractShaderProgram::uniformLocation("color"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _backgroundColorUniform = uniformLocation("backgroundColor"); + _colorUniform = uniformLocation("color"); + } } #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - GL::AbstractShaderProgram::setUniform(GL::AbstractShaderProgram::uniformLocation("vectorTexture"), AbstractVectorGL::VectorTextureUnit); + setUniform(uniformLocation("vectorTexture"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Color4{1.0f}); /* Background color is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Background color is zero by default */ + setColor(Color4{1.0f}); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL::VectorGL(const Flags flags): VectorGL{flags, 1, 1} {} +#endif + template VectorGL& VectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { - GL::AbstractShaderProgram::setUniform(_transformationProjectionMatrixUniform, matrix); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template VectorGL& VectorGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::VectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); - GL::AbstractShaderProgram::setUniform(_textureMatrixUniform, matrix); + setUniform(_textureMatrixUniform, matrix); return *this; } template VectorGL& VectorGL::setBackgroundColor(const Color4& color) { - GL::AbstractShaderProgram::setUniform(_backgroundColorUniform, color); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setBackgroundColor(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_backgroundColorUniform, color); return *this; } template VectorGL& VectorGL::setColor(const Color4& color) { - GL::AbstractShaderProgram::setUniform(_colorUniform, color); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif + setUniform(_colorUniform, color); + return *this; +} + +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL& VectorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::VectorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +template VectorGL& VectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template VectorGL& VectorGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template VectorGL& VectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +template VectorGL& VectorGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + +template VectorGL& VectorGL::bindVectorTexture(GL::Texture2D& texture) { + texture.bind(TextureUnit); return *this; } @@ -144,6 +339,10 @@ Debug& operator<<(Debug& debug, const VectorGLFlag value) { /* LCOV_EXCL_START */ #define _c(v) case VectorGLFlag::v: return debug << "::" #v; _c(TextureTransformation) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -153,8 +352,12 @@ Debug& operator<<(Debug& debug, const VectorGLFlag value) { Debug& operator<<(Debug& debug, const VectorGLFlags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::VectorGL::Flags{}", { - VectorGLFlag::TextureTransformation - }); + VectorGLFlag::TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + VectorGLFlag::MultiDraw, /* Superset of UniformBuffers */ + VectorGLFlag::UniformBuffers + #endif + }); } } diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index d788f18d3..ef3d2bd33 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -31,14 +31,19 @@ */ #include "Magnum/DimensionTraits.h" -#include "Magnum/Shaders/AbstractVectorGL.h" +#include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/Shaders/GenericGL.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { namespace Implementation { enum class VectorGLFlag: UnsignedByte { - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 1, + MultiDraw = UniformBuffers|(1 << 2) + #endif }; typedef Containers::EnumSet VectorGLFlags; } @@ -73,10 +78,69 @@ Common rendering setup: @snippet MagnumShaders-gl.cpp VectorGL-usage2 +@section Shaders-VectorGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, because the shader doesn't need a separate +projection and transformation matrix, a combined one is supplied via a +@ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D +buffer. To maximize use of the limited uniform buffer memory, materials are +supplied separately in a @ref VectorMaterialUniform buffer and then referenced +via @relativeref{VectorDrawUniform,materialId} from a @ref VectorDrawUniform; +for optional texture transformation a per-draw +@ref TextureTransformationUniform can be supplied as well. A uniform buffer +setup equivalent to the above would look like this: + +@snippet MagnumShaders-gl.cpp VectorGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw, supply desired material +and draw count in the @ref VectorGL(Flags, UnsignedInt, UnsignedInt) +constructor and specify material references and texture offsets for every draw. +Texture arrays aren't currently supported for this shader. Besides that, the +usage is similar for all shaders, see @ref shaders-usage-multidraw for an +example. + +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. +@requires_webgl20 Uniform buffers are not available in WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders, @ref VectorGL2D, @ref VectorGL3D */ -template class MAGNUM_SHADERS_EXPORT VectorGL: public AbstractVectorGL { +template class MAGNUM_SHADERS_EXPORT VectorGL: public GL::AbstractShaderProgram { public: + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector2 "Vector2" in 2D, @ref Magnum::Vector3 "Vector3" + * in 3D. + */ + typedef typename GenericGL::Position Position; + + /** + * @brief 2D texture coordinates + * + * @ref shaders-generic "Generic attribute", + * @ref Magnum::Vector2 "Vector2". + */ + typedef typename GenericGL::TextureCoordinates TextureCoordinates; + + enum: UnsignedInt { + /** + * Color shader output. @ref shaders-generic "Generic output", + * present always. Expects three- or four-component floating-point + * or normalized buffer attachment. + */ + ColorOutput = GenericGL::ColorOutput + }; + #ifdef DOXYGEN_GENERATING_OUTPUT /** * @brief Flag @@ -90,7 +154,48 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * @see @ref setTextureMatrix() * @m_since{2020,06} */ - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer() + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 1, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 2) + #endif }; /** @@ -110,9 +215,48 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref VectorGL(Flags, UnsignedInt, UnsignedInt) with @p materialCount + * and @p drawCount set to @cpp 1 @ce. */ explicit VectorGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref VectorMaterialUniform buffer + * bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / + * @ref VectorDrawUniform / @ref TextureTransformationUniform + * buffer bound with @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials specified via + * @ref VectorDrawUniform::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is + * ignored and the constructor behaves the same as @ref VectorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit VectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -125,12 +269,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * However note that this is a low-level and a potentially dangerous * API, see the documentation of @ref NoCreate for alternatives. */ - explicit VectorGL(NoCreateT) noexcept - /** @todoc remove workaround when doxygen is sane */ - #ifndef DOXYGEN_GENERATING_OUTPUT - : AbstractVectorGL{NoCreate} - #endif - {} + explicit VectorGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} /** @brief Copying is not allowed */ VectorGL(const VectorGL&) = delete; @@ -150,8 +289,38 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref VectorMaterialUniform uniform + * buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref VectorDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -159,6 +328,11 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ VectorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -170,6 +344,11 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an * identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ VectorGL& setTextureMatrix(const Matrix3& matrix); @@ -178,6 +357,10 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * @return Reference to self (for method chaining) * * Initial value is @cpp 0x00000000_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref VectorMaterialUniform::backgroundColor and call + * @ref bindMaterialBuffer() instead. * @see @ref setColor() */ VectorGL& setBackgroundColor(const Color4& color); @@ -187,6 +370,10 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * @return Reference to self (for method chaining) * * Initial value is @cpp 0xffffffff_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref VectorMaterialUniform::color and call @ref bindMaterialBuffer() + * instead. * @see @ref setBackgroundColor() */ VectorGL& setColor(const Color4& color); @@ -195,11 +382,161 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab * @} */ - #ifndef DOXYGEN_GENERATING_OUTPUT + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref VectorDrawUniform and @ref TextureTransformationUniform buffers + * bound with @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * should be used for current draw. Expects that + * @ref Flag::UniformBuffers is set and @p offset is less than + * @ref drawCount(). Initial value is @cpp 0 @ce, if @ref drawCount() + * is @cpp 1 @ce, the function is a no-op as the shader assumes draw + * offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref VectorDrawUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref VectorMaterialUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + + /** @{ + * @name Texture binding + */ + + /** + * @brief Bind vector texture + * @return Reference to self (for method chaining) + * + * @see @ref Flag::TextureTransformation, @ref setTextureMatrix()s + */ + VectorGL& bindVectorTexture(GL::Texture2D& texture); + + /** + * @} + */ + /* Overloads to remove WTF-factor from method chaining order */ - VectorGL& bindVectorTexture(GL::Texture2D& texture) { - AbstractVectorGL::bindVectorTexture(texture); - return *this; + #ifndef DOXYGEN_GENERATING_OUTPUT + VectorGL& draw(GL::Mesh& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VectorGL& draw(GL::Mesh&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VectorGL& draw(GL::MeshView& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VectorGL& draw(GL::MeshView&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VectorGL& draw(Containers::ArrayView> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + VectorGL& draw(std::initializer_list> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); } #endif @@ -213,10 +550,18 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public Ab #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, _backgroundColorUniform{2}, _colorUniform{3}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** diff --git a/src/Magnum/Shaders/VertexColor.vert b/src/Magnum/Shaders/VertexColor.vert index fda6574e3..9c16869b6 100644 --- a/src/Magnum/Shaders/VertexColor.vert +++ b/src/Magnum/Shaders/VertexColor.vert @@ -23,13 +23,26 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + #ifndef NEW_GLSL #define in attribute #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -49,6 +62,41 @@ uniform highp mat4 transformationProjectionMatrix #error #endif +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -72,6 +120,28 @@ in lowp vec4 color; out lowp vec4 interpolatedColor; void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + highp uint drawId = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = mat3(transformationProjectionMatrices[drawId]); + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationProjectionMatrix = transformationProjectionMatrices[drawId]; + #else + #error + #endif + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 66ef23fc1..5a7c5fb9e 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -25,6 +25,7 @@ #include "VertexColorGL.h" +#include #include #include @@ -35,11 +36,58 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { -template VertexColorGL::VertexColorGL() { +namespace { + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1 + }; + #endif +} + +template VertexColorGL::VertexColorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::VertexColorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -58,8 +106,17 @@ template VertexColorGL::VertexColorGL() { GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); - vert.addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) + vert.addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + vert.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("VertexColor.vert")); frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("VertexColor.frag")); @@ -85,21 +142,108 @@ template VertexColorGL::VertexColorGL() { if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + } } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers + #ifndef MAGNUM_TARGET_GLES + && !context.isExtensionSupported(version) + #endif + ) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + } + #endif + /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{flags, 1} {} +#endif + template VertexColorGL& VertexColorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VertexColorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template VertexColorGL& VertexColorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::VertexColorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +template VertexColorGL& VertexColorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template VertexColorGL& VertexColorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT VertexColorGL<2>; template class MAGNUM_SHADERS_EXPORT VertexColorGL<3>; +namespace Implementation { + +Debug& operator<<(Debug& debug, const VertexColorGLFlag value) { + debug << "Shaders::VertexColorGL::Flag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case VertexColorGLFlag::v: return debug << "::" #v; + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + _c(MultiDraw) + #endif + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const VertexColorGLFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Shaders::VertexColorGL::Flags{}", { + #ifndef MAGNUM_TARGET_GLES2 + VertexColorGLFlag::MultiDraw, /* Superset of UniformBuffers */ + VertexColorGLFlag::UniformBuffers + #endif + }); +} + +} + }} diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 513312fba..73b23dfa2 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -37,6 +37,16 @@ namespace Magnum { namespace Shaders { +namespace Implementation { + enum class VertexColorGLFlag: UnsignedByte { + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 0, + MultiDraw = UniformBuffers|(1 << 1) + #endif + }; + typedef Containers::EnumSet VertexColorGLFlags; +} + /** @brief Vertex color OpenGL shader @m_since_latest @@ -69,6 +79,33 @@ Common rendering setup: @snippet MagnumShaders-gl.cpp VertexColorGL-usage2 +@section Shaders-VertexColorGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, because the shader doesn't need a separate +projection and transformation matrix, a combined one is supplied via a +@ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D +buffer. This is also the only buffer supplied, as there are no other draw +parameters. A uniform buffer setup equivalent to the above would look like +this: + +@snippet MagnumShaders-gl.cpp VectorGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw and supply desired draw +count in the @ref VertexColorGL(Flags, UnsignedInt) constructor. The usage is +similar for all shaders, see @ref shaders-usage-multidraw for an example. + +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. +@requires_webgl20 Uniform buffers are not available in WebGL 1.0. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + @see @ref shaders, @ref VertexColorGL2D, @ref VertexColorGL3D */ template class MAGNUM_SHADERS_EXPORT VertexColorGL: public GL::AbstractShaderProgram { @@ -107,7 +144,109 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ ColorOutput = GenericGL::ColorOutput }; - explicit VertexColorGL(); + #ifdef DOXYGEN_GENERATING_OUTPUT + /** + * @brief Flag + * @m_since{2020,06} + * + * @see @ref Flags, @ref flags() + */ + enum class Flag: UnsignedByte { + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer() instead of direct + * uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 0, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up per-draw parameters directly, without having to rebind + * the uniform buffers or specify @ref setDrawOffset() before each + * draw. In a non-multidraw scenario, @glsl gl_DrawID @ce is + * @cpp 0 @ce, which means a shader with this flag enabled can be + * used for regular draws as well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + * @m_since_latest + */ + MultiDraw = UniformBuffers|(1 << 1) + #endif + }; + + /** + * @brief Flags + * @m_since{2020,06} + * + * @see @ref flags() + */ + typedef Containers::EnumSet Flags; + #else + /* Done this way to be prepared for possible future diversion of 2D + and 3D flags (e.g. introducing 3D-specific features) */ + typedef Implementation::VertexColorGLFlag Flag; + typedef Implementation::VertexColorGLFlags Flags; + #endif + + /** + * @brief Constructor + * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref VertexColorGL(Flags, UnsignedInt) with @p drawCount set to + * @cpp 1 @ce. + */ + explicit VertexColorGL(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D buffer bound with + * @ref bindTransformationProjectionBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p drawCount + * describes the uniform buffer sizes as these are required to have a + * statically defined size. The draw offset is then set via + * @ref setDrawOffset(). + * + * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is + * ignored and the constructor behaves the same as + * @ref VertexColorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit VertexColorGL(Flags flags, UnsignedInt drawCount); + #endif /** * @brief Construct without creating the underlying OpenGL object @@ -135,8 +274,28 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ /** @brief Move assignment */ VertexColorGL& operator=(VertexColorGL&&) noexcept = default; + /** @brief Flags */ + Flags flags() const { return _flags; } + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D uniform buffers. Has use only + * if @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -144,6 +303,11 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @return Reference to self (for method chaining) * * Default is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ VertexColorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -151,6 +315,83 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D buffers bound with + * @ref bindTransformationProjectionBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce, if + * @ref drawCount() is @cpp 1 @ce, the function is a no-op as the + * shader assumes draw offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>) + * pick up its own per-draw parameters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VertexColorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VertexColorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VertexColorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + + /* Overloads to remove WTF-factor from method chaining order */ + #ifndef DOXYGEN_GENERATING_OUTPUT + VertexColorGL& draw(GL::Mesh& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VertexColorGL& draw(GL::Mesh&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VertexColorGL& draw(GL::MeshView& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VertexColorGL& draw(GL::MeshView&& mesh) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); + } + VertexColorGL& draw(Containers::ArrayView> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + VertexColorGL& draw(std::initializer_list> meshes) { + return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); + } + #endif + private: /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -160,7 +401,16 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ using GL::AbstractShaderProgram::dispatchCompute; #endif + Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** @@ -175,6 +425,20 @@ typedef VertexColorGL<2> VertexColorGL2D; */ typedef VertexColorGL<3> VertexColorGL3D; +#ifdef DOXYGEN_GENERATING_OUTPUT +/** @debugoperatorclassenum{VertexColorGL,VertexColorGL::Flag} */ +template Debug& operator<<(Debug& debug, VertexColorGL::Flag value); + +/** @debugoperatorclassenum{VertexColorGL,VertexColorGL::Flags} */ +template Debug& operator<<(Debug& debug, VertexColorGL::Flags value); +#else +namespace Implementation { + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, VertexColorGLFlag value); + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, VertexColorGLFlags value); + CORRADE_ENUMSET_OPERATORS(VertexColorGLFlags) +} +#endif + }} #endif diff --git a/src/Magnum/Shaders/compatibility.glsl b/src/Magnum/Shaders/compatibility.glsl index fa09c4b59..8c92c6859 100644 --- a/src/Magnum/Shaders/compatibility.glsl +++ b/src/Magnum/Shaders/compatibility.glsl @@ -35,7 +35,7 @@ #if !defined(GL_ES) && defined(GL_ARB_shading_language_420pack) && !defined(DISABLE_GL_ARB_shading_language_420pack) #extension GL_ARB_shading_language_420pack: enable #define RUNTIME_CONST - #define EXPLICIT_TEXTURE_LAYER + #define EXPLICIT_BINDING #endif #if !defined(GL_ES) && defined(GL_ARB_explicit_uniform_location) && !defined(DISABLE_GL_ARB_explicit_uniform_location) @@ -45,7 +45,7 @@ #if defined(GL_ES) && __VERSION__ >= 300 #define EXPLICIT_ATTRIB_LOCATION - /* EXPLICIT_TEXTURE_LAYER, EXPLICIT_UNIFORM_LOCATION and RUNTIME_CONST is not + /* EXPLICIT_BINDING, EXPLICIT_UNIFORM_LOCATION and RUNTIME_CONST is not available in OpenGL ES */ #endif diff --git a/src/Magnum/Shaders/generic.glsl b/src/Magnum/Shaders/generic.glsl index 985220296..5acb24f38 100644 --- a/src/Magnum/Shaders/generic.glsl +++ b/src/Magnum/Shaders/generic.glsl @@ -35,7 +35,7 @@ #define TRANSFORMATION_MATRIX_ATTRIBUTE_LOCATION 8 #define NORMAL_MATRIX_ATTRIBUTE_LOCATION 12 -#define TEXTURE_OFFSET_ATTRIBUTE_LOCATION 15 +#define TEXTURE_OFFSET_ATTRIBUTE_LOCATION 15 /* + layer in the 3rd component */ /* Outputs */ #define COLOR_OUTPUT_ATTRIBUTE_LOCATION 0 diff --git a/src/Magnum/Shaders/resources-gl.conf b/src/Magnum/Shaders/resources-gl.conf index f545425c9..57a1c346e 100644 --- a/src/Magnum/Shaders/resources-gl.conf +++ b/src/Magnum/Shaders/resources-gl.conf @@ -1,8 +1,5 @@ group=MagnumShadersGL -[file] -filename=AbstractVector.vert - [file] filename=Flat.vert @@ -30,6 +27,9 @@ filename=Phong.vert [file] filename=Phong.frag +[file] +filename=Vector.vert + [file] filename=Vector.frag diff --git a/src/Magnum/Text/Renderer.cpp b/src/Magnum/Text/Renderer.cpp index 088561041..c0b059f9c 100644 --- a/src/Magnum/Text/Renderer.cpp +++ b/src/Magnum/Text/Renderer.cpp @@ -33,7 +33,7 @@ #include "Magnum/GL/Extensions.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Functions.h" -#include "Magnum/Shaders/AbstractVectorGL.h" +#include "Magnum/Shaders/GenericGL.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/GlyphCache.h" @@ -72,7 +72,7 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo std::vector vertices; vertices.reserve(text.size()*4); - /* Total rendered bounds, intial line position, line increment, last+1 + /* Total rendered bounds, initial line position, line increment, last+1 vertex on previous line */ Range2D rectangle; Vector2 linePosition; @@ -255,9 +255,9 @@ template std::tuple Renderer(r); mesh.addVertexBuffer(vertexBuffer, 0, - typename Shaders::AbstractVectorGL::Position( - Shaders::AbstractVectorGL::Position::Components::Two), - typename Shaders::AbstractVectorGL::TextureCoordinates()); + typename Shaders::GenericGL::Position( + Shaders::GenericGL::Position::Components::Two), + typename Shaders::GenericGL::TextureCoordinates()); return r; } @@ -320,8 +320,9 @@ AbstractRenderer::~AbstractRenderer() = default; template Renderer::Renderer(AbstractFont& font, const GlyphCache& cache, const Float size, const Alignment alignment): AbstractRenderer(font, cache, size, alignment) { /* Finalize mesh configuration */ _mesh.addVertexBuffer(_vertexBuffer, 0, - typename Shaders::AbstractVectorGL::Position(Shaders::AbstractVectorGL::Position::Components::Two), - typename Shaders::AbstractVectorGL::TextureCoordinates()); + typename Shaders::GenericGL::Position( + Shaders::GenericGL::Position::Components::Two), + typename Shaders::GenericGL::TextureCoordinates()); } void AbstractRenderer::reserve(const uint32_t glyphCount, const GL::BufferUsage vertexBufferUsage, const GL::BufferUsage indexBufferUsage) { diff --git a/src/Magnum/Text/Renderer.h b/src/Magnum/Text/Renderer.h index eb9edfdf9..8640ea982 100644 --- a/src/Magnum/Text/Renderer.h +++ b/src/Magnum/Text/Renderer.h @@ -202,7 +202,7 @@ asynchronous buffer updates. There is no similar extension in WebGL, thus plain (and slow) buffer updates are used there. @see @ref Renderer2D, @ref Renderer3D, @ref AbstractFont, - @ref Shaders::AbstractVectorGL + @ref Shaders::VectorGL, @ref Shaders::DistanceFieldVectorGL */ template class MAGNUM_TEXT_EXPORT Renderer: public AbstractRenderer { public: @@ -217,8 +217,9 @@ template class MAGNUM_TEXT_EXPORT Renderer: public Abstr * @param usage Usage of vertex and index buffer * @param alignment Text alignment * - * Returns mesh prepared for use with @ref Shaders::AbstractVectorGL - * subclasses and rectangle spanning the rendered text. + * Returns mesh prepared for use with @ref Shaders::VectorGL or + * @ref Shaders::DistanceFieldVectorGL and rectangle spanning the + * rendered text. */ static std::tuple render(AbstractFont& font, const GlyphCache& cache, Float size, const std::string& text, GL::Buffer& vertexBuffer, GL::Buffer& indexBuffer, GL::BufferUsage usage, Alignment alignment = Alignment::LineLeft); diff --git a/src/Magnum/TextureTools/DistanceFieldShader.frag b/src/Magnum/TextureTools/DistanceFieldShader.frag index 7e9cfe3ae..f5e44fcfc 100644 --- a/src/Magnum/TextureTools/DistanceFieldShader.frag +++ b/src/Magnum/TextureTools/DistanceFieldShader.frag @@ -42,7 +42,7 @@ layout(location = 0) #endif uniform mediump vec2 scaling; -#ifdef EXPLICIT_TEXTURE_LAYER +#ifdef EXPLICIT_BINDING layout(binding = 7) #endif uniform lowp sampler2D textureData; diff --git a/src/Magnum/TextureTools/Test/CMakeLists.txt b/src/Magnum/TextureTools/Test/CMakeLists.txt index a3e67a301..cbabb8193 100644 --- a/src/Magnum/TextureTools/Test/CMakeLists.txt +++ b/src/Magnum/TextureTools/Test/CMakeLists.txt @@ -77,5 +77,13 @@ if(BUILD_GL_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(TextureToolsDistanceFieldGLTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(TextureToolsDistanceFieldGLTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(TextureToolsDistanceFieldGLTest TgaImporter) + endif() endif() endif() diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index e44adc466..0e4ad45a2 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -870,6 +870,82 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ bool convertToFile(const ImageData3D& image, Containers::StringView filename); + protected: + /** + * @brief Implementation for @ref convertToFile(const ImageView1D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::Convert1DToData is supported, default + * implementation calls @ref doConvertToData(const ImageView1D&) and + * saves the result to given file. It is allowed to call this function + * from your @ref doConvertToFile() implementation, for example when + * you only need to do format detection based on file extension. + */ + virtual bool doConvertToFile(const ImageView1D& image, Containers::StringView filename); + + /** + * @brief Implementation for @ref convertToFile(const ImageView2D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::Convert2DToData is supported, default + * implementation calls @ref doConvertToData(const ImageView2D&) and + * saves the result to given file. It is allowed to call this function + * from your @ref doConvertToFile() implementation, for example when + * you only need to do format detection based on file extension. + */ + virtual bool doConvertToFile(const ImageView2D& image, Containers::StringView filename); + + /** + * @brief Implementation for @ref convertToFile(const ImageView3D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::Convert3DToData is supported, default + * implementation calls @ref doConvertToData(const ImageView3D&) and + * saves the result to given file. It is allowed to call this function + * from your @ref doConvertToFile() implementation, for example when + * you only need to do format detection based on file extension. + */ + virtual bool doConvertToFile(const ImageView3D& image, Containers::StringView filename); + + /** + * @brief Implementation for @ref convertToFile(const CompressedImageView1D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::ConvertCompressed1DToData is + * supported, default implementation calls @ref doConvertToData(const CompressedImageView1D&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + */ + virtual bool doConvertToFile(const CompressedImageView1D& image, Containers::StringView filename); + + /** + * @brief Implementation for @ref convertToFile(const CompressedImageView2D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::ConvertCompressed2DToData is + * supported, default implementation calls @ref doConvertToData(const CompressedImageView2D&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + */ + virtual bool doConvertToFile(const CompressedImageView2D& image, Containers::StringView filename); + + /** + * @brief Implementation for @ref convertToFile(const CompressedImageView3D&, Containers::StringView) + * @m_since_latest + * + * If @ref ImageConverterFeature::ConvertCompressed3DToData is + * supported, default implementation calls @ref doConvertToData(const CompressedImageView3D&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + */ + virtual bool doConvertToFile(const CompressedImageView3D& image, Containers::StringView filename); + private: /** @brief Implementation for @ref features() */ virtual ImageConverterFeatures doFeatures() const = 0; @@ -962,66 +1038,6 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ virtual Containers::Array doConvertToData(const CompressedImageView3D& image); - /** - * @brief Implementation for @ref convertToFile(const ImageView1D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::Convert1DToData is supported, default - * implementation calls @ref doConvertToData(const ImageView1D&) and - * saves the result to given file. - */ - virtual bool doConvertToFile(const ImageView1D& image, Containers::StringView filename); - - /** - * @brief Implementation for @ref convertToFile(const ImageView2D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::Convert2DToData is supported, default - * implementation calls @ref doConvertToData(const ImageView2D&) and - * saves the result to given file. - */ - virtual bool doConvertToFile(const ImageView2D& image, Containers::StringView filename); - - /** - * @brief Implementation for @ref convertToFile(const ImageView3D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::Convert3DToData is supported, default - * implementation calls @ref doConvertToData(const ImageView3D&) and - * saves the result to given file. - */ - virtual bool doConvertToFile(const ImageView3D& image, Containers::StringView filename); - - /** - * @brief Implementation for @ref convertToFile(const CompressedImageView1D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::ConvertCompressed1DToData is - * supported, default implementation calls @ref doConvertToData(const CompressedImageView1D&) - * and saves the result to given file. - */ - virtual bool doConvertToFile(const CompressedImageView1D& image, Containers::StringView filename); - - /** - * @brief Implementation for @ref convertToFile(const CompressedImageView2D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::ConvertCompressed2DToData is - * supported, default implementation calls @ref doConvertToData(const CompressedImageView2D&) - * and saves the result to given file. - */ - virtual bool doConvertToFile(const CompressedImageView2D& image, Containers::StringView filename); - - /** - * @brief Implementation for @ref convertToFile(const CompressedImageView3D&, Containers::StringView) - * @m_since_latest - * - * If @ref ImageConverterFeature::ConvertCompressed3DToData is - * supported, default implementation calls @ref doConvertToData(const CompressedImageView3D&) - * and saves the result to given file. - */ - virtual bool doConvertToFile(const CompressedImageView3D& image, Containers::StringView filename); - ImageConverterFlags _flags; }; diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 720c40b3e..310143e79 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -283,7 +283,7 @@ properly use the callbacks to both load the top-level file in @ref doOpenFile() and also load any external files when needed. The @ref doOpenFile() can delegate back into the base implementation, but it should remember at least the base file path to pass correct paths to subsequent file callbacks. The -@ref doSetFileCallback() can be overriden in case it's desired to respond to +@ref doSetFileCallback() can be overridden in case it's desired to respond to file loading callback setup, but doesn't have to be. For multi-data formats the file opening shouldn't take long and all parsing @@ -1526,7 +1526,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doScene() (with correct scene count + * preferably during @ref doScene() (with correct scene count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doSceneCount() const; @@ -1553,7 +1553,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doAnimation() (with correct animation count + * preferably during @ref doAnimation() (with correct animation count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doAnimationCount() const; @@ -1580,7 +1580,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doLight() (with correct light count + * preferably during @ref doLight() (with correct light count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doLightCount() const; @@ -1607,7 +1607,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doCamera() (with correct camera count + * preferably during @ref doCamera() (with correct camera count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doCameraCount() const; @@ -1634,7 +1634,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doObject2D() (with correct object count + * preferably during @ref doObject2D() (with correct object count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doObject2DCount() const; @@ -1661,7 +1661,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doObject3D() (with correct object count + * preferably during @ref doObject3D() (with correct object count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doObject3DCount() const; @@ -1689,7 +1689,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doSkin2D() (with correct skin count + * preferably during @ref doSkin2D() (with correct skin count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doSkin2DCount() const; @@ -1722,7 +1722,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doSkin3D() (with correct skin count + * preferably during @ref doSkin3D() (with correct skin count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doSkin3DCount() const; @@ -1755,7 +1755,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doMesh() (with correct mesh count + * preferably during @ref doMesh() (with correct mesh count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doMeshCount() const; @@ -1931,7 +1931,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doMaterial() (with correct material count + * preferably during @ref doMaterial() (with correct material count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doMaterialCount() const; @@ -1958,7 +1958,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doTexture() (with correct texture count + * preferably during @ref doTexture() (with correct texture count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doTextureCount() const; @@ -1985,7 +1985,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doImage1D() (with correct image count + * preferably during @ref doImage1D() (with correct image count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doImage1DCount() const; @@ -2021,7 +2021,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doImage2D() (with correct image count + * preferably during @ref doImage2D() (with correct image count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doImage2DCount() const; @@ -2065,7 +2065,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Default implementation returns @cpp 0 @ce. This function isn't * expected to fail --- if an import error occus, it should be handled - * preferrably during @ref doImage3D() (with correct image count + * preferably during @ref doImage3D() (with correct image count * reported), and if not possible, already during file opening. */ virtual UnsignedInt doImage3DCount() const; diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index b5587f4af..c1f13c7f7 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -318,6 +318,19 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract CORRADE_DEPRECATED("use convertToFile(const MeshData&, Containers::StringView) instead") bool convertToFile(const std::string& filename, const MeshData& mesh); #endif + protected: + /** + * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) + * + * If @ref SceneConverterFeature::ConvertMeshToData is supported, + * default implementation calls @ref doConvertToData(const MeshData&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + */ + virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + private: /** * @brief Implementation for @ref features() @@ -350,15 +363,6 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** @brief Implementation for @ref convertToData(const MeshData&) */ virtual Containers::Array doConvertToData(const MeshData& mesh); - /** - * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) - * - * If @ref SceneConverterFeature::ConvertMeshToData is supported, - * default implementation calls @ref doConvertToData(const MeshData&) - * and saves the result to given file. - */ - virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); - SceneConverterFlags _flags; }; diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index a83083fb9..6b84034ae 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -447,7 +447,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * used in most @ref MeshTools algorithms. * * Additionally, for even more flexibility, the @p vertexCount can be - * overriden at @ref MeshData construction time, however all attributes + * overridden at @ref MeshData construction time, however all attributes * are still required to have the same vertex count to catch accidents. * * Note that due to the @cpp constexpr @ce nature of this constructor, diff --git a/src/Magnum/Trade/PbrClearCoatMaterialData.h b/src/Magnum/Trade/PbrClearCoatMaterialData.h index b8e2039f5..77f71a168 100644 --- a/src/Magnum/Trade/PbrClearCoatMaterialData.h +++ b/src/Magnum/Trade/PbrClearCoatMaterialData.h @@ -72,7 +72,7 @@ class MAGNUM_TRADE_EXPORT PbrClearCoatMaterialData: public MaterialLayerData data; if(flags & Flag::DoubleSided) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DoubleSided, true); + arrayAppend(data, InPlaceInit, MaterialAttribute::DoubleSided, true); if(alphaMode == MaterialAlphaMode::Blend) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AlphaBlend, true); + arrayAppend(data, InPlaceInit, MaterialAttribute::AlphaBlend, true); /* Include a mask also if it has a non-default value to stay compatible with existing behavior */ if(alphaMode == MaterialAlphaMode::Mask || alphaMask != 0.0f) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AlphaMask, alphaMask); + arrayAppend(data, InPlaceInit, MaterialAttribute::AlphaMask, alphaMask); CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)), "Trade::PhongMaterialData: texture transformation enabled but the material has no textures", data); @@ -59,37 +59,37 @@ PhongMaterialData::PhongMaterialData(const Flags flags, const Color4& ambientCol CORRADE_ASSERT((flags & Flag::TextureCoordinateSets) || (ambientTextureCoordinates == 0 && diffuseTextureCoordinates == 0 && specularTextureCoordinates == 0 && normalTextureCoordinates == 0), "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinates to be enabled", data); - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientColor, ambientColor); + arrayAppend(data, InPlaceInit, MaterialAttribute::AmbientColor, ambientColor); if(flags & Flag::AmbientTexture) { - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientTexture, ambientTexture); + arrayAppend(data, InPlaceInit, MaterialAttribute::AmbientTexture, ambientTexture); if(ambientTextureCoordinates) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientTextureCoordinates, ambientTextureCoordinates); + arrayAppend(data, InPlaceInit, MaterialAttribute::AmbientTextureCoordinates, ambientTextureCoordinates); } - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseColor, diffuseColor); + arrayAppend(data, InPlaceInit, MaterialAttribute::DiffuseColor, diffuseColor); if(flags & Flag::DiffuseTexture) { - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseTexture, diffuseTexture); + arrayAppend(data, InPlaceInit, MaterialAttribute::DiffuseTexture, diffuseTexture); if(diffuseTextureCoordinates) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseTextureCoordinates, diffuseTextureCoordinates); + arrayAppend(data, InPlaceInit, MaterialAttribute::DiffuseTextureCoordinates, diffuseTextureCoordinates); } - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularColor, specularColor); + arrayAppend(data, InPlaceInit, MaterialAttribute::SpecularColor, specularColor); if(flags & Flag::SpecularTexture) { - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularTexture, specularTexture); + arrayAppend(data, InPlaceInit, MaterialAttribute::SpecularTexture, specularTexture); if(specularTextureCoordinates) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularTextureCoordinates, specularTextureCoordinates); + arrayAppend(data, InPlaceInit, MaterialAttribute::SpecularTextureCoordinates, specularTextureCoordinates); } if(flags & Flag::NormalTexture) { - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::NormalTexture, normalTexture); + arrayAppend(data, InPlaceInit, MaterialAttribute::NormalTexture, normalTexture); if(normalTextureCoordinates) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::NormalTextureCoordinates, normalTextureCoordinates); + arrayAppend(data, InPlaceInit, MaterialAttribute::NormalTextureCoordinates, normalTextureCoordinates); } if(flags & Flag::TextureTransformation) - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::TextureMatrix, textureMatrix); + arrayAppend(data, InPlaceInit, MaterialAttribute::TextureMatrix, textureMatrix); - arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::Shininess, shininess); + arrayAppend(data, InPlaceInit, MaterialAttribute::Shininess, shininess); /* Convert back to a non-growable Array as importers don't allow custom deleters in plugin implementations */ diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 4f7deecb2..ce9bbfa0f 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -1552,8 +1552,8 @@ void MeshDataTest::constructAttributelessNotOwned() { } void MeshDataTest::constructIndexlessAttributeless() { - int importerState; - MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + MeshData data{MeshPrimitive::TriangleStrip, 37, &state}; /* These are both empty so it doesn't matter, but this is a nice non-restrictive default */ CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -1562,7 +1562,7 @@ void MeshDataTest::constructIndexlessAttributeless() { CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); - CORRADE_COMPARE(data.importerState(), &importerState); + CORRADE_COMPARE(data.importerState(), &state); CORRADE_VERIFY(!data.isIndexed()); CORRADE_COMPARE(data.vertexCount(), 37); @@ -1570,13 +1570,13 @@ void MeshDataTest::constructIndexlessAttributeless() { } void MeshDataTest::constructIndexlessAttributelessZeroVertices() { - int importerState; - MeshData data{MeshPrimitive::TriangleStrip, 0, &importerState}; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ + MeshData data{MeshPrimitive::TriangleStrip, 0, &state}; CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); - CORRADE_COMPARE(data.importerState(), &importerState); + CORRADE_COMPARE(data.importerState(), &state); CORRADE_VERIFY(!data.isIndexed()); CORRADE_COMPARE(data.vertexCount(), 0); diff --git a/src/Magnum/Trade/Test/SkinDataTest.cpp b/src/Magnum/Trade/Test/SkinDataTest.cpp index f7deb7780..35fed740a 100644 --- a/src/Magnum/Trade/Test/SkinDataTest.cpp +++ b/src/Magnum/Trade/Test/SkinDataTest.cpp @@ -56,7 +56,7 @@ SkinDataTest::SkinDataTest() { } void SkinDataTest::construct() { - int state; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ SkinData3D data{{0, 2, 3}, { Matrix4::translation(Vector3::zAxis(0.0f)), Matrix4::translation(Vector3::zAxis(2.0f)), @@ -102,7 +102,7 @@ void SkinDataTest::constructCopy() { } void SkinDataTest::constructMove() { - int state; + int state{}; /* GCC 11 complains that "maybe uninitialized" w/o the {} */ SkinData3D a{{0, 2, 3}, { Matrix4::translation(Vector3::zAxis(0.0f)), Matrix4::translation(Vector3::zAxis(2.0f)), diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index 17a304e5a..b261efd50 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -245,7 +245,7 @@ key=true; configuration subgroups are delimited with /.)") /* Set options, if passed */ if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); - Implementation::setOptions(*importer, args.value("importer-options")); + Implementation::setOptions(*importer, "AnyImageImporter", args.value("importer-options")); /* Print image info, if requested */ if(args.isSet("info")) { @@ -329,7 +329,7 @@ key=true; configuration subgroups are delimited with /.)") /* Set options, if passed */ if(args.isSet("verbose")) converter->addFlags(Trade::ImageConverterFlag::Verbose); - Implementation::setOptions(*converter, args.value("converter-options")); + Implementation::setOptions(*converter, "AnyImageConverter", args.value("converter-options")); /* Save output file */ if(!converter->convertToFile(*image, output)) { diff --git a/src/Magnum/Vk/Device.cpp b/src/Magnum/Vk/Device.cpp index 245982a93..5c4357e4a 100644 --- a/src/Magnum/Vk/Device.cpp +++ b/src/Magnum/Vk/Device.cpp @@ -141,7 +141,7 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext we don't need to bother with String allocations. */ Containers::StringView disabledWorkarounds = args.value("disable-workarounds"); if(!disabledWorkarounds.isEmpty()) { - const Containers::Array split = disabledWorkarounds.splitWithoutEmptyParts(); + const Containers::Array split = disabledWorkarounds.splitOnWhitespaceWithoutEmptyParts(); arrayReserve(_state->encounteredWorkarounds, split.size()); for(const Containers::StringView workaround: split) Implementation::disableWorkaround(_state->encounteredWorkarounds, workaround); @@ -152,7 +152,7 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext Containers::String disabledExtensions = args.value("disable-extensions"); if(!disabledExtensions.isEmpty()) { _state->disabledExtensionsStorage = std::move(disabledExtensions); - _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitWithoutEmptyParts(); + _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitOnWhitespaceWithoutEmptyParts(); std::sort(_state->disabledExtensions.begin(), _state->disabledExtensions.end()); } @@ -164,7 +164,7 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext allocation-free, the strings will be turned into owning copies because none of them is null-terminated or global -- could be a better idea to just grow one giant string internally (once we have growable strings) */ - addEnabledExtensions(args.value("enable-extensions").splitWithoutEmptyParts()); + addEnabledExtensions(args.value("enable-extensions").splitOnWhitespaceWithoutEmptyParts()); /* Enable implicit extensions unless that's forbidden */ /** @todo move this somewhere else as this will grow significantly? */ diff --git a/src/Magnum/Vk/Device.h b/src/Magnum/Vk/Device.h index 8289e8621..b0e93aec3 100644 --- a/src/Magnum/Vk/Device.h +++ b/src/Magnum/Vk/Device.h @@ -401,7 +401,7 @@ class MAGNUM_VK_EXPORT Device { /** * @brief Version supported by the device * - * Unless overriden using `--magnum-vulkan-version` on the + * Unless overridden using `--magnum-vulkan-version` on the * @ref Vk-Device-command-line "command line", corresponds to * @ref DeviceProperties::version(). */ diff --git a/src/Magnum/Vk/Instance.cpp b/src/Magnum/Vk/Instance.cpp index d89ad4a36..b1202d39e 100644 --- a/src/Magnum/Vk/Instance.cpp +++ b/src/Magnum/Vk/Instance.cpp @@ -104,14 +104,14 @@ InstanceCreateInfo::InstanceCreateInfo(const Int argc, const char** const argv, if(!_state) _state.emplace(); _state->disabledLayersStorage = std::move(disabledLayers); - _state->disabledLayers = Containers::StringView{_state->disabledLayersStorage}.splitWithoutEmptyParts(); + _state->disabledLayers = Containers::StringView{_state->disabledLayersStorage}.splitOnWhitespaceWithoutEmptyParts(); std::sort(_state->disabledLayers.begin(), _state->disabledLayers.end()); } if(!disabledExtensions.isEmpty()) { if(!_state) _state.emplace(); _state->disabledExtensionsStorage = std::move(disabledExtensions); - _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitWithoutEmptyParts(); + _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitOnWhitespaceWithoutEmptyParts(); std::sort(_state->disabledExtensions.begin(), _state->disabledExtensions.end()); } @@ -123,8 +123,8 @@ InstanceCreateInfo::InstanceCreateInfo(const Int argc, const char** const argv, allocation-free, the strings will be turned into owning copies because none of them is null-terminated or global -- could be a better idea to just grow one giant string internally (once we have growable strings) */ - addEnabledLayers(args.value("enable-layers").splitWithoutEmptyParts()); - addEnabledExtensions(args.value("enable-instance-extensions").splitWithoutEmptyParts()); + addEnabledLayers(args.value("enable-layers").splitOnWhitespaceWithoutEmptyParts()); + addEnabledExtensions(args.value("enable-instance-extensions").splitOnWhitespaceWithoutEmptyParts()); /** @todo use this (enabling debug layers etc.) */ static_cast(layerProperties); diff --git a/src/Magnum/Vk/Instance.h b/src/Magnum/Vk/Instance.h index dbbf3b0b8..c9b524d38 100644 --- a/src/Magnum/Vk/Instance.h +++ b/src/Magnum/Vk/Instance.h @@ -348,7 +348,7 @@ class MAGNUM_VK_EXPORT Instance { /** * @brief Version supported by the instance * - * Unless overriden using `--magnum-vulkan-version` on the + * Unless overridden using `--magnum-vulkan-version` on the * @ref Vk-Device-command-line "command line", corresponds to * @ref enumerateInstanceVersion(). */ diff --git a/src/Magnum/Vk/MeshLayout.h b/src/Magnum/Vk/MeshLayout.h index 99a4169bb..73b21bd3f 100644 --- a/src/Magnum/Vk/MeshLayout.h +++ b/src/Magnum/Vk/MeshLayout.h @@ -40,7 +40,7 @@ namespace Magnum { namespace Vk { /* About naming -- I wonder why Vulkan tries *so hard* to avoid naming anything - a "mesh". It would so nicely group things togehter BUT NO, there's primitive + a "mesh". It would so nicely group things together BUT NO, there's primitive topology, and vertex input state, and input assembly and ugh. */ /** diff --git a/src/Magnum/Vk/ShaderSet.cpp b/src/Magnum/Vk/ShaderSet.cpp index 96f7c5c79..a4f0fb5c6 100644 --- a/src/Magnum/Vk/ShaderSet.cpp +++ b/src/Magnum/Vk/ShaderSet.cpp @@ -86,7 +86,7 @@ ShaderSet& ShaderSet::addShader(const ShaderStage stage, const VkShaderModule sh } /* Specialization, also only if there are any to avoid allocating the state - struct when not neccessary */ + struct when not necessary */ if(!specializations.empty()) { if(!_state) _state.emplace(); diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 2c21967f2..4ea7d965f 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -236,6 +236,14 @@ if(BUILD_VK_TESTS) if(WITH_TGAIMPORTER) target_link_libraries(VkMeshVkTest PRIVATE TgaImporter) endif() + else() + # So the plugins get properly built when building the test + if(WITH_ANYIMAGEIMPORTER) + add_dependencies(VkMeshVkTest AnyImageImporter) + endif() + if(WITH_TGAIMPORTER) + add_dependencies(VkMeshVkTest TgaImporter) + endif() endif() corrade_add_test(VkPipelineVkTest PipelineVkTest.cpp diff --git a/src/Magnum/Vk/Test/ExtensionsTest.cpp b/src/Magnum/Vk/Test/ExtensionsTest.cpp index f9b6cb3f5..8c56c4068 100644 --- a/src/Magnum/Vk/Test/ExtensionsTest.cpp +++ b/src/Magnum/Vk/Test/ExtensionsTest.cpp @@ -64,7 +64,7 @@ void ExtensionsTest::isInstanceExtension() { CORRADE_VERIFY(!Implementation::IsInstanceExtension::value); /* Variadic check (used in variadic addEnabledExtensions()), check that it - properly fails for each occurence of a device extension */ + properly fails for each occurrence of a device extension */ CORRADE_VERIFY(Implementation::IsInstanceExtension< Extensions::KHR::get_physical_device_properties2, Extensions::KHR::external_memory_capabilities, @@ -103,7 +103,7 @@ void ExtensionsTest::isExtension() { } /* Variadic check (used in variadic addEnabledExtensions()), check that it - properly fails for each occurence of a device extension */ + properly fails for each occurrence of a device extension */ CORRADE_VERIFY(Implementation::IsExtension< Extensions::KHR::external_memory, Extensions::KHR::depth_stencil_resolve, diff --git a/src/Magnum/Vk/Test/MeshVkTest.cpp b/src/Magnum/Vk/Test/MeshVkTest.cpp index 529cb6abd..8bde055c1 100644 --- a/src/Magnum/Vk/Test/MeshVkTest.cpp +++ b/src/Magnum/Vk/Test/MeshVkTest.cpp @@ -319,7 +319,7 @@ void MeshVkTest::cmdDrawIndexed() { { Buffer buffer{device(), BufferCreateInfo{ BufferUsage::VertexBuffer|BufferUsage::IndexBuffer, - /* Artifical offset at the beginning to test that the offset is + /* Artificial offset at the beginning to test that the offset is used correctly in both cases */ 32 + 12*4 + sizeof(QuadIndexData) }, MemoryFlag::HostVisible}; diff --git a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp index 677c6f3f9..62334ea3d 100644 --- a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.cpp @@ -27,10 +27,13 @@ #include #include +#include #include #include #include +#include "MagnumPlugins/Implementation/propagateConfiguration.h" + namespace Magnum { namespace Audio { AnyImporter::AnyImporter(PluginManager::Manager& manager): AbstractImporter{manager} {} @@ -74,9 +77,16 @@ void AnyImporter::doOpenFile(const std::string& filename) { return; } + /* Instantiate the plugin */ + Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); + + /* Propagate configuration */ + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + Magnum::Implementation::propagateConfiguration("Audio::AnyImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ - Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); if(!importer->openFile(filename)) return; /* Success, save the instance */ diff --git a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h index 25e1ae371..e54d85f7c 100644 --- a/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h +++ b/src/MagnumPlugins/AnyAudioImporter/AnyImporter.h @@ -99,6 +99,18 @@ target_link_libraries(your-app PRIVATE Magnum::AnyAudioImporter) @endcode See @ref building, @ref cmake and @ref plugins for more information. + +@section Audio-AnyImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile(), a file format is detected from the extension and +a corresponding plugin is loaded. After that, options set through +@ref configuration() are propagated to the concrete implementation, with a +warning emitted in case given option is not present in the default +configuration of the target plugin. + +Calls to the @ref format(), @ref frequency() and @ref data() functions are then +proxied to the concrete implementation. The @ref close() function closes and +discards the internally instantiated plugin; @ref isOpened() works as usual. */ class MAGNUM_ANYAUDIOIMPORTER_EXPORT AnyImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp index a898cf772..52a3aac32 100644 --- a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,9 @@ struct AnyImporterTest: TestSuite::Tester { void unknown(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; }; @@ -72,7 +76,10 @@ AnyImporterTest::AnyImporterTest() { addInstancedTests({&AnyImporterTest::detect}, Containers::arraySize(DetectData)); - addTests({&AnyImporterTest::unknown}); + addTests({&AnyImporterTest::unknown, + + &AnyImporterTest::propagateConfiguration, + &AnyImporterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -133,6 +140,23 @@ void AnyImporterTest::unknown() { CORRADE_COMPARE(output.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); } +void AnyImporterTest::propagateConfiguration() { + CORRADE_SKIP("No importer has any configuration options to test."); +} + +void AnyImporterTest::propagateConfigurationUnknown() { + if(!(_manager.loadState("WavAudioImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("WavAudioImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnyAudioImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(WAV_FILE)); + CORRADE_COMPARE(out.str(), "Audio::AnyImporter::openFile(): option noSuchOption not recognized by WavAudioImporter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Audio::Test::AnyImporterTest) diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index c92cbae8a..862086131 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -26,17 +26,20 @@ #include "AnyImageConverter.h" #include -#include /* for Directory */ +#include /* for PluginManager */ #include #include #include -#include +#include /* for PluginMetadata::name() */ #include #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { +using namespace Containers::Literals; + AnyImageConverter::AnyImageConverter(PluginManager::Manager& manager): AbstractImageConverter{manager} {} AnyImageConverter::AnyImageConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImageConverter{manager, plugin} {} @@ -50,30 +53,31 @@ ImageConverterFeatures AnyImageConverter::doFeatures() const { bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containers::StringView filename) { CORRADE_INTERNAL_ASSERT(manager()); - /** @todo lowercase only the extension, once Directory::split() is done */ - const std::string normalized = Utility::String::lowercase(filename); + /** @todo once Directory is std::string-free, use splitExtension(), but + only if we don't detect more than one extension yet */ + const Containers::String normalized = Utility::String::lowercase(filename); /* Detect the plugin from extension */ - std::string plugin; - if(Utility::String::endsWith(normalized, ".bmp")) - plugin = "BmpImageConverter"; - else if(Utility::String::endsWith(normalized, ".basis")) - plugin = "BasisImageConverter"; - else if(Utility::String::endsWith(normalized, ".exr")) - plugin = "OpenExrImageConverter"; - else if(Utility::String::endsWith(normalized, ".hdr")) - plugin = "HdrImageConverter"; - else if(Utility::String::endsWith(normalized, ".jpg") || - Utility::String::endsWith(normalized, ".jpeg") || - Utility::String::endsWith(normalized, ".jpe")) - plugin = "JpegImageConverter"; - else if(Utility::String::endsWith(normalized, ".png")) - plugin = "PngImageConverter"; - else if(Utility::String::endsWith(normalized, ".tga") || - Utility::String::endsWith(normalized, ".vda") || - Utility::String::endsWith(normalized, ".icb") || - Utility::String::endsWith(normalized, ".vst")) - plugin = "TgaImageConverter"; + Containers::StringView plugin; + if(normalized.hasSuffix(".bmp"_s)) + plugin = "BmpImageConverter"_s; + else if(normalized.hasSuffix(".basis"_s)) + plugin = "BasisImageConverter"_s; + else if(normalized.hasSuffix(".exr"_s)) + plugin = "OpenExrImageConverter"_s; + else if(normalized.hasSuffix(".hdr"_s)) + plugin = "HdrImageConverter"_s; + else if(normalized.hasSuffix(".jpg"_s) || + normalized.hasSuffix(".jpeg"_s) || + normalized.hasSuffix(".jpe"_s)) + plugin = "JpegImageConverter"_s; + else if(normalized.hasSuffix(".png"_s)) + plugin = "PngImageConverter"_s; + else if(normalized.hasSuffix(".tga"_s) || + normalized.hasSuffix(".vda"_s) || + normalized.hasSuffix(".icb"_s) || + normalized.hasSuffix( ".vst"_s)) + plugin = "TgaImageConverter"_s; else { Error{} << "Trade::AnyImageConverter::convertToFile(): cannot determine the format of" << filename; return false; @@ -84,11 +88,12 @@ bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containe Error{} << "Trade::AnyImageConverter::convertToFile(): cannot load the" << plugin << "plugin"; return false; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImageConverterFlag::Verbose) { Debug d; d << "Trade::AnyImageConverter::convertToFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -97,6 +102,9 @@ bool AnyImageConverter::doConvertToFile(const ImageView2D& image, const Containe Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); converter->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertToFile(image, filename); diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h index c2133b902..ab3afded3 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.h @@ -104,6 +104,17 @@ target_link_libraries(your-app PRIVATE Magnum::AnyImageConverter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnyImageConverter-proxy Interface proxying and option propagation + +On a call to @ref convertToFile(), a target file format is detected from the +extension and a corresponding plugin is loaded. After that, flags set via +@ref setFlags() and options set through @ref configuration() are propagated to +the concrete implementation, with a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref convertToFile() function called on the concrete +implementation is then proxied back. */ class MAGNUM_ANYIMAGECONVERTER_EXPORT AnyImageConverter: public AbstractImageConverter { public: diff --git a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp index 6bf445a5c..f518a5723 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp +++ b/src/MagnumPlugins/AnyImageConverter/Test/AnyImageConverterTest.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -43,12 +45,22 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct AnyImageConverterTest: TestSuite::Tester { explicit AnyImageConverterTest(); - void convert(); - void detect(); + void convert2D(); + void convertCompressed2D(); + void detect2D(); + void detectCompressed2D(); - void unknown(); + void unknown2D(); + void unknownCompressed2D(); - void verbose(); + void propagateFlags2D(); + void propagateFlagsCompressed2D(); + void propagateConfiguration2D(); + void propagateConfigurationUnknown2D(); + void propagateConfigurationCompressed2D(); + void propagateConfigurationCompressedUnknown2D(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -76,15 +88,25 @@ constexpr struct { }; AnyImageConverterTest::AnyImageConverterTest() { - addInstancedTests({&AnyImageConverterTest::convert}, + addInstancedTests({&AnyImageConverterTest::convert2D}, Containers::arraySize(ConvertData)); - addInstancedTests({&AnyImageConverterTest::detect}, + addTests({&AnyImageConverterTest::convertCompressed2D}); + + addInstancedTests({&AnyImageConverterTest::detect2D}, Containers::arraySize(DetectData)); - addTests({&AnyImageConverterTest::unknown, + addTests({&AnyImageConverterTest::detectCompressed2D, + + &AnyImageConverterTest::unknown2D, + &AnyImageConverterTest::unknownCompressed2D, - &AnyImageConverterTest::verbose}); + &AnyImageConverterTest::propagateFlags2D, + &AnyImageConverterTest::propagateFlagsCompressed2D, + &AnyImageConverterTest::propagateConfiguration2D, + &AnyImageConverterTest::propagateConfigurationUnknown2D, + &AnyImageConverterTest::propagateConfigurationCompressed2D, + &AnyImageConverterTest::propagateConfigurationCompressedUnknown2D}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -97,7 +119,7 @@ AnyImageConverterTest::AnyImageConverterTest() { #endif /* Create the output directory if it doesn't exist yet */ - CORRADE_INTERNAL_ASSERT_OUTPUT(Utility::Directory::mkpath(ANYIMAGECONVERTER_TEST_DIR)); + CORRADE_INTERNAL_ASSERT_OUTPUT(Utility::Directory::mkpath(ANYIMAGECONVERTER_TEST_OUTPUT_DIR)); } constexpr const char Data[] = { @@ -108,14 +130,14 @@ constexpr const char Data[] = { const ImageView2D Image{PixelFormat::RGB8Unorm, {2, 3}, Data}; -void AnyImageConverterTest::convert() { +void AnyImageConverterTest::convert2D() { auto&& data = ConvertData[testCaseInstanceId()]; setTestCaseDescription(data.name); if(!(_manager.loadState("TgaImageConverter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("TgaImageConverter plugin not enabled, cannot test"); - const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_DIR, data.filename); + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, data.filename); if(Utility::Directory::exists(filename)) CORRADE_VERIFY(Utility::Directory::rm(filename)); @@ -126,7 +148,11 @@ void AnyImageConverterTest::convert() { CORRADE_VERIFY(Utility::Directory::exists(filename)); } -void AnyImageConverterTest::detect() { +void AnyImageConverterTest::convertCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::detect2D() { auto&& data = DetectData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -145,7 +171,11 @@ void AnyImageConverterTest::detect() { #endif } -void AnyImageConverterTest::unknown() { +void AnyImageConverterTest::detectCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::unknown2D() { std::ostringstream output; Error redirectError{&output}; @@ -155,11 +185,15 @@ void AnyImageConverterTest::unknown() { CORRADE_COMPARE(output.str(), "Trade::AnyImageConverter::convertToFile(): cannot determine the format of image.xcf\n"); } -void AnyImageConverterTest::verbose() { +void AnyImageConverterTest::unknownCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::propagateFlags2D() { if(!(_manager.loadState("TgaImageConverter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("TgaImageConverter plugin not enabled, cannot test"); - const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_DIR, "output.tga"); + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.tga"); if(Utility::Directory::exists(filename)) CORRADE_VERIFY(Utility::Directory::rm(filename)); @@ -178,6 +212,62 @@ void AnyImageConverterTest::verbose() { "Trade::TgaImageConverter::convertToData(): converting from RGB to BGR\n"); } +void AnyImageConverterTest::propagateFlagsCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::propagateConfiguration2D() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR}; + #ifdef ANYIMAGECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImageConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImageConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "depth32f-custom-channels.exr"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + const Float Depth32fData[] = { + 0.125f, 0.250f, 0.375f, + 0.500f, 0.625f, 0.750f + }; + + const ImageView2D Depth32f{PixelFormat::Depth32F, {3, 2}, Depth32fData}; + + Containers::Pointer converter = manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("layer", "left"); + converter->configuration().setValue("depth", "height"); + CORRADE_VERIFY(converter->convertToFile(Depth32f, filename)); + /* Compare to an expected output to ensure the custom channels names were + used */ + CORRADE_COMPARE_AS(filename, EXR_FILE, TestSuite::Compare::File); +} + +void AnyImageConverterTest::propagateConfigurationUnknown2D() { + if(!(_manager.loadState("TgaImageConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("TgaImageConverter plugin not enabled, cannot test"); + + /* Just test that the exported file exists */ + Containers::Pointer converter = _manager.instantiate("AnyImageConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertToFile(Image, Utility::Directory::join(ANYIMAGECONVERTER_TEST_OUTPUT_DIR, "output.tga"))); + CORRADE_COMPARE(out.str(), "Trade::AnyImageConverter::convertToFile(): option noSuchOption not recognized by TgaImageConverter\n"); +} + +void AnyImageConverterTest::propagateConfigurationCompressed2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + +void AnyImageConverterTest::propagateConfigurationCompressedUnknown2D() { + CORRADE_SKIP("No file formats to store compressed data yet."); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnyImageConverterTest) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt index e7b665bb9..1a8d49da1 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageConverter/Test/CMakeLists.txt @@ -24,9 +24,11 @@ # if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) - set(ANYIMAGECONVERTER_TEST_DIR "write") + set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR "write") + set(EXR_FILE depth32f-custom-channels.exr) else() - set(ANYIMAGECONVERTER_TEST_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(ANYIMAGECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(EXR_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -47,7 +49,9 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnyImageConverterTest AnyImageConverterTest.cpp - LIBRARIES MagnumTrade) + LIBRARIES MagnumTrade + FILES + ../../AnyImageImporter/Test/depth32f-custom-channels.exr) target_include_directories(AnyImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYIMAGECONVERTER_BUILD_STATIC) target_link_libraries(AnyImageConverterTest PRIVATE AnyImageConverter) diff --git a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake index a64fed90f..4719ac532 100644 --- a/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageConverter/Test/configure.h.cmake @@ -25,4 +25,19 @@ #cmakedefine ANYIMAGECONVERTER_PLUGIN_FILENAME "${ANYIMAGECONVERTER_PLUGIN_FILENAME}" #cmakedefine TGAIMAGECONVERTER_PLUGIN_FILENAME "${TGAIMAGECONVERTER_PLUGIN_FILENAME}" -#define ANYIMAGECONVERTER_TEST_DIR "${ANYIMAGECONVERTER_TEST_DIR}" +#define ANYIMAGECONVERTER_TEST_OUTPUT_DIR "${ANYIMAGECONVERTER_TEST_OUTPUT_DIR}" +#define EXR_FILE "${EXR_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMAGECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index a458087d0..097a41465 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -35,6 +35,7 @@ #include #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -118,14 +119,6 @@ void AnyImageImporter::doOpenFile(const std::string& filename) { Error{} << "Trade::AnyImageImporter::openFile(): cannot determine the format of" << filename; return; } - if(flags() & ImporterFlag::Verbose) { - Debug d; - d << "Trade::AnyImageImporter::openFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); - if(plugin != metadata->name()) - d << "(provided by" << metadata->name() << Debug::nospace << ")"; - } /* Try to load the plugin */ if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { @@ -133,10 +126,22 @@ void AnyImageImporter::doOpenFile(const std::string& filename) { return; } + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); + if(flags() & ImporterFlag::Verbose) { + Debug d; + d << "Trade::AnyImageImporter::openFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + /* Instantiate the plugin, propagate flags */ Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openFile(filename)) return; @@ -157,6 +162,9 @@ void AnyImageImporter::doOpenData(Containers::ArrayView data) { /* https://github.com/BinomialLLC/basis_universal/blob/7d784c728844c007d8c95d63231f7adcc0f65364/transcoder/basisu_file_headers.h#L78 */ if(dataString.hasPrefix("sB"_s)) plugin = "BasisImporter"; + /* https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header */ + else if(dataString.hasPrefix("BM"_s)) + plugin = "BmpImporter"; /* https://docs.microsoft.com/cs-cz/windows/desktop/direct3ddds/dx-graphics-dds-pguide */ else if(dataString.hasPrefix("DDS "_s)) plugin = "DdsImporter"; @@ -219,11 +227,12 @@ void AnyImageImporter::doOpenData(Containers::ArrayView data) { Error{} << "Trade::AnyImageImporter::openData(): cannot load the" << plugin << "plugin"; return; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImporterFlag::Verbose) { Debug d; d << "Trade::AnyImageImporter::openData(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -232,6 +241,9 @@ void AnyImageImporter::doOpenData(Containers::ArrayView data) { Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnyImageImporter::openData():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openData(data)) return; diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index 92d6f332d..abbbb5c63 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -56,9 +56,10 @@ namespace Magnum { namespace Trade { Detects file type based on file extension, loads corresponding plugin and then tries to open the file with it. Supported formats: -- Basis Universal (`*.basis`), loaded @ref BasisImporter or any other plugin - that provides it -- Windows Bitmap (`*.bmp`), loaded with any plugin that provides `BmpImporter` +- Basis Universal (`*.basis` or data with corresponding signature), loaded + with @ref BasisImporter or any other plugin that provides it +- Windows Bitmap (`*.bmp` or data with corresponding signature), loaded with + any plugin that provides `BmpImporter` - DirectDraw Surface (`*.dds` or data with corresponding signature), loaded with @ref DdsImporter or any other plugin that provides it - Graphics Interchange Format (`*.gif`), loaded with any plugin that provides @@ -127,6 +128,20 @@ target_link_libraries(your-app PRIVATE Magnum::AnyImageImporter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Audio-AnyImageImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile() / @ref openData(), a file format is detected from +the extension / file signature and a corresponding plugin is loaded. After +that, flags set via @ref setFlags() and options set through +@ref configuration() are propagated to the concrete implementation, with a +warning emitted in case given option is not present in the default +configuration of the target plugin. + +Calls to the @ref image2DCount(), @ref image2DLevelCount() and @ref image2D() +functions are then proxied to the concrete implementation. The @ref close() +function closes and discards the internally instantiated plugin; +@ref isOpened() works as usual. */ class MAGNUM_ANYIMAGEIMPORTER_EXPORT AnyImageImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index d1ad081fc..c57a205a4 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -28,10 +28,13 @@ #include #include #include +#include #include #include #include +#include "Magnum/ImageView.h" +#include "Magnum/DebugTools/CompareImage.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" @@ -49,7 +52,11 @@ struct AnyImageImporterTest: TestSuite::Tester { void unknownSignature(); void emptyData(); - void verbose(); + void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -64,7 +71,7 @@ constexpr struct { const char* name; const char* filename; Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array&); - const char* verboseFunctionName; + const char* messageFunctionName; } LoadData[]{ {"TGA", TGA_FILE, nullptr, "openFile"}, {"TGA data", TGA_FILE, fileCallback, "openData"} @@ -89,7 +96,8 @@ constexpr struct { {"ICO", "pngs.ico", nullptr, "IcoImporter"}, {"DDS", "rgba_dxt1.dds", nullptr, "DdsImporter"}, {"DDS data", "rgba_dxt1.dds", fileCallback, "DdsImporter"}, - {"BMP", "image.bmp", nullptr, "BmpImporter"}, + {"BMP", "rgb.bmp", nullptr, "BmpImporter"}, + {"BMP data", "rgb.bmp", fileCallback, "BmpImporter"}, {"GIF", "image.gif", nullptr, "GifImporter"}, {"PSD", "image.psd", nullptr, "PsdImporter"}, {"TIFF", "image.tiff", nullptr, "TiffImporter"}, @@ -116,6 +124,15 @@ const struct { {"TIFF, but no zero byte", "MM\xff\x2a"_s, "4d4dff2a"} }; +constexpr struct { + const char* name; + const char* filename; + Containers::Optional>(*callback)(const std::string&, InputFileCallbackPolicy, Containers::Array&); +} PropagateConfigurationData[]{ + {"EXR", EXR_FILE, nullptr}, + {"EXR data", EXR_FILE, fileCallback} +}; + AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::load}, Containers::arraySize(LoadData)); @@ -130,7 +147,13 @@ AnyImageImporterTest::AnyImageImporterTest() { addTests({&AnyImageImporterTest::emptyData}); - addInstancedTests({&AnyImageImporterTest::verbose}, + addInstancedTests({&AnyImageImporterTest::propagateFlags}, + Containers::arraySize(LoadData)); + + addInstancedTests({&AnyImageImporterTest::propagateConfiguration}, + Containers::arraySize(PropagateConfigurationData)); + + addInstancedTests({&AnyImageImporterTest::propagateConfigurationUnknown}, Containers::arraySize(LoadData)); /* Load the plugin directly from the build tree. Otherwise it's static and @@ -222,7 +245,7 @@ void AnyImageImporterTest::emptyData() { CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); } -void AnyImageImporterTest::verbose() { +void AnyImageImporterTest::propagateFlags() { auto&& data = LoadData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -244,7 +267,59 @@ void AnyImageImporterTest::verbose() { CORRADE_COMPARE(out.str(), Utility::formatString( "Trade::AnyImageImporter::{}(): using TgaImporter\n" "Trade::TgaImporter::image2D(): converting from BGR to RGB\n", - data.verboseFunctionName)); + data.messageFunctionName)); +} + +void AnyImageImporterTest::propagateConfiguration() { + auto&& data = PropagateConfigurationData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("OpenExrImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("OpenExrImporter plugin can't be loaded."); + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + importer->configuration().setValue("layer", "left"); + importer->configuration().setValue("depth", "height"); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + CORRADE_VERIFY(importer->openFile(data.filename)); + + Containers::Optional image = importer->image2D(0); + CORRADE_VERIFY(image); + + /* Comparing image contents to verify the custom channels were used */ + const Float Depth32fData[] = { + 0.125f, 0.250f, 0.375f, + 0.500f, 0.625f, 0.750f + }; + const ImageView2D Depth32f{PixelFormat::Depth32F, {3, 2}, Depth32fData}; + CORRADE_COMPARE_AS(*image, Depth32f, + DebugTools::CompareImage); +} + +void AnyImageImporterTest::propagateConfigurationUnknown() { + auto&& data = LoadData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("TgaImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(data.filename)); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::AnyImageImporter::{}(): option noSuchOption not recognized by TgaImporter\n", data.messageFunctionName)); } }}}} diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index 03a5c386a..e70e85c26 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -26,9 +26,11 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(TEST_FILE_DIR .) set(TGA_FILE rgb.tga) + set(EXR_FILE depth32f-custom-channels.exr) else() set(TEST_FILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TGA_FILE ${CMAKE_CURRENT_SOURCE_DIR}/rgb.tga) + set(EXR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/depth32f-custom-channels.exr) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -49,13 +51,15 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp - LIBRARIES MagnumTrade + LIBRARIES MagnumTrade MagnumDebugTools FILES + depth32f-custom-channels.exr gray.jpg image.exr image.tiff pngs.ico rgb.basis + rgb.bmp rgb.hdr rgb.png rgb.tga diff --git a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake index 5380890bf..a07bc6682 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake @@ -27,3 +27,18 @@ #cmakedefine TGAIMPORTER_PLUGIN_FILENAME "${TGAIMPORTER_PLUGIN_FILENAME}" #define TGA_FILE "${TGA_FILE}" #define TEST_FILE_DIR "${TEST_FILE_DIR}" +#define EXR_FILE "${EXR_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr b/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr new file mode 100644 index 000000000..02b41f97a Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/depth32f-custom-channels.exr differ diff --git a/src/MagnumPlugins/AnyImageImporter/Test/rgb.bmp b/src/MagnumPlugins/AnyImageImporter/Test/rgb.bmp new file mode 100644 index 000000000..f0c917a14 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/rgb.bmp differ diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index b6bd015f3..2c1c450a4 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -26,17 +26,20 @@ #include "AnySceneConverter.h" #include -#include +#include /* for PluginManager */ #include #include #include -#include +#include /* for PluginMetadata::name() */ #include #include "Magnum/Trade/ImageData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { +using namespace Containers::Literals; + AnySceneConverter::AnySceneConverter(PluginManager::Manager& manager): AbstractSceneConverter{manager} {} AnySceneConverter::AnySceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractSceneConverter{manager, plugin} {} @@ -50,13 +53,14 @@ SceneConverterFeatures AnySceneConverter::doFeatures() const { bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { CORRADE_INTERNAL_ASSERT(manager()); - /** @todo lowercase only the extension, once Directory::split() is done */ - const std::string normalized = Utility::String::lowercase(filename); + /** @todo once Directory is std::string-free, use splitExtension(), but + only if we don't detect more than one extension yet */ + const Containers::StringView normalized = Utility::String::lowercase(filename); /* Detect the plugin from extension */ - std::string plugin; - if(Utility::String::endsWith(normalized, ".ply")) - plugin = "StanfordSceneConverter"; + Containers::StringView plugin; + if(normalized.hasSuffix(".ply"_s)) + plugin = "StanfordSceneConverter"_s; else { Error{} << "Trade::AnySceneConverter::convertToFile(): cannot determine the format of" << filename; return false; @@ -67,11 +71,12 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: Error{} << "Trade::AnySceneConverter::convertToFile(): cannot load the" << plugin << "plugin"; return false; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & SceneConverterFlag::Verbose) { Debug d; d << "Trade::AnySceneConverter::convertToFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -80,6 +85,9 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); converter->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneConverter::convertToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertToFile(mesh, filename); diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h index 4dce14d30..82934b2d8 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h @@ -91,6 +91,17 @@ target_link_libraries(your-app PRIVATE Magnum::AnySceneConverter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnySceneConverter-proxy Interface proxying and option propagation + +On a call to @ref convertToFile(), a target file format is detected from the +extension and a corresponding plugin is loaded. After that, flags set via +@ref setFlags() and options set through @ref configuration() are propagated to +the concrete implementation, with a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref convertToFile() function called on the concrete +implementation is then proxied back. */ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneConverter { public: diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index e545d5dbf..3e2361688 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -25,12 +25,16 @@ #include #include +#include #include #include +#include +#include #include #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Trade/MeshData.h" @@ -41,12 +45,16 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct AnySceneConverterTest: TestSuite::Tester { explicit AnySceneConverterTest(); - void load(); + void convert(); void detect(); void unknown(); - void verbose(); + void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); + /* configuration propagation fully tested in AnySceneImporter, as there the + plugins have configuration subgroups as well */ /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -62,14 +70,16 @@ constexpr struct { }; AnySceneConverterTest::AnySceneConverterTest() { - addTests({&AnySceneConverterTest::load}); + addTests({&AnySceneConverterTest::convert}); addInstancedTests({&AnySceneConverterTest::detect}, Containers::arraySize(DetectData)); addTests({&AnySceneConverterTest::unknown, - &AnySceneConverterTest::verbose}); + &AnySceneConverterTest::propagateFlags, + &AnySceneConverterTest::propagateConfiguration, + &AnySceneConverterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -81,8 +91,34 @@ AnySceneConverterTest::AnySceneConverterTest() { CORRADE_INTERNAL_ASSERT_OUTPUT(Utility::Directory::mkpath(ANYSCENECONVERTER_TEST_OUTPUT_DIR)); } -void AnySceneConverterTest::load() { - CORRADE_SKIP("No scene converter plugin available to test."); +void AnySceneConverterTest::convert() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + if(Utility::Directory::exists(filename)) + CORRADE_VERIFY(Utility::Directory::rm(filename)); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + CORRADE_VERIFY(converter->convertToFile(mesh, filename)); + /* This file is reused in AnySceneImporter tests, so it's worth to save it + here */ + CORRADE_COMPARE_AS(filename, PLY_FILE, TestSuite::Compare::File); } void AnySceneConverterTest::detect() { @@ -114,8 +150,100 @@ void AnySceneConverterTest::unknown() { CORRADE_COMPARE(output.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); } -void AnySceneConverterTest::verbose() { - CORRADE_SKIP("No plugin available to test."); +void AnySceneConverterTest::propagateFlags() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->setFlags(SceneConverterFlag::Verbose); + + std::ostringstream out; + { + Debug redirectOutput{&out}; + CORRADE_VERIFY(converter->convertToFile(mesh, filename)); + CORRADE_VERIFY(Utility::Directory::exists(filename)); + } + CORRADE_COMPARE(out.str(), + "Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter\n"); + + /* We tested AnySceneConverter's verbose output, but can't actually test + the flag propagation in any way yet */ + CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); +} + +void AnySceneConverterTest::propagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const std::string filename = Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + + const struct Data { + Vector3 position; + UnsignedInt objectId; + } data[] { + {{-0.5f, -0.5f, 0.0f}, 4678}, + {{ 0.5f, -0.5f, 0.0f}, 3232}, + {{ 0.0f, 0.5f, 0.0f}, 1536} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, data, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(data).slice(&Data::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::stridedArrayView(data).slice(&Data::objectId)}, + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("objectIdAttribute", "OID"); + CORRADE_VERIFY(converter->convertToFile(mesh, filename)); + /* Compare to an expected output to ensure the custom attribute name was + used */ + CORRADE_COMPARE_AS(filename, PLY_OBJECTID_FILE, TestSuite::Compare::File); +} + +void AnySceneConverterTest::propagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.loadState("StanfordSceneConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + const Vector3 positions[] { + {-0.5f, -0.5f, 0.0f}, + { 0.5f, -0.5f, 0.0f}, + { 0.0f, 0.5f, 0.0f} + }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Containers::Pointer converter = manager.instantiate("AnySceneConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertToFile(mesh, Utility::Directory::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"))); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); } }}}} diff --git a/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt index 3b411839d..38f58507c 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt @@ -25,8 +25,12 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(ANYSCENECONVERTER_TEST_OUTPUT_DIR "write") + set(PLY_FILE triangle.ply) + set(PLY_OBJECTID_FILE objectid.ply) else() set(ANYSCENECONVERTER_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) + set(PLY_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/AnySceneImporter/Test/triangle.ply) + set(PLY_OBJECTID_FILE ${CMAKE_CURRENT_SOURCE_DIR}/objectid.ply) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -47,7 +51,10 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(AnySceneConverterTest AnySceneConverterTest.cpp - LIBRARIES MagnumTrade) + LIBRARIES MagnumTrade + FILES + ../../AnySceneImporter/Test/triangle.ply + objectid.ply) target_include_directories(AnySceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYSCENECONVERTER_BUILD_STATIC) target_link_libraries(AnySceneConverterTest PRIVATE AnySceneConverter) diff --git a/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake b/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake index 89af33ff4..0fa2a7cae 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake @@ -25,3 +25,19 @@ #cmakedefine ANYSCENECONVERTER_PLUGIN_FILENAME "${ANYSCENECONVERTER_PLUGIN_FILENAME}" #define ANYSCENECONVERTER_TEST_OUTPUT_DIR "${ANYSCENECONVERTER_TEST_OUTPUT_DIR}" +#define PLY_FILE "${PLY_FILE}" +#define PLY_OBJECTID_FILE "${PLY_OBJECTID_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply b/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply new file mode 100644 index 000000000..525034f3a Binary files /dev/null and b/src/MagnumPlugins/AnySceneConverter/Test/objectid.ply differ diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 00e78ff2c..12d60b569 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -43,6 +43,7 @@ #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" +#include "MagnumPlugins/Implementation/propagateConfiguration.h" #ifdef MAGNUM_BUILD_DEPRECATED #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ @@ -140,11 +141,12 @@ void AnySceneImporter::doOpenFile(const std::string& filename) { Error{} << "Trade::AnySceneImporter::openFile(): cannot load the" << plugin << "plugin"; return; } + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ImporterFlag::Verbose) { Debug d; d << "Trade::AnySceneImporter::openFile(): using" << plugin; - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -153,6 +155,9 @@ void AnySceneImporter::doOpenFile(const std::string& filename) { Containers::Pointer importer = static_cast*>(manager())->instantiate(plugin); importer->setFlags(flags()); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneImporter::openFile():", {}, metadata->name(), configuration(), importer->configuration()); + /* Try to open the file (error output should be printed by the plugin itself) */ if(!importer->openFile(filename)) return; diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index 312252b75..836d504d6 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -127,6 +127,21 @@ target_link_libraries(your-app PRIVATE Magnum::AnySceneImporter) See @ref building, @ref cmake, @ref plugins and @ref file-formats for more information. + +@section Trade-AnySceneImporter-proxy Interface proxying and option propagation + +On a call to @ref openFile(), a file format is detected from the extension and +a corresponding plugin is loaded. After that, flags set via @ref setFlags() and +options set through @ref configuration() are propagated to the concrete +implementation, with a warning emitted in case given option is not present in +the default configuration of the target plugin. + +Calls to the @ref animation(), @ref scene(), @ref light(), @ref camera(), +@ref object2D(), @ref object3D(), @ref skin2D(), @ref skin3D(), @ref mesh(), +@ref material(), @ref texture(), @ref image1D(), @ref image2D(), @ref image3D() +and corresponding count-/name-related functions are then proxied to the +concrete implementation. The @ref close() function closes and discards the +internally instantiated plugin; @ref isOpened() works as usual. */ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { public: diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 9f5f88610..888cc45fc 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -56,7 +57,9 @@ struct AnySceneImporterTest: TestSuite::Tester { void unknown(); - void verbose(); + void propagateFlags(); + void propagateConfiguration(); + void propagateConfigurationUnknown(); /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; @@ -98,7 +101,9 @@ AnySceneImporterTest::AnySceneImporterTest() { addTests({&AnySceneImporterTest::unknown, - &AnySceneImporterTest::verbose}); + &AnySceneImporterTest::propagateFlags, + &AnySceneImporterTest::propagateConfiguration, + &AnySceneImporterTest::propagateConfigurationUnknown}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -181,25 +186,89 @@ void AnySceneImporterTest::unknown() { CORRADE_COMPARE(output.str(), "Trade::AnySceneImporter::openFile(): cannot determine the format of mesh.wtf\n"); } -void AnySceneImporterTest::verbose() { - if(!(_manager.loadState("ObjImporter") & PluginManager::LoadState::Loaded)) - CORRADE_SKIP("ObjImporter plugin not enabled, cannot test"); +void AnySceneImporterTest::propagateFlags() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYSCENEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif - Containers::Pointer importer = _manager.instantiate("AnySceneImporter"); + if(manager.load("AssimpImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("AssimpImporter plugin can't be loaded."); + /* Ensure Assimp is used for PLY files and not our StanfordImporter */ + manager.setPreferredPlugins("StanfordImporter", {"AssimpImporter"}); + + Containers::Pointer importer = manager.instantiate("AnySceneImporter"); importer->setFlags(ImporterFlag::Verbose); std::ostringstream out; { Debug redirectOutput{&out}; - CORRADE_VERIFY(importer->openFile(OBJ_FILE)); + CORRADE_VERIFY(importer->openFile(PLY_FILE)); CORRADE_VERIFY(importer->mesh(0)); } - CORRADE_COMPARE(out.str(), - "Trade::AnySceneImporter::openFile(): using ObjImporter\n"); - /* We tested AnySceneImporter's verbose output, but can't actually test - the flag propagation in any way yet */ - CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); + Containers::StringView expected = + "Trade::AnySceneImporter::openFile(): using StanfordImporter (provided by AssimpImporter)\n" + "Trade::AssimpImporter: Info, T0: Load " PLY_FILE "\n"; + /** @todo use Compare::StringPrefix(?) when it exists */ + CORRADE_COMPARE(out.str().substr(0, expected.size()), expected); +} + +void AnySceneImporterTest::propagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYSCENEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("AssimpImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("AssimpImporter plugin can't be loaded."); + /* Ensure Assimp is used for PLY files and not our StanfordImporter */ + manager.setPreferredPlugins("StanfordImporter", {"AssimpImporter"}); + + Containers::Pointer importer = manager.instantiate("AnySceneImporter"); + + { + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(!mesh->hasAttribute(Trade::MeshAttribute::Normal)); + } { + importer->configuration().addGroup("postprocess")->setValue("GenNormals", true); + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(mesh->hasAttribute(Trade::MeshAttribute::Normal)); + } +} + +void AnySceneImporterTest::propagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + #ifdef ANYSCENEIMPORTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("AssimpImporter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("AssimpImporter plugin can't be loaded."); + /* Ensure Assimp is used for PLY files and not our StanfordImporter. This + thus also accidentally checks that correct plugin name (and not the + alias) is used in the warning messages. */ + manager.setPreferredPlugins("StanfordImporter", {"AssimpImporter"}); + + Containers::Pointer importer = manager.instantiate("AnySceneImporter"); + importer->configuration().setValue("noSuchOption", "isHere"); + importer->configuration().addGroup("postprocess"); + importer->configuration().group("postprocess")->setValue("notHere", false); + importer->configuration().group("postprocess")->addGroup("feh")->setValue("noHereNotEither", false); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(importer->openFile(PLY_FILE)); + CORRADE_COMPARE(out.str(), + "Trade::AnySceneImporter::openFile(): option noSuchOption not recognized by AssimpImporter\n" + "Trade::AnySceneImporter::openFile(): option postprocess/notHere not recognized by AssimpImporter\n" + "Trade::AnySceneImporter::openFile(): option postprocess/feh/noHereNotEither not recognized by AssimpImporter\n"); } }}}} diff --git a/src/MagnumPlugins/AnySceneImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnySceneImporter/Test/CMakeLists.txt index fded7df55..e03f55098 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnySceneImporter/Test/CMakeLists.txt @@ -25,8 +25,10 @@ if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(OBJ_FILE pointMesh.obj) + set(PLY_FILE triangle.ply) else() set(OBJ_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/ObjImporter/Test/pointMesh.obj) + set(PLY_FILE ${CMAKE_CURRENT_SOURCE_DIR}/triangle.ply) endif() # CMake before 3.8 has broken $ expressions for iOS (see @@ -49,7 +51,8 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(AnySceneImporterTest AnySceneImporterTest.cpp LIBRARIES MagnumTrade FILES - ../../ObjImporter/Test/pointMesh.obj) + ../../ObjImporter/Test/pointMesh.obj + triangle.ply) target_include_directories(AnySceneImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYSCENEIMPORTER_BUILD_STATIC) target_link_libraries(AnySceneImporterTest PRIVATE AnySceneImporter) diff --git a/src/MagnumPlugins/AnySceneImporter/Test/configure.h.cmake b/src/MagnumPlugins/AnySceneImporter/Test/configure.h.cmake index 121acdf97..2b40b2f65 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnySceneImporter/Test/configure.h.cmake @@ -26,3 +26,18 @@ #cmakedefine ANYSCENEIMPORTER_PLUGIN_FILENAME "${ANYSCENEIMPORTER_PLUGIN_FILENAME}" #cmakedefine OBJIMPORTER_PLUGIN_FILENAME "${OBJIMPORTER_PLUGIN_FILENAME}" #define OBJ_FILE "${OBJ_FILE}" +#define PLY_FILE "${PLY_FILE}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/MagnumPlugins/AnySceneImporter/Test/triangle.ply b/src/MagnumPlugins/AnySceneImporter/Test/triangle.ply new file mode 100644 index 000000000..36ce77768 Binary files /dev/null and b/src/MagnumPlugins/AnySceneImporter/Test/triangle.ply differ diff --git a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp index 274334d83..58a512bad 100644 --- a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.cpp @@ -27,14 +27,15 @@ #include #include -#include #include #include #include -#include +#include /* for PluginMetadata::name() */ #include #include +#include "MagnumPlugins/Implementation/propagateConfiguration.h" + namespace Magnum { namespace ShaderTools { struct AnyConverter::State { @@ -120,49 +121,50 @@ Containers::StringView stringForFormat(const Format format) { } Format formatForExtension(const char* prefix, const Containers::StringView filename) { - /** @todo lowercase only the extension, once Directory::split() is done */ - const std::string normalized = Utility::String::lowercase(filename); + /* Can't reliably lowercase just the extension as we detect double + extensions as well */ + const Containers::String normalized = Utility::String::lowercase(filename); /* https://github.com/KhronosGroup/SPIRV-Tools/blob/a715b1b4053519ad0f2bdb2d22ace35d35867cff/README.md#command-line-tools "It's a convention to name SPIR-V assembly and binary files with suffix .spvasm and .spv, respectively." IT'S GREAT THAT I HAD TO SEARCH HALF THE INTERNET TO FIND THIS CONVENTION. Especially when tests in the SPIRV-Cross repo use `.asm.bla` instead, FFS. */ - if(Utility::String::endsWith(normalized, ".spvasm") || + if(normalized.hasSuffix(".spvasm"_s) || /* Not official, used by https://github.com/KhronosGroup/SPIRV-Cross */ - Utility::String::endsWith(normalized, ".asm.vert") || - Utility::String::endsWith(normalized, ".asm.frag") || - Utility::String::endsWith(normalized, ".asm.geom") || - Utility::String::endsWith(normalized, ".asm.comp") || - Utility::String::endsWith(normalized, ".asm.tesc") || - Utility::String::endsWith(normalized, ".asm.tese") || - Utility::String::endsWith(normalized, ".asm.rgen") || - Utility::String::endsWith(normalized, ".asm.rint") || - Utility::String::endsWith(normalized, ".asm.rahit") || - Utility::String::endsWith(normalized, ".asm.rchit") || - Utility::String::endsWith(normalized, ".asm.rmiss") || - Utility::String::endsWith(normalized, ".asm.rcall") || - Utility::String::endsWith(normalized, ".asm.mesh") || - Utility::String::endsWith(normalized, ".asm.task")) + normalized.hasSuffix(".asm.vert"_s) || + normalized.hasSuffix(".asm.frag"_s) || + normalized.hasSuffix(".asm.geom"_s) || + normalized.hasSuffix(".asm.comp"_s) || + normalized.hasSuffix(".asm.tesc"_s) || + normalized.hasSuffix(".asm.tese"_s) || + normalized.hasSuffix(".asm.rgen"_s) || + normalized.hasSuffix(".asm.rint"_s) || + normalized.hasSuffix(".asm.rahit"_s) || + normalized.hasSuffix(".asm.rchit"_s) || + normalized.hasSuffix(".asm.rmiss"_s) || + normalized.hasSuffix(".asm.rcall"_s) || + normalized.hasSuffix(".asm.mesh"_s) || + normalized.hasSuffix(".asm.task"_s)) return Format::SpirvAssembly; /* https://github.com/KhronosGroup/glslang/blob/3ce148638bdc3807316e358dee4a5c9583189ae7/StandAlone/StandAlone.cpp#L260-L274 */ - else if(Utility::String::endsWith(normalized, ".glsl") || - Utility::String::endsWith(normalized, ".vert") || - Utility::String::endsWith(normalized, ".frag") || - Utility::String::endsWith(normalized, ".geom") || - Utility::String::endsWith(normalized, ".comp") || - Utility::String::endsWith(normalized, ".tesc") || - Utility::String::endsWith(normalized, ".tese") || - Utility::String::endsWith(normalized, ".rgen") || - Utility::String::endsWith(normalized, ".rint") || - Utility::String::endsWith(normalized, ".rahit") || - Utility::String::endsWith(normalized, ".rchit") || - Utility::String::endsWith(normalized, ".rmiss") || - Utility::String::endsWith(normalized, ".rcall") || - Utility::String::endsWith(normalized, ".mesh") || - Utility::String::endsWith(normalized, ".task")) + else if(normalized.hasSuffix(".glsl"_s) || + normalized.hasSuffix(".vert"_s) || + normalized.hasSuffix(".frag"_s) || + normalized.hasSuffix(".geom"_s) || + normalized.hasSuffix(".comp"_s) || + normalized.hasSuffix(".tesc"_s) || + normalized.hasSuffix(".tese"_s) || + normalized.hasSuffix(".rgen"_s) || + normalized.hasSuffix(".rint"_s) || + normalized.hasSuffix(".rahit"_s) || + normalized.hasSuffix(".rchit"_s) || + normalized.hasSuffix(".rmiss"_s) || + normalized.hasSuffix(".rcall"_s) || + normalized.hasSuffix(".mesh"_s) || + normalized.hasSuffix(".task"_s)) return Format::Glsl; - else if(Utility::String::endsWith(normalized, ".spv")) + else if(normalized.hasSuffix(".spv"_s)) return Format::Spirv; Error{} << prefix << "cannot determine the format of" << filename; @@ -190,11 +192,11 @@ std::pair AnyConverter::doValidateFile(const Stage sta Error{} << "ShaderTools::AnyConverter::validateFile(): cannot load the" << plugin << "plugin"; return {}; } - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ConverterFlag::Verbose) { Debug d; d << "ShaderTools::AnyConverter::validateFile(): using" << plugin; - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -223,6 +225,9 @@ std::pair AnyConverter::doValidateFile(const Stage sta if(!_state->definitionViews.empty()) converter->setDefinitions(_state->definitionViews); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::validateFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to validate the file (error output should be printed by the plugin itself) */ return converter->validateFile(stage, filename); @@ -243,11 +248,12 @@ std::pair AnyConverter::doValidateData(const Stage sta Error{} << "ShaderTools::AnyConverter::validateData(): cannot load the" << plugin << "plugin"; return {}; } - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ConverterFlag::Verbose) { Debug d; d << "ShaderTools::AnyConverter::validateData(): using" << plugin; - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -276,6 +282,9 @@ std::pair AnyConverter::doValidateData(const Stage sta if(!_state->definitionViews.empty()) converter->setDefinitions(_state->definitionViews); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::validateData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to validate the data (error output should be printed by the plugin itself) */ return converter->validateData(stage, data); @@ -307,11 +316,12 @@ bool AnyConverter::doConvertFileToFile(const Stage stage, const Containers::Stri Error{} << "ShaderTools::AnyConverter::convertFileToFile(): cannot load the" << plugin << "plugin"; return {}; } - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ConverterFlag::Verbose) { Debug d; d << "ShaderTools::AnyConverter::convertFileToFile(): using" << plugin; - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -356,6 +366,9 @@ bool AnyConverter::doConvertFileToFile(const Stage stage, const Containers::Stri if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertFileToFile():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertFileToFile(stage, from, to); @@ -388,11 +401,12 @@ Containers::Array AnyConverter::doConvertFileToData(const Stage stage, con Error{} << "ShaderTools::AnyConverter::convertFileToData(): cannot load the" << plugin << "plugin"; return {}; } - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ConverterFlag::Verbose) { Debug d; d << "ShaderTools::AnyConverter::convertFileToData(): using" << plugin; - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -437,6 +451,9 @@ Containers::Array AnyConverter::doConvertFileToData(const Stage stage, con if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertFileToData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertFileToData(stage, filename); @@ -467,11 +484,12 @@ Containers::Array AnyConverter::doConvertDataToData(const Stage stage, con Error{} << "ShaderTools::AnyConverter::convertDataToData(): cannot load the" << plugin << "plugin"; return {}; } - PluginManager::PluginMetadata* metadata = manager()->metadata(plugin); + + const PluginManager::PluginMetadata* const metadata = manager()->metadata(plugin); + CORRADE_INTERNAL_ASSERT(metadata); if(flags() & ConverterFlag::Verbose) { Debug d; d << "ShaderTools::AnyConverter::convertDataToData(): using" << plugin; - CORRADE_INTERNAL_ASSERT(metadata); if(plugin != metadata->name()) d << "(provided by" << metadata->name() << Debug::nospace << ")"; } @@ -516,6 +534,9 @@ Containers::Array AnyConverter::doConvertDataToData(const Stage stage, con if(!_state->optimizationLevel.isEmpty()) converter->setOptimizationLevel(_state->optimizationLevel); + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("ShaderTools::AnyConverter::convertDataToData():", {}, metadata->name(), configuration(), converter->configuration()); + /* Try to convert the file (error output should be printed by the plugin itself) */ return converter->convertDataToData(stage, from); diff --git a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h index f1529dd53..a8c4dcc5d 100644 --- a/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h +++ b/src/MagnumPlugins/AnyShaderConverter/AnyConverter.h @@ -121,6 +121,24 @@ target_link_libraries(your-app PRIVATE Magnum::AnyShaderConverter) @endcode See @ref building, @ref cmake and @ref plugins for more information. + +@section ShaderTools-AnyConverter-proxy Interface proxying and option propagation + +On a call to @ref validateFile() / @ref validateData(), @ref convertFileToFile() +/ @ref convertFileToData() / @ref convertDataToData(), an input/output file +format is detected from either the extensions or taken from the +@ref setInputFormat() and @ref setOutputFormat() calls and a corresponding +plugin is loaded. After that, everything set via @ref setFlags(), +@ref setInputFormat(), @ref setOutputFormat(), @ref setDefinitions(), +@ref setDebugInfoLevel(), @ref setOptimizationLevel() is propagated to the +concrete implementation, with an error emitted in case the target plugin +doesn't support given feature. Options set through @ref configuration() are +propagated as well, with just a warning emitted in case given option is not +present in the default configuration of the target plugin. + +The output of the @ref validateFile() / @ref validateData(), +@ref convertFileToFile() / @ref convertFileToData() / @ref convertDataToData() +function called on the concrete implementation is then proxied back. */ class MAGNUM_ANYSHADERCONVERTER_EXPORT AnyConverter: public AbstractConverter { public: diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp index 498ea94cc..dc4c46317 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ struct AnyConverterTest: TestSuite::Tester { void validateFilePropagateInputVersion(); void validateFilePropagateOutputVersion(); void validateFilePropagatePreprocess(); + void validateFilePropagateConfiguration(); + void validateFilePropagateConfigurationUnknown(); void validateData(); void validateDataPluginLoadFailed(); @@ -62,6 +65,8 @@ struct AnyConverterTest: TestSuite::Tester { void validateDataPropagateInputVersion(); void validateDataPropagateOutputVersion(); void validateDataPropagatePreprocess(); + void validateDataPropagateConfiguration(); + void validateDataPropagateConfigurationUnknown(); void convertFileToFile(); void convertFileToFilePluginLoadFailed(); @@ -77,6 +82,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertFileToFilePropagatePreprocess(); void convertFileToFilePropagateDebugInfo(); void convertFileToFilePropagateOptimization(); + void convertFileToFilePropagateConfiguration(); + void convertFileToFilePropagateConfigurationUnknown(); void convertFileToData(); void convertFileToDataPluginLoadFailed(); @@ -92,6 +99,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertFileToDataPropagatePreprocess(); void convertFileToDataPropagateDebugInfo(); void convertFileToDataPropagateOptimization(); + void convertFileToDataPropagateConfiguration(); + void convertFileToDataPropagateConfigurationUnknown(); void convertDataToData(); void convertDataToDataPluginLoadFailed(); @@ -107,6 +116,8 @@ struct AnyConverterTest: TestSuite::Tester { void convertDataToDataPropagatePreprocess(); void convertDataToDataPropagateDebugInfo(); void convertDataToDataPropagateOptimization(); + void convertDataToDataPropagateConfiguration(); + void convertDataToDataPropagateConfigurationUnknown(); void detectValidate(); void detectValidateExplicitFormat(); @@ -151,6 +162,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::validateFilePropagateInputVersion, &AnyConverterTest::validateFilePropagateOutputVersion, &AnyConverterTest::validateFilePropagatePreprocess, + &AnyConverterTest::validateFilePropagateConfiguration, + &AnyConverterTest::validateFilePropagateConfigurationUnknown, &AnyConverterTest::validateData, &AnyConverterTest::validateDataPluginLoadFailed, @@ -161,6 +174,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::validateDataPropagateInputVersion, &AnyConverterTest::validateDataPropagateOutputVersion, &AnyConverterTest::validateDataPropagatePreprocess, + &AnyConverterTest::validateDataPropagateConfiguration, + &AnyConverterTest::validateDataPropagateConfigurationUnknown, &AnyConverterTest::convertFileToFile, &AnyConverterTest::convertFileToFilePluginLoadFailed, @@ -176,6 +191,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertFileToFilePropagatePreprocess, &AnyConverterTest::convertFileToFilePropagateDebugInfo, &AnyConverterTest::convertFileToFilePropagateOptimization, + &AnyConverterTest::convertFileToFilePropagateConfiguration, + &AnyConverterTest::convertFileToFilePropagateConfigurationUnknown, &AnyConverterTest::convertFileToData, &AnyConverterTest::convertFileToDataPluginLoadFailed, @@ -191,6 +208,8 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertFileToDataPropagatePreprocess, &AnyConverterTest::convertFileToDataPropagateDebugInfo, &AnyConverterTest::convertFileToDataPropagateOptimization, + &AnyConverterTest::convertFileToDataPropagateConfiguration, + &AnyConverterTest::convertFileToDataPropagateConfigurationUnknown, &AnyConverterTest::convertDataToData, &AnyConverterTest::convertDataToDataPluginLoadFailed, @@ -205,7 +224,9 @@ AnyConverterTest::AnyConverterTest() { &AnyConverterTest::convertDataToDataPropagateOutputVersion, &AnyConverterTest::convertDataToDataPropagatePreprocess, &AnyConverterTest::convertDataToDataPropagateDebugInfo, - &AnyConverterTest::convertDataToDataPropagateOptimization}); + &AnyConverterTest::convertDataToDataPropagateOptimization, + &AnyConverterTest::convertDataToDataPropagateConfiguration, + &AnyConverterTest::convertDataToDataPropagateConfigurationUnknown}); addInstancedTests({&AnyConverterTest::detectValidate}, Containers::arraySize(DetectValidateData)); @@ -398,6 +419,53 @@ void AnyConverterTest::validateFilePropagatePreprocess() { std::make_pair(true, Utility::formatString("WARNING: {}:10: 'different__but_also_wrong' : identifiers containing consecutive underscores (\"__\") are reserved", filename))); } +void AnyConverterTest::validateFilePropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + const std::string filename = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, filename), + std::make_pair(false, Utility::formatString("ERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.", filename))); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, filename), + std::make_pair(true, "WARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version")); + } +} + +void AnyConverterTest::validateFilePropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_COMPARE(converter->validateFile(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl")), + std::make_pair(true, "")); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::validateFile(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::validateData() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -577,6 +645,55 @@ void AnyConverterTest::validateDataPropagatePreprocess() { std::make_pair(true, "WARNING: 0:10: 'different__but_also_wrong' : identifiers containing consecutive underscores (\"__\") are reserved")); } +void AnyConverterTest::validateDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + + const std::string filename = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(filename)), + std::make_pair(false, "ERROR: 0:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.")); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(filename)), + std::make_pair(true, Utility::formatString("WARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version"))); + } +} + +void AnyConverterTest::validateDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_COMPARE(converter->validateData(Stage::Fragment, Utility::Directory::read(Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"))), + std::make_pair(true, "")); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::validateData(): option noSuchOption not recognized by GlslangShaderConverter\n"); +} + void AnyConverterTest::convertFileToFile() { PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME @@ -877,7 +994,60 @@ void AnyConverterTest::convertFileToFilePropagateOptimization() { Error redirectError{&out}; CORRADE_VERIFY(!converter->convertFileToFile(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.spv"), Utility::Directory::join(ANYSHADERCONVERTER_TEST_OUTPUT_DIR, "file.spv"))); CORRADE_COMPARE(out.str(), - "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl, vulkanToWebGpu, webGpuToVulkan or empty but got 2\n"); + "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); +} + +void AnyConverterTest::convertFileToFilePropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + const std::string output = Utility::Directory::join(ANYSHADERCONVERTER_TEST_OUTPUT_DIR, "file.spv"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertFileToFile(Stage::Fragment, input, output)); + CORRADE_COMPARE(out.str(), + Utility::formatString("ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n", input)); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToFile(Stage::Fragment, input, output)); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertFileToFilePropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToFile(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"), Utility::Directory::join(ANYSHADERCONVERTER_TEST_OUTPUT_DIR, "file.glsl"))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertFileToFile(): option noSuchOption not recognized by GlslangShaderConverter\n"); } void AnyConverterTest::convertFileToData() { @@ -1188,7 +1358,62 @@ void AnyConverterTest::convertFileToDataPropagateOptimization() { Error redirectError{&out}; CORRADE_VERIFY(!converter->convertFileToData(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.spv"))); CORRADE_COMPARE(out.str(), - "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl, vulkanToWebGpu, webGpuToVulkan or empty but got 2\n"); + "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); +} + +void AnyConverterTest::convertFileToDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + converter->setOutputFormat(Format::Spirv); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertFileToData(Stage::Fragment, input)); + CORRADE_COMPARE(out.str(), + Utility::formatString("ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: {}:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n", input)); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToData(Stage::Fragment, input)); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertFileToDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setOutputFormat(Format::Spirv); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertFileToData(Stage::Fragment, Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl"))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertFileToData(): option noSuchOption not recognized by GlslangShaderConverter\n"); } void AnyConverterTest::convertDataToData() { @@ -1506,7 +1731,64 @@ void AnyConverterTest::convertDataToDataPropagateOptimization() { Error redirectError{&out}; CORRADE_VERIFY(!converter->convertDataToData(Stage::Fragment, Utility::Directory::read(Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.spv")))); CORRADE_COMPARE(out.str(), - "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl, vulkanToWebGpu, webGpuToVulkan or empty but got 2\n"); + "ShaderTools::SpirvToolsConverter::convertDataToData(): optimization level should be 0, 1, s, legalizeHlsl or empty but got 2\n"); +} + +void AnyConverterTest::convertDataToDataPropagateConfiguration() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + + converter->setInputFormat(Format::Glsl); + converter->setOutputFormat(Format::Spirv); + + const std::string input = Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "version-not-first.glsl"); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->convertDataToData(Stage::Fragment, Utility::Directory::read(input))); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation failed:\nERROR: 0:2: '#version' : must occur first in shader \nERROR: 1 compilation errors. No code generated.\n"); + } { + converter->configuration().setValue("permissive", true); + /* Lol stupid thing, apparently it has two differently worded messages + for the same thing? Dumpster fire. */ + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertDataToData(Stage::Fragment, Utility::Directory::read(input))); + CORRADE_COMPARE(out.str(), + "ShaderTools::GlslangConverter::convertDataToData(): compilation succeeded with the following message:\nWARNING: 0:0: '#version' : Illegal to have non-comment, non-whitespace tokens before #version\n"); + } +} + +void AnyConverterTest::convertDataToDataPropagateConfigurationUnknown() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SHADERCONVERTER_INSTALL_DIR}; + #ifdef ANYSHADERCONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSHADERCONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + if(manager.load("GlslangShaderConverter") < PluginManager::LoadState::Loaded) + CORRADE_SKIP("GlslangShaderConverter plugin can't be loaded."); + + Containers::Pointer converter = manager.instantiate("AnyShaderConverter"); + converter->setInputFormat(Format::Glsl); + converter->setOutputFormat(Format::Spirv); + converter->configuration().setValue("noSuchOption", "isHere"); + /* So it doesn't warn about anything */ + converter->setDefinitions({{"reserved__identifier", "sorry"}}); + + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_VERIFY(converter->convertDataToData(Stage::Fragment, Utility::Directory::read(Utility::Directory::join(ANYSHADERCONVERTER_TEST_DIR, "file.glsl")))); + CORRADE_COMPARE(out.str(), + "ShaderTools::AnyConverter::convertDataToData(): option noSuchOption not recognized by GlslangShaderConverter\n"); } void AnyConverterTest::detectValidate() { diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt index 2c91fe1f5..87fa08ce5 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyShaderConverter/Test/CMakeLists.txt @@ -49,7 +49,8 @@ corrade_add_test(AnyShaderConverterTest AnyConverterTest.cpp LIBRARIES MagnumShaderTools FILES file.glsl - file.spv) + file.spv + version-not-first.glsl) target_include_directories(AnyShaderConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_ANYSHADERCONVERTER_BUILD_STATIC) target_link_libraries(AnyShaderConverterTest PRIVATE AnyShaderConverter) diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl b/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl new file mode 100644 index 000000000..15197432d --- /dev/null +++ b/src/MagnumPlugins/AnyShaderConverter/Test/version-not-first.glsl @@ -0,0 +1,4 @@ +#define a haha +#version 450 + +void main() {} diff --git a/src/MagnumPlugins/Implementation/propagateConfiguration.h b/src/MagnumPlugins/Implementation/propagateConfiguration.h new file mode 100644 index 000000000..0bf903f17 --- /dev/null +++ b/src/MagnumPlugins/Implementation/propagateConfiguration.h @@ -0,0 +1,69 @@ +#ifndef Magnum_Implementation_propagateConfiguration_h +#define Magnum_Implementation_propagateConfiguration_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Magnum.h" + +/* Used by Any* plugins to propagate configuration to the concrete + implementation. Assumes that the Any* plugin itself doesn't have any + configuration options and so propagates all groups and values that were + set, emitting a warning if the target doesn't have such option in its + default configuration. */ + +namespace Magnum { namespace Implementation { + +/* Used only in plugins where we don't want it to be exported */ +namespace { + +void propagateConfiguration(const char* warningPrefix, const Containers::String& groupPrefix, const Containers::StringView plugin, const Utility::ConfigurationGroup& src, Utility::ConfigurationGroup& dst) { + using namespace Containers::Literals; + + /* Propagate values */ + for(Containers::Pair value: src.values()) { + if(!dst.hasValue(value.first())) { + Warning{} << warningPrefix << "option" << "/"_s.joinWithoutEmptyParts({groupPrefix, value.first()}) << "not recognized by" << plugin; + } + + dst.setValue(value.first(), value.second()); + } + + /* Recursively propagate groups */ + for(Containers::Pair> group: src.groups()) { + Utility::ConfigurationGroup* dstGroup = dst.group(group.first()); + if(!dstGroup) dstGroup = dst.addGroup(group.first()); + propagateConfiguration(warningPrefix, "/"_s.joinWithoutEmptyParts({groupPrefix, group.first()}), plugin, group.second(), *dstGroup); + } +} + +} + +}} + +#endif diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 0fc084ff1..18cae9762 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -189,7 +189,7 @@ void ObjImporter::parseMeshNames() { /* Index data, just mark that we found something for first unnamed object */ - } else if(thisIsFirstMeshAndItHasNoData) for(const std::string& data: {"p", "l", "f"}) { + } else if(thisIsFirstMeshAndItHasNoData) for(const std::string data: {"p", "l", "f"}) { if(keyword == data) { thisIsFirstMeshAndItHasNoData = false; break;