diff --git a/.editorconfig b/.editorconfig index 4c86f3ff1..d0764f768 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,6 @@ indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true -[*.{css,html,yml,rb}] +[*.{css,html,yml,rb,gltf}] indent_style = space indent_size = 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index de81e1920..f5433172b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,6 @@ set(_MAGNUM_DEPRECATED_UNPREFIXED_OPTIONS WITH_WINDOWLESSGLXAPPLICATION WITH_WINDOWLESSIOSAPPLICATION WITH_WINDOWLESSWGLAPPLICATION - WITH_WINDOWLESSWINDOWSEGLAPPLICATION WITH_CGLCONTEXT WITH_EGLCONTEXT WITH_GLXCONTEXT @@ -147,27 +146,20 @@ if(NOT DEFINED _MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS) endforeach() endif() -# If targeting iOS, Android, Emscripten or Windows RT, set explicit OpenGL ES -# support -if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) - option(MAGNUM_TARGET_GLES "Build for OpenGL ES / WebGL" OFF) -else() - set(MAGNUM_TARGET_GLES ON) -endif() - -cmake_dependent_option(MAGNUM_TARGET_GLES2 "Build for OpenGL ES 2 / WebGL 1.0" ON "MAGNUM_TARGET_GLES" OFF) -cmake_dependent_option(MAGNUM_TARGET_DESKTOP_GLES "Build for OpenGL ES on desktop" OFF "MAGNUM_TARGET_GLES" OFF) - # Magnum GL Info (currently only using GLX/CGL/EGL on *nix, WGL/EGL on Windows # and EGL on Emscripten) if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS OR CORRADE_TARGET_EMSCRIPTEN) option(MAGNUM_WITH_GL_INFO "Build magnum-gl-info utility" OFF) endif() -# Desktop-only utilities +# Desktop-only utilities. Not guaranteed to build on GLES, but showing the +# option everywhere for simplicity. if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS) - cmake_dependent_option(MAGNUM_WITH_FONTCONVERTER "Build magnum-fontconverter utility" OFF "NOT MAGNUM_TARGET_GLES" OFF) - cmake_dependent_option(MAGNUM_WITH_DISTANCEFIELDCONVERTER "Build magnum-distancefieldconverter utility" OFF "NOT MAGNUM_TARGET_GLES" OFF) + option(MAGNUM_WITH_FONTCONVERTER "Build magnum-fontconverter utility" OFF) + option(MAGNUM_WITH_DISTANCEFIELDCONVERTER "Build magnum-distancefieldconverter utility" OFF) + + set(MAGNUM_FONTCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-fontconverter utility") + set(MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-distancefieldconverter utility") endif() # API-independent utilities @@ -175,6 +167,10 @@ option(MAGNUM_WITH_IMAGECONVERTER "Build magnum-imageconverter utility" OFF) option(MAGNUM_WITH_SCENECONVERTER "Build magnum-sceneconverter utility" OFF) option(MAGNUM_WITH_SHADERCONVERTER "Build magnum-shaderconverter utility" OFF) +set(MAGNUM_IMAGECONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-imageconverter utility") +set(MAGNUM_SCENECONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-sceneconverter utility") +set(MAGNUM_SHADERCONVERTER_STATIC_PLUGINS "" CACHE STRING "Static plugins to link to the magnum-shaderconverter utility") + # Magnum AL Info option(MAGNUM_WITH_AL_INFO "Build magnum-al-info utility" OFF) @@ -200,21 +196,40 @@ cmake_dependent_option(MAGNUM_WITH_AUDIO "Build Audio library" OFF "NOT MAGNUM_W option(MAGNUM_WITH_DEBUGTOOLS "Build DebugTools library" ON) cmake_dependent_option(MAGNUM_WITH_MESHTOOLS "Build MeshTools library" ON "NOT MAGNUM_WITH_OBJIMPORTER;NOT MAGNUM_WITH_SCENECONVERTER" ON) option(MAGNUM_WITH_SCENEGRAPH "Build SceneGraph library" ON) -cmake_dependent_option(MAGNUM_WITH_SCENETOOLS "Build SceneTools library" ON "NOT WITH_SCENECONVERTER" ON) +cmake_dependent_option(MAGNUM_WITH_SCENETOOLS "Build SceneTools library" ON "NOT MAGNUM_WITH_SCENECONVERTER" ON) option(MAGNUM_WITH_SHADERS "Build Shaders library" ON) cmake_dependent_option(MAGNUM_WITH_SHADERTOOLS "Build ShaderTools library" ON "NOT MAGNUM_WITH_SHADERCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TEXT "Build Text library" ON "NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_MAGNUMFONT;NOT MAGNUM_WITH_MAGNUMFONTCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TEXTURETOOLS "Build TextureTools library" ON "NOT MAGNUM_WITH_TEXT;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) cmake_dependent_option(MAGNUM_WITH_TRADE "Build Trade library" ON "NOT MAGNUM_WITH_MESHTOOLS;NOT MAGNUM_WITH_PRIMITIVES;NOT MAGNUM_WITH_SCENETOOLS;NOT MAGNUM_WITH_IMAGECONVERTER;NOT MAGNUM_WITH_ANYIMAGEIMPORTER;NOT MAGNUM_WITH_ANYIMAGECONVERTER;NOT MAGNUM_WITH_ANYSCENEIMPORTER;NOT MAGNUM_WITH_OBJIMPORTER;NOT MAGNUM_WITH_TGAIMAGECONVERTER;NOT MAGNUM_WITH_TGAIMPORTER" ON) -cmake_dependent_option(MAGNUM_WITH_GL "Build GL library" ON "NOT MAGNUM_WITH_SHADERS;NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_ANDROIDAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSIOSAPPLICATION;NOT MAGNUM_WITH_CGLCONTEXT;NOT MAGNUM_WITH_GLXAPPLICATION;NOT MAGNUM_WITH_GLXCONTEXT;NOT MAGNUM_WITH_XEGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSWGLAPPLICATION;NOT MAGNUM_WITH_WGLCONTEXT;NOT MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) +cmake_dependent_option(MAGNUM_WITH_GL "Build GL library" ON "NOT MAGNUM_WITH_SHADERS;NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_ANDROIDAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSIOSAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSCGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSGLXAPPLICATION;NOT MAGNUM_WITH_CGLCONTEXT;NOT MAGNUM_WITH_GLXAPPLICATION;NOT MAGNUM_WITH_GLXCONTEXT;NOT MAGNUM_WITH_XEGLAPPLICATION;NOT MAGNUM_WITH_WINDOWLESSWGLAPPLICATION;NOT MAGNUM_WITH_WGLCONTEXT;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) option(MAGNUM_WITH_PRIMITIVES "Build Primitives library" ON) -cmake_dependent_option(MAGNUM_TARGET_HEADLESS "Build command-line utilities for use on a headless machines" OFF "MAGNUM_WITH_GL" OFF) cmake_dependent_option(MAGNUM_TARGET_GL "Build libraries with OpenGL interoperability" ON "MAGNUM_WITH_GL" OFF) -# EGL context and windowless EGL application, available everywhere -cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES OR NOT MAGNUM_WITH_GL_INFO;NOT TARGET_HEADLESS" ON) -option(MAGNUM_WITH_EGLCONTEXT "Build EglContext library" OFF) +# If targeting iOS, Android, Emscripten or Windows RT, implicitly enable GLES. +# Otherwise default to desktop GL. +if(CORRADE_TARGET_IOS OR CORRADE_TARGET_ANDROID OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_WINDOWS_RT) + set(MAGNUM_TARGET_GLES ON) +elseif(MAGNUM_WITH_GL) + cmake_dependent_option(MAGNUM_TARGET_GLES "Build for OpenGL ES / WebGL" OFF "MAGNUM_WITH_GL" OFF) +endif() + +# If targeting Android, Emscripten or Windows RT, implicitly enable EGL. +# Otherwise enable EGL by default only if targeting GLES and not on iOS (where +# it's EAGL instead) +if(CORRADE_TARGET_ANDROID OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_WINDOWS_RT) + set(MAGNUM_TARGET_EGL ON) +else() + if(MAGNUM_TARGET_GLES AND NOT CORRADE_TARGET_IOS) + set(_MAGNUM_TARGET_EGL_DEFAULT ON) + else() + set(_MAGNUM_TARGET_EGL_DEFAULT OFF) + endif() + cmake_dependent_option(MAGNUM_TARGET_EGL "Build for EGL instead of EAGL / CGL / GLX / WGL" ${_MAGNUM_TARGET_EGL_DEFAULT} "MAGNUM_WITH_GL" OFF) +endif() + +cmake_dependent_option(MAGNUM_TARGET_GLES2 "Build for OpenGL ES 2 / WebGL 1.0" ON "MAGNUM_TARGET_GLES" OFF) # Vulkan, everywhere except Emscripten if(NOT CORRADE_TARGET_EMSCRIPTEN) @@ -222,6 +237,18 @@ if(NOT CORRADE_TARGET_EMSCRIPTEN) cmake_dependent_option(MAGNUM_TARGET_VK "Build libraries with Vulkan interoperability" ON "MAGNUM_WITH_VK" OFF) endif() +# EGL context and windowless EGL application, available everywhere. If +# targeting EGL and not on Windows, it's implied by the CLI tools, otherwise +# it's independent. +if(MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_WINDOWS) + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER;NOT MAGNUM_WITH_FONTCONVERTER" ON) +else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDWLESSEGLAPPLICATION "Build WindowlessEglApplication library" OFF "ON" OFF) +endif() +option(MAGNUM_WITH_EGLCONTEXT "Build EglContext library" OFF) + # Android-specific application libraries if(CORRADE_TARGET_ANDROID) option(MAGNUM_WITH_ANDROIDAPPLICATION "Build AndroidApplication library" OFF) @@ -235,27 +262,42 @@ elseif(CORRADE_TARGET_IOS) option(MAGNUM_WITH_WINDOWLESSIOSAPPLICATION "Build WindowlessIosApplication library" OFF) # macOS-specific application libraries -elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) - cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) +elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_EGL) + # WindowlessCglApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) + else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION "Build WindowlessCglApplication library" OFF "ON" OFF) + endif() option(MAGNUM_WITH_CGLCONTEXT "Build CglContext library" OFF) # X11 + GLX/EGL-specific application libraries elseif(CORRADE_TARGET_UNIX) option(MAGNUM_WITH_GLXAPPLICATION "Build GlxApplication library" OFF) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + # WindowlessGlxApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) cmake_dependent_option(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION "Build WindowlessGlxApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) - option(MAGNUM_WITH_GLXCONTEXT "Build GlxContext library" OFF) + else() + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION "Build WindowlessGlxApplication library" OFF "ON" OFF) endif() + option(MAGNUM_WITH_GLXCONTEXT "Build GlxContext library" OFF) option(MAGNUM_WITH_XEGLAPPLICATION "Build XEglApplication library" OFF) # Windows-specific application libraries elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + # WindowlessWglApplication implied by the CLI tools unless targeting EGL + if(NOT MAGNUM_TARGET_EGL) cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION "Build WindowlessWglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) - option(MAGNUM_WITH_WGLCONTEXT "Build WglContext library" OFF) else() - cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION "Build WindowlessWindowsEglApplication library" OFF "NOT MAGNUM_WITH_GL_INFO;NOT MAGNUM_WITH_FONTCONVERTER;NOT MAGNUM_WITH_DISTANCEFIELDCONVERTER" ON) + # TODO when CMake 3.22 can be relied on, clean this up to use a proper + # condition instead + cmake_dependent_option(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION "Build WindowlessWglApplication library" OFF "ON" OFF) endif() + option(MAGNUM_WITH_WGLCONTEXT "Build WglContext library" OFF) endif() # Platform-independent (almost) application libraries @@ -268,18 +310,6 @@ endif() option(MAGNUM_BUILD_DEPRECATED "Include deprecated API in the build" ON) -# BUILD_MULTITHREADED got moved to Corrade itself. In case we're building with -# deprecated features enabled, print a warning in case it's set but Corrade -# reports a different value. We can't print a warning in case it's set because -# that would cause false positives when both Corrade and Magnum are subprojects -# (and thus this option is visible to both). Otherwise it's silent --- for -# non-deprecated builds CMake will at most warn about "variable being unused". -if(MAGNUM_BUILD_DEPRECATED) - if(DEFINED BUILD_MULTITHREADED AND ((NOT CORRADE_BUILD_MULTITHREADED AND BUILD_MULTITHREADED) OR (CORRADE_BUILD_MULTITHREADED AND NOT BUILD_MULTITHREADED))) - message(DEPRECATION "BUILD_MULTITHREADED (set to ${BUILD_MULTITHREADED}) is now ignored — you need to set it when building Corrade instead (there it's ${CORRADE_BUILD_MULTITHREADED} now)") - endif() -endif() - set(MAGNUM_DEPLOY_PREFIX "." CACHE STRING "Prefix where to put final application executables") @@ -389,6 +419,9 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) set(WITH_WINDOWLESSCGLAPPLICATION ON) endif() elseif(CORRADE_TARGET_UNIX) + # Checking the old deprecated options here, checking + # MAGNUM_TARGET_EGL wouldn't make sense as that's an option the + # old code definitely won't use. if((NOT TARGET_GLES AND NOT TARGET_HEADLESS) OR TARGET_DESKTOP_GLES) if(NOT DEFINED WITH_WINDOWLESSGLXAPPLICATION) set(WITH_WINDOWLESSGLXAPPLICATION ON) @@ -404,8 +437,8 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) set(WITH_WINDOWLESSWGLAPPLICATION ON) endif() else() - if(NOT DEFINED WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - set(WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON) + if(NOT DEFINED WITH_WINDOWLESSEGLAPPLICATION) + set(WITH_WINDOWLESSEGLAPPLICATION ON) endif() endif() endif() @@ -457,6 +490,45 @@ if(_MAGNUM_ACCEPT_DEPRECATED_UNPREFIXED_OPTIONS AND MAGNUM_BUILD_DEPRECATED) endif() endif() +# Handle other deprecated options. For non-deprecated builds CMake will at most +# warn about "variable being unused". Done after the MAGNUM_ prefix backwards +# compatibility above to pick up also the old names, i.e. TARGET_HEADLESS -> +# MAGNUM_TARGET_EGL. +if(MAGNUM_BUILD_DEPRECATED) + # BUILD_MULTITHREADED got moved to Corrade itself. Print a warning in case + # it's set but Corrade reports a different value. We can't print a warning + # in case it's set because that would cause false positives when both + # Corrade and Magnum are subprojects (and thus this option is visible to + # both). + if(DEFINED BUILD_MULTITHREADED AND ((NOT CORRADE_BUILD_MULTITHREADED AND BUILD_MULTITHREADED) OR (CORRADE_BUILD_MULTITHREADED AND NOT BUILD_MULTITHREADED))) + message(DEPRECATION "BUILD_MULTITHREADED (set to ${BUILD_MULTITHREADED}) is now ignored — you need to set it when building Corrade instead (there it's ${CORRADE_BUILD_MULTITHREADED} now)") + endif() + + # The following two options were desktop-only, so don't handle any + # backwards compatibility on mobile / web platforms + if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) + # MAGNUM_TARGET_HEADLESS is now MAGNUM_TARGET_EGL. Print a warning in + # case we're on desktop GL (where it was meant to be used) and the two + # are set to a different value, and sync them. + if(NOT MAGNUM_TARGET_GLES AND DEFINED MAGNUM_TARGET_HEADLESS AND ((NOT MAGNUM_TARGET_EGL AND MAGNUM_TARGET_HEADLESS) OR (MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_HEADLESS))) + message(DEPRECATION "MAGNUM_TARGET_HEADLESS is deprecated, use MAGNUM_TARGET_EGL instead") + set(MAGNUM_TARGET_EGL ${MAGNUM_TARGET_HEADLESS}) + endif() + + # MAGNUM_TARGET_DESKTOP_GLES is now an inverse of MAGNUM_TARGET_EGL. + # Print a warning in case we're on GLES (where it was meant to be used) + # and the two are set to a different value, and sync them. + if(MAGNUM_TARGET_GLES AND DEFINED MAGNUM_TARGET_DESKTOP_GLES AND ((MAGNUM_TARGET_EGL AND MAGNUM_TARGET_DESKTOP_GLES) OR (NOT MAGNUM_TARGET_EGL AND NOT MAGNUM_TARGET_DESKTOP_GLES))) + message(DEPRECATION "MAGNUM_TARGET_DESKTOP_GLES is deprecated, use MAGNUM_TARGET_EGL instead") + if(MAGNUM_TARGET_DESKTOP_GLES) + set(MAGNUM_TARGET_EGL OFF) + else() + set(MAGNUM_TARGET_EGL ON) + endif() + endif() + endif() +endif() + # Dynamic linking is meaningless on Emscripten and too inconvenient on Android if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(MAGNUM_BUILD_STATIC ON) @@ -468,7 +540,7 @@ endif() # Check dependencies if(MAGNUM_WITH_GL) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # OpenGL library preference. Prefer to use GLVND, since that's the # better approach nowadays, but allow the users to override it from # outside in case it is broken for some reason (Nvidia drivers in @@ -490,7 +562,7 @@ else() # consistency set(MAGNUM_TARGET_GLES OFF) set(MAGNUM_TARGET_GLES2 OFF) - set(MAGNUM_TARGET_DESKTOP_GLES OFF) + set(MAGNUM_TARGET_EGL OFF) endif() if(NOT MAGNUM_WITH_VK) @@ -526,7 +598,7 @@ if(MAGNUM_BUILD_TESTS) endif() if(MAGNUM_WITH_OPENGLTESTER) - if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + if(MAGNUM_TARGET_EGL) set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) @@ -536,21 +608,11 @@ if(MAGNUM_WITH_OPENGLTESTER) set(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION ON) set(OPENGLTESTER_APPLICATION MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - set(MAGNUM_WITH_WINDOWLESSEGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessEglApplication) - else() - set(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessGlxApplication) - endif() + set(MAGNUM_WITH_WINDOWLESSGLXAPPLICATION ON) + set(OPENGLTESTER_APPLICATION MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) - set(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessWglApplication) - else() - set(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON) - set(OPENGLTESTER_APPLICATION MagnumWindowlessWindowsEglApplication) - endif() + set(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION ON) + set(OPENGLTESTER_APPLICATION MagnumWindowlessWglApplication) else() # Assuming this gets hit only if MAGNUM_BUILD_GL_TESTS are enabled message(FATAL_ERROR "Cannot run tests for OpenGL code on this platform. Set MAGNUM_BUILD_GL_TESTS to OFF to skip building them.") diff --git a/doc/building.dox b/doc/building.dox index 08123b1c8..783177e4e 100644 --- a/doc/building.dox +++ b/doc/building.dox @@ -461,15 +461,10 @@ available for desktop OpenGL only, see @ref requires-gl. - `MAGNUM_TARGET_GLES2` --- Target OpenGL ES 2.0 instead of 3.0 and later. Available only when `MAGNUM_WITH_GL` is enabled. Currently enabled by default when `MAGNUM_TARGET_GLES` is set. -- `MAGNUM_TARGET_DESKTOP_GLES` --- Target OpenGL ES on desktop, i.e. use - OpenGL ES emulation in desktop OpenGL drivers. Available on Linux and - Windows, though might not be supported by all drivers. Available only when - `MAGNUM_WITH_GL` is enabled. -- `MAGNUM_TARGET_HEADLESS` --- Build command-line utilities for use on a - headless machine. Basically it means that EGL with no display attachment is - being used everywhere instead of platform-specific toolkits like CGL, GLX - or WGL. Supported mainly on OpenGL ES drivers. Available only when - `MAGNUM_WITH_GL` is enabled. +- `MAGNUM_TARGET_EGL` --- Target EGL instead of a platform-specific OpenGL + support library like CGL, EAGL, GLX or WGL. Enabled implicitly on Android, + Emscripten and Windows RT, enabled by default when `MAGNUM_TARGET_GLES` is + set unless on iOS. Available only when `MAGNUM_WITH_GL` is enabled. - `MAGNUM_TARGET_VK` --- Build libraries with Vulkan interoperability enabled. Enabled by default when `MAGNUM_WITH_VK` is enabled. Disabling this will cause libraries to not depend on the @ref Vk library, but doesn't @@ -546,9 +541,6 @@ going to build any of the @ref example-index "examples", you'll need it. - `MAGNUM_WITH_WINDOWLESSWGLAPPLICATION` --- Build the @ref Platform::WindowlessWglApplication "WindowlessWglApplication" library. Requires `MAGNUM_TARGET_GL` to be enabled. -- `MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION` --- Build the - @ref Platform::WindowlessWindowsEglApplication "WindowlessWindowsEglApplication" - library. Requires `MAGNUM_TARGET_GL` to be enabled. None of the context libraries is built by default. Similarly to the application libraries, they are always built as static. You need them only if you chose to @@ -682,7 +674,24 @@ Options controlling the build: update your code whenever there's a breaking API change. It's however recommended to have this option disabled when deploying a final application as it can result in smaller binaries. -- Additional options are inherited from the @ref CORRADE_BUILD_MULTITHREADED +- `MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS`, + `MAGNUM_FONTCONVERTER_STATIC_PLUGINS`, + `MAGNUM_IMAGECONVERTER_STATIC_PLUGINS`, + `MAGNUM_SCENECONVERTER_STATIC_PLUGINS` and + `MAGNUM_SHADERCONVERTER_STATIC_PLUGINS` --- Static plugins to link to the + @ref magnum-distancefieldconverter "magnum-distancefieldconverter", + @ref magnum-fontconverter "magnum-fontconverter", + @ref magnum-imageconverter "magnum-imageconverter", + @ref magnum-sceneconverter "magnum-sceneconverter" and + @ref magnum-shaderconverter "magnum-shaderconverter" utilities, + respectively. Intended for use in scenarios where both + `MAGNUM_BUILD_STATIC` and `MAGNUM_BUILD_PLUGINS_STATIC` is enabled, in + which case these executables don't have a possibility to load dynamic + plugins from a filesystem. Plugins from the Magnum Plugins repository + (and elsewhere) can be linked if it's added as a CMake subproject. Expects + a semicolon-separated list of existing CMake targets, for example `Magnum::AnyImageImporter;MagnumPlugins::StbImageImporter`. +- Additional options are inherited from the @ref CORRADE_BUILD_MULTITHREADED, + @ref CORRADE_BUILD_CPU_RUNTIME_DISPATCH and @ref CORRADE_CPU_USE_IFUNC options specified when building Corrade. The features used can be conveniently detected in depending projects both in @@ -739,6 +748,9 @@ compatibility if `MAGNUM_BUILD_DEPRECATED` isn't disabled. this was used to handle that case. Nowadays please use NDK r19 and newer, with the unified sysroot layout. Defaults to ``.``. If a relative path is used, it's relative to `CMAKE_INSTALL_PREFIX`. +- `MAGNUM_TARGET_HEADLESS` --- Alias to `MAGNUM_TARGET_EGL`. +- `MAGNUM_TARGET_DESKTOP_GLES` --- Inverse of `MAGNUM_TARGET_EGL` if + `MAGNUM_TARGET_GLES` is enabled. Various plugin interfaces search for plugins in locations and order documented in @ref Corrade::PluginManager::implicitPluginSearchPaths(), diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 385a258b5..4f2bb2b79 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -744,7 +744,7 @@ a high-level overview. [mosra/magnum-bootstrap#6](https://github.com/mosra/magnum-bootstrap/pull/6)) - Text input support in @ref Platform::Sdl2Application and @ref Platform::GlfwApplication (see [mosra/magnum#129](https://github.com/mosra/magnum/issues/129)) -- Added @ref Platform::WindowlessWindowsEglApplication and +- Added @cpp Platform::WindowlessWindowsEglApplication @ce and @ref Platform::WindowlessIosApplication for ANGLE and iOS - New @ref Platform::WindowlessEglApplication that works on headless NVidia, Mesa drivers and Emscripten (see [mosra/magnum#133](https://github.com/mosra/magnum/pull/133)) diff --git a/doc/changelog.dox b/doc/changelog.dox index 50e188a76..7e6e319c7 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -110,9 +110,11 @@ See also: - Recognizing @webgl_extension{EXT,float_blend} and @webgl_extension{WEBGL,debug_shaders} WebGL extensions, no implementation done yet -- Recognizing @gl_extension{KHR,parallel_shader_compile} GL, GLES and - @webgl_extension{KHR,parallel_shader_compile} WebGL extensions, no - implementation done yet +- Implemented @ref GL-AbstractShaderProgram-async "Async shader compilation and linking" + including the @gl_extension{KHR,parallel_shader_compile} GL, GLES and + @webgl_extension{KHR,parallel_shader_compile} WebGL extension (see + [mosra/magnum#534](https://github.com/mosra/magnum/issues/534) and + [mosra/magnum#576](https://github.com/mosra/magnum/pull/576)) - Recognizing ANGLE GLES and WebGL base vertex, base instance and multi-draw extensions and using them in @ref GL::AbstractShaderProgram::draw(Mesh&) and @ref GL::AbstractShaderProgram::draw(Containers::ArrayView>): @@ -203,8 +205,7 @@ See also: - Implemented @relativeref{Platform::WindowlessEglApplication,Configuration::Flag::NoError} in @ref Platform::WindowlessEglApplication, @ref Platform::WindowlessGlxApplication, - @ref Platform::WindowlessWglApplication, - @ref Platform::WindowlessWindowsEglApplication and + @ref Platform::WindowlessWglApplication and @ref Platform::Sdl2Application @subsubsection changelog-latest-new-scenegraph SceneGraph library @@ -229,6 +230,10 @@ See also: 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. +- All builtin shaders now have opt-in capability of + @ref shaders-async "async compilation and linking" (see + [mosra/magnum534](https://github.com/mosra/magnum/issues/534) and + [mosra/magnum#576](https://github.com/mosra/magnum/pull/576)) - @ref Shaders::FlatGL and @ref Shaders::PhongGL now support texture arrays, available also in multi-draw and instanced scenarios - @ref Shaders::FlatGL and @ref Shaders::PhongGL now support object ID @@ -285,6 +290,10 @@ See also: - New @ref Trade::SkinData class and @ref Trade::AbstractImporter::skin2D() / @ref Trade::AbstractImporter::skin3D() family of APIs for skin import, as well as support in @ref Trade::AnySceneImporter "AnySceneImporter" +- The @ref Trade::AbstractSceneConverter plugin interface gained support for + batch conversion of whole scenes --- meshes, hierarchies, materials, + textures, animations and other data; @relativeref{Trade,AnySceneConverter} + is updated to support batch conversion as well - 1D and 3D image support in @ref Trade::AbstractImageConverter - @ref Trade::LightData got extended to support light attenuation and range parameters as well and spot light inner and outer angle @@ -341,6 +350,9 @@ See also: - @ref Image, @ref CompressedImage, @ref ImageView, @ref CompressedImageView as well as @ref Trade::ImageData are now able to annotate array, cube map and cube map array images using @ref ImageFlags +- Removed unnecessary @ref std::string usage from certain + @relativeref{Corrade,Utility::ConfigurationValue} specializations (see + [mosra/magnum#582](https://github.com/mosra/magnum/pull/582)) @subsubsection changelog-latest-changes-debugtools DebugTools library @@ -399,6 +411,7 @@ See also: complement @relativeref{GL::Framebuffer::InvalidationAttachment,Depth} and @relativeref{GL::Framebuffer::InvalidationAttachment,Stencil} (see [mosra/magnum#554](https://github.com/mosra/magnum/pull/554)) +- Added @ref GL::Shader::wrap() and @relativeref{GL::Shader,release()} @subsubsection changelog-latest-changes-math Math library @@ -428,7 +441,7 @@ See also: - @ref MeshTools::interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags) and - @ref MeshTools::concatenate(Containers::ArrayView>, InterleaveFlags) + @ref MeshTools::concatenate(Containers::Iterable, InterleaveFlags) optionally take a @ref MeshTools::InterleaveFlags parameter affecting the output, in particular whether to preserve the original interleaved layout. @@ -436,9 +449,11 @@ See also: - Added a @ref Platform::GlfwApplication::setWindowIcon() overload taking a @ref Corrade::Containers::ArrayView in addition to @ref std::initializer_list -- @ref Platform::GlfwApplication now defaults to EGL on - @ref MAGNUM_TARGET_DESKTOP_GLES "non-desktop" - @ref MAGNUM_TARGET_GLES "GLES builds" (see [mosra/magnum#470](https://github.com/mosra/magnum/pull/470)) +- @ref Platform::GlfwApplication now properly uses + @ref MAGNUM_TARGET_EGL "EGL" on @ref MAGNUM_TARGET_GLES "GLES builds" (see + [mosra/magnum#470](https://github.com/mosra/magnum/pull/470)) +- @ref Platform::GlfwApplication and @ref Platform::Sdl2Application can now + work with EGL on desktop GL as well if @ref MAGNUM_TARGET_EGL is enabled - On Emscripten, @ref Platform::EmscriptenApplication used an internal allocation function, which changed signature in 2.0.5 and caused runtime failures when `-s ASSERTIONS` was enabled. A public stable API is now used @@ -481,10 +496,10 @@ See also: - Added a `--bounds` option to @ref magnum-sceneconverter "magnum-sceneconverter", showing data ranges of known attributes - @ref magnum-sceneconverter "magnum-sceneconverter" now has separate - `--info-animations`, `--info-images`, `--info-lights`, `--info-materials`, - `--info-meshes`, `--info-skins` and `--info-textures` for printing - information just about particular data type, with `--info` being a shortcut - for all specified together + `--info-animations`, `--info-images`, `--info-lights`, `--info-cameras`, + `--info-materials`, `--info-meshes`, `--info-skins` and `--info-textures` + for printing information just about particular data type, with `--info` + being a shortcut for all specified together @subsubsection changelog-latest-changes-shaders Shaders library @@ -520,6 +535,10 @@ See also: @relativeref{Trade::AbstractImageConverter,doConvertToData()}, for example when the implementation only neeeds to do a format detection based on file extension +- New @ref Trade::AbstractImageConverter::extension() and + @relativeref{Trade::AbstractImageConverter,mimeType()} interfaces to get + a file extension and MIME type corresponding to a file format produced by + a particular plugin. - Recognizing BMP and TIFF file header magic in @relativeref{Trade,AnyImageImporter} - Recognizing ASTC and WebP files and data in @relativeref{Trade,AnyImageImporter} @@ -528,6 +547,7 @@ See also: [mosra/magnum#529](https://github.com/mosra/magnum/pull/529)) - Recognizing KTX2 for (compressed) 1D/2D/3D and multi-level 1D/2D/3D images in @relativeref{Trade,AnyImageConverter} +- Recognizing glTF files in @relativeref{Trade,AnySceneConverter} - Recognizing 3MF files in @relativeref{Trade,AnySceneImporter} - Recognizing OpenVBD files in @relativeref{Trade,AnyImageImporter} and @relativeref{Trade,AnyImageConverter} @@ -652,6 +672,13 @@ See also: [mosra/magnum#570](https://github.com/mosra/magnum/pull/570). - Fixed wrong `.gitattributes` option for LF line endings in MSYS PKGBUILDs (see [mosra/magnum#574](https://github.com/mosra/magnum/issues/574)) +- Added `MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS`, + `MAGNUM_FONTCONVERTER_STATIC_PLUGINS`, + `MAGNUM_IMAGECONVERTER_STATIC_PLUGINS`, + `MAGNUM_SCENECONVERTER_STATIC_PLUGINS` and + `MAGNUM_SHADERCONVERTER_STATIC_PLUGINS` CMake options for linking static + plugins to the command-line utilities. See @ref building-features for more + information. @subsection changelog-latest-bugfixes Bug fixes @@ -750,6 +777,13 @@ See also: @subsection changelog-latest-deprecated Deprecated APIs +- The (mutually exclusive) @ref MAGNUM_TARGET_HEADLESS and + @ref MAGNUM_TARGET_DESKTOP_GLES options, CMake variables and preprocessor + variables are deprecated in favor of @ref MAGNUM_TARGET_EGL. It's enabled + by default on GLES and disabled by default on desktop GL --- disabling it + on GLES will force creation of a GLES context using the GLX / WGL libraries + (if available); enabling it on desktop GL will allow running windowless + applications on headless machines. - All @ref building-features "CMake build options" are now prefixed with `MAGNUM_`. For backwards compatibility, unless @ref MAGNUM_BUILD_DEPRECATED is disabled and unless a prefixed option is already set during the initial @@ -803,6 +837,16 @@ 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. +- List-taking @cpp GL::Shader::compile() @ce and + @cpp GL::AbstractShaderProgram::link() @ce functions are deprecated in + favor of new @ref GL::Shader::submitCompile(), + @relativeref{GL::Shader,checkCompile()}, + @ref GL::AbstractShaderProgram::submitLink() and + @relativeref{GL::AbstractShaderProgram,checkLink()} APIs. These were + originally meant to make use of parallel shader compilation, but in + practice that meant compiling at most two or three shaders at once. The new + API allows for much larger parallelism as well as an ability to query + completion status. - @cpp Shaders::DistanceFieldVector @ce, @cpp Shaders::Flat @ce, @cpp Shaders::Generic @ce, @cpp Shaders::MeshVisualizer2D @ce, @cpp Shaders::MeshVisualizer3D @ce, @cpp Shaders::Phong @ce, @@ -1055,6 +1099,12 @@ See also: @ref Math::RectangularMatrix::data() are no longer @cpp constexpr @ce in order to make them return a reference to a fixed-size array instead of a pointer, which was deemed a more useful property. +- @cpp Platform::WindowlessWindowsEglApplication @ce is now merged into + @ref Platform::WindowlessEglApplication. Since its use case was rather rare + (windowless applications on ANGLE on Windows) and it wasn't even built in + any packages, it's completely removed without providing any backwards + compatibility --- switch to @ref Platform::WindowlessEglApplication + instead. - @ref SceneGraph::Object::addChild() no longer requires the type constructor to have the last parameter a parent object object pointer, as that was quite limiting. Instead it's calling @ref SceneGraph::Object::setParent() @@ -1343,7 +1393,8 @@ Released 2020-06-27, tagged as @ref Platform::WindowlessEglApplication, @ref Platform::WindowlessGlxApplication, @ref Platform::WindowlessWglApplication and - @ref Platform::WindowlessWindowsEglApplication (see [mosra/magnum#433](https://github.com/mosra/magnum/pull/433) + @cpp Platform::WindowlessWindowsEglApplication @ce (see + [mosra/magnum#433](https://github.com/mosra/magnum/pull/433) and [mosra/magnum#437](https://github.com/mosra/magnum/pull/437)) - CUDA device selection in @ref Platform::WindowlessEglApplication (see [mosra/magnum#449](https://github.com/mosra/magnum/pull/449)) @@ -2389,7 +2440,7 @@ Released 2019-10-24, tagged as @ref Platform::Sdl2Application::exitEvent() "exitEvent()" - On OpenGL ES builds, @ref Platform::Sdl2Application now tells SDL whether to use a system GL driver or a dedicated GLES driver based on whether - @ref MAGNUM_TARGET_DESKTOP_GLES is defined. This allows for a smoother + @cpp MAGNUM_TARGET_DESKTOP_GLES @ce is defined. This allows for a smoother experience for example when using ANGLE on Windows. See @ref Platform-Sdl2Application-usage-gles for more information. - Replaced uses of `Pointer_stringify()` in @ref Platform::Sdl2Application diff --git a/doc/cmake.dox b/doc/cmake.dox index 8db179b45..e6c5b0b38 100644 --- a/doc/cmake.dox +++ b/doc/cmake.dox @@ -217,7 +217,6 @@ Platform namespace is split into more components: - `WindowlessGlxApplication` --- @ref Platform::WindowlessGlxApplication "WindowlessGlxApplication" - `WindowlessIosApplication` --- @ref Platform::WindowlessIosApplication "WindowlessIosApplication" - `WindowlessWglApplication` --- @ref Platform::WindowlessWglApplication "WindowlessWglApplication" -- `WindowlessWindowsEglApplication` --- @ref Platform::WindowlessWindowsEglApplication "WindowlessWindowsEglApplication" For manual context creation (without application wrappers) there are also platform-specific context libraries (see @ref platform-custom for more @@ -328,11 +327,9 @@ are also available as preprocessor variables if including - `MAGNUM_TARGET_GLES` --- Defined if compiled for OpenGL ES - `MAGNUM_TARGET_GLES2` --- Defined if compiled for OpenGL ES 2.0 - `MAGNUM_TARGET_GLES3` --- Defined if compiled for OpenGL ES 3.0 -- `MAGNUM_TARGET_DESKTOP_GLES` --- Defined if compiled with OpenGL ES - emulation on desktop OpenGL - `MAGNUM_TARGET_WEBGL` --- Defined if compiled for WebGL -- `MAGNUM_TARGET_HEADLESS` --- Defined if compiled for headless machines. See - @ref MAGNUM_TARGET_HEADLESS documentation for more information. +- `MAGNUM_TARGET_EGL` --- Defined if compiled for EGL instead of a + platform-specific OpenGL support library such as CGL, EAGL, GLX or WGL. - `MAGNUM_TARGET_VK` --- Defined if compiled with Vulkan interoperability enabled @@ -342,6 +339,10 @@ release: - `MAGNUM_BUILD_MULTITHREADED` --- Alias to `CORRADE_BUILD_MULTITHREADED`. Use @ref CORRADE_BUILD_MULTITHREADED instead. +- `MAGNUM_TARGET_HEADLESS` --- Alias to `MAGNUM_TARGET_EGL`, unless on iOS, + Android, Emscripten or Windows RT. Use @ref MAGNUM_TARGET_EGL instead. +- `MAGNUM_TARGET_DESKTOP_GLES` --- Defined if compiled for OpenGL ES but + GLX / WGL is used instead of EGL. Use @ref MAGNUM_TARGET_EGL instead. Corrade library provides also its own set of CMake macros and variables, see @ref corrade-cmake "its documentation" for more information. diff --git a/doc/credits.dox b/doc/credits.dox index f5927f862..0d46ab2b2 100644 --- a/doc/credits.dox +++ b/doc/credits.dox @@ -147,7 +147,7 @@ Are the below lists missing your name or something's wrong? - **Hilario Pérez Corona** ([\@hpcorona](https://github.com/hpcorona)) --- improvements to @cb{.cmake} android_create_apk() @ce - **Hugo Amnov** ([\@hugoam](https://github.com/hugoam)) --- buildsystem - improvements + improvements and STL usage cleanup - **Ivan P.** ([\@uzername](https://github.com/uzername)) --- documentation improvements - **Ivan Sanz Carasa** ([\@isc30](https://github.com/isc30)) --- buildsystem @@ -228,6 +228,8 @@ Are the below lists missing your name or something's wrong? --- OpenGL ES compatibility improvements - **Travis Watkins** ([\@amaranth](https://github.com/amaranth)) --- support for windowless applications under macOS +- **Vladislav** ([\@dranikpg](https://github.com/dranikpg)) --- Async @ref GL + shader compilation */ } diff --git a/doc/file-formats.dox b/doc/file-formats.dox index 1fabe99f0..192547e23 100644 --- a/doc/file-formats.dox +++ b/doc/file-formats.dox @@ -583,6 +583,16 @@ Derived from @ref Trade::AbstractSceneConverter. @m_span{m-text m-dim} none @m_endspan + + + +glTF (`*.gltf`, `*.glb`) +`GltfSceneConverter` +@relativeref{Trade,GltfSceneConverter} +@ref Trade-GltfSceneConverter-behavior "some" +@m_span{m-text m-dim} none @m_endspan + + @endparblock diff --git a/doc/namespaces.dox b/doc/namespaces.dox index a3927ea5a..361294b9d 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -109,8 +109,10 @@ more information. #include @endcode @par - In addition, contents of the @ref GlmIntegration and @ref EigenIntegration - libraries are included as well --- opt-in by specifying either + If you need the deinlined symbols to be exported from a shared library, + @cpp #define MAGNUM_EXPORT @ce as appropriate. In addition, contents of the + @ref GlmIntegration and @ref EigenIntegration libraries are included as + well --- opt-in by specifying either @cpp #define MAGNUM_MATH_GLM_INTEGRATION @ce or @cpp #define MAGNUM_MATH_EIGEN_INTEGRATION @ce before including the file. Including it multiple times with different macros defined works as well. diff --git a/doc/opengl-support.dox b/doc/opengl-support.dox index cf85a07b6..7568a28fc 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -297,7 +297,7 @@ Extension | Status @gl_extension{KHR,blend_equation_advanced} | done @gl_extension2{KHR,blend_equation_advanced_coherent,KHR_blend_equation_advanced} | done @gl_extension{KHR,texture_compression_astc_sliced_3d} | done (nothing to do) -@gl_extension{KHR,parallel_shader_compile} | | +@gl_extension{KHR,parallel_shader_compile} | missing thread count setting @subsection opengl-support-extensions-vendor Vendor OpenGL extensions @@ -490,7 +490,7 @@ Extension | Status @gl_extension{KHR,context_flush_control} | | @gl_extension{KHR,no_error} | done @gl_extension{KHR,texture_compression_astc_sliced_3d} | done (nothing to do) -@gl_extension{KHR,parallel_shader_compile} | | +@gl_extension{KHR,parallel_shader_compile} | missing thread count setting @gl_extension2{NV,read_buffer_front,NV_read_buffer} | done @gl_extension2{NV,read_depth,NV_read_depth_stencil} | done @gl_extension2{NV,read_stencil,NV_read_depth_stencil} | done @@ -566,7 +566,7 @@ Extension | Status @webgl_extension{EXT,clip_cull_distance} | done @webgl_extension{EXT,texture_norm16} | done @webgl_extension{EXT,draw_buffers_indexed} | done -@webgl_extension{KHR,parallel_shader_compile} | | +@webgl_extension{KHR,parallel_shader_compile} | done @webgl_extension{OES,texture_float_linear} | done @webgl_extension{OVR,multiview2} | | @webgl_extension{WEBGL,lose_context} | | diff --git a/doc/opengl-wrapping.dox b/doc/opengl-wrapping.dox index 84db5b8fc..2efda4083 100644 --- a/doc/opengl-wrapping.dox +++ b/doc/opengl-wrapping.dox @@ -61,8 +61,8 @@ Magnum object instance using @cpp wrap() @ce: @snippet MagnumGL.cpp opengl-wrapping-transfer The @cpp wrap() @ce and @cpp release() @ce functions are available for all -OpenGL classes except for @ref GL::Shader, instances of which are rather -short-lived and thus wrapping external instances makes less sense. +OpenGL classes except for @ref GL::AbstractShaderProgram, where the desired +usage via subclassing isn't really suited for wrapping external objects. @attention Note that interaction with third-party OpenGL code as shown above usually diff --git a/doc/shaders.dox b/doc/shaders.dox index 36ab1fd2b..9489b5853 100644 --- a/doc/shaders.dox +++ b/doc/shaders.dox @@ -287,6 +287,34 @@ 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-async Async shader compilation and linking + +By default, shaders are compiled and linked directly in their constructor. +While that's convenient and easy to use, applications using heavier shaders, +many shader combinations or running on platforms that translate GLSL to other +APIs such as HLSL or MSL, may spend a significant portion of their startup +time just on shader compilation and linking. + +To mitigate this problem, shaders can be compiled in an asynchronous way. +Depending on the driver and system, this can mean that for example eight +shaders get compiled at the same time in eight parallel threads, instead of +sequentially one after another. To achieve such parallelism, the construction +needs to be broken into two parts --- first submitting compilation of all +shaders using @ref Shaders::FlatGL::compile() "Shaders::*GL::compile()", +forming temporary @ref Shaders::FlatGL::CompileState "Shaders::*GL::CompileState" +instances, then possibly doing other work until it's completed, and finally +constructing final shader instances out of the temporary state: + +@snippet MagnumShaders-gl.cpp shaders-async + +The above code will work correctly also on drivers that implement async +compilation partially or not at all --- there +@ref GL::AbstractShaderProgram::isLinkFinished() will implicitly return +@cpp true @ce, and the final construction will stall if it happens before a +(potentially async) compilation is finished. See also the +@ref GL-AbstractShaderProgram-async "GL::AbstractShaderProgram documentation" +for more information. + @section shaders-generic Generic vertex attributes and framebuffer attachments Many shaders share the same vertex attribute definitions, such as positions, diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index 2425d0d9c..26a25311f 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -25,6 +25,7 @@ #include /* for std::tie() :( */ #include +#include #include #include @@ -90,165 +91,6 @@ using namespace Magnum; using namespace Magnum::Math::Literals; -int main() { - -#ifndef MAGNUM_TARGET_GLES2 -{ -ImageView2D diffuse{PixelFormat::RGBA8Unorm, {}}; -ImageView2D specular{PixelFormat::RGBA8Unorm, {}}; -ImageView2D bump{PixelFormat::RGBA8Unorm, {}}; -/* [method-chaining-texture] */ -GL::Texture2D carDiffuseTexture, carSpecularTexture, carBumpTexture; - -carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}); -carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}); -carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}); -carDiffuseTexture.setSubImage(0, {}, diffuse); -carSpecularTexture.setSubImage(0, {}, specular); -carBumpTexture.setSubImage(0, {}, bump); -carDiffuseTexture.generateMipmap(); -carSpecularTexture.generateMipmap(); -carBumpTexture.generateMipmap(); -/* [method-chaining-texture] */ - -/* [method-chaining-texture-chained] */ -carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}) - .setSubImage(0, {}, diffuse) - .generateMipmap(); -carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}) - .setSubImage(0, {}, diffuse) - .generateMipmap(); -carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}) - .setSubImage(0, {}, bump) - .generateMipmap(); -/* [method-chaining-texture-chained] */ -} -#endif - -{ -struct Foo { - void setSomeBuffer(GLuint) {} - GLuint someBuffer() { return {}; } -} externalLib; -char someData[1]; -/* [opengl-wrapping-transfer] */ -/* Transferring the instance to external library */ -{ - GL::Buffer buffer; - buffer.setData(someData, GL::BufferUsage::StaticDraw); - GLuint id = buffer.release(); - externalLib.setSomeBuffer(id); /* The library is responsible for deletion */ -} - -/* Acquiring an instance from external library */ -{ - GLuint id = externalLib.someBuffer(); - GL::Buffer buffer = GL::Buffer::wrap(id, GL::ObjectFlag::DeleteOnDestruction); - /* The buffer instance now handles deletion */ -} -/* [opengl-wrapping-transfer] */ -} - -#ifndef MAGNUM_TARGET_GLES -{ -struct: GL::AbstractShaderProgram {} someShader; -/* [opengl-wrapping-state] */ -GL::Buffer buffer; -GL::Mesh mesh; -// ... -someShader.draw(mesh); - -{ - /* Entering a section with 3rd-party OpenGL code -- clean up all state that - could cause accidental modifications of our objects from outside */ - GL::Context::current().resetState(GL::Context::State::EnterExternal); - - /* Raw OpenGL calls */ - glBindBuffer(GL_ARRAY_BUFFER, buffer.id()); - glBufferStorage(GL_ARRAY_BUFFER, 32768, nullptr, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT); - // ... - - /* Exiting a section with 3rd-party OpenGL code -- reset our state tracker */ - GL::Context::current().resetState(GL::Context::State::ExitExternal); -} - -/* Use the buffer through Magnum again */ -auto data = buffer.map(0, 32768, GL::Buffer::MapFlag::Read|GL::Buffer::MapFlag::Write); -// ... -/* [opengl-wrapping-state] */ -static_cast(data); -} -#endif - -#ifndef MAGNUM_TARGET_GLES -{ -/* [opengl-wrapping-extensions] */ -GL::TextureFormat format; -if(GL::Context::current().isExtensionSupported()) - format = GL::TextureFormat::DepthComponent32F; -else - format = GL::TextureFormat::DepthComponent24; -/* [opengl-wrapping-extensions] */ -static_cast(format); -} -#endif - -#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) -{ -/* [opengl-wrapping-dsa] */ -GL::Texture2D texture; - -/* - on OpenGL 4.5+/ARB_direct_state_access this calls glTextureStorage2D() - - on OpenGL 4.2+/ARB_texture_storage and OpenGL ES 3.0+ calls glTexStorage2D() - - on OpenGL ES 2.0 with EXT_texture_storage calls glTexStorage2DEXT() - - otherwise emulated using a sequence of four glTexImage2D() calls */ -texture.setStorage(4, GL::TextureFormat::RGBA8, {256, 256}); -/* [opengl-wrapping-dsa] */ -} -#endif - -{ -/* [portability-targets] */ -#ifndef MAGNUM_TARGET_GLES -GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); -// draw mesh as wireframe... -#else -// use different mesh, as polygon mode is not supported in OpenGL ES... -#endif -/* [portability-targets] */ -} - -#ifndef MAGNUM_TARGET_GLES -{ -/* [portability-extensions] */ -if(GL::Context::current().isExtensionSupported()) { - // draw mesh with wireframe on top in one pass using geometry shader... -} else { - // draw underlying mesh... - GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); - // draw mesh as wirefreame in second pass... -} -/* [portability-extensions] */ -} - -{ -/* [portability-extension-assert] */ -MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); -// just use geometry shader and don't care about old hardware -/* [portability-extension-assert] */ -} - -{ -/* [portability-shaders] */ -// MyShader.cpp -GL::Version version = GL::Context::current().supportedVersion({ - GL::Version::GL430, GL::Version::GL330, GL::Version::GL210}); -GL::Shader vert{version, GL::Shader::Type::Vertex}; -vert.addFile("MyShader.vert"); -/* [portability-shaders] */ -} -#endif - #ifndef MAGNUM_TARGET_GLES struct MyShader: GL::AbstractShaderProgram { /* [AbstractShaderProgram-input-attributes] */ @@ -305,8 +147,8 @@ explicit MyShader() { vert.addFile("MyShader.vert"); frag.addFile("MyShader.frag"); - /* Invoke parallel compilation for best performance */ - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + /* Compile them */ + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); /* Attach the shaders */ attachShaders({vert, frag}); @@ -416,13 +258,225 @@ setTransformFeedbackOutputs({ }; #endif +#ifndef MAGNUM_TARGET_GLES +namespace Foo { + +struct MyShader: GL::AbstractShaderProgram { + class CompileState; + + MyShader(NoInitT); + MyShader(CompileState&&); + MyShader(int); + + static CompileState compile(int); +}; + +/* [AbstractShaderProgram-async] */ +class MyShader::CompileState: public MyShader { + friend MyShader; + + explicit CompileState(MyShader&& shader, GL::Shader&& vert, GL::Shader&& frag): + MyShader{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)} {} + + GL::Shader _vert, _frag; +}; + +MyShader::CompileState MyShader::compile(DOXYGEN_ELLIPSIS(int)) { + GL::Shader vert{GL::Version::GL430, GL::Shader::Type::Vertex}; + GL::Shader frag{GL::Version::GL430, GL::Shader::Type::Fragment}; + DOXYGEN_ELLIPSIS() + vert.submitCompile(); + frag.submitCompile(); + + MyShader out{NoInit}; + DOXYGEN_ELLIPSIS() + out.attachShaders({vert, frag}); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag)}; +} + +MyShader::MyShader(NoInitT) {} + +MyShader::MyShader(CompileState&& state): + MyShader{static_cast(std::move(state))} +{ + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag})); + DOXYGEN_ELLIPSIS() +} + +MyShader::MyShader(DOXYGEN_ELLIPSIS(int a)): MyShader{compile(DOXYGEN_ELLIPSIS(a))} {} +/* [AbstractShaderProgram-async] */ + +} +#endif + +int main() { + +#ifndef MAGNUM_TARGET_GLES2 +{ +ImageView2D diffuse{PixelFormat::RGBA8Unorm, {}}; +ImageView2D specular{PixelFormat::RGBA8Unorm, {}}; +ImageView2D bump{PixelFormat::RGBA8Unorm, {}}; +/* [method-chaining-texture] */ +GL::Texture2D carDiffuseTexture, carSpecularTexture, carBumpTexture; + +carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}); +carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}); +carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}); +carDiffuseTexture.setSubImage(0, {}, diffuse); +carSpecularTexture.setSubImage(0, {}, specular); +carBumpTexture.setSubImage(0, {}, bump); +carDiffuseTexture.generateMipmap(); +carSpecularTexture.generateMipmap(); +carBumpTexture.generateMipmap(); +/* [method-chaining-texture] */ + +/* [method-chaining-texture-chained] */ +carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256}) + .setSubImage(0, {}, diffuse) + .generateMipmap(); +carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256}) + .setSubImage(0, {}, diffuse) + .generateMipmap(); +carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256}) + .setSubImage(0, {}, bump) + .generateMipmap(); +/* [method-chaining-texture-chained] */ +} +#endif + +{ +struct Foo { + void setSomeBuffer(GLuint) {} + GLuint someBuffer() { return {}; } +} externalLib; +char someData[1]; +/* [opengl-wrapping-transfer] */ +/* Transferring the instance to external library */ +{ + GL::Buffer buffer; + buffer.setData(someData, GL::BufferUsage::StaticDraw); + GLuint id = buffer.release(); + externalLib.setSomeBuffer(id); /* The library is responsible for deletion */ +} + +/* Acquiring an instance from external library */ +{ + GLuint id = externalLib.someBuffer(); + GL::Buffer buffer = GL::Buffer::wrap(id, GL::ObjectFlag::DeleteOnDestruction); + /* The buffer instance now handles deletion */ +} +/* [opengl-wrapping-transfer] */ +} + +#ifndef MAGNUM_TARGET_GLES +{ +struct: GL::AbstractShaderProgram {} someShader; +/* [opengl-wrapping-state] */ +GL::Buffer buffer; +GL::Mesh mesh; +// ... +someShader.draw(mesh); + +{ + /* Entering a section with 3rd-party OpenGL code -- clean up all state that + could cause accidental modifications of our objects from outside */ + GL::Context::current().resetState(GL::Context::State::EnterExternal); + + /* Raw OpenGL calls */ + glBindBuffer(GL_ARRAY_BUFFER, buffer.id()); + glBufferStorage(GL_ARRAY_BUFFER, 32768, nullptr, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT); + // ... + + /* Exiting a section with 3rd-party OpenGL code -- reset our state tracker */ + GL::Context::current().resetState(GL::Context::State::ExitExternal); +} + +/* Use the buffer through Magnum again */ +auto data = buffer.map(0, 32768, GL::Buffer::MapFlag::Read|GL::Buffer::MapFlag::Write); +// ... +/* [opengl-wrapping-state] */ +static_cast(data); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +{ +/* [opengl-wrapping-extensions] */ +GL::TextureFormat format; +if(GL::Context::current().isExtensionSupported()) + format = GL::TextureFormat::DepthComponent32F; +else + format = GL::TextureFormat::DepthComponent24; +/* [opengl-wrapping-extensions] */ +static_cast(format); +} +#endif + +#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) +{ +/* [opengl-wrapping-dsa] */ +GL::Texture2D texture; + +/* - on OpenGL 4.5+/ARB_direct_state_access this calls glTextureStorage2D() + - on OpenGL 4.2+/ARB_texture_storage and OpenGL ES 3.0+ calls glTexStorage2D() + - on OpenGL ES 2.0 with EXT_texture_storage calls glTexStorage2DEXT() + - otherwise emulated using a sequence of four glTexImage2D() calls */ +texture.setStorage(4, GL::TextureFormat::RGBA8, {256, 256}); +/* [opengl-wrapping-dsa] */ +} +#endif + +{ +/* [portability-targets] */ +#ifndef MAGNUM_TARGET_GLES +GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); +// draw mesh as wireframe... +#else +// use different mesh, as polygon mode is not supported in OpenGL ES... +#endif +/* [portability-targets] */ +} + +#ifndef MAGNUM_TARGET_GLES +{ +/* [portability-extensions] */ +if(GL::Context::current().isExtensionSupported()) { + // draw mesh with wireframe on top in one pass using geometry shader... +} else { + // draw underlying mesh... + GL::Renderer::setPolygonMode(GL::Renderer::PolygonMode::Line); + // draw mesh as wirefreame in second pass... +} +/* [portability-extensions] */ +} + +{ +/* [portability-extension-assert] */ +MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); +// just use geometry shader and don't care about old hardware +/* [portability-extension-assert] */ +} + +{ +/* [portability-shaders] */ +// MyShader.cpp +GL::Version version = GL::Context::current().supportedVersion({ + GL::Version::GL430, GL::Version::GL330, GL::Version::GL210}); +GL::Shader vert{version, GL::Shader::Type::Vertex}; +vert.addFile("MyShader.vert"); +/* [portability-shaders] */ +} +#endif + #ifndef MAGNUM_TARGET_GLES { -MyShader shader; GL::Mesh mesh; Matrix4 transformation, projection; GL::Texture2D diffuseTexture, specularTexture; /* [AbstractShaderProgram-rendering] */ +MyShader shader; shader.setTransformationMatrix(transformation) .setProjectionMatrix(projection) .bindDiffuseTexture(diffuseTexture) @@ -432,6 +486,22 @@ shader.setTransformationMatrix(transformation) } #endif +#ifndef MAGNUM_TARGET_GLES +{ +using Foo::MyShader; +/* [AbstractShaderProgram-async-usage] */ +MyShader::CompileState state = MyShader::compile(DOXYGEN_ELLIPSIS(0)); +// Other shaders to compile.... + +while(!state.isLinkFinished()) { + // Do other work... +} + +MyShader shader{std::move(state)}; +/* [AbstractShaderProgram-async-usage] */ +} +#endif + { GL::Framebuffer framebuffer{{}}; /* [AbstractFramebuffer-read1] */ diff --git a/doc/snippets/MagnumMath.cpp b/doc/snippets/MagnumMath.cpp index d99c97154..7de1317bc 100644 --- a/doc/snippets/MagnumMath.cpp +++ b/doc/snippets/MagnumMath.cpp @@ -994,8 +994,8 @@ static_cast(bClamped); using namespace Math::Literals; Half a = 3.14159_h; -Debug{} << a; // Prints 3.14159 -Debug{} << Float(a); // Prints 3.14159 +Debug{} << a; // Prints 3.141 +Debug{} << Float(a); // Prints 3.14062 Debug{} << UnsignedShort(a); // Prints 25675 /* [Half-usage] */ } @@ -1004,7 +1004,7 @@ Debug{} << UnsignedShort(a); // Prints 25675 /* [Half-usage-vector] */ Vector3h a{3.14159_h, -1.4142_h, 1.618_h}; Vector3 b{a}; // converts to 32-bit floats -Debug{} << a; // prints {3.14159, -1.4142, 1.618} +Debug{} << a; // prints {3.141, -1.414, 1.618} Debug{} << Vector3us{a}; // prints {16968, 48552, 15993} /* [Half-usage-vector] */ } diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index ad5ac5f93..5e5de8316 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -284,7 +284,7 @@ ImageView2D coneDiffuse{DOXYGEN_ELLIPSIS({}, {})}, cubeDiffuse{DOXYGEN_ELLIPSIS( GL::Texture2DArray diffuseTexture; diffuseTexture DOXYGEN_ELLIPSIS() - /* Assuming all iamges have the same format and size */ + /* Assuming all images have the same format and size */ .setStorage(1, GL::textureFormat(coneDiffuse.format()), {coneDiffuse.size(), 3}) .setSubImage(0, {}, coneDiffuse) @@ -341,6 +341,27 @@ shader /* [shaders-meshvisualizer] */ } +{ +/* [shaders-async] */ +Shaders::FlatGL3D::CompileState flatState = + Shaders::FlatGL3D::compile(); +Shaders::FlatGL3D::CompileState flatTexturedState = + Shaders::FlatGL3D::compile(Shaders::FlatGL3D::Flag::Textured); +Shaders::MeshVisualizerGL3D::CompileState meshVisualizerState = + Shaders::MeshVisualizerGL3D::compile(DOXYGEN_ELLIPSIS(Shaders::MeshVisualizerGL3D::Flag::Wireframe)); + +while(!flatState.isLinkFinished() || + !flatTexturedState.isLinkFinished() || + !meshVisualizerState.isLinkFinished()) { + // Do other work ... +} + +Shaders::FlatGL3D flat{std::move(flatState)}; +Shaders::FlatGL3D flatTextured{std::move(flatTexturedState)}; +Shaders::MeshVisualizerGL3D meshVisualizer{std::move(meshVisualizerState)}; +/* [shaders-async] */ +} + /* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 on GCC 4.8 in the [60] array */ #if !defined(CORRADE_TARGET_GCC) || defined(CORRADE_TARGET_CLANG) || __GNUC__ >= 5 diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 365dc0d33..62e10d9a9 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -78,7 +78,6 @@ # WindowlessGlxApplication - Windowless GLX application # WindowlessIosApplication - Windowless iOS application # WindowlessWglApplication - Windowless WGL application -# WindowlessWindowsEglApplication - Windowless Windows/EGL application # CglContext - CGL context # EglContext - EGL context # GlxContext - GLX context @@ -137,10 +136,9 @@ # MAGNUM_TARGET_GLES - Defined if compiled for OpenGL ES # MAGNUM_TARGET_GLES2 - Defined if compiled for OpenGL ES 2.0 # MAGNUM_TARGET_GLES3 - Defined if compiled for OpenGL ES 3.0 -# MAGNUM_TARGET_DESKTOP_GLES - Defined if compiled with OpenGL ES -# emulation on desktop OpenGL # MAGNUM_TARGET_WEBGL - Defined if compiled for WebGL -# MAGNUM_TARGET_HEADLESS - Defined if compiled for headless machines +# MAGNUM_TARGET_EGL - Defined if compiled for EGL instead of a +# platform-specific OpenGL support library like CGL, EAGL, GLX or WGL # MAGNUM_TARGET_VK - Defined if compiled with Vulkan interop # # The following variables are provided for backwards compatibility purposes @@ -149,6 +147,10 @@ # # MAGNUM_BUILD_MULTITHREADED - Alias to CORRADE_BUILD_MULTITHREADED. Use # CORRADE_BUILD_MULTITHREADED instead. +# MAGNUM_TARGET_HEADLESS - Alias to MAGNUM_TARGET_EGL, unless on iOS, +# Android, Emscripten or Windows RT. Use MAGNUM_TARGET_EGL instead. +# MAGNUM_TARGET_DESKTOP_GLES` - Defined if compiled for OpenGL ES but +# GLX / WGL is used instead of EGL. Use MAGNUM_TARGET_EGL instead. # # Additionally these variables are defined for internal usage: # @@ -269,9 +271,8 @@ set(_magnumFlags TARGET_GLES TARGET_GLES2 TARGET_GLES3 - TARGET_DESKTOP_GLES TARGET_WEBGL - TARGET_HEADLESS + TARGET_EGL TARGET_VK) foreach(_magnumFlag ${_magnumFlags}) list(FIND _magnumConfigure "#define MAGNUM_${_magnumFlag}" _magnum_${_magnumFlag}) @@ -280,9 +281,20 @@ foreach(_magnumFlag ${_magnumFlags}) endif() endforeach() -# For compatibility only, to be removed at some point -if(MAGNUM_BUILD_DEPRECATED AND CORRADE_BUILD_MULTITHREADED) - set(MAGNUM_BUILD_MULTITHREADED 1) +# For compatibility only, to be removed at some point. Refer to +# src/Magnum/configure.h.cmake for the decision logic here. +if(MAGNUM_BUILD_DEPRECATED) + if(CORRADE_BUILD_MULTITHREADED) + set(MAGNUM_BUILD_MULTITHREADED 1) + endif() + if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT) + if(NOT MAGNUM_TARGET_GLES AND MAGNUM_TARGET_EGL) + set(MAGNUM_TARGET_HEADLESS 1) + endif() + if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL) + set(MAGNUM_TARGET_DESKTOP_GLES 1) + endif() + endif() endif() # OpenGL library preference. Prefer to use GLVND, since that's the better @@ -395,7 +407,7 @@ if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) list(APPEND _MAGNUM_LIBRARY_COMPONENTS GlxApplication XEglApplication WindowlessGlxApplication GlxContext) endif() if(CORRADE_TARGET_WINDOWS) - list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext WindowlessWindowsEglApplication) + list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext) endif() if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS) list(APPEND _MAGNUM_EXECUTABLE_COMPONENTS fontconverter distancefieldconverter) @@ -426,24 +438,16 @@ if(MAGNUM_TARGET_GL) endif() set(_MAGNUM_OpenGLTester_DEPENDENCIES GL) -if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) +if(MAGNUM_TARGET_EGL) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) elseif(CORRADE_TARGET_IOS) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessIosApplication) -elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) +elseif(CORRADE_TARGET_APPLE) list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication) - else() - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication) - endif() + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) - else() - list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWindowsEglApplication) - endif() + list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication) endif() set(_MAGNUM_Primitives_DEPENDENCIES MeshTools Trade) @@ -492,7 +496,6 @@ set(_MAGNUM_WindowlessEglApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessGlxApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessIosApplication_DEPENDENCIES GL) set(_MAGNUM_WindowlessWglApplication_DEPENDENCIES GL) -set(_MAGNUM_WindowlessWindowsEglApplication_DEPENDENCIES GL) set(_MAGNUM_XEglApplication_DEPENDENCIES GL) set(_MAGNUM_CglContext_DEPENDENCIES GL) set(_MAGNUM_EglContext_DEPENDENCIES GL) @@ -708,16 +711,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # OPENGL_opengl_LIBRARY because that's set even if # OpenGL_GL_PREFERENCE is explicitly set to LEGACY. if(MAGNUM_TARGET_GL) - if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if(MAGNUM_TARGET_EGL) + find_package(EGL) + set_property(TARGET Magnum::${_component} APPEND + PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) + elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) find_package(OpenGL) if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND) set_property(TARGET Magnum::${_component} APPEND PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX) endif() - elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND - PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) endif() endif() @@ -747,16 +750,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # OPENGL_opengl_LIBRARY because that's set even if # OpenGL_GL_PREFERENCE is explicitly set to LEGACY. if(MAGNUM_TARGET_GL) - if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if(MAGNUM_TARGET_EGL) + find_package(EGL) + set_property(TARGET Magnum::${_component} APPEND + PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) + elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE) find_package(OpenGL) if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND) set_property(TARGET Magnum::${_component} APPEND PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX) endif() - elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND - PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL) endif() endif() @@ -799,12 +802,6 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # Windowless WGL application has no additional dependencies - # Windowless Windows/EGL application dependencies - elseif(_component STREQUAL WindowlessWindowsEglApplication) - find_package(EGL) - set_property(TARGET Magnum::${_component} APPEND PROPERTY - INTERFACE_LINK_LIBRARIES EGL::EGL) - # X/EGL application dependencies elseif(_component STREQUAL XEglApplication) find_package(EGL) @@ -860,7 +857,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) # GL library elseif(_component STREQUAL GL) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the # imported target. Otherwise (and also on all systems except # Linux) link to the classic libGL. Can't use diff --git a/package/archlinux/PKGBUILD-es2desktop b/package/archlinux/PKGBUILD-es2desktop index eedadf5f2..a8712d86f 100644 --- a/package/archlinux/PKGBUILD-es2desktop +++ b/package/archlinux/PKGBUILD-es2desktop @@ -22,7 +22,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr \ -DMAGNUM_TARGET_GLES=ON \ -DMAGNUM_TARGET_GLES2=ON \ - -DMAGNUM_TARGET_DESKTOP_GLES=ON \ + -DMAGNUM_TARGET_EGL=OFF \ -DMAGNUM_WITH_AUDIO=ON \ -DMAGNUM_WITH_GLFWAPPLICATION=ON \ -DMAGNUM_WITH_GLXAPPLICATION=ON \ diff --git a/package/archlinux/PKGBUILD-es3desktop b/package/archlinux/PKGBUILD-es3desktop index dd24b445a..907ee65a3 100644 --- a/package/archlinux/PKGBUILD-es3desktop +++ b/package/archlinux/PKGBUILD-es3desktop @@ -22,7 +22,7 @@ build() { -DCMAKE_INSTALL_PREFIX=/usr \ -DMAGNUM_TARGET_GLES=ON \ -DMAGNUM_TARGET_GLES2=OFF \ - -DMAGNUM_TARGET_DESKTOP_GLES=ON \ + -DMAGNUM_TARGET_EGL=OFF \ -DMAGNUM_WITH_AUDIO=ON \ -DMAGNUM_WITH_GLFWAPPLICATION=ON \ -DMAGNUM_WITH_GLXAPPLICATION=ON \ diff --git a/package/ci/appveyor-desktop-gles.bat b/package/ci/appveyor-desktop-gles.bat index 31bb5b849..48f0be498 100644 --- a/package/ci/appveyor-desktop-gles.bat +++ b/package/ci/appveyor-desktop-gles.bat @@ -27,7 +27,7 @@ cmake .. ^ -DCMAKE_PREFIX_PATH="%APPVEYOR_BUILD_FOLDER%/openal" ^ -DMAGNUM_TARGET_GLES=ON ^ -DMAGNUM_TARGET_GLES2=%TARGET_GLES2% ^ - -DMAGNUM_TARGET_DESKTOP_GLES=ON ^ + -DMAGNUM_TARGET_EGL=OFF ^ -DMAGNUM_WITH_AUDIO=OFF ^ -DMAGNUM_WITH_VK=OFF ^ -DMAGNUM_WITH_SCENETOOLS=OFF ^ diff --git a/package/ci/appveyor-lcov.sh b/package/ci/appveyor-lcov.sh index f0bf24f36..9e5f681c5 100644 --- a/package/ci/appveyor-lcov.sh +++ b/package/ci/appveyor-lcov.sh @@ -20,12 +20,16 @@ set -ev # AppVeyor ships Perl on its own and since we fetch our own lcov anyway, the # MSYS insanity is not needed for ANYTHING AT ALL, in fact. -wget https://github.com/linux-test-project/lcov/archive/v1.15.tar.gz -tar -xzf v1.15.tar.gz +# Important: 1.13 is the only version that actually works. 1.15 doesn't, tries +# to find the original source files in build/.../CMakeFiles/src/Magnum and +# results in a zero-byte coverage being happily uploaded, with no error message +# produced whatsoever. How nice. +wget https://github.com/linux-test-project/lcov/archive/v1.13.tar.gz +tar -xzf v1.13.tar.gz # Keep in sync with PKBUILD-coverage and circleci.yml, please -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --directory . --capture --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --extract coverage.info "*/src/Magnum*/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/src/MagnumExternal/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/Test/*" --output-file coverage.info > /dev/null -lcov-1.15/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/build/src/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --directory . --capture --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --extract coverage.info "*/src/Magnum*/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/src/MagnumExternal/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/Test/*" --output-file coverage.info > /dev/null +lcov-1.13/bin/lcov --gcov-tool /c/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/gcov --remove coverage.info "*/build/src/*" --output-file coverage.info > /dev/null diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index f82254895..bf31bda38 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -67,6 +67,10 @@ commands: if [[ "$CMAKE_CXX_FLAGS" == *"--coverage"* ]]; then export LCOV_PACKAGES="lcov curl"; fi sudo apt install -y ninja-build gcc cmake $LCOV_PACKAGES << parameters.extra >> + # TODO this might get resolved with 1.11.1: + # https://github.com/ninja-build/ninja/pull/1827 + # https://github.com/ninja-build/ninja/pull/2174 + # But wouldn't it build too slow then? Heh cap-ninja-jobs: parameters: count: @@ -271,7 +275,7 @@ jobs: extra: libgl1-mesa-dev libsdl2-dev libglfw3-dev libopenal-dev libvulkan-dev # In this case it gets stuck even with 24 jobs. Only on GCC, usually when # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs: count: 20 - install-gcc-4_8 @@ -310,7 +314,7 @@ jobs: # It crashes with the default setting. Only on GCC, usually when the huge # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest # get involved. - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs - install-gcc-4_8 - install-swiftshader-vulkan: @@ -331,11 +335,11 @@ jobs: steps: - install-base-linux: extra: libsdl2-dev libglfw3-dev wget unzip - # It crashes with the default setting. Only on GCC, usually when the huge - # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest - # get involved. - # TODO: revisit when we get rid of more STL - - cap-ninja-jobs + # In this case it gets stuck even with 24 jobs. Only on GCC, usually when + # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. + # TODO: revisit when we get rid of more STL / deprecated includes + - cap-ninja-jobs: + count: 20 - install-gcc-4_8 - install-cmake: version: "3.4.3" @@ -357,11 +361,11 @@ jobs: steps: - install-base-linux: extra: libsdl2-dev libglfw3-dev wget unzip - # It crashes with the default setting. Only on GCC, usually when the huge - # TradeAbstractImporterTest / TradeMaterialDataTest / TradeSceneDataTest - # get involved. - # TODO: revisit when we get rid of more STL - - cap-ninja-jobs + # In this case it gets stuck even with 20 jobs. Only on GCC, usually when + # the huge TradeAbstractImporterTest / TradeMaterialDataTest get involved. + # TODO: revisit when we get rid of more STL / deprecated includes + - cap-ninja-jobs: + count: 16 - install-gcc-4_8 - install-cmake: version: "3.4.3" @@ -377,6 +381,11 @@ jobs: # STUPID yml interprets unquoted ON as a boolean # https://stackoverflow.com/questions/53648244/specifying-the-string-value-yes-in-a-yaml-property BUILD_STATIC: "ON" + # Testing magnum-sceneconverter and other utilities requires the plugins + # to be either installed or static. Tests are however deliberately run + # before install, so the static builds are the only case where the + # utilities get thoroughly tested. + EXTRA_OPTS: -DMAGNUM_SCENECONVERTER_STATIC_PLUGINS=Magnum::AnySceneImporter;Magnum::ObjImporter CMAKE_CXX_FLAGS: --coverage LCOV_EXTRA_OPTS: --gcov-tool /usr/bin/gcov-4.8 CONFIGURATION: Debug @@ -385,7 +394,7 @@ jobs: - install-base-linux: extra: libgl1-mesa-dev libsdl2-dev libglfw3-dev libopenal-dev libvulkan-dev # 24 is not enough, unlike the other GCC-based builds - # TODO: revisit when we get rid of more STL + # TODO: revisit when we get rid of more STL / deprecated includes - cap-ninja-jobs: count: 20 - install-gcc-4_8 @@ -510,6 +519,9 @@ jobs: environment: # STUPID yml interprets unquoted ON as a boolean BUILD_STATIC: "ON" + # Same comment as with the linux-static build -- these are the only jobs + # that test command-line tools thoroughly + EXTRA_OPTS: -DMAGNUM_SCENECONVERTER_STATIC_PLUGINS=Magnum::AnySceneImporter;Magnum::ObjImporter CMAKE_CXX_FLAGS: --coverage CONFIGURATION: Debug PLATFORM_GL_API: CGL diff --git a/package/ci/unix-desktop.sh b/package/ci/unix-desktop.sh index 49369492b..ebcd06608 100755 --- a/package/ci/unix-desktop.sh +++ b/package/ci/unix-desktop.sh @@ -65,6 +65,7 @@ cmake .. \ -DMAGNUM_BUILD_DEPRECATED=$BUILD_DEPRECATED \ -DMAGNUM_BUILD_STATIC=$BUILD_STATIC \ -DMAGNUM_BUILD_PLUGINS_STATIC=$BUILD_STATIC \ + $EXTRA_OPTS \ -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 "GLTest|GLBenchmark|VkTest" diff --git a/src/Magnum/Animation/Track.h b/src/Magnum/Animation/Track.h index 74736f244..dfcad07da 100644 --- a/src/Magnum/Animation/Track.h +++ b/src/Magnum/Animation/Track.h @@ -549,6 +549,8 @@ template @ce (where @p K_ / @p V_ are * with @cpp const @ce removed) when they are @cpp const @ce. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ typedef typename std::conditional::value, const std::pair::type, typename std::remove_const::type>, std::pair>::type KeyValueType; /** @brief Animation result type */ @@ -599,12 +601,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: TrackView{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolator, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolator, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ explicit TrackView(Containers::ArrayView data, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolator, extrapolation, extrapolation} {} /** @@ -641,12 +647,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Interpolator interpolator, Extrapolation before, Extrapolation after) noexcept: TrackViewStorage{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolation, interpolator, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolation, Interpolator, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Interpolator interpolator, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolation, interpolator, extrapolation, extrapolation} {} /** @@ -681,12 +691,16 @@ template&, const Containers::StridedArrayView1D&, Interpolator, Extrapolation, Extrapolation). */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Extrapolation before, Extrapolation after) noexcept: TrackView{Containers::StridedArrayView1D{data, data ? &data[0].first : nullptr, data.size(), sizeof(std::pair)}, Containers::StridedArrayView1D{data, data ? &data[0].second : nullptr, data.size(), sizeof(std::pair)}, interpolation, before, after} {} /** @overload * Equivalent to calling @ref TrackView(Containers::ArrayView, Interpolation, Extrapolation, Extrapolation) * with both @p before and @p after set to @p extrapolation. */ + /** @todo drop this, supplying strided array views is the usual + workflow at this point */ /*implicit*/ TrackView(Containers::ArrayView data, Interpolation interpolation, Extrapolation extrapolation = Extrapolation::Constant) noexcept: TrackView{data, interpolation, extrapolation, extrapolation} {} /** @brief Convert a mutable view to a const one */ diff --git a/src/Magnum/Audio/AbstractImporter.cpp b/src/Magnum/Audio/AbstractImporter.cpp index 040c9c2cb..ab7ffd87a 100644 --- a/src/Magnum/Audio/AbstractImporter.cpp +++ b/src/Magnum/Audio/AbstractImporter.cpp @@ -145,21 +145,24 @@ Containers::Array AbstractImporter::data() { } Debug& operator<<(Debug& debug, const ImporterFeature value) { - debug << "Audio::ImporterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Audio::ImporterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImporterFeature::v: return debug << "::" #v; + #define _c(v) case ImporterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImporterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Audio::ImporterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Audio::ImporterFeatures{}", { ImporterFeature::OpenData}); } diff --git a/src/Magnum/Audio/Context.h b/src/Magnum/Audio/Context.h index 5458aeacc..c449b7bb6 100644 --- a/src/Magnum/Audio/Context.h +++ b/src/Magnum/Audio/Context.h @@ -565,7 +565,7 @@ Example usage: #define MAGNUM_ASSERT_AUDIO_EXTENSION_SUPPORTED(extension) \ do { \ if(!Magnum::Audio::Context::current().isExtensionSupported()) { \ - Corrade::Utility::Error() << "Magnum: required OpenAL extension" << extension::string() << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::Audio: required OpenAL extension" << extension::string() << "is not supported"; \ std::abort(); \ } \ } while(0) diff --git a/src/Magnum/Audio/Test/AbstractImporterTest.cpp b/src/Magnum/Audio/Test/AbstractImporterTest.cpp index 650244065..c40300f00 100644 --- a/src/Magnum/Audio/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Audio/Test/AbstractImporterTest.cpp @@ -67,7 +67,9 @@ struct AbstractImporterTest: TestSuite::Tester { void dataCustomDeleter(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractImporterTest::AbstractImporterTest() { @@ -92,7 +94,9 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::dataCustomDeleter, &AbstractImporterTest::debugFeature, - &AbstractImporterTest::debugFeatures}); + &AbstractImporterTest::debugFeaturePacked, + &AbstractImporterTest::debugFeatures, + &AbstractImporterTest::debugFeaturesPacked}); } void AbstractImporterTest::construct() { @@ -388,11 +392,25 @@ void AbstractImporterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData Audio::ImporterFeature(0xf0)\n"); } +void AbstractImporterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImporterFeature::OpenData << Debug::packed << ImporterFeature(0xf0) << ImporterFeature::OpenData; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Audio::ImporterFeature::OpenData\n"); +} + void AbstractImporterTest::debugFeatures() { std::ostringstream out; - Debug{&out} << ImporterFeature::OpenData << ImporterFeatures{}; - CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData Audio::ImporterFeatures{}\n"); + Debug{&out} << (ImporterFeature::OpenData|ImporterFeature(0xf0)) << ImporterFeatures{}; + CORRADE_COMPARE(out.str(), "Audio::ImporterFeature::OpenData|Audio::ImporterFeature(0xf0) Audio::ImporterFeatures{}\n"); +} + +void AbstractImporterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImporterFeature::OpenData|ImporterFeature(0xf0)) << Debug::packed << ImporterFeatures{} << ImporterFeature::OpenData; + CORRADE_COMPARE(out.str(), "OpenData|0xf0 {} Audio::ImporterFeature::OpenData\n"); } }}}} diff --git a/src/Magnum/DebugTools/CompareImage.h b/src/Magnum/DebugTools/CompareImage.h index e66ec4402..5567e2ed0 100644 --- a/src/Magnum/DebugTools/CompareImage.h +++ b/src/Magnum/DebugTools/CompareImage.h @@ -92,7 +92,7 @@ class MAGNUM_DEBUGTOOLS_EXPORT ImageComparatorBase { void saveDiagnostic(TestSuite::ComparisonStatusFlags flags, Utility::Debug& out, Containers::StringView path); private: - class MAGNUM_DEBUGTOOLS_LOCAL State; + class State; Containers::Pointer _state; }; @@ -359,7 +359,7 @@ class CompareImage { }; /** -@brief Image file comparator +@brief Image file comparator for @ref Corrade::TestSuite Similar to @ref CompareImage, but comparing images loaded from files. Example usage: @@ -465,7 +465,7 @@ class CompareImageFile { }; /** -@brief Image-to-file comparator +@brief Image-to-file comparator for @ref Corrade::TestSuite A combination of @ref CompareImage and @ref CompareImageFile, which allows to compare an in-memory image to a image file. See their documentation for more @@ -528,7 +528,7 @@ class CompareImageToFile { }; /** -@brief File-to-image comparator +@brief File-to-image comparator for @ref Corrade::TestSuite A combination of @ref CompareImage and @ref CompareImageFile, which allows to compare an image file to an in-memory image. See their documentation for more diff --git a/src/Magnum/DebugTools/TextureImage.cpp b/src/Magnum/DebugTools/TextureImage.cpp index 64987f240..253394413 100644 --- a/src/Magnum/DebugTools/TextureImage.cpp +++ b/src/Magnum/DebugTools/TextureImage.cpp @@ -87,7 +87,7 @@ FloatReinterpretShader::FloatReinterpretShader() { vert.addSource(rs.getString("TextureImage.vert")); frag.addSource(rs.getString("TextureImage.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); if(!GL::Context::current().isExtensionSupported()) { diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index aab585448..d14161ebd 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,10 +27,12 @@ #include "AbstractShaderProgram.h" #include +#include #include #ifndef MAGNUM_TARGET_WEBGL #include #endif +#include /** @todo remove once -free */ #include #include @@ -533,7 +536,7 @@ void AbstractShaderProgram::attachShader(Shader& shader) { glAttachShader(_id, shader.id()); } -void AbstractShaderProgram::attachShaders(std::initializer_list> shaders) { +void AbstractShaderProgram::attachShaders(Containers::Iterable shaders) { for(Shader& s: shaders) attachShader(s); } @@ -583,54 +586,74 @@ void AbstractShaderProgram::transformFeedbackVaryingsImplementationDanglingWorka #endif #endif -bool AbstractShaderProgram::link() { return link({*this}); } +bool AbstractShaderProgram::link() { + submitLink(); + return checkLink({}); +} -bool AbstractShaderProgram::link(std::initializer_list> shaders) { - bool allSuccess = true; +void AbstractShaderProgram::submitLink() { + glLinkProgram(_id); +} + +bool AbstractShaderProgram::checkLink(const Containers::Iterable shaders) { + /* If any compilation failed, abort without even checking the link status. + The checkCompile() API is called always, to print also compilation + warnings even in case everything still manages to link well. */ + for(Shader& shader: shaders) + if(!shader.checkCompile()) return false; + + GLint success, logLength; + glGetProgramiv(_id, GL_LINK_STATUS, &success); + glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength); + + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\n'); + if(message.size() > 1) + glGetProgramInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); - /* Invoke (possibly parallel) linking on all shaders */ - for(AbstractShaderProgram& shader: shaders) glLinkProgram(shader._id); - - /* After linking phase, check status of all shaders */ - Int i = 1; - for(AbstractShaderProgram& shader: shaders) { - GLint success, logLength; - glGetProgramiv(shader._id, GL_LINK_STATUS, &success); - glGetProgramiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\n'); - if(message.size() > 1) - glGetProgramInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shaderProgram.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shaderProgram.cleanLogImplementation(message); + + /* Usually the driver messages contain a newline at the end. But sometimes + not, such as in case of a program link error due to shaders not being + compiled yet on Mesa; sometimes there's two newlines, sometimes just a + newline and nothing else etc. Because trying do this in driver-specific + workarounds would involve an impossible task of checking all possible + error messages on every possible driver, just trim all whitespace around + the message always and let Debug add its own newline. */ + const Containers::StringView messageTrimmed = Containers::StringView{message}.trimmed(); + + /* Show error log */ + if(!success) { + Error{} << "GL::AbstractShaderProgram::link(): linking failed with the following message:" + << Debug::newline << messageTrimmed; + + /* Or just warnings, if any */ + } else if(messageTrimmed) { + Warning{} << "GL::AbstractShaderProgram::link(): linking succeeded with the following message:" + << Debug::newline << messageTrimmed; } + return success; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +bool AbstractShaderProgram::link(std::initializer_list> shaders) { + for(AbstractShaderProgram& shader: shaders) shader.submitLink(); + bool allSuccess = true; + for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink({}); return allSuccess; } +#endif + +bool AbstractShaderProgram::isLinkFinished() { + GLint success; + Context::current().state().shaderProgram.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} void AbstractShaderProgram::cleanLogImplementationNoOp(std::string&) {} @@ -646,6 +669,10 @@ void AbstractShaderProgram::cleanLogImplementationAngle(std::string& message) { } #endif +void AbstractShaderProgram::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + Int AbstractShaderProgram::uniformLocationInternal(const Containers::ArrayView name) { const GLint location = glGetUniformLocation(_id, name); if(location == -1) diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 01b4429ab..b0a3bd9d8 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -42,6 +43,9 @@ #endif #ifdef MAGNUM_BUILD_DEPRECATED +#include +/* For attachShaders(), which used to take a std::initializer_list */ +#include /* For label() / setLabel(), which used to be a std::string */ #include #endif @@ -422,6 +426,74 @@ See also @ref Attribute::DataType enum for additional type options. @ref Magnum::Matrix4x2 "Matrix4x2", @ref Magnum::Matrix3x4 "Matrix3x4" and @ref Magnum::Matrix4x3 "Matrix4x3") are not available in WebGL 1.0. +@section GL-AbstractShaderProgram-async Asynchronous shader compilation and linking + +The workflow described @ref GL-AbstractShaderProgram-subclassing "at the very top" +compiles and links the shader directly in a constructor. While that's fine for +many use cases, with heavier shaders, many shader combinations or on +platforms that translate GLSL to other APIs such as HLSL or MSL, the +compilation and linking can take a significant portion of application startup +time. + +To mitigate this problem, nowadays drivers implement *asynchronous compilation* +--- when shader compilation or linking is requested, the driver offloads the +work to separate worker threads, and serializes it back to the application +thread only once the application wants to retrieve the result of the operation. +Which means, the ideal way to spread the operation over more CPU cores is to +first submit compilation & linking of several shaders at once and only then ask +for operation result. That allows the driver to perform compilation/linking of +multiple shaders at once. Furthermore, the +@gl_extension{KHR,parallel_shader_compile} extension adds a possibility to +query whether the operation was finished for a particular shader. That allows +the application to schedule other work in the meantime. + +Async compilation and linking can be implemented by using +@ref Shader::submitCompile() and @ref submitLink(), followed by +@ref checkLink() (which optionally delegates to @ref Shader::checkCompile()), +instead of @ref Shader::compile() and @ref link(). Calling the submit functions +will trigger a (potentially async) compilation and linking, calling the check +functions will check the operation result, potentially stalling if the async +operation isn't finished yet. + +The @ref Shader::isCompileFinished() and +@ref isLinkFinished() APIs then provide a way to query if the submitted +operation finished. If @gl_extension{KHR,parallel_shader_compile} is not +available, those two implicitly return @cpp true @ce, thus effectively causing +a stall if the operation isn't yet done at the time you call +@ref Shader::checkCompile() / @ref checkLink() --- but compared to the linear +workflow you still get the benefits from submitting multiple operations at +once. + +A common way to equip an @ref AbstractShaderProgram subclass with async +creation capability while keeping also the simple constructor is the following: + +1. An internal @ref NoInit constructor for the subclass is added, which only + creates the @ref AbstractShaderProgram base but does nothing else. +2. A @cpp CompileState @ce inner class is defined as a subclass of + @cpp MyShader @ce. Besides that it holds all temporary state needed to + finish the construction --- in particular all @ref Shader instances. +3. A @cpp static CompileState compile(…) @ce function does everything until + and including linking as the original constructor did, except that it calls + @ref Shader::submitCompile() and @ref submitLink() instead of + @ref Shader::compile() and @ref link(), and returns a populated + @cpp CompileState @ce instance. +4. A @cpp MyShader(CompileState&&) @ce constructor then takes over the base + of @cpp CompileState @ce by delegating it into the move constructor. Then + it calls @ref checkLink(), passing all input shaders to it for a complete + context in case of an error, and finally performs any remaining post-link + steps such as uniform setup. +5. The original @cpp MyShader(…) @ce constructor now only passes the result of + @cpp compile() @ce to @cpp MyShader(CompileState&&) @ce. + +@snippet MagnumGL.cpp AbstractShaderProgram-async + +Usage-wise, it can look for example like below, with the last line waiting for +linking to finish and making the shader ready to use. On drivers that don't +perform any async compilation this will behave the same as if the construction +was done the usual way. + +@snippet MagnumGL.cpp AbstractShaderProgram-async-usage + @section GL-AbstractShaderProgram-performance-optimization Performance optimizations The engine tracks currently used shader program to avoid unnecessary calls to @@ -1258,21 +1330,40 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { AbstractShaderProgram& dispatchCompute(const Vector3ui& workgroupCount); #endif + /** + * @brief Whether a @ref submitLink() operation has finished + * @m_since_latest + * + * Has to be called only if @ref submitLink() was called before, and + * before @ref checkLink(). If returns @cpp false @ce, a subsequent + * @ref checkLink() call will block until the linking is finished. If + * @gl_extension{KHR,parallel_shader_compile} is not available, the + * function always returns @cpp true @ce --- i.e., as if the linking + * was done synchronously. See @ref GL-AbstractShaderProgram-async for + * more information. + * @see @ref Shader::isCompileFinished(), + * @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + */ + bool isLinkFinished(); + protected: + #ifdef MAGNUM_BUILD_DEPRECATED /** - * @brief Link the shader + * @brief Link multiple shaders simultaenously + * @m_deprecated_since_latest Originally meant to batch multiple link + * operations together in a way that allowed the driver to perform + * the linking in multiple threads. Superseded by @ref submitLink() + * and @ref checkLink(), use either those or the zero-argument + * @ref link() instead. See @ref GL-AbstractShaderProgram-async + * for more information. * + * Calls @ref submitLink() on all shaders first, then @ref checkLink(). * Returns @cpp false @ce if linking of any shader failed, @cpp true @ce - * if everything succeeded. Linker message (if any) is printed to error - * output. All attached shaders must be compiled with - * @ref Shader::compile() before linking. The operation is batched in a - * way that allows the driver to link multiple shaders simultaneously - * (i.e. in multiple threads). - * @see @fn_gl_keyword{LinkProgram}, @fn_gl_keyword{GetProgram} with - * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, - * @fn_gl_keyword{GetProgramInfoLog} + * if everything succeeded. */ - static bool link(std::initializer_list> shaders); + static CORRADE_DEPRECATED("use either submitLink() and checkLink() or the zero-argument link() instead") bool link(std::initializer_list> shaders); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** @@ -1325,7 +1416,7 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * than one shader at once. Other than that there is no other * (performance) difference when using this function. */ - void attachShaders(std::initializer_list> shaders); + void attachShaders(Containers::Iterable shaders); /** * @brief Bind an attribute to given location @@ -1446,13 +1537,56 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { /** * @brief Link the shader * - * Links single shader. If possible, prefer to link multiple shaders - * at once using @ref link(std::initializer_list>) - * for improved performance, see its documentation for more - * information. + * Calls @ref submitLink(), immediately followed by @ref checkLink(), + * passing back its return value. See documentation of those two + * functions for details. + * @see @ref Shader::compile() */ bool link(); + /** + * @brief Submit the shader for linking + * @m_since_latest + * + * The attached shaders must be at least submitted for compilation + * with @ref Shader::submitCompile() or @ref Shader::compile() before + * linking. Call @ref isLinkFinished() or @ref checkLink() after, see + * @ref GL-AbstractShaderProgram-async for more information. + * @see @fn_gl_keyword{LinkProgram} + */ + void submitLink(); + + /** + * @brief Check shader linking status and await completion + * @m_since_latest + * + * Has to be called only if @ref submitLink() was called before. + * + * If @p shaders are not empty, first calls @ref Shader::checkCompile() + * on each. If a compilation failure is reached, returns @cpp false @ce + * without even checking link status. To have error messages with full + * context in case of a failed shader compilation or linking, an + * application is encouraged to pass all input @ref Shader instances to + * this function or, if not possible, explicitly call + * @ref Shader::checkCompile() on each. + * + * Then, link status is checked and a message (if any) is printed + * Returns @cpp false @ce if linking failed, @cpp true @ce on success. + * If linking failed, it first goes through @p shaders and calls + * @ref Shader::checkCompile() on each until a failure is reached. If + * no compilation failed, a linker message is printed to error output. + * The function will stall until a (potentially async) linking + * operation finishes, you can use @ref isLinkFinished() to check the + * status instead. See @ref GL-AbstractShaderProgram-async for more + * information. + * @see @ref Shader::checkCompile(), @fn_gl_keyword{GetProgram} with + * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, + * @fn_gl_keyword{GetProgramInfoLog} + */ + /* No default argument is provided in order to *really* encourage apps + to pass the shaders here */ + bool checkLink(Containers::Iterable shaders); + /** * @brief Get uniform location * @param name Uniform name @@ -1668,6 +1802,8 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationAngle(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + MAGNUM_GL_LOCAL static void use(GLuint id); void use(); diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 0461c6068..02977172f 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -214,7 +214,7 @@ elseif(MAGNUM_BUILD_STATIC_PIC) set_target_properties(MagnumGL PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumGL PUBLIC Magnum) -if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) +if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the imported # target. Otherwise (and also on all systems except Linux) link to the # classic libGL. Can't use OpenGL_OpenGL_FOUND, because that one is set @@ -297,7 +297,7 @@ if(MAGNUM_BUILD_TESTS) endif() target_link_libraries(MagnumGLTestLib PUBLIC Magnum) - if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES) + if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS)) # If the GLVND library (CMake 3.11+) was found, link to the imported # target. Otherwise (and also on all systems except Linux) link to the # classic libGL. Can't use OpenGL_OpenGL_FOUND, because that one is set @@ -331,7 +331,7 @@ if(MAGNUM_BUILD_TESTS) # Windows only and elsewhere I just link the same way as with # MagnumOpenGLTester. if(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) + if(MAGNUM_TARGET_EGL) # Otherwise it complains that EGL::EGL does not exist here find_package(EGL) endif() diff --git a/src/Magnum/GL/Context.h b/src/Magnum/GL/Context.h index 2ba235f44..862f771df 100644 --- a/src/Magnum/GL/Context.h +++ b/src/Magnum/GL/Context.h @@ -1192,7 +1192,7 @@ Example usage: #define MAGNUM_ASSERT_GL_VERSION_SUPPORTED(version) \ do { \ if(!Magnum::GL::Context::current().isVersionSupported(version)) { \ - Corrade::Utility::Error() << "Magnum: required version" << version << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::GL: required version" << version << "is not supported"; \ std::abort(); \ } \ } while(0) @@ -1221,7 +1221,7 @@ Example usage: #define MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(extension) \ do { \ if(!Magnum::GL::Context::current().isExtensionSupported()) { \ - Corrade::Utility::Error() << "Magnum: required extension" << extension::string() << "is not supported"; \ + Corrade::Utility::Error{} << "Magnum::GL: required extension" << extension::string() << "is not supported"; \ std::abort(); \ } \ } while(0) diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.cpp b/src/Magnum/GL/Implementation/ShaderProgramState.cpp index 1e3125560..9799fc087 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.cpp +++ b/src/Magnum/GL/Implementation/ShaderProgramState.cpp @@ -78,6 +78,13 @@ ShaderProgramState::ShaderProgramState(Context& context, Containers::StaticArray cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationNoOp; } + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetProgramiv; + } else { + completionStatusImplementation = &AbstractShaderProgram::completionStatusImplementationFallback; + } + #ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.h b/src/Magnum/GL/Implementation/ShaderProgramState.h index 01f131b8f..95001ea80 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.h +++ b/src/Magnum/GL/Implementation/ShaderProgramState.h @@ -47,6 +47,9 @@ struct ShaderProgramState { void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView, AbstractShaderProgram::TransformFeedbackBufferMode); #endif void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); #ifndef MAGNUM_TARGET_WEBGL void(APIENTRY *uniform1fvImplementation)(GLuint, GLint, GLsizei, const GLfloat*); diff --git a/src/Magnum/GL/Implementation/ShaderState.cpp b/src/Magnum/GL/Implementation/ShaderState.cpp index ffc0c2f1e..17758a801 100644 --- a/src/Magnum/GL/Implementation/ShaderState.cpp +++ b/src/Magnum/GL/Implementation/ShaderState.cpp @@ -31,12 +31,13 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Extensions.h" namespace Magnum { namespace GL { namespace Implementation { using namespace Containers::Literals; -ShaderState::ShaderState(Context& context, Containers::StaticArrayView): +ShaderState::ShaderState(Context& context, Containers::StaticArrayView extensions): maxVertexOutputComponents{}, maxFragmentInputComponents{}, #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) maxTessellationControlInputComponents{}, maxTessellationControlOutputComponents{}, maxTessellationControlTotalOutputComponents{}, maxTessellationEvaluationInputComponents{}, maxTessellationEvaluationOutputComponents{}, maxGeometryInputComponents{}, maxGeometryOutputComponents{}, maxGeometryTotalOutputComponents{}, maxAtomicCounterBuffers{}, maxCombinedAtomicCounterBuffers{}, maxAtomicCounters{}, maxCombinedAtomicCounters{}, maxImageUniforms{}, maxCombinedImageUniforms{}, maxShaderStorageBlocks{}, maxCombinedShaderStorageBlocks{}, @@ -68,9 +69,12 @@ ShaderState::ShaderState(Context& context, Containers::StaticArrayView(context); + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetShaderiv; + } else { + completionStatusImplementation = &Shader::completionStatusImplementationFallback; + } } }}} diff --git a/src/Magnum/GL/Implementation/ShaderState.h b/src/Magnum/GL/Implementation/ShaderState.h index 3eef9f0b5..4d37c764d 100644 --- a/src/Magnum/GL/Implementation/ShaderState.h +++ b/src/Magnum/GL/Implementation/ShaderState.h @@ -53,6 +53,9 @@ struct ShaderState { void(Shader::*addSourceImplementation)(std::string); void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); GLint maxVertexOutputComponents, maxFragmentInputComponents; diff --git a/src/Magnum/GL/OpenGL.h b/src/Magnum/GL/OpenGL.h index 9fecc070f..ef206e0cf 100644 --- a/src/Magnum/GL/OpenGL.h +++ b/src/Magnum/GL/OpenGL.h @@ -46,7 +46,7 @@ #endif /* Special case for desktop GLES on Windows (still links to the old opengl32.dll) */ -#elif defined(CORRADE_TARGET_WINDOWS) && defined(MAGNUM_TARGET_DESKTOP_GLES) +#elif defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_EGL) #ifdef MAGNUM_TARGET_GLES2 #include "MagnumExternal/OpenGL/GLES2/flextGLWindowsDesktop.h" #else diff --git a/src/Magnum/GL/OpenGLTester.h b/src/Magnum/GL/OpenGLTester.h index 576175ff2..1ff80c326 100644 --- a/src/Magnum/GL/OpenGLTester.h +++ b/src/Magnum/GL/OpenGLTester.h @@ -37,25 +37,17 @@ #include "Magnum/GL/Renderer.h" #include "Magnum/GL/TimeQuery.h" -#if defined(MAGNUM_TARGET_HEADLESS) || defined(CORRADE_TARGET_EMSCRIPTEN) || defined(CORRADE_TARGET_ANDROID) +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_DESKTOP_GLES) #include "Magnum/Platform/WindowlessWglApplication.h" #else -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" -#endif -#else #error cannot run OpenGL tests on this platform #endif @@ -98,7 +90,7 @@ See @ref building, @ref cmake and @ref testsuite for more information. Implicitly, running the test executables requires presence of a GPU with OpenGL drivers. In addition, on desktop, unless Magnum is built with -`MAGNUM_TARGET_HEADLESS`, OpenGL context creation requires a graphical desktop +@ref MAGNUM_TARGET_EGL, OpenGL context creation requires a graphical desktop to be running. On embedded systems (and @ref CORRADE_TARGET_IOS "iOS", @ref CORRADE_TARGET_ANDROID "Android" in particular) running the tests has no special requirements. On Emscripten the tests have to be running in a browser, diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 8708fa4b9..3167ffe0e 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,7 +27,9 @@ #include "Shader.h" #include +#ifdef MAGNUM_BUILD_DEPRECATED #include +#endif #ifndef MAGNUM_TARGET_WEBGL #include #endif @@ -642,7 +645,7 @@ Int Shader::maxCombinedUniformComponents(const Type type) { } #endif -Shader::Shader(const Version version, const Type type): _type(type), _id(0) { +Shader::Shader(const Version version, const Type type): _type{type}, _flags{ObjectFlag::DeleteOnDestruction|ObjectFlag::Created} { _id = glCreateShader(GLenum(_type)); switch(version) { @@ -675,9 +678,11 @@ Shader::Shader(const Version version, const Type type): _type(type), _id(0) { CORRADE_ASSERT_UNREACHABLE("GL::Shader::Shader(): unsupported version" << version, ); } +Shader::Shader(const Type type, const GLuint id, ObjectFlags flags) noexcept: _type{type}, _id{id}, _flags{flags} {} + Shader::~Shader() { - /* Moved out, nothing to do */ - if(!_id) return; + /* Moved out or not deleting on destruction, nothing to do */ + if(!_id || !(_flags & ObjectFlag::DeleteOnDestruction)) return; glDeleteShader(_id); } @@ -747,75 +752,84 @@ Shader& Shader::addFile(const std::string& filename) { return *this; } -bool Shader::compile() { return compile({*this}); } +bool Shader::compile() { + submitCompile(); + return checkCompile(); +} -bool Shader::compile(std::initializer_list> shaders) { - bool allSuccess = true; +void Shader::submitCompile() { + CORRADE_ASSERT(_sources.size() > 1, "GL::Shader::compile(): no files added", ); - /* Allocate large enough array for source pointers and sizes (to avoid - reallocating it for each of them) */ - std::size_t maxSourceCount = 0; - for(Shader& shader: shaders) { - CORRADE_ASSERT(shader._sources.size() > 1, "GL::Shader::compile(): no files added", false); - maxSourceCount = Math::max(shader._sources.size(), maxSourceCount); - } /** @todo ArrayTuple/VLAs */ - Containers::Array pointers(maxSourceCount); - Containers::Array sizes(maxSourceCount); + Containers::Array pointers(_sources.size()); + Containers::Array sizes(_sources.size()); /* Upload sources of all shaders */ - for(Shader& shader: shaders) { - for(std::size_t i = 0; i != shader._sources.size(); ++i) { - pointers[i] = static_cast(shader._sources[i].data()); - sizes[i] = shader._sources[i].size(); - } - - glShaderSource(shader._id, shader._sources.size(), pointers, sizes); + for(std::size_t i = 0; i != _sources.size(); ++i) { + pointers[i] = static_cast(_sources[i].data()); + sizes[i] = _sources[i].size(); } - /* Invoke (possibly parallel) compilation on all shaders */ - for(Shader& shader: shaders) glCompileShader(shader._id); - - /* After compilation phase, check status of all shaders */ - Int i = 1; - for(Shader& shader: shaders) { - GLint success, logLength; - glGetShaderiv(shader._id, GL_COMPILE_STATUS, &success); - glGetShaderiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\0'); - if(message.size() > 1) - glGetShaderInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shader.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + glShaderSource(_id, _sources.size(), pointers, sizes); + glCompileShader(_id); +} + +bool Shader::checkCompile() { + GLint success, logLength; + glGetShaderiv(_id, GL_COMPILE_STATUS, &success); + glGetShaderiv(_id, GL_INFO_LOG_LENGTH, &logLength); + + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\0'); + if(message.size() > 1) + glGetShaderInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); + + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shader.cleanLogImplementation(message); + + /* Usually the driver messages contain a newline at the end. But sometimes + not, such as in case of a program link error due to shaders not being + compiled yet on Mesa; sometimes there's two newlines, sometimes just a + newline and nothing else etc. Because trying do this in driver-specific + workarounds would involve an impossible task of checking all possible + error messages on every possible driver, just trim all whitespace around + the message always and let Debug add its own newline. */ + const Containers::StringView messageTrimmed = Containers::StringView{message}.trimmed(); + + /* Show error log */ + if(!success) { + Error{} << "GL::Shader::compile(): compilation of" << shaderName(_type) + << "shader failed with the following message:" << Debug::newline + << messageTrimmed; + + /* Or just warnings, if any */ + } else if(messageTrimmed) { + Warning{} << "GL::Shader::compile(): compilation of" << shaderName(_type) + << "shader succeeded with the following message:" << Debug::newline + << messageTrimmed; } + return success; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +bool Shader::compile(std::initializer_list> shaders) { + /* Invoke (possibly parallel) compilation on all shaders */ + for(Shader& shader: shaders) shader.submitCompile(); + bool allSuccess = true; + for(Shader& shader: shaders) allSuccess = allSuccess && shader.checkCompile(); return allSuccess; } +#endif + +bool Shader::isCompileFinished() { + GLint success; + Context::current().state().shader.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} void Shader::cleanLogImplementationNoOp(std::string&) {} @@ -825,6 +839,10 @@ void Shader::cleanLogImplementationIntelWindows(std::string& message) { } #endif +void Shader::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + #ifndef DOXYGEN_GENERATING_OUTPUT Debug& operator<<(Debug& debug, const Shader::Type value) { debug << "GL::Shader::Type" << Debug::nospace; diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index 99984dc64..da8943c44 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -38,6 +39,7 @@ #include "Magnum/GL/GL.h" #ifdef MAGNUM_BUILD_DEPRECATED +#include /* For label() / setLabel(), which used to be a std::string. Not ideal for the return type, but at least something. */ #include @@ -507,19 +509,39 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static Int maxCombinedUniformComponents(Type type); #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Compile multiple shaders simultaneously + * @m_deprecated_since_latest Originally meant to batch multiple + * compile operations together in a way that allowed the driver to + * perform the compilation in multiple threads. Superseded by + * @ref submitCompile() and @ref checkCompile(), use either those + * or the zero-argument @ref compile() instead. See + * @ref GL-AbstractShaderProgram-async for more information. + * + * Calls @ref submitCompile() on all shaders first, then + * @ref checkCompile(). Returns @cpp false @ce if compilation of any + * shader failed, @cpp true @ce if everything succeeded. + */ + static CORRADE_DEPRECATED("use either submitCompile() and checkCompile() or the zero-argument compile() instead") bool compile(std::initializer_list> shaders); + #endif + + /** + * @brief Wrap existing OpenGL shader object + * @param type Shader type + * @param id OpenGL shader ID + * @param flags Object creation flags + * @m_since_latest * - * Returns @cpp false @ce if compilation of any shader failed, - * @cpp true @ce if everything succeeded. Compiler messages (if any) - * are printed to error output. The operation is batched in a way that - * allows the driver to perform multiple compilations simultaneously - * (i.e. in multiple threads). - * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader}, - * @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and - * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} + * The @p id is expected to be of an existing OpenGL shader object. + * Unlike a shader created using a constructor, the OpenGL object is by + * default not deleted on destruction, use @p flags for different + * behavior. + * @see @ref release() */ - static bool compile(std::initializer_list> shaders); + static Shader wrap(Type type, GLuint id, ObjectFlags flags = {}) { + return Shader{type, id, flags}; + } /** * @brief Constructor @@ -530,7 +552,8 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * corresponding to @p version parameter at the beginning. If * @ref Version::None is specified, (not) adding the @glsl #version @ce * directive is left to the user. - * @see @fn_gl_keyword{CreateShader} + * @see @ref Shader(NoCreateT), @ref wrap(), + * @fn_gl_keyword{CreateShader} */ explicit Shader(Version version, Type type); @@ -559,7 +582,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { * @brief Destructor * * Deletes associated OpenGL shader. - * @see @fn_gl_keyword{DeleteShader} + * @see @ref wrap(), @ref release(), @fn_gl_keyword{DeleteShader} */ ~Shader(); @@ -572,6 +595,18 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** @brief OpenGL shader ID */ GLuint id() const { return _id; } + /** + * @brief Release the underlying OpenGL object + * @m_since_latest + * + * Releases ownership of the OpenGL shader object and returns its ID so + * it's not deleted on destruction. The internal state is then + * equivalent to a moved-from state. + * @see @ref wrap() + */ + /* MinGW complains loudly if the declaration doesn't also have inline */ + inline GLuint release(); + #ifndef MAGNUM_TARGET_WEBGL /** * @brief Shader label @@ -634,16 +669,80 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { Shader& addFile(const std::string& filename); /** - * @brief Compile shader + * @brief Compile the shader * - * Compiles single shader. Prefer to compile multiple shaders at once - * using @ref compile(std::initializer_list>) - * for improved performance, see its documentation for more - * information. + * Calls @ref submitCompile(), immediately followed by + * @ref checkCompile(), passing back its return value. See + * documentation of those two functions for details. */ bool compile(); + /** + * @brief Submit the shader for compilation + * @m_since_latest + * + * You can call @ref isCompileFinished() or @ref checkCompile() after, + * but it's recommended to instead immediately call + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()}, then optionally + * continue with @relativeref{AbstractShaderProgram,isLinkFinished()} + * and pass all input shaders to + * @relativeref{AbstractShaderProgram,checkLink()} on the final program + * --- if compilation would fail, subsequent linking will as well, and + * @relativeref{AbstractShaderProgram,checkLink()} will print the + * compilation error if linking failed due to that. See + * @ref GL-AbstractShaderProgram-async for more information. + * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} + */ + void submitCompile(); + + /** + * @brief Check shader compilation status and await completion + * @m_since_latest + * + * Has to be called only if @ref submitCompile() was called before. + * It's however recommended to instead immediately call + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()}, then optionally + * continue with @relativeref{AbstractShaderProgram,isLinkFinished()} + * and pass all input shaders to + * @relativeref{AbstractShaderProgram,checkLink()} on the final program + * --- if compilation would fail, subsequent linking will as well, and + * @relativeref{AbstractShaderProgram,checkLink()} will print the + * compilation error if linking failed due to that. See + * @ref GL-AbstractShaderProgram-async for more information. + * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and + * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} + */ + bool checkCompile(); + + /** + * @brief Whether a @ref submitCompile() operation has finished + * @m_since_latest + * + * Has to be called only if @ref submitCompile() was called before, and + * before @ref checkCompile(). If returns @cpp false @ce, a subsequent + * @ref checkCompile() call will block until the compilation is + * finished. If @gl_extension{KHR,parallel_shader_compile} is not + * available, the function always returns @cpp true @ce --- i.e., as if + * the compilation was done synchronously. + * + * It's however recommended to wait only for the final link to finish, + * and not for particular compilations --- i.e., right after + * @ref submitCompile() continue with + * @ref AbstractShaderProgram::attachShader() and + * @relativeref{AbstractShaderProgram,submitLink()}, and then check + * with @relativeref{AbstractShaderProgram,isLinkFinished()} on the + * final program. See @ref GL-AbstractShaderProgram-async for more + * information. + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + */ + bool isCompileFinished(); + private: + explicit Shader(Type type, GLuint id, ObjectFlags flags) noexcept; + void MAGNUM_GL_LOCAL addSourceImplementationDefault(std::string source); #if defined(CORRADE_TARGET_EMSCRIPTEN) && defined(__EMSCRIPTEN_PTHREADS__) void MAGNUM_GL_LOCAL addSourceImplementationEmscriptenPthread(std::string source); @@ -654,8 +753,11 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationIntelWindows(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + Type _type; GLuint _id; + ObjectFlags _flags; std::vector _sources; }; @@ -663,7 +765,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** @debugoperatorclassenum{Shader,Shader::Type} */ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, Shader::Type value); -inline Shader::Shader(Shader&& other) noexcept: _type(other._type), _id(other._id), _sources(std::move(other._sources)) { +inline Shader::Shader(Shader&& other) noexcept: _type{other._type}, _id{other._id}, _flags{other._flags}, _sources{std::move(other._sources)} { other._id = 0; } @@ -671,10 +773,17 @@ inline Shader& Shader::operator=(Shader&& other) noexcept { using std::swap; swap(_type, other._type); swap(_id, other._id); + swap(_flags, other._flags); swap(_sources, other._sources); return *this; } +inline GLuint Shader::release() { + const GLuint id = _id; + _id = 0; + return id; +} + }} #endif diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 658fa112e..201e5fee4 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -24,11 +24,14 @@ */ #include +#include #include #include /** @todo remove when Shader is -free */ #include +#include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -68,8 +71,12 @@ struct AbstractShaderProgramGLTest: OpenGLTester { #ifndef MAGNUM_TARGET_GLES void createMultipleOutputsIndexed(); #endif + void createAsync(); void linkFailure(); + void linkFailureAsync(); + void linkFailureAsyncShaderList(); + void uniformNotFound(); void uniform(); @@ -103,12 +110,15 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() { #endif &AbstractShaderProgramGLTest::create, + &AbstractShaderProgramGLTest::createAsync, &AbstractShaderProgramGLTest::createMultipleOutputs, #ifndef MAGNUM_TARGET_GLES &AbstractShaderProgramGLTest::createMultipleOutputsIndexed, #endif &AbstractShaderProgramGLTest::linkFailure, + &AbstractShaderProgramGLTest::linkFailureAsync, + &AbstractShaderProgramGLTest::linkFailureAsyncShaderList, &AbstractShaderProgramGLTest::uniformNotFound, &AbstractShaderProgramGLTest::uniform, @@ -205,6 +215,8 @@ struct MyPublicShader: AbstractShaderProgram { using AbstractShaderProgram::bindFragmentDataLocation; #endif using AbstractShaderProgram::link; + using AbstractShaderProgram::submitLink; + using AbstractShaderProgram::checkLink; using AbstractShaderProgram::uniformLocation; #ifndef MAGNUM_TARGET_GLES2 using AbstractShaderProgram::uniformBlockIndex; @@ -257,6 +269,80 @@ void AbstractShaderProgramGLTest::create() { MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_VERIFY(linked); + + // Some drivers need a bit of time to update this result + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(valid); + } + + const Int matrixUniform = program.uniformLocation("matrix"); + const Int multiplierUniform = program.uniformLocation("multiplier"); + const Int colorUniform = program.uniformLocation("color"); + const Int additionsUniform = program.uniformLocation("additions"); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(matrixUniform >= 0); + CORRADE_VERIFY(multiplierUniform >= 0); + CORRADE_VERIFY(colorUniform >= 0); + CORRADE_VERIFY(additionsUniform >= 0); +} + +void AbstractShaderProgramGLTest::createAsync() { + Utility::Resource rs("AbstractShaderProgramGLTest"); + + Shader vert( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Vertex); + vert.addSource(rs.getString("MyShader.vert")); + const bool vertCompiled = vert.compile(); + + Shader frag( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + frag.addSource(rs.getString("MyShader.frag")); + const bool fragCompiled = frag.compile(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(vertCompiled); + CORRADE_VERIFY(fragCompiled); + + MyPublicShader program; + program.attachShaders({vert, frag}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + program.bindAttributeLocation(0, "position"); + program.submitLink(); + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(program.checkLink({vert, frag})); + CORRADE_VERIFY(program.isLinkFinished()); + const bool valid = program.validate().first; + + MAGNUM_VERIFY_NO_GL_ERROR(); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); @@ -402,6 +488,7 @@ void AbstractShaderProgramGLTest::linkFailure() { , Shader::Type::Fragment); shader.addSource("[fu] bleh error #:! stuff\n"); + /* The compilation should fail */ { Error redirectError{nullptr}; CORRADE_VERIFY(!shader.compile()); @@ -409,7 +496,125 @@ void AbstractShaderProgramGLTest::linkFailure() { MyPublicShader program; program.attachShaders({shader}); - CORRADE_VERIFY(!program.link()); + + /* And thus linking as well, saying something like "error: linking with + uncompiled/unspecialized shader" */ + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.link()); + } + + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringHasPrefix); +} + +void AbstractShaderProgramGLTest::linkFailureAsync() { + Shader shader( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + /* The compilation should fail */ + { + Error redirectError{nullptr}; + CORRADE_VERIFY(!shader.compile()); + } + + MyPublicShader program; + program.attachShaders({shader}); + + /* The link submission should not print anything ... */ + std::ostringstream out; + { + Error redirectError{&out}; + program.submitLink(); + } + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); + + /* ... only the final check should. In this case it's "error: linking with + uncompiled/unspecialized shader" as well, but if the shaders would be + supplied like in linkFailureAsyncShaderList() below, it'd print the + shader failure instead. */ + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.checkLink({})); + } + CORRADE_VERIFY(program.isLinkFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringHasPrefix); +} + +void AbstractShaderProgramGLTest::linkFailureAsyncShaderList() { + Shader vert( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Vertex); + vert.addSource("void main() {}\n"); + + Shader frag( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + frag.addSource("[fu] bleh error #:! stuff\n"); + + vert.submitCompile(); + frag.submitCompile(); + + MyPublicShader program; + program.attachShaders({vert, frag}); + + /* The link submission should not print anything ... */ + { + std::ostringstream out; + Error redirectError{&out}; + program.submitLink(); + CORRADE_VERIFY(out.str().empty()); + } + + /* ... only the final check should. Vertex shader should be fine, but + fragment should fail. */ + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!program.checkLink({vert, frag})); + } + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); + + /* The linker error (which would most probably say something like "error: + linking with uncompiled/unspecialized shader") should not be even + printed */ + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringNotContains); } void AbstractShaderProgramGLTest::uniformNotFound() { @@ -447,7 +652,8 @@ void AbstractShaderProgramGLTest::uniformNotFound() { #endif ); - CORRADE_VERIFY(Shader::compile({vert, frag})); + CORRADE_VERIFY(vert.compile() && frag.compile()); + program.attachShaders({vert, frag}); CORRADE_VERIFY(program.link()); @@ -497,10 +703,10 @@ MyShader::MyShader() { Version::GLES200 #endif , Shader::Type::Fragment); - vert.addSource(rs.getString("MyShader.vert")); - frag.addSource(rs.getString("MyShader.frag")); - - Shader::compile({vert, frag}); + vert.addSource(rs.getString("MyShader.vert")) + .compile(); + frag.addSource(rs.getString("MyShader.frag")) + .compile(); attachShaders({vert, frag}); @@ -581,10 +787,10 @@ MyDoubleShader::MyDoubleShader() { Shader vert(Version::GL320, Shader::Type::Vertex); Shader frag(Version::GL320, Shader::Type::Fragment); - vert.addSource(rs.getString("MyDoubleShader.vert")); - frag.addSource(rs.getString("MyDoubleShader.frag")); - - Shader::compile({vert, frag}); + vert.addSource(rs.getString("MyDoubleShader.vert")) + .compile(); + frag.addSource(rs.getString("MyDoubleShader.frag")) + .compile(); attachShaders({vert, frag}); @@ -743,8 +949,8 @@ void AbstractShaderProgramGLTest::uniformBlockIndexNotFound() { vert.addSource("void main() { gl_Position = vec4(0.0); }"); frag.addSource("out lowp vec4 color;\n" "void main() { color = vec4(1.0); }"); + CORRADE_VERIFY(vert.compile() && frag.compile()); - CORRADE_VERIFY(Shader::compile({vert, frag})); program.attachShaders({vert, frag}); CORRADE_VERIFY(program.link()); @@ -784,10 +990,11 @@ UniformBlockShader::UniformBlockShader() { Version::GLES300 #endif , Shader::Type::Fragment); - vert.addSource(rs.getString("UniformBlockShader.vert")); - frag.addSource(rs.getString("UniformBlockShader.frag")); + vert.addSource(rs.getString("UniformBlockShader.vert")) + .compile(); + frag.addSource(rs.getString("UniformBlockShader.frag")) + .compile(); - Shader::compile({vert, frag}); attachShaders({vert, frag}); link(); diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index 85137729b..01c17f3ea 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -25,6 +25,7 @@ */ #include +#include #include #include #include @@ -1023,7 +1024,7 @@ FloatShader::FloatShader(const std::string& type, const std::string& conversion) "#endif\n" "void main() { result = " + conversion + "; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -1065,7 +1066,7 @@ IntegerShader::IntegerShader(const std::string& type) { "out mediump " + type + " result;\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -1100,7 +1101,7 @@ DoubleShader::DoubleShader(const std::string& type, const std::string& outputTyp "out " + outputType + " result;\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -2147,7 +2148,7 @@ MultipleShader::MultipleShader() { "#endif\n" "void main() { result = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -3836,7 +3837,7 @@ MultiDrawShader::MultiDrawShader(bool vertexId, bool drawId) { "#endif\n" "void main() { result.r = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); @@ -4641,7 +4642,7 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId "#endif\n" "void main() { result.r = valueInterpolated; }\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/RendererGLTest.cpp b/src/Magnum/GL/Test/RendererGLTest.cpp index fd8084748..e9818be2c 100644 --- a/src/Magnum/GL/Test/RendererGLTest.cpp +++ b/src/Magnum/GL/Test/RendererGLTest.cpp @@ -23,9 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include -#include #include #include @@ -203,7 +203,7 @@ void RendererGLTest::pointCoord() { color = vec4(gl_PointCoord.x, gl_PointCoord.y, 0.0, 1.0); })GLSL"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/SampleQueryGLTest.cpp b/src/Magnum/GL/Test/SampleQueryGLTest.cpp index 1929de2fb..1481db0c4 100644 --- a/src/Magnum/GL/Test/SampleQueryGLTest.cpp +++ b/src/Magnum/GL/Test/SampleQueryGLTest.cpp @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include #include #include @@ -207,7 +207,7 @@ MyShader::MyShader() { " color = vec4(1.0, 1.0, 1.0, 1.0);\n" "}\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/GL/Test/ShaderGLTest.cpp b/src/Magnum/GL/Test/ShaderGLTest.cpp index 5c7978b77..ba8194c05 100644 --- a/src/Magnum/GL/Test/ShaderGLTest.cpp +++ b/src/Magnum/GL/Test/ShaderGLTest.cpp @@ -23,8 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include /** @todo remove once Shader is -free */ +#include #include +#include #include #include "Magnum/GL/Context.h" @@ -46,6 +49,7 @@ struct ShaderGLTest: OpenGLTester { void construct(); void constructNoVersion(); void constructMove(); + void wrap(); #ifndef MAGNUM_TARGET_WEBGL void label(); @@ -55,6 +59,9 @@ struct ShaderGLTest: OpenGLTester { void addSourceNoVersion(); void addFile(); void compile(); + void compileAsync(); + void compileFailure(); + void compileFailureAsync(); void compileUtf8(); void compileNoVersion(); }; @@ -63,6 +70,7 @@ ShaderGLTest::ShaderGLTest() { addTests({&ShaderGLTest::construct, &ShaderGLTest::constructNoVersion, &ShaderGLTest::constructMove, + &ShaderGLTest::wrap, #ifndef MAGNUM_TARGET_WEBGL &ShaderGLTest::label, @@ -72,6 +80,9 @@ ShaderGLTest::ShaderGLTest() { &ShaderGLTest::addSourceNoVersion, &ShaderGLTest::addFile, &ShaderGLTest::compile, + &ShaderGLTest::compileAsync, + &ShaderGLTest::compileFailure, + &ShaderGLTest::compileFailureAsync, &ShaderGLTest::compileUtf8, &ShaderGLTest::compileNoVersion}); } @@ -151,6 +162,20 @@ void ShaderGLTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +void ShaderGLTest::wrap() { + GLuint id = glCreateShader(GL_FRAGMENT_SHADER); + + /* Releasing won't delete anything */ + { + auto shader = Shader::wrap(Shader::Type::Fragment, id, ObjectFlag::DeleteOnDestruction); + CORRADE_COMPARE(shader.release(), id); + } + + /* ...so we can wrap it again */ + Shader::wrap(Shader::Type::Fragment, id); + glDeleteShader(id); +} + #ifndef MAGNUM_TARGET_WEBGL void ShaderGLTest::label() { /* No-Op version is tested in AbstractObjectGLTest */ @@ -276,11 +301,97 @@ void ShaderGLTest::compile() { Shader shader(v, Shader::Type::Fragment); shader.addSource("void main() {}\n"); + CORRADE_VERIFY(shader.compile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileAsync() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("void main() {}\n"); + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(shader.checkCompile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileFailure() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Vertex); + shader.addSource("[fu] bleh error #:! stuff\n"); + + std::ostringstream out; + { + Error redirectError{&out}; + CORRADE_VERIFY(!shader.compile()); + } + CORRADE_VERIFY(shader.isCompileFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of vertex shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); +} + +void ShaderGLTest::compileFailureAsync() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + /* The compile submission should not print anything ... */ + std::ostringstream out; + { + Error redirectError{&out}; + shader.submitCompile(); + } - Shader shader2(v, Shader::Type::Fragment); - shader2.addSource("[fu] bleh error #:! stuff\n"); - CORRADE_VERIFY(!shader2.compile()); + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); + + /* ... only the final check should */ + { + Error redirectError{&out}; + CORRADE_VERIFY(!shader.checkCompile()); + } + CORRADE_VERIFY(shader.isCompileFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void ShaderGLTest::compileUtf8() { diff --git a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp index 25a3b008f..08eba636a 100644 --- a/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp +++ b/src/Magnum/GL/Test/TransformFeedbackGLTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include "Magnum/Image.h" #include "Magnum/GL/AbstractShaderProgram.h" @@ -635,7 +635,8 @@ void TransformFeedbackGLTest::draw() { else geom.addSource( " EmitVertex();\n"); geom.addSource("}\n"); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, geom})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && geom.compile()); + attachShaders({vert, geom}); setTransformFeedbackOutputs({"geomOutput"}, TransformFeedbackBufferMode::SeparateAttributes); CORRADE_INTERNAL_ASSERT_OUTPUT(link()); @@ -696,8 +697,8 @@ void TransformFeedbackGLTest::draw() { "void main() {\n" " outputData = interleaved.x + 2*interleaved.y;\n" "}\n"); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); - CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); attachShaders({vert, frag}); bindAttributeLocation(Input::Location, "inputData"); CORRADE_INTERNAL_ASSERT_OUTPUT(link()); diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 0210090c0..ef8a65ad1 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -131,7 +131,7 @@ Defined if the engine is built with OpenGL interoperability enabled --- extra APIs in various libraries interacting with the @ref Magnum::GL "GL" library. Enabled by default in case the @ref Magnum::GL "GL" library is built. @see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, - @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, @ref cmake + @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GL /* (enabled by default) */ @@ -141,7 +141,7 @@ Enabled by default in case the @ref Magnum::GL "GL" library is built. Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0. @see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, - @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, @ref cmake + @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES #undef MAGNUM_TARGET_GLES @@ -151,7 +151,7 @@ Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0. Defined if the engine is built for OpenGL ES 2.0. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES3, @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, +@see @ref MAGNUM_TARGET_GLES3, @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES2 @@ -162,23 +162,12 @@ Defined if the engine is built for OpenGL ES 2.0. Implies also Defined if the engine is built for OpenGL ES 3.0. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_DESKTOP_GLES, @ref building, +@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_EGL, @ref building, @ref cmake */ #define MAGNUM_TARGET_GLES3 #undef MAGNUM_TARGET_GLES3 -/** -@brief Desktop emulation of OpenGL ES target - -Defined if the engine is built for OpenGL ES 3.0 or OpenGL ES 2.0 emulated -within standard desktop OpenGL. Implies also @ref MAGNUM_TARGET_GLES. -@see @ref MAGNUM_TARGET_GLES2, @ref MAGNUM_TARGET_GLES3, @ref building, - @ref cmake -*/ -#define MAGNUM_TARGET_DESKTOP_GLES -#undef MAGNUM_TARGET_DESKTOP_GLES - /** @brief WebGL target @@ -193,19 +182,48 @@ which you might want to be aware of. Implies also @ref MAGNUM_TARGET_GLES and #define MAGNUM_TARGET_WEBGL #undef MAGNUM_TARGET_WEBGL +/** +@brief EGL target +@m_since_latest + +Defined if the engine is built for EGL instead of a platform-specific OpenGL +support library like CGL, EAGL, GLX or WGL. When enabled, +@relativeref{Magnum,Platform::Sdl2Application} and +@relativeref{Magnum,Platform::GlfwApplication} +will create the context using EGL, and command-line utilities like +@ref magnum-gl-info "magnum-gl-info" or +@ref magnum-distancefieldconverter "magnum-distancefieldconverter" as well as +the @relativeref{Magnum,GL::OpenGLTester} library will use +@relativeref{Magnum,Platform::WindowlessEglApplication}. Defined implicitly on +@ref CORRADE_TARGET_IOS "iOS", @ref CORRADE_TARGET_ANDROID "Android", +@ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" and +@ref CORRADE_TARGET_WINDOWS_RT "Windows RT". +*/ +#define MAGNUM_TARGET_EGL +#undef MAGNUM_TARGET_EGL + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Headless target +@m_deprecated_since_latest Use @ref MAGNUM_TARGET_EGL instead. -Defined if the engine is built for use on a headless machine (without any -graphical desktop environment). Basically it means that EGL with no display -attachment is being used everywhere instead of platform-specific toolkits like -CGL, GLX or WGL. Note that this might not be supported on all platforms, see -@ref Magnum::Platform::WindowlessEglApplication "Platform::WindowlessEglApplication" -for more information. +Alias to @ref MAGNUM_TARGET_EGL, unless on iOS, Android, Emscripten or Windows +RT. */ #define MAGNUM_TARGET_HEADLESS #undef MAGNUM_TARGET_HEADLESS +/** +@brief OpenGL ES target on GLX / WGL +@m_deprecated_since_latest Use @ref MAGNUM_TARGET_EGL instead. + +Defined if @ref MAGNUM_TARGET_GLES is set but @ref MAGNUM_TARGET_EGL isn't, +unless on iOS, Android, Emscripten or Windows RT. +*/ +#define MAGNUM_TARGET_DESKTOP_GLES +#undef MAGNUM_TARGET_DESKTOP_GLES +#endif + /** @brief Vulkan interoperability diff --git a/src/Magnum/Math/Complex.h b/src/Magnum/Math/Complex.h index 382186861..38d706991 100644 --- a/src/Magnum/Math/Complex.h +++ b/src/Magnum/Math/Complex.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::Math::Complex, function @ref Magnum::Math::dot(), @ref Magnum::math::angle() + * @brief Class @ref Magnum::Math::Complex, function @ref Magnum::Math::dot(), @ref Magnum::Math::angle() */ #include diff --git a/src/Magnum/Math/FunctionsBatch.h b/src/Magnum/Math/FunctionsBatch.h index c52f03811..736a21d30 100644 --- a/src/Magnum/Math/FunctionsBatch.h +++ b/src/Magnum/Math/FunctionsBatch.h @@ -40,9 +40,9 @@ namespace Implementation { /** @todo Utility/Algorithms.h has a similar (but different) variant of this, maybe turn that into some public utility once we have one more use case? */ -template::type>::from(std::declval()))> static auto stridedArrayViewTypeFor(T&&) -> typename View::Type; -template static T stridedArrayViewTypeFor(const Corrade::Containers::ArrayView&); -template static T stridedArrayViewTypeFor(const Corrade::Containers::StridedArrayView1D&); +template::type>::from(std::declval()))> static auto stridedArrayViewTypeFor(T&&) -> typename std::remove_const::type; +template static typename std::remove_const::type stridedArrayViewTypeFor(const Corrade::Containers::ArrayView&); +template static typename std::remove_const::type stridedArrayViewTypeFor(const Corrade::Containers::StridedArrayView1D&); } diff --git a/src/Magnum/Math/Test/FunctionsBatchTest.cpp b/src/Magnum/Math/Test/FunctionsBatchTest.cpp index 5043edda8..253eb9b43 100644 --- a/src/Magnum/Math/Test/FunctionsBatchTest.cpp +++ b/src/Magnum/Math/Test/FunctionsBatchTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include @@ -44,6 +45,8 @@ struct FunctionsBatchTest: Corrade::TestSuite::Tester { void nanIgnoring(); void nanIgnoringVector(); + + void constIterable(); }; using namespace Literals; @@ -62,7 +65,9 @@ FunctionsBatchTest::FunctionsBatchTest() { &FunctionsBatchTest::minmax, &FunctionsBatchTest::nanIgnoring, - &FunctionsBatchTest::nanIgnoringVector}); + &FunctionsBatchTest::nanIgnoringVector, + + &FunctionsBatchTest::constIterable}); } void FunctionsBatchTest::isInf() { @@ -284,6 +289,20 @@ void FunctionsBatchTest::nanIgnoringVector() { CORRADE_COMPARE(Math::minmax(allNan).second[1], Constants::nan()); } +void FunctionsBatchTest::constIterable() { + const Vector2 data[]{{5, -3}, {-2, 14}, {9, -5}}; + + /* It shouldn't try to operate with a const type (such as trying to to + assign to `std::pair`) internally, instead + it should remove the const */ + CORRADE_COMPARE(Math::min(Corrade::Containers::arrayView(data)), + (Vector2{-2, -5})); + CORRADE_COMPARE(Math::max(Corrade::Containers::stridedArrayView(data)), + (Vector2{9, 14})); + CORRADE_COMPARE(Math::minmax(Corrade::Containers::Array{data, 3, [](const Vector2*, std::size_t){}}), + std::make_pair(Vector2{-2, -5}, Vector2{9, 14})); +} + }}}} CORRADE_TEST_MAIN(Magnum::Math::Test::FunctionsBatchTest) diff --git a/src/Magnum/Mesh.cpp b/src/Magnum/Mesh.cpp index 7fbcf5b8f..0e30d0538 100644 --- a/src/Magnum/Mesh.cpp +++ b/src/Magnum/Mesh.cpp @@ -25,7 +25,7 @@ #include "Mesh.h" -#include +#include #include #include #include @@ -105,28 +105,28 @@ UnsignedInt meshIndexTypeSize(const MeshIndexType type) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshPrimitiveNames)) return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::MeshPrimitive ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::MeshPrimitive ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshPrimitiveNames); ++i) if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i + 1); return {}; } -std::string ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshIndexTypeNames)) return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::MeshIndexType ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::MeshIndexType ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshIndexTypeNames); ++i) if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i + 1); diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 3902ba5c5..331754793 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -365,14 +364,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::MeshPrimitive value, ConfigurationValueFlags); + static Containers::String toString(Magnum::MeshPrimitive value, ConfigurationValueFlags); /** * @brief Reads enum value as string * * If the value is invalid, returns a zero (invalid) primitive. */ - static Magnum::MeshPrimitive fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::MeshPrimitive fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; /** @configurationvalue{Magnum::MeshIndexType} */ @@ -384,14 +383,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::MeshIndexType value, ConfigurationValueFlags); + static Containers::String toString(Magnum::MeshIndexType value, ConfigurationValueFlags); /** * @brief Read enum value as string * * If the value is invalid, returns a zero (invalid) type. */ - static Magnum::MeshIndexType fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::MeshIndexType fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; }} diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index 0ca545e2d..0accd4459 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -26,8 +26,7 @@ #include "Combine.h" #include -#include -#include +#include #include #include "Magnum/Math/Functions.h" @@ -44,20 +43,20 @@ Trade::MeshData combineIndexedImplementation( #ifndef CORRADE_NO_ASSERT const char* assertPrefix, #endif - const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::ArrayView> data) + const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::Iterable data) { /* Calculate attribute count and vertex stride */ UnsignedInt attributeCount = 0; UnsignedInt vertexStride = 0; for(std::size_t i = 0; i != data.size(); ++i) { - attributeCount += data[i]->attributeCount(); - for(UnsignedInt j = 0; j != data[i]->attributeCount(); ++j) { - const VertexFormat format = data[i]->attributeFormat(j); + attributeCount += data[i].attributeCount(); + for(UnsignedInt j = 0; j != data[i].attributeCount(); ++j) { + const VertexFormat format = data[i].attributeFormat(j); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), assertPrefix << "attribute" << j << "of mesh" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), (Trade::MeshData{MeshPrimitive::Points, 0})); - vertexStride += vertexFormatSize(format)*Math::max(data[i]->attributeArraySize(j), UnsignedShort{1}); + vertexStride += vertexFormatSize(format)*Math::max(data[i].attributeArraySize(j), UnsignedShort{1}); } } @@ -111,7 +110,7 @@ Trade::MeshData combineIndexedImplementation( } -Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { +Trade::MeshData combineIndexedAttributes(const Containers::Iterable data) { CORRADE_ASSERT(!data.isEmpty(), "MeshTools::combineIndexedAttributes(): no meshes passed", (Trade::MeshData{MeshPrimitive{}, 0})); @@ -124,21 +123,21 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayViewisIndexed(), + CORRADE_ASSERT(data[i].isIndexed(), "MeshTools::combineIndexedAttributes(): data" << i << "is not indexed", (Trade::MeshData{MeshPrimitive{}, 0})); - const MeshIndexType indexType = data[i]->indexType(); + const MeshIndexType indexType = data[i].indexType(); CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(indexType), "MeshTools::combineIndexedAttributes(): data" << i << "has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(indexType)), (Trade::MeshData{MeshPrimitive{}, 0})); if(i == 0) { - primitive = data[i]->primitive(); - indexCount = data[i]->indexCount(); + primitive = data[i].primitive(); + indexCount = data[i].indexCount(); } else { - CORRADE_ASSERT(data[i]->primitive() == primitive, - "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); - CORRADE_ASSERT(data[i]->indexCount() == indexCount, - "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i].primitive() == primitive, + "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i].primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i].indexCount() == indexCount, + "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i].indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); } indexStride += meshIndexTypeSize(indexType); } @@ -171,10 +170,6 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { - return combineIndexedAttributes(Containers::arrayView(data)); -} - Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes) { CORRADE_ASSERT(mesh.isIndexed(), "MeshTools::combineFaceAttributes(): vertex mesh is not indexed", @@ -194,12 +189,11 @@ Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade:: CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(meshIndexType), "MeshTools::combineFaceAttributes(): vertex mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(meshIndexType)), (Trade::MeshData{MeshPrimitive{}, 0})); - const UnsignedInt meshIndexSize = meshIndexTypeSize(mesh.indexType()); + const UnsignedInt meshIndexSize = meshIndexTypeSize(meshIndexType); UnsignedInt faceIndexSize; if(faceAttributes.isIndexed()) { - const MeshIndexType faceIndexType = faceAttributes.indexType(); - CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(faceIndexType), - "MeshTools::combineFaceAttributes(): face mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(faceIndexType)), + CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(faceAttributes.indexType()), + "MeshTools::combineFaceAttributes(): face mesh has an implementation-specific index type" << reinterpret_cast(meshIndexTypeUnwrap(faceAttributes.indexType())), (Trade::MeshData{MeshPrimitive{}, 0})); faceIndexSize = meshIndexTypeSize(faceAttributes.indexType()); } else faceIndexSize = 4; @@ -236,10 +230,9 @@ Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade:: #ifndef CORRADE_NO_ASSERT "MeshTools::combineFaceAttributes():", #endif - mesh.primitive(), combinedIndices, meshIndexCount, indexStride, - Containers::arrayView>({ + mesh.primitive(), combinedIndices, meshIndexCount, indexStride, { mesh, faceAttributes - })); + }); } Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, Containers::ArrayView faceAttributes) { diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h index bbceef152..fb0341802 100644 --- a/src/Magnum/MeshTools/Combine.h +++ b/src/Magnum/MeshTools/Combine.h @@ -35,6 +35,13 @@ #include "Magnum/MeshTools/visibility.h" #include "Magnum/Trade/Trade.h" +#ifdef MAGNUM_BUILD_DEPRECATED +/* combineIndexedAttributes() used to take an ArrayView>, + now it's through the Iterable class. Include it explicitly until people + learn to include it themselves. */ +#include +#endif + namespace Magnum { namespace MeshTools { /** @@ -88,13 +95,7 @@ implementation-specific format. @see @ref isMeshIndexTypeImplementationSpecific(), @ref isVertexFormatImplementationSpecific() */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data); - -/** - * @overload - * @m_since{2020,06} - */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list> data); +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::Iterable data); /** @brief Combine per-face attributes into an existing mesh diff --git a/src/Magnum/MeshTools/Concatenate.cpp b/src/Magnum/MeshTools/Concatenate.cpp index 34a0f4e73..67517cc7f 100644 --- a/src/Magnum/MeshTools/Concatenate.cpp +++ b/src/Magnum/MeshTools/Concatenate.cpp @@ -33,7 +33,7 @@ namespace Magnum { namespace MeshTools { namespace Implementation { -std::pair concatenateIndexVertexCount(Containers::ArrayView> meshes) { +std::pair concatenateIndexVertexCount(Containers::Iterable meshes) { UnsignedInt indexCount = 0; UnsignedInt vertexCount = 0; for(const Trade::MeshData& mesh: meshes) { @@ -62,7 +62,7 @@ struct MeshAttributeHash: std::hash&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Containers::ArrayView> meshes, const char* const assertPrefix) { +Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Containers::Iterable meshes, const char* const assertPrefix) { #ifdef CORRADE_NO_ASSERT static_cast(assertPrefix); #endif @@ -82,17 +82,17 @@ Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedI /** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when those are done */ CORRADE_ASSERT( - meshes.front()->primitive() != MeshPrimitive::LineStrip && - meshes.front()->primitive() != MeshPrimitive::LineLoop && - meshes.front()->primitive() != MeshPrimitive::TriangleStrip && - meshes.front()->primitive() != MeshPrimitive::TriangleFan, - assertPrefix << meshes.front()->primitive() << "is not supported, turn it into a plain indexed mesh first", + meshes.front().primitive() != MeshPrimitive::LineStrip && + meshes.front().primitive() != MeshPrimitive::LineLoop && + meshes.front().primitive() != MeshPrimitive::TriangleStrip && + meshes.front().primitive() != MeshPrimitive::TriangleFan, + assertPrefix << meshes.front().primitive() << "is not supported, turn it into a plain indexed mesh first", (Trade::MeshData{MeshPrimitive{}, 0})); /* Populate the resulting instance with what we have. It'll be used below for convenient access to vertex / index data */ auto indices = Containers::arrayCast(indexData); - Trade::MeshData out{meshes.front()->primitive(), + Trade::MeshData out{meshes.front().primitive(), /* If the index array is empty, we're creating a non-indexed mesh (not an indexed mesh with zero indices) */ std::move(indexData), indices.isEmpty() ? @@ -189,13 +189,13 @@ Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedI } -Trade::MeshData concatenate(const Containers::ArrayView> meshes, const InterleaveFlags flags) { +Trade::MeshData concatenate(const Containers::Iterable meshes, const InterleaveFlags flags) { CORRADE_ASSERT(!meshes.isEmpty(), "MeshTools::concatenate(): expected at least one mesh", (Trade::MeshData{MeshPrimitive::Points, 0})); #ifndef CORRADE_NO_ASSERT - for(std::size_t i = 0; i != meshes.front()->attributeCount(); ++i) { - const VertexFormat format = meshes.front()->attributeFormat(i); + for(std::size_t i = 0; i != meshes.front().attributeCount(); ++i) { + const VertexFormat format = meshes.front().attributeFormat(i); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), "MeshTools::concatenate(): attribute" << i << "of the first mesh has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), (Trade::MeshData{MeshPrimitive::Points, 0})); @@ -208,13 +208,13 @@ Trade::MeshData concatenate(const Containers::ArrayView attributeData; - if(meshes.front()->attributeCount()) - attributeData = Implementation::interleavedLayout(Trade::MeshData{meshes.front()->primitive(), - {}, meshes.front()->vertexData(), - Trade::meshAttributeDataNonOwningArray(meshes.front()->attributeData())}, {}, flags); + if(meshes.front().attributeCount()) + attributeData = Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), + {}, meshes.front().vertexData(), + Trade::meshAttributeDataNonOwningArray(meshes.front().attributeData())}, {}, flags); else attributeData = - Implementation::interleavedLayout(Trade::MeshData{meshes.front()->primitive(), - meshes.front()->vertexCount()}, {}, flags); + Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), + meshes.front().vertexCount()}, {}, flags); /* Calculate total index/vertex count and allocate the target memory. Index data are allocated with NoInit as the whole array will be written, @@ -227,8 +227,4 @@ Trade::MeshData concatenate(const Containers::ArrayView> meshes, const InterleaveFlags flags) { - return concatenate(Containers::arrayView(meshes), flags); -} - }} diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h index 5ba5c5d43..3f5323baf 100644 --- a/src/Magnum/MeshTools/Concatenate.h +++ b/src/Magnum/MeshTools/Concatenate.h @@ -31,7 +31,7 @@ */ #include -#include +#include #include "Magnum/MeshTools/Interleave.h" #include "Magnum/Trade/MeshData.h" @@ -39,8 +39,8 @@ namespace Magnum { namespace MeshTools { namespace Implementation { - MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(Containers::ArrayView> meshes); - MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, Containers::ArrayView> meshes, const char* assertPrefix); + MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(Containers::Iterable meshes); + MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, Containers::Iterable meshes, const char* assertPrefix); } /** @@ -82,13 +82,7 @@ to compress it to a smaller type, if desired. @ref SceneTools::flattenMeshHierarchy2D(), @ref SceneTools::flattenMeshHierarchy3D() */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::ArrayView> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); - -/** - * @overload - * @m_since{2020,06} - */ -MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(std::initializer_list> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Iterable meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); /** @brief Concatenate a list of meshes into a pre-existing destination, enlarging it if necessary @@ -99,14 +93,14 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(std::initializer_list>, InterleaveFlags) +Compared to @ref concatenate(Containers::Iterable, InterleaveFlags) this function resizes existing index and vertex buffers in @p destination using @ref Containers::arrayResize() and given @p allocator, and reuses its atttribute data array instead of always allocating new ones. Only the attribute layout from @p destination is used, all vertex/index data are taken from @p meshes. Expects that @p meshes contains at least one item. */ -template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, Containers::ArrayView> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, Containers::Iterable meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { CORRADE_ASSERT(!meshes.isEmpty(), "MeshTools::concatenateInto(): no meshes passed", ); #ifndef CORRADE_NO_ASSERT @@ -142,14 +136,6 @@ template class Allocator = Containers::ArrayAllocator> void conc destination = Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes, "MeshTools::concatenateInto():"); } -/** - * @overload - * @m_since{2020,06} - */ -template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, std::initializer_list> meshes, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes) { - concatenateInto(destination, Containers::arrayView(meshes), flags); -} - }} #endif diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 497623b9f..5cf734497 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -139,9 +139,8 @@ Containers::Array interleavedLayout(Trade::MeshData&& stride = 0; minOffset = 0; for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) { - const VertexFormat format = data.attributeFormat(i); - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), - "MeshTools::interleavedLayout(): attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(data.attributeFormat(i)), + "MeshTools::interleavedLayout(): attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(data.attributeFormat(i))), {}); stride += attributeSize(data, i); } } @@ -149,12 +148,12 @@ Containers::Array interleavedLayout(Trade::MeshData&& /* Add the extra attributes and explicit padding */ std::size_t extraAttributeCount = 0; for(std::size_t i = 0; i != extra.size(); ++i) { - if(extra[i].format() == VertexFormat{}) { + const VertexFormat format = extra[i].format(); + if(format == VertexFormat{}) { CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()), "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, {}); stride += extra[i].stride(); } else { - const VertexFormat format = extra[i].format(); CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), "MeshTools::interleavedLayout(): extra attribute" << i << "has an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); stride += attributeSize(extra[i]); diff --git a/src/Magnum/MeshTools/InterleaveFlags.h b/src/Magnum/MeshTools/InterleaveFlags.h index 2bf4387d5..d9437980a 100644 --- a/src/Magnum/MeshTools/InterleaveFlags.h +++ b/src/Magnum/MeshTools/InterleaveFlags.h @@ -43,7 +43,7 @@ namespace Magnum { namespace MeshTools { @see @ref InterleaveFlags, @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags), - @ref concatenate(Containers::ArrayView>, InterleaveFlags) + @ref concatenate(Containers::Iterable, InterleaveFlags) */ enum class InterleaveFlag: UnsignedInt { /** @@ -72,7 +72,7 @@ enum class InterleaveFlag: UnsignedInt { * * Has no effect when passed to @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags) "interleavedLayout()" * as that function doesn't preserve the index buffer. Has no effect when - * passed to @ref concatenate(Containers::ArrayView>, InterleaveFlags) "concatenate()" + * passed to @ref concatenate(Containers::Iterable, InterleaveFlags) "concatenate()" * as that function allocates a new combined index buffer anyway. * @see @ref isMeshIndexTypeImplementationSpecific() */ @@ -85,7 +85,7 @@ enum class InterleaveFlag: UnsignedInt { @see @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView, InterleaveFlags), @ref interleave(const Trade::MeshData&, Containers::ArrayView, InterleaveFlags), - @ref concatenate(Containers::ArrayView>, InterleaveFlags) + @ref concatenate(Containers::Iterable, InterleaveFlags) */ typedef Containers::EnumSet InterleaveFlags; diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp index 0f40e4639..cda4d9485 100644 --- a/src/Magnum/MeshTools/Test/CombineTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include #include diff --git a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp index 3bed21b7d..9fa791b77 100644 --- a/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp +++ b/src/Magnum/MeshTools/Test/FullScreenTriangleGLTest.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include @@ -102,7 +102,7 @@ void main() { } )"); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/PixelFormat.cpp b/src/Magnum/PixelFormat.cpp index da887735a..40afec253 100644 --- a/src/Magnum/PixelFormat.cpp +++ b/src/Magnum/PixelFormat.cpp @@ -25,7 +25,7 @@ #include "PixelFormat.h" -#include +#include #include #include #include @@ -556,14 +556,14 @@ Debug& operator<<(Debug& debug, const CompressedPixelFormat value) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::PixelFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::PixelFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::PixelFormatNames)) return Magnum::PixelFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::PixelFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::PixelFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { /** @todo This is extremely slow with >100 values. Do a binary search on a sorted index list instead (extracted into a common utility) */ for(std::size_t i = 0; i != Containers::arraySize(Magnum::PixelFormatNames); ++i) @@ -572,14 +572,14 @@ Magnum::PixelFormat ConfigurationValue::fromString(const st return {}; } -std::string ConfigurationValue::toString(Magnum::CompressedPixelFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::CompressedPixelFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::CompressedPixelFormatNames)) return Magnum::CompressedPixelFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::CompressedPixelFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::CompressedPixelFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { /** @todo This is extremely slow with >100 values. Do a binary search on a sorted index list instead (extracted into a common utility) */ for(std::size_t i = 0; i != Containers::arraySize(Magnum::CompressedPixelFormatNames); ++i) diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index 1cf1cd298..290f5aa28 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -2491,14 +2490,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::PixelFormat value, ConfigurationValueFlags); + static Containers::String toString(Magnum::PixelFormat value, ConfigurationValueFlags); /** * @brief Reads enum value as string * * If the value is invalid, returns a zero (invalid) format. */ - static Magnum::PixelFormat fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::PixelFormat fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; /** @@ -2513,14 +2512,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue) + find_package(EGL REQUIRED) + set(MagnumSomeContext_LIBRARY EGL::EGL) + elseif(CORRADE_TARGET_APPLE) set(NEED_CGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(CORRADE_TARGET_WINDOWS AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + elseif(CORRADE_TARGET_WINDOWS) set(NEED_WGLCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(CORRADE_TARGET_UNIX AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + elseif(CORRADE_TARGET_UNIX) set(NEED_GLXCONTEXT 1) set(MagnumSomeContext_OBJECTS $) - elseif(MAGNUM_TARGET_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN) - set(NEED_EGLCONTEXT 1) - set(MagnumSomeContext_OBJECTS $) - # We're linking to EGL explicitly, no need to bother with GLVND there endif() endif() # This is needed also by [Windowless]GlxApplication - if((MAGNUM_WITH_GLXAPPLICATION OR MAGNUM_WITH_WINDOWLESSGLXAPPLICATION OR MAGNUM_WITH_GLFWAPPLICATION OR MAGNUM_WITH_SDL2APPLICATION) AND CORRADE_TARGET_UNIX AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)) + if((MAGNUM_WITH_GLXAPPLICATION OR MAGNUM_WITH_WINDOWLESSGLXAPPLICATION OR MAGNUM_WITH_GLFWAPPLICATION OR MAGNUM_WITH_SDL2APPLICATION) AND CORRADE_TARGET_UNIX AND NOT MAGNUM_TARGET_EGL) # If the GLVND library (CMake 3.11+) was found and linked to, we need # to link to GLX explicitly. Otherwise (and also on all systems except # Linux) the transitive dependency to classic GL lib from MagnumGL is @@ -255,12 +261,6 @@ if(MAGNUM_WITH_GLFWAPPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if on ES (and not on WebGL) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT MAGNUM_TARGET_WEBGL) - find_package(EGL REQUIRED) - target_link_libraries(MagnumGlfwApplication PUBLIC EGL::EGL) - endif() - install(FILES ${MagnumGlfwApplication_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) install(TARGETS MagnumGlfwApplication RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} @@ -327,12 +327,6 @@ if(MAGNUM_WITH_SDL2APPLICATION) ${MagnumSomeContext_LIBRARY}) endif() - # Link also EGL library, if on ES (and not on WebGL) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT MAGNUM_TARGET_WEBGL) - find_package(EGL REQUIRED) - target_link_libraries(MagnumSdl2Application PUBLIC EGL::EGL) - endif() - install(FILES ${MagnumSdl2Application_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) install(TARGETS MagnumSdl2Application RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} @@ -623,44 +617,6 @@ if(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION) add_library(Magnum::WindowlessWglApplication ALIAS MagnumWindowlessWglApplication) endif() -# Windowless Windows/EGL application -if(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - if(NOT MAGNUM_TARGET_GL) - message(SEND_ERROR "WindowlessWindowsEglApplication is available only if MAGNUM_TARGET_GL is enabled") - endif() - - set(NEED_EGLCONTEXT 1) - - set(MagnumWindowlessWindowsEglApplication_SRCS - WindowlessWindowsEglApplication.cpp - Implementation/Egl.cpp - $) - set(MagnumWindowlessWindowsEglApplication_HEADERS - WindowlessWindowsEglApplication.h) - set(MagnumWindowlessWindowsEglApplication_PRIVATE_HEADERS - Implementation/Egl.h) - - add_library(MagnumWindowlessWindowsEglApplication STATIC - ${MagnumWindowlessWindowsEglApplication_SRCS} - ${MagnumWindowlessWindowsEglApplication_HEADERS} - ${MagnumWindowlessWindowsEglApplication_PRIVATE_HEADERS}) - set_target_properties(MagnumWindowlessWindowsEglApplication PROPERTIES - DEBUG_POSTFIX "-d") - if(NOT MAGNUM_BUILD_STATIC OR MAGNUM_BUILD_STATIC_PIC) - set_target_properties(MagnumWindowlessWindowsEglApplication PROPERTIES POSITION_INDEPENDENT_CODE ON) - endif() - target_link_libraries(MagnumWindowlessWindowsEglApplication PUBLIC MagnumGL EGL::EGL) - - install(FILES ${MagnumWindowlessWindowsEglApplication_HEADERS} DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Platform) - install(TARGETS MagnumWindowlessWindowsEglApplication - RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} - LIBRARY DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR} - ARCHIVE DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR}) - - # Magnum WindowlessWindowsEglApplication target alias for superprojects - add_library(Magnum::WindowlessWindowsEglApplication ALIAS MagnumWindowlessWindowsEglApplication) -endif() - # Windowless CGL application if(MAGNUM_WITH_WINDOWLESSCGLAPPLICATION) if(NOT MAGNUM_TARGET_GL) @@ -769,7 +725,7 @@ endif() if(NOT MAGNUM_TARGET_GLES) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GL/flextGLPlatform.cpp) elseif(MAGNUM_TARGET_GLES AND MAGNUM_TARGET_GLES2) - if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) + if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatformWindowsDesktop.cpp) elseif(CORRADE_TARGET_IOS) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatformIOS.cpp) @@ -777,7 +733,7 @@ elseif(MAGNUM_TARGET_GLES AND MAGNUM_TARGET_GLES2) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES2/flextGLPlatform.cpp) endif() elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_GLES2) - if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) + if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES3/flextGLPlatformWindowsDesktop.cpp) elseif(CORRADE_TARGET_IOS) list(APPEND MagnumContext_SRCS ../../MagnumExternal/OpenGL/GLES3/flextGLPlatformIOS.cpp) @@ -941,24 +897,16 @@ if(MAGNUM_WITH_GL_INFO) add_executable(magnum-gl-info gl-info.cpp) target_link_libraries(magnum-gl-info PRIVATE MagnumGL) - if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWindowsEglApplication) - else() - target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-gl-info PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-gl-info is not available on this platform. Set MAGNUM_WITH_GL_INFO to OFF to skip building it.") endif() diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 1852cf8fc..55da5ad12 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -457,10 +457,7 @@ bool GlfwApplication::tryCreate(const Configuration& configuration, const GLConf } #else glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - /* Force EGL on Windows and non-desktop GLES -- needed by ANGLE: - https://stackoverflow.com/a/58904181/4084782 . Might be useful on - other platforms as well (Mac?), not tested yet. */ - #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_DESKTOP_GLES) + #ifdef MAGNUM_TARGET_EGL /* Force EGL if desired */ glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); #endif #endif @@ -487,10 +484,7 @@ bool GlfwApplication::tryCreate(const Configuration& configuration, const GLConf #endif glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - /* Force EGL on Windows and non-desktop GLES -- needed by ANGLE: - https://stackoverflow.com/a/58904181/4084782 . Might be useful on - other platforms as well (Mac?), not tested yet. */ - #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_DESKTOP_GLES) + #ifdef MAGNUM_TARGET_EGL /* Force EGL if desired */ glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); #endif #endif diff --git a/src/Magnum/Platform/GlxApplication.h b/src/Magnum/Platform/GlxApplication.h index 7dbdd578c..b9d478557 100644 --- a/src/Magnum/Platform/GlxApplication.h +++ b/src/Magnum/Platform/GlxApplication.h @@ -43,8 +43,7 @@ namespace Magnum { namespace Platform { @m_keywords{Application} Application using pure X11 and GLX. Supports keyboard and mouse handling. -Available on desktop OpenGL and -@ref MAGNUM_TARGET_DESKTOP_GLES "OpenGL ES emulation on desktop" on Linux. +Available on desktop OpenGL and OpenGL ES using GLX on Linux. @section Platform-GlxApplication-bootstrap Bootstrap application diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index bdcd2e05f..7410c3ee6 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -139,10 +139,9 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): #ifdef SDL_HINT_NO_SIGNAL_HANDLERS SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); #endif - /* Available since 2.0.6, uses dedicated OpenGL ES drivers by default and a - desktop GLES context only if MAGNUM_TARGET_DESKTOP_GLES is defined as - well. */ - #if !defined(MAGNUM_TARGET_DESKTOP_GLES) && defined(SDL_HINT_OPENGL_ES_DRIVER) + /* Available since 2.0.6, uses dedicated OpenGL ES drivers if EGL is used, + and desktop GLES context otherwise. */ + #if defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) && defined(SDL_HINT_OPENGL_ES_DRIVER) SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); #endif /* Available since 2.0.8, disables compositor bypass on X11, which causes @@ -150,6 +149,10 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): #ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); #endif + /* Available since 2.0.12, use EGL if MAGNUM_TARGET_HEADLESS is enabled */ + #if defined(MAGNUM_TARGET_HEADLESS) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL) + SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1"); + #endif if(SDL_Init(SDL_INIT_VIDEO) < 0) { Error() << "Cannot initialize SDL:" << SDL_GetError(); diff --git a/src/Magnum/Platform/Test/CMakeLists.txt b/src/Magnum/Platform/Test/CMakeLists.txt index 8075b7fec..8e86d26ed 100644 --- a/src/Magnum/Platform/Test/CMakeLists.txt +++ b/src/Magnum/Platform/Test/CMakeLists.txt @@ -85,6 +85,9 @@ if(MAGNUM_WITH_GLFWAPPLICATION) # HiDPi.manifest not needed, as GLFW sets that on its own target_link_libraries(PlatformGlfwApplicationTest PRIVATE MagnumGlfwApplication Corrade::Main) # Window icon loading + if(NOT MAGNUM_WITH_TRADE) + message(FATAL_ERROR "GlfwApplication tests need the Trade library enabled") + endif() target_sources(PlatformGlfwApplicationTest PRIVATE ${Platform_RESOURCES}) target_link_libraries(PlatformGlfwApplicationTest PRIVATE MagnumTrade) if(CORRADE_TARGET_APPLE) @@ -112,6 +115,9 @@ if(MAGNUM_WITH_SDL2APPLICATION) endif() target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumSdl2Application Corrade::Main) # Window icon loading + if(NOT MAGNUM_WITH_TRADE) + message(FATAL_ERROR "GlfwApplication tests need the Trade library enabled") + endif() if(NOT CORRADE_TARGET_EMSCRIPTEN) target_sources(PlatformSdl2ApplicationTest PRIVATE ${Platform_RESOURCES}) target_link_libraries(PlatformSdl2ApplicationTest PRIVATE MagnumTrade) @@ -176,8 +182,3 @@ if(MAGNUM_WITH_WINDOWLESSWGLAPPLICATION) add_executable(PlatformWindowlessWglApplicationTest WindowlessWglApplicationTest.cpp) target_link_libraries(PlatformWindowlessWglApplicationTest PRIVATE MagnumWindowlessWglApplication) endif() - -if(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION) - add_executable(PlatformWindowlessWindowsEglApplicationTest WindowlessWindowsEglApplicationTest.cpp) - target_link_libraries(PlatformWindowlessWindowsEglApplicationTest PRIVATE MagnumWindowlessWindowsEglApplication) -endif() diff --git a/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp b/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp deleted file mode 100644 index f45937652..000000000 --- a/src/Magnum/Platform/Test/WindowlessWindowsEglApplicationTest.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 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 "Magnum/Platform/WindowlessWindowsEglApplication.h" - -namespace Magnum { namespace Platform { namespace Test { namespace { - -struct WindowlessWindowsEglApplicationTest: Platform::WindowlessApplication { - explicit WindowlessWindowsEglApplicationTest(const Arguments& arguments); - int exec() override { return 0; } -}; - -WindowlessWindowsEglApplicationTest::WindowlessWindowsEglApplicationTest(const Arguments& arguments): Platform::WindowlessApplication{arguments, NoCreate} { - Utility::Arguments args; - args.addSkippedPrefix("magnum", "engine-specific options") - .addBooleanOption("quiet").setHelp("quiet", "like --magnum-log quiet, but specified via a Context::Configuration instead") - .addBooleanOption("gpu-validation").setHelp("gpu-validation", "like --magnum-gpu-validation, but specified via a Context::Configuration instead") - .parse(arguments.argc, arguments.argv); - - Configuration conf; - if(args.isSet("quiet")) - conf.addFlags(Configuration::Flag::QuietLog); - /* No verbose logs in this app */ - if(args.isSet("gpu-validation")) - conf.addFlags(Configuration::Flag::GpuValidation); - createContext(conf); -} - -}}}} - -MAGNUM_WINDOWLESSAPPLICATION_MAIN(Magnum::Platform::Test::WindowlessWindowsEglApplicationTest) diff --git a/src/Magnum/Platform/WindowlessEglApplication.cpp b/src/Magnum/Platform/WindowlessEglApplication.cpp index 15b237dde..c492e1468 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.cpp +++ b/src/Magnum/Platform/WindowlessEglApplication.cpp @@ -39,6 +39,14 @@ #include "Implementation/Egl.h" +/* ANGLE's EGL on Windows needs an actual window */ +/** @todo investigate if this is still needed */ +#ifdef CORRADE_TARGET_WINDOWS +#define WIN32_LEAN_AND_MEAN 1 +#define VC_EXTRALEAN +#include +#endif + /* None of this is in the Emscripten emulation layer, so no need to include that there */ #ifndef MAGNUM_TARGET_WEBGL @@ -252,10 +260,44 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G } #endif + /* ANGLE's EGL on Windows needs to get a display from an actual + window. Elsewhere EGL_DEFAULT_DISPLAY is fine. */ + /** @todo investigate if this is still needed */ + #ifdef CORRADE_TARGET_WINDOWS + /* Register the window class (if not yet done) */ + WNDCLASSW wc; + if(!GetClassInfoW(GetModuleHandleW(nullptr), L"Magnum Windowless Application", &wc)) { + wc = WNDCLASSW{ + 0, + DefWindowProcW, + 0, + 0, + GetModuleHandleW(nullptr), + nullptr, + nullptr, + HBRUSH(COLOR_BACKGROUND), + nullptr, + L"Magnum Windowless Application" + }; + + if(!RegisterClassW(&wc)) { + Error() << "Platform::WindowlessWglContext: cannot create window class:" << GetLastError(); + return; + } + } + + /* Create the window */ + _window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", + WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); + + /* Initialize */ + _display = eglGetDisplay(GetDC(_window)); + #else if(!(_display = eglGetDisplay(EGL_DEFAULT_DISPLAY))) { Error{} << "Platform::WindowlessEglApplication::tryCreateContext(): cannot get default EGL display:" << Implementation::eglErrorString(eglGetError()); return; } + #endif } if(!eglInitialize(_display, nullptr, nullptr)) { @@ -489,7 +531,14 @@ WindowlessEglContext::WindowlessEglContext(const Configuration& configuration, G return; } - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #ifdef CORRADE_TARGET_WINDOWS + /* ANGLE's EGL on Windows needs has an actual window, and so it also needs + a surface */ + /** @todo investigate if this is still needed */ + if(!(_surface = eglCreateWindowSurface(_display, config, _window, nullptr))) + Error() << "Platform::WindowlessEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); + + #elif defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) /* Android Emulator can run with a SwiftShader GPU and thus needs some of the SwiftShader context creation workarounds. However, it's impossible to detect, as EGL_VERSION is always "1.4 Android META-EGL" and @@ -525,13 +574,25 @@ WindowlessEglContext::WindowlessEglContext(WindowlessEglContext&& other) noexcep #ifndef MAGNUM_TARGET_WEBGL _sharedContext{other._sharedContext}, #endif + #ifdef CORRADE_TARGET_WINDOWS + _window{other._window}, + #endif _display{other._display}, _context{other._context} + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) + , _surface{other._surface} + #endif { #ifndef MAGNUM_TARGET_WEBGL other._sharedContext = false; #endif + #ifdef CORRADE_TARGET_WINDOWS + other._window = {}; + #endif other._display = {}; other._context = {}; + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) + other._surface = {}; + #endif } WindowlessEglContext::~WindowlessEglContext() { @@ -548,7 +609,7 @@ WindowlessEglContext::~WindowlessEglContext() { eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(_display, _context); } - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) if(_surface) eglDestroySurface(_display, _surface); #endif @@ -561,6 +622,10 @@ WindowlessEglContext::~WindowlessEglContext() { !_sharedContext && #endif _display) eglTerminate(_display); + + #ifdef CORRADE_TARGET_WINDOWS + if(_window) DestroyWindow(_window); + #endif } WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& other) noexcept { @@ -570,6 +635,12 @@ WindowlessEglContext& WindowlessEglContext::operator=(WindowlessEglContext&& oth #endif swap(other._display, _display); swap(other._context, _context); + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) + swap(other._surface, _surface); + #endif + #ifdef CORRADE_TARGET_WINDOWS + swap(other._window, _window); + #endif return *this; } diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index 4233f3a56..fce480e14 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -162,9 +162,14 @@ class WindowlessEglContext { #ifndef MAGNUM_TARGET_WEBGL bool _sharedContext = false; #endif + #ifdef CORRADE_TARGET_WINDOWS + /* It's a HWND, which is HANDLE, which is PVOID, which is void*. FFS + Windows you're really mad with the typedefs. */ + void* _window{}; + #endif EGLDisplay _display{}; EGLContext _context{}; - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + #if defined(CORRADE_TARGET_WINDOWS) || (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)) /* Needed only by SwiftShader, using EGL_NO_SURFACE everywhere else */ EGLSurface _surface = EGL_NO_SURFACE; #endif diff --git a/src/Magnum/Platform/WindowlessGlxApplication.h b/src/Magnum/Platform/WindowlessGlxApplication.h index a0360a44f..e8ca1c680 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.h +++ b/src/Magnum/Platform/WindowlessGlxApplication.h @@ -344,9 +344,8 @@ CORRADE_ENUMSET_OPERATORS(WindowlessGlxContext::Configuration::Flags) @m_keywords{WindowlessApplication GLX} -Application for offscreen rendering using @ref WindowlessGlxContext. This -application library is available on desktop OpenGL and -@ref MAGNUM_TARGET_DESKTOP_GLES "OpenGL ES emulation on desktop" on Linux. +Application for offscreen rendering using @ref WindowlessGlxContext. Available +on desktop OpenGL and OpenGL ES using GLX on Linux. @section Platform-WindowlessGlxApplication-bootstrap Bootstrap application diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp b/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp deleted file mode 100644 index b27c2ac9d..000000000 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 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 "WindowlessWindowsEglApplication.h" - -#include -#include - -#include "Magnum/GL/Version.h" - -#include "Implementation/Egl.h" - -#ifndef EGL_KHR_create_context_no_error -#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 -#endif - -namespace Magnum { namespace Platform { - -WindowlessWindowsEglContext::WindowlessWindowsEglContext(const Configuration& configuration, GLContext* const magnumContext) { - /** @todo device selection and skipping of eglInitialize()/Terminate() the - same way as with WindowlessEglContext */ - - /* Register the window class (if not yet done) */ - WNDCLASSW wc; - if(!GetClassInfoW(GetModuleHandleW(nullptr), L"Magnum Windowless Application", &wc)) { - wc = WNDCLASSW{ - 0, - DefWindowProcW, - 0, - 0, - GetModuleHandleW(nullptr), - nullptr, - nullptr, - HBRUSH(COLOR_BACKGROUND), - nullptr, - L"Magnum Windowless Application" - }; - - if(!RegisterClassW(&wc)) { - Error() << "Platform::WindowlessWglContext: cannot create window class:" << GetLastError(); - return; - } - } - - /* Create the window */ - _window = CreateWindowW(wc.lpszClassName, L"Magnum Windowless Application", - WS_OVERLAPPEDWINDOW, 0, 0, 32, 32, 0, 0, wc.hInstance, 0); - - /* Initialize */ - _display = eglGetDisplay(GetDC(_window)); - if(!eglInitialize(_display, nullptr, nullptr)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot initialize EGL:" << Implementation::eglErrorString(eglGetError()); - return; - } - - const EGLenum api = - #ifndef MAGNUM_TARGET_GLES - EGL_OPENGL_API - #else - EGL_OPENGL_ES_API - #endif - ; - if(!eglBindAPI(api)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot bind EGL API:" << Implementation::eglErrorString(eglGetError()); - return; - } - - /* Choose EGL config */ - static const EGLint attribs[] = { - EGL_RED_SIZE, 1, - EGL_GREEN_SIZE, 1, - EGL_BLUE_SIZE, 1, - EGL_DEPTH_SIZE, 1, - #ifndef MAGNUM_TARGET_GLES - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - #elif defined(MAGNUM_TARGET_GLES3) - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - #elif defined(MAGNUM_TARGET_GLES2) - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - #else - #error unsupported OpenGL edition - #endif - EGL_NONE - }; - EGLint configCount; - EGLConfig config; - if(!eglChooseConfig(_display, attribs, &config, 1, &configCount)) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): cannot get EGL visual config:" << Implementation::eglErrorString(eglGetError()); - return; - } - - if(!configCount) { - Error() << "Platform::WindowlessWindowsEglApplication::tryCreateContext(): no matching EGL visual config available"; - return; - } - - /* Request debug context if GpuValidation is enabled either via the - configuration or via command-line */ - Configuration::Flags flags = configuration.flags(); - if((flags & Configuration::Flag::GpuValidation) || (magnumContext && magnumContext->configurationFlags() & GL::Context::Configuration::Flag::GpuValidation)) - flags |= Configuration::Flag::Debug; - else if((flags & Configuration::Flag::GpuValidationNoError) || (magnumContext && magnumContext->configurationFlags() & GL::Context::Configuration::Flag::GpuValidationNoError)) - flags |= Configuration::Flag::NoError; - - /** @todo needs a growable DynamicArray with disabled alloc or somesuch */ - EGLint attributes[7] = { - #ifdef MAGNUM_TARGET_GLES - EGL_CONTEXT_CLIENT_VERSION, - #ifdef MAGNUM_TARGET_GLES3 - 3, - #elif defined(MAGNUM_TARGET_GLES2) - 2, - #else - #error unsupported OpenGL ES version - #endif - #endif - /* Mask out the upper 32bits used for other flags */ - EGL_CONTEXT_FLAGS_KHR, EGLint(UnsignedLong(flags) & 0xffffffffu), - - /* The rest is added optionally */ - EGL_NONE, EGL_NONE, /* EGL_CONTEXT_OPENGL_NO_ERROR_KHR */ - EGL_NONE - }; - - std::size_t nextAttribute = 4; - CORRADE_INTERNAL_ASSERT(attributes[nextAttribute] == EGL_NONE); - - if(flags & Configuration::Flag::NoError) { - attributes[nextAttribute++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; - attributes[nextAttribute++] = true; - } - - CORRADE_INTERNAL_ASSERT(nextAttribute < Containers::arraySize(attributes)); - - if(!(_context = eglCreateContext(_display, config, configuration.sharedContext(), attributes))) { - Error() << "Platform::WindowlessWindowsEglContext: cannot create EGL context:" << Implementation::eglErrorString(eglGetError()); - return; - } - - if(!(_surface = eglCreateWindowSurface(_display, config, _window, nullptr))) - Error() << "Platform::WindowlessWindowsEglContext: cannot create window surface:" << Implementation::eglErrorString(eglGetError()); -} - -WindowlessWindowsEglContext::WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept: _window{other._window}, _display{other._display}, _surface{other._surface}, _context{other._context} { - other._window = {}; - other._display = {}; - other._surface = {}; - other._context = {}; -} - -WindowlessWindowsEglContext::~WindowlessWindowsEglContext() { - if(_context) eglDestroyContext(_display, _context); - if(_surface) eglDestroySurface(_display, _surface); - if(_display) eglTerminate(_display); - if(_window) DestroyWindow(_window); -} - -WindowlessWindowsEglContext& WindowlessWindowsEglContext::operator=(WindowlessWindowsEglContext&& other) noexcept { - using std::swap; - swap(other._window, _window); - swap(other._display, _display); - swap(other._surface, _surface); - swap(other._context, _context); - return *this; -} - -bool WindowlessWindowsEglContext::makeCurrent() { - if(eglMakeCurrent(_display, _surface, _surface, _context)) - return true; - - Error() << "Platform::WindowlessWindowsEglContext::makeCurrent(): cannot make context current:" << GetLastError(); - return false; -} - -bool WindowlessWindowsEglContext::release() { - if(eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) - return true; - - Error() << "Platform::WindowlessWindowsEglApplication::release(): cannot release current context:" << GetLastError(); - return false; -} - -WindowlessWindowsEglContext::Configuration::Configuration() { - GL::Context::Configuration::addFlags(GL::Context::Configuration::Flag::Windowless); -} - -#ifndef DOXYGEN_GENERATING_OUTPUT -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments): WindowlessWindowsEglApplication{arguments, Configuration{}} {} -#endif - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration): WindowlessWindowsEglApplication{arguments, NoCreate} { - createContext(configuration); -} - -WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(const Arguments& arguments, NoCreateT): _glContext{NoCreate}, _context{NoCreate, arguments.argc, arguments.argv} {} - -void WindowlessWindowsEglApplication::createContext() { createContext({}); } - -void WindowlessWindowsEglApplication::createContext(const Configuration& configuration) { - if(!tryCreateContext(configuration)) std::exit(1); -} - -bool WindowlessWindowsEglApplication::tryCreateContext(const Configuration& configuration) { - CORRADE_ASSERT(_context.version() == Version::None, "Platform::WindowlessWindowsEglApplication::tryCreateContext(): context already created", false); - - WindowlessWindowsEglContext glContext{configuration, &_context}; - if(!glContext.isCreated() || !glContext.makeCurrent() || !_context.tryCreate(configuration)) - return false; - - _glContext = std::move(glContext); - return true; -} - -WindowlessWindowsEglApplication::~WindowlessWindowsEglApplication() = default; - -}} diff --git a/src/Magnum/Platform/WindowlessWindowsEglApplication.h b/src/Magnum/Platform/WindowlessWindowsEglApplication.h deleted file mode 100644 index 21a309379..000000000 --- a/src/Magnum/Platform/WindowlessWindowsEglApplication.h +++ /dev/null @@ -1,538 +0,0 @@ -#ifndef Magnum_Platform_WindowlessWindowsEglApplication_h -#define Magnum_Platform_WindowlessWindowsEglApplication_h -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022 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::Platform::WindowlessWindowsEglApplication, @ref Magnum::Platform::WindowlessWindowsEglContext, macro @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() - */ - -#include "Magnum/configure.h" - -#ifdef MAGNUM_TARGET_GL -#ifndef DOXYGEN_GENERATING_OUTPUT -#define WIN32_LEAN_AND_MEAN 1 -#define VC_EXTRALEAN -#endif -#include -#include -#include -#include - -#include "Magnum/Magnum.h" -#include "Magnum/Tags.h" -#include "Magnum/Platform/GLContext.h" - -namespace Magnum { namespace Platform { - -/** -@brief Windowless Windows/EGL context - -@m_keywords{WindowlessGLContext EGL} - -GL context using pure WINAPI and EGL, used in @ref WindowlessWindowsEglApplication. - -Meant to be used when there is a need to manage (multiple) GL contexts -manually. See @ref platform-windowless-contexts for more information. If no -other application header is included, this class is also aliased to -@cpp Platform::WindowlessGLContext @ce. - -@note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. -*/ -class WindowlessWindowsEglContext { - public: - class Configuration; - - /** - * @brief Constructor - * @param configuration Context configuration - * @param context Optional Magnum context instance constructed - * using @ref NoCreate to manage driver workarounds - * - * Once the context is created, make it current using - * @ref makeCurrent() and create @ref Platform::GLContext instance to - * be able to use Magnum. - * @see @ref isCreated() - */ - explicit WindowlessWindowsEglContext(const Configuration& configuration, GLContext* context = nullptr); - - /** - * @brief Construct without creating the context - * - * Move a instance with created context over to make it usable. - */ - explicit WindowlessWindowsEglContext(NoCreateT) {} - - /** @brief Copying is not allowed */ - WindowlessWindowsEglContext(const WindowlessWindowsEglContext&) = delete; - - /** @brief Move constructor */ - WindowlessWindowsEglContext(WindowlessWindowsEglContext&& other) noexcept; - - /** @brief Copying is not allowed */ - WindowlessWindowsEglContext& operator=(const WindowlessWindowsEglContext&) = delete; - - /** @brief Move assignment */ - WindowlessWindowsEglContext& operator=(WindowlessWindowsEglContext&& other) noexcept; - - /** - * @brief Destructor - * - * Destroys the context, if any. - */ - ~WindowlessWindowsEglContext(); - - /** @brief Whether the context is created */ - bool isCreated() const { return _context; } - - /** - * @brief Make the context current - * - * Prints error message and returns @cpp false @ce on failure, - * otherwise returns @cpp true @ce. If the context is current on - * another thread, you have to @ref release() it there first --- an - * OpenGL context can't be current in multiple threads at the same - * time. - */ - bool makeCurrent(); - - /** - * @brief Release current context - * @m_since_latest - * - * Releases a context previously made current using @ref makeCurrent(). - * Prints error message and returns @cpp false @ce on failure, - * otherwise returns @cpp true @ce. - */ - bool release(); - - /** - * @brief Underlying OpenGL context - * @m_since{2020,06} - * - * Use in case you need to call EGL functionality directly or in order - * to create a shared context. Returns @cpp nullptr @ce in case the - * context was not created yet. - * @see @ref Configuration::setSharedContext() - */ - EGLContext glContext() { return _context; } - - private: - HWND _window{}; - EGLDisplay _display{}; - EGLSurface _surface{}; - EGLContext _context{}; -}; - -/** -@brief Configuration - -@see @ref WindowlessWindowsEglContext(), - @ref WindowlessWindowsEglApplication::WindowlessWindowsEglApplication(), - @ref WindowlessWindowsEglApplication::createContext(), - @ref WindowlessWindowsEglApplication::tryCreateContext() -*/ -class WindowlessWindowsEglContext::Configuration: public GL::Context::Configuration { - public: - /** - * @brief Context flag - * - * Includes also everything from @ref GL::Context::Configuration::Flag - * except for @relativeref{GL::Context::Configuration,Flag::Windowless}, - * which is enabled implicitly by default. - * @see @ref Flags, @ref setFlags(), @ref GL::Context::Flag - */ - enum class Flag: UnsignedLong { - /** - * Debug context. Enabled automatically if supported by the driver - * and the @ref Flag::GpuValidation flag is set or if the - * `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" - * is set to `on`. - */ - Debug = EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, - - /** - * Context without error reporting. Might result in better - * performance, but situations that would have generated errors - * instead cause undefined behavior. Enabled automatically if - * supported by the driver and the @ref Flag::GpuValidationNoError - * flag is set or if the `--magnum-gpu-validation` @ref GL-Context-usage-command-line "command-line option" - * is set to `no-error`. - * @m_since_latest - */ - /* Treated as a separate attribute and not a flag in EGL, thus - handling manually. */ - NoError = 1ull << 32, - - /** - * @copydoc GL::Context::Configuration::Flag::QuietLog - * @m_since_latest - */ - QuietLog = UnsignedLong(GL::Context::Configuration::Flag::QuietLog), - - /** - * @copydoc GL::Context::Configuration::Flag::VerboseLog - * @m_since_latest - */ - VerboseLog = UnsignedLong(GL::Context::Configuration::Flag::VerboseLog), - - /** - * @copydoc GL::Context::Configuration::Flag::GpuValidation - * @m_since_latest - */ - GpuValidation = UnsignedLong(GL::Context::Configuration::Flag::GpuValidation), - - /** - * @copydoc GL::Context::Configuration::Flag::GpuValidationNoError - * @m_since_latest - */ - GpuValidationNoError = UnsignedLong(GL::Context::Configuration::Flag::GpuValidationNoError) - }; - - /** - * @brief Context flags - * - * @see @ref setFlags(), @ref Context::Flags - */ - typedef Containers::EnumSet Flags; - - /*implicit*/ Configuration(); - - /** @brief Context flags */ - Flags flags() const { - return Flag(UnsignedLong(GL::Context::Configuration::flags())); - } - - /** - * @brief Set context flags - * @return Reference to self (for method chaining) - * - * Default is no flag. To avoid clearing default flags by accident, - * prefer to use @ref addFlags() and @ref clearFlags() instead. - * @see @ref GL::Context::flags() - */ - Configuration& setFlags(Flags flags) { - GL::Context::Configuration::setFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Add context flags - * @return Reference to self (for method chaining) - * - * Unlike @ref setFlags(), ORs the flags with existing instead of - * replacing them. Useful for preserving the defaults. - * @see @ref clearFlags() - */ - Configuration& addFlags(Flags flags) { - GL::Context::Configuration::addFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Clear context flags - * @return Reference to self (for method chaining) - * - * Unlike @ref setFlags(), ANDs the inverse of @p flags with existing - * instead of replacing them. Useful for removing default flags. - * @see @ref addFlags() - */ - Configuration& clearFlags(Flags flags) { - GL::Context::Configuration::clearFlags(GL::Context::Configuration::Flag(UnsignedLong(flags))); - return *this; - } - - /** - * @brief Create a shared context - * @return Reference to self (for method chaining) - * @m_since{2020,06} - * - * When set, the created context will share a subset of OpenGL objects - * with @p context, instead of being independent. Many caveats and - * limitations apply to shared OpenGL contexts, please consult the - * OpenGL specification for details. Default is `EGL_NO_CONTEXT`, i.e. - * no sharing. - * @see @ref WindowlessWindowsEglContext::glContext(), - * @ref WindowlessWindowsEglApplication::glContext() - */ - Configuration& setSharedContext(EGLContext context) { - _sharedContext = context; - return *this; - } - - /** - * @brief Shared context - * @m_since{2020,06} - */ - EGLContext sharedContext() const { return _sharedContext; } - - /* Overloads to remove WTF-factor from method chaining order */ - #ifndef DOXYGEN_GENERATING_OUTPUT - MAGNUM_GL_CONTEXT_CONFIGURATION_SUBCLASS_IMPLEMENTATION(Configuration) - #endif - - private: - EGLContext _sharedContext = EGL_NO_CONTEXT; -}; - -CORRADE_ENUMSET_OPERATORS(WindowlessWindowsEglContext::Configuration::Flags) - -/** -@brief Windowless Windows/EGL application - -@m_keywords{WindowlessApplication EGL} - -Application for offscreen rendering using @ref WindowlessWindowsEglContext. -This application library is available on OpenGL ES (also ANGLE) on Windows. - -@section Platform-WindowlessWindowsEglApplication-bootstrap Bootstrap application - -Fully contained windowless application using @ref WindowlessWindowsEglApplication -along with CMake setup is available in `windowless` branch of -[Magnum Bootstrap](https://github.com/mosra/magnum-bootstrap) repository, -download it as [tar.gz](https://github.com/mosra/magnum-bootstrap/archive/windowless.tar.gz) -or [zip](https://github.com/mosra/magnum-bootstrap/archive/windowless.zip) -file. After extracting the downloaded archive you can build and run the -application with these four commands: - -@code{.sh} -mkdir build && cd build -cmake .. -cmake --build . -./src/MyApplication # or ./src/Debug/MyApplication -@endcode - -See @ref cmake for more information. - -@section Platform-WindowlessWindowsEglApplication-usage General usage - -This application library is built if `MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION` -is enabled when building Magnum. To use this library from CMake, put -[FindEGL.cmake](https://github.com/mosra/magnum/blob/master/modules/FindEGL.cmake) -into your `modules/` directory, request the `WindowlessWindowsEglApplication` -component of the `Magnum` package and link to the -`Magnum::WindowlessWindowsEglApplication` target: - -@code{.cmake} -find_package(Magnum REQUIRED) -if(CORRADE_TARGET_WINDOWS) - find_package(Magnum REQUIRED WindowlessWindowsEglApplication) -endif() - -# ... -if(CORRADE_TARGET_WINDOWS) - target_link_libraries(your-app PRIVATE Magnum::WindowlessWindowsEglApplication) -endif() -@endcode - -Additionally, if you're using Magnum as a CMake subproject, do the following -* *before* calling @cmake find_package() @ce to ensure it's enabled, as the -library is not built by default: - -@code{.cmake} -set(MAGNUM_WITH_WINDOWLESSWINDOWSEGLAPPLICATION ON CACHE BOOL "" FORCE) -add_subdirectory(magnum EXCLUDE_FROM_ALL) -@endcode - -If no other application is requested, you can also use the generic -`Magnum::WindowlessApplication` alias to simplify porting. Again, see -@ref building and @ref cmake for more information. - -Place your code into @ref exec(). The subclass can be then used in main -function using @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() macro. See -@ref platform for more information. - -@code{.cpp} -class MyApplication: public Platform::WindowlessWindowsEglApplication { - // implement required methods... -}; -MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(MyApplication) -@endcode - -If no other application header is included, this class is also aliased to -@cpp Platform::WindowlessApplication @ce and the macro is aliased to -@cpp MAGNUM_WINDOWLESSAPPLICATION_MAIN() @ce to simplify porting. - -@note This class is available only if Magnum is compiled with - @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features - for more information. -*/ -class WindowlessWindowsEglApplication { - public: - /** @brief Application arguments */ - struct Arguments { - /** @brief Constructor */ - /*implicit*/ constexpr Arguments(int& argc, char** argv) noexcept: argc{argc}, argv{argv} {} - - int& argc; /**< @brief Argument count */ - char** argv; /**< @brief Argument values */ - }; - - /** - * @brief Configuration - * - * @see @ref WindowlessWindowsEglApplication(), @ref createContext(), - * @ref tryCreateContext() - */ - typedef WindowlessWindowsEglContext::Configuration Configuration; - - /** - * @brief Default constructor - * @param arguments Application arguments - * @param configuration Configuration - * - * Creates application with default or user-specified configuration. - * See @ref Configuration for more information. The program exits if - * the context cannot be created, see @ref tryCreateContext() for an - * alternative. - */ - #ifdef DOXYGEN_GENERATING_OUTPUT - explicit WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration = Configuration()); - #else - /* To avoid "invalid use of incomplete type" */ - explicit WindowlessWindowsEglApplication(const Arguments& arguments, const Configuration& configuration); - explicit WindowlessWindowsEglApplication(const Arguments& arguments); - #endif - - /** - * @brief Constructor - * @param arguments Application arguments - * - * Unlike above, the context is not created and must be created later - * with @ref createContext() or @ref tryCreateContext(). - */ - explicit WindowlessWindowsEglApplication(const Arguments& arguments, NoCreateT); - - /** @brief Copying is not allowed */ - WindowlessWindowsEglApplication(const WindowlessWindowsEglApplication&) = delete; - - /** @brief Moving is not allowed */ - WindowlessWindowsEglApplication(WindowlessWindowsEglApplication&&) = delete; - - /** @brief Copying is not allowed */ - WindowlessWindowsEglApplication& operator=(const WindowlessWindowsEglApplication&) = delete; - - /** @brief Moving is not allowed */ - WindowlessWindowsEglApplication& operator=(WindowlessWindowsEglApplication&&) = delete; - - /** - * @brief Execute application - * @return Value for returning from @cpp main() @ce - * - * See @ref MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN() for usage - * information. - */ - virtual int exec() = 0; - - /** - * @brief Underlying OpenGL context - * @m_since{2020,06} - * - * Use in case you need to call EGL functionality directly or in order - * to create a shared context. Returns @cpp nullptr @ce in case the - * context was not created yet. - * @see @ref Configuration::setSharedContext() - */ - EGLContext glContext() { return _glContext.glContext(); } - - protected: - /* Nobody will need to have (and delete) WindowlessWindowsEglApplication*, - thus this is faster than public pure virtual destructor */ - ~WindowlessWindowsEglApplication(); - - /** - * @brief Create context with given configuration - * - * Must be called if and only if the context wasn't created by the - * constructor itself. Error message is printed and the program exits - * if the context cannot be created, see @ref tryCreateContext() for an - * alternative. - */ - #ifdef DOXYGEN_GENERATING_OUTPUT - void createContext(const Configuration& configuration = Configuration()); - #else - /* To avoid "invalid use of incomplete type" */ - void createContext(const Configuration& configuration); - void createContext(); - #endif - - /** - * @brief Try to create context with given configuration - * - * Unlike @ref createContext() returns @cpp false @ce if the context - * cannot be created, @cpp true @ce otherwise. - */ - bool tryCreateContext(const Configuration& configuration); - - private: - WindowlessWindowsEglContext _glContext; - Platform::GLContext _context; -}; - -/** @hideinitializer -@brief Entry point for windowless Windows/EGL application -@param className Class name - -@m_keywords{MAGNUM_WINDOWLESSAPPLICATION_MAIN()} - -See @ref Magnum::Platform::WindowlessWindowsEglApplication "Platform::WindowlessWindowsEglApplication" -for usage information.This macro abstracts out platform-specific entry point -code and is equivalent to the following, see @ref portability-applications for -more information. - -@code{.cpp} -int main(int argc, char** argv) { - className app({argc, argv}); - return app.exec(); -} -@endcode - -When no other windowless application header is included this macro is also -aliased to @cpp MAGNUM_WINDOWLESSAPPLICATION_MAIN() @ce. -*/ -#define MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) \ - int main(int argc, char** argv) { \ - className app({argc, argv}); \ - return app.exec(); \ - } - -#ifndef DOXYGEN_GENERATING_OUTPUT -#ifndef MAGNUM_WINDOWLESSAPPLICATION_MAIN -typedef WindowlessWindowsEglApplication WindowlessApplication; -typedef WindowlessWindowsEglContext WindowlessGLContext; -#define MAGNUM_WINDOWLESSAPPLICATION_MAIN(className) MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN(className) -#else -#undef MAGNUM_WINDOWLESSAPPLICATION_MAIN -#endif -#endif - -}} -#else -#error this header is available only in the OpenGL build -#endif - -#endif diff --git a/src/Magnum/Platform/gl-info.cpp b/src/Magnum/Platform/gl-info.cpp index 2c18680e3..2ded27ad3 100644 --- a/src/Magnum/Platform/gl-info.cpp +++ b/src/Magnum/Platform/gl-info.cpp @@ -58,24 +58,16 @@ #include "Magnum/GL/TransformFeedback.h" #endif -#if defined(MAGNUM_TARGET_HEADLESS) || defined(CORRADE_TARGET_EMSCRIPTEN) || defined(CORRADE_TARGET_ANDROID) +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif @@ -241,8 +233,6 @@ MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplicat Debug{} << "Used application: Platform::WindowlessGlxApplication"; #elif defined(MAGNUM_WINDOWLESSWGLAPPLICATION_MAIN) Debug{} << "Used application: Platform::WindowlessWglApplication"; - #elif defined(MAGNUM_WINDOWLESSWINDOWSEGLAPPLICATION_MAIN) - Debug{} << "Used application: Platform::WindowlessWindowsEglApplication"; #else #error no windowless application available on this platform #endif @@ -349,14 +339,11 @@ MagnumInfo::MagnumInfo(const Arguments& arguments): Platform::WindowlessApplicat #ifdef MAGNUM_TARGET_GLES2 Debug{} << " MAGNUM_TARGET_GLES2"; #endif - #ifdef MAGNUM_TARGET_DESKTOP_GLES - Debug{} << " MAGNUM_TARGET_DESKTOP_GLES"; - #endif #ifdef MAGNUM_TARGET_WEBGL Debug{} << " MAGNUM_TARGET_WEBGL"; #endif - #ifdef MAGNUM_TARGET_HEADLESS - Debug{} << " MAGNUM_TARGET_HEADLESS"; + #ifdef MAGNUM_TARGET_EGL + Debug{} << " MAGNUM_TARGET_EGL"; #endif Debug{} << "Compiled CPU features:"; Debug{} << " " << Debug::packed << Cpu::compiledFeatures(); diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 9cce7c8bd..f82451f7b 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -27,6 +27,17 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/SceneTools") +# Somehow, due to MagnumTradeObjects having target_include_directories() with +# $, +# if MAGNUM_SCENECONVERTER_STATIC_PLUGINS is non-empty then CMake fails with +# +# Target "Corrade::PluginManager" not found. +# +# unless the find_package() is here. Not sure why, probably some bug in CMake +# dependency handling? Changing target_include_directories() to PRIVATE doesn't +# help, removing it altogether helps. +find_package(Corrade REQUIRED PluginManager) + # Files shared between main library and unit test library set(MagnumSceneTools_SRCS ) @@ -44,7 +55,8 @@ set(MagnumSceneTools_HEADERS set(MagnumSceneTools_PRIVATE_HEADERS Implementation/combine.h - Implementation/convertToSingleFunctionObjects.h) + Implementation/convertToSingleFunctionObjects.h + Implementation/sceneConverterUtilities.h) ## Objects shared between main and test library #add_library(MagnumSceneToolsObjects OBJECT @@ -87,7 +99,8 @@ if(MAGNUM_WITH_SCENECONVERTER) Magnum MagnumMeshTools MagnumSceneTools - MagnumTrade) + MagnumTrade + ${MAGNUM_SCENECONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-sceneconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) diff --git a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h new file mode 100644 index 000000000..2de8de8f9 --- /dev/null +++ b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h @@ -0,0 +1,1035 @@ +#ifndef Magnum_SceneTools_Implementation_sceneConverterUtilities_h +#define Magnum_SceneTools_Implementation_sceneConverterUtilities_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 /* std::isupper() */ +#include /* sceneFieldNames */ +#include +#include +#include + +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/CameraData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +namespace Magnum { namespace SceneTools { namespace Implementation { + +using namespace Containers::Literals; + +/* Used only in executables where we don't want it to be exported -- in + particular magnum-sceneconverter and its tests */ +namespace { + +/** @todo const Array& doesn't work, minmax() would fail to match */ +template Containers::String calculateBounds(Containers::Array&& attribute) { + /** @todo clean up when Debug::toString() exists */ + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << Math::minmax(attribute); + return out.str(); +} + +/* Named attribute index from a global index */ +/** @todo some helper for this directly on the MeshData class? */ +UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { + const Trade::MeshAttribute name = mesh.attributeName(id); + for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) + if(mesh.attributeId(name, i) == id) return i; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility::Arguments& args, Trade::AbstractImporter& importer, std::chrono::high_resolution_clock::duration& importTime) { + struct AnimationInfo { + UnsignedInt animation; + Trade::AnimationData data{{}, {}}; + Containers::String name; + }; + + struct SkinInfo { + bool twoDimensions; + UnsignedInt skin; + UnsignedInt jointCount; + Containers::String name; + }; + + struct LightInfo { + UnsignedInt light; + Trade::LightData data{{}, {}, {}}; + Containers::String name; + }; + + struct CameraInfo { + UnsignedInt camera; + Trade::CameraData data{{}, {}, {}, {}}; + Containers::String name; + }; + + struct MaterialInfo { + UnsignedInt material; + Trade::MaterialData data{{}, {}}; + Containers::String name; + }; + + struct TextureInfo { + UnsignedInt texture; + Trade::TextureData data{{}, {}, {}, {}, {}, {}}; + Containers::String name; + }; + + struct MeshAttributeInfo { + std::size_t offset; + Int stride; + UnsignedInt arraySize; + Trade::MeshAttribute name; + Containers::String customName; + VertexFormat format; + Containers::String bounds; + }; + + struct MeshInfo { + UnsignedInt mesh, level; + MeshPrimitive primitive; + UnsignedInt indexCount, vertexCount; + std::size_t indexOffset; + Int indexStride; + Containers::String indexBounds; + MeshIndexType indexType; + Containers::Array attributes; + std::size_t indexDataSize, vertexDataSize; + Trade::DataFlags indexDataFlags, vertexDataFlags; + Containers::String name; + }; + + struct SceneFieldInfo { + Trade::SceneField name; + Trade::SceneFieldFlags flags; + Trade::SceneFieldType type; + UnsignedInt arraySize; + std::size_t size; + }; + + struct SceneInfo { + UnsignedInt scene; + Trade::SceneMappingType mappingType; + UnsignedLong mappingBound; + Containers::Array fields; + std::size_t dataSize; + Trade::DataFlags dataFlags; + Containers::String name; + }; + + struct ObjectInfo { + UnsignedLong object; + /* A bitfield, assuming no more than 32 scenes */ + /** @todo might be too little? */ + UnsignedInt scenes; + Containers::Array> fields; + Containers::String name; + }; + + /* Parse everything first to avoid errors interleaved with output */ + bool error = false; + + /* Object properties */ + Containers::Array objectInfos; + if(args.isSet("info") || args.isSet("info-objects")) { + objectInfos = Containers::Array{std::size_t(importer.objectCount())}; + + for(UnsignedLong i = 0; i != importer.objectCount(); ++i) { + objectInfos[i].object = i; + objectInfos[i].name = importer.objectName(i); + } + } + + /* Scene properties, together with counting how much is each mesh / light / + material / skin / object referenced (which gets used only if both + --info-scenes and --info-{lights,materials,skins,objects} is passed and + the file has at least one scene). Texture reference count is calculated + when parsing materials. */ + Containers::Array sceneInfos; + /* Only the very latest GCC seems to support enum classes as keys and I + can't be bothered to write a std::hash specialization, so just making + the key typeless */ + std::unordered_map sceneFieldNames; + Containers::Array materialReferenceCount; + Containers::Array lightReferenceCount; + Containers::Array cameraReferenceCount; + Containers::Array meshReferenceCount; + Containers::Array skin2DReferenceCount; + Containers::Array skin3DReferenceCount; + if((args.isSet("info") || args.isSet("info-scenes")) && importer.sceneCount()) { + materialReferenceCount = Containers::Array{importer.materialCount()}; + lightReferenceCount = Containers::Array{importer.lightCount()}; + cameraReferenceCount = Containers::Array{importer.cameraCount()}; + meshReferenceCount = Containers::Array{importer.meshCount()}; + skin2DReferenceCount = Containers::Array{importer.skin2DCount()}; + skin3DReferenceCount = Containers::Array{importer.skin3DCount()}; + + for(UnsignedInt i = 0; i != importer.sceneCount(); ++i) { + Containers::Optional scene = importer.scene(i); + if(!scene) { + error = true; + continue; + } + + SceneInfo info{}; + info.scene = i; + info.mappingType = scene->mappingType(); + info.mappingBound = scene->mappingBound(); + info.dataSize = scene->data().size(); + info.dataFlags = scene->dataFlags(); + info.name = importer.sceneName(i); + for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { + const Trade::SceneField name = scene->fieldName(j); + + if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { + if(meshMaterial.second().first() < meshReferenceCount.size()) + ++meshReferenceCount[meshMaterial.second().first()]; + if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) + ++materialReferenceCount[meshMaterial.second().second()]; + } + + if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { + if(scene->is2D() && skin.second() < skin2DReferenceCount.size()) + ++skin2DReferenceCount[skin.second()]; + if(scene->is3D() && skin.second() < skin3DReferenceCount.size()) + ++skin3DReferenceCount[skin.second()]; + } + + if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { + if(light.second() < lightReferenceCount.size()) + ++lightReferenceCount[light.second()]; + } + + if(name == Trade::SceneField::Camera) for(const Containers::Pair& camera: scene->camerasAsArray()) { + if(camera.second() < cameraReferenceCount.size()) + ++cameraReferenceCount[camera.second()]; + } + + arrayAppend(info.fields, InPlaceInit, + name, + scene->fieldFlags(j), + scene->fieldType(j), + scene->fieldArraySize(j), + scene->fieldSize(j)); + + /* If the field has a custom name, save it into the map. Not + putting it into the fields array as the map is reused by + object info as well. */ + if(Trade::isSceneFieldCustom(name)) { + /* Fetch the name only if it's not already there */ + const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); + if(inserted.second) + inserted.first->second = importer.sceneFieldName(name); + } + + if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { + if(object >= objectInfos.size()) continue; + + objectInfos[object].object = object; + objectInfos[object].scenes |= 1 << i; + + /* If the field is repeated, increase the count instead */ + if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) + ++objectInfos[object].fields.back().second(); + else + arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); + } + } + + arrayAppend(sceneInfos, std::move(info)); + } + } + + /* Animation properties */ + Containers::Array animationInfos; + if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer.animationCount(); ++i) { + Containers::Optional animation; + { + Trade::Implementation::Duration d{importTime}; + if(!(animation = importer.animation(i))) { + error = true; + continue; + } + } + + AnimationInfo info{}; + info.animation = i; + info.name = importer.animationName(i); + info.data = *std::move(animation); + + arrayAppend(animationInfos, std::move(info)); + } + + /* Skin properties */ + Containers::Array skinInfos; + if(args.isSet("info") || args.isSet("info-skins")) { + for(UnsignedInt i = 0; i != importer.skin2DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin2D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = true; + info.skin = i; + info.name = importer.skin2DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + + for(UnsignedInt i = 0; i != importer.skin3DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin3D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = false; + info.skin = i; + info.name = importer.skin3DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + } + + /* Light properties */ + Containers::Array lightInfos; + if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer.lightCount(); ++i) { + Containers::Optional light; + { + Trade::Implementation::Duration d{importTime}; + if(!(light = importer.light(i))) { + error = true; + continue; + } + } + + LightInfo info{}; + info.light = i; + info.name = importer.lightName(i); + info.data = *std::move(light); + + arrayAppend(lightInfos, std::move(info)); + } + + /* Camera properties */ + Containers::Array cameraInfos; + if(args.isSet("info") || args.isSet("info-cameras")) for(UnsignedInt i = 0; i != importer.cameraCount(); ++i) { + Containers::Optional camera; + { + Trade::Implementation::Duration d{importTime}; + if(!(camera = importer.camera(i))) { + error = true; + continue; + } + } + + CameraInfo info{}; + info.camera = i; + info.name = importer.cameraName(i); + info.data = *std::move(camera); + + arrayAppend(cameraInfos, std::move(info)); + } + + /* Material properties, together with how much is each texture shared + (which gets used only if both --info-materials and --info-textures is + passed and the file has at least one material). */ + Containers::Array materialInfos; + Containers::Array textureReferenceCount; + if((args.isSet("info") || args.isSet("info-materials")) && importer.materialCount()) { + textureReferenceCount = Containers::Array{importer.textureCount()}; + + for(UnsignedInt i = 0; i != importer.materialCount(); ++i) { + Containers::Optional material; + { + Trade::Implementation::Duration d{importTime}; + if(!(material = importer.material(i))) { + error = true; + continue; + } + } + + /* Calculate texture reference count for all properties that look + like a texture */ + for(UnsignedInt j = 0; j != material->layerCount(); ++j) { + for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { + if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !material->attributeName(j, k).hasSuffix("Texture"_s)) + continue; + + const UnsignedInt texture = material->attribute(j, k); + /** @todo once StridedBitArrayView2D exists, fix this to + count each material only once by having one bit for + every material and texture */ + if(texture < textureReferenceCount.size()) + ++textureReferenceCount[texture]; + } + } + + MaterialInfo info{}; + info.material = i; + info.name = importer.materialName(i); + info.data = *std::move(material); + + arrayAppend(materialInfos, std::move(info)); + } + } + + /* Mesh properties */ + Containers::Array meshInfos; + if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer.meshCount(); ++i) { + for(UnsignedInt j = 0; j != importer.meshLevelCount(i); ++j) { + Containers::Optional mesh; + { + Trade::Implementation::Duration d{importTime}; + if(!(mesh = importer.mesh(i, j))) { + error = true; + continue; + } + } + + MeshInfo info{}; + info.mesh = i; + info.level = j; + info.primitive = mesh->primitive(); + info.vertexCount = mesh->vertexCount(); + info.vertexDataSize = mesh->vertexData().size(); + info.vertexDataFlags = mesh->vertexDataFlags(); + if(!j) { + info.name = importer.meshName(i); + } + if(mesh->isIndexed()) { + info.indexCount = mesh->indexCount(); + info.indexType = mesh->indexType(); + info.indexOffset = mesh->indexOffset(); + info.indexStride = mesh->indexStride(); + info.indexDataSize = mesh->indexData().size(); + info.indexDataFlags = mesh->indexDataFlags(); + if(args.isSet("bounds")) + info.indexBounds = calculateBounds(mesh->indicesAsArray()); + } + for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { + const Trade::MeshAttribute name = mesh->attributeName(k); + + /* Calculate bounds, if requested, if this is not an + implementation-specific format and if it's not a custom + attribute */ + Containers::String bounds; + if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { + case Trade::MeshAttribute::Position: + bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Tangent: + bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Bitangent: + bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Normal: + bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::TextureCoordinates: + bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Color: + bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::ObjectId: + bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); + break; + } + + arrayAppend(info.attributes, InPlaceInit, + mesh->attributeOffset(k), + mesh->attributeStride(k), + mesh->attributeArraySize(k), + name, Trade::isMeshAttributeCustom(name) ? + importer.meshAttributeName(name) : "", + mesh->attributeFormat(k), + bounds); + } + + arrayAppend(meshInfos, std::move(info)); + } + } + + /* Texture properties, together with how much is each image shared (which + gets used only if both --info-textures and --info-images is passed and + the file has at least one texture). */ + Containers::Array textureInfos; + Containers::Array image1DReferenceCount; + Containers::Array image2DReferenceCount; + Containers::Array image3DReferenceCount; + if((args.isSet("info") || args.isSet("info-textures")) && importer.textureCount()) { + image1DReferenceCount = Containers::Array{importer.image1DCount()}; + image2DReferenceCount = Containers::Array{importer.image2DCount()}; + image3DReferenceCount = Containers::Array{importer.image3DCount()}; + for(UnsignedInt i = 0; i != importer.textureCount(); ++i) { + Containers::Optional texture; + { + Trade::Implementation::Duration d{importTime}; + if(!(texture = importer.texture(i))) { + error = true; + continue; + } + } + + switch(texture->type()) { + case Trade::TextureType::Texture1D: + if(texture->image() < image1DReferenceCount.size()) + ++image1DReferenceCount[texture->image()]; + break; + case Trade::TextureType::Texture1DArray: + case Trade::TextureType::Texture2D: + if(texture->image() < image2DReferenceCount.size()) + ++image2DReferenceCount[texture->image()]; + break; + case Trade::TextureType::CubeMap: + case Trade::TextureType::CubeMapArray: + case Trade::TextureType::Texture2DArray: + case Trade::TextureType::Texture3D: + if(texture->image() < image3DReferenceCount.size()) + ++image3DReferenceCount[texture->image()]; + break; + } + + TextureInfo info{}; + info.texture = i; + info.name = importer.textureName(i); + info.data = *std::move(texture); + + arrayAppend(textureInfos, std::move(info)); + } + } + + Containers::Array imageInfos; + if(args.isSet("info") || args.isSet("info-images")) { + imageInfos = Trade::Implementation::imageInfo(importer, error, importTime); + } + + std::size_t totalSceneDataSize = 0; + for(const SceneInfo& info: sceneInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + d << " Bound:" << info.mappingBound << "objects" + << Debug::color(Debug::Color::Blue) << "@" << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.mappingType + << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + d << Debug::newline << " Fields:"; + for(const SceneFieldInfo& field: info.fields) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isSceneFieldCustom(field.name)) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(field.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(field.name)] + << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << field.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; + if(field.arraySize) + d << Debug::nospace << Utility::format("[{}]", field.arraySize); + d << Debug::resetColor; + if(field.flags) d << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Green) + << field.flags << Debug::resetColor; + d << Debug::nospace << "," << field.size << "entries"; + } + + totalSceneDataSize += info.dataSize; + } + if(!sceneInfos.isEmpty()) + Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; + + for(const ObjectInfo& info: objectInfos) { + /* Objects without a name and not referenced by any scenes are useless, + ignore */ + if(!info.name && !info.scenes) continue; + + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; + + if(sceneInfos) { + const UnsignedInt count = Math::popcount(info.scenes); + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "scenes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + if(info.scenes) { + d << Debug::newline << " Fields:"; + + for(std::size_t i = 0; i != info.fields.size(); ++i) { + if(i) d << Debug::nospace << ","; + const Containers::Pair nameCount = info.fields[i]; + d << Debug::color(Debug::Color::Cyan); + if(Trade::isSceneFieldCustom(nameCount.first())) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(nameCount.first()) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(nameCount.first())] + << Debug::nospace + << Debug::color(Debug::Color::Cyan) << ")"; + } else d << Debug::packed << nameCount.first(); + if(nameCount.second() != 1) + d << Debug::nospace << Utility::format("[{}]", nameCount.second()); + d << Debug::resetColor; + } + } + } + + std::size_t totalAnimationDataSize = 0; + for(const AnimationInfo& info: animationInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.duration().min() << Debug::nospace << "," + << info.data.duration().max() << Debug::nospace << "} (" + << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; + if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.data.dataFlags() << Debug::resetColor; + d << Debug::nospace << ")"; + + for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { + d << Debug::newline << " Track" << i << Debug::nospace << ":" + << Debug::packed << Debug::boldColor(Debug::Color::Default) + << info.data.trackTargetType(i) + << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackType(i) << Debug::resetColor; + if(info.data.trackType(i) != info.data.trackResultType(i)) + d << Debug::color(Debug::Color::Blue) << "->" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackResultType(i) << Debug::resetColor; + d << Debug::nospace << "," << info.data.track(i).size() + << "keyframes"; + if(info.data.track(i).duration() != info.data.duration()) + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.track(i).duration().min() << Debug::nospace + << "," << info.data.track(i).duration().max() + << Debug::nospace << "}"; + d << Debug::newline + << " Interpolation:" + << Debug::packed << Debug::color(info.data.track(i).interpolation() == Animation::Interpolation::Custom ? Debug::Color::Yellow : Debug::Color::Cyan) + << info.data.track(i).interpolation() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).before() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).after() << Debug::resetColor; + /** @todo might be useful to show bounds here as well, though not + so much for things like complex numbers or quats */ + } + + totalAnimationDataSize += info.data.data().size(); + } + if(!animationInfos.isEmpty()) + Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; + + for(const SkinInfo& info: skinInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << (info.twoDimensions ? "2D skin" : "3D skin") << info.skin + << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if((info.twoDimensions && skin2DReferenceCount) || + (!info.twoDimensions && skin3DReferenceCount)) + { + const UnsignedInt count = info.twoDimensions ? skin2DReferenceCount[info.skin] : skin3DReferenceCount[info.skin]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " " << info.jointCount << "joints"; + } + + for(const LightInfo& info: lightInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(lightReferenceCount) { + const UnsignedInt count = lightReferenceCount[info.light]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.type() << Debug::resetColor; + if(info.data.type() == Trade::LightData::Type::Spot) + d << Debug::nospace << "," << Debug::packed + << Deg(info.data.innerConeAngle()) << Debug::nospace + << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) + << Debug::nospace << "°"; + d << Debug::newline << " Color:"; + if(useColor24) d << Debug::color + << Math::pack(info.data.color()); + d << Debug::packed << info.data.color(); + if(!Math::equal(info.data.intensity(), 1.0f)) + d << "*" << info.data.intensity(); + if(info.data.type() != Trade::LightData::Type::Ambient && + info.data.type() != Trade::LightData::Type::Directional) + d << Debug::newline << " Attenuation:" << Debug::packed + << info.data.attenuation(); + if(info.data.range() != Constants::inf()) + d << Debug::newline << " Range:" << Debug::packed + << info.data.range(); + } + + for(const CameraInfo& info: cameraInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Camera" << info.camera << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(cameraReferenceCount) { + const UnsignedInt count = cameraReferenceCount[info.camera]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.type() << Debug::resetColor << Debug::newline; + /* Print orthographic cameras with size, perspective with FoV */ + if(info.data.type() == Trade::CameraType::Orthographic2D || + info.data.type() == Trade::CameraType::Orthographic3D) { + d << " Size:" << Debug::packed << info.data.size(); + } else if(info.data.type() == Trade::CameraType::Perspective3D) { + d << " FoV:" << Debug::packed << Deg(info.data.fov()) + << Debug::nospace << "°"; + } + /* Near/far is implicitly 0 for 2D */ + if(info.data.type() != Trade::CameraType::Orthographic2D) + d << Debug::nospace << "," << info.data.near() << "-" << info.data.far(); + d << Debug::newline << " Aspect ratio:" << info.data.aspectRatio(); + } + + for(const MaterialInfo& info: materialInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(materialReferenceCount) { + const UnsignedInt count = materialReferenceCount[info.material]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; + + for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { + /* Print extra layers with extra indent */ + const char* indent; + if(info.data.layerCount() != 1 && i != 0) { + d << Debug::newline << " Layer" << i << Debug::nospace << ":"; + if(!info.data.layerName(i).isEmpty()) { + if(std::isupper(info.data.layerName(i)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.layerName(i) << Debug::resetColor; + } + indent = " "; + } else { + d << Debug::newline << " Base layer:"; + indent = " "; + } + + for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { + /* Ignore layer name (which is always first) unless it's in the + base material, in which case we print it as it wouldn't + otherwise be shown anywhere */ + if(i && !j && info.data.attributeName(i, j) == " LayerName") + continue; + + d << Debug::newline << indent; + if(std::isupper(info.data.attributeName(i, j)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace + << ":"; + switch(info.data.attributeType(i, j)) { + #define _c(type) case Trade::MaterialAttributeType::type: \ + d << Debug::packed << info.data.attribute(i, j); \ + break; + /* LCOV_EXCL_START */ + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + /* Vector3 handled below */ + _c(Vector3ui) + _c(Vector3i) + /* Vector4 handled below */ + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + /* LCOV_EXCL_STOP */ + #undef _c + case Trade::MaterialAttributeType::Bool: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector3: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j)); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector4: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Pointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::MutablePointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::String: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::TextureSwizzle: + d << Debug::packed << info.data.attribute(i, j); + break; + } + } + } + } + + std::size_t totalMeshDataSize = 0; + for(const MeshInfo& info: meshInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(meshReferenceCount) { + const UnsignedInt count = meshReferenceCount[info.mesh]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":" + << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) + << "kB"; + if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.vertexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + + for(const MeshAttributeInfo& attribute: info.attributes) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isMeshAttributeCustom(attribute.name)) { + d << "Custom(" << Debug::nospace + << Trade::meshAttributeCustom(attribute.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << attribute.customName << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << attribute.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; + if(attribute.arraySize) + d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); + d << Debug::resetColor; + d << Debug::nospace << ", offset" << attribute.offset; + d << Debug::nospace << ", stride" + << attribute.stride; + if(attribute.bounds) + d << Debug::newline << " Bounds:" << attribute.bounds; + } + + if(info.indexType != MeshIndexType{}) { + d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace + << Utility::format("{:.1f}", info.indexDataSize/1024.0f) + << "kB"; + if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + if(info.indexBounds) + d << Debug::newline << " Bounds:" << info.indexBounds; + } + + totalMeshDataSize += info.vertexDataSize + info.indexDataSize; + } + if(!meshInfos.isEmpty()) + Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; + + for(const TextureInfo& info: textureInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; + + /* Print reference count only if there actually are materials and they + were parsed, otherwise this information is useless */ + if(textureReferenceCount) { + const UnsignedInt count = textureReferenceCount[info.texture]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "material attributes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + d << " Type:" + << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.type() + << Debug::resetColor << Debug::nospace << ", image" + << info.data.image(); + d << Debug::newline << " Minification, mipmap and magnification:" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.minificationFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.mipmapFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.magnificationFilter() << Debug::resetColor; + /** @todo show only the dimensions that matter for a particular texture + type */ + d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.wrapping()[0] << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "}"; + } + + Trade::Implementation::printImageInfo(useColor, imageInfos, image1DReferenceCount, image2DReferenceCount, image3DReferenceCount); + + return error; +} + +} + +}}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index ac1e1e4e8..51f448cbb 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -27,7 +27,65 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/SceneTools/Test") +if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(SCENETOOLS_TEST_DIR ".") + set(SCENETOOLS_TEST_OUTPUT_DIR "write") +else() + set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + set(SCENETOOLS_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() + +# Executable testing is implemented on Unix platforms only at the moment, so +# don't even provide the filename elsewhere. +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + set(SCENECONVERTER_EXECUTABLE_FILENAME $) +endif() + +# First replace ${} variables, then $<> generator expressions +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h + INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) + corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) + +corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp + LIBRARIES + MagnumSceneTools + # Link the same static plugins as for the magnum-sceneconverter + # executable so plugin existence checks are consistent between the two + ${MAGNUM_SCENECONVERTER_STATIC_PLUGINS} + FILES + SceneConverterTestFiles/broken-mesh.obj + SceneConverterTestFiles/broken-scene.gltf + SceneConverterTestFiles/empty.gltf + SceneConverterTestFiles/info-animations.txt + SceneConverterTestFiles/info-cameras.txt + SceneConverterTestFiles/info-images.txt + SceneConverterTestFiles/info-lights.txt + SceneConverterTestFiles/info-materials.txt + SceneConverterTestFiles/info-meshes-bounds.txt + SceneConverterTestFiles/info-meshes.txt + SceneConverterTestFiles/info-objects.txt + SceneConverterTestFiles/info-references.txt + SceneConverterTestFiles/info-scenes-objects.txt + SceneConverterTestFiles/info-scenes.txt + SceneConverterTestFiles/info-skins.txt + SceneConverterTestFiles/info-textures.txt + SceneConverterTestFiles/point.obj + SceneConverterTestFiles/quad-duplicates-fuzzy.obj + SceneConverterTestFiles/quad-duplicates.obj + SceneConverterTestFiles/quad-duplicates.ply + SceneConverterTestFiles/quad-normals-texcoords.obj + SceneConverterTestFiles/quad.obj + SceneConverterTestFiles/quad.ply + SceneConverterTestFiles/two-triangles-transformed.bin + SceneConverterTestFiles/two-triangles-transformed.gltf + SceneConverterTestFiles/two-triangles.obj) +target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + add_dependencies(SceneToolsSceneConverterTest magnum-sceneconverter) +endif() diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp new file mode 100644 index 000000000..05d528d67 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -0,0 +1,1686 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include +#include +#include +#include +#include + +#include "Magnum/Math/CubicHermite.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Trade/AbstractSceneConverter.h" + +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct SceneConverterTest: TestSuite::Tester { + explicit SceneConverterTest(); + + void infoImplementationEmpty(); + void infoImplementationScenesObjects(); + void infoImplementationAnimations(); + void infoImplementationSkins(); + void infoImplementationLights(); + void infoImplementationCameras(); + void infoImplementationMaterials(); + void infoImplementationMeshes(); + void infoImplementationMeshesBounds(); + void infoImplementationTextures(); + void infoImplementationImages(); + /* Image info further tested in ImageConverterTest */ + void infoImplementationReferenceCount(); + void infoImplementationError(); + + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + void info(); + void convert(); + void error(); + #endif + + Utility::Arguments _infoArgs; +}; + +using namespace Containers::Literals; +using namespace Math::Literals; + +const struct { + const char* name; + const char* arg; + const char* expected; + bool printVisualCheck; +} InfoImplementationScenesObjectsData[]{ + {"", "--info", "info-scenes-objects.txt", true}, + {"only scenes", "--info-scenes", "info-scenes.txt", false}, + {"only objects", "--info-objects", "info-objects.txt", false}, +}; + +const struct { + const char* name; + bool oneOrAll; + bool printVisualCheck; +} InfoImplementationOneOrAllData[]{ + {"", true, true}, + {"--info", false, false}, +}; + +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +const struct { + const char* name; + Containers::Array args; + const char* expected; +} InfoData[]{ + {"", Containers::array({}), + "info.txt"}, + {"map", Containers::array({ + "--map"}), + /** @todo change to something else once we have a plugin that can + zero-copy pass the imported data */ + "info.txt"}, + {"ignored output file", Containers::array({ + "whatever.ply"}), + "info-ignored-output.txt"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + const char* expected; + Containers::String message; +} ConvertData[]{ + {"one mesh", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, explicit importer and converter", Containers::array({ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, map", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, options", Containers::array({ + /* It's silly, but since we have option propagation tested in + AnySceneImporter / AnySceneConverter .cpp already, it's enough to + just verify the (nonexistent) options arrive there */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Trade::AnySceneImporter::openFile(): option nonexistentOption not recognized by ObjImporter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, options, explicit importer and converter", Containers::array({ + /* Same here, since we have option propagation tested in + Magnum/Test/ConverterUtilitiesTest.cpp already, to verify it's + getting called we can just supply nonexistent options */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentOption not recognized by ObjImporter\n" + "Option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"concatenate meshes without a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"concatenate meshes with a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles-transformed.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "GltfImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"filter attributes", Containers::array({ + /* Only 0 gets picked from here, others ignored */ + "--only-attributes", "17,0,25-36", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-normals-texcoords.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates", Containers::array({ + "--remove-duplicates", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Duplicate removal: 6 -> 4 vertices\n"}, + {"remove duplicates fuzzy", Containers::array({ + "--remove-duplicates-fuzzy 1.0e-1", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates fuzzy, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates-fuzzy 1.0e-1", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Fuzzy duplicate removal: 6 -> 4 vertices\n"}, + {"one mesh, two converters", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, explicit last", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /** @todo this is a no-op, use some other converter that tests also + that the resulting mesh is actually passed further */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /* As the importers and converters are specified explicitly, there's + no messages from AnySceneConverter, OTOH as we have more than one -C + option the verbose output includes a progress info */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Processing (1/2) with MeshOptimizerSceneConverter...\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Saving output (2/2) with StanfordSceneConverter...\n"}, + {"one mesh, two converters, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-c", "nonexistentAnyConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentAnyConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + "-c", "nonexistentStanfordConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Option nonexistentStanfordConverterOption not recognized by StanfordSceneConverter\n"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + Containers::String message; +} ErrorData[]{ + {"missing output argument", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}), + nullptr, nullptr, + /* The output should be optional only for --info, required otherwise. + No need to test anything else as that's handled by Utility::Arguments + already. Testing just a prefix of the message. */ + "Missing command-line argument output\nUsage:\n "}, + {"can't load importer plugin", Containers::array({ + /* Override also the plugin directory for consistent output */ + "--plugin-dir", "nonexistent", "-I", "NonexistentImporter", "whatever.obj", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + nullptr, nullptr, + "PluginManager::Manager::load(): plugin NonexistentImporter is not static and was not found in nonexistent/importers\n" + "Available importer plugins: "}, + {"can't open a file", Containers::array({ + "noexistent.ffs", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Trade::AnySceneImporter::openFile(): cannot determine the format of noexistent.ffs\n" + "Cannot open file noexistent.ffs\n"}, + {"can't map a file", Containers::array({ + "noexistent.ffs", "--map", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Utility::Path::mapRead(): can't open noexistent.ffs: error 2 (No such file or directory)\n" + "Cannot memory-map file noexistent.ffs\n"}, + {"no meshes found", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + Utility::format("No meshes found in {}\n", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"))}, + {"can't import a mesh", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import the mesh\n"}, + {"can't import a mesh for concatenation", Containers::array({ + "-I", "ObjImporter", "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import mesh 0\n"}, + {"can't import a scene for concatenation", Containers::array({ + /** @todo change to an OBJ once ObjImporter imports materials (and thus + scenes) */ + "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-scene.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + "Trade::GltfImporter::scene(): mesh index 1 in node 0 out of range for 1 meshes\n" + "Cannot import scene 0 for mesh concatenation\n"}, + {"invalid attribute filter", Containers::array({ + "-I", "ObjImporter", "--only-attributes", "LOLNEIN", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Utility::parseNumberSequence(): unrecognized character L in LOLNEIN\n"}, + {"can't load converter plugin", Containers::array({ + /* Override also the plugin directory for consistent output, however + then the importer plugin has to be loaded through an absolute file + path (unless using static plugins) */ + "--plugin-dir", "nonexistent", "-I", + #ifndef MAGNUM_BUILD_STATIC + Utility::Path::join(MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR, "ObjImporter" + Trade::AbstractImporter::pluginSuffix()), + #else + "ObjImporter", + #endif + "-C", "NonexistentSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + /* Just a prefix */ + "PluginManager::Manager::load(): plugin NonexistentSceneConverter is not static and was not found in nonexistent/sceneconverters\n" + "Available converter plugins: "}, + {"file coversion failed", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx")}), + "ObjImporter", "AnySceneConverter", + Utility::format("Trade::AnySceneConverter::convertToFile(): cannot determine the format of {0}\n" + "Cannot save file {0}\n", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx"))}, + {"mesh coversion failed", Containers::array({ + "-I", "ObjImporter", "-C", "MeshOptimizerSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "MeshOptimizerSceneConverter", + "Trade::MeshOptimizerSceneConverter::convert(): expected a triangle mesh, got MeshPrimitive::Points\n" + "MeshOptimizerSceneConverter cannot convert the mesh\n"}, + {"plugin doesn't support mesh conversion", Containers::array({ + /* Pass the same plugin twice, which means the first instance should + get used for a mesh-to-mesh conversion */ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", "-C", "StanfordSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "StanfordSceneConverter", + "StanfordSceneConverter doesn't support mesh conversion, only Trade::SceneConverterFeature::ConvertMeshToData\n"}, +}; +#endif + +SceneConverterTest::SceneConverterTest() { + addTests({&SceneConverterTest::infoImplementationEmpty}); + + addInstancedTests({&SceneConverterTest::infoImplementationScenesObjects}, + Containers::arraySize(InfoImplementationScenesObjectsData)); + + addInstancedTests({&SceneConverterTest::infoImplementationAnimations, + &SceneConverterTest::infoImplementationSkins, + &SceneConverterTest::infoImplementationLights, + &SceneConverterTest::infoImplementationCameras, + &SceneConverterTest::infoImplementationMaterials, + &SceneConverterTest::infoImplementationMeshes}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationMeshesBounds}); + + addInstancedTests({&SceneConverterTest::infoImplementationTextures, + &SceneConverterTest::infoImplementationImages}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationReferenceCount, + &SceneConverterTest::infoImplementationError}); + + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + addInstancedTests({&SceneConverterTest::info}, + Containers::arraySize(InfoData)); + + addInstancedTests({&SceneConverterTest::convert}, + Containers::arraySize(ConvertData)); + + addInstancedTests({&SceneConverterTest::error}, + Containers::arraySize(ErrorData)); + #endif + + /* A subset of arguments needed by the info printing code */ + _infoArgs.addBooleanOption("info") + .addBooleanOption("info-scenes") + .addBooleanOption("info-objects") + .addBooleanOption("info-animations") + .addBooleanOption("info-skins") + .addBooleanOption("info-lights") + .addBooleanOption("info-cameras") + .addBooleanOption("info-materials") + .addBooleanOption("info-meshes") + .addBooleanOption("info-textures") + .addBooleanOption("info-images") + .addBooleanOption("bounds"); + + /* Create output dir, if doesn't already exist */ + Utility::Path::make(Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles")); +} + +void SceneConverterTest::infoImplementationEmpty() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE(out.str(), ""); +} + +void SceneConverterTest::infoImplementationScenesObjects() { + auto&& data = InfoImplementationScenesObjectsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* First scene has 4, second 7, the last three are not in any scene and + thus not listed. Object 5 has no fields and thus not listed either. */ + UnsignedLong doObjectCount() const override { return 10; } + UnsignedInt doSceneCount() const override { return 2; } + Containers::String doSceneName(UnsignedInt id) override { + return id == 0 ? "A simple scene" : ""; + } + Containers::String doObjectName(UnsignedLong id) override { + if(id == 0) return "Parent-less mesh"; + if(id == 2) return "Two meshes, shared among two scenes"; + if(id == 4) return "Two custom arrays"; + if(id == 6) return "Only in the second scene, but no fields, thus same as unreferenced"; + if(id == 8) return "Not in any scene"; + return ""; + } + Containers::String doSceneFieldName(UnsignedInt name) override { + if(name == 1337) return "DirectionVector"; + return ""; + } + Containers::Optional doScene(UnsignedInt id) override { + /* Builtin fields, some duplicated, one marked as ordered */ + if(id == 0) { + Containers::ArrayView parentMapping; + Containers::ArrayView parents; + Containers::ArrayView meshMapping; + Containers::ArrayView meshes; + Containers::ArrayTuple data{ + {NoInit, 3, parentMapping}, + {ValueInit, 3, parents}, + {NoInit, 4, meshMapping}, + {ValueInit, 4, meshes}, + }; + Utility::copy({1, 3, 2}, parentMapping); + Utility::copy({2, 0, 2, 1}, meshMapping); + /* No need to fill the data, zero-init is fine */ + return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, std::move(data), { + Trade::SceneFieldData{Trade::SceneField::Parent, parentMapping, parents}, + Trade::SceneFieldData{Trade::SceneField::Mesh, meshMapping, meshes, Trade::SceneFieldFlag::OrderedMapping}, + }}; + } + + /* Two custom fields, one array. Stored as an external memory. */ + if(id == 1) { + return Trade::SceneData{Trade::SceneMappingType::UnsignedByte, 8, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, scene2Data, { + Trade::SceneFieldData{Trade::sceneFieldCustom(42), Containers::arrayView(scene2Data->customMapping), Containers::arrayView(scene2Data->custom)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(1337), Trade::SceneMappingType::UnsignedByte, scene2Data->customArrayMapping, Trade::SceneFieldType::Short, scene2Data->customArray, 3}, + }}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedByte customMapping[2]; + Double custom[2]; + UnsignedByte customArrayMapping[3]; + Vector3s customArray[3]; + } scene2Data[1]{{ + /* No need to fill the data, zero-init is fine */ + {7, 3}, {}, {2, 4, 4}, {} + }}; + } importer; + + const char* argv[]{"", data.arg}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationAnimations() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::String doAnimationName(UnsignedInt id) override { + return id == 1 ? "Custom track duration and interpolator function" : ""; + } + Containers::Optional doAnimation(UnsignedInt id) override { + /* First has two tracks with a shared time and implicit duration, + one with a different result type. */ + if(id == 0) { + Containers::ArrayView time; + Containers::ArrayView translation; + Containers::ArrayView rotation; + Containers::ArrayTuple data{ + {ValueInit, 3, time}, + {ValueInit, 3, translation}, + {ValueInit, 3, rotation} + }; + Utility::copy({0.5f, 1.0f, 1.25f}, time); + return Trade::AnimationData{std::move(data), { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Translation2D, 17, Animation::TrackView{time, translation, Animation::Interpolation::Linear, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Rotation2D, 17, Animation::TrackView{time, rotation, Animation::Interpolation::Constant, Animation::Extrapolation::Extrapolated}}, + }}; + } + + /* Second has track duration different from animation duration and + a custom interpolator. Stored as an external memory. */ + if(id == 1) { + return Trade::AnimationData{Trade::DataFlag::ExternallyOwned, animation2Data, { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Scaling3D, 666, Animation::TrackView{animation2Data->time, animation2Data->scaling, Math::lerp, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + }, {0.1f, 1.3f}}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + Float time[5]; + Vector3 scaling[5]; + } animation2Data[1]{{ + {0.75f, 0.75f, 1.0f, 1.0f, 1.25f}, + {} + }}; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-animations" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-animations.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationSkins() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 1 ? "Second 2D skin, external data" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + /* First a regular skin, second externally owned */ + if(id == 0) return Trade::SkinData2D{ + {3, 6, 7, 12, 22}, + {{}, {}, {}, {}, {}} + }; + + if(id == 1) return Trade::SkinData2D{Trade::DataFlag::ExternallyOwned, skin2JointData, Trade::DataFlag::ExternallyOwned, skin2MatrixData}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "First 3D skin, external data" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + /* Reverse order in 3D, plus one more to ensure the count isn't + mismatched between 2D and 3D */ + if(id == 0) return Trade::SkinData3D{Trade::DataFlag::ExternallyOwned, skin3JointData, Trade::DataFlag::ExternallyOwned, skin3MatrixData}; + + if(id == 1) return Trade::SkinData3D{ + {3, 22}, + {{}, {}} + }; + + if(id == 2) return Trade::SkinData3D{ + {3}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt skin2JointData[15]; + Matrix3 skin2MatrixData[15]; + UnsignedInt skin3JointData[12]; + Matrix4 skin3MatrixData[12]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-skins" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-skins.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationLights() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doLightCount() const override { return 2; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Directional light with always-implicit attenuation and range" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + /* First a blue spot light */ + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Spot, + 0x3457ff_rgbf, + 15.0f, + {1.2f, 0.3f, 0.04f}, + 100.0f, + 55.0_degf, + 85.0_degf + }; + + /* Second a yellow directional light with infinite range */ + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0xff5734_rgbf, + 5.0f + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-lights" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-lights.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationCameras() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doCameraCount() const override { return 3; } + Containers::String doCameraName(UnsignedInt id) override { + return id == 0 ? "Orthographic 2D" : ""; + } + Containers::Optional doCamera(UnsignedInt id) override { + /* First 2D ortho camera, where near/far will get omited */ + if(id == 0) return Trade::CameraData{ + Trade::CameraType::Orthographic2D, + {5.0f, 6.0f}, + 0.0f, 0.0f + }; + + /* 3D ortho camera */ + if(id == 1) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 3.0f}, + -1.0f, 0.5f + }; + + /* Third a perspective camera, specified with size, but printed + with FoV */ + if(id == 2) return Trade::CameraData{ + Trade::CameraType::Perspective3D, + 35.0_degf, 4.0f/3.0f, 0.01f, 100.0f + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-cameras" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-cameras.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMaterials() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 1 ? "Lots o' laierz" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + /* First has custom attributes */ + if(id == 0) return Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0x3bd26799_rgbaf}, + {Trade::MaterialAttribute::DoubleSided, true}, + {Trade::MaterialAttribute::EmissiveColor, 0xe9eca_rgbf}, + {Trade::MaterialAttribute::RoughnessTexture, 67u}, + {Trade::MaterialAttribute::RoughnessTextureMatrix, Matrix3::translation({0.25f, 0.75f})}, + {Trade::MaterialAttribute::RoughnessTextureSwizzle, Trade::MaterialTextureSwizzle::B}, + {"reflectionAngle", 35.0_degf}, + /* These shouldn't have a color swatch rendered */ + {"notAColour4", Vector4{0.1f, 0.2f, 0.3f, 0.4f}}, + {"notAColour3", Vector3{0.2f, 0.3f, 0.4f}}, + {"deadBeef", reinterpret_cast(0xdeadbeef)}, + {"undeadBeef", reinterpret_cast(0xbeefbeef)}, + }}; + + /* Second has layers, custom layers, unnamed layers and a name */ + if(id == 1) return Trade::MaterialData{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Phong, { + {Trade::MaterialAttribute::DiffuseColor, 0xc7cf2f99_rgbaf}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.5f}, + {Trade::MaterialAttribute::LayerFactorTexture, 3u}, + {Trade::MaterialAttribute::LayerName, "anEmptyLayer"}, + {Trade::MaterialAttribute::LayerFactor, 0.25f}, + {Trade::MaterialAttribute::LayerFactorTexture, 2u}, + {"yes", "a string"}, + }, {1, 4, 5, 8}}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-materials" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-materials.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshes() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 3; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "LODs? No, meshets." : ""; + } + Containers::String doMeshAttributeName(UnsignedShort name) override { + if(name == 25) return "vertices"; + if(name == 26) return "triangles"; + /* 37 (triangleCount) deliberately not named */ + if(name == 116) return "vertexCount"; + + return ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + /* First is indexed & externally owned */ + if(id == 0 && level == 0) return Trade::MeshData{MeshPrimitive::Points, + Trade::DataFlag::ExternallyOwned, indices, + Trade::MeshIndexData{indices}, + Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, points, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(points)} + }}; + + /* Second is multi-level, with second level being indexed meshlets + with custom (array) attributes */ + if(id == 1 && level == 0) { + Containers::ArrayView positions; + Containers::ArrayView tangents; + Containers::ArrayTuple data{ + {NoInit, 250, positions}, + {NoInit, 250, tangents}, + }; + return Trade::MeshData{MeshPrimitive::Triangles, std::move(data), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, tangents}, + }}; + } + if(id == 1 && level == 1) { + Containers::StridedArrayView2D vertices; + Containers::StridedArrayView2D indices; + Containers::ArrayView triangleCount; + Containers::ArrayView vertexCount; + Containers::ArrayTuple data{ + {NoInit, {135, 64}, vertices}, + {NoInit, {135, 126}, indices}, + {NoInit, 135, triangleCount}, + {NoInit, 135, vertexCount}, + }; + return Trade::MeshData{MeshPrimitive::Meshlets, std::move(data), { + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), vertices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(26), indices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(37), triangleCount}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(116), vertexCount}, + }}; + } + + /* Third is an empty instance mesh */ + if(id == 2 && level == 0) return Trade::MeshData{MeshPrimitive::Instances, 15}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedShort indices[70]; + Vector3 points[50]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-meshes" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshesBounds() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return Trade::MeshData{MeshPrimitive::Lines, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData->positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, Containers::arrayView(vertexData->tangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent, Containers::arrayView(vertexData->bitangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectId)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData->normal)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, Containers::arrayView(vertexData->textureCoordinates)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, Containers::arrayView(vertexData->color)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectIdSecondary)}, + }}; + } + + UnsignedByte indexData[3]{15, 3, 176}; + + struct { + Vector3 positions[2]; + Vector3 tangent[2]; + Vector3 bitangent[2]; + UnsignedShort objectId[2]; + Vector3 normal[2]; + Vector2 textureCoordinates[2]; + Vector4 color[2]; + UnsignedInt objectIdSecondary[2]; + } vertexData[1]{{ + {{0.1f, -0.1f, 0.2f}, {0.2f, 0.0f, -0.2f}}, + {{0.2f, -0.2f, 0.8f}, {0.3f, 0.8f, 0.2f}}, + {{0.4f, 0.2f, 1.0f}, {0.3f, 0.9f, 0.0f}}, + {155, 12}, + {{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}}, + {{0.5f, 0.5f}, {1.5f, 0.5f}}, + {0x99336600_rgbaf, 0xff663333_rgbaf}, + {15, 337}, + }}; + } importer; + + const char* argv[]{"", "--info-meshes", "--bounds"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes-bounds.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationTextures() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doTextureCount() const override { return 2; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Name!" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + /* First a 1D texture */ + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 666 + }; + + /* Second a 2D array texture */ + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture2DArray, + SamplerFilter::Linear, + SamplerFilter::Nearest, + SamplerMipmap::Linear, + {SamplerWrapping::MirroredRepeat, SamplerWrapping::ClampToEdge, SamplerWrapping::MirrorClampToEdge}, + 3 + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-textures" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-textures.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationImages() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Just the very basics to ensure image info *is* printed. Tested in full + in ImageConverterTest. */ + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + return Trade::ImageData1D{PixelFormat::R32F, 1024, Containers::Array{NoInit, 4096}}; + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-images" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-images.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationReferenceCount() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* One data of each kind should be always referenced twice+, one once, + one not at all, and one reference should be OOB */ + + UnsignedLong doObjectCount() const override { return 4; } + Containers::String doObjectName(UnsignedLong id) override { + return id == 2 ? "Not referenced" : ""; + } + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + if(id == 0) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 2, {}, sceneData3D, { + /* To mark the scene as 3D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->materials)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->lights)}, + Trade::SceneFieldData{Trade::SceneField::Camera, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->cameras)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->skins)}, + }}; + if(id == 1) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, {}, sceneData2D, { + /* To mark the scene as 2D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->skins)}, + }}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin2DCount() const override { return 3; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData2D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData2D{ + {33, 10, 100}, + {{}, {}, {}} + }; + if(id == 2) return Trade::SkinData2D{ + {66}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData3D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData3D{ + {37}, + {{}} + }; + if(id == 2) return Trade::SkinData3D{ + {300, 10, 1000}, + {{}, {}, {}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doLightCount() const override { return 3; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x57ff34_rgbf, + 5.0f + }; + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Ambient, + 0xff5734_rgbf, + 0.1f + }; + if(id == 2) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x3457ff_rgbf, + 1.0f + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doCameraCount() const override { return 3; } + Containers::String doCameraName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doCamera(UnsignedInt id) override { + if(id == 0) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 3.0f}, + -1.0f, 0.5f + }; + if(id == 1) return Trade::CameraData{ + Trade::CameraType::Orthographic3D, + {2.0f, 2.0f}, + 0.0f, 1.0f + }; + if(id == 2) return Trade::CameraData{ + Trade::CameraType::Orthographic2D, + {2.0f, 2.0f}, + 0.0f, 0.0f + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMaterialCount() const override { return 3; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + if(id == 0) return Trade::MaterialData{{}, { + {Trade::MaterialAttribute::DiffuseTexture, 2u}, + {Trade::MaterialAttribute::BaseColorTexture, 2u}, + }}; + if(id == 1) return Trade::MaterialData{{}, { + {"lookupTexture", 0u}, + {"volumeTexture", 3u}, + {Trade::MaterialAttribute::NormalTexture, 17u}, + {Trade::MaterialAttribute::EmissiveTexture, 4u}, + }}; + if(id == 2) return Trade::MaterialData{{}, {}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMeshCount() const override { return 3; } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + if(id == 0) return Trade::MeshData{MeshPrimitive::Points, 5}; + if(id == 1) return Trade::MeshData{MeshPrimitive::Lines, 4}; + if(id == 2) return Trade::MeshData{MeshPrimitive::TriangleFan, 4}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doTextureCount() const override { return 5; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture1DArray, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 225 + }; + if(id == 2) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + if(id == 3) return Trade::TextureData{ + Trade::TextureType::Texture3D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 4) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData1D{PixelFormat::RGBA8I, 1, Containers::Array{NoInit, 4}}; + if(id == 1) + return Trade::ImageData1D{PixelFormat::R8I, 4, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData2D{PixelFormat::RGBA8I, {1, 2}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData2D{PixelFormat::R8I, {4, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData3D{PixelFormat::RGBA8I, {1, 2, 1}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData3D{PixelFormat::R8I, {4, 1, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedInt mapping[4]; + UnsignedInt meshes[4]; + Int materials[4]; + UnsignedInt lights[4]; + UnsignedInt cameras[4]; + UnsignedInt skins[4]; + } sceneData3D[1]{{ + {0, 1, 1, 25}, + {2, 0, 2, 67}, + {0, 1, 23, 0}, + {0, 17, 0, 2}, + {166, 1, 2, 1}, + {1, 1, 22, 2} + }}; + + struct { + UnsignedInt mapping[3]; + UnsignedInt meshes[3]; + UnsignedInt skins[3]; + } sceneData2D[1]{{ + {3, 116, 1}, + {2, 0, 23}, + {177, 0, 1} + }}; + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-references.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* The one single object is named, and that name should be printed + after all error messages */ + UnsignedLong doObjectCount() const override { return 1; } + Containers::String doObjectName(UnsignedLong) override { return "A name"; } + + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + Error{} << "Scene" << id << "error!"; + return {}; + } + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::Optional doAnimation(UnsignedInt id) override { + Error{} << "Animation" << id << "error!"; + return {}; + } + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::Optional doSkin2D(UnsignedInt id) override { + Error{} << "2D skin" << id << "error!"; + return {}; + } + + UnsignedInt doSkin3DCount() const override { return 2; } + Containers::Optional doSkin3D(UnsignedInt id) override { + Error{} << "3D skin" << id << "error!"; + return {}; + } + + UnsignedInt doLightCount() const override { return 2; } + Containers::Optional doLight(UnsignedInt id) override { + Error{} << "Light" << id << "error!"; + return {}; + } + + UnsignedInt doCameraCount() const override { return 2; } + Containers::Optional doCamera(UnsignedInt id) override { + Error{} << "Camera" << id << "error!"; + return {}; + } + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::Optional doMaterial(UnsignedInt id) override { + Error{} << "Material" << id << "error!"; + return {}; + } + + UnsignedInt doMeshCount() const override { return 2; } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + Error{} << "Mesh" << id << "error!"; + return {}; + } + + UnsignedInt doTextureCount() const override { return 2; } + Containers::Optional doTexture(UnsignedInt id) override { + Error{} << "Texture" << id << "error!"; + return {}; + } + + /* Errors for all image types tested in ImageConverterTest */ + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "Image" << id << "error!"; + return {}; + } + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* It should return a failure */ + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == true); + CORRADE_COMPARE(out.str(), + /* It should not exit after first error... */ + "Scene 0 error!\n" + "Scene 1 error!\n" + "Animation 0 error!\n" + "Animation 1 error!\n" + "2D skin 0 error!\n" + "2D skin 1 error!\n" + "3D skin 0 error!\n" + "3D skin 1 error!\n" + "Light 0 error!\n" + "Light 1 error!\n" + "Camera 0 error!\n" + "Camera 1 error!\n" + "Material 0 error!\n" + "Material 1 error!\n" + "Mesh 0 error!\n" + "Mesh 1 error!\n" + "Texture 0 error!\n" + "Texture 1 error!\n" + "Image 0 error!\n" + "Image 1 error!\n" + /* ... and it should print all info output after the errors */ + "Object 0: A name\n"); +} + +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +namespace { + +#ifdef SCENECONVERTER_EXECUTABLE_FILENAME +/** @todo take a StringIterable once it exists */ +Containers::Pair call(Containers::ArrayView arguments) { + /* Create a string view array for the arguments, implicitly pass the + application name and plugin directory override */ + /** @todo drop once StringIterable exists */ + Containers::Array argumentViews{ValueInit, arguments.size() + 3}; + argumentViews[0] = ""_s; + argumentViews[1] = "--plugin-dir"_s; + argumentViews[2] = MAGNUM_PLUGINS_INSTALL_DIR; + for(std::size_t i = 0; i != arguments.size(); ++i) + argumentViews[i + 3] = arguments[i]; + + const Containers::String outputFilename = Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/output.txt"); + /** @todo clean up once Utility::System::execute() with output redirection + exists */ + const bool success = std::system(Utility::format("{} {} > {} 2>&1", + SCENECONVERTER_EXECUTABLE_FILENAME, + " "_s.join(argumentViews), /** @todo handle space escaping here? */ + outputFilename + ).data()) == 0; + + const Containers::Optional output = Utility::Path::readString(outputFilename); + CORRADE_VERIFY(output); + + return {success, std::move(*output)}; +} +#endif + +} + +void SceneConverterTest::info() { + auto&& data = InfoData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + if(!(importerManager.load("ObjImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("ObjImporter plugin can't be loaded."); + + Containers::Array args{InPlaceInit, + {"-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}}; + arrayAppend(args, arrayView(data.args)); /** @todo FFS fix the casts */ + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(args); + CORRADE_COMPARE_AS(output.second(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); + CORRADE_VERIFY(output.first()); + #endif +} + +void SceneConverterTest::convert() { + auto&& data = ConvertData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + /* AnySceneImporter & AnySceneConverter are required implicitly for + simplicity */ + if(!(importerManager.load("AnySceneImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneImporter plugin can't be loaded."); + if(!(converterManager.load("AnySceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneConverter plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + CORRADE_COMPARE(output.second(), data.message); + CORRADE_VERIFY(output.first()); + + CORRADE_COMPARE_AS(Utility::Path::join({SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles", data.expected}), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::File); + #endif +} + +void SceneConverterTest::error() { + auto&& data = ErrorData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + /* If the message ends with a \n, assume it's the whole message. Otherwise + it's just a prefix. */ + if(data.message.hasSuffix('\n')) + CORRADE_COMPARE(output.second(), data.message); + else + CORRADE_COMPARE_AS(output.second(), + data.message, + TestSuite::Compare::StringHasPrefix); + /* It should return a non-zero code */ + CORRADE_VERIFY(!output.first()); + #endif +} +#endif + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::SceneConverterTest) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj new file mode 100644 index 000000000..0d41da794 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj @@ -0,0 +1,3 @@ +# A point with two indices +v 1 2 3 +p 5 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf new file mode 100644 index 000000000..c2e5f1095 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf @@ -0,0 +1,22 @@ +{ + "asset": { + "version": "2.0" + }, + "scenes": [ + { + "nodes": [0] + } + ], + "meshes": [ + { + "primitives": [ + {} + ] + } + ], + "nodes": [ + { + "mesh": 1 + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf new file mode 100644 index 000000000..8b778ccfb --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf @@ -0,0 +1,5 @@ +{ + "asset": { + "version": "2.0" + } +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt new file mode 100644 index 000000000..df71d5da7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt @@ -0,0 +1,12 @@ +Animation 0: + Duration: {0.5, 1.25} (0.1 kB) + Track 0: Translation2D @ Vector2, 3 keyframes + Interpolation: Linear, DefaultConstructed, Constant + Track 1: Rotation2D @ CubicHermite2D -> Vector2, 3 keyframes + Interpolation: Constant, Extrapolated, Extrapolated +Animation 1: Custom track duration and interpolator function + Duration: {0.1, 1.3} (0.1 kB, ExternallyOwned) + Track 0: Scaling3D @ Vector3, 5 keyframes + Duration: {0.75, 1.25} + Interpolation: Custom, DefaultConstructed, Constant +Total animation data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt new file mode 100644 index 000000000..9977ea326 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-cameras.txt @@ -0,0 +1,12 @@ +Camera 0: Orthographic 2D + Type: Orthographic2D + Size: {5, 6} + Aspect ratio: 0.833333 +Camera 1: + Type: Orthographic3D + Size: {2, 3}, -1 - 0.5 + Aspect ratio: 0.666667 +Camera 2: + Type: Perspective3D + FoV: 35°, 0.01 - 100 + Aspect ratio: 1.33333 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt new file mode 100644 index 000000000..ed3609a7e --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt @@ -0,0 +1,6 @@ +Ignoring output file for --info: whatever.ply +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt new file mode 100644 index 000000000..7455a53f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt @@ -0,0 +1,3 @@ +1D image 0: + Level 0: {1024} @ R32F (4.0 kB) +Total image data size: 4.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt new file mode 100644 index 000000000..7942af9c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt @@ -0,0 +1,8 @@ +Light 0: + Type: Spot, 55° - 85° + Color: {0.203922, 0.341176, 1} * 15 + Attenuation: {1.2, 0.3, 0.04} + Range: 100 +Light 1: Directional light with always-implicit attenuation and range + Type: Directional + Color: {1, 0.341176, 0.203922} * 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt new file mode 100644 index 000000000..35175acc6 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt @@ -0,0 +1,28 @@ +Material 0: + Type: PbrMetallicRoughness + Base layer: + BaseColor @ Vector4: {0.231373, 0.823529, 0.403922, 0.6} + DoubleSided @ Bool: true + EmissiveColor @ Vector3: {0.054902, 0.619608, 0.792157} + RoughnessTexture @ UnsignedInt: 67 + RoughnessTextureMatrix @ Matrix3x3: {1, 0, 0.25, + 0, 1, 0.75, + 0, 0, 1} + RoughnessTextureSwizzle @ TextureSwizzle: B + deadBeef @ Pointer: 0xdeadbeef + notAColour3 @ Vector3: {0.2, 0.3, 0.4} + notAColour4 @ Vector4: {0.1, 0.2, 0.3, 0.4} + reflectionAngle @ Deg: 35 + undeadBeef @ MutablePointer: 0xbeefbeef +Material 1: Lots o' laierz + Type: Phong|PbrClearCoat + Base layer: + DiffuseColor @ Vector4: {0.780392, 0.811765, 0.184314, 0.6} + Layer 1: ClearCoat + LayerFactor @ Float: 0.5 + LayerFactorTexture @ UnsignedInt: 3 + Layer 2: anEmptyLayer + Layer 3: + LayerFactor @ Float: 0.25 + LayerFactorTexture @ UnsignedInt: 2 + yes @ String: a string diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt new file mode 100644 index 000000000..e5195e9f7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt @@ -0,0 +1,21 @@ +Mesh 0: + Level 0: 2 vertices @ Lines (0.2 kB, {}) + Position @ Vector3, offset 0, stride 12 + Bounds: ({0.1, -0.1, -0.2}, {0.2, 0, 0.2}) + Tangent @ Vector3, offset 24, stride 12 + Bounds: ({0.2, -0.2, 0.2}, {0.3, 0.8, 0.8}) + Bitangent @ Vector3, offset 48, stride 12 + Bounds: ({0.3, 0.2, 0}, {0.4, 0.9, 1}) + ObjectId @ UnsignedShort, offset 72, stride 2 + Bounds: (12, 155) + Normal @ Vector3, offset 76, stride 12 + Bounds: ({0, 0, 0}, {1, 1, 1}) + TextureCoordinates @ Vector2, offset 100, stride 8 + Bounds: ({0.5, 0.5}, {1.5, 0.5}) + Color @ Vector4, offset 116, stride 16 + Bounds: ({0.6, 0.2, 0.2, 0}, {1, 0.4, 0.4, 0.2}) + ObjectId @ UnsignedInt, offset 148, stride 4 + Bounds: (15, 337) + 3 indices @ UnsignedByte, offset 0, stride 1 (0.0 kB, {}) + Bounds: (3, 176) +Total mesh data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt new file mode 100644 index 000000000..b96fb94c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt @@ -0,0 +1,16 @@ +Mesh 0: + Level 0: 50 vertices @ Points (0.6 kB, ExternallyOwned|Mutable) + Position @ Vector3, offset 0, stride 12 + 70 indices @ UnsignedShort, offset 0, stride 2 (0.1 kB, ExternallyOwned) +Mesh 1: LODs? No, meshets. + Level 0: 250 vertices @ Triangles (6.8 kB) + Position @ Vector3, offset 0, stride 12 + Tangent @ Vector4, offset 3000, stride 16 + Level 1: 135 vertices @ Meshlets (83.8 kB) + Custom(25:vertices) @ UnsignedInt[64], offset 0, stride 256 + Custom(26:triangles) @ Vector3ub[126], offset 34560, stride 378 + Custom(37:) @ UnsignedByte, offset 85590, stride 1 + Custom(116:vertexCount) @ UnsignedByte, offset 85725, stride 1 +Mesh 2: + Level 0: 15 vertices @ Instances (0.0 kB) +Total mesh data size: 91.4 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt new file mode 100644 index 000000000..89a64cfb8 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt @@ -0,0 +1,5 @@ +Object 0: Parent-less mesh +Object 2: Two meshes, shared among two scenes +Object 4: Two custom arrays +Object 6: Only in the second scene, but no fields, thus same as unreferenced +Object 8: Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt new file mode 100644 index 000000000..927de37b7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt @@ -0,0 +1,111 @@ +Scene 0: + Bound: 2 objects @ UnsignedInt (0.1 kB, {}) + Fields: + Transformation @ Matrix4x4, 0 entries + Mesh @ UnsignedInt, 4 entries + MeshMaterial @ Int, 4 entries + Light @ UnsignedInt, 4 entries + Camera @ UnsignedInt, 4 entries + Skin @ UnsignedInt, 4 entries +Scene 1: + Bound: 4 objects @ UnsignedInt (0.0 kB, {}) + Fields: + Transformation @ Matrix3x3, 0 entries + Mesh @ UnsignedInt, 3 entries + Skin @ UnsignedInt, 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): + Fields: Mesh, MeshMaterial, Light, Camera, Skin +Object 1 (referenced by 2 scenes): + Fields: Mesh[2], MeshMaterial[2], Light[2], Camera[2], Skin[2], Mesh, Skin +Object 2 (referenced by 0 scenes): Not referenced +Object 3 (referenced by 1 scenes): + Fields: Mesh, Skin +2D skin 0 (referenced by 1 objects): + 2 joints +2D skin 1 (referenced by 1 objects): + 3 joints +2D skin 2 (referenced by 0 objects): Not referenced + 1 joints +3D skin 0 (referenced by 0 objects): Not referenced + 2 joints +3D skin 1 (referenced by 2 objects): + 1 joints +3D skin 2 (referenced by 1 objects): + 3 joints +Light 0 (referenced by 2 objects): + Type: Directional + Color: {0.341176, 1, 0.203922} * 5 +Light 1 (referenced by 0 objects): Not referenced + Type: Ambient + Color: {1, 0.341176, 0.203922} * 0.1 +Light 2 (referenced by 1 objects): + Type: Directional + Color: {0.203922, 0.341176, 1} +Camera 0 (referenced by 0 objects): Not referenced + Type: Orthographic3D + Size: {2, 3}, -1 - 0.5 + Aspect ratio: 0.666667 +Camera 1 (referenced by 2 objects): + Type: Orthographic3D + Size: {2, 2}, 0 - 1 + Aspect ratio: 1 +Camera 2 (referenced by 1 objects): + Type: Orthographic2D + Size: {2, 2} + Aspect ratio: 1 +Material 0 (referenced by 2 objects): + Type: {} + Base layer: + BaseColorTexture @ UnsignedInt: 2 + DiffuseTexture @ UnsignedInt: 2 +Material 1 (referenced by 1 objects): + Type: {} + Base layer: + EmissiveTexture @ UnsignedInt: 4 + NormalTexture @ UnsignedInt: 17 + lookupTexture @ UnsignedInt: 0 + volumeTexture @ UnsignedInt: 3 +Material 2 (referenced by 0 objects): Not referenced + Type: {} + Base layer: +Mesh 0 (referenced by 2 objects): + Level 0: 5 vertices @ Points (0.0 kB) +Mesh 1 (referenced by 0 objects): Not referenced + Level 0: 4 vertices @ Lines (0.0 kB) +Mesh 2 (referenced by 3 objects): + Level 0: 4 vertices @ TriangleFan (0.0 kB) +Total mesh data size: 0.0 kB +Texture 0 (referenced by 1 material attributes): + Type: Texture1D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1 (referenced by 0 material attributes): Not referenced + Type: Texture1DArray, image 225 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 2 (referenced by 2 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 3 (referenced by 1 material attributes): + Type: Texture3D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 4 (referenced by 1 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +1D image 0 (referenced by 0 textures): Not referenced + Level 0: {1} @ RGBA8I (0.0 kB) +1D image 1 (referenced by 1 textures): + Level 0: {4} @ R8I (0.0 kB) +2D image 0 (referenced by 2 textures): + Level 0: {1, 2} @ RGBA8I (0.0 kB) +2D image 1 (referenced by 0 textures): Not referenced + Level 0: {4, 1} @ R8I (0.0 kB) +3D image 0 (referenced by 0 textures): Not referenced + Level 0: {1, 2, 1} @ RGBA8I (0.0 kB) +3D image 1 (referenced by 1 textures): + Level 0: {4, 1, 1} @ R8I (0.0 kB) +Total image data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt new file mode 100644 index 000000000..781d20482 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt @@ -0,0 +1,25 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): Parent-less mesh + Fields: Mesh +Object 1 (referenced by 1 scenes): + Fields: Parent, Mesh +Object 2 (referenced by 2 scenes): Two meshes, shared among two scenes + Fields: Parent, Mesh[2], Custom(1337:DirectionVector) +Object 3 (referenced by 2 scenes): + Fields: Parent, Custom(42:) +Object 4 (referenced by 1 scenes): Two custom arrays + Fields: Custom(1337:DirectionVector)[2] +Object 6 (referenced by 0 scenes): Only in the second scene, but no fields, thus same as unreferenced +Object 7 (referenced by 1 scenes): + Fields: Custom(42:) +Object 8 (referenced by 0 scenes): Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt new file mode 100644 index 000000000..acd050cc3 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt @@ -0,0 +1,11 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt new file mode 100644 index 000000000..e8eb3a983 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt @@ -0,0 +1,10 @@ +2D skin 0: + 5 joints +2D skin 1: Second 2D skin, external data + 15 joints +3D skin 0: First 3D skin, external data + 12 joints +3D skin 1: + 2 joints +3D skin 2: + 1 joints diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt new file mode 100644 index 000000000..0b3afe593 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt @@ -0,0 +1,8 @@ +Texture 0: + Type: Texture1D, image 666 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1: Name! + Type: Texture2DArray, image 3 + Minification, mipmap and magnification: Linear, Linear, Nearest + Wrapping: {MirroredRepeat, ClampToEdge, ClampToEdge} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt new file mode 100644 index 000000000..1f10cb3cc --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt @@ -0,0 +1,5 @@ +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj new file mode 100644 index 000000000..d90424cae --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj @@ -0,0 +1,2 @@ +v 1 2 3 +p 1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj new file mode 100644 index 000000000..f3f2acd4c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -0.9 0.9 0 +v 0.9 -0.9 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj new file mode 100644 index 000000000..9c6d005ff --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply new file mode 100644 index 000000000..aa319b344 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj new file mode 100644 index 000000000..e96d09630 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj @@ -0,0 +1,12 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +vn 0 0 1 +vt 0 0 +f 1/1/1 2/1/1 3/1/1 +f 1/1/1 3/1/1 4/1/1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj new file mode 100644 index 000000000..a552bb87c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj @@ -0,0 +1,10 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +f 1 2 3 +f 1 3 4 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply new file mode 100644 index 000000000..e36655e07 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin new file mode 100644 index 000000000..84f5a85a4 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in new file mode 100644 index 000000000..5a64016f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in @@ -0,0 +1,15 @@ +# Like two-triangles.obj, but the second triangle moved 10 units on Y. Convert +# with magnum-plugins/src/MagnumPlugins/GltfImporter/Test/in2bin.py. + +type = "3f3f3f 3f3f3f" +input = [ + -1, 1, 0, + -1, -1, 0, + 1, -1, 0, + + -1, 11, 0, + 1, 9, 0, + 1, 11, 0 +] + +# kate: hl python diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf new file mode 100644 index 000000000..b9baca103 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf @@ -0,0 +1,75 @@ +{ + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "two-triangles-transformed.bin", + "byteLength": 72 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 36 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 36 + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 3, + "type": "VEC3" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 0 + } + } + ] + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 1 + } + } + ] + } + ], + "nodes": [ + { + "mesh": 0 + }, + { + "name": "Explicit intermediate node to test proper traversal", + "translation": [0, -5, 0], + "children": [2] + }, + { + "mesh": 1, + "translation": [0, -5, 0] + } + ], + "scenes": [ + { + "nodes": [0, 1] + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj new file mode 100644 index 000000000..66a190eb5 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj @@ -0,0 +1,14 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +o First +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +o Second +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake new file mode 100644 index 000000000..01fccabe8 --- /dev/null +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -0,0 +1,50 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" +#define SCENETOOLS_TEST_OUTPUT_DIR "${SCENETOOLS_TEST_OUTPUT_DIR}" +#cmakedefine SCENECONVERTER_EXECUTABLE_FILENAME "${SCENECONVERTER_EXECUTABLE_FILENAME}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#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_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 88afda92f..87c5f2410 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -23,9 +23,7 @@ DEALINGS IN THE SOFTWARE. */ -#include /* std::isupper() */ #include -#include /* sceneFieldNames */ #include #include #include @@ -35,25 +33,16 @@ #include #include -#include "Magnum/PixelFormat.h" -#include "Magnum/Implementation/converterUtilities.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Math/Matrix4.h" -#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/SceneTools/FlattenMeshHierarchy.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/AnimationData.h" -#include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/SceneData.h" -#include "Magnum/Trade/SkinData.h" -#include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" -#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "Magnum/Implementation/converterUtilities.h" +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" namespace Magnum { @@ -133,8 +122,8 @@ magnum-sceneconverter [-h|--help] [-I|--importer PLUGIN] [-i|--importer-options key=val,key2=val2,…] [-c|--converter-options key=val,key2=val2,…]... [--mesh MESH] [--level LEVEL] [--concatenate-meshes] [--info-animations] [--info-images] - [--info-lights] [--info-materials] [--info-meshes] [--info-objects] - [--info-scenes] [--info-skins] [--info-textures] [--info] + [--info-lights] [--info-cameras] [--info-materials] [--info-meshes] + [--info-objects] [--info-scenes] [--info-skins] [--info-textures] [--info] [--color on|4bit|off|auto] [--bounds] [-v|--verbose] [--profile] [--] input output @endcode @@ -172,6 +161,7 @@ Arguments: exit - `--info-images` --- print into about images in the input file and exit - `--info-lights` --- print into about lights in the input file and exit +- `--info-cameras` --- print into about cameras in the input file and exit - `--info-materials` --- print into about materials in the input file and exit - `--info-meshes` --- print into about meshes in the input file and exit @@ -225,27 +215,11 @@ using namespace Containers::Literals; namespace { -/** @todo const Array& doesn't work, minmax() would fail to match */ -template Containers::String calculateBounds(Containers::Array&& attribute) { - /** @todo clean up when Debug::toString() exists */ - std::ostringstream out; - Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Math::minmax(attribute); - return out.str(); -} - -/* Named attribute index from a global index */ -/** @todo some helper for this directly on the MeshData class? */ -UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { - const Trade::MeshAttribute name = mesh.attributeName(id); - for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) - if(mesh.attributeId(name, i) == id) return i; - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); -} - bool isInfoRequested(const Utility::Arguments& args) { return args.isSet("info-animations") || args.isSet("info-images") || args.isSet("info-lights") || + args.isSet("info-cameras") || args.isSet("info-materials") || args.isSet("info-meshes") || args.isSet("info-objects") || @@ -278,6 +252,7 @@ int main(int argc, char** argv) { .addBooleanOption("info-animations").setHelp("info-animations", "print info about animations in the input file and exit") .addBooleanOption("info-images").setHelp("info-images", "print info about images in the input file and exit") .addBooleanOption("info-lights").setHelp("info-lights", "print info about images in the input file and exit") + .addBooleanOption("info-cameras").setHelp("info-cameras", "print info about cameras in the input file and exit") .addBooleanOption("info-materials").setHelp("info-materials", "print info about materials in the input file and exit") .addBooleanOption("info-meshes").setHelp("info-meshes", "print info about meshes in the input file and exit") .addBooleanOption("info-objects").setHelp("info-objects", "print info about objects in the input file and exit") @@ -325,6 +300,31 @@ attributes that are present in the first mesh are taken, if --only-attributes is specified as well, the IDs reference attributes of the first mesh.)") .parse(argc, argv); + /* Colored output. Enable only if a TTY. */ + Debug::Flags useColor; + bool useColor24; + if(args.value("color") == "on") { + useColor = Debug::Flags{}; + useColor24 = true; + } else if(args.value("color") == "4bit") { + useColor = Debug::Flags{}; + useColor24 = false; + } else if(args.value("color") == "off") { + useColor = Debug::Flag::DisableColors; + useColor24 = false; + } else if(Debug::isTty()) { + useColor = Debug::Flags{}; + /* https://unix.stackexchange.com/a/450366, not perfect but good enough + I'd say */ + /** @todo make this more robust and put directly on Debug, + including a "Disable 24 colors" flag */ + const Containers::StringView colorterm = std::getenv("COLORTERM"); + useColor24 = colorterm == "truecolor"_s || colorterm == "24bit"_s; + } else { + useColor = Debug::Flag::DisableColors; + useColor24 = false; + } + /* Generic checks */ if(!args.value("output").isEmpty()) { /* Not an error in this case, it should be possible to just append @@ -334,9 +334,15 @@ is specified as well, the IDs reference attributes of the first mesh.)") Warning{} << "Ignoring output file for --info:" << args.value("output"); } + /* Importer manager */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; + + /* Scene converter manager */ + PluginManager::Manager converterManager{ + args.value("plugin-dir").empty() ? Containers::String{} : + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractSceneConverter::pluginSearchPaths().back()).second())}; Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) { @@ -372,937 +378,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") /* Print file info, if requested */ if(isInfoRequested(args)) { - struct AnimationInfo { - UnsignedInt animation; - Trade::AnimationData data{{}, {}}; - Containers::String name; - }; - - struct SkinInfo { - UnsignedInt skin; - Trade::SkinData3D data{{}, {}}; - Containers::String name; - }; - - struct LightInfo { - UnsignedInt light; - Trade::LightData data{{}, {}, {}}; - Containers::String name; - }; - - struct MaterialInfo { - UnsignedInt material; - Trade::MaterialData data{{}, {}}; - Containers::String name; - }; - - struct TextureInfo { - UnsignedInt texture; - Trade::TextureData data{{}, {}, {}, {}, {}, {}}; - Containers::String name; - }; - - struct MeshAttributeInfo { - std::size_t offset; - Int stride; - UnsignedInt arraySize; - Trade::MeshAttribute name; - Containers::String customName; - VertexFormat format; - Containers::String bounds; - }; - - struct MeshInfo { - UnsignedInt mesh, level; - MeshPrimitive primitive; - UnsignedInt indexCount, vertexCount; - std::size_t indexOffset; - Int indexStride; - Containers::String indexBounds; - MeshIndexType indexType; - Containers::Array attributes; - std::size_t indexDataSize, vertexDataSize; - Trade::DataFlags indexDataFlags, vertexDataFlags; - Containers::String name; - }; - - struct SceneFieldInfo { - Trade::SceneField name; - Trade::SceneFieldFlags flags; - Trade::SceneFieldType type; - UnsignedInt arraySize; - std::size_t size; - }; - - struct SceneInfo { - UnsignedInt scene; - Trade::SceneMappingType mappingType; - UnsignedLong mappingBound; - Containers::Array fields; - std::size_t dataSize; - Trade::DataFlags dataFlags; - Containers::String name; - }; - - struct ObjectInfo { - UnsignedLong object; - /* A bitfield, assuming no more than 32 scenes */ - /** @todo might be too little? */ - UnsignedInt scenes; - Containers::Array> fields; - Containers::String name; - }; - - /* Parse everything first to avoid errors interleaved with output */ - bool error = false; - - /* Object properties */ - Containers::Array objectInfos; - if(args.isSet("info") || args.isSet("info-objects")) { - objectInfos = Containers::Array{std::size_t(importer->objectCount())}; - - for(UnsignedLong i = 0; i != importer->objectCount(); ++i) { - objectInfos[i].object = i; - objectInfos[i].name = importer->objectName(i); - } - } - - /* Scene properties, together with counting how much is each mesh / - light / material / skin / object referenced (which gets used only if - both --info-scenes and --info-{lights,materials,skins,objects} is - passed and the file has at least one scene). Texture reference count - is calculated when parsing materials. */ - Containers::Array sceneInfos; - /* Only the very latest GCC seems to support enum classes as keys and - I can't be bothered to write a std::hash specialization, so just - making the key typeless */ - std::unordered_map sceneFieldNames; - Containers::Array materialReferenceCount; - Containers::Array lightReferenceCount; - Containers::Array meshReferenceCount; - Containers::Array skinReferenceCount; - if((args.isSet("info") || args.isSet("info-scenes")) && importer->sceneCount()) { - materialReferenceCount = Containers::Array{importer->materialCount()}; - lightReferenceCount = Containers::Array{importer->lightCount()}; - meshReferenceCount = Containers::Array{importer->meshCount()}; - skinReferenceCount = Containers::Array{importer->skin3DCount()}; - - for(UnsignedInt i = 0; i != importer->sceneCount(); ++i) { - Containers::Optional scene = importer->scene(i); - if(!scene) { - error = true; - continue; - } - - SceneInfo info{}; - info.scene = i; - info.mappingType = scene->mappingType(); - info.mappingBound = scene->mappingBound(); - info.dataSize = scene->data().size(); - info.dataFlags = scene->dataFlags(); - info.name = importer->sceneName(i); - for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { - const Trade::SceneField name = scene->fieldName(j); - - if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { - if(meshMaterial.second().first() < meshReferenceCount.size()) - ++meshReferenceCount[meshMaterial.second().first()]; - if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) - ++materialReferenceCount[meshMaterial.second().second()]; - } - - if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { - if(skin.second() < skinReferenceCount.size()) - ++skinReferenceCount[skin.second()]; - /** @todo 2D/3D distinction */ - } - - if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { - if(light.second() < lightReferenceCount.size()) - ++lightReferenceCount[light.second()]; - } - - arrayAppend(info.fields, InPlaceInit, - name, - scene->fieldFlags(j), - scene->fieldType(j), - scene->fieldArraySize(j), - scene->fieldSize(j)); - - /* If the field has a custom name, save it into the map. - Not putting it into the fields array as the map is - reused by object info as well. */ - if(Trade::isSceneFieldCustom(name)) { - /* Fetch the name only if it's not already there */ - const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); - if(inserted.second) - inserted.first->second = importer->sceneFieldName(name); - } - - if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { - if(object >= objectInfos.size()) continue; - - objectInfos[object].object = object; - objectInfos[object].scenes |= 1 << i; - - /* If the field is repeated, increase the count - instead */ - if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) - ++objectInfos[object].fields.back().second(); - else - arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); - } - } - - arrayAppend(sceneInfos, std::move(info)); - } - } - - /* Animation properties */ - Containers::Array animationInfos; - if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer->animationCount(); ++i) { - Containers::Optional animation; - { - Trade::Implementation::Duration d{importTime}; - if(!(animation = importer->animation(i))) { - error = true; - continue; - } - } - - AnimationInfo info{}; - info.animation = i; - info.name = importer->animationName(i); - info.data = *std::move(animation); - - arrayAppend(animationInfos, std::move(info)); - } - - /* Skin properties */ - Containers::Array skinInfos; - if(args.isSet("info") || args.isSet("info-skins")) for(UnsignedInt i = 0; i != importer->skin3DCount(); ++i) { - Containers::Optional skin; - { - Trade::Implementation::Duration d{importTime}; - if(!(skin = importer->skin3D(i))) { - error = true; - continue; - } - } - - SkinInfo info{}; - info.skin = i; - info.name = importer->skin3DName(i); - info.data = *std::move(skin); - - arrayAppend(skinInfos, std::move(info)); - } - - /* Light properties */ - Containers::Array lightInfos; - if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer->lightCount(); ++i) { - Containers::Optional light; - { - Trade::Implementation::Duration d{importTime}; - if(!(light = importer->light(i))) { - error = true; - continue; - } - } - - LightInfo info{}; - info.light = i; - info.name = importer->lightName(i); - info.data = *std::move(light); - - arrayAppend(lightInfos, std::move(info)); - } - - /* Material properties, together with how much is each texture shared - (which gets used only if both --info-materials and --info-textures - is passed and the file has at least one material). */ - Containers::Array materialInfos; - Containers::Array textureReferenceCount; - if((args.isSet("info") || args.isSet("info-materials")) && importer->materialCount()) { - textureReferenceCount = Containers::Array{importer->textureCount()}; - - for(UnsignedInt i = 0; i != importer->materialCount(); ++i) { - Containers::Optional material; - { - Trade::Implementation::Duration d{importTime}; - if(!(material = importer->material(i))) { - error = true; - continue; - } - } - - /* Calculate texture reference count for all properties that - look like a texture */ - for(UnsignedInt j = 0; j != material->layerCount(); ++j) { - for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { - if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !Utility::String::endsWith(material->attributeName(j, k), "Texture")) - continue; - - const UnsignedInt texture = material->attribute(j, k); - /** @todo once StridedBitArrayView2D exists, fix this - to count each material only once by having one bit - for every material and texture */ - if(texture < textureReferenceCount.size()) - ++textureReferenceCount[texture]; - } - } - - MaterialInfo info{}; - info.material = i; - info.name = importer->materialName(i); - info.data = *std::move(material); - - arrayAppend(materialInfos, std::move(info)); - } - } - - /* Mesh properties */ - Containers::Array meshInfos; - if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { - for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) { - Containers::Optional mesh; - { - Trade::Implementation::Duration d{importTime}; - if(!(mesh = importer->mesh(i, j))) { - error = true; - continue; - } - } - - MeshInfo info{}; - info.mesh = i; - info.level = j; - info.primitive = mesh->primitive(); - info.vertexCount = mesh->vertexCount(); - info.vertexDataSize = mesh->vertexData().size(); - info.vertexDataFlags = mesh->vertexDataFlags(); - if(!j) { - info.name = importer->meshName(i); - } - if(mesh->isIndexed()) { - info.indexCount = mesh->indexCount(); - info.indexType = mesh->indexType(); - info.indexOffset = mesh->indexOffset(); - info.indexStride = mesh->indexStride(); - info.indexDataSize = mesh->indexData().size(); - info.indexDataFlags = mesh->indexDataFlags(); - if(args.isSet("bounds")) - info.indexBounds = calculateBounds(mesh->indicesAsArray()); - } - for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { - const Trade::MeshAttribute name = mesh->attributeName(k); - - /* Calculate bounds, if requested, if this is not an - implementation-specific format and if it's not a custom - attribute */ - Containers::String bounds; - if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { - case Trade::MeshAttribute::Position: - bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Tangent: - bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Bitangent: - bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Normal: - bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::TextureCoordinates: - bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Color: - bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::ObjectId: - Debug{} << mesh->objectIdsAsArray(namedAttributeId(*mesh, k)); - bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); - break; - } - - arrayAppend(info.attributes, InPlaceInit, - mesh->attributeOffset(k), - mesh->attributeStride(k), - mesh->attributeArraySize(k), - name, Trade::isMeshAttributeCustom(name) ? - importer->meshAttributeName(name) : "", - mesh->attributeFormat(k), - bounds); - } - - arrayAppend(meshInfos, std::move(info)); - } - } - - /* Texture properties, together with how much is each image shared - (which gets used only if both --info-textures and --info-images is - passed and the file has at least one texture). */ - Containers::Array textureInfos; - Containers::Array image1DReferenceCount; - Containers::Array image2DReferenceCount; - Containers::Array image3DReferenceCount; - if((args.isSet("info") || args.isSet("info-textures")) && importer->textureCount()) { - image1DReferenceCount = Containers::Array{importer->image1DCount()}; - image2DReferenceCount = Containers::Array{importer->image2DCount()}; - image3DReferenceCount = Containers::Array{importer->image3DCount()}; - for(UnsignedInt i = 0; i != importer->textureCount(); ++i) { - Containers::Optional texture; - { - Trade::Implementation::Duration d{importTime}; - if(!(texture = importer->texture(i))) { - error = true; - continue; - } - } - - switch(texture->type()) { - case Trade::TextureType::Texture1D: - if(texture->image() < image1DReferenceCount.size()) - ++image1DReferenceCount[texture->image()]; - break; - case Trade::TextureType::Texture1DArray: - case Trade::TextureType::Texture2D: - if(texture->image() < image2DReferenceCount.size()) - ++image2DReferenceCount[texture->image()]; - break; - case Trade::TextureType::CubeMap: - case Trade::TextureType::CubeMapArray: - case Trade::TextureType::Texture2DArray: - case Trade::TextureType::Texture3D: - if(texture->image() < image3DReferenceCount.size()) - ++image3DReferenceCount[texture->image()]; - break; - } - - TextureInfo info{}; - info.texture = i; - info.name = importer->textureName(i); - info.data = *std::move(texture); - - arrayAppend(textureInfos, std::move(info)); - } - } - - - Containers::Array imageInfos; - if(args.isSet("info") || args.isSet("info-images")) { - imageInfos = Trade::Implementation::imageInfo(*importer, error, importTime); - } - - /* Colored output. Enable only if a TTY. */ - Debug::Flags useColor; - bool useColor24; - if(args.value("color") == "on") { - useColor = Debug::Flags{}; - useColor24 = true; - } else if(args.value("color") == "4bit") { - useColor = Debug::Flags{}; - useColor24 = false; - } else if(args.value("color") == "off") { - useColor = Debug::Flag::DisableColors; - useColor24 = false; - } else if(Debug::isTty()) { - useColor = Debug::Flags{}; - /* https://unix.stackexchange.com/a/450366, not perfect but good - enough I'd say */ - /** @todo make this more robust and put directly on Debug, - including a "Disable 24 colors" flag */ - const Containers::StringView colorterm = std::getenv("COLORTERM"); - useColor24 = colorterm == "truecolor"_s || colorterm == "24bit"_s; - } else { - useColor = Debug::Flag::DisableColors; - useColor24 = false; - } - - std::size_t totalSceneDataSize = 0; - for(const SceneInfo& info: sceneInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - d << " Bound:" << info.mappingBound << "objects" - << Debug::color(Debug::Color::Blue) << "@" << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.mappingType - << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - d << Debug::newline << " Fields:"; - for(const SceneFieldInfo& field: info.fields) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::White); - if(Trade::isSceneFieldCustom(field.name)) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(field.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(field.name)] - << Debug::nospace - << Debug::boldColor(Debug::Color::White) << ")"; - } else d << Debug::packed << field.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; - if(field.arraySize) - d << Debug::nospace << Utility::format("[{}]", field.arraySize); - d << Debug::resetColor; - if(field.flags) d << Debug::nospace << ", flags:" - << Debug::packed << Debug::color(Debug::Color::Green) - << field.flags << Debug::resetColor; - d << Debug::nospace << "," << field.size << "entries"; - } - - totalSceneDataSize += info.dataSize; - } - if(!sceneInfos.isEmpty()) - Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; - - for(const ObjectInfo& info: objectInfos) { - /* Objects without a name and not referenced by any scenes are - useless, ignore */ - if(!info.name && !info.scenes) continue; - - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Object" << info.object << Debug::resetColor; - - if(sceneInfos) { - const UnsignedInt count = Math::popcount(info.scenes); - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "scenes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - if(info.scenes) { - d << Debug::newline << " Fields:"; - - for(std::size_t i = 0; i != info.fields.size(); ++i) { - if(i) d << Debug::nospace << ","; - const Containers::Pair nameCount = info.fields[i]; - d << Debug::color(Debug::Color::Cyan); - if(Trade::isSceneFieldCustom(nameCount.first())) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(nameCount.first()) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(nameCount.first())] - << Debug::nospace - << Debug::color(Debug::Color::Cyan) << ")"; - } else d << Debug::packed << nameCount.first(); - if(nameCount.second() != 1) - d << Debug::nospace << Utility::format("[{}]", nameCount.second()); - d << Debug::resetColor; - } - } - } - - std::size_t totalAnimationDataSize = 0; - for(const AnimationInfo& info: animationInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Duration:" << info.data.duration() - << "(" << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; - if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.data.dataFlags() << Debug::resetColor; - d << Debug::nospace << ")"; - - for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { - d << Debug::newline << " Track" << i << Debug::nospace << ":" - << Debug::packed << Debug::boldColor(Debug::Color::White) - << info.data.trackTargetType(i) - << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackType(i) << Debug::resetColor; - if(info.data.trackType(i) != info.data.trackResultType(i)) - d << Debug::color(Debug::Color::Blue) << "->" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackResultType(i) << Debug::resetColor; - d << Debug::nospace << "," << info.data.track(i).size() - << "keyframes"; - if(info.data.track(i).duration() != info.data.duration()) - d << Debug::nospace << "," << info.data.track(i).duration(); - d << Debug::newline - << " Interpolation:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.track(i).interpolation() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).before() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).after() << Debug::resetColor; - /** @todo might be useful to show bounds here as well, though - not so much for things like complex numbers or quats */ - } - - totalAnimationDataSize += info.data.data().size(); - } - if(!animationInfos.isEmpty()) - Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; - - for(const SkinInfo& info: skinInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Skin" << info.skin - << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(skinReferenceCount) { - const UnsignedInt count = skinReferenceCount[info.skin]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " " << info.data.joints().size() << "joints"; - } - - for(const LightInfo& info: lightInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Light" << info.light << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(lightReferenceCount) { - const UnsignedInt count = lightReferenceCount[info.light]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.type() << Debug::resetColor; - if(info.data.type() == Trade::LightData::Type::Spot) - d << Debug::nospace << "," << Debug::packed - << Deg(info.data.innerConeAngle()) << Debug::nospace - << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) - << Debug::nospace << "°"; - d << Debug::newline << " Color:"; - if(useColor24) d << Debug::color - << Math::pack(info.data.color()); - d << Debug::packed << info.data.color(); - if(!Math::equal(info.data.intensity(), 1.0f)) - d << "*" << info.data.intensity(); - d << Debug::newline << " Attenuation:" << Debug::packed - << info.data.attenuation(); - if(info.data.range() != Constants::inf()) - d << Debug::newline << " Range:" << Debug::packed - << info.data.range(); - } - - for(const MaterialInfo& info: materialInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Material" << info.material << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(materialReferenceCount) { - const UnsignedInt count = materialReferenceCount[info.material]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; - - for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { - /* Print extra layers with extra indent */ - const char* indent; - if(info.data.layerCount() != 1 && i != 0) { - d << Debug::newline << " Layer" << i << Debug::nospace << ":"; - if(!info.data.layerName(i).isEmpty()) { - if(std::isupper(info.data.layerName(i)[0])) - d << Debug::boldColor(Debug::Color::White); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.layerName(i) << Debug::resetColor; - } - indent = " "; - } else { - d << Debug::newline << " Base layer:"; - indent = " "; - } - - for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { - /* Ignore layer name (which is always first) unless it's in - the base material, in which case we print it as it - wouldn't otherwise be shown anywhere */ - if(i && !j && info.data.attributeName(i, j) == " LayerName") - continue; - - d << Debug::newline << indent; - if(std::isupper(info.data.attributeName(i, j)[0])) - d << Debug::boldColor(Debug::Color::White); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace - << ":"; - switch(info.data.attributeType(i, j)) { - case Trade::MaterialAttributeType::Bool: - d << info.data.attribute(i, j); - break; - #define _c(type) case Trade::MaterialAttributeType::type: \ - d << Debug::packed << info.data.attribute(i, j); \ - break; - _c(Float) - _c(Deg) - _c(Rad) - _c(UnsignedInt) - _c(Int) - _c(UnsignedLong) - _c(Long) - _c(Vector2) - _c(Vector2ui) - _c(Vector2i) - case Trade::MaterialAttributeType::Vector3: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j)); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector3ui) - _c(Vector3i) - case Trade::MaterialAttributeType::Vector4: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector4ui) - _c(Vector4i) - _c(Matrix2x2) - _c(Matrix2x3) - _c(Matrix2x4) - _c(Matrix3x2) - _c(Matrix3x3) - _c(Matrix3x4) - _c(Matrix4x2) - _c(Matrix4x3) - #undef _c - case Trade::MaterialAttributeType::Pointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::MutablePointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::String: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::TextureSwizzle: - d << Debug::packed << info.data.attribute(i, j); - break; - } - } - } - } - - std::size_t totalMeshDataSize = 0; - for(const MeshInfo& info: meshInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White) << "Mesh" << info.mesh << Debug::resetColor; - - /* Print reference count only if there actually are scenes and - they were parsed, otherwise this information is useless */ - if(meshReferenceCount) { - const UnsignedInt count = meshReferenceCount[info.mesh]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":" - << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) - << "kB"; - if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) - << info.vertexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - for(const MeshAttributeInfo& attribute: info.attributes) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::White); - if(Trade::isMeshAttributeCustom(attribute.name)) { - d << "Custom(" << Debug::nospace - << Trade::meshAttributeCustom(attribute.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << attribute.customName << Debug::nospace - << Debug::boldColor(Debug::Color::White) << ")"; - } else d << Debug::packed << attribute.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; - if(attribute.arraySize) - d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); - d << Debug::resetColor; - d << Debug::nospace << ", offset" << attribute.offset; - d << Debug::nospace << ", stride" - << attribute.stride; - if(attribute.bounds) - d << Debug::newline << " bounds:" << attribute.bounds; - } - - if(info.indexType != MeshIndexType{}) { - d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace - << Utility::format("{:.1f}", info.indexDataSize/1024.0f) - << "kB"; - if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - if(info.indexBounds) - d << Debug::newline << " bounds:" << info.indexBounds; - } - - totalMeshDataSize += info.vertexDataSize + info.indexDataSize; - } - if(!meshInfos.isEmpty()) - Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; - - for(const TextureInfo& info: textureInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::White) << "Texture" << info.texture << Debug::resetColor; - - /* Print reference count only if there actually are materials and - they were parsed, otherwise this information is useless */ - if(textureReferenceCount) { - const UnsignedInt count = textureReferenceCount[info.texture]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "material attributes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - d << " Type:" - << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.type() - << Debug::resetColor << Debug::nospace << ", image" - << info.data.image(); - d << Debug::newline << " Minification, mipmap and magnification:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.minificationFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.mipmapFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.magnificationFilter() << Debug::resetColor; - d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.wrapping()[0] << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "}"; - } - - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: imageInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::resetColor; - - /* Print reference count only if there actually are textures - and they were parsed otherwise this information is - useless */ - Containers::Optional count; - if(info.size.z() && image3DReferenceCount) { - count = image3DReferenceCount[info.image]; - } else if(info.size.y() && image2DReferenceCount) { - count = image2DReferenceCount[info.image]; - } else if(image1DReferenceCount) { - count = image1DReferenceCount[info.image]; - } - if(count) { - if(!*count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << *count << "textures)"; - if(!*count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::White) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!imageInfos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importTime); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; @@ -1409,10 +485,6 @@ is specified as well, the IDs reference attributes of the first mesh.)") Debug{} << "Fuzzy duplicate removal:" << beforeVertexCount << "->" << mesh->vertexCount() << "vertices"; } - PluginManager::Manager converterManager{ - args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractSceneConverter::pluginSearchPaths().back())}; - /* Assume there's always one passed --converter option less, and the last is implicitly AnySceneConverter. All converters except the last one are expected to support ConvertMesh and the mesh is "piped" from one to the @@ -1438,7 +510,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(i + 1 >= converterCount && (converter->features() & Trade::SceneConverterFeature::ConvertMeshToFile)) { /* No verbose output for just one converter */ if(converterCount > 1 && args.isSet("verbose")) - Debug{} << "Saving output with" << converterName << Debug::nospace << "..."; + Debug{} << "Saving output (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; Trade::Implementation::Duration d{conversionTime}; if(!converter->convertToFile(*mesh, args.value("output"))) { diff --git a/src/Magnum/ShaderTools/AbstractConverter.cpp b/src/Magnum/ShaderTools/AbstractConverter.cpp index 4d3a9596e..dec8ac9fd 100644 --- a/src/Magnum/ShaderTools/AbstractConverter.cpp +++ b/src/Magnum/ShaderTools/AbstractConverter.cpp @@ -733,11 +733,14 @@ Containers::Optional> AbstractConverter::doLinkFilesToDa } Debug& operator<<(Debug& debug, const ConverterFeature value) { - debug << "ShaderTools::ConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "ShaderTools::ConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ConverterFeature::v: return debug << "::" #v; + #define _c(v) case ConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ValidateData) _c(ValidateFile) _c(ConvertData) @@ -752,11 +755,11 @@ Debug& operator<<(Debug& debug, const ConverterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "ShaderTools::ConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "ShaderTools::ConverterFeatures{}", { ConverterFeature::ValidateData, /* Implied by ValidateData, has to be after */ ConverterFeature::ValidateFile, diff --git a/src/Magnum/ShaderTools/CMakeLists.txt b/src/Magnum/ShaderTools/CMakeLists.txt index 131daa46a..400af9302 100644 --- a/src/Magnum/ShaderTools/CMakeLists.txt +++ b/src/Magnum/ShaderTools/CMakeLists.txt @@ -91,7 +91,8 @@ if(MAGNUM_WITH_SHADERCONVERTER) add_executable(magnum-shaderconverter shaderconverter.cpp) target_link_libraries(magnum-shaderconverter PRIVATE Magnum - MagnumShaderTools) + MagnumShaderTools + ${MAGNUM_SHADERCONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-shaderconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) diff --git a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp index 1ce7185ac..949e917e2 100644 --- a/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp +++ b/src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp @@ -189,7 +189,9 @@ struct AbstractConverterTest: TestSuite::Tester { void setInputFileCallbackLinkFilesToDataAsDataFailed(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); @@ -339,7 +341,9 @@ AbstractConverterTest::AbstractConverterTest() { &AbstractConverterTest::setInputFileCallbackLinkFilesToDataAsDataFailed, &AbstractConverterTest::debugFeature, + &AbstractConverterTest::debugFeaturePacked, &AbstractConverterTest::debugFeatures, + &AbstractConverterTest::debugFeaturesPacked, &AbstractConverterTest::debugFeaturesSupersets, &AbstractConverterTest::debugFlag, &AbstractConverterTest::debugFlags, @@ -3533,6 +3537,13 @@ void AbstractConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "ShaderTools::ConverterFeature::ConvertData ShaderTools::ConverterFeature(0xf0)\n"); } +void AbstractConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ConverterFeature::ConvertData << Debug::packed << ConverterFeature(0xf0) << ConverterFeature::ValidateFile; + CORRADE_COMPARE(out.str(), "ConvertData 0xf0 ShaderTools::ConverterFeature::ValidateFile\n"); +} + void AbstractConverterTest::debugFeatures() { std::ostringstream out; @@ -3540,6 +3551,13 @@ void AbstractConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "ShaderTools::ConverterFeature::ValidateData|ShaderTools::ConverterFeature::ConvertFile ShaderTools::ConverterFeatures{}\n"); } +void AbstractConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ConverterFeature::ValidateData|ConverterFeature::ConvertFile) << Debug::packed << ConverterFeatures{} << ConverterFeature::InputFileCallback; + CORRADE_COMPARE(out.str(), "ValidateData|ConvertFile {} ShaderTools::ConverterFeature::InputFileCallback\n"); +} + void AbstractConverterTest::debugFeaturesSupersets() { /* ValidateData is a superset of ValidateFile, so only one should be printed */ diff --git a/src/Magnum/ShaderTools/shaderconverter.cpp b/src/Magnum/ShaderTools/shaderconverter.cpp index 6c4b1256a..b35d3552a 100644 --- a/src/Magnum/ShaderTools/shaderconverter.cpp +++ b/src/Magnum/ShaderTools/shaderconverter.cpp @@ -359,7 +359,7 @@ see documentation of a particular converter for more information.)") /* Set up a converter manager */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), ShaderTools::AbstractConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(ShaderTools::AbstractConverter::pluginSearchPaths().back()).second())}; /* Data passed from one converter to another in case there's more than one */ Containers::Array data; diff --git a/src/Magnum/Shaders/CMakeLists.txt b/src/Magnum/Shaders/CMakeLists.txt index 4a5f9532c..3c05a0d49 100644 --- a/src/Magnum/Shaders/CMakeLists.txt +++ b/src/Magnum/Shaders/CMakeLists.txt @@ -42,7 +42,9 @@ set(MagnumShaders_GracefulAssert_SRCS MeshVisualizerGL.cpp PhongGL.cpp VectorGL.cpp - VertexColorGL.cpp) + VertexColorGL.cpp + + glShaderWrapper.cpp) set(MagnumShaders_HEADERS DistanceFieldVector.h @@ -60,6 +62,7 @@ set(MagnumShaders_HEADERS VectorGL.h VertexColorGL.h + glShaderWrapper.h visibility.h) if(MAGNUM_BUILD_DEPRECATED) diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index a33a9386d..9b852be30 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,7 +27,7 @@ #include "DistanceFieldVectorGL.h" #include -#include +#include #include #include "Magnum/GL/Context.h" @@ -63,22 +64,16 @@ namespace { #endif } -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags +template typename DistanceFieldVectorGL::CompileState DistanceFieldVectorGL::compile(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", ); + "Shaders::DistanceFieldVectorGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::DistanceFieldVectorGL: draw count can't be zero", ); + "Shaders::DistanceFieldVectorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -142,9 +137,17 @@ template DistanceFieldVectorGL::DistanceFiel frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("DistanceFieldVector.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + DistanceFieldVectorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -152,25 +155,39 @@ template DistanceFieldVectorGL::DistanceFiel if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(CompileState&& state): DistanceFieldVectorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); _colorUniform = uniformLocation("color"); _outlineColorUniform = uniformLocation("outlineColor"); @@ -185,11 +202,11 @@ template DistanceFieldVectorGL::DistanceFiel { setUniform(uniformLocation("vectorTexture"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); } #endif @@ -198,13 +215,13 @@ template DistanceFieldVectorGL::DistanceFiel /* 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) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); setColor(Color4{1.0f}); /* Outline color is zero by default */ @@ -212,10 +229,22 @@ template DistanceFieldVectorGL::DistanceFiel setSmoothness(0.04f); } #endif + + static_cast(context); + static_cast(version); } +template DistanceFieldVectorGL::DistanceFieldVectorGL(NoInitT) {} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{flags, 1, 1} {} +template typename DistanceFieldVectorGL::CompileState DistanceFieldVectorGL::compile(const Flags flags) { + return compile(flags, 1, 1); +} + +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + DistanceFieldVectorGL{compile(flags, materialCount, drawCount)} {} #endif template DistanceFieldVectorGL& DistanceFieldVectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index 1be8f0465..36f087e87 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -33,6 +34,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -120,6 +122,8 @@ example. */ template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -217,6 +221,34 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector typedef Implementation::DistanceFieldVectorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref DistanceFieldVectorGL(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref DistanceFieldVectorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref DistanceFieldVectorGL(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -225,6 +257,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * 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. + * @see @ref compile(Flags) */ explicit DistanceFieldVectorGL(Flags flags = {}); @@ -240,6 +273,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @ref TextureTransformationUniform buffer bound with * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() * and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required @@ -250,6 +284,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * 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). + * @see @ref compile(Flags, UnsignedInt, 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. @@ -266,6 +301,16 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector explicit DistanceFieldVectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit DistanceFieldVectorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -554,7 +599,7 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ /** - * @brief Bind vector texture + * @brief Bind a vector texture * @return Reference to self (for method chaining) * * @see @ref DistanceFieldVectorGL::Flag::TextureTransformation, @@ -602,6 +647,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit DistanceFieldVectorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -627,6 +676,24 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +template class DistanceFieldVectorGL::CompileState: public DistanceFieldVectorGL { + /* Everything deliberately private except for the inheritance */ + friend class DistanceFieldVectorGL; + + explicit CompileState(NoCreateT): DistanceFieldVectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(DistanceFieldVectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): DistanceFieldVectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + Implementation::GLShaderWrapper _vert, _frag; + GL::Version _version; +}; + /** @brief Two-dimensional distance field vector OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 86504acad..2bc1cb68e 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,7 +27,7 @@ #include "FlatGL.h" #include -#include +#include #include #include "Magnum/GL/Context.h" @@ -68,16 +69,11 @@ namespace { #endif } -template FlatGL::FlatGL(const Flags flags +template typename FlatGL::CompileState FlatGL::compile(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 CORRADE_NO_ASSERT { const bool textureTransformationNotEnabledOrTextured = !(flags & Flag::TextureTransformation) || flags & Flag::Textured @@ -86,22 +82,22 @@ template FlatGL::FlatGL(const Flags flags #endif ; CORRADE_ASSERT(textureTransformationNotEnabledOrTextured, - "Shaders::FlatGL: texture transformation enabled but the shader is not textured", ); + "Shaders::FlatGL: texture transformation enabled but the shader is not textured", CompileState{NoCreate}); } #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::FlatGL: material count can't be zero", ); + "Shaders::FlatGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::FlatGL: draw count can't be zero", ); + "Shaders::FlatGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags & Flag::TextureArrays) || flags & Flag::Textured || flags >= Flag::ObjectIdTexture, - "Shaders::FlatGL: texture arrays enabled but the shader is not textured", ); + "Shaders::FlatGL: texture arrays enabled but the shader is not textured", CompileState{NoCreate}); 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", ); + "Shaders::FlatGL: texture arrays require texture transformation enabled as well if uniform buffers are used", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -195,9 +191,17 @@ template FlatGL::FlatGL(const Flags flags frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Flat.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + FlatGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly and doesn't even provide bindFragmentDataLocation() */ @@ -206,53 +210,68 @@ template FlatGL::FlatGL(const Flags flags if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); if(flags & Flag::Textured #ifndef MAGNUM_TARGET_GLES2 || flags >= Flag::ObjectIdTexture #endif ) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags & Flag::VertexColor) - bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ + out.bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ #ifndef MAGNUM_TARGET_GLES2 if(flags & Flag::ObjectId) { - bindFragmentDataLocation(ColorOutput, "color"); - bindFragmentDataLocation(ObjectIdOutput, "objectId"); + out.bindFragmentDataLocation(ColorOutput, "color"); + out.bindFragmentDataLocation(ObjectIdOutput, "objectId"); } if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template FlatGL::FlatGL(CompileState&& state): FlatGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::TextureArrays) + if(_flags & Flag::TextureArrays) _textureLayerUniform = uniformLocation("textureLayer"); #endif _colorUniform = uniformLocation("color"); - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + if(_flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif } } @@ -261,13 +280,13 @@ template FlatGL::FlatGL(const Flags flags if(!context.isExtensionSupported(version)) #endif { - if(flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); + if(_flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); } @@ -277,26 +296,38 @@ template FlatGL::FlatGL(const Flags flags /* 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) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + 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); + if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ } #endif + + static_cast(version); + static_cast(context); } +template FlatGL::FlatGL(Flags flags): FlatGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1, 1} {} +template typename FlatGL::CompileState FlatGL::compile(Flags flags) { + return compile(flags, 1, 1); +} + +template FlatGL::FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount): + FlatGL{compile(flags, materialCount, drawCount)} {} #endif +template FlatGL::FlatGL(NoInitT) {} + template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index bd76a49c8..adedb7c82 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -33,6 +34,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -201,6 +203,8 @@ all shaders, see @ref shaders-usage-multidraw for an example. */ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -540,6 +544,34 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: typedef Implementation::FlatGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref FlatGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref FlatGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref FlatGL(Flags, UnsignedInt, UnsignedInt) can perform + * an asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref FlatGL(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -548,6 +580,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * 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. + * @see @ref compile(Flags) */ explicit FlatGL(Flags flags = {}); @@ -562,6 +595,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * / @ref TextureTransformationUniform buffer bound with * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() * and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required @@ -572,6 +606,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * 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). + * @see @ref compile(Flags, UnsignedInt, 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. @@ -588,6 +623,16 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: explicit FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit FlatGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -1011,6 +1056,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit FlatGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -1038,6 +1087,24 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +template class FlatGL::CompileState: public FlatGL { + /* Everything deliberately private except for the inheritance */ + friend class FlatGL; + + explicit CompileState(NoCreateT): FlatGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(FlatGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): FlatGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + Implementation::GLShaderWrapper _vert, _frag; + GL::Version _version; +}; + /** @brief 2D flat OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 615231aa5..7a093c2ee 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,8 +27,7 @@ #include "MeshVisualizerGL.h" #include -#include -#include +#include #include #include @@ -72,17 +72,7 @@ namespace { namespace Implementation { -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 -{ +void MeshVisualizerGLBase::assertExtensions(const FlagsBase flags) { #ifndef MAGNUM_TARGET_GLES2 #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; @@ -120,7 +110,7 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif #ifndef MAGNUM_TARGET_GLES2 - if(_flags & FlagBase::Wireframe && !(_flags & FlagBase::NoGeometryShader)) { + if(flags & FlagBase::Wireframe && !(flags & FlagBase::NoGeometryShader)) { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GL320); MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::geometry_shader4); @@ -129,12 +119,12 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif } #else - if(_flags & FlagBase::Wireframe) + if(flags & FlagBase::Wireframe) MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::OES::standard_derivatives); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(_flags & FlagBase::PrimitiveId && !(_flags >= FlagBase::PrimitiveIdFromVertexId)) { + if(flags & FlagBase::PrimitiveId && !(flags >= FlagBase::PrimitiveIdFromVertexId)) { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GL320); #else @@ -150,18 +140,22 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #endif } -GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const { +GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, UnsignedInt const drawCount + #endif +) { GL::Context& context = GL::Context::current(); #ifndef MAGNUM_TARGET_GLES const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); /* Extended in MeshVisualizerGL3D for TBN visualization */ - CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); + CORRADE_INTERNAL_ASSERT(!(flags & FlagBase::Wireframe) || flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); #elif !defined(MAGNUM_TARGET_WEBGL) /* 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); + 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}); #endif @@ -169,18 +163,18 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); - vert.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") + vert.addSource(flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(flags & FlagBase::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") + .addSource(flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(_flags & FlagBase::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") + .addSource(flags & FlagBase::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags >= FlagBase::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") + .addSource(flags >= FlagBase::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") + .addSource(flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") #endif #ifdef MAGNUM_TARGET_WEBGL .addSource("#define SUBSCRIPTING_WORKAROUND\n") @@ -190,38 +184,38 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra #endif ; #ifndef MAGNUM_TARGET_GLES2 - if(_flags >= FlagBase::UniformBuffers) { + 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" : ""); + drawCount, + materialCount)); + vert.addSource(flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif - frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") + frag.addSource(flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define OBJECT_ID_TEXTURE\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(flags >= FlagBase::ObjectIdTexture ? "#define OBJECT_ID_TEXTURE\n" : "") + .addSource(flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(flags & FlagBase::PrimitiveId ? + (flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : "") #endif ; #ifndef MAGNUM_TARGET_GLES2 - if(_flags >= FlagBase::UniformBuffers) { + 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" : ""); + drawCount, + materialCount)); + frag.addSource(flags >= FlagBase::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -382,21 +376,20 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindObjectIdTexture(GL::Texture2DArr } -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)) - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif -} { +) { + const FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); + assertExtensions(baseFlags); + #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", CompileState{NoCreate}); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", ); + "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", CompileState{NoCreate}); #endif /* Has to be here and not in the base class in order to have it exit the @@ -404,9 +397,9 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags otherwise */ #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::MeshVisualizerGL2D: material count can't be zero", ); + "Shaders::MeshVisualizerGL2D: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::MeshVisualizerGL2D: draw count can't be zero", ); + "Shaders::MeshVisualizerGL2D: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -416,7 +409,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; - const GL::Version version = setupShaders(vert, frag, rs); + const GL::Version version = setupShaders(vert, frag, rs, baseFlags + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif + ); + Containers::Optional geom; vert.addSource("#define TWO_DIMENSIONS\n") /* Pass NO_GEOMETRY_SHADER not only when NoGeometryShader but also when @@ -439,19 +437,19 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("MeshVisualizer.frag")); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Containers::Optional geom; if(flags & Flag::Wireframe && !(flags & Flag::NoGeometryShader)) { geom = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Geometry); (*geom) .addSource("#define WIREFRAME_RENDERING\n#define MAX_VERTICES 3\n") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(baseFlags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(baseFlags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(baseFlags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(baseFlags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(baseFlags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(baseFlags & FlagBase::PrimitiveId ? + (baseFlags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : ""); #ifndef MAGNUM_TARGET_GLES2 @@ -461,8 +459,8 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); + drawCount, + materialCount)); geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -472,48 +470,80 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags static_cast(version); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, *geom, frag})); - else - #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + if(geom) geom->submitCompile(); - attachShaders({vert, frag}); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) attachShader(*geom); + MeshVisualizerGL2D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; #endif + out.attachShaders({vert, frag}); + if(geom) out.attachShader(*geom); + /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::ObjectIdTexture) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); #endif #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isVersionSupported(GL::Version::GL310)) #endif { - bindAttributeLocation(VertexIndex::Location, "vertexIndex"); + out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, version}; +} + +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{compile(flags)} {} + +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Flags flags) { + return compile(flags, 1, 1); +} + +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): MeshVisualizerGL2D{compile(flags, materialCount, drawCount)} {} +#endif + +MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + if(state._geom.id) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag), GL::Shader(state._geom)})); + else + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; + const Flags flags = state.flags(); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -608,11 +638,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags #endif } #endif -} -#ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{flags, 1, 1} {} -#endif + static_cast(context); + static_cast(version); +} MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { /* Not asserting here, since the relation to wireframe is a bit vague. @@ -674,33 +703,32 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer, const } #endif -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags +MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(Flags flags #ifndef MAGNUM_TARGET_GLES2 , const UnsignedInt materialCount, const UnsignedInt drawCount #endif -): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)) - #ifndef MAGNUM_TARGET_GLES2 - , materialCount, drawCount - #endif -} { +) { + FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(flags)); + assertExtensions(baseFlags); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::NoGeometryShader && flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)), - "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction", ); + "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::BitangentDirection && flags & Flag::BitangentFromTangentDirection), - "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", ); + "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", CompileState{NoCreate}); #elif !defined(MAGNUM_TARGET_GLES2) CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", CompileState{NoCreate}); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), - "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", ); + "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", CompileState{NoCreate}); #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", ); + "Shaders::MeshVisualizerGL3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", CompileState{NoCreate}); #endif /* Has to be here and not in the base class in order to have it exit the @@ -708,9 +736,9 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags otherwise */ #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::MeshVisualizerGL3D: material count can't be zero", ); + "Shaders::MeshVisualizerGL3D: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::MeshVisualizerGL3D: draw count can't be zero", ); + "Shaders::MeshVisualizerGL3D: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -720,7 +748,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; - const GL::Version version = setupShaders(vert, frag, rs); + const GL::Version version = setupShaders(vert, frag, rs, baseFlags + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif + ); + Containers::Optional geom; /* Expands the check done for wireframe in MeshVisualizerBase with TBN */ #ifndef MAGNUM_TARGET_GLES @@ -768,7 +801,6 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags .addSource(rs.getString("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Containers::Optional geom; if(flags & (Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) && !(flags & Flag::NoGeometryShader)) { Int maxVertices = 0; if(flags & Flag::Wireframe) maxVertices += 3; @@ -781,13 +813,13 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags (*geom) .addSource(Utility::formatString("#define MAX_VERTICES {}\n", maxVertices)) .addSource(flags & Flag::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") - .addSource(_flags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") - .addSource(_flags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") - .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") - .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") - .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") - .addSource(_flags & FlagBase::PrimitiveId ? - (_flags >= FlagBase::PrimitiveIdFromVertexId ? + .addSource(baseFlags >= FlagBase::ObjectIdTexture ? "#define TEXTURED\n" : "") + .addSource(baseFlags & FlagBase::TextureArrays ? "#define TEXTURE_ARRAYS\n" : "") + .addSource(baseFlags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(baseFlags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(baseFlags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") + .addSource(baseFlags & FlagBase::PrimitiveId ? + (baseFlags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "#define PRIMITIVE_ID\n") : "") .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") @@ -800,8 +832,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags "#define UNIFORM_BUFFERS\n" "#define DRAW_COUNT {}\n" "#define MATERIAL_COUNT {}\n", - _drawCount, - _materialCount)); + drawCount, + materialCount)); geom->addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -811,50 +843,53 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags static_cast(version); #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, *geom, frag})); - else - #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + if(geom) geom->submitCompile(); - attachShaders({vert, frag}); - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(geom) attachShader(*geom); + MeshVisualizerGL3D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; #endif + out.attachShaders({vert, frag}); + if(geom) out.attachShader(*geom); + /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::ObjectIdTexture) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) { - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) - bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); + out.bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); #endif } #ifndef MAGNUM_TARGET_GLES2 if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & Flag::TangentDirection || flags & Flag::BitangentFromTangentDirection) - bindAttributeLocation(Tangent4::Location, "tangent"); + out.bindAttributeLocation(Tangent4::Location, "tangent"); if(flags & Flag::BitangentDirection) - bindAttributeLocation(Bitangent::Location, "bitangent"); + out.bindAttributeLocation(Bitangent::Location, "bitangent"); if(flags & Flag::NormalDirection || flags & Flag::BitangentFromTangentDirection) - bindAttributeLocation(Normal::Location, "normal"); + out.bindAttributeLocation(Normal::Location, "normal"); #endif #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -862,13 +897,32 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags if(!context.isVersionSupported(GL::Version::GL310)) #endif { - bindAttributeLocation(VertexIndex::Location, "vertexIndex"); + out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), geom ? &*geom : nullptr, version}; +} + +MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + if(state._geom.id) + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag), GL::Shader(state._geom)})); + else + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; + Flags flags = state.flags(); #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) @@ -996,10 +1050,19 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags #endif } #endif + + static_cast(context); + static_cast(version); } +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{flags, 1, 1} {} +MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Flags flags) { + return compile(flags, 1, 1); +} + +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): MeshVisualizerGL3D{compile(flags, materialCount, drawCount)} {} #endif MeshVisualizerGL3D& MeshVisualizerGL3D::setTransformationMatrix(const Matrix4& matrix) { diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index b217f1d39..cbf503757 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -35,6 +36,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -69,14 +71,16 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr CORRADE_ENUMSET_FRIEND_OPERATORS(FlagsBase) - explicit MeshVisualizerGLBase(FlagsBase flags + explicit MeshVisualizerGLBase(NoInitT) {} + + explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + + static MAGNUM_SHADERS_LOCAL void assertExtensions(const FlagsBase flags); + static MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const 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; #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& setTextureMatrix(const Matrix3& matrix); @@ -184,6 +188,8 @@ texture offset (or offset and layer). */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisualizerGLBase { public: + class CompileState; + /** * @brief Vertex position * @@ -441,6 +447,35 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua /** @brief Flags */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL2D(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref MeshVisualizerGL2D(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + /* No default value, consistently with MeshVisualizerGL2D(Flags) */ + static CompileState compile(Flags flags); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref MeshVisualizerGL2D(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -451,6 +486,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * 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. + * @see @ref compile(Flags) */ explicit MeshVisualizerGL2D(Flags flags); @@ -464,6 +500,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * / @ref MeshVisualizerMaterialUniform buffer bound with * @ref bindTransformationProjectionBuffer() and * @ref bindDrawBuffer() + * @m_since_latest * * At least @ref Flag::Wireframe is expected to be enabled. * @@ -476,6 +513,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * 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). + * @see @ref compile(Flags, UnsignedInt, 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. @@ -491,6 +529,16 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua explicit MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit MeshVisualizerGL2D(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -862,9 +910,33 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit MeshVisualizerGL2D(NoInitT): Implementation::MeshVisualizerGLBase{NoInit} {} + Int _transformationProjectionMatrixUniform{9}; }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +class MeshVisualizerGL2D::CompileState: public MeshVisualizerGL2D { + /* Everything deliberately private except for the inheritance */ + friend class MeshVisualizerGL2D; + + explicit CompileState(NoCreateT): MeshVisualizerGL2D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} + + explicit CompileState(MeshVisualizerGL2D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, GL::Version version): MeshVisualizerGL2D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _version{version} { + if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; + } + + Implementation::GLShaderWrapper _vert, _frag, _geom; + GL::Version _version; +}; + /** @brief 3D mesh visualization OpenGL shader @m_since_latest @@ -1103,6 +1175,8 @@ similar for all shaders, see @ref shaders-usage-multidraw for an example. */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisualizerGLBase { public: + class CompileState; + /** * @brief Vertex position * @@ -1617,6 +1691,35 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua /** @brief Flags */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL3D(Flags) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref MeshVisualizerGL3D(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + /* No default value, consistently with MeshVisualizerGL3D(Flags) */ + static CompileState compile(Flags flags); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref MeshVisualizerGL3D(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -1630,6 +1733,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * 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. + * @see @ref compile(Flags) */ explicit MeshVisualizerGL3D(Flags flags); @@ -1639,7 +1743,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @m_deprecated_since{2020,06} Use @ref MeshVisualizerGL3D(Flags) * instead. */ - explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{{}} {} + explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{Flags{}} {} #endif #ifndef MAGNUM_TARGET_GLES2 @@ -1653,6 +1757,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref MeshVisualizerMaterialUniform buffer bound with * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() * and @ref bindDrawBuffer() + * @m_since_latest * * At least @ref Flag::Wireframe or one of @ref Flag::TangentDirection, * @ref Flag::BitangentFromTangentDirection, @@ -1666,8 +1771,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @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). + * @p materialCount and @p drawCount is ignored and the constructo + * @see @ref compile(Flags, UnsignedInt, 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. @@ -1698,6 +1803,16 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua */ explicit MeshVisualizerGL3D(NoCreateT) noexcept: Implementation::MeshVisualizerGLBase{NoCreate} {} + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit MeshVisualizerGL3D(CompileState&& state); + /** @brief Copying is not allowed */ MeshVisualizerGL3D(const MeshVisualizerGL3D&) = delete; @@ -2326,6 +2441,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit MeshVisualizerGL3D(NoInitT): Implementation::MeshVisualizerGLBase{NoInit} {} + Int _transformationMatrixUniform{9}, _projectionMatrixUniform{10}; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -2335,6 +2454,26 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +class MeshVisualizerGL3D::CompileState: public MeshVisualizerGL3D { + /* Everything deliberately private except for the inheritance */ + friend class MeshVisualizerGL3D; + + explicit CompileState(NoCreateT): MeshVisualizerGL3D{NoCreate}, _vert{NoCreate}, _frag{NoCreate}, _geom{NoCreate} {} + + explicit CompileState(MeshVisualizerGL3D&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Shader* geom, GL::Version version): MeshVisualizerGL3D{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _geom{NoCreate}, _version{version} { + if(geom) _geom = Implementation::GLShaderWrapper{std::move(*geom)}; + } + + Implementation::GLShaderWrapper _vert, _frag, _geom; + GL::Version _version; +}; + /** @debugoperatorclassenum{MeshVisualizerGL2D,MeshVisualizerGL2D::Flag} */ MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, MeshVisualizerGL2D::Flag value); diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index a23561463..6d1aaa13b 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -29,7 +30,7 @@ #include #endif #include -#include +#include #include #include #include @@ -74,21 +75,12 @@ namespace { #endif } -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount +PhongGL::CompileState PhongGL::compile(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)} -{ +) { + #ifndef CORRADE_NO_ASSERT { const bool textureTransformationNotEnabledOrTextured = !(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)) #ifndef MAGNUM_TARGET_GLES2 @@ -96,32 +88,33 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount #endif ; CORRADE_ASSERT(textureTransformationNotEnabledOrTextured, - "Shaders::PhongGL: texture transformation enabled but the shader is not textured", ); + "Shaders::PhongGL: texture transformation enabled but the shader is not textured", CompileState{NoCreate}); } + #endif #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", ); + "Shaders::PhongGL: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, - "Shaders::PhongGL: material count can't be zero", ); + "Shaders::PhongGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::PhongGL: draw count can't be zero", ); + "Shaders::PhongGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(flags & Flag::TextureArrays) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)) || flags >= Flag::ObjectIdTexture, - "Shaders::PhongGL: texture arrays enabled but the shader is not textured", ); + "Shaders::PhongGL: texture arrays enabled but the shader is not textured", CompileState{NoCreate}); 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", ); + "Shaders::PhongGL: texture arrays require texture transformation enabled as well if uniform buffers are used", CompileState{NoCreate}); CORRADE_ASSERT(!(flags & Flag::LightCulling) || (flags & Flag::UniformBuffers), - "Shaders::PhongGL: light culling requires uniform buffers to be enabled", ); + "Shaders::PhongGL: light culling requires uniform buffers to be enabled", CompileState{NoCreate}); #endif CORRADE_ASSERT(!(flags & Flag::SpecularTexture) || !(flags & (Flag::NoSpecular)), - "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", ); + "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", CompileState{NoCreate}); #ifndef MAGNUM_TARGET_GLES if(flags >= Flag::UniformBuffers) @@ -154,7 +147,7 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount #ifndef MAGNUM_TARGET_GLES CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || context.isExtensionSupported(), - "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), ); + "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -163,6 +156,17 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount const GL::Version version = context.supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); #endif + PhongGL out{NoInit}; + out._flags = flags; + out._lightCount = lightCount; + out._lightColorsUniform = out._lightPositionsUniform + Int(lightCount); + out._lightSpecularColorsUniform = out._lightPositionsUniform + 2*Int(lightCount); + out._lightRangesUniform = out._lightPositionsUniform + 3*Int(lightCount); + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif + GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); @@ -283,9 +287,9 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount "#define LIGHT_SPECULAR_COLORS_LOCATION {}\n" "#define LIGHT_RANGES_LOCATION {}\n", lightCount, - _lightPositionsUniform + lightCount, - _lightPositionsUniform + 2*lightCount, - _lightPositionsUniform + 3*lightCount)); + out._lightPositionsUniform + lightCount, + out._lightPositionsUniform + 2*lightCount, + out._lightPositionsUniform + 3*lightCount)); } #ifndef MAGNUM_TARGET_GLES if(!(flags >= Flag::UniformBuffers) && lightCount) @@ -294,9 +298,10 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Phong.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly and doesn't even provide bindFragmentDataLocation() */ @@ -305,103 +310,118 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Position::Location, "position"); if(lightCount) - bindAttributeLocation(Normal::Location, "normal"); + out.bindAttributeLocation(Normal::Location, "normal"); if((flags & Flag::NormalTexture) && lightCount) { - bindAttributeLocation(Tangent::Location, "tangent"); + out.bindAttributeLocation(Tangent::Location, "tangent"); if(flags & Flag::Bitangent) - bindAttributeLocation(Bitangent::Location, "bitangent"); + out.bindAttributeLocation(Bitangent::Location, "bitangent"); } if(flags & Flag::VertexColor) - bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ + out.bindAttributeLocation(Color3::Location, "vertexColor"); /* Color4 is the same */ if(flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture) #ifndef MAGNUM_TARGET_GLES2 || flags >= Flag::ObjectIdTexture #endif ) - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); #ifndef MAGNUM_TARGET_GLES2 if(flags & Flag::ObjectId) { - bindFragmentDataLocation(ColorOutput, "color"); - bindFragmentDataLocation(ObjectIdOutput, "objectId"); + out.bindFragmentDataLocation(ColorOutput, "color"); + out.bindFragmentDataLocation(ObjectIdOutput, "objectId"); } if(flags >= Flag::InstancedObjectId) - bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif if(flags & Flag::InstancedTransformation) { - bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); if(lightCount) - bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); + out.bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); } if(flags >= Flag::InstancedTextureOffset) - bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationMatrixUniform = uniformLocation("transformationMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::TextureArrays) + if(_flags & Flag::TextureArrays) _textureLayerUniform = uniformLocation("textureLayer"); #endif _projectionMatrixUniform = uniformLocation("projectionMatrix"); _ambientColorUniform = uniformLocation("ambientColor"); - if(lightCount) { + if(_lightCount) { _normalMatrixUniform = uniformLocation("normalMatrix"); _diffuseColorUniform = uniformLocation("diffuseColor"); - if(!(flags & Flag::NoSpecular)) { + if(!(_flags & Flag::NoSpecular)) { _specularColorUniform = uniformLocation("specularColor"); _shininessUniform = uniformLocation("shininess"); } - if(flags & Flag::NormalTexture) + if(_flags & Flag::NormalTexture) _normalTextureScaleUniform = uniformLocation("normalTextureScale"); _lightPositionsUniform = uniformLocation("lightPositions"); _lightColorsUniform = uniformLocation("lightColors"); - if(!(flags & Flag::NoSpecular)) + if(!(_flags & Flag::NoSpecular)) _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); _lightRangesUniform = uniformLocation("lightRanges"); } - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + if(_flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif } } #ifndef MAGNUM_TARGET_GLES - if(flags && !context.isExtensionSupported(version)) + if(_flags && !context.isExtensionSupported(version)) #endif { - if(flags & Flag::AmbientTexture) setUniform(uniformLocation("ambientTexture"), AmbientTextureUnit); - if(lightCount) { - if(flags & Flag::DiffuseTexture) setUniform(uniformLocation("diffuseTexture"), DiffuseTextureUnit); - if(flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); - if(flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); + if(_flags & Flag::AmbientTexture) setUniform(uniformLocation("ambientTexture"), AmbientTextureUnit); + if(_lightCount) { + if(_flags & Flag::DiffuseTexture) setUniform(uniformLocation("diffuseTexture"), DiffuseTextureUnit); + if(_flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); + if(_flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); } #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::ObjectIdTexture) setUniform(uniformLocation("objectIdTextureData"), ObjectIdTextureUnit); + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); - if(lightCount) + if(_lightCount) setUniformBlockBinding(uniformBlockIndex("Light"), LightBufferBinding); } #endif @@ -410,44 +430,53 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount /* 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) { + 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}); + 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) { + if(_lightCount) { setDiffuseColor(Magnum::Color4{1.0f}); - if(!(flags & Flag::NoSpecular)) { + if(!(_flags & Flag::NoSpecular)) { setSpecularColor(Magnum::Color4{1.0f, 0.0f}); setShininess(80.0f); } - if(flags & Flag::NormalTexture) + 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}}; + 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)) + if(!(_flags & Flag::NoSpecular)) setLightSpecularColors(colors); - setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); + setLightRanges(Containers::Array{DirectInit, _lightCount, Constants::inf()}); /* Light position is zero by default */ setNormalMatrix(Matrix3x3{Math::IdentityInit}); } - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); /* Texture layer is zero by default */ - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ } #endif + + static_cast(context); + static_cast(version); } +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{compile(flags, lightCount)} {} + #ifndef MAGNUM_TARGET_GLES2 -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{flags, lightCount, 1, 1} {} +PhongGL::CompileState PhongGL::compile(const Flags flags, const UnsignedInt lightCount) { + return compile(flags, lightCount, 1, 1); +} + +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount, const UnsignedInt materialCount, const UnsignedInt drawCount): PhongGL{compile(flags, lightCount, materialCount, drawCount)} {} #endif PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 1a59c564b..12396bc30 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,6 +33,7 @@ #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -296,6 +298,8 @@ Besides that, the usage is similar for all shaders, see */ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -753,6 +757,34 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ typedef Containers::EnumSet Flags; + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref PhongGL(Flags, UnsignedInt) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref PhongGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}, UnsignedInt lightCount = 1); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + * can perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref PhongGL(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -762,6 +794,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * 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. + * @see @ref compile(Flags, UnsignedInt) */ explicit PhongGL(Flags flags = {}, UnsignedInt lightCount = 1); @@ -778,6 +811,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref TextureTransformationUniform buffer bound with * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p lightCount, * @p materialCount and @p drawCount describe the uniform buffer sizes @@ -791,6 +825,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * 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). + * @see @ref compile(Flags, UnsignedInt, UnsignedInt, 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. @@ -807,6 +842,16 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { explicit PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit PhongGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -1744,6 +1789,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit PhongGL(NoInitT) {} + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -1784,6 +1833,24 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +class PhongGL::CompileState: public PhongGL { + /* Everything deliberately private except for the inheritance */ + friend class PhongGL; + + explicit CompileState(NoCreateT): PhongGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(PhongGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): PhongGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + Implementation::GLShaderWrapper _vert, _frag; + GL::Version _version; +}; + /** @debugoperatorclassenum{PhongGL,PhongGL::Flag} */ MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, PhongGL::Flag value); diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 968adb16c..91ab6651a 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -30,6 +30,7 @@ set(CMAKE_FOLDER "Magnum/Shaders/Test") 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(ShadersGLShaderWrapperTest GLShaderWrapperTest.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) @@ -164,6 +165,8 @@ if(MAGNUM_BUILD_GL_TESTS) endif() endif() + corrade_add_test(ShadersGLShaderWrapperGLTest GLShaderWrapperGLTest.cpp LIBRARIES MagnumShaders MagnumOpenGLTester) + set(ShadersMeshVisualizerGLTest_SRCS MeshVisualizerGLTest.cpp) if(CORRADE_TARGET_IOS) list(APPEND ShadersMeshVisualizerGLTest_SRCS FlatTestFiles MeshVisualizerTestFiles) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index d8bf027ac..f81bd17aa 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,6 +33,7 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -84,8 +86,10 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { explicit DistanceFieldVectorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -251,11 +255,19 @@ DistanceFieldVectorGLTest::DistanceFieldVectorGLTest() { &DistanceFieldVectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &DistanceFieldVectorGLTest::constructAsync<2>, + &DistanceFieldVectorGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &DistanceFieldVectorGLTest::constructUniformBuffers<2>, &DistanceFieldVectorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &DistanceFieldVectorGLTest::constructUniformBuffersAsync<2>, + &DistanceFieldVectorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -374,6 +386,29 @@ template void DistanceFieldVectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void DistanceFieldVectorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename DistanceFieldVectorGL::CompileState state = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + DistanceFieldVectorGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::TextureTransformation); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 template void DistanceFieldVectorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -413,6 +448,38 @@ template void DistanceFieldVectorGLTest::constructUnifor MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void DistanceFieldVectorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + typename DistanceFieldVectorGL::CompileState state = DistanceFieldVectorGL::compile(DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 48); + CORRADE_COMPARE(state.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(state.materialCount(), 16); + CORRADE_COMPARE(state.drawCount(), 48); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + DistanceFieldVectorGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), DistanceFieldVectorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(shader.materialCount(), 16); + CORRADE_COMPARE(shader.drawCount(), 48); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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() { diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 730bb0fad..60198e313 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,6 +33,7 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -83,8 +85,10 @@ struct FlatGLTest: GL::OpenGLTester { explicit FlatGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -600,11 +604,19 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &FlatGLTest::constructAsync<2>, + &FlatGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &FlatGLTest::constructUniformBuffers<2>, &FlatGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &FlatGLTest::constructUniformBuffersAsync<2>, + &FlatGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -853,6 +865,29 @@ template void FlatGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void FlatGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename FlatGL::CompileState state = FlatGL::compile(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + FlatGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); + + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 template void FlatGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -896,6 +931,38 @@ template void FlatGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void FlatGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + typename FlatGL::CompileState state = FlatGL::compile(FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 8, 48); + CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 48); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + FlatGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 48); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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() { diff --git a/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp b/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp new file mode 100644 index 000000000..f4a83ed58 --- /dev/null +++ b/src/Magnum/Shaders/Test/GLShaderWrapperGLTest.cpp @@ -0,0 +1,171 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Shader.h" +#include "Magnum/GL/Version.h" +#include "Magnum/Shaders/glShaderWrapper.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GLShaderWrapperGLTest: GL::OpenGLTester { + explicit GLShaderWrapperGLTest(); + + void construct(); + void constructMove(); + + void convert(); + void convertRvalue(); +}; + +GLShaderWrapperGLTest::GLShaderWrapperGLTest() { + addTests({&GLShaderWrapperGLTest::construct, + &GLShaderWrapperGLTest::constructMove, + + &GLShaderWrapperGLTest::convert, + &GLShaderWrapperGLTest::convertRvalue}); +} + +void GLShaderWrapperGLTest::construct() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + + GLuint id = glShader.id(); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + Implementation::GLShaderWrapper shader{std::move(glShader)}; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(shader.id, id); + CORRADE_COMPARE(shader.type, GL_FRAGMENT_SHADER); + CORRADE_VERIFY(!glShader.id()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +void GLShaderWrapperGLTest::constructMove() { + GL::Shader glShaderA{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper a{std::move(glShaderA)}; + + GLuint id = a.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + Implementation::GLShaderWrapper b{std::move(a)}; + CORRADE_VERIFY(!a.id); + CORRADE_COMPARE(b.id, id); + CORRADE_COMPARE(b.type, GL_FRAGMENT_SHADER); + + GL::Shader glShaderB{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL210, + #else + GL::Version::GLES200, + #endif + GL::Shader::Type::Vertex}; + Implementation::GLShaderWrapper c{std::move(glShaderB)}; + + GLuint cId = c.id; + c = std::move(b); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(cId > 0); + CORRADE_COMPARE(b.id, cId); + CORRADE_COMPARE(c.id, id); + CORRADE_COMPARE(c.type, GL_FRAGMENT_SHADER); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void GLShaderWrapperGLTest::convert() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper shader{std::move(glShader)}; + + GLuint id = shader.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + GL::Shader glShader2 = shader; + MAGNUM_VERIFY_NO_GL_ERROR(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(glShader2.id(), id); + CORRADE_COMPARE(glShader2.type(), GL::Shader::Type::Fragment); + CORRADE_VERIFY(shader.id); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +void GLShaderWrapperGLTest::convertRvalue() { + { + GL::Shader glShader{ + #ifndef MAGNUM_TARGET_GLES + GL::Version::GL300, + #else + GL::Version::GLES300, + #endif + GL::Shader::Type::Fragment}; + Implementation::GLShaderWrapper shader{std::move(glShader)}; + + GLuint id = shader.id; + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(id > 0); + + GL::Shader glShader2 = std::move(shader); + MAGNUM_VERIFY_NO_GL_ERROR(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(glShader2.id(), id); + CORRADE_COMPARE(glShader2.type(), GL::Shader::Type::Fragment); + CORRADE_VERIFY(!shader.id); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GLShaderWrapperGLTest) diff --git a/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp b/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp new file mode 100644 index 000000000..aaf462921 --- /dev/null +++ b/src/Magnum/Shaders/Test/GLShaderWrapperTest.cpp @@ -0,0 +1,58 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "Magnum/Magnum.h" +#include "Magnum/Shaders/glShaderWrapper.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GLShaderWrapperTest: TestSuite::Tester { + explicit GLShaderWrapperTest(); + + void constructNoCreate(); + void constructCopy(); +}; + +GLShaderWrapperTest::GLShaderWrapperTest() { + addTests({&GLShaderWrapperTest::constructNoCreate, + &GLShaderWrapperTest::constructCopy}); +} + +void GLShaderWrapperTest::constructNoCreate() { + Implementation::GLShaderWrapper shader{NoCreate}; + CORRADE_COMPARE(shader.type, 0); + CORRADE_COMPARE(shader.id, 0); +} + +void GLShaderWrapperTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GLShaderWrapperTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 7ac0ac612..5c091d384 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,6 +33,7 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -88,12 +90,16 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { explicit MeshVisualizerGLTest(); void construct2D(); + void construct2DAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers2D(); + void constructUniformBuffers2DAsync(); #endif void construct3D(); + void construct3DAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers3D(); + void constructUniformBuffers3DAsync(); #endif void construct2DInvalid(); @@ -1055,17 +1061,23 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct2D}, Containers::arraySize(ConstructData2D)); + addTests({&MeshVisualizerGLTest::construct2DAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2D}, Containers::arraySize(ConstructUniformBuffersData2D)); + addTests({&MeshVisualizerGLTest::constructUniformBuffers2DAsync}); #endif addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); + addTests({&MeshVisualizerGLTest::construct3DAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3D}, Containers::arraySize(ConstructUniformBuffersData3D)); + addTests({&MeshVisualizerGLTest::constructUniformBuffers3DAsync}); #endif addInstancedTests({&MeshVisualizerGLTest::construct2DInvalid}, @@ -1403,6 +1415,28 @@ void MeshVisualizerGLTest::construct2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void MeshVisualizerGLTest::construct2DAsync() { + MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL2D shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::constructUniformBuffers2D() { auto&& data = ConstructUniformBuffersData2D[testCaseInstanceId()]; @@ -1480,8 +1514,40 @@ void MeshVisualizerGLTest::constructUniformBuffers2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + + +void MeshVisualizerGLTest::constructUniformBuffers2DAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile( MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 8, 55); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 55); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL2D shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 55); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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 MeshVisualizerGLTest::construct3D() { auto&& data = ConstructData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -1541,6 +1607,27 @@ void MeshVisualizerGLTest::construct3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +void MeshVisualizerGLTest::construct3DAsync() { + MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL3D shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::constructUniformBuffers3D() { auto&& data = ConstructUniformBuffersData3D[testCaseInstanceId()]; @@ -1618,6 +1705,36 @@ void MeshVisualizerGLTest::constructUniformBuffers3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void MeshVisualizerGLTest::constructUniformBuffers3DAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 6, 28); + CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.materialCount(), 6); + CORRADE_COMPARE(state.drawCount(), 28); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + MeshVisualizerGL3D shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(state.materialCount(), 6); + CORRADE_COMPARE(state.drawCount(), 28); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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 MeshVisualizerGLTest::construct2DInvalid() { diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index e6414a2c2..6248629cc 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -33,10 +34,10 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include -#include /* isSandboxed() */ #endif #include "Magnum/Image.h" @@ -83,8 +84,10 @@ struct PhongGLTest: GL::OpenGLTester { explicit PhongGLTest(); void construct(); + void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers(); + void constructUniformBuffersAsync(); #endif void constructMove(); @@ -927,9 +930,13 @@ PhongGLTest::PhongGLTest() { addInstancedTests({&PhongGLTest::construct}, Containers::arraySize(ConstructData)); + addTests({&PhongGLTest::constructAsync}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&PhongGLTest::constructUniformBuffers}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({&PhongGLTest::constructUniformBuffersAsync}); #endif addTests({ @@ -1192,6 +1199,29 @@ void PhongGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +void PhongGLTest::constructAsync() { + PhongGL::CompileState state = PhongGL::compile(PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset, 3); + CORRADE_COMPARE(state.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); + CORRADE_COMPARE(state.lightCount(), 3); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + PhongGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); + CORRADE_COMPARE(shader.lightCount(), 3); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::constructUniformBuffers() { auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; @@ -1234,6 +1264,38 @@ void PhongGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +void PhongGLTest::constructUniformBuffersAsync() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + PhongGL::CompileState state = PhongGL::compile(PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24); + CORRADE_COMPARE(state.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); + CORRADE_COMPARE(state.lightCount(), 8); + CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.drawCount(), 24); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + PhongGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); + CORRADE_COMPARE(shader.lightCount(), 8); + CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.drawCount(), 24); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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() { diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp index 0e720f4b3..7624ad78e 100644 --- a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -94,6 +94,8 @@ namespace Magnum { namespace Shaders { namespace Test { namespace { struct ShadersGLBenchmark: GL::OpenGLTester { explicit ShadersGLBenchmark(); + /** @todo async vs sync setup? could be useful especially on WebGL & ANGLE */ + void renderSetup(); void renderTeardown(); diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 670886fb7..bc7b560ed 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -32,6 +33,7 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -83,8 +85,10 @@ struct VectorGLTest: GL::OpenGLTester { explicit VectorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -247,11 +251,19 @@ VectorGLTest::VectorGLTest() { &VectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + addTests({ + &VectorGLTest::constructAsync<2>, + &VectorGLTest::constructAsync<3>}); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &VectorGLTest::constructUniformBuffers<2>, &VectorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &VectorGLTest::constructUniformBuffersAsync<2>, + &VectorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -370,6 +382,30 @@ template void VectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void VectorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename VectorGL::CompileState state = VectorGL::compile(VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.flags(), VectorGL2D::Flag::TextureTransformation); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + VectorGL shader{std::move(state)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::TextureTransformation); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + + #ifndef MAGNUM_TARGET_GLES2 template void VectorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -408,6 +444,38 @@ template void VectorGLTest::constructUniformBuffers() { MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void VectorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + typename VectorGL::CompileState state = VectorGL::compile(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 15, 42); + CORRADE_COMPARE(state.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(state.materialCount(), 15); + CORRADE_COMPARE(state.drawCount(), 42); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + VectorGL shader{std::move(state)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.flags(), VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation); + CORRADE_COMPARE(shader.materialCount(), 15); + CORRADE_COMPARE(shader.drawCount(), 42); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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() { diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index abbd56bf4..352cf96d0 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -30,6 +31,7 @@ #include #include #include +#include #ifdef CORRADE_TARGET_APPLE #include @@ -74,8 +76,10 @@ struct VertexColorGLTest: GL::OpenGLTester { explicit VertexColorGLTest(); template void construct(); + template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); #endif template void constructMove(); @@ -190,13 +194,19 @@ constexpr struct { VertexColorGLTest::VertexColorGLTest() { addTests({ &VertexColorGLTest::construct<2>, - &VertexColorGLTest::construct<3>}); + &VertexColorGLTest::construct<3>, + &VertexColorGLTest::constructAsync<2>, + &VertexColorGLTest::constructAsync<3>}); #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({ &VertexColorGLTest::constructUniformBuffers<2>, &VertexColorGLTest::constructUniformBuffers<3>}, Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &VertexColorGLTest::constructUniformBuffersAsync<2>, + &VertexColorGLTest::constructUniformBuffersAsync<3>}); #endif addTests({ @@ -309,6 +319,28 @@ template void VertexColorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +template void VertexColorGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename VertexColorGL::CompileState state = VertexColorGL::compile({}); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + VertexColorGL shader{std::move(state)}; + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + + #ifndef MAGNUM_TARGET_GLES2 template void VertexColorGLTest::constructUniformBuffers() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -347,6 +379,36 @@ template void VertexColorGLTest::constructUniformBuffers MAGNUM_VERIFY_NO_GL_ERROR(); } + +template void VertexColorGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + typename VertexColorGL::CompileState state = VertexColorGL::compile(VertexColorGL2D::Flag::UniformBuffers, 63); + CORRADE_COMPARE(state.flags(), VertexColorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(state.drawCount(), 63); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + VertexColorGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), VertexColorGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(shader.drawCount(), 63); + CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + 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() { diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index addcb7433..f06d5cba4 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,7 +27,7 @@ #include "VectorGL.h" #include -#include +#include #include #include "Magnum/GL/Context.h" @@ -63,21 +64,16 @@ namespace { #endif } -template VectorGL::VectorGL(const Flags flags +template typename VectorGL::CompileState VectorGL::compile(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", ); + "Shaders::VectorGL: material count can't be zero", CompileState{NoCreate}); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, - "Shaders::VectorGL: draw count can't be zero", ); + "Shaders::VectorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -141,9 +137,17 @@ template VectorGL::VectorGL(const Flags flag frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("Vector.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + VectorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._materialCount = materialCount; + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -151,25 +155,40 @@ template VectorGL::VectorGL(const Flags flag if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(TextureCoordinates::Location, "textureCoordinates"); } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template VectorGL::VectorGL(CompileState&& state): VectorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"); _backgroundColorUniform = uniformLocation("backgroundColor"); _colorUniform = uniformLocation("color"); @@ -182,10 +201,10 @@ template VectorGL::VectorGL(const Flags flag { setUniform(uniformLocation("vectorTexture"), TextureUnit); #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); } @@ -195,24 +214,35 @@ template VectorGL::VectorGL(const Flags flag /* 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) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif { setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) + if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); /* Background color is zero by default */ setColor(Color4{1.0f}); } #endif + + static_cast(context); + static_cast(version); } +template VectorGL::VectorGL(const Flags flags): VectorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template VectorGL::VectorGL(const Flags flags): VectorGL{flags, 1, 1} {} +template typename VectorGL::CompileState VectorGL::compile(const Flags flags) { + return compile(flags, 1, 1); +} + +template VectorGL::VectorGL(const Flags flags, const UnsignedInt materialCount, const UnsignedInt drawCount): VectorGL{compile(flags, materialCount, drawCount)} {} #endif +template VectorGL::VectorGL(NoInitT) {} + template VectorGL& VectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index e1157ba9d..5196ce5da 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -33,6 +34,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -115,6 +117,8 @@ example. */ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -212,6 +216,34 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL typedef Implementation::VectorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref VectorGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref VectorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref VectorGL(Flags, UnsignedInt, UnsignedInt) can + * perform an asynchronous compilation and linking. See + * @ref shaders-async for more information. + * @see @ref VectorGL(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -220,6 +252,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * 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. + * @see @ref compile(Flags) */ explicit VectorGL(Flags flags = {}); @@ -234,6 +267,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @ref VectorDrawUniform / @ref TextureTransformationUniform * buffer bound with @ref bindTransformationProjectionBuffer(), * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and * @p drawCount describe the uniform buffer sizes as these are required @@ -243,6 +277,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is * ignored and the constructor behaves the same as @ref VectorGL(Flags). + * @see @ref compile(Flags, UnsignedInt, 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. @@ -259,6 +294,16 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL explicit VectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit VectorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -509,7 +554,7 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ /** - * @brief Bind vector texture + * @brief Bind a vector texture * @return Reference to self (for method chaining) * * @see @ref Flag::TextureTransformation, @ref setTextureMatrix()s @@ -554,6 +599,10 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit VectorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -577,6 +626,24 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +template class VectorGL::CompileState: public VectorGL { + /* Everything deliberately private except for the inheritance */ + friend class VectorGL; + + explicit CompileState(NoCreateT): VectorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(VectorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VectorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + Implementation::GLShaderWrapper _vert, _frag; + GL::Version _version; +}; + /** @brief Two-dimensional vector OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 31d6e4821..77907b903 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,12 +27,11 @@ #include "VertexColorGL.h" #include -#include +#include #include #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/Shader.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" @@ -57,19 +57,14 @@ namespace { #endif } -template VertexColorGL::VertexColorGL(const Flags flags +template typename VertexColorGL::CompileState VertexColorGL::compile(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", ); + "Shaders::VertexColorGL: draw count can't be zero", CompileState{NoCreate}); #endif #ifndef MAGNUM_TARGET_GLES @@ -121,9 +116,16 @@ template VertexColorGL::VertexColorGL(const frag.addSource(rs.getString("generic.glsl")) .addSource(rs.getString("VertexColor.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + vert.submitCompile(); + frag.submitCompile(); + + VertexColorGL out{NoInit}; + out._flags = flags; + #ifndef MAGNUM_TARGET_GLES2 + out._drawCount = drawCount; + #endif - attachShaders({vert, frag}); + out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly */ #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) @@ -131,19 +133,34 @@ template VertexColorGL::VertexColorGL(const if(!context.isExtensionSupported(version)) #endif { - bindAttributeLocation(Position::Location, "position"); - bindAttributeLocation(Color3::Location, "color"); /* Color4 is the same */ + out.bindAttributeLocation(Position::Location, "position"); + out.bindAttributeLocation(Color3::Location, "color"); /* Color4 is the same */ } #endif - CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag), version}; +} + +template VertexColorGL::VertexColorGL(CompileState&& state): VertexColorGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + const GL::Version version = state._version; #ifndef MAGNUM_TARGET_GLES if(!context.isExtensionSupported(version)) #endif { #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers) { + if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else #endif @@ -153,7 +170,7 @@ template VertexColorGL::VertexColorGL(const } #ifndef MAGNUM_TARGET_GLES2 - if(flags >= Flag::UniformBuffers + if(_flags >= Flag::UniformBuffers #ifndef MAGNUM_TARGET_GLES && !context.isExtensionSupported(version) #endif @@ -165,7 +182,7 @@ template VertexColorGL::VertexColorGL(const /* 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) { + if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else #endif @@ -173,12 +190,23 @@ template VertexColorGL::VertexColorGL(const setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); } #endif + + static_cast(context); + static_cast(version); } +template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{compile(flags)} {} + #ifndef MAGNUM_TARGET_GLES2 -template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{flags, 1} {} +template typename VertexColorGL::CompileState VertexColorGL::compile(const Flags flags) { + return compile(flags, 1); +} + +template VertexColorGL::VertexColorGL(const Flags flags, const UnsignedInt drawCount): VertexColorGL{compile(flags, drawCount)} {} #endif +template VertexColorGL::VertexColorGL(NoInitT) {} + template VertexColorGL& VertexColorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 3ecdd673f..d09e95bdd 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš + Copyright © Vladislav Oleshko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -33,6 +34,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" #include "Magnum/Shaders/visibility.h" namespace Magnum { namespace Shaders { @@ -110,6 +112,8 @@ similar for all shaders, see @ref shaders-usage-multidraw for an example. */ template class MAGNUM_SHADERS_EXPORT VertexColorGL: public GL::AbstractShaderProgram { public: + class CompileState; + /** * @brief Vertex position * @@ -207,6 +211,34 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ typedef Implementation::VertexColorGLFlags Flags; #endif + /** + * @brief Compile asynchronously + * @m_since_latest + * + * Compared to @ref VertexColorGL(Flags) can perform an asynchronous + * compilation and linking. See @ref shaders-async for more + * information. + * @see @ref VertexColorGL(CompileState&&), + * @ref compile(Flags, UnsignedInt) + */ + static CompileState compile(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Compile for a multi-draw scenario asynchronously + * @m_since_latest + * + * Compared to @ref VertexColorGL(Flags, UnsignedInt) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref VertexColorGL(CompileState&&), @ref compile(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. + */ + static CompileState compile(Flags flags, UnsignedInt drawCount); + #endif + /** * @brief Constructor * @param flags Flags @@ -215,6 +247,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * scenario (without @ref Flag::UniformBuffers set), it's equivalent to * @ref VertexColorGL(Flags, UnsignedInt) with @p drawCount set to * @cpp 1 @ce. + * @see @ref compile(Flags) */ explicit VertexColorGL(Flags flags = {}); @@ -225,6 +258,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @param drawCount Size of a @ref TransformationProjectionUniform2D * / @ref TransformationProjectionUniform3D buffer bound with * @ref bindTransformationProjectionBuffer() + * @m_since_latest * * If @p flags contains @ref Flag::UniformBuffers, @p drawCount * describes the uniform buffer sizes as these are required to have a @@ -234,6 +268,7 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is * ignored and the constructor behaves the same as * @ref VertexColorGL(Flags). + * @see @ref compile(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. @@ -250,6 +285,16 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ explicit VertexColorGL(Flags flags, UnsignedInt drawCount); #endif + /** + * @brief Finalize an asynchronous compilation + * @m_since_latest + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit VertexColorGL(CompileState&& state); + /** * @brief Construct without creating the underlying OpenGL object * @@ -406,6 +451,10 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit VertexColorGL(NoInitT); + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -426,6 +475,24 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ #endif }; +/** +@brief Asynchronous compilation state +@m_since_latest + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +template class VertexColorGL::CompileState: public VertexColorGL { + /* Everything deliberately private except for the inheritance */ + friend class VertexColorGL; + + explicit CompileState(NoCreateT): VertexColorGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(VertexColorGL&& shader, GL::Shader&& vert, GL::Shader&& frag, GL::Version version): VertexColorGL{std::move(shader)}, _vert{std::move(vert)}, _frag{std::move(frag)}, _version{version} {} + + Implementation::GLShaderWrapper _vert, _frag; + GL::Version _version; +}; + /** @brief 2D vertex color OpenGL shader @m_since_latest diff --git a/src/Magnum/Shaders/glShaderWrapper.cpp b/src/Magnum/Shaders/glShaderWrapper.cpp new file mode 100644 index 000000000..63f43ca63 --- /dev/null +++ b/src/Magnum/Shaders/glShaderWrapper.cpp @@ -0,0 +1,61 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "glShaderWrapper.h" + +#include "Magnum/GL/Shader.h" + +namespace Magnum { namespace Shaders { namespace Implementation { + +GLShaderWrapper::GLShaderWrapper(GL::Shader&& shader) noexcept: type{GLenum(shader.type())}, id{shader.release()} {} + +GLShaderWrapper::GLShaderWrapper(GLShaderWrapper&& other) noexcept: type{other.type}, id{other.id} { + other.id = 0; +} + +GLShaderWrapper& GLShaderWrapper::operator=(GLShaderWrapper&& other) noexcept { + using std::swap; + swap(other.type, type); + swap(other.id, id); + return *this; +} + +GLShaderWrapper::~GLShaderWrapper() { + /* Convert itself to a temporary GL::Shader, triggering deletion in its + destructor */ + if(id) GL::Shader{std::move(*this)}; +} + +GLShaderWrapper::operator GL::Shader() & noexcept { + return GL::Shader::wrap(GL::Shader::Type(type), id); +} + +GLShaderWrapper::operator GL::Shader() && noexcept { + GL::Shader out = GL::Shader::wrap(GL::Shader::Type(type), id, GL::ObjectFlag::DeleteOnDestruction); + id = 0; + return out; +} + +}}} diff --git a/src/Magnum/Shaders/glShaderWrapper.h b/src/Magnum/Shaders/glShaderWrapper.h new file mode 100644 index 000000000..bc4d77b2f --- /dev/null +++ b/src/Magnum/Shaders/glShaderWrapper.h @@ -0,0 +1,62 @@ +#ifndef Magnum_Shaders_glShaderWrapper_h +#define Magnum_Shaders_glShaderWrapper_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "Magnum/Tags.h" +#include "Magnum/GL/GL.h" +#include "Magnum/Shaders/visibility.h" + +namespace Magnum { namespace Shaders { namespace Implementation { + +/* A lightweight alternative to GL::Shader that holds just the type and ID, + used by CompileState instances of all shaders. There it's used just to + retrieve error messages in case of a compilation failures, so it doesn't + make sense to pull in stuff needed to store shader sources such as strings + and vectors/arrays. + + Might get revisited once GL::Shader gets the deSTLification treatment, but + even then this class seems significantly lighter weight. */ +struct MAGNUM_SHADERS_EXPORT GLShaderWrapper { + /*implicit*/ GLShaderWrapper(NoCreateT) noexcept: type{}, id{} {} + /*implicit*/ GLShaderWrapper(GL::Shader&& shader) noexcept; + + GLShaderWrapper(const GLShaderWrapper&) = delete; + GLShaderWrapper(GLShaderWrapper&& other) noexcept; + + GLShaderWrapper& operator=(const GLShaderWrapper&) = delete; + GLShaderWrapper& operator=(GLShaderWrapper&& other) noexcept; + + ~GLShaderWrapper(); + /*implicit*/ operator GL::Shader() & noexcept; + /*implicit*/ operator GL::Shader() && noexcept; + + GLenum type; + GLuint id; +}; + +}}} + +#endif diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index cab58ee1a..e22f69069 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -27,6 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/Test") +find_package(Corrade REQUIRED PluginManager) + +corrade_add_test(ConverterUtilitiesTest ConverterUtilitiesTest.cpp LIBRARIES Magnum Corrade::PluginManager) corrade_add_test(FileCallbackTest FileCallbackTest.cpp LIBRARIES Magnum) corrade_add_test(ImageTest ImageTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(ImageFlagsTest ImageFlagsTest.cpp LIBRARIES Magnum) diff --git a/src/Magnum/Test/ConverterUtilitiesTest.cpp b/src/Magnum/Test/ConverterUtilitiesTest.cpp new file mode 100644 index 000000000..e825673f9 --- /dev/null +++ b/src/Magnum/Test/ConverterUtilitiesTest.cpp @@ -0,0 +1,174 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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/Implementation/converterUtilities.h" + +namespace Magnum { namespace Test { namespace { + +struct ConverterUtilitiesTest: TestSuite::Tester { + explicit ConverterUtilitiesTest(); + + void setOptions(); +}; + +const struct { + const char* name; + const char* options; + const char* anyPluginName; + const char* expectedConfig; + const char* expectedWarning; +} SetOptionsData[]{ + {"", "option=value", "AnyPlugin", R"([configuration] +option=value +another= +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"two options", "option=value,another=yes", "AnyPlugin", R"([configuration] +option=value +another=yes +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"implicit true", "option=value,another", "AnyPlugin", R"([configuration] +option=value +another=true +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"group", "group/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option=value +[configuration/group/nested] +option= +)", nullptr}, + {"nested group + root option after", "group/nested/option=value,another=yes", "AnyPlugin", R"([configuration] +option= +another=yes +[configuration/group] +option= +[configuration/group/nested] +option=value +)", nullptr}, + {"unrecognized option", "notFound=value", "AnyPlugin", R"([configuration] +option= +another= +notFound=value +[configuration/group] +option= +[configuration/group/nested] +option= +)", + /* The trailing space is there because the plugin name is empty */ + "Option notFound not recognized by \n"}, + {"unrecognized option in Any plugin", "notFound=value", "", R"([configuration] +option= +another= +notFound=value +[configuration/group] +option= +[configuration/group/nested] +option= +)", nullptr}, + {"unrecognized group", "notFound/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option= +[configuration/group/nested] +option= +[configuration/notFound] +option=value +)", + /* The trailing space is there because the plugin name is empty */ + "Option notFound/option not recognized by \n"}, + {"unrecognized nested group", "group/notFound/nested/option=value", "AnyPlugin", R"([configuration] +option= +another= +[configuration/group] +option= +[configuration/group/nested] +option= +[configuration/group/notFound/nested] +option=value +)", + /* The trailing space is there because the plugin name is empty */ + "Option group/notFound/nested/option not recognized by \n"} +}; + +ConverterUtilitiesTest::ConverterUtilitiesTest() { + addInstancedTests({&ConverterUtilitiesTest::setOptions}, + Containers::arraySize(SetOptionsData)); +} + +void ConverterUtilitiesTest::setOptions() { + auto&& data = SetOptionsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Plugin: PluginManager::AbstractPlugin { + explicit Plugin() { + /* Populate default config */ + configuration().setValue("option", ""); + configuration().setValue("another", ""); + Utility::ConfigurationGroup& group = *configuration().addGroup("group"); + group.setValue("option", ""); + group.addGroup("nested")->setValue("option", ""); + } + } plugin; + + { + std::ostringstream out; + Warning redirectWarning{&out}; + Implementation::setOptions(plugin, data.anyPluginName, data.options); + if(data.expectedWarning) + CORRADE_COMPARE(out.str(), data.expectedWarning); + else + CORRADE_COMPARE(out.str(), ""); + } + + Utility::Configuration conf; + /** @todo ugh, is there no better way to serialize a ConfigurationGroup? */ + conf.addGroup("configuration", new Utility::ConfigurationGroup{plugin.configuration()}); + CORRADE_COMPARE(conf.group("configuration")->configuration(), &conf); + std::ostringstream out; + conf.save(out); + CORRADE_COMPARE(out.str(), data.expectedConfig); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Test::ConverterUtilitiesTest) diff --git a/src/Magnum/Text/AbstractFont.cpp b/src/Magnum/Text/AbstractFont.cpp index b88755173..a926aed6e 100644 --- a/src/Magnum/Text/AbstractFont.cpp +++ b/src/Magnum/Text/AbstractFont.cpp @@ -283,11 +283,14 @@ Containers::Pointer AbstractFont::layout(const AbstractGlyphCa } Debug& operator<<(Debug& debug, const FontFeature value) { - debug << "Text::FontFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Text::FontFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case FontFeature::v: return debug << "::" #v; + #define _c(v) case FontFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) _c(FileCallback) _c(PreparedGlyphCache) @@ -295,11 +298,11 @@ Debug& operator<<(Debug& debug, const FontFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const FontFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Text::FontFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Text::FontFeatures{}", { FontFeature::OpenData, FontFeature::FileCallback, FontFeature::PreparedGlyphCache}); diff --git a/src/Magnum/Text/AbstractFont.h b/src/Magnum/Text/AbstractFont.h index 5d7b34f18..90dadf437 100644 --- a/src/Magnum/Text/AbstractFont.h +++ b/src/Magnum/Text/AbstractFont.h @@ -312,7 +312,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { bool isOpened() const { return doIsOpened(); } /** - * @brief Open font from raw data + * @brief Open raw data * @param data File data * @param size Font size * @@ -339,7 +339,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { #endif /** - * @brief Open font from file + * @brief Open a file * @param filename Font file * @param size Font size * @@ -349,7 +349,7 @@ class MAGNUM_TEXT_EXPORT AbstractFont: public PluginManager::AbstractPlugin { */ bool openFile(const std::string& filename, Float size); - /** @brief Close font */ + /** @brief Close currently opened file */ void close(); /** @@ -592,11 +592,11 @@ class MAGNUM_TEXT_EXPORT AbstractLayouter { /** @brief Moving is not allowed */ AbstractLayouter& operator=(const AbstractLayouter&&) = delete; - /** @brief Count of glyphs in laid out text */ + /** @brief Count of glyphs in the laid out text */ UnsignedInt glyphCount() const { return _glyphCount; } /** - * @brief Render glyph + * @brief Render a glyph * @param i Glyph index * @param cursorPosition Cursor position * @param rectangle Bounding rectangle diff --git a/src/Magnum/Text/AbstractFontConverter.cpp b/src/Magnum/Text/AbstractFontConverter.cpp index 02058ee2c..89a8f30ce 100644 --- a/src/Magnum/Text/AbstractFontConverter.cpp +++ b/src/Magnum/Text/AbstractFontConverter.cpp @@ -263,11 +263,14 @@ Containers::Pointer AbstractFontConverter::doImportGlyphCach } Debug& operator<<(Debug& debug, const FontConverterFeature value) { - debug << "Text::FontConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Text::FontConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case FontConverterFeature::v: return debug << "::" #v; + #define _c(v) case FontConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ExportFont) _c(ExportGlyphCache) _c(ImportGlyphCache) @@ -277,11 +280,11 @@ Debug& operator<<(Debug& debug, const FontConverterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(Containers::enumCastUnderlyingType(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(Containers::enumCastUnderlyingType(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const FontConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Text::FontConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Text::FontConverterFeatures{}", { FontConverterFeature::ExportFont, FontConverterFeature::ExportGlyphCache, FontConverterFeature::ImportGlyphCache, diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index 6fc0e0c37..8dd3b71b0 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -116,25 +116,18 @@ if(MAGNUM_WITH_FONTCONVERTER) target_link_libraries(magnum-fontconverter PRIVATE Magnum MagnumText - MagnumTrade) - if(MAGNUM_TARGET_HEADLESS) + MagnumTrade + ${MAGNUM_FONTCONVERTER_STATIC_PLUGINS}) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWindowsEglApplication) - else() - target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-fontconverter PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-fontconverter is not available on this platform. Set MAGNUM_WITH_FONTCONVERTER to OFF to suppress this warning.") endif() diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.h b/src/Magnum/Text/DistanceFieldGlyphCache.h index 430269708..944d54655 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.h +++ b/src/Magnum/Text/DistanceFieldGlyphCache.h @@ -77,7 +77,7 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { explicit DistanceFieldGlyphCache(const Vector2i& originalSize, const Vector2i& size, UnsignedInt radius); /** - * @brief Set distance field cache image + * @brief Set a distance field cache image * * Uploads already computed distance field image to given offset in * distance field texture. diff --git a/src/Magnum/Text/Test/AbstractFontConverterTest.cpp b/src/Magnum/Text/Test/AbstractFontConverterTest.cpp index a30d36da4..4e7cff7a7 100644 --- a/src/Magnum/Text/Test/AbstractFontConverterTest.cpp +++ b/src/Magnum/Text/Test/AbstractFontConverterTest.cpp @@ -95,7 +95,9 @@ struct AbstractFontConverterTest: TestSuite::Tester { void importGlyphCacheFromFileAsSingleDataNotFound(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractFontConverterTest::AbstractFontConverterTest() { @@ -149,7 +151,9 @@ AbstractFontConverterTest::AbstractFontConverterTest() { &AbstractFontConverterTest::importGlyphCacheFromFileAsSingleDataNotFound, &AbstractFontConverterTest::debugFeature, - &AbstractFontConverterTest::debugFeatures}); + &AbstractFontConverterTest::debugFeaturePacked, + &AbstractFontConverterTest::debugFeatures, + &AbstractFontConverterTest::debugFeaturesPacked}); /* Create testing dir */ Utility::Path::make(TEXT_TEST_OUTPUT_DIR); @@ -1019,6 +1023,13 @@ void AbstractFontConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Text::FontConverterFeature::ExportFont Text::FontConverterFeature(0xf0)\n"); } +void AbstractFontConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << FontConverterFeature::ExportFont << Debug::packed << FontConverterFeature(0xf0) << FontConverterFeature::ImportGlyphCache; + CORRADE_COMPARE(out.str(), "ExportFont 0xf0 Text::FontConverterFeature::ImportGlyphCache\n"); +} + void AbstractFontConverterTest::debugFeatures() { std::ostringstream out; @@ -1026,6 +1037,13 @@ void AbstractFontConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Text::FontConverterFeature::ExportFont|Text::FontConverterFeature::ImportGlyphCache Text::FontConverterFeatures{}\n"); } +void AbstractFontConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (FontConverterFeature::ExportFont|FontConverterFeature::ImportGlyphCache) << Debug::packed << FontConverterFeatures{} << FontConverterFeature::ExportGlyphCache; + CORRADE_COMPARE(out.str(), "ExportFont|ImportGlyphCache {} Text::FontConverterFeature::ExportGlyphCache\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::AbstractFontConverterTest) diff --git a/src/Magnum/Text/Test/AbstractFontTest.cpp b/src/Magnum/Text/Test/AbstractFontTest.cpp index f0df222a6..e11f2dd55 100644 --- a/src/Magnum/Text/Test/AbstractFontTest.cpp +++ b/src/Magnum/Text/Test/AbstractFontTest.cpp @@ -95,7 +95,9 @@ struct AbstractFontTest: TestSuite::Tester { void createGlyphCacheNoFont(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); }; AbstractFontTest::AbstractFontTest() { @@ -150,7 +152,9 @@ AbstractFontTest::AbstractFontTest() { &AbstractFontTest::createGlyphCacheNoFont, &AbstractFontTest::debugFeature, - &AbstractFontTest::debugFeatures}); + &AbstractFontTest::debugFeaturePacked, + &AbstractFontTest::debugFeatures, + &AbstractFontTest::debugFeaturesPacked}); } void AbstractFontTest::construct() { @@ -1154,6 +1158,13 @@ void AbstractFontTest::debugFeature() { CORRADE_COMPARE(out.str(), "Text::FontFeature::OpenData Text::FontFeature(0xf0)\n"); } +void AbstractFontTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << FontFeature::OpenData << Debug::packed << FontFeature(0xf0) << FontFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Text::FontFeature::FileCallback\n"); +} + void AbstractFontTest::debugFeatures() { std::ostringstream out; @@ -1161,6 +1172,13 @@ void AbstractFontTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Text::FontFeature::OpenData|Text::FontFeature::PreparedGlyphCache Text::FontFeatures{}\n"); } +void AbstractFontTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (FontFeature::OpenData|FontFeature::PreparedGlyphCache) << Debug::packed << FontFeatures{} << FontFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData|PreparedGlyphCache {} Text::FontFeature::FileCallback\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::AbstractFontTest) diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index cff8872ec..419fa21fa 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -34,24 +35,16 @@ #include "Magnum/Text/DistanceFieldGlyphCache.h" #include "Magnum/Trade/AbstractImageConverter.h" -#ifdef MAGNUM_TARGET_HEADLESS +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" #elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif @@ -166,12 +159,12 @@ int FontConverter::exec() { /* Font converter dependencies */ PluginManager::Manager imageConverterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; /* Load font */ PluginManager::Manager fontManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Text::AbstractFont::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Text::AbstractFont::pluginSearchPaths().back()).second())}; Containers::Pointer font = fontManager.loadAndInstantiate(args.value("font")); if(!font) return 1; @@ -179,7 +172,7 @@ int FontConverter::exec() { (MagnumFontConverter needs TgaImageConverter, for example) */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Text::AbstractFontConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Text::AbstractFontConverter::pluginSearchPaths().back()).second())}; converterManager.registerExternalManager(imageConverterManager); /* Load font converter */ diff --git a/src/Magnum/TextureTools/Atlas.cpp b/src/Magnum/TextureTools/Atlas.cpp index a5b573894..d58768c5b 100644 --- a/src/Magnum/TextureTools/Atlas.cpp +++ b/src/Magnum/TextureTools/Atlas.cpp @@ -83,7 +83,7 @@ Containers::Pair> atlasArrayPowerOfTwo(const Ve CORRADE_ASSERT(size.product() && size.x() == size.y() && (size & (size - Vector2i{1})).isZero(), "TextureTools::atlasArrayPowerOfTwo(): expected size" << i << "to be a non-zero power-of-two square, got" << Debug::packed << size, {}); - sortedSizes[i].xy() = sizes[i]; + sortedSizes[i].xy() = size; sortedSizes[i].z() = i; } diff --git a/src/Magnum/TextureTools/CMakeLists.txt b/src/Magnum/TextureTools/CMakeLists.txt index 114f0f564..f701a6396 100644 --- a/src/Magnum/TextureTools/CMakeLists.txt +++ b/src/Magnum/TextureTools/CMakeLists.txt @@ -27,6 +27,18 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/TextureTools") +# Somehow, due to MagnumTradeObjects having target_include_directories() with +# $, +# if MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS is non-empty then CMake fails +# with +# +# Target "Corrade::PluginManager" not found. +# +# unless the find_package() is here. Not sure why, probably some bug in CMake +# dependency handling? Changing target_include_directories() to PRIVATE doesn't +# help, removing it altogether helps. +find_package(Corrade REQUIRED PluginManager) + set(MagnumTextureTools_GracefulAssert_SRCS Atlas.cpp) @@ -84,25 +96,18 @@ if(MAGNUM_WITH_DISTANCEFIELDCONVERTER) target_link_libraries(magnum-distancefieldconverter PRIVATE Magnum MagnumTextureTools - MagnumTrade) - if(MAGNUM_TARGET_HEADLESS) + MagnumTrade + ${MAGNUM_DISTANCEFIELDCONVERTER_STATIC_PLUGINS}) + if(MAGNUM_TARGET_EGL) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) elseif(CORRADE_TARGET_IOS) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessIosApplication) - elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES) + elseif(CORRADE_TARGET_APPLE) target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessCglApplication) elseif(CORRADE_TARGET_UNIX) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessEglApplication) - else() - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessGlxApplication) - endif() + target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessGlxApplication) elseif(CORRADE_TARGET_WINDOWS) - if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES) - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWindowsEglApplication) - else() - target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWglApplication) - endif() + target_link_libraries(magnum-distancefieldconverter PRIVATE MagnumWindowlessWglApplication) else() message(FATAL_ERROR "magnum-distancefieldconverter is not available on this platform. Set MAGNUM_WITH_DISTANCEFIELDCONVERTER to OFF to suppress this warning.") endif() diff --git a/src/Magnum/TextureTools/DistanceField.cpp b/src/Magnum/TextureTools/DistanceField.cpp index 9865b7dea..841ead75a 100644 --- a/src/Magnum/TextureTools/DistanceField.cpp +++ b/src/Magnum/TextureTools/DistanceField.cpp @@ -25,7 +25,7 @@ #include "DistanceField.h" -#include +#include #include #include @@ -104,7 +104,7 @@ DistanceFieldShader::DistanceFieldShader(const UnsignedInt radius) { frag.addSource(Utility::formatString("#define RADIUS {}\n", radius)) .addSource(rs.getString("DistanceFieldShader.frag")); - CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); attachShaders({vert, frag}); diff --git a/src/Magnum/TextureTools/distancefieldconverter.cpp b/src/Magnum/TextureTools/distancefieldconverter.cpp index 50830ef32..222c0fc4f 100644 --- a/src/Magnum/TextureTools/distancefieldconverter.cpp +++ b/src/Magnum/TextureTools/distancefieldconverter.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove once Arguments is std::string-free */ #include @@ -42,24 +43,16 @@ #include "Magnum/Trade/AbstractImageConverter.h" #include "Magnum/Trade/ImageData.h" -#ifdef MAGNUM_TARGET_HEADLESS +#ifdef MAGNUM_TARGET_EGL #include "Magnum/Platform/WindowlessEglApplication.h" #elif defined(CORRADE_TARGET_IOS) #include "Magnum/Platform/WindowlessIosApplication.h" -#elif defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) +#elif defined(CORRADE_TARGET_APPLE) #include "Magnum/Platform/WindowlessCglApplication.h" #elif defined(CORRADE_TARGET_UNIX) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessEglApplication.h" -#else #include "Magnum/Platform/WindowlessGlxApplication.h" -#endif #elif defined(CORRADE_TARGET_WINDOWS) -#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_DESKTOP_GLES) -#include "Magnum/Platform/WindowlessWindowsEglApplication.h" -#else #include "Magnum/Platform/WindowlessWglApplication.h" -#endif #else #error no windowless application available on this platform #endif @@ -164,14 +157,14 @@ int DistanceFieldConverter::exec() { /* Load importer plugin */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) return 1; /* Load converter plugin */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths()[0])}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; Containers::Pointer converter = converterManager.loadAndInstantiate(args.value("converter")); if(!converter) return 2; diff --git a/src/Magnum/Trade/AbstractImageConverter.cpp b/src/Magnum/Trade/AbstractImageConverter.cpp index 9c51de7c9..fc7bba2c0 100644 --- a/src/Magnum/Trade/AbstractImageConverter.cpp +++ b/src/Magnum/Trade/AbstractImageConverter.cpp @@ -57,7 +57,7 @@ using namespace Containers::Literals; Containers::StringView AbstractImageConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.2"_s +"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.3"_s /* [interface] */ ; } @@ -106,6 +106,40 @@ void AbstractImageConverter::clearFlags(ImageConverterFlags flags) { setFlags(_flags & ~flags); } +Containers::String AbstractImageConverter::extension() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::extension(): file conversion not supported", {}); + + Containers::String out = doExtension(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doExtension() const { return {}; } + +Containers::String AbstractImageConverter::mimeType() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::mimeType(): file conversion not supported", {}); + + Containers::String out = doMimeType(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doMimeType() const { return {}; } + Containers::Optional AbstractImageConverter::convert(const ImageView1D& image) { CORRADE_ASSERT(features() & ImageConverterFeature::Convert1D, "Trade::AbstractImageConverter::convert(): 1D image conversion not supported", {}); @@ -309,7 +343,7 @@ AbstractImageConverter::convertToData(const ImageView1D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView1D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 1D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -342,7 +376,7 @@ AbstractImageConverter::convertToData(const ImageView2D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView2D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 2D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -382,7 +416,7 @@ AbstractImageConverter::convertToData(const ImageView3D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const ImageView3D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): 3D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -415,7 +449,7 @@ AbstractImageConverter::convertToData(const CompressedImageView1D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView1D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 1D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -448,7 +482,7 @@ AbstractImageConverter::convertToData(const CompressedImageView2D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView2D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 2D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -488,7 +522,7 @@ AbstractImageConverter::convertToData(const CompressedImageView3D& image) { } Containers::Optional> AbstractImageConverter::doConvertToData(const CompressedImageView3D& image) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): compressed 3D image conversion advertised but not implemented", {}); return doConvertToData(Containers::arrayView({image})); @@ -589,7 +623,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -628,7 +662,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -667,7 +701,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -706,7 +740,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -745,7 +779,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -784,7 +818,7 @@ Containers::Optional> Implementation::ImageConverterOptionalButAlsoArray #endif AbstractImageConverter::convertToData(const Containers::ArrayView imageLevels) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, + CORRADE_ASSERT(features() >= ImageConverterFeature::Levels, "Trade::AbstractImageConverter::convertToData(): multi-level compressed 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -834,7 +868,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView1D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels1DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert1DToData, "Trade::AbstractImageConverter::convertToFile(): 1D image conversion advertised but not implemented", false); @@ -868,7 +902,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView2D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels2DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert2DToData, "Trade::AbstractImageConverter::convertToFile(): 2D image conversion advertised but not implemented", false); @@ -908,7 +942,7 @@ bool AbstractImageConverter::doConvertToFile(const ImageView3D& image, const Con /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertLevels3DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::Convert3DToData, "Trade::AbstractImageConverter::convertToFile(): 3D image conversion advertised but not implemented", false); @@ -942,7 +976,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView1D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels1DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed1DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 1D image conversion advertised but not implemented", false); @@ -976,7 +1010,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView2D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels2DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed2DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 2D image conversion advertised but not implemented", false); @@ -1016,7 +1050,7 @@ bool AbstractImageConverter::doConvertToFile(const CompressedImageView3D& image, /* Prefer to go through the ToFile variant instead of ToData assuming that it could have a more memory-efficient implementation than having to materialize the whole output in memory first */ - if(features() >= ImageConverterFeature::ConvertCompressedLevels3DToFile) + if(features() >= ImageConverterFeature::Levels) return doConvertToFile(Containers::arrayView({image}), filename); CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed3DToData, "Trade::AbstractImageConverter::convertToFile(): compressed 3D image conversion advertised but not implemented", false); @@ -1052,7 +1086,7 @@ bool AbstractImageConverter::convertToFile(const ImageData3D& image, const Conta } bool AbstractImageConverter::convertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels1DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert1DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1069,7 +1103,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 1D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1084,7 +1118,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels2DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert2DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1101,7 +1135,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 2D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1116,7 +1150,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertLevels3DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::Convert3DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1133,7 +1167,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertLevels3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::Convert3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level 3D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1148,7 +1182,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels1DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed1DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1165,7 +1199,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed1DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 1D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1180,7 +1214,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels2DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed2DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1197,7 +1231,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed2DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 2D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1212,7 +1246,7 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView imageLevels, const Containers::StringView filename) { - CORRADE_ASSERT(features() & ImageConverterFeature::ConvertCompressedLevels3DToFile, + CORRADE_ASSERT(features() >= (ImageConverterFeature::ConvertCompressed3DToFile|ImageConverterFeature::Levels), "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion not supported", {}); #ifndef CORRADE_NO_ASSERT @@ -1229,7 +1263,7 @@ bool AbstractImageConverter::convertToFile(const std::initializer_list imageLevels, Containers::StringView filename) { - CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressedLevels3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion advertised but not implemented", false); + CORRADE_ASSERT(features() >= ImageConverterFeature::ConvertCompressed3DToData, "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion advertised but not implemented", false); const Containers::Optional> data = doConvertToData(imageLevels); /* No deleter checks as it doesn't matter here */ @@ -1244,11 +1278,21 @@ bool AbstractImageConverter::doConvertToFile(const Containers::ArrayView= Debug::Flag::Packed; + + #ifdef MAGNUM_BUILD_DEPRECATED + /* If printing a deprecated flag combination, make it look like the enum + set */ + if((value & ImageConverterFeature::Levels) && (value & ~ImageConverterFeature::Levels)) + return debug << (value & ~ImageConverterFeature::Levels) << Debug::nospace << (packed ? "|Levels" : "|Trade::ImageConverterFeature::Levels"); + #endif + + if(!packed) + debug << "Trade::ImageConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImageConverterFeature::v: return debug << "::" #v; + #define _c(v) case ImageConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(Convert1D) _c(Convert2D) _c(Convert3D) @@ -1267,49 +1311,42 @@ Debug& operator<<(Debug& debug, const ImageConverterFeature value) { _c(ConvertCompressed1DToData) _c(ConvertCompressed2DToData) _c(ConvertCompressed3DToData) - _c(ConvertLevels1DToFile) - _c(ConvertLevels2DToFile) - _c(ConvertLevels3DToFile) - _c(ConvertCompressedLevels1DToFile) - _c(ConvertCompressedLevels2DToFile) - _c(ConvertCompressedLevels3DToFile) - _c(ConvertLevels1DToData) - _c(ConvertLevels2DToData) - _c(ConvertLevels3DToData) - _c(ConvertCompressedLevels1DToData) - _c(ConvertCompressedLevels2DToData) - _c(ConvertCompressedLevels3DToData) + _c(Levels) #undef _c /* LCOV_EXCL_STOP */ + + #ifdef MAGNUM_BUILD_DEPRECATED + /* LCOV_EXCL_START */ + CORRADE_IGNORE_DEPRECATED_PUSH + case ImageConverterFeature::ConvertLevels1DToData: + case ImageConverterFeature::ConvertLevels2DToData: + case ImageConverterFeature::ConvertLevels3DToData: + case ImageConverterFeature::ConvertCompressedLevels1DToData: + case ImageConverterFeature::ConvertCompressedLevels2DToData: + case ImageConverterFeature::ConvertCompressedLevels3DToData: + case ImageConverterFeature::ConvertLevels1DToFile: + case ImageConverterFeature::ConvertLevels2DToFile: + case ImageConverterFeature::ConvertLevels3DToFile: + case ImageConverterFeature::ConvertCompressedLevels1DToFile: + case ImageConverterFeature::ConvertCompressedLevels2DToFile: + case ImageConverterFeature::ConvertCompressedLevels3DToFile: + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + CORRADE_IGNORE_DEPRECATED_POP + /* LCOV_EXCL_STOP */ + #endif } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImageConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::ImageConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::ImageConverterFeatures{}", { ImageConverterFeature::Convert1D, ImageConverterFeature::Convert2D, ImageConverterFeature::Convert3D, ImageConverterFeature::ConvertCompressed1D, ImageConverterFeature::ConvertCompressed2D, ImageConverterFeature::ConvertCompressed3D, - ImageConverterFeature::ConvertLevels1DToData, - ImageConverterFeature::ConvertLevels2DToData, - ImageConverterFeature::ConvertLevels3DToData, - ImageConverterFeature::ConvertCompressedLevels1DToData, - ImageConverterFeature::ConvertCompressedLevels2DToData, - ImageConverterFeature::ConvertCompressedLevels3DToData, - /* These 6 are implied by Convert[Compressed]LevelsToData, so have to - be after */ - ImageConverterFeature::ConvertLevels1DToFile, - ImageConverterFeature::ConvertLevels2DToFile, - ImageConverterFeature::ConvertLevels3DToFile, - ImageConverterFeature::ConvertCompressedLevels1DToFile, - ImageConverterFeature::ConvertCompressedLevels2DToFile, - ImageConverterFeature::ConvertCompressedLevels3DToFile, - /* These 12 are implied by Convert[Compressed]LevelsTo{File,Data}, so - have to be after */ ImageConverterFeature::Convert1DToData, ImageConverterFeature::Convert2DToData, ImageConverterFeature::Convert3DToData, @@ -1323,7 +1360,8 @@ Debug& operator<<(Debug& debug, const ImageConverterFeatures value) { ImageConverterFeature::Convert3DToFile, ImageConverterFeature::ConvertCompressed1DToFile, ImageConverterFeature::ConvertCompressed2DToFile, - ImageConverterFeature::ConvertCompressed3DToFile}); + ImageConverterFeature::ConvertCompressed3DToFile, + ImageConverterFeature::Levels}); } Debug& operator<<(Debug& debug, const ImageConverterFlag value) { diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index 621e1589e..fc819399a 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -242,112 +242,126 @@ enum class ImageConverterFeature: UnsignedInt { ConvertCompressed3DToData = ConvertCompressed3DToFile|(1 << 13), /** - * Convert a set of 1D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert1DToFile. + * Convert multiple image levels with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert1DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed1DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert2DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed2DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) / + * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView) + * if @ref ImageConverterFeature::Convert3DToFile / + * @relativeref{ImageConverterFeature,ConvertCompressed3DToFile} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert1DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed1DToData} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert2DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed2DToData} is also + * supported; with + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) / + * @ref AbstractImageConverter::convertToData(Containers::ArrayView) + * if @ref ImageConverterFeature::Convert3DToData / + * @relativeref{ImageConverterFeature,ConvertCompressed3DToData} is also + * supported. * @m_since_latest */ - ConvertLevels1DToFile = Convert1DToFile|(1 << 14), + Levels = 1 << 14, + #ifdef MAGNUM_BUILD_DEPRECATED /** - * Convert a set of 2D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert2DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert1DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels2DToFile = Convert2DToFile|(1 << 14), + ConvertLevels1DToFile CORRADE_DEPRECATED_ENUM("use Convert1DToFile together with Levels instead") = Convert1DToFile|Levels, /** - * Convert a set of 3D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::Convert3DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert2DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels3DToFile = Convert3DToFile|(1 << 14), + ConvertLevels2DToFile CORRADE_DEPRECATED_ENUM("use Convert2DToFile together with Levels instead") = Convert2DToFile|Levels, /** - * Convert a set of compressed 1D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed1DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert3DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels1DToFile = ConvertCompressed1DToFile|(1 << 14), + ConvertLevels3DToFile CORRADE_DEPRECATED_ENUM("use Convert3DToFile together with Levels instead") = Convert3DToFile|Levels, /** - * Convert a set of compressed 2D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed2DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed1DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels2DToFile = ConvertCompressed2DToFile|(1 << 14), + ConvertCompressedLevels1DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed1DToFile together with Levels instead") = ConvertCompressed1DToFile|Levels, /** - * Convert a set of compressed 3D image levels to a file with - * @ref AbstractImageConverter::convertToFile(Containers::ArrayView, Containers::StringView). - * Implies @ref ImageConverterFeature::ConvertCompressed3DToFile. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed2DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels3DToFile = ConvertCompressed3DToFile|(1 << 14), + ConvertCompressedLevels2DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed2DToFile together with Levels instead") = ConvertCompressed2DToFile|Levels, /** - * Convert a set of 1D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels1DToFile and - * @relativeref{ImageConverterFeature,Convert1DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert1DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed3DToFile together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels1DToData = ConvertLevels1DToFile|Convert1DToData|(1 << 14), + ConvertCompressedLevels3DToFile CORRADE_DEPRECATED_ENUM("use ConvertCompressed3DToFile together with Levels instead") = ConvertCompressed3DToFile|Levels, /** - * Convert a set of 2D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels2DToFile and - * @relativeref{ImageConverterFeature,Convert2DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert2DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert1DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels2DToData = ConvertLevels2DToFile|Convert2DToData|(1 << 14), + ConvertLevels1DToData CORRADE_DEPRECATED_ENUM("use Convert1DToData together with Levels instead") = Convert1DToData|Levels, /** - * Convert a set of 3D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertLevels3DToFile and - * @relativeref{ImageConverterFeature,Convert3DToData}, which implies also - * @relativeref{ImageConverterFeature,Convert3DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert2DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertLevels3DToData = ConvertLevels3DToFile|Convert3DToData|(1 << 14), + ConvertLevels2DToData CORRADE_DEPRECATED_ENUM("use Convert2DToData together with Levels instead") = Convert2DToData|Levels, /** - * Convert a set of compressed 1D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels1DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed1DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed1DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::Convert3DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels1DToData = ConvertCompressedLevels1DToFile|ConvertCompressed1DToData|(1 << 14), + ConvertLevels3DToData CORRADE_DEPRECATED_ENUM("use Convert3DToData together with Levels instead") = Convert3DToData|Levels, /** - * Convert a set of compressed 2D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels2DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed2DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed2DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed1DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels2DToData = ConvertCompressedLevels2DToFile|ConvertCompressed2DToData|(1 << 14), + ConvertCompressedLevels1DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed1DToData together with Levels instead") = ConvertCompressed1DToData|Levels, /** - * Convert a set of compressed 3D image levels to raw data with - * @ref AbstractImageConverter::convertToData(Containers::ArrayView). - * Implies @ref ImageConverterFeature::ConvertCompressedLevels3DToFile and - * @relativeref{ImageConverterFeature,ConvertCompressed3DToData}, which - * implies also @relativeref{ImageConverterFeature,ConvertCompressed3DToFile}. - * @m_since_latest + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed2DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. + */ + ConvertCompressedLevels2DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed2DToData together with Levels instead") = ConvertCompressed2DToData|Levels, + + /** + * @m_deprecated_since_latest Use + * @ref ImageConverterFeature::ConvertCompressed3DToData together with + * @relativeref{ImageConverterFeature,Levels} instead. */ - ConvertCompressedLevels3DToData = ConvertCompressedLevels3DToFile|ConvertCompressed3DToData|(1 << 14) + ConvertCompressedLevels3DToData CORRADE_DEPRECATED_ENUM("use ConvertCompressed3DToData together with Levels instead") = ConvertCompressed3DToData|Levels + #endif }; /** @@ -454,20 +468,9 @@ commonly advertising support for a subset of them via @ref features(): - Saving a set of (compressed) 1D/2D/3D image levels to a file / data using @ref convertToFile() / @ref convertToData(). Common use case is to save already pregenerated levels instead of having to create them during load. - Advertised with @ref ImageConverterFeature::ConvertLevels1DToFile / - @relativeref{ImageConverterFeature,ConvertLevels2DToFile} / - @relativeref{ImageConverterFeature,ConvertLevels3DToFile} or - @ref ImageConverterFeature::ConvertLevels1DToData / - @relativeref{ImageConverterFeature,ConvertLevels2DToData} / - @relativeref{ImageConverterFeature,ConvertLevels3DToData} and - @ref ImageConverterFeature::ConvertCompressedLevels1DToFile / - @relativeref{ImageConverterFeature,ConvertCompressedLevels2DToFile} / - @relativeref{ImageConverterFeature,ConvertCompressedLevels3DToFile} or - @ref ImageConverterFeature::ConvertCompressedLevels1DToData - @relativeref{ImageConverterFeature,ConvertCompressedLevels2DToData} / - @relativeref{ImageConverterFeature,ConvertCompressedLevels3DToData} for - compressed input images. Note that if a plugin advertises those, it's also - capable of saving single images --- in that case the single-image + Advertised with @ref ImageConverterFeature::Levels in addition to the + single-image feature. Note that if a plugin advertises multi-level support, + it's also capable of saving single images --- in that case the single-image @ref convertToFile() / @ref convertToData() delegates to the multi-level variant with just a single image. - Performing an operation on the image data itself using @ref convert(), from @@ -572,36 +575,13 @@ based on what features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: -- The function @ref doConvert(const ImageView2D&) is called only if - @ref ImageConverterFeature::Convert2D is supported and equivalently for the - 1D and 3D case. -- The function @ref doConvert(const CompressedImageView2D&) is called only if - @ref ImageConverterFeature::ConvertCompressed2D is supported and - equivalently for the 1D and 3D case. -- The function @ref doConvertToData(const ImageView2D&) is called only if - @ref ImageConverterFeature::Convert2DToData is supported and equivalently - for the 1D and 3D case. -- The function @ref doConvertToData(Containers::ArrayView) - is called only if @ref ImageConverterFeature::ConvertLevels2DToData is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToData(const CompressedImageView2D&) is called - only if @ref ImageConverterFeature::ConvertCompressed2DToData is supported - and equivalently for the 1D and 3D case. -- The function @ref doConvertToData(Containers::ArrayView) - is called only if @ref ImageConverterFeature::ConvertCompressedLevels2DToData - is supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(const ImageView2D&, Containers::StringView) - is called only if @ref ImageConverterFeature::Convert2DToFile is supported - and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertLevels2DToFile is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(const CompressedImageView2D&, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertCompressed2DToFile is - supported and equivalently for the 1D and 3D case. -- The function @ref doConvertToFile(Containers::ArrayView, Containers::StringView) - is called only if @ref ImageConverterFeature::ConvertCompressedLevels2DToFile - is supported and equivalently for the 1D and 3D case. +- The @ref doExtension() and @ref doMimeType() functions are called only + if @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + or @ref ImageConverterFeature::Convert2DToFile "Convert*ToFile" is + supported +- The @ref doConvert(), @ref doConvertToData() and @ref doConvertToFile() + functions are called only if a corresponding @ref ImageConverterFeature is + supported - All @ref doConvertToData() and @ref doConvertToFile() functions taking a single (compressed) image are called only if the image has a non-zero size in all dimensions and the view is not @cpp nullptr @ce. Note that this does @@ -721,6 +701,38 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ void clearFlags(ImageConverterFlags flags); + /** + * @brief File extension + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized file extension corresponding + * to the file format used, such as @cpp "png" @ce for PNG files. If + * the file format doesn't have a standardized extension, empty string + * is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String extension() const; + + /** + * @brief File MIME type + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized [MIME type](https://en.wikipedia.org/wiki/Media_type) + * corresponding to the file format used, such as @cpp "image/png" @ce + * for PNG files. If the file format doesn't have a standardized MIME + * type, empty string is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String mimeType() const; + /** * @brief Convert a 1D image * @m_since_latest @@ -879,10 +891,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 1D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert1DToData or - * @ref ImageConverterFeature::ConvertLevels1DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size. On failure prints a message to + * Available only if @ref ImageConverterFeature::Convert1DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView1D&), * @ref convertToData(const ImageData1D&), @ref convert(), @@ -899,11 +910,11 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 2D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert2DToData or - * @ref ImageConverterFeature::ConvertLevels2DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size in all dimensions. On failure prints a message to - * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Convert2DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView2D&), * @ref convertToData(const ImageData2D&), @ref convert(), * @ref convertToFile() @@ -928,11 +939,11 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 3D image to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert3DToData or - * @ref ImageConverterFeature::ConvertLevels3DToData is supported. The - * image view is expected to not be @cpp nullptr @ce and to have a - * non-zero size in all dimensions. On failure prints a message to - * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Convert3DToData is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const CompressedImageView3D&), * @ref convertToData(const ImageData3D&), @ref convert(), * @ref convertToFile() @@ -949,9 +960,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed1DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels1DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size. On failure prints a message to + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size. On failure prints a message to * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView1D&), * @ref convertToData(const ImageData1D&), @ref convert(), @@ -969,9 +979,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed2DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels2DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView2D&), @@ -999,9 +1008,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed3DToData - * or @ref ImageConverterFeature::ConvertCompressedLevels3DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns * @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(const ImageView3D&), @@ -1079,14 +1087,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 1D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels1DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size, and all of them sharing the same pixel format and - * layout flags. Note that certain converters may impose additional - * size and order restrictions on the images, see documentation of a - * particular plugin for more information. On failure prints a message - * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert1DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size, and + * all of them sharing the same pixel format and layout flags. Note + * that certain converters may impose additional size and order + * restrictions on the images, see documentation of a particular plugin + * for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1111,15 +1120,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 2D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels2DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert2DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1144,15 +1153,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 3D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels3DToData - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert3DToData} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt. * @see @ref features(), @ref convertToData(Containers::ArrayView), * @ref convert(), @ref convertToFile() */ @@ -1177,8 +1186,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 1D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels1DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed1DToData} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size, and all views sharing the same pixel format and * layout flags. Note that certain converters may impose additional @@ -1209,8 +1219,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 2D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels2DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed2DToData} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1242,8 +1253,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 3D image levels to a raw data * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels3DToData - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed3DToData} is supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1275,11 +1286,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 1D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert1DToFile or - * @ref ImageConverterFeature::Convert1DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size. On failure prints a message to @relativeref{Magnum,Error} and - * returns @cpp false @ce. + * Available only if @ref ImageConverterFeature::Convert1DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const CompressedImageView1D&, Containers::StringView), * @ref convertToFile(const ImageData1D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1290,11 +1300,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 2D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert2DToFile or - * @ref ImageConverterFeature::Convert2DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size in all dimensions. Returns @cpp true @ce on success, - * @cpp false @ce otherwise. + * Available only if @ref ImageConverterFeature::Convert2DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size in all dimensions. Returns @cpp true @ce on + * success, @cpp false @ce otherwise. * @see @ref features(), @ref convertToFile(const CompressedImageView2D&, Containers::StringView), * @ref convertToFile(const ImageData2D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1315,11 +1324,10 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a 3D image to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::Convert3DToFile or - * @ref ImageConverterFeature::Convert3DToData is supported. The image - * view is expected to not be @cpp nullptr @ce and to have a non-zero - * size. On failure prints a message to @relativeref{Magnum,Error} and - * returns @cpp false @ce. + * Available only if @ref ImageConverterFeature::Convert3DToFile is + * supported. The image view is expected to not be @cpp nullptr @ce and + * to have a non-zero size. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const CompressedImageView3D&, Containers::StringView), * @ref convertToFile(const ImageData3D&, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1331,9 +1339,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed1DToFile - * or @ref ImageConverterFeature::ConvertCompressed1DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView1D&, Containers::StringView), * @ref convertToFile(const ImageData1D&, Containers::StringView), @@ -1346,9 +1353,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed2DToFile - * or @ref ImageConverterFeature::ConvertCompressed2DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView2D&, Containers::StringView), * @ref convertToFile(const ImageData2D&, Containers::StringView), @@ -1371,9 +1377,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @m_since_latest * * Available only if @ref ImageConverterFeature::ConvertCompressed3DToFile - * or @ref ImageConverterFeature::ConvertCompressed3DToData is - * supported. The image view is expected to not be @cpp nullptr @ce and - * to have a non-zero size in all dimensions. On failure prints a + * is supported. The image view is expected to not be @cpp nullptr @ce + * and to have a non-zero size in all dimensions. On failure prints a * message to @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(const ImageView3D&, Containers::StringView), * @ref convertToFile(const ImageData3D&, Containers::StringView), @@ -1443,13 +1448,14 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 1D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels1DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size, and all views sharing the same pixel format. Note - * that certain converters may impose additional size and order - * restrictions on the images, see documentation of a particular plugin - * for more information. On failure prints a message to + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert1DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size, and + * all views sharing the same pixel format and layout flags. Note that + * certain converters may impose additional size and order restrictions + * on the images, see documentation of a particular plugin for more + * information. On failure prints a message to * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() @@ -1465,15 +1471,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 2D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels2DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert2DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() */ @@ -1488,15 +1494,15 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of 3D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertLevels3DToFile - * is supported. The function expects at least one image to be passed, - * with each view expected to not be @cpp nullptr @ce, to have a - * non-zero size in all dimensions, and all views sharing the same - * pixel format and layout flags. Note that certain converters may - * impose additional size and order restrictions on the images, see - * documentation of a particular plugin for more information. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,Convert3DToFile} is supported. + * The function expects at least one image to be passed, with each view + * expected to not be @cpp nullptr @ce, to have a non-zero size in all + * dimensions, and all views sharing the same pixel format and layout + * flags. Note that certain converters may impose additional size and + * order restrictions on the images, see documentation of a particular + * plugin for more information. On failure prints a message to + * @relativeref{Magnum,Error} and returns @cpp false @ce. * @see @ref features(), @ref convertToFile(Containers::ArrayView, Containers::StringView), * @ref convert(), @ref convertToData() */ @@ -1511,8 +1517,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 1D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels1DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed1DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size, and all views sharing the same pixel format and * layout flags. Note that certain converters may impose additional @@ -1533,8 +1540,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 2D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels2DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed2DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1556,8 +1564,9 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Convert a set of compressed 3D image levels to a file * @m_since_latest * - * Available only if @ref ImageConverterFeature::ConvertCompressedLevels3DToFile - * is supported. The function expects at least one image to be passed, + * Available only if @ref ImageConverterFeature::Levels together with + * @relativeref{ImageConverterFeature,ConvertCompressed3DToFile} is + * supported. The function expects at least one image to be passed, * with each view expected to not be @cpp nullptr @ce, to have a * non-zero size in all dimensions, and all views sharing the same * pixel format and layout flags. Note that certain converters may @@ -1580,9 +1589,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView1D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert1DToData is supported, default * implementation calls @ref doConvertToData(const ImageView1D&) and @@ -1596,9 +1604,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView2D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels2DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert2DToData is supported, default * implementation calls @ref doConvertToData(const ImageView2D&) and @@ -1612,9 +1619,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const ImageView3D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels3DToFile is supported, - * default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::Convert3DToData is supported, default * implementation calls @ref doConvertToData(const ImageView3D&) and @@ -1628,9 +1634,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView1D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed1DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView1D&) @@ -1645,9 +1650,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView2D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed2DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView2D&) @@ -1662,9 +1666,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(const CompressedImageView3D&, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToFile is - * supported, default implementation calls - * @ref doConvertToFile(Containers::ArrayView, Containers::StringView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToFile(Containers::ArrayView, Containers::StringView) * with just the single @p image. Otherwise, if * @ref ImageConverterFeature::ConvertCompressed3DToData is supported, * default implementation calls @ref doConvertToData(const CompressedImageView3D&) @@ -1679,12 +1682,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) 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. + * If @ref ImageConverterFeature::Convert1DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * 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(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1692,12 +1695,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) 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. + * If @ref ImageConverterFeature::Convert2DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * 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(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1705,12 +1708,12 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls - * @ref doConvertToData(Containers::ArrayView) 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. + * If @ref ImageConverterFeature::Convert3DToData is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) + * 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(Containers::ArrayView imageLevels, Containers::StringView filename); @@ -1718,7 +1721,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToData is + * If @ref ImageConverterFeature::ConvertCompressed1DToData is * supported, default implementation calls * @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this @@ -1732,7 +1735,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToData is + * If @ref ImageConverterFeature::ConvertCompressed2DToData is * supported, default implementation calls @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this * function from your @ref doConvertToFile() implementation, for @@ -1745,7 +1748,7 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToFile(Containers::ArrayView, Containers::StringView) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToData is + * If @ref ImageConverterFeature::ConvertCompressed3DToData is * supported, default implementation calls @ref doConvertToData(Containers::ArrayView) * and saves the result to given file. It is allowed to call this * function from your @ref doConvertToFile() implementation, for @@ -1774,6 +1777,22 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ virtual void doSetFlags(ImageConverterFlags flags); + /** + * @brief Implementation for @ref extension() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doExtension() const; + + /** + * @brief Implementation for @ref mimeType() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doMimeType() const; + /** * @brief Implementation for @ref convert(const ImageView1D&) * @m_since_latest @@ -1814,8 +1833,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView1D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels1DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView1D& image); @@ -1824,8 +1843,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView2D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels2DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView2D& image); @@ -1834,8 +1853,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const ImageView3D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertLevels3DToData is supported, - * default implementation calls @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const ImageView3D& image); @@ -1844,9 +1863,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView1D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels1DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView1D& image); @@ -1855,9 +1873,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView2D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels2DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView2D& image); @@ -1866,9 +1883,8 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract * @brief Implementation for @ref convertToData(const CompressedImageView3D&) * @m_since_latest * - * If @ref ImageConverterFeature::ConvertCompressedLevels3DToData is - * supported, default implementation calls - * @ref doConvertToData(Containers::ArrayView) + * If @ref ImageConverterFeature::Levels is supported, default + * implementation calls @ref doConvertToData(Containers::ArrayView) * with just the single @p image and propagates the result back. */ virtual Containers::Optional> doConvertToData(const CompressedImageView3D& image); diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index f5d012d3c..8bda00fa6 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -429,6 +429,8 @@ Containers::Optional AbstractImporter::animation(const UnsignedIn CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); Containers::Optional animation = doAnimation(id); + /** @todo maybe this should also disallow custom interpolators? since thise + would be dangling on plugin unload */ CORRADE_ASSERT(!animation || ((!animation->_data.deleter() || animation->_data.deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || animation->_data.deleter() == ArrayAllocator::deleter) && (!animation->_tracks.deleter() || animation->_tracks.deleter() == static_cast(Implementation::nonOwnedArrayDeleter))), @@ -1598,11 +1600,14 @@ const void* AbstractImporter::importerState() const { const void* AbstractImporter::doImporterState() const { return nullptr; } Debug& operator<<(Debug& debug, const ImporterFeature value) { - debug << "Trade::ImporterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Trade::ImporterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case ImporterFeature::v: return debug << "::" #v; + #define _c(v) case ImporterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(OpenData) _c(OpenState) _c(FileCallback) @@ -1610,11 +1615,11 @@ Debug& operator<<(Debug& debug, const ImporterFeature value) { /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const ImporterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::ImporterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::ImporterFeatures{}", { ImporterFeature::OpenData, ImporterFeature::OpenState, ImporterFeature::FileCallback}); diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index b72541173..a9bae93eb 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -25,15 +25,23 @@ #include "AbstractSceneConverter.h" +#include #include #include +#include #include +#include #include #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" #ifdef MAGNUM_BUILD_DEPRECATED /* needed by deprecated convertToFile() that takes a std::string */ @@ -54,10 +62,75 @@ namespace Magnum { namespace Trade { using namespace Containers::Literals; +/* Gets allocated in begin*() and deallocated in end*() or abort(). The direct + conversion functions such as convert(const MeshData&) don't directly need + this state, but can indirectly delegate to it, such as when + convert(const MeshData&) is emulated with a sequence of begin(), + add(const MeshData&) and end(). */ +struct AbstractSceneConverter::State { + enum class Type { + Convert, + ConvertToData, + ConvertToFile + }; + + explicit State(Type type): type{type} { + if(type == Type::Convert) + new(&converted.mesh) Containers::Optional{}; + else if(type == Type::ConvertToData) + new(&converted.meshToData) Containers::Optional>{}; + else if(type == Type::ConvertToFile) + new(&converted.meshToFile) bool{}; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + ~State() { + if(type == Type::Convert) + converted.mesh.~Optional(); + else if(type == Type::ConvertToData) + converted.meshToData.~Optional(); + else if(type == Type::ConvertToFile) + ; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + Type type; + + UnsignedInt sceneCount = 0; + UnsignedInt animationCount = 0; + UnsignedInt lightCount = 0; + UnsignedInt cameraCount = 0; + UnsignedInt skin2DCount = 0; + UnsignedInt skin3DCount = 0; + UnsignedInt meshCount = 0; + UnsignedInt materialCount = 0; + UnsignedInt textureCount = 0; + UnsignedInt image1DCount = 0; + UnsignedInt image2DCount = 0; + UnsignedInt image3DCount = 0; + + /* Used if type == Type::ConvertToFile. Could theoretically be in the same + allocation as State (ArrayTuple?), or at least reusing the space in + `converted`, but I don't think a single allocation matters that much. */ + Containers::String filename; + + union Converted { + /* C++, FUCK OFF, what's the point of requiring me to create an + explicit constructor and destructor if I have no way to store or + know the information about the active field at this point?! */ + Converted() noexcept {} + ~Converted() {} + + Containers::Optional mesh; + Containers::Optional> meshToData; + bool meshToFile; + } converted; +}; + Containers::StringView AbstractSceneConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2"_s +"cz.mosra.magnum.Trade.AbstractSceneConverter/0.2"_s /* [interface] */ ; } @@ -115,6 +188,8 @@ void AbstractSceneConverter::clearFlags(SceneConverterFlags flags) { } Containers::Optional AbstractSceneConverter::convert(const MeshData& mesh) { + abort(); + CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMesh, "Trade::AbstractSceneConverter::convert(): mesh conversion not supported", {}); @@ -132,6 +207,8 @@ Containers::Optional AbstractSceneConverter::doConvert(const MeshData& } bool AbstractSceneConverter::convertInPlace(MeshData& mesh) { + abort(); + CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshInPlace, "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported", {}); @@ -148,19 +225,31 @@ Containers::Optional> Implementation::SceneConverterOptionalButAlsoArray #endif AbstractSceneConverter::convertToData(const MeshData& mesh) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, - "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); + abort(); - Containers::Optional> out = doConvertToData(mesh); - CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, - "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); + if(features() >= SceneConverterFeature::ConvertMeshToData) { + Containers::Optional> out = doConvertToData(mesh); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); - /* GCC 4.8 needs an explicit conversion here */ - #ifdef MAGNUM_BUILD_DEPRECATED - return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; - #else - return out; - #endif + /* GCC 4.8 needs an explicit conversion here */ + #ifdef MAGNUM_BUILD_DEPRECATED + return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; + #else + return out; + #endif + + } else if(features() >= (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::AddMeshes)) { + beginData(); + + if(add(mesh)) return endData(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return {}; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); } Containers::Optional> AbstractSceneConverter::doConvertToData(const MeshData&) { @@ -168,10 +257,22 @@ Containers::Optional> AbstractSceneConverter::doConvertT } bool AbstractSceneConverter::convertToFile(const MeshData& mesh, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToFile, - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); + abort(); + + if(features() >= SceneConverterFeature::ConvertMeshToFile) { + return doConvertToFile(mesh, filename); + + } else if(features() & (SceneConverterFeature::ConvertMultipleToFile|SceneConverterFeature::AddMeshes)) { + beginFile(filename); - return doConvertToFile(mesh, filename); + if(add(mesh)) return endFile(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return false; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -183,42 +284,976 @@ bool AbstractSceneConverter::convertToFile(const std::string& filename, const Me bool AbstractSceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented", false); - const Containers::Optional> data = doConvertToData(mesh); + const Containers::Optional> out = doConvertToData(mesh); + /* No deleter checks as it doesn't matter here */ + if(!out) return false; + + if(!Utility::Path::write(filename, *out)) { + Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + return false; + } + + return true; +} + +bool AbstractSceneConverter::isConverting() const { + return !!_state; +} + +void AbstractSceneConverter::abort() { + if(!_state) return; + + doAbort(); + _state = {}; +} + +void AbstractSceneConverter::doAbort() {} + +bool AbstractSceneConverter::begin() { + abort(); + + _state.emplace(State::Type::Convert); + + if(features() >= SceneConverterFeature::ConvertMultiple) { + if(!doBegin()) { + _state = {}; + return false; + } + + return true; + + } else if(features() & SceneConverterFeature::ConvertMesh) { + /* Actual operation performed in doAdd(const MeshData&) */ + return true; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", {}); +} + +bool AbstractSceneConverter::doBegin() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature advertised but not implemented", {}); +} + +Containers::Pointer AbstractSceneConverter::end() { + CORRADE_ASSERT(_state && _state->type == State::Type::Convert, + "Trade::AbstractSceneConverter::end(): no conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() & SceneConverterFeature::ConvertMesh) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh"; + return {}; + } + + struct SingleMeshImporter: AbstractImporter { + explicit SingleMeshImporter(Containers::Optional&& mesh) noexcept: _mesh{std::move(mesh)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + _opened = false; + _mesh = {}; + } + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + /* To avoid complicated logic (such as returning non-owned + data and then having to specify the lifetime guarantees), + the mesh can be retrieved only once. Second time it's an + error. Another option would be to behave like if the + importer is closed afterwards, but that would result in + assertions which isn't nice. */ + if(!_mesh) { + Error{} << "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh"; + return {}; + } + + Containers::Optional out = std::move(_mesh); + _mesh = {}; + return out; + } + + private: + bool _opened = true; + Containers::Optional _mesh; + }; + + return Containers::Pointer(new SingleMeshImporter{std::move(_state->converted.mesh)}); + + } else if(features() & SceneConverterFeature::ConvertMultiple) { + return doEnd(); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Pointer AbstractSceneConverter::doEnd() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::end(): feature advertised but not implemented", {}); +} + +bool AbstractSceneConverter::beginData() { + abort(); + + _state.emplace(State::Type::ConvertToData); + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + if(!doBeginData()) { + _state = {}; + return false; + } + + return true; + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + /* Actual operation performed in doAdd(const MeshData&) */ + return true; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", {}); +} + +bool AbstractSceneConverter::doBeginData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented", {}); +} + +Containers::Optional> AbstractSceneConverter::endData() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToData, + "Trade::AbstractSceneConverter::endData(): no data conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + Containers::Optional> out = doEndData(); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter", {}); + + return out; + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh"; + return {}; + } + + /* No deleter validity checks here, those were performed in + convertToData(const MeshData&) already */ + + return std::move(_state->converted.meshToData); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Optional> AbstractSceneConverter::doEndData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::endData(): feature advertised but not implemented", {}); +} + +bool AbstractSceneConverter::beginFile(const Containers::StringView filename) { + abort(); + + _state.emplace(State::Type::ConvertToFile); + _state->filename = Containers::String::nullTerminatedGlobalView(filename); + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + if(!doBeginFile(_state->filename)) { + _state = {}; + return false; + } + + return true; + + } else if(features() >= SceneConverterFeature::ConvertMeshToFile) { + /* Actual operation performed in doAdd(const MeshData&) */ + return true; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", {}); +} + +bool AbstractSceneConverter::doBeginFile(Containers::StringView) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", {}); + + return doBeginData(); +} + +bool AbstractSceneConverter::endFile() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToFile, + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + return doEndFile(_state->filename); + + } else if(features() & SceneConverterFeature::ConvertMeshToFile) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got" << _state->meshCount; + return {}; + } + + return _state->converted.meshToFile; + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool AbstractSceneConverter::doEndFile(const Containers::StringView filename) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented", {}); + + const Containers::Optional> data = doEndData(); /* No deleter checks as it doesn't matter here */ if(!data) return false; if(!Utility::Path::write(filename, *data)) { - Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + Error{} << "Trade::AbstractSceneConverter::endFile(): cannot write to file" << filename; return false; } return true; } +UnsignedInt AbstractSceneConverter::sceneCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress", {}); + return _state->sceneCount; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::add(): scene conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->sceneCount, scene, name)) + return _state->sceneCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene) { + return add(scene, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SceneData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setSceneFieldName(const SceneField field, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress", ); + CORRADE_ASSERT(isSceneFieldCustom(field), + "Trade::AbstractSceneConverter::setSceneFieldName():" << field << "is not custom", ); + + doSetSceneFieldName(sceneFieldCustom(field), name); +} + +void AbstractSceneConverter::doSetSceneFieldName(UnsignedInt, Containers::StringView) {} + +void AbstractSceneConverter::setObjectName(const UnsignedLong object, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setObjectName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress", ); + + doSetObjectName(object, name); +} + +void AbstractSceneConverter::doSetObjectName(UnsignedLong, Containers::StringView) {} + +void AbstractSceneConverter::setDefaultScene(const UnsignedInt id) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress", ); + CORRADE_ASSERT(id < _state->sceneCount, + "Trade::AbstractSceneConverter::setDefaultScene(): index" << id << "out of range for" << _state->sceneCount << "scenes", ); + + doSetDefaultScene(id); +} + +void AbstractSceneConverter::doSetDefaultScene(UnsignedInt) {} + +UnsignedInt AbstractSceneConverter::animationCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::animationCount(): no conversion in progress", {}); + return _state->animationCount; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddAnimations, + "Trade::AbstractSceneConverter::add(): animation conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->animationCount, animation, name)) + return _state->animationCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation) { + return add(animation, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const AnimationData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::lightCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::lightCount(): no conversion in progress", {}); + return _state->lightCount; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddLights, + "Trade::AbstractSceneConverter::add(): light conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->lightCount, light, name)) + return _state->lightCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light) { + return add(light, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const LightData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::cameraCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress", {}); + return _state->cameraCount; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddCameras, + "Trade::AbstractSceneConverter::add(): camera conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->cameraCount, camera, name)) + return _state->cameraCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera) { + return add(camera, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const CameraData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress", {}); + return _state->skin2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins2D, + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin2DCount, skin, name)) + return _state->skin2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress", {}); + return _state->skin3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins3D, + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin3DCount, skin, name)) + return _state->skin3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::meshCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::meshCount(): no conversion in progress", {}); + return _state->meshCount; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(features() >= SceneConverterFeature::AddMeshes) { + if(!doAdd(_state->meshCount, mesh, name)) return {}; + + } else if(features() & (SceneConverterFeature::ConvertMesh| + SceneConverterFeature::ConvertMeshToData| + SceneConverterFeature::ConvertMeshToFile)) { + if(_state->meshCount != 0) { + Error{} << "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got" << _state->meshCount + 1; + return {}; + } + + if(_state->type == State::Type::Convert) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMesh); + if(!(_state->converted.mesh = doConvert(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToData) { + CORRADE_INTERNAL_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData); + if(!(_state->converted.meshToData = doConvertToData(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToFile) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMeshToFile); + if(!(_state->converted.meshToFile = doConvertToFile(mesh, _state->filename))) + return {}; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): mesh conversion not supported", {}); + + return _state->meshCount++; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh) { + return add(mesh, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::MeshLevels, "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{mesh}, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels, const Containers::StringView name) { + CORRADE_ASSERT(features() >= (SceneConverterFeature::AddMeshes|SceneConverterFeature::MeshLevels), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + CORRADE_ASSERT(!meshLevels.isEmpty(), + "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified", false); + + if(doAdd(_state->meshCount, meshLevels, name)) + return _state->meshCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels) { + return add(meshLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setMeshAttributeName(const MeshAttribute attribute, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMeshes, + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress", ); + CORRADE_ASSERT(isMeshAttributeCustom(attribute), + "Trade::AbstractSceneConverter::setMeshAttributeName():" << attribute << "is not custom", ); + + doSetMeshAttributeName(meshAttributeCustom(attribute), name); +} + +void AbstractSceneConverter::doSetMeshAttributeName(UnsignedShort, Containers::StringView) {} + +UnsignedInt AbstractSceneConverter::materialCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::materialCount(): no conversion in progress", {}); + return _state->materialCount; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMaterials, + "Trade::AbstractSceneConverter::add(): material conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->materialCount, material, name)) + return _state->materialCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material) { + return add(material, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const MaterialData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::textureCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::textureCount(): no conversion in progress", {}); + return _state->textureCount; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddTextures, + "Trade::AbstractSceneConverter::add(): texture conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->textureCount, texture, name)) + return _state->textureCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture) { + return add(texture, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const TextureData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented", {}); +} + +#ifndef CORRADE_NO_ASSERT +namespace { + +template bool checkImageValidity(const char* const messagePrefix, const ImageData& image) { + /* At some point there might be a file format that allows zero-sized + images, but so far I don't know about any. When such format appears, + this check will get moved to plugin implementations that can't work with + zero-sized images. + + Also note that this check isn't done for the Image->Image conversion + above, there zero-sized images and nullptr *could* make sense. */ + CORRADE_ASSERT(image.size().product(), + messagePrefix << "can't add image with a zero size:" << image.size(), false); + CORRADE_ASSERT(image.data(), + messagePrefix << "can't add image with a nullptr view", false); + return true; +} + +template bool checkImageValidity(const char* const messagePrefix, const Containers::Iterable> imageLevels) { + CORRADE_ASSERT(!imageLevels.isEmpty(), + messagePrefix << "at least one image level has to be specified", false); + + const bool isCompressed = imageLevels[0].isCompressed(); + const PixelFormat format = isCompressed ? PixelFormat{} : imageLevels[0].format(); + const UnsignedInt formatExtra = isCompressed ? 0 : imageLevels[0].formatExtra(); + const CompressedPixelFormat compressedFormat = isCompressed ? imageLevels[0].compressedFormat() : CompressedPixelFormat{}; + const ImageFlags flags = imageLevels[0].flags(); + /* Going through *all* levels although the format assertion is never fired + in the first iteration in order to properly check also the first one for + zero size / nullptr. */ + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + CORRADE_ASSERT(imageLevels[i].size().product(), + messagePrefix << "can't add image level" << i << "with a zero size:" << imageLevels[i].size(), false); + CORRADE_ASSERT(imageLevels[i].data(), + messagePrefix << "can't add image level" << i << "with a nullptr view", false); + CORRADE_ASSERT(imageLevels[i].isCompressed() == isCompressed, + messagePrefix << "image level" << i << (isCompressed ? "is not" : "is") << "compressed but previous" << (isCompressed ? "are" : "aren't"), false); + if(!isCompressed) { + CORRADE_ASSERT(imageLevels[i].format() == format, + messagePrefix << "image levels don't have the same format, expected" << format << "but got" << imageLevels[i].format() << "for level" << i, false); + CORRADE_ASSERT(imageLevels[i].formatExtra() == formatExtra, + messagePrefix << "image levels don't have the same extra format field, expected" << formatExtra << "but got" << imageLevels[i].formatExtra() << "for level" << i, false); + } else { + CORRADE_ASSERT(imageLevels[i].compressedFormat() == compressedFormat, + messagePrefix << "image levels don't have the same format, expected" << compressedFormat << "but got" << imageLevels[i].compressedFormat() << "for level" << i, false); + } + CORRADE_ASSERT(imageLevels[i].flags() == flags, + messagePrefix << "image levels don't have the same flags, expected" << flags << "but got" << imageLevels[i].flags() << "for level" << i, false); + } + + return true; +} + +} +#endif + +UnsignedInt AbstractSceneConverter::image1DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress", {}); + return _state->image1DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image1DCount, image, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image1DCount, imageLevels, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress", {}); + return _state->image2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image2DCount, image, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image2DCount, imageLevels, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress", {}); + return _state->image3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image3DCount, image, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image3DCount, imageLevels, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data(), image.flags()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + Debug& operator<<(Debug& debug, const SceneConverterFeature value) { - debug << "Trade::SceneConverterFeature" << Debug::nospace; + const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; + + if(!packed) + debug << "Trade::SceneConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ - #define _c(v) case SceneConverterFeature::v: return debug << "::" #v; + #define _c(v) case SceneConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ConvertMesh) _c(ConvertMeshInPlace) _c(ConvertMeshToData) _c(ConvertMeshToFile) + _c(ConvertMultiple) + _c(ConvertMultipleToData) + _c(ConvertMultipleToFile) + _c(AddScenes) + _c(AddAnimations) + _c(AddLights) + _c(AddCameras) + _c(AddSkins2D) + _c(AddSkins3D) + _c(AddMeshes) + _c(AddMaterials) + _c(AddTextures) + _c(AddImages1D) + _c(AddImages2D) + _c(AddImages3D) + _c(AddCompressedImages1D) + _c(AddCompressedImages2D) + _c(AddCompressedImages3D) + _c(MeshLevels) + _c(ImageLevels) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFeatures{}", { + return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::SceneConverterFeatures{}", { SceneConverterFeature::ConvertMesh, SceneConverterFeature::ConvertMeshInPlace, SceneConverterFeature::ConvertMeshToData, /* Implied by ConvertMeshToData, has to be after */ - SceneConverterFeature::ConvertMeshToFile}); + SceneConverterFeature::ConvertMeshToFile, + SceneConverterFeature::ConvertMultiple, + SceneConverterFeature::ConvertMultipleToData, + /* Implied by ConvertMultipleToData, has to be after */ + SceneConverterFeature::ConvertMultipleToFile, + SceneConverterFeature::AddScenes, + SceneConverterFeature::AddAnimations, + SceneConverterFeature::AddLights, + SceneConverterFeature::AddCameras, + SceneConverterFeature::AddSkins2D, + SceneConverterFeature::AddSkins3D, + SceneConverterFeature::AddMeshes, + SceneConverterFeature::AddMaterials, + SceneConverterFeature::AddTextures, + SceneConverterFeature::AddImages1D, + SceneConverterFeature::AddImages2D, + SceneConverterFeature::AddImages3D, + SceneConverterFeature::AddCompressedImages1D, + SceneConverterFeature::AddCompressedImages2D, + SceneConverterFeature::AddCompressedImages3D, + SceneConverterFeature::MeshLevels, + SceneConverterFeature::ImageLevels}); } Debug& operator<<(Debug& debug, const SceneConverterFlag value) { diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index f5cac1b5b..984cc1262 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -30,6 +30,7 @@ * @m_since{2020,06} */ +#include #include #include "Magnum/Magnum.h" @@ -53,31 +54,214 @@ namespace Magnum { namespace Trade { @see @ref SceneConverterFeatures, @ref AbstractSceneConverter::features() */ -enum class SceneConverterFeature: UnsignedByte { +enum class SceneConverterFeature: UnsignedInt { /** - * Convert a mesh with - * @ref AbstractSceneConverter::convert(const MeshData&). + * Convert a single mesh instance with + * @ref AbstractSceneConverter::convert(const MeshData&). The function can + * be also used if both @ref SceneConverterFeature::ConvertMultiple and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMesh = 1 << 0, /** - * Convert a mesh in-place with + * Convert a single mesh instance in-place with * @ref AbstractSceneConverter::convertInPlace(MeshData&). */ ConvertMeshInPlace = 1 << 1, /** - * Converting a mesh to a file with - * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). + * Convert a single mesh instance to a file with + * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). The function can be also used if both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMeshToFile = 1 << 2, /** - * Converting a mesh to raw data with + * Convert a single mesh instance to raw data with * @ref AbstractSceneConverter::convertToData(const MeshData&). Implies - * @ref SceneConverterFeature::ConvertMeshToFile. + * @ref SceneConverterFeature::ConvertMeshToFile. The function can be also + * used if both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes are supported. */ - ConvertMeshToData = ConvertMeshToFile|(1 << 3) + ConvertMeshToData = ConvertMeshToFile|(1 << 3), + + /** + * Convert multiple data with + * @ref AbstractSceneConverter::begin() and + * @relativeref{AbstractSceneConverter,end()}. + * @m_since_latest + */ + ConvertMultiple = 1 << 4, + + /** + * Convert multiple data to a file with + * @ref AbstractSceneConverter::beginFile() and + * @relativeref{AbstractSceneConverter,endFile()}. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToFile is supported. + * @m_since_latest + */ + ConvertMultipleToFile = 1 << 5, + + /** + * Convert multiple data to raw data with + * @ref AbstractSceneConverter::beginData() and + * @relativeref{AbstractSceneConverter,endData()}. Implies + * @ref SceneConverterFeature::ConvertMultipleToFile. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + */ + ConvertMultipleToData = ConvertMultipleToFile|(1 << 6), + + /** + * Add scene instances with + * @ref AbstractSceneConverter::add(const SceneData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setSceneFieldName()}, + * @relativeref{AbstractSceneConverter,setObjectName()} and + * @relativeref{AbstractSceneConverter,setDefaultScene()}. + * @m_since_latest + */ + AddScenes = 1 << 7, + + /** + * Add animation instances with + * @ref AbstractSceneConverter::add(const AnimationData&, Containers::StringView). + * @m_since_latest + */ + AddAnimations = 1 << 8, + + /** + * Add light instances with + * @ref AbstractSceneConverter::add(const LightData&, Containers::StringView). + * @m_since_latest + */ + AddLights = 1 << 9, + + /** + * Add camera instances with + * @ref AbstractSceneConverter::add(const CameraData&, Containers::StringView). + * @m_since_latest + */ + AddCameras = 1 << 10, + + /** + * Add 2D skin instances with + * @ref AbstractSceneConverter::add(const SkinData2D&, Containers::StringView). + * @m_since_latest + */ + AddSkins2D = 1 << 11, + + /** + * Add 3D skin instances with + * @ref AbstractSceneConverter::add(const SkinData3D&, Containers::StringView). + * @m_since_latest + */ + AddSkins3D = 1 << 12, + + /** + * Add single-level mesh instances with + * @ref AbstractSceneConverter::add(const MeshData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setMeshAttributeName()}. + * This function can be also used if + * @ref SceneConverterFeature::ConvertMesh, + * @ref SceneConverterFeature::ConvertMeshToFile or + * @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + * @see @ref SceneConverterFeature::MeshLevels + */ + AddMeshes = 1 << 13, + + /** + * Add material instances with + * @ref AbstractSceneConverter::add(const MaterialData&, Containers::StringView). + * @m_since_latest + */ + AddMaterials = 1 << 14, + + /** + * Add texture instances with + * @ref AbstractSceneConverter::add(const TextureData&, Containers::StringView). + * @m_since_latest + */ + AddTextures = 1 << 15, + + /** + * Add single-level 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages1D = 1 << 16, + + /** + * Add single-level 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages2D = 1 << 17, + + /** + * Add single-level 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages3D = 1 << 18, + + /** + * Add single-level compressed 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages1D = 1 << 19, + + /** + * Add single-level compressed 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages2D = 1 << 20, + + /** + * Add single-level compressed 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages3D = 1 << 21, + + /** + * Add multiple mesh levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddMeshes is also supported. + * @m_since_latest + */ + MeshLevels = 1 << 22, + + /** + * Add multiple image levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is also + * supported; with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is also + * supported; or with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is also + * supported. + * @m_since_latest + */ + ImageLevels = 1 << 23 }; /** @@ -162,15 +346,16 @@ namespace Implementation { Provides functionality for converting meshes and other scene data between various formats or performing optimizations and other operations on them. -The interface supports three main kinds of operation, with implementations -advertising support for a subset of them via @ref features(): +The interface provides a direct and a batch interface, with implementations +advertising support for a subset of them via @ref features(). The direct +interface has three main kinds of operation: - Saving a mesh to a file / data using @ref convertToFile(const MeshData&, Containers::StringView) / @ref convertToData(const MeshData&). This is mostly for exporting the mesh data to a common format like OBJ or PLY in order to be used with an external tool. Advertised with @ref SceneConverterFeature::ConvertMeshToFile - or @ref SceneConverterFeature::ConvertMeshToData + / @relativeref{SceneConverterFeature,ConvertMeshToData}. - Performing an operation on the mesh data itself using @ref convert(const MeshData&), from which you get a @ref MeshData again. This includes operations like mesh decimation or topology cleanup. @@ -181,23 +366,48 @@ advertising support for a subset of them via @ref features(): shuffle the data around. Advertised with @ref SceneConverterFeature::ConvertMeshInPlace. +The batch interface allows converting a whole scene consisting of multiple +meshes, materials, images, ... instead of a single mesh. Similarly, it has two +main kinds of operation: + +- Saving a scene to a file / data using @ref beginFile() / @ref beginData() + and @ref endFile() / @ref endData(). This is the usual process for + exporting a whole scene to file formats such as glTF. Advertised with + @ref SceneConverterFeature::ConvertMultipleToFile / + @relativeref{SceneConverterFeature,ConvertMultipleToData}. +- Performing an operation on the whole scene using @ref begin() and + @ref end(), getting a live @ref AbstractImporter instance as a result. This + includes more complex operations such as scale-aware mesh decimation or + texture downsampling, joining meshes with the same material, or exploding + large meshes into smaller and easier to cull chunks. Advertised with + @ref SceneConverterFeature::ConvertMultiple. + +The actual data is then supplied to the converter in between the begin and end +calls using various @ref add() overloads and related APIs. Support for +particular data types is then advertised with +@ref SceneConverterFeature::AddScenes, +@relativeref{SceneConverterFeature,AddMeshes}, +@relativeref{SceneConverterFeature,AddCameras}, +@relativeref{SceneConverterFeature,AddImages2D} etc. + @section Trade-AbstractSceneConverter-usage Usage Scene converters are commonly implemented as plugins, which means the concrete converter implementation is loaded and instantiated through a @relativeref{Corrade,PluginManager::Manager}. Then, based on the intent and on -what the particular converter supports, @ref convertToFile(), -@ref convertToData(), @ref convert() or @ref convertInPlace() gets called. +what the particular converter supports, either a single-mesh conversion with +@ref convert() and related functions is performed, or a whole-scene conversion +with @ref begin(), various @ref add() APIs and @ref end() is done. -As each converter has different requirements on the input data layout and -vertex formats, you're expected to perform error handling on the application -side --- if a conversion fails, you get an empty +As each converter has different requirements on the input data, their layout +and formats, you're expected to perform error handling on the application side +--- if a conversion fails, you get an empty @relativeref{Corrade,Containers::Optional} or @cpp false @ce and a reason printed to @relativeref{Magnum,Error}. Everything else (using a feature not implemented in the converter, ...) is treated as a programmer error and will produce the usual assertions. -@subsection Trade-AbstractSceneConverter-usage-file Saving a mesh to a file +@subsection Trade-AbstractSceneConverter-usage-mesh-file Converting a single mesh to a file In the following example a mesh is saved to a PLY file using the @ref AnySceneConverter plugin, together with all needed error handling. In this @@ -219,7 +429,7 @@ scene converter plugins. that exposes functionality of all scene converter plugins through a command line interface. -@subsection Trade-AbstractSceneConverter-usage-mesh Converting mesh data +@subsection Trade-AbstractSceneConverter-usage-mesh-data Converting a single mesh data In the following snippet we use the @ref MeshOptimizerSceneConverter to perform a set of optimizations on the mesh to make it render faster. While @@ -234,7 +444,7 @@ of configuration options to specify what actually gets done and how, and the default setup may not even do anything. See @ref plugins-configuration for details and a usage example. -@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting mesh data in-place +@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting a single mesh data in-place Certain operations such as buffer reordering can be performed by directly modifying the input data instead of having to allocate a copy of the whole @@ -249,6 +459,16 @@ following: @snippet MagnumTrade.cpp AbstractSceneConverter-usage-mesh-in-place +@subsection Trade-AbstractSceneConverter-usage-multiple Converting multiple data + +While the operations shown above are convenient enough for simple cases +involving just a single mesh, general conversion of a whole scene needs much +more than that. Such conversion is done in a batch way --- first initializing +the conversion of desired kind, adding particular data, and finalizing the +conversion together with getting the output back. + +@todoc usage example once a batch converter exists + @section Trade-AbstractSceneConverter-data-dependency Data dependency The instances returned from various functions *by design* have no dependency on @@ -263,8 +483,11 @@ plugin module has been unloaded. @section Trade-AbstractSceneConverter-subclassing Subclassing The plugin needs to implement the @ref doFeatures() function and one or more of -@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData() or -@ref doConvertToFile() functions based on what features are supported. +@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData(), +@ref doConvertToFile() functions based on what single-mesh conversion features +are supported, or pairs of @ref doBegin() / @ref doEnd(), @ref doBeginData() / +@ref doEndData(), @ref doBeginFile() / @ref doEndFile() functions and one or +more @ref doAdd() functions based on what multiple-data features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: @@ -278,6 +501,62 @@ checked by the implementation: - The @ref doConvertToFile(const MeshData&, Containers::StringView) function is called only if @ref SceneConverterFeature::ConvertMeshToFile is supported. +- The @ref doBegin() and @ref doEnd() functions are called only if + @ref SceneConverterFeature::ConvertMultiple is supported. +- The @ref doBeginData() and @ref doEndData() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToData is supported. +- The @ref doBeginFile() and @ref doEndFile() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToFile is supported. +- The @ref doEnd(), @ref doEndData(), @ref doEndFile(), @ref doAbort() and + @ref doAdd() functions are called only if a corresponding @ref begin(), + @ref beginData() or @ref beginFile() was called before and @ref abort() + wasn't called in the meantime. +- The @ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData(), + @ref doConvertToFile(), @ref doBegin(), @ref doBeginData() and + @ref doBeginFile() functions are called only after the previous conversion + (if any) was aborted with @ref doAbort(). +- The @ref doAdd() and various `doSet*()` functions are called only if a + corresponding @ref SceneConverterFeature is supported. +- The @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + function is called only if the list has at least one mesh +- All @ref doAdd() functions taking a single image are called only if the + image has a non-zero size in all dimensions and the data is not + @cpp nullptr @ce. +- All @ref doAdd() functions taking multiple images are called only if the + list has at least one image, each of the images has a non-zero size, the + data are not @cpp nullptr @ce and additionally all images are either + uncompressed or all compressed and they have the same pixel format. + Since file formats have varying requirements on image level sizes and their + order and some don't impose any requirements at all, the plugin + implementation is expected to check the sizes on its own. + +For user convenience it's possible to use a single-mesh converter through the +multi-mesh interface as well as use a multi-mesh converter through the +single-mesh interface. The base class can proxy one to the other and does all +necessary edge-case checks: + +- If you have a multi-mesh interface implemented using @ref doBeginFile() / + @ref doBeginData(), @ref doEndFile() / @ref doEndData() and + @ref doAdd(UnsignedInt, const MeshData&, Containers::StringView), it's + automatically delegated to from + @ref convertToFile(const MeshData&, Containers::StringView) + and @ref convertToData(const MeshData&). +- If you have a single-mesh interface implemented using + @ref doConvert(const MeshData&) / @ref doConvertToFile(const MeshData&, Containers::StringView) + / @ref doConvertToData(const MeshData&), it's automatically delegated to + from @ref begin() / @ref beginFile() / @ref beginData(), + @ref end() / @ref endFile() / @ref endData() and + @ref add(const MeshData&, Containers::StringView), succeeding only if + exactly one mesh is added. +- As an exception, a multi-mesh interface implemented using @ref doBegin() + and @ref doEnd() can't be automatically delegated to from + @ref convert(const MeshData&), as the returned @ref AbstractImporter + instance is allowed to have any number of meshes and the delegation logic + would be too complex. In that case, you need to implement + @ref doConvert(const MeshData&) and advertise + @ref SceneConverterFeature::ConvertMesh in @ref doFeatures() yourself. + + @m_class{m-block m-warning} @@ -289,6 +568,10 @@ checked by the implementation: could cause dangling function pointer call on array destruction if the plugin gets unloaded before the array is destroyed. This is asserted by the base implementation on return. +@par + The only exception is the @ref AbstractImporter instance returned by + @ref end() --- since its implementation is in the plugin module itself, the + plugin can't be unloaded until the returned instance is destroyed. */ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::AbstractManagingPlugin { public: @@ -373,36 +656,49 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh * - * Depending on the plugin, can perform for example vertex format - * conversion, overdraw optimization or decimation / subdivision. - * Available only if @ref SceneConverterFeature::ConvertMesh is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @ref Containers::NullOpt. - * @see @ref features(), @ref convertInPlace(MeshData&) + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMesh is supported. + * If @ref SceneConverterFeature::AddMeshes is supported instead, you + * have to use @ref begin(), @ref add(const MeshData&, Containers::StringView) + * and retrieve the output from the importer returned by @ref end() --- + * in such case the process can also return zero or more than one mesh + * instead of always exactly one. + * @see @ref isConverting(), @ref features(), + * @ref convertInPlace(MeshData&) */ Containers::Optional convert(const MeshData& mesh); /** * @brief Convert a mesh in-place * - * Depending on the plugin, can perform for example index buffer - * reordering for better vertex cache use or overdraw optimization. - * Available only if @ref SceneConverterFeature::ConvertMeshInPlace is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. - * @see @ref features(), @ref convert(const MeshData&) + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * + * Expects that @ref SceneConverterFeature::ConvertMeshInPlace is + * supported. + * @see @ref isConverting(), @ref features(), + * @ref convert(const MeshData&) */ bool convertInPlace(MeshData& mesh); /** * @brief Convert a mesh to a raw data * - * Depending on the plugin, can convert the mesh to a file format that - * can be saved to disk. Available only if - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. - * @see @ref features(), @ref convertToFile() + * If (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToData is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and @ref endData(). + * @see @ref isConverting(), @ref features(), @ref convertToFile() */ #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) Containers::Optional> @@ -415,11 +711,18 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Convert a mesh to a file * @m_since_latest * - * Available only if @ref SceneConverterFeature::ConvertMeshToFile or - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. - * @see @ref features(), @ref convertToData() + * If a (batch) conversion is currently in progress, calls @ref abort() + * first. On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToFile is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginFile(), + * @ref add(const MeshData&, Containers::StringView) and + * @ref endFile(). + * @see @ref isConverting(), @ref features(), @ref convertToData() */ bool convertToFile(const MeshData& mesh, Containers::StringView filename); @@ -432,52 +735,1381 @@ 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) + * @brief Whether any conversion is in progress + * @m_since_latest * - * 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. + * Returns @cpp true @ce if any conversion started by @ref begin(), + * @ref beginData() or @ref beginFile() has not ended yet and + * @ref abort() wasn't called; @cpp false @ce otherwise. */ - virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + bool isConverting() const; - private: /** - * @brief Implementation for @ref features() + * @brief Abort any in-progress conversion + * @m_since_latest * - * The implementation is expected to support at least one feature. + * On particular implementations an explicit call to this function may + * result in freed memory. If no conversion is currently in progress, + * does nothing. After this function is called, @ref isConverting() + * returns @cpp false @ce. */ - virtual SceneConverterFeatures doFeatures() const = 0; + void abort(); /** - * @brief Implementation for @ref setFlags() + * @brief Begin converting a scene + * @m_since_latest * - * Useful when the converter needs to modify some internal state on - * flag setup. Default implementation does nothing and this - * function doesn't need to be implemented --- the flags are available - * through @ref flags(). + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned via an importer instance upon calling + * @ref end(). On failure prints a message to @relativeref{Magnum,Error} + * and returns @cpp false @ce. * - * To reduce the amount of error checking on user side, this function - * isn't expected to fail --- if a flag combination is invalid / - * unsuported, error reporting should be delayed to various conversion - * functions, where the user is expected to do error handling anyway. + * Expects that @ref SceneConverterFeature::ConvertMultiple is + * supported. If not and @ref SceneConverterFeature::ConvertMesh is + * supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convert(const MeshData&) and return the result from + * @ref end(). + * @see @ref isConverting(), @ref features(), @ref beginData(), + * @ref beginFile() */ - virtual void doSetFlags(SceneConverterFlags flags); + bool begin(); - /** @brief Implementation for @ref convert(const MeshData&) */ - virtual Containers::Optional doConvert(const MeshData& mesh); + /** + * @brief End converting a scene + * @m_since_latest + * + * Expects that @ref begin() was called before. The returned + * @ref AbstractImporter may contain arbitrary amounts of data + * depending on the particular converter plugin. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + * + * @m_class{m-note m-warning} + * + * @par Data dependency + * The returned importer instance is fully self-contained, with no + * dependency on the originating converter instance or its + * internal state, meaning you can immediately reuse the instance + * for another conversion without corrupting the importer instance + * returned earlier. + * @par + * Its *code*, however, is coming from the plugin binary and thus + * the plugin should not be unloaded (or its originating + * @relativeref{Corrade,PluginManager::Manager} destroyed) before + * the importer instance is destroyed. + * + * If @ref SceneConverterFeature::ConvertMultiple is not supported and + * @ref SceneConverterFeature::ConvertMesh is supported instead, + * returns an importer exposing a single mesh that was converted via + * the @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convert(const MeshData&). To simplify data + * ownership logic, the mesh can be extracted from the returned + * importer only once, subsequent calls to @ref AbstractImporter::mesh() + * will fail. If no mesh was added, prints a message to + * @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + */ + Containers::Pointer end(); - /** @brief Implementation for @ref convertInPlace(MeshData&) */ - virtual bool doConvertInPlace(MeshData& mesh); + /** + * @brief Begin converting a scene to raw data + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endData(). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @cpp false @ce. + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToData is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToData + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToData(const MeshData&) and return the result from + * @ref endData(). + * @see @ref isConverting(), @ref features(), @ref begin(), + * @ref beginFile() + */ + bool beginData(); - /** @brief Implementation for @ref convertToData(const MeshData&) */ - virtual Containers::Optional> doConvertToData(const MeshData& mesh); + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is not + * supported and @ref SceneConverterFeature::ConvertMeshToData is + * supported instead, returns data converted via the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToData(const MeshData&). If no mesh was + * added, prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + */ + Containers::Optional> endData(); + + /** + * @brief Begin converting a scene to a file + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endFile(). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @cpp false @ce. + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToFile is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToFile + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToFile(const MeshData&, Containers::StringView) and + * return the result from @ref endFile(). + * @see @ref isConverting(), @ref features(), @ref begin(), + * @ref beginData() + */ + bool beginFile(Containers::StringView filename); + + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp false @ce. + * + * If @ref SceneConverterFeature::ConvertMultipleToFile is not + * supported and @ref SceneConverterFeature::ConvertMeshToFile is + * supported instead, returns a result of the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToFile(const MeshData&, Containers::StringView). + * If no mesh was added, prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt. + */ + bool endFile(); + + /** + * @brief Count of added scenes + * @m_since_latest + * + * Count of scenes successfully added with + * @ref add(const SceneData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddScenes is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt sceneCount() const; + + /** + * @brief Add a scene + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddScenes is supported. The returned ID + * is implicitly equal to @ref sceneCount() before calling this + * function and can be subsequently used in functions like + * @ref setDefaultScene(). On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt --- + * the count of added animations doesn't change in that case. + * + * If the converter doesn't support scene naming, @p name is ignored. + * + * Because a scene directly or indirectly references majority of other + * data, it's recommended to be added only after all data it uses are + * added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the scene referencing yet-unknown data. + * @see @ref isConverting(), @ref features(), + * @ref setSceneFieldName(), @ref setObjectName(), + * @ref setDefaultScene() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SceneData& data, Containers::StringView name = {}); + #else + Containers::Optional add(const SceneData& data, Containers::StringView name); + Containers::Optional add(const SceneData& data); + #endif + + /** + * @brief Set name of a custom scene field + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The field name will get used only for scene data added + * after this function has been called. If the converter doesn't + * support custom scene fields or doesn't support naming them, the call + * is ignored. + * @see @ref isConverting(), @ref features(), @ref isSceneFieldCustom(), + * @ref setMeshAttributeName() + */ + void setSceneFieldName(SceneField field, Containers::StringView name); + + /** + * @brief Set object name + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The object name will get used only for scene data + * added after this function has been called. If the converter doesn't + * support naming objects, the call is ignored. + * @see @ref isConverting(), @ref features() + */ + void setObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Set default scene + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and + * @ref sceneCount() is greater than @p id. If the converter doesn't + * support multiple scenes or default scene selection, the call is + * ignored. + * @see @ref isConverting(), @ref features() + */ + void setDefaultScene(UnsignedInt id); + + /** + * @brief Count of added animations + * @m_since_latest + * + * Count of animations successfully added with + * @ref add(const AnimationData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddAnimations is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt animationCount() const; + + /** + * @brief Add an animation + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddAnimations is supported. The returned + * ID is implicitly equal to @ref animationCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added + * animations doesn't change in that case. + * + * If the converter doesn't support animation naming, @p name is + * ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const AnimationData& animation, Containers::StringView name = {}); + #else + Containers::Optional add(const AnimationData& animation, Containers::StringView name); + Containers::Optional add(const AnimationData& animation); + #endif + + /** + * @brief Count of added lights + * @m_since_latest + * + * Count of lights successfully added with + * @ref add(const LightData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddLights is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt lightCount() const; + + /** + * @brief Add a light + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddLights is supported. The returned ID + * is implicitly equal to @ref lightCount() before calling this + * function and can be subsequently used to for example reference a + * light from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added lights doesn't + * change in that case. + * + * If the converter doesn't support light naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const LightData& light, Containers::StringView name = {}); + #else + Containers::Optional add(const LightData& light, Containers::StringView name); + Containers::Optional add(const LightData& light); + #endif + + /** + * @brief Count of added cameras + * @m_since_latest + * + * Count of cameras successfully added with + * @ref add(const CameraData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddCameras is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt cameraCount() const; + + /** + * @brief Add a camera + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddCameras is supported. The returned ID + * is implicitly equal to @ref cameraCount() before calling this + * function and can be subsequently used to for example reference a + * camera from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added cameras doesn't + * change in that case. + * + * If the converter doesn't support camera naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CameraData& camera, Containers::StringView name = {}); + #else + Containers::Optional add(const CameraData& camera, Containers::StringView name); + Containers::Optional add(const CameraData& camera); + #endif + + /** + * @brief Count of added 2D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData2D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins2D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin2DCount() const; + + /** + * @brief Add a 2D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins2D is supported. The returned ID + * is implicitly equal to @ref skin2DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData2D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData2D& skin, Containers::StringView name); + Containers::Optional add(const SkinData2D& skin); + #endif + + /** + * @brief Count of added 3D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData3D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins3D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin3DCount() const; + + /** + * @brief Add a 3D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins3D is supported. The returned ID + * is implicitly equal to @ref skin3DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData3D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData3D& skin, Containers::StringView name); + Containers::Optional add(const SkinData3D& skin); + #endif + + /** + * @brief Count of added meshes + * @m_since_latest + * + * Count of meshes successfully added with + * @ref add(const MeshData&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * since the initial @ref begin(), @ref beginData() or @ref beginFile() + * call. Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMeshes is not supported and only the + * singular @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * returns always either @cpp 0 @ce or @cpp 1 @ce. Otherwise returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt meshCount() const; + + /** + * @brief Add a mesh + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddMeshes, + * @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is + * supported. The returned ID is implicitly equal to @ref meshCount() + * before calling this function and can be subsequently used to for + * example reference a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added meshes doesn't + * change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * + * If only the singular @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * the function can be called only exactly once to successfully produce + * an output, and the process is equivalent to + * @ref convert(const MeshData&), + * @ref convertToData(const MeshData&) or + * @ref convertToFile(const MeshData&, Containers::StringView), with + * the @p name ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MeshData& mesh, Containers::StringView name = {}); + #else + Containers::Optional add(const MeshData& mesh, Containers::StringView name); + Containers::Optional add(const MeshData& mesh); + #endif + + /** + * @brief Add a set of mesh levels + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::MeshLevels together with + * @relativeref{SceneConverterFeature,AddMeshes} is supported. The + * returned ID is implicitly equal to @ref meshCount() before calling + * this function and can be subsequently used to for example reference + * a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView); all levels + * together are treated as a single mesh. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added meshes doesn't change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable meshLevels); + #endif + + /** + * @brief Set name of a custom mesh attribute + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddMeshes is supported and @p attribute + * is a custom attribute. The attribute name will get used only mesh + * data added after this function has been called. If the converter + * doesn't support custom mesh attributes or doesn't support naming + * them, the call is ignored. + * @see @ref isConverting(), @ref features(), + * @ref isMeshAttributeCustom(), @ref setSceneFieldName() + */ + void setMeshAttributeName(MeshAttribute attribute, Containers::StringView name); + + /** + * @brief Count of added materials + * @m_since_latest + * + * Count of materials successfully added with + * @ref add(const MaterialData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMaterials is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt materialCount() const; + + /** + * @brief Add a material + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddMaterials is supported. The returned + * ID is implicitly equal to @ref materialCount() before calling this + * function and can be subsequently used to for example reference a + * material from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added materials doesn't + * change in that case. + * + * If the converter doesn't support material naming, @p name is + * ignored. + * + * Because a material directly or indirectly references textures and + * images, it's recommended to be added only after all data it uses + * are added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the material referencing yet-unknown + * data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MaterialData& material, Containers::StringView name = {}); + #else + Containers::Optional add(const MaterialData& material, Containers::StringView name); + Containers::Optional add(const MaterialData& material); + #endif + + /** + * @brief Count of added textures + * @m_since_latest + * + * Count of textures successfully added with + * @ref add(const TextureData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddTextures is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt textureCount() const; + + /** + * @brief Add a texture + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddTextures is supported. The returned + * ID is implicitly equal to @ref textureCount() before calling this + * function and can be subsequently used to for example reference a + * texture from a @ref MaterialData passed to + * @ref add(const MaterialData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added textures doesn't + * change in that case. + * + * If the converter doesn't support texture naming, @p name is ignored. + * + * Because a texture references an image, it's recommended to be added + * only after the image it uses is added as well. Particular converter + * plugins may have looser requirements, but adding it last guarantees + * that the conversion process doesn't fail due to the texture + * referencing yet-unknown data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const TextureData& texture, Containers::StringView name = {}); + #else + Containers::Optional add(const TextureData& texture, Containers::StringView name); + Containers::Optional add(const TextureData& texture); + #endif + + /** + * @brief Count of added 1D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData1D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages1D nor + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image1DCount() const; + + /** + * @brief Add a 1D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData1D& image, Containers::StringView name); + Containers::Optional add(const ImageData1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView1D& image, Containers::StringView name); + Containers::Optional add(const ImageView1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView1D& image); + #endif + + /** + * @brief Add a set of 1D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages1D} or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 2D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData2D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages2D nor + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image2DCount() const; + + /** + * @brief Add a 2D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData2D& image, Containers::StringView name); + Containers::Optional add(const ImageData2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView2D& image, Containers::StringView name); + Containers::Optional add(const ImageView2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView2D& image); + #endif + + /** + * @brief Add a set of 2D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages2D} or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 3D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData3D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages3D nor + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image3DCount() const; + + /** + * @brief Add a 3D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData3D& image, Containers::StringView name); + Containers::Optional add(const ImageData3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView3D& image, Containers::StringView name); + Containers::Optional add(const ImageView3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView3D& image); + #endif + + /** + * @brief Add a set of 3D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages3D} or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #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. + * + * Otherwise, if both @ref SceneConverterFeature::ConvertMultipleToFile + * and @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + + /** + * @brief Implementation for @ref beginFile() + * @m_since_latest + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation delegates to @ref doBeginData(). + * + * It is allowed to call this function from your @ref doBeginFile() + * implementation, for example when you only need to do format + * detection based on file extension. + * + * The @p filename string is guaranteed to stay in scope until a call + * to @ref doEndFile(). It's not guaranteed to be + * @relativeref{Corrade,Containers::StringViewFlag::Global} or + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated}, + * however. + */ + virtual bool doBeginFile(Containers::StringView filename); + + /** + * @brief Implementation for @ref endFile() + * @m_since_latest + * + * Receives the same @p filename as was passed to @ref doBeginFile() + * earlier. Expected to save the output data and reset the internal + * state for a potential new conversion to happen. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation calls @ref doEndData() and saves the result + * to given file. + * + * It is allowed to call this function from your @ref doEndFile() + * implementation, for example when you only need to do format + * detection based on file extension. + */ + virtual bool doEndFile(Containers::StringView filename); + + private: + struct State; + + /** + * @brief Implementation for @ref features() + * + * The implementation is expected to support at least one feature. + */ + virtual SceneConverterFeatures doFeatures() const = 0; + + /** + * @brief Implementation for @ref setFlags() + * + * Useful when the converter needs to modify some internal state on + * flag setup. Default implementation does nothing and this + * function doesn't need to be implemented --- the flags are available + * through @ref flags(). + * + * To reduce the amount of error checking on user side, this function + * isn't expected to fail --- if a flag combination is invalid / + * unsuported, error reporting should be delayed to various conversion + * functions, where the user is expected to do error handling anyway. + */ + virtual void doSetFlags(SceneConverterFlags flags); + + /** @brief Implementation for @ref convert(const MeshData&) */ + virtual Containers::Optional doConvert(const MeshData& mesh); + + /** @brief Implementation for @ref convertInPlace(MeshData&) */ + virtual bool doConvertInPlace(MeshData& mesh); + + /** + * @brief Implementation for @ref convertToData(const MeshData&) + * + * If both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual Containers::Optional> doConvertToData(const MeshData& mesh); + + /** + * @brief Implementation for @ref abort() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doAbort(); + + /** + * @brief Implementation for @ref begin() + * @m_since_latest + */ + virtual bool doBegin(); + + /** + * @brief Implementation for @ref end() + * @m_since_latest + * + * Expected to return an importer instance owning all output data and + * reset the internal state for a potential new conversion to happen. + */ + virtual Containers::Pointer doEnd(); + + /** + * @brief Implementation for @ref beginData() + * @m_since_latest + */ + virtual bool doBeginData(); + + /** + * @brief Implementation for @ref endData() + * @m_since_latest + * + * Expected to return the output data and reset the internal state for + * a potential new conversion to happen. + */ + virtual Containers::Optional> doEndData(); + + /** + * @brief Implementation for @ref add(const SceneData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref sceneCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name); + + /** + * @brief Implementation for @ref setSceneFieldName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref sceneFieldCustom(SceneField). Default implementation does + * nothing. + */ + virtual void doSetSceneFieldName(UnsignedInt field, Containers::StringView name); + + /** + * @brief Implementation for @ref setObjectName() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Implementation for @ref setDefaultScene() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetDefaultScene(UnsignedInt id); + + /** + * @brief Implementation for @ref add(const AnimationData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref animationCount() at the time this + * function is called. + */ + virtual bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const LightData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref lightCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const CameraData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref cameraCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin2DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin3DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MeshData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function is + * called. + * + * If @ref SceneConverterFeature::AddMeshes together with + * @relativeref{SceneConverterFeature,MeshLevels} is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p mesh. + * + * Otherwise, if @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * default implementation delegates to @ref doConvert(const MeshData&), + * @ref doConvertToData(const MeshData&) or + * @ref doConvertToFile(const MeshData&, Containers::StringView) and + * remembers the result to return it from @ref doEnd(), + * @ref doEndData() or @ref doEndFile(). If the delegated-to function + * fails, returns @cpp false @ce and the subsequent @ref doEnd(), + * @ref doEndData() or @ref doEndFile() call prints a message to + * @relativeref{Magnum,Error} and returns a @cpp nullptr @ce, + * @ref Containers::NullOpt or @cpp false @ce. Since the delegation + * operates just on a single mesh at a time, if this function is called + * more than once after a @ref begin(), @ref beginData() or + * @ref beginFile(), prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + */ + virtual bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref setMeshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref meshAttributeCustom(MeshAttribute). Default implementation does + * nothing. + */ + virtual void doSetMeshAttributeName(UnsignedShort attribute, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MaterialData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref materialCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const TextureData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref textureCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData1D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(const ImageView1D&, Containers::StringView) + * or @ref add(const CompressedImageView1D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData1D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData1D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(const ImageView2D&, Containers::StringView) + * or @ref add(const CompressedImageView2D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData2D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData2D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(const ImageView3D&, Containers::StringView) + * or @ref add(const CompressedImageView3D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData3D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData3D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); SceneConverterFlags _flags; + Containers::Pointer _state; }; }} diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 05a92eced..2de24976b 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -280,6 +280,9 @@ class AnimationTrackData { * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ + /** @todo stop taking TrackViewStorage and instead directly take the + key/value views, interpolator/interpolation and extrapolation -- + it's just 6 overloads and makes usage much better */ explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 6c981bc54..547bed61a 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -160,7 +160,8 @@ if(MAGNUM_WITH_IMAGECONVERTER) MagnumTrade # BasisImageConverter uses these, and linking pthread to just the # plugin doesn't work. See its documentation for details. - Threads::Threads) + Threads::Threads + ${MAGNUM_IMAGECONVERTER_STATIC_PLUGINS}) install(TARGETS magnum-imageconverter DESTINATION ${MAGNUM_BINARY_INSTALL_DIR}) diff --git a/src/Magnum/Trade/Implementation/converterUtilities.h b/src/Magnum/Trade/Implementation/converterUtilities.h index f856f83b2..d6dbcf57d 100644 --- a/src/Magnum/Trade/Implementation/converterUtilities.h +++ b/src/Magnum/Trade/Implementation/converterUtilities.h @@ -27,13 +27,18 @@ #include #include +#include +#include +#include +#include "Magnum/PixelFormat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" namespace Magnum { namespace Trade { namespace Implementation { -/* Used only in executables where we don't want it to be exported */ +/* Used only in executables where we don't want it to be exported -- in + particular magnum-imageconverter, magnum-sceneconverter and their tests */ namespace { struct Duration { @@ -74,7 +79,7 @@ struct ImageInfo { Containers::Array imageInfo(AbstractImporter& importer, bool& error, std::chrono::high_resolution_clock::duration& importTime) { Containers::Array infos; for(UnsignedInt i = 0; i != importer.image1DCount(); ++i) { - const std::string name = importer.image1DName(i); + const Containers::String name = importer.image1DName(i); const UnsignedInt levelCount = importer.image1DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -96,11 +101,11 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, image->data().size(), image->dataFlags(), ImageInfoFlags{image->flags()}, - j ? "" : importer.image1DName(i)); + j ? "" : name); } } for(UnsignedInt i = 0; i != importer.image2DCount(); ++i) { - const std::string name = importer.image2DName(i); + const Containers::String name = importer.image2DName(i); const UnsignedInt levelCount = importer.image2DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -126,7 +131,7 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, } } for(UnsignedInt i = 0; i != importer.image3DCount(); ++i) { - const std::string name = importer.image3DName(i); + const Containers::String name = importer.image3DName(i); const UnsignedInt levelCount = importer.image3DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -155,6 +160,73 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, return infos; } +void printImageInfo(const Debug::Flags useColor, const Containers::ArrayView imageInfos, const Containers::ArrayView image1DReferenceCount, const Containers::ArrayView image2DReferenceCount, const Containers::ArrayView image3DReferenceCount) { + std::size_t totalImageDataSize = 0; + for(const Trade::Implementation::ImageInfo& info: imageInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default); + if(info.size.z()) d << "3D image"; + else if(info.size.y()) d << "2D image"; + else d << "1D image"; + d << info.image << Debug::resetColor; + + /* Print reference count only if there actually are any (i.e., the + arrays are non-empty) otherwise this information is useless */ + Containers::Optional count; + if(info.size.z() && image3DReferenceCount) { + count = image3DReferenceCount[info.image]; + } else if(info.size.y() && image2DReferenceCount) { + count = image2DReferenceCount[info.image]; + } else if(image1DReferenceCount) { + count = image1DReferenceCount[info.image]; + } + if(count) { + if(!*count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << *count << "textures)"; + if(!*count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":"; + if(info.flags.one) { + d << Debug::packed << Debug::color(Debug::Color::Cyan); + if(info.size.z()) d << info.flags.three; + else if(info.size.y()) d << info.flags.two; + else d << info.flags.one; + d << Debug::resetColor; + } + d << Debug::packed; + if(info.size.z()) d << info.size; + else if(info.size.y()) d << info.size.xy(); + /* Kinda unnecessary, but makes the output more consistent if also 1D + size is in {}s */ + else d << Math::Vector<1, Int>(info.size.x()); + d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; + d << Debug::packed; + /* Compressed formats are printed yellow. That kinda conflicts with + custom fields / attributes elsewhere, but is significant enough to + have it highlighted. */ + if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; + else d << Debug::color(Debug::Color::Cyan) << info.format; + d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + totalImageDataSize += info.dataSize; + } + if(!imageInfos.isEmpty()) + Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; +} + } }}} diff --git a/src/Magnum/Trade/LightData.h b/src/Magnum/Trade/LightData.h index f41bc9e93..902f2ef39 100644 --- a/src/Magnum/Trade/LightData.h +++ b/src/Magnum/Trade/LightData.h @@ -121,6 +121,7 @@ class MAGNUM_TRADE_EXPORT LightData { * @brief Light type * * @see @ref type() + * @todo move this to LightType outside of the class for consistency */ enum class Type: UnsignedByte { /* Zero reserved for an invalid value */ diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp index b8b12099f..d9f2eecab 100644 --- a/src/Magnum/Trade/MaterialData.cpp +++ b/src/Magnum/Trade/MaterialData.cpp @@ -65,6 +65,39 @@ constexpr struct { } +namespace Implementation { + +Containers::StringView materialLayerNameInternal(const MaterialLayer layer) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(layer) - 1 >= Containers::arraySize(LayerMap)) + return nullptr; + #endif + return LayerMap[UnsignedInt(layer) - 1]; +} + +Containers::StringView materialAttributeNameInternal(const MaterialAttribute attribute) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(attribute) - 1 >= Containers::arraySize(AttributeMap)) + return nullptr; + #endif + return AttributeMap[UnsignedInt(attribute) - 1].name; +} + +} + +Containers::StringView materialLayerName(const MaterialLayer layer) { + CORRADE_ASSERT(UnsignedInt(layer) - 1 < Containers::arraySize(LayerMap), + "Trade::materialLayerName(): invalid layer" << layer, {}); + return LayerMap[UnsignedInt(layer) - 1]; +} + +Containers::StringView materialAttributeName(const MaterialAttribute attribute) { + CORRADE_ASSERT(UnsignedInt(attribute) - 1 < Containers::arraySize(AttributeMap), + "Trade::materialAttributeName(): invalid attribute" << attribute, {}); + return AttributeMap[UnsignedInt(attribute) - 1].name; +} + + UnsignedInt materialTextureSwizzleComponentCount(const MaterialTextureSwizzle swizzle) { return (UnsignedInt(swizzle) & 0xff000000u ? 1 : 0) + (UnsignedInt(swizzle) & 0x00ff0000u ? 1 : 0) + @@ -298,23 +331,7 @@ MaterialData::~MaterialData() = default; MaterialData& MaterialData::operator=(MaterialData&&) noexcept = default; -Containers::StringView MaterialData::layerString(const MaterialLayer name) { - #ifndef CORRADE_NO_ASSERT - if(UnsignedInt(name) - 1 >= Containers::arraySize(LayerMap)) - return nullptr; - #endif - return LayerMap[UnsignedInt(name) - 1]; -} - -Containers::StringView MaterialData::attributeString(const MaterialAttribute name) { - #ifndef CORRADE_NO_ASSERT - if(UnsignedInt(name) - 1 >= Containers::arraySize(AttributeMap)) - return nullptr; - #endif - return AttributeMap[UnsignedInt(name) - 1].name; -} - -UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { +UnsignedInt MaterialData::findLayerIdInternal(const Containers::StringView layer) const { for(std::size_t i = 1; i < _layerOffsets.size(); ++i) { if(_layerOffsets[i] > _layerOffsets[i - 1] && _data[_layerOffsets[i - 1]].name() == " LayerName"_s && @@ -325,24 +342,35 @@ UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { } bool MaterialData::hasLayer(const Containers::StringView layer) const { - return layerFor(layer) != ~UnsignedInt{}; + return findLayerIdInternal(layer) != ~UnsignedInt{}; } bool MaterialData::hasLayer(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasLayer(): invalid name" << layer, {}); return hasLayer(string); } +Containers::Optional MaterialData::findLayerId(const Containers::StringView layer) const { + const UnsignedInt id = findLayerIdInternal(layer); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findLayerId(const MaterialLayer layer) const { + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findLayerId(): invalid name" << layer, {}); + return findLayerId(string); +} + UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { - const UnsignedInt id = layerFor(layer); + const UnsignedInt id = findLayerIdInternal(layer); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::layerId(): layer" << layer << "not found", {}); return id; } UnsignedInt MaterialData::layerId(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerId(): invalid name" << layer, {}); return layerId(string); } @@ -363,14 +391,14 @@ Float MaterialData::layerFactor(const UnsignedInt layer) const { } Float MaterialData::layerFactor(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactor(): layer" << layer << "not found", {}); return layerFactor(layerId); } Float MaterialData::layerFactor(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactor(): invalid name" << layer, {}); return layerFactor(string); } @@ -383,7 +411,7 @@ UnsignedInt MaterialData::layerFactorTexture(const UnsignedInt layer) const { UnsignedInt MaterialData::layerFactorTexture(const Containers::StringView layer) const { #ifndef CORRADE_NO_ASSERT - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); #endif CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTexture(): layer" << layer << "not found", {}); @@ -393,7 +421,7 @@ UnsignedInt MaterialData::layerFactorTexture(const Containers::StringView layer) } UnsignedInt MaterialData::layerFactorTexture(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTexture(): invalid name" << layer, {}); return layerFactorTexture(string); } @@ -408,7 +436,7 @@ MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const UnsignedInt MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const Containers::StringView layer) const { #ifndef CORRADE_NO_ASSERT - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); #endif CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureSwizzle(): layer" << layer << "not found", {}); @@ -420,7 +448,7 @@ MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const Containers: } MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureSwizzle(): invalid name" << layer, {}); return layerFactorTextureSwizzle(string); } @@ -438,7 +466,7 @@ Matrix3 MaterialData::layerFactorTextureMatrix(const UnsignedInt layer) const { } Matrix3 MaterialData::layerFactorTextureMatrix(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureMatrix(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -453,7 +481,7 @@ Matrix3 MaterialData::layerFactorTextureMatrix(const Containers::StringView laye } Matrix3 MaterialData::layerFactorTextureMatrix(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureMatrix(): invalid name" << layer, {}); return layerFactorTextureMatrix(string); } @@ -471,7 +499,7 @@ UnsignedInt MaterialData::layerFactorTextureCoordinates(const UnsignedInt layer) } UnsignedInt MaterialData::layerFactorTextureCoordinates(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureCoordinates(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -486,7 +514,7 @@ UnsignedInt MaterialData::layerFactorTextureCoordinates(const Containers::String } UnsignedInt MaterialData::layerFactorTextureCoordinates(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureCoordinates(): invalid name" << layer, {}); return layerFactorTextureCoordinates(string); } @@ -504,7 +532,7 @@ UnsignedInt MaterialData::layerFactorTextureLayer(const UnsignedInt layer) const } UnsignedInt MaterialData::layerFactorTextureLayer(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::layerFactorTextureLayer(): layer" << layer << "not found", {}); CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), @@ -519,7 +547,7 @@ UnsignedInt MaterialData::layerFactorTextureLayer(const Containers::StringView l } UnsignedInt MaterialData::layerFactorTextureLayer(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::layerFactorTextureLayer(): invalid name" << layer, {}); return layerFactorTextureLayer(string); } @@ -533,19 +561,19 @@ UnsignedInt MaterialData::attributeCount(const UnsignedInt layer) const { } UnsignedInt MaterialData::attributeCount(const Containers::StringView layer) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeCount(): layer" << layer << "not found", {}); return attributeCount(layerId); } UnsignedInt MaterialData::attributeCount(const MaterialLayer layer) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeCount(): invalid name" << layer, {}); return attributeCount(string); } -UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers::StringView name) const { +UnsignedInt MaterialData::findAttributeIdInternal(const UnsignedInt layer, const Containers::StringView name) const { const MaterialAttributeData* begin = _data.begin() + (layer && _layerOffsets ? _layerOffsets[layer - 1] : 0); const MaterialAttributeData* end = @@ -560,79 +588,126 @@ UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers bool MaterialData::hasAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::hasAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - return attributeFor(layer, name) != ~UnsignedInt{}; + return findAttributeIdInternal(layer, name) != ~UnsignedInt{}; } bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); return hasAttribute(layer, string); } bool MaterialData::hasAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::hasAttribute(): layer" << layer << "not found", {}); return hasAttribute(layerId, name); } bool MaterialData::hasAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); return hasAttribute(layer, string); } bool MaterialData::hasAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); return hasAttribute(string, name); } bool MaterialData::hasAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); return hasAttribute(string, name); } +Containers::Optional MaterialData::findAttributeId(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::findAttributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = findAttributeIdInternal(layer, name); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findAttributeId(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); + return findAttributeId(layer, string); +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = findLayerIdInternal(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::findAttributeId(): layer" << layer << "not found", {}); + const UnsignedInt id = findAttributeIdInternal(layerId, name); + return id == ~UnsignedInt{} ? Containers::Optional{} : id; +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << name, {}); + return findAttributeId(layer, string); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); + return findAttributeId(string, name); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); + CORRADE_ASSERT(string, "Trade::MaterialData::findAttributeId(): invalid name" << layer, {}); + return findAttributeId(string, name); +} + +Containers::Optional MaterialData::findAttributeId(const Containers::StringView name) const { + return findAttributeId(0, name); +} + +Containers::Optional MaterialData::findAttributeId(const MaterialAttribute name) const { + return findAttributeId(0, name); +} + UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); return id; } UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << name, {}); return attributeId(layer, string); } UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); return id; } UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << name, {}); return attributeId(layer, string); } UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << layer, {}); return attributeId(string, name); } UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeId(): invalid name" << layer, {}); return attributeId(string, name); } @@ -646,7 +721,7 @@ Containers::StringView MaterialData::attributeName(const UnsignedInt layer, cons } Containers::StringView MaterialData::attributeName(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeName(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -655,7 +730,7 @@ Containers::StringView MaterialData::attributeName(const Containers::StringView } Containers::StringView MaterialData::attributeName(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeName(): invalid name" << layer, {}); return attributeName(string, id); } @@ -671,20 +746,20 @@ MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layer) + id]._data.type; } MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << name, {}); return attributeType(layer, string); } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -693,35 +768,35 @@ MaterialAttributeType MaterialData::attributeType(const Containers::StringView l } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layerId) + id]._data.type; } MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << name, {}); return attributeType(layer, string); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, id); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, name); } MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attributeType(): invalid name" << layer, {}); return attributeType(string, name); } @@ -747,7 +822,7 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const UnsignedInt const void* MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layer) + id].value(); @@ -758,26 +833,26 @@ void* MaterialData::mutableAttribute(const UnsignedInt layer, const Containers:: "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::mutableAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, {}); return const_cast(_data[layerOffset(layer) + id].value()); } const void* MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } void* MaterialData::mutableAttribute(const UnsignedInt layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << name, {}); return mutableAttribute(layer, string); } const void* MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -788,7 +863,7 @@ const void* MaterialData::attribute(const Containers::StringView layer, const Un void* MaterialData::mutableAttribute(const Containers::StringView layer, const UnsignedInt id) { CORRADE_ASSERT(_attributeDataFlags & DataFlag::Mutable, "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -797,10 +872,10 @@ void* MaterialData::mutableAttribute(const Containers::StringView layer, const U } const void* MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return _data[layerOffset(layerId) + id].value(); @@ -809,59 +884,59 @@ const void* MaterialData::attribute(const Containers::StringView layer, const Co void* MaterialData::mutableAttribute(const Containers::StringView layer, const Containers::StringView name) { CORRADE_ASSERT(_attributeDataFlags & DataFlag::Mutable, "Trade::MaterialData::mutableAttribute(): attribute data not mutable", {}); - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, {}); return const_cast(_data[layerOffset(layerId) + id].value()); } const void* MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } void* MaterialData::mutableAttribute(const Containers::StringView layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << name, {}); return mutableAttribute(layer, string); } const void* MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, id); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const UnsignedInt id) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, id); } const void* MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const Containers::StringView name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, name); } const void* MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } void* MaterialData::mutableAttribute(const MaterialLayer layer, const MaterialAttribute name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::mutableAttribute(): invalid name" << layer, {}); return mutableAttribute(string, name); } @@ -901,40 +976,40 @@ template<> MAGNUM_TRADE_EXPORT Containers::MutableStringView MaterialData::mutab const void* MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return nullptr; return _data[layerOffset(layer) + id].value(); } const void* MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } const void* MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); if(id == ~UnsignedInt{}) return nullptr; return _data[layerOffset(layerId) + id].value(); } const void* MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } const void* MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } const void* MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string, "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h index ca3a9de7f..d1c00f021 100644 --- a/src/Magnum/Trade/MaterialData.h +++ b/src/Magnum/Trade/MaterialData.h @@ -57,7 +57,8 @@ for @cpp "ClearCoat" @ce. Each layer is expected to contain (a subset of) the @ref MaterialAttribute::LayerFactorTextureMatrix, @ref MaterialAttribute::LayerFactorTextureCoordinates attributes in addition to what's specified for a particular named layer. -@see @ref MaterialData, @ref MaterialData::layerName(), @ref MaterialLayerData +@see @ref MaterialData, @ref MaterialData::layerName(), @ref MaterialLayerData, + @ref materialLayerName() */ enum class MaterialLayer: UnsignedInt { /* Zero used for an invalid value */ @@ -80,6 +81,21 @@ enum class MaterialLayer: UnsignedInt { ClearCoat = 1, }; +namespace Implementation { + /* Compared to materialLayerName() below returns an empty string for + invalid layers, used internally to provide better assertion messages */ + MAGNUM_TRADE_EXPORT Containers::StringView materialLayerNameInternal(MaterialLayer layer); +} + +/** +@brief Material layer name as a string + +Expects that @p layer is a valid @ref MaterialLayer value. The returned view +has both @relativeref{Corrade,Containers::StringViewFlag::Global} and +@relativeref{Corrade::Containers::StringViewFlag,NullTerminated} set. +*/ +MAGNUM_TRADE_EXPORT Containers::StringView materialLayerName(MaterialLayer layer); + /** @debugoperatorenum{MaterialLayer} @m_since_latest @@ -99,7 +115,8 @@ only exception is @ref MaterialAttribute::LayerName which is When this enum is used in @ref MaterialAttributeData constructors, the data are additionally checked for type compatibility. Other than that, there is no difference to the string variants. -@see @ref MaterialAttributeData, @ref MaterialData +@see @ref MaterialAttributeData, @ref MaterialData, + @ref materialAttributeName() */ enum class MaterialAttribute: UnsignedInt { /* Zero used for an invalid value */ @@ -1052,6 +1069,22 @@ enum class MaterialAttribute: UnsignedInt { TextureLayer, }; +namespace Implementation { + /* Compared to materialLayerName() below returns an empty string for + invalid layers, used internally to provide better assertion messages */ + MAGNUM_TRADE_EXPORT Containers::StringView materialAttributeNameInternal(MaterialAttribute attribute); +} + +/** +@brief Material layer name as a string +@m_since_latest + +Expects that @p attribute is a valid @ref MaterialAttribute value. The returned +view has both @relativeref{Corrade,Containers::StringViewFlag::Global} and +@relativeref{Corrade::Containers::StringViewFlag,NullTerminated} set. +*/ +MAGNUM_TRADE_EXPORT Containers::StringView materialAttributeName(MaterialAttribute attribute); + /** @debugoperatorenum{MaterialAttribute} @m_since_latest @@ -1184,8 +1217,8 @@ enum class MaterialAttributeType: UnsignedByte { /** * Null-terminated string. Can be stored using any type convertible to - * @ref Corrade::Containers::StringView, retrieval has to be done using - * @ref Corrade::Containers::StringView. + * @relativeref{Corrade,Containers::StringView}, retrieval has to be done + * using @relativeref{Corrade,Containers::StringView}. */ String, @@ -1355,7 +1388,7 @@ class MAGNUM_TRADE_EXPORT MaterialAttributeData { * @brief Attribute name * * The returned view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. */ Containers::StringView name() const { return _data.data + 1; } @@ -1697,7 +1730,7 @@ existing attributes. @section Trade-MaterialData-populating Populating an instance A @ref MaterialData instance by default takes over ownership of an -@ref Corrade::Containers::Array containing @ref MaterialAttributeData +@relativeref{Corrade,Containers::Array} containing @ref MaterialAttributeData instances, together with @ref MaterialTypes suggesting available material types (or an empty set, in case of a fully custom material). Attribute values can be in one of the types from @ref MaterialAttributeType, and the type is in most @@ -1727,8 +1760,8 @@ already sorted by name. @par Additionally, as shown above, in order to create a @cpp constexpr @ce @ref MaterialAttributeData array, you need to use - @ref Corrade::Containers::StringView literals instead of plain C strings - or the @ref MaterialAttribute enum, and be sure to call only + @relativeref{Corrade,Containers::StringView} literals instead of plain C + strings or the @ref MaterialAttribute enum, and be sure to call only @cpp constexpr @ce-enabled constructors of stored data types. @subsection Trade-MaterialData-populating-custom Custom material attributes @@ -2038,15 +2071,26 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @cpp 0 @ce is skipped as well) to avoid confusing base material with * a layer. If you want to create a material consisting of just a * layer, use @cpp 0 @ce for the first layer offset in the constructor. - * @see @ref hasAttribute() + * @see @ref hasAttribute(), @ref findLayerId() */ bool hasLayer(Containers::StringView layer) const; bool hasLayer(MaterialLayer layer) const; /**< @overload */ + /** + * @brief Find ID of a named layer + * + * The @p layer doesn't exist, returns @ref Containers::NullOpt. The + * lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with + * @f$ n @f$ being the layer count. + * @see @ref hasLayer() + */ + Containers::Optional findLayerId(Containers::StringView layer) const; + Containers::Optional findLayerId(MaterialLayer layer) const; /**< @overload */ + /** * @brief ID of a named layer * - * The @p layer is expected to exist. + * Like @ref findLayerId(), but the @p layer is expected to exist. * @see @ref hasLayer() */ UnsignedInt layerId(Containers::StringView layer) const; @@ -2064,6 +2108,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @cpp 0 @ce) to avoid confsing base material with a layer. If you * want to create a material consisting of just a layer, use @cpp 0 @ce * for the first layer offset in the constructor. + * @see @ref materialLayerName() */ Containers::StringView layerName(UnsignedInt layer) const; @@ -2250,7 +2295,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Whether a material layer has given attribute * * The @p layer is expected to be smaller than @ref layerCount() const. - * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer(), + * @ref findAttributeId() */ bool hasAttribute(UnsignedInt layer, Containers::StringView name) const; bool hasAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ @@ -2259,7 +2305,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Whether a named material layer has given attribute * * The @p layer is expected to exist. - * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer(), + * @ref findAttributeId() */ bool hasAttribute(Containers::StringView layer, Containers::StringView name) const; bool hasAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ @@ -2271,7 +2318,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * Equivalent to calling @ref hasAttribute(UnsignedInt, Containers::StringView) const * with @p layer set to @cpp 0 @ce. - * @see @ref tryAttribute(), @ref attributeOr() + * @see @ref tryAttribute(), @ref attributeOr(), @ref findAttributeId() */ bool hasAttribute(Containers::StringView name) const { return hasAttribute(0, name); @@ -2280,12 +2327,49 @@ class MAGNUM_TRADE_EXPORT MaterialData { return hasAttribute(0, name); } /**< @overload */ + /** + * @brief Find ID of a named attribute in given material layer + * + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * @p layer is expected to be smaller than @ref layerCount() const. The + * lookup is done in an @f$ \mathcal{O}(\log n) @f$ complexity with + * @f$ n @f$ being attribute count in given @p layer. + * @see @ref hasAttribute(), @ref attributeId() + */ + Containers::Optional findAttributeId(UnsignedInt layer, Containers::StringView name) const; + Containers::Optional findAttributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Find ID of a named attribute in a named material layer + * + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * @p layer is expected to exist. The lookup is done in an + * @f$ \mathcal{O}(m + \log n) @f$ complexity with @f$ m @f$ being + * layer count and @f$ n @f$ being attribute count in given @p layer. + * @see @ref hasLayer(), @ref hasAttribute(), @ref attributeId(), + * @ref findLayerId() + */ + Containers::Optional findAttributeId(Containers::StringView layer, Containers::StringView name) const; + Containers::Optional findAttributeId(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + Containers::Optional findAttributeId(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + Containers::Optional findAttributeId(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Find ID of a named attribute in the base material + * + * Equivalent to calling @ref findAttributeId(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + Containers::Optional findAttributeId(Containers::StringView name) const; + Containers::Optional findAttributeId(MaterialAttribute name) const; /**< @overload */ + /** * @brief ID of a named attribute in given material layer * - * The @p layer is expected to be smaller than @ref layerCount() const - * and @p name is expected to exist in that layer. - * @see @ref hasAttribute() + * Like @ref findAttributeId(UnsignedInt, Containers::StringView) const, + * but the @p name is expected to exist. + * @see @ref hasAttribute(), + * @ref attributeName(UnsignedInt, UnsignedInt) const */ UnsignedInt attributeId(UnsignedInt layer, Containers::StringView name) const; UnsignedInt attributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ @@ -2293,8 +2377,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { /** * @brief ID of a named attribute in a named material layer * - * The @p layer is expected to exist and @p name is expected to exist - * in that layer. + * Like @ref findAttributeId(Containers::StringView, Containers::StringView) const, + * but the @p name is expected to exist. * @see @ref hasLayer(), @ref hasAttribute() */ UnsignedInt attributeId(Containers::StringView layer, Containers::StringView name) const; @@ -2321,8 +2405,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * The @p layer is expected to be smaller than @ref layerCount() const * and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const * in that layer. The returned view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. - * @see @ref attributeType() + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} set. + * @see @ref attributeType(), @ref materialAttributeName() */ Containers::StringView attributeName(UnsignedInt layer, UnsignedInt id) const; @@ -2331,7 +2415,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const * in that layer. - * @see @ref hasLayer() + * @see @ref hasLayer(), @ref materialAttributeName() */ Containers::StringView attributeName(Containers::StringView layer, UnsignedInt id) const; Containers::StringView attributeName(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ @@ -2341,6 +2425,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * * Equivalent to calling @ref attributeName(UnsignedInt, UnsignedInt) const * with @p layer set to @cpp 0 @ce. + * @see @ref materialAttributeName() */ Containers::StringView attributeName(UnsignedInt id) const { return attributeName(0, id); @@ -2593,7 +2678,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @ref attributeCount(UnsignedInt) const in that layer. Expects that * @p T corresponds to @ref attributeType(UnsignedInt, UnsignedInt) const * for given @p layer and @p id. In case of a string, the returned view - * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} * set. */ template T attribute(UnsignedInt layer, UnsignedInt id) const; @@ -2619,8 +2704,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * and @p name is expected to exist in that layer. Expects that @p T * corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const * for given @p layer and @p name. In case of a string, the returned - * view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * view always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * set. * @see @ref hasLayer(), @ref hasAttribute() */ template T attribute(UnsignedInt layer, Containers::StringView name) const; @@ -2649,7 +2734,7 @@ class MAGNUM_TRADE_EXPORT MaterialData { * Expects that @p T corresponds to * @ref attributeType(Containers::StringView, UnsignedInt) const * for given @p layer and @p id. In case of a string, the returned view - * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} * set. * @see @ref hasLayer() */ @@ -2678,8 +2763,8 @@ class MAGNUM_TRADE_EXPORT MaterialData { * in that layer. Expects that @p T corresponds to * @ref attributeType(Containers::StringView, Containers::StringView) const * for given @p layer and @p name. In case of a string, the returned - * view always has - * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * view always has @relativeref{Corrade,Containers::StringViewFlag::NullTerminated} + * set. * @see @ref hasLayer(), @ref hasAttribute() */ template T attribute(Containers::StringView layer, Containers::StringView name) const; @@ -2780,9 +2865,10 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Value of a named attribute in given material layer, if exists * * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, - * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt - * instead of asserting. Expects that @p layer is smaller than - * @ref layerCount() const and that @p T corresponds to + * if @p name doesn't exist, returns + * @relativeref{Corrade,Containers::NullOpt} instead of asserting. + * Expects that @p layer is smaller than @ref layerCount() const and + * that @p T corresponds to * @ref attributeType(UnsignedInt, Containers::StringView) const for * given @p layer and @p name. */ @@ -2793,9 +2879,10 @@ class MAGNUM_TRADE_EXPORT MaterialData { * @brief Value of a named attribute in a named material layer, if exists * * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, - * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt - * instead of asserting. Expects that @p layer exists and that @p T - * corresponds to @ref attributeType(Containers::StringView, Containers::StringView) const + * if @p name doesn't exist, returns + * @relativeref{Corrade,Containers::NullOpt} instead of asserting. + * Expects that @p layer exists and that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const * for given @p layer and @p name. * @see @ref hasLayer() */ @@ -2959,14 +3046,12 @@ class MAGNUM_TRADE_EXPORT MaterialData { implementations. */ friend AbstractImporter; - static Containers::StringView layerString(MaterialLayer name); - static Containers::StringView attributeString(MaterialAttribute name); /* Internal helpers that don't assert, unlike layerId() / attributeId() */ - UnsignedInt layerFor(Containers::StringView layer) const; + UnsignedInt findLayerIdInternal(Containers::StringView layer) const; UnsignedInt layerOffset(UnsignedInt layer) const { return layer && _layerOffsets ? _layerOffsets[layer - 1] : 0; } - UnsignedInt attributeFor(UnsignedInt layer, Containers::StringView name) const; + UnsignedInt findAttributeIdInternal(UnsignedInt layer, Containers::StringView name) const; Containers::Array _data; Containers::Array _layerOffsets; @@ -3141,7 +3226,7 @@ template<> Containers::MutableStringView MaterialData::mutableAttribute T MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return attribute(layer, id); @@ -3150,26 +3235,26 @@ template T MaterialData::attribute(const UnsignedInt layer, const Conta template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const UnsignedInt layer, const Containers::StringView name) { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::mutableAttribute(): index" << layer << "out of range for" << layerCount() << "layers", *reinterpret_cast(this)); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, *reinterpret_cast(this)); return mutableAttribute(layer, id); } template T MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const UnsignedInt layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << name, *reinterpret_cast(this)); return mutableAttribute(layer, string); } template T MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); CORRADE_ASSERT(id < attributeCount(layer), @@ -3178,7 +3263,7 @@ template T MaterialData::attribute(const Containers::StringView layer, } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const UnsignedInt id) { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", *reinterpret_cast(this)); CORRADE_ASSERT(id < attributeCount(layer), @@ -3187,69 +3272,69 @@ template typename std::conditional T MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); return attribute(layerId, id); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const Containers::StringView name) { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): layer" << layer << "not found", *reinterpret_cast(this)); - const UnsignedInt id = attributeFor(layerId, name); + const UnsignedInt id = findAttributeIdInternal(layerId, name); CORRADE_ASSERT(id != ~UnsignedInt{}, "Trade::MaterialData::mutableAttribute(): attribute" << name << "not found in layer" << layer, *reinterpret_cast(this)); return mutableAttribute(layerId, id); } template T MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); return attribute(layer, string); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const Containers::StringView layer, const MaterialAttribute name) { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << name, *reinterpret_cast(this)); return mutableAttribute(layer, string); } template T MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, id); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const UnsignedInt id) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, id); } template T MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const Containers::StringView name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, name); } template T MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); return attribute(string, name); } template typename std::conditional::value, Containers::MutableStringView, T&>::type MaterialData::mutableAttribute(const MaterialLayer layer, const MaterialAttribute name) { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::mutableAttribute(): invalid name" << layer, *reinterpret_cast(this)); return mutableAttribute(string, name); } @@ -3257,38 +3342,38 @@ template typename std::conditional Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return {}; return attribute(layer, id); } template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); return tryAttribute(layerId, name); } template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); return tryAttribute(layer, string); } template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); return tryAttribute(string, name); } @@ -3296,38 +3381,38 @@ template Containers::Optional MaterialData::tryAttribute(const Mater template T MaterialData::attributeOr(const UnsignedInt layer, const Containers::StringView name, const T& defaultValue) const { CORRADE_ASSERT(layer < layerCount(), "Trade::MaterialData::attributeOr(): index" << layer << "out of range for" << layerCount() << "layers", {}); - const UnsignedInt id = attributeFor(layer, name); + const UnsignedInt id = findAttributeIdInternal(layer, name); if(id == ~UnsignedInt{}) return defaultValue; return attribute(layer, id); } template T MaterialData::attributeOr(const UnsignedInt layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); return attributeOr(layer, string, defaultValue); } template T MaterialData::attributeOr(const Containers::StringView layer, const Containers::StringView name, const T& defaultValue) const { - const UnsignedInt layerId = layerFor(layer); + const UnsignedInt layerId = findLayerIdInternal(layer); CORRADE_ASSERT(layerId != ~UnsignedInt{}, "Trade::MaterialData::attributeOr(): layer" << layer << "not found", {}); return attributeOr(layerId, name, defaultValue); } template T MaterialData::attributeOr(const Containers::StringView layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = attributeString(name); + const Containers::StringView string = Implementation::materialAttributeNameInternal(name); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); return attributeOr(layer, string, defaultValue); } template T MaterialData::attributeOr(const MaterialLayer layer, const Containers::StringView name, const T& defaultValue) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); return attributeOr(string, name, defaultValue); } template T MaterialData::attributeOr(const MaterialLayer layer, const MaterialAttribute name, const T& defaultValue) const { - const Containers::StringView string = layerString(layer); + const Containers::StringView string = Implementation::materialLayerNameInternal(layer); CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); return attributeOr(string, name, defaultValue); } diff --git a/src/Magnum/Trade/MaterialLayerData.h b/src/Magnum/Trade/MaterialLayerData.h index 7ca98eff1..6af51c24c 100644 --- a/src/Magnum/Trade/MaterialLayerData.h +++ b/src/Magnum/Trade/MaterialLayerData.h @@ -30,6 +30,8 @@ * @m_since_latest */ +#include + #include "Magnum/Math/Matrix3.h" #include "Magnum/Trade/MaterialData.h" @@ -141,6 +143,18 @@ template class MaterialLayerData: public MaterialData { return MaterialData::hasAttribute(layer, name); } /**< @overload */ + /** + * @brief Find ID of a named attribute in this layer + * + * Same as calling @ref MaterialData::findAttributeId() with @p layer. + */ + Containers::Optional findAttributeId(Containers::StringView name) const { + return MaterialData::findAttributeId(layer, name); + } + Containers::Optional findAttributeId(MaterialAttribute name) const { + return MaterialData::findAttributeId(layer, name); + } /**< @overload */ + /** * @brief ID of a named attribute in this layer * @@ -294,6 +308,7 @@ template class MaterialLayerData: public MaterialData { #if !defined(CORRADE_TARGET_MSVC) || defined(CORRADE_TARGET_CLANG_CL) using MaterialData::attributeCount; using MaterialData::hasAttribute; + using MaterialData::findAttributeId; using MaterialData::attributeId; using MaterialData::attributeName; using MaterialData::attributeType; @@ -331,6 +346,25 @@ template class MaterialLayerData: public MaterialData { return MaterialData::hasAttribute(layer_, name); } + Containers::Optional findAttributeId(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::findAttributeId(layer_, name); + } + Containers::Optional findAttributeId(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::findAttributeId(layer_, name); + } + UnsignedInt attributeId(UnsignedInt layer_, Containers::StringView name) const { return MaterialData::attributeId(layer_, name); } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index ed83af1e9..e45e32cb2 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1396,7 +1396,8 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Whether the mesh has given attribute * - * @see @ref attributeCount(MeshAttribute) const + * @see @ref attributeCount(MeshAttribute) const, + * @ref findAttributeId() */ bool hasAttribute(MeshAttribute name) const { return attributeCount(name); diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 4f88b3809..3ebbac9ca 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -584,14 +584,14 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp checked against a map and only custom fields are checked in an O(n^2) way with the assumption there isn't many of them (and that they'll gradually become builtin). */ - if(!isSceneFieldCustom(_fields[i]._name)) { - CORRADE_INTERNAL_ASSERT(UnsignedInt(_fields[i]._name) < fieldsPresent.Size); - CORRADE_ASSERT(!fieldsPresent[UnsignedInt(_fields[i]._name)], - "Trade::SceneData: duplicate field" << _fields[i]._name, ); - fieldsPresent.set(UnsignedInt(_fields[i]._name), true); + if(!isSceneFieldCustom(field._name)) { + CORRADE_INTERNAL_ASSERT(UnsignedInt(field._name) < fieldsPresent.Size); + CORRADE_ASSERT(!fieldsPresent[UnsignedInt(field._name)], + "Trade::SceneData: duplicate field" << field._name, ); + fieldsPresent.set(UnsignedInt(field._name), true); } else for(std::size_t j = 0; j != i; ++j) { - CORRADE_ASSERT(_fields[j]._name != _fields[i]._name, - "Trade::SceneData: duplicate field" << _fields[i]._name, ); + CORRADE_ASSERT(_fields[j]._name != field._name, + "Trade::SceneData: duplicate field" << field._name, ); } /* Check that both the mapping and field view fits into the provided @@ -634,21 +634,21 @@ SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mapp /* Remember TRS and mesh/material fields to figure out whether the scene is 2D or 3D and check their object mapping consistency outside of the loop below */ - if(_fields[i]._name == SceneField::Transformation) { + if(field._name == SceneField::Transformation) { transformationField = i; - } else if(_fields[i]._name == SceneField::Translation) { + } else if(field._name == SceneField::Translation) { translationField = i; - } else if(_fields[i]._name == SceneField::Rotation) { + } else if(field._name == SceneField::Rotation) { rotationField = i; - } else if(_fields[i]._name == SceneField::Scaling) { + } else if(field._name == SceneField::Scaling) { scalingField = i; } #ifndef CORRADE_NO_ASSERT - else if(_fields[i]._name == SceneField::Mesh) { + else if(field._name == SceneField::Mesh) { meshField = i; - } else if(_fields[i]._name == SceneField::MeshMaterial) { + } else if(field._name == SceneField::MeshMaterial) { meshMaterialField = i; - } else if(_fields[i]._name == SceneField::Skin) { + } else if(field._name == SceneField::Skin) { skinField = i; } #endif @@ -851,6 +851,13 @@ Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal return fieldDataFieldViewInternal(field, 0, field._size); } +std::size_t SceneData::fieldSizeBound() const { + std::size_t out = 0; + for(const SceneFieldData& i: _fields) + out = Math::max(out, std::size_t(i._size)); + return out; +} + SceneFieldData SceneData::fieldData(const UnsignedInt id) const { CORRADE_ASSERT(id < _fields.size(), "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 6f7aa2c7f..bc9243148 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -1304,6 +1304,11 @@ class MAGNUM_TRADE_EXPORT SceneData { * @m_since_latest * * Upper bound on object mapping indices of all fields in the scene. + * Note that an object can have a certain field associated with + * it multiple times with different values (for example an object + * having multiple meshes), and thus a field size can be larger than + * @ref mappingBound() --- see @ref fieldSizeBound() for an upper + * bound for all field sizes. * @see @ref fieldCount(), @ref fieldSize() */ UnsignedLong mappingBound() const { return _mappingBound; } @@ -1320,6 +1325,18 @@ class MAGNUM_TRADE_EXPORT SceneData { */ UnsignedInt fieldCount() const { return _fields.size(); } + /** + * @brief Field size bound + * @m_since_latest + * + * A maximum of all @ref fieldSize() or @cpp 0 @ce for a scene with no + * fields. Note that an object can have a certain field associated with + * it multiple times with different values (for example an object + * having multiple meshes), and thus a field size can be larger than + * @ref mappingBound(). + */ + std::size_t fieldSizeBound() const; + /** * @brief Raw field metadata * @m_since_latest @@ -1488,7 +1505,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * @brief Whether the scene has given field * @m_since_latest * - * @see @ref is2D(), @ref is3D() + * @see @ref is2D(), @ref is3D(), @ref findFieldId() */ bool hasField(SceneField name) const; diff --git a/src/Magnum/Trade/SkinData.h b/src/Magnum/Trade/SkinData.h index d64f05c63..a3c5fa8f5 100644 --- a/src/Magnum/Trade/SkinData.h +++ b/src/Magnum/Trade/SkinData.h @@ -90,6 +90,8 @@ template class SkinData { /** @brief Move assignment */ SkinData& operator=(SkinData&& other) noexcept; + /** @todo expose DataFlags (so users can know if the data are externally owned, at least) */ + /** * @brief Joint IDs * diff --git a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp index 71af13a11..078ee968f 100644 --- a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp @@ -56,6 +56,10 @@ struct AbstractImageConverterTest: TestSuite::Tester { void thingNotSupported(); + void extensionMimeType(); + void extensionMimeTypeNotImplemented(); + void extensionMimeTypeCustomDeleter(); + void convert1D(); void convert2D(); void convert3D(); @@ -292,7 +296,13 @@ struct AbstractImageConverterTest: TestSuite::Tester { void convertCompressed3DToFileThroughLevels(); void debugFeature(); + void debugFeaturePacked(); + #ifdef MAGNUM_BUILD_DEPRECATED + void debugFeatureDeprecated(); + void debugFeatureDeprecatedPacked(); + #endif void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); @@ -307,6 +317,10 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::thingNotSupported, + &AbstractImageConverterTest::extensionMimeType, + &AbstractImageConverterTest::extensionMimeTypeNotImplemented, + &AbstractImageConverterTest::extensionMimeTypeCustomDeleter, + &AbstractImageConverterTest::convert1D, &AbstractImageConverterTest::convert2D, &AbstractImageConverterTest::convert3D, @@ -524,7 +538,13 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::convertCompressed3DToFileThroughLevels, &AbstractImageConverterTest::debugFeature, + &AbstractImageConverterTest::debugFeaturePacked, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImageConverterTest::debugFeatureDeprecated, + &AbstractImageConverterTest::debugFeatureDeprecatedPacked, + #endif &AbstractImageConverterTest::debugFeatures, + &AbstractImageConverterTest::debugFeaturesPacked, &AbstractImageConverterTest::debugFeaturesSupersets, &AbstractImageConverterTest::debugFlag, &AbstractImageConverterTest::debugFlags}); @@ -599,6 +619,8 @@ void AbstractImageConverterTest::thingNotSupported() { std::ostringstream out; Error redirectError{&out}; + converter.extension(); + converter.mimeType(); converter.convert(ImageView1D{PixelFormat::R8Unorm, 0, nullptr}); converter.convert(ImageView2D{PixelFormat::R8Unorm, {}, nullptr}); converter.convert(ImageView3D{PixelFormat::R8Unorm, {}, nullptr}); @@ -630,6 +652,8 @@ void AbstractImageConverterTest::thingNotSupported() { converter.convertToFile({CompressedImageView2D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); converter.convertToFile({CompressedImageView3D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): file conversion not supported\n" + "Trade::AbstractImageConverter::mimeType(): file conversion not supported\n" "Trade::AbstractImageConverter::convert(): 1D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 2D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 3D image conversion not supported\n" @@ -662,6 +686,54 @@ void AbstractImageConverterTest::thingNotSupported() { "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion not supported\n"); } +void AbstractImageConverterTest::extensionMimeType() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData; + } + Containers::String doExtension() const override { return "yello"; } + Containers::String doMimeType() const override { return "yel/low"; } + } converter; + + CORRADE_COMPARE(converter.extension(), "yello"); + CORRADE_COMPARE(converter.mimeType(), "yel/low"); +} + +void AbstractImageConverterTest::extensionMimeTypeNotImplemented() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile; + } + } converter; + + CORRADE_COMPARE(converter.extension(), ""); + CORRADE_COMPARE(converter.mimeType(), ""); +} + +void AbstractImageConverterTest::extensionMimeTypeCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData; + } + Containers::String doExtension() const override { + return Containers::String{"yello", 5, [](char*, std::size_t) {}}; + } + Containers::String doMimeType() const override { + return Containers::String{"yel/low", 7, [](char*, std::size_t) {}}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.extension(); + converter.mimeType(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter\n" + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter\n"); +} + void AbstractImageConverterTest::convert1D() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::Convert1D; } @@ -1706,7 +1778,10 @@ void AbstractImageConverterTest::convertImageData3DToData() { void AbstractImageConverterTest::convertLevels1DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1725,7 +1800,10 @@ void AbstractImageConverterTest::convertLevels1DToData() { void AbstractImageConverterTest::convertLevels2DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1744,7 +1822,10 @@ void AbstractImageConverterTest::convertLevels2DToData() { void AbstractImageConverterTest::convertLevels3DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -1764,7 +1845,8 @@ void AbstractImageConverterTest::convertLevels3DToData() { void AbstractImageConverterTest::convertLevels1DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels1DToData; + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1783,7 +1865,8 @@ void AbstractImageConverterTest::convertLevels1DToDataFailed() { void AbstractImageConverterTest::convertLevels2DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels2DToData; + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1802,7 +1885,8 @@ void AbstractImageConverterTest::convertLevels2DToDataFailed() { void AbstractImageConverterTest::convertLevels3DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels3DToData; + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -1822,7 +1906,10 @@ void AbstractImageConverterTest::convertLevels1DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -1835,7 +1922,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNoLevels() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -1848,7 +1938,10 @@ void AbstractImageConverterTest::convertLevels2DToDataZeroSize() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1865,7 +1958,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNullptr() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1882,7 +1978,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFormat() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1900,7 +1999,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFormatExtra() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1918,7 +2020,10 @@ void AbstractImageConverterTest::convertLevels2DToDataInconsistentFlags() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -1936,7 +2041,10 @@ void AbstractImageConverterTest::convertLevels3DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -1949,7 +2057,10 @@ void AbstractImageConverterTest::convertLevels1DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -1963,7 +2074,10 @@ void AbstractImageConverterTest::convertLevels2DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -1977,7 +2091,10 @@ void AbstractImageConverterTest::convertLevels3DToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -1991,7 +2108,10 @@ void AbstractImageConverterTest::convertLevels1DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2008,7 +2128,10 @@ void AbstractImageConverterTest::convertLevels2DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2025,7 +2148,10 @@ void AbstractImageConverterTest::convertLevels3DToDataCustomDeleter() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2040,7 +2166,10 @@ void AbstractImageConverterTest::convertLevels3DToDataCustomDeleter() { void AbstractImageConverterTest::convertCompressedLevels1DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2059,7 +2188,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToData() { void AbstractImageConverterTest::convertCompressedLevels2DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2078,7 +2210,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToData() { void AbstractImageConverterTest::convertCompressedLevels3DToData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2098,7 +2233,8 @@ void AbstractImageConverterTest::convertCompressedLevels3DToData() { void AbstractImageConverterTest::convertCompressedLevels1DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels1DToData; + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2117,7 +2253,8 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataFailed() { void AbstractImageConverterTest::convertCompressedLevels2DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels2DToData; + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2136,7 +2273,8 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataFailed() { void AbstractImageConverterTest::convertCompressedLevels3DToDataFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels3DToData; + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -2156,7 +2294,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2169,7 +2310,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNoLevels() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2182,7 +2326,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataZeroSize() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2199,7 +2346,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNullptr() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2216,7 +2366,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataInconsistentForm CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2234,7 +2387,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataInconsistentFlag CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[16]{}; @@ -2252,7 +2408,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -2265,7 +2424,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2279,7 +2441,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2293,7 +2458,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -2307,7 +2475,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2324,7 +2495,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2341,7 +2515,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataCustomDeleter() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::Array{nullptr, 0, [](char*, std::size_t) {}}; } @@ -2356,7 +2533,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToDataCustomDeleter() void AbstractImageConverterTest::convert1DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2370,7 +2550,10 @@ void AbstractImageConverterTest::convert1DToDataThroughLevels() { void AbstractImageConverterTest::convert2DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2384,7 +2567,10 @@ void AbstractImageConverterTest::convert2DToDataThroughLevels() { void AbstractImageConverterTest::convert3DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2398,7 +2584,10 @@ void AbstractImageConverterTest::convert3DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed1DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2412,7 +2601,10 @@ void AbstractImageConverterTest::convertCompressed1DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed2DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -2426,7 +2618,10 @@ void AbstractImageConverterTest::convertCompressed2DToDataThroughLevels() { void AbstractImageConverterTest::convertCompressed3DToDataThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::Array{nullptr, std::size_t(imageLevels[0].size().product()*imageLevels.size())}; } @@ -3250,7 +3445,10 @@ void AbstractImageConverterTest::convertImageData3DToFile() { void AbstractImageConverterTest::convertLevels1DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -3274,7 +3472,10 @@ void AbstractImageConverterTest::convertLevels1DToFile() { void AbstractImageConverterTest::convertLevels2DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -3298,7 +3499,10 @@ void AbstractImageConverterTest::convertLevels2DToFile() { void AbstractImageConverterTest::convertLevels3DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -3323,7 +3527,8 @@ void AbstractImageConverterTest::convertLevels3DToFile() { void AbstractImageConverterTest::convertLevels1DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels1DToFile; + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3342,7 +3547,8 @@ void AbstractImageConverterTest::convertLevels1DToFileFailed() { void AbstractImageConverterTest::convertLevels2DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels2DToFile; + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3361,7 +3567,8 @@ void AbstractImageConverterTest::convertLevels2DToFileFailed() { void AbstractImageConverterTest::convertLevels3DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertLevels3DToFile; + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3379,7 +3586,10 @@ void AbstractImageConverterTest::convertLevels3DToFileFailed() { void AbstractImageConverterTest::convertLevels1DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size()[0]), char(imageLevels.size())}); @@ -3405,7 +3615,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughData() { void AbstractImageConverterTest::convertLevels2DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())}); @@ -3430,7 +3643,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughData() { void AbstractImageConverterTest::convertLevels3DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())}); @@ -3455,7 +3671,10 @@ void AbstractImageConverterTest::convertLevels3DToFileThroughData() { void AbstractImageConverterTest::convertLevels1DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3480,7 +3699,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels2DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3505,7 +3727,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels3DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3530,7 +3755,10 @@ void AbstractImageConverterTest::convertLevels3DToFileThroughDataFailed() { void AbstractImageConverterTest::convertLevels1DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); }; @@ -3547,7 +3775,10 @@ void AbstractImageConverterTest::convertLevels1DToFileThroughDataNotWritable() { void AbstractImageConverterTest::convertLevels2DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3565,7 +3796,10 @@ void AbstractImageConverterTest::convertLevels2DToFileThroughDataNotWritable() { void AbstractImageConverterTest::convertLevels3DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3585,7 +3819,10 @@ void AbstractImageConverterTest::convertLevels1DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3598,7 +3835,10 @@ void AbstractImageConverterTest::convertLevels2DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3611,7 +3851,10 @@ void AbstractImageConverterTest::convertLevels3DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -3624,7 +3867,10 @@ void AbstractImageConverterTest::convertLevels1DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3638,7 +3884,10 @@ void AbstractImageConverterTest::convertLevels2DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3652,7 +3901,10 @@ void AbstractImageConverterTest::convertLevels3DToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[4]{}; @@ -3664,7 +3916,10 @@ void AbstractImageConverterTest::convertLevels3DToFileNotImplemented() { void AbstractImageConverterTest::convertCompressedLevels1DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -3687,7 +3942,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFile() { void AbstractImageConverterTest::convertCompressedLevels2DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -3710,7 +3968,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFile() { void AbstractImageConverterTest::convertCompressedLevels3DToFile() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -3734,7 +3995,8 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFile() { void AbstractImageConverterTest::convertCompressedLevels1DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels1DToFile; + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3753,7 +4015,8 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels2DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels2DToFile; + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3772,7 +4035,8 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels3DToFileFailed() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { - return ImageConverterFeature::ConvertCompressedLevels3DToFile; + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; } bool doConvertToFile(Containers::ArrayView, Containers::StringView) override { return {}; @@ -3790,7 +4054,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileFailed() { void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size()[0]), char(imageLevels.size())}); @@ -3815,7 +4082,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())}); @@ -3840,7 +4110,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughData() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView imageLevels) override { return Containers::array({char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())}); @@ -3865,7 +4138,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughData() { void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3890,7 +4166,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3915,7 +4194,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataFailed() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return {}; @@ -3940,7 +4222,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataFaile void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3958,7 +4243,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileThroughDataNotWr void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3976,7 +4264,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileThroughDataNotWr void AbstractImageConverterTest::convertCompressedLevels3DToFileThroughDataNotWritable() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToData; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToData| + ImageConverterFeature::Levels; + } Containers::Optional> doConvertToData(Containers::ArrayView) override { return Containers::array({'\x00'}); @@ -3996,7 +4287,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4009,7 +4303,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4022,7 +4319,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileInvalidImage() { CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } } converter; std::ostringstream out; @@ -4035,7 +4335,10 @@ void AbstractImageConverterTest::convertCompressedLevels1DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4049,7 +4352,10 @@ void AbstractImageConverterTest::convertCompressedLevels2DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4063,7 +4369,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileNotImplemented() CORRADE_SKIP_IF_NO_ASSERT(); struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } } converter; const char data[8]{}; @@ -4075,7 +4384,10 @@ void AbstractImageConverterTest::convertCompressedLevels3DToFileNotImplemented() void AbstractImageConverterTest::convert1DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -4094,7 +4406,10 @@ void AbstractImageConverterTest::convert1DToFileThroughLevels() { void AbstractImageConverterTest::convert2DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -4113,7 +4428,10 @@ void AbstractImageConverterTest::convert2DToFileThroughLevels() { void AbstractImageConverterTest::convert3DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -4132,7 +4450,10 @@ void AbstractImageConverterTest::convert3DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed1DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels1DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size()[0]), char(imageLevels.size())})); @@ -4151,7 +4472,10 @@ void AbstractImageConverterTest::convertCompressed1DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed2DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels2DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels.size())})); @@ -4170,7 +4494,10 @@ void AbstractImageConverterTest::convertCompressed2DToFileThroughLevels() { void AbstractImageConverterTest::convertCompressed3DToFileThroughLevels() { struct: AbstractImageConverter { - ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::ConvertCompressedLevels3DToFile; } + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed3DToFile| + ImageConverterFeature::Levels; + } bool doConvertToFile(Containers::ArrayView imageLevels, Containers::StringView filename) override { return Utility::Path::write(filename, Containers::arrayView( {char(imageLevels[0].size().x()), char(imageLevels[0].size().y()), char(imageLevels[0].size().z()), char(imageLevels.size())})); @@ -4194,6 +4521,34 @@ void AbstractImageConverterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed2D Trade::ImageConverterFeature(0xdeadbeef)\n"); } +void AbstractImageConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImageConverterFeature::ConvertCompressed2D << Debug::packed << ImageConverterFeature(0xdeadbeef) << ImageConverterFeature::Convert3D; + CORRADE_COMPARE(out.str(), "ConvertCompressed2D 0xdeadbeef Trade::ImageConverterFeature::Convert3D\n"); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImageConverterTest::debugFeatureDeprecated() { + std::ostringstream out; + + CORRADE_IGNORE_DEPRECATED_PUSH + Debug{&out} << ImageConverterFeature::ConvertCompressedLevels1DToData << ImageConverterFeature::ConvertLevels3DToFile; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed1DToData|Trade::ImageConverterFeature::Levels Trade::ImageConverterFeature::Convert3DToFile|Trade::ImageConverterFeature::Levels\n"); +} + +void AbstractImageConverterTest::debugFeatureDeprecatedPacked() { + std::ostringstream out; + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImageConverterFeature::ConvertCompressedLevels1DToData << Debug::packed << ImageConverterFeature::ConvertLevels3DToFile << ImageConverterFeature::Convert1D; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "ConvertCompressed1DToData|Levels Convert3DToFile|Levels Trade::ImageConverterFeature::Convert1D\n"); +} +#endif + void AbstractImageConverterTest::debugFeatures() { std::ostringstream out; @@ -4201,6 +4556,13 @@ void AbstractImageConverterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::Convert2DToData|Trade::ImageConverterFeature::ConvertCompressed2DToFile Trade::ImageConverterFeatures{}\n"); } +void AbstractImageConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImageConverterFeature::Convert2DToData|ImageConverterFeature::ConvertCompressed2DToFile) << Debug::packed << ImageConverterFeatures{} << ImageConverterFeature::Convert1D; + CORRADE_COMPARE(out.str(), "Convert2DToData|ConvertCompressed2DToFile {} Trade::ImageConverterFeature::Convert1D\n"); +} + void AbstractImageConverterTest::debugFeaturesSupersets() { /* Convert*DToData is a superset of Convert*DToFile, so only one should be printed */ @@ -4215,34 +4577,6 @@ void AbstractImageConverterTest::debugFeaturesSupersets() { std::ostringstream out; Debug{&out} << (ImageConverterFeature::ConvertCompressed1DToData|ImageConverterFeature::ConvertCompressed1DToFile); CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressed1DToData\n"); - - /* ConvertLevels*DToData is a superset of ConvertLevels*DToFile, so only - one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertLevels2DToData|ImageConverterFeature::ConvertLevels2DToFile); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertLevels2DToData\n"); - - /* ConvertLevels*DToData is *also* a superset of Convert*DToData, so only - one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertLevels3DToData|ImageConverterFeature::Convert3DToData); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertLevels3DToData\n"); - - /* ConvertCompressedLevels*DToData is a superset of - ConvertCompressedLevels*DToFile, so only one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertCompressedLevels1DToData|ImageConverterFeature::ConvertCompressedLevels1DToFile); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressedLevels1DToData\n"); - - /* ConvertCompressedLevels*DToData is *also* a superset of - ConvertCompressed*DToData, so only one should be printed */ - } { - std::ostringstream out; - Debug{&out} << (ImageConverterFeature::ConvertCompressedLevels3DToData|ImageConverterFeature::ConvertCompressed3DToData); - CORRADE_COMPARE(out.str(), "Trade::ImageConverterFeature::ConvertCompressedLevels3DToData\n"); } } diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index c8464df4f..23dd7fc25 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -373,7 +373,9 @@ struct AbstractImporterTest: TestSuite::Tester { void importerStateNoFile(); void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFlag(); void debugFlags(); }; @@ -699,7 +701,9 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::importerStateNoFile, &AbstractImporterTest::debugFeature, + &AbstractImporterTest::debugFeaturePacked, &AbstractImporterTest::debugFeatures, + &AbstractImporterTest::debugFeaturesPacked, &AbstractImporterTest::debugFlag, &AbstractImporterTest::debugFlags}); } @@ -7695,6 +7699,13 @@ void AbstractImporterTest::debugFeature() { CORRADE_COMPARE(out.str(), "Trade::ImporterFeature::OpenData Trade::ImporterFeature(0xf0)\n"); } +void AbstractImporterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << ImporterFeature::OpenData << Debug::packed << ImporterFeature(0xf0) << ImporterFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData 0xf0 Trade::ImporterFeature::FileCallback\n"); +} + void AbstractImporterTest::debugFeatures() { std::ostringstream out; @@ -7702,6 +7713,13 @@ void AbstractImporterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::ImporterFeature::OpenData|Trade::ImporterFeature::OpenState Trade::ImporterFeatures{}\n"); } +void AbstractImporterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (ImporterFeature::OpenData|ImporterFeature::OpenState) << Debug::packed << ImporterFeatures{} << ImporterFeature::FileCallback; + CORRADE_COMPARE(out.str(), "OpenData|OpenState {} Trade::ImporterFeature::FileCallback\n"); +} + void AbstractImporterTest::debugFlag() { std::ostringstream out; diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 5cee66b72..dc0519ea9 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove once Debug is stream-free */ #include @@ -34,10 +35,23 @@ #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AbstractSceneConverter.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/CameraData.h" +#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" #include "configure.h" @@ -52,6 +66,8 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void setFlagsNotImplemented(); void thingNotSupported(); + /* Certain features need a combination of flags, test them explicitly */ + void thingLevelsNotSupported(); void convertMesh(); void convertMeshFailed(); @@ -68,6 +84,9 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToData(); void convertMeshToDataFailed(); + void convertMeshToDataThroughBatch(); + void convertMeshToDataThroughBatchAddFailed(); + void convertMeshToDataThroughBatchEndFailed(); void convertMeshToDataNotImplemented(); void convertMeshToDataNonOwningDeleter(); void convertMeshToDataGrowableDeleter(); @@ -78,15 +97,194 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToFileThroughData(); void convertMeshToFileThroughDataFailed(); void convertMeshToFileThroughDataNotWritable(); + void convertMeshToFileThroughBatch(); + void convertMeshToFileThroughBatchAddFailed(); + void convertMeshToFileThroughBatchEndFailed(); void convertMeshToFileNotImplemented(); + void beginEnd(); + void beginFailed(); + void endFailed(); + void beginNotImplemented(); + void endNotImplemented(); + + void beginEndData(); + void beginDataFailed(); + void endDataFailed(); + void beginDataNotImplemented(); + void endDataNotImplemented(); + void beginEndDataCustomDeleter(); + + void beginEndFile(); + void beginFileFailed(); + void endFileFailed(); + void beginEndFileThroughData(); + void beginEndFileThroughDataFailed(); + void beginEndFileThroughDataNotWritable(); + void beginFileNotImplemented(); + void endFileNotImplemented(); + + void abort(); + void abortNotImplemented(); + void abortImplicitlyConvertMesh(); + void abortImplicitlyConvertMeshInPlace(); + void abortImplicitlyConvertMeshToData(); + void abortImplicitlyConvertMeshToFile(); + void abortImplicitlyBegin(); + void abortImplicitlyBeginData(); + void abortImplicitlyBeginFile(); + + void thingNoBegin(); + void endMismatchedBegin(); + void endDataMismatchedBegin(); + void endFileMismatchedBegin(); + + void addScene(); + void addSceneFailed(); + void addSceneNotImplemented(); + + void setSceneFieldName(); + void setSceneFieldNameNotImplemented(); + void setSceneFieldNameNotCustom(); + + void setObjectName(); + void setObjectNameNotImplemented(); + + void setDefaultScene(); + void setDefaultSceneNotImplemented(); + void setDefaultSceneOutOfRange(); + + void addAnimation(); + void addAnimationFailed(); + void addAnimationNotImplemented(); + + void addLight(); + void addLightFailed(); + void addLightNotImplemented(); + + void addCamera(); + void addCameraFailed(); + void addCameraNotImplemented(); + + void addSkin2D(); + void addSkin2DFailed(); + void addSkin2DNotImplemented(); + + void addSkin3D(); + void addSkin3DFailed(); + void addSkin3DNotImplemented(); + + void addMesh(); + void addMeshFailed(); + void addMeshThroughConvertMesh(); + void addMeshThroughConvertMeshFailed(); + void addMeshThroughConvertMeshZeroMeshes(); + void addMeshThroughConvertMeshTwoMeshes(); + void addMeshThroughConvertMeshToData(); + void addMeshThroughConvertMeshToDataFailed(); + void addMeshThroughConvertMeshToDataZeroMeshes(); + void addMeshThroughConvertMeshToDataTwoMeshes(); + void addMeshThroughConvertMeshToFile(); + void addMeshThroughConvertMeshToFileThroughData(); + void addMeshThroughConvertMeshToFileFailed(); + void addMeshThroughConvertMeshToFileZeroMeshes(); + void addMeshThroughConvertMeshToFileTwoMeshes(); + void addMeshNotImplemented(); + + void addMeshLevels(); + void addMeshLevelsFailed(); + void addMeshLevelsNoLevels(); + void addMeshLevelsNotImplemented(); + + void addMeshThroughLevels(); + + void setMeshAttributeName(); + void setMeshAttributeNameNotImplemented(); + void setMeshAttributeNameNotCustom(); + + void addMaterial(); + void addMaterialFailed(); + void addMaterialNotImplemented(); + + void addTexture(); + void addTextureFailed(); + void addTextureNotImplemented(); + + void addImage1D(); + void addImage1DView(); + void addImage1DCompressedView(); + void addImage1DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage1DInvalidImage(); + void addImage1DNotImplemented(); + + void addImage2D(); + void addImage2DView(); + void addImage2DCompressedView(); + void addImage2DFailed(); + void addImage2DZeroSize(); + void addImage2DNullptr(); + void addImage2DNotImplemented(); + + void addImage3D(); + void addImage3DView(); + void addImage3DCompressedView(); + void addImage3DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage3DInvalidImage(); + void addImage3DNotImplemented(); + + void addImageLevels1D(); + void addImageLevels1DView(); + void addImageLevels1DCompressedView(); + void addImageLevels1DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels1DInvalidImage(); + void addImageLevels1DNotImplemented(); + + void addImageLevels2D(); + void addImageLevels2DView(); + void addImageLevels2DCompressedView(); + void addImageLevels2DFailed(); + void addImageLevels2DNoLevels(); + void addImageLevels2DZeroSize(); + void addImageLevels2DNullptr(); + void addImageLevels2DInconsistentCompressed(); + void addImageLevels2DInconsistentFormat(); + void addImageLevels2DInconsistentFormatExtra(); + void addImageLevels2DInconsistentCompressedFormat(); + void addImageLevels2DInconsistentFlags(); + void addImageLevels2DNotImplemented(); + + void addImageLevels3D(); + void addImageLevels3DView(); + void addImageLevels3DCompressedView(); + void addImageLevels3DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels3DInvalidImage(); + void addImageLevels3DNotImplemented(); + + void addImage1DThroughLevels(); + void addImage2DThroughLevels(); + void addImage3DThroughLevels(); + void debugFeature(); + void debugFeaturePacked(); void debugFeatures(); + void debugFeaturesPacked(); void debugFeaturesSupersets(); void debugFlag(); void debugFlags(); }; +using namespace Containers::Literals; + AbstractSceneConverterTest::AbstractSceneConverterTest() { addTests({&AbstractSceneConverterTest::featuresNone, @@ -94,6 +292,7 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::setFlagsNotImplemented, &AbstractSceneConverterTest::thingNotSupported, + &AbstractSceneConverterTest::thingLevelsNotSupported, &AbstractSceneConverterTest::convertMesh, &AbstractSceneConverterTest::convertMeshFailed, @@ -110,6 +309,9 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToData, &AbstractSceneConverterTest::convertMeshToDataFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatch, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToDataNotImplemented, &AbstractSceneConverterTest::convertMeshToDataNonOwningDeleter, &AbstractSceneConverterTest::convertMeshToDataGrowableDeleter, @@ -120,10 +322,177 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToFileThroughData, &AbstractSceneConverterTest::convertMeshToFileThroughDataFailed, &AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable, + &AbstractSceneConverterTest::convertMeshToFileThroughBatch, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToFileNotImplemented, + &AbstractSceneConverterTest::beginEnd, + &AbstractSceneConverterTest::beginFailed, + &AbstractSceneConverterTest::endFailed, + &AbstractSceneConverterTest::beginNotImplemented, + &AbstractSceneConverterTest::endNotImplemented, + + &AbstractSceneConverterTest::beginEndData, + &AbstractSceneConverterTest::beginDataFailed, + &AbstractSceneConverterTest::endDataFailed, + &AbstractSceneConverterTest::beginDataNotImplemented, + &AbstractSceneConverterTest::endDataNotImplemented, + &AbstractSceneConverterTest::beginEndDataCustomDeleter, + + &AbstractSceneConverterTest::beginEndFile, + &AbstractSceneConverterTest::beginFileFailed, + &AbstractSceneConverterTest::endFileFailed, + &AbstractSceneConverterTest::beginEndFileThroughData, + &AbstractSceneConverterTest::beginEndFileThroughDataFailed, + &AbstractSceneConverterTest::beginEndFileThroughDataNotWritable, + &AbstractSceneConverterTest::beginFileNotImplemented, + &AbstractSceneConverterTest::endFileNotImplemented, + + &AbstractSceneConverterTest::abort, + &AbstractSceneConverterTest::abortNotImplemented, + &AbstractSceneConverterTest::abortImplicitlyConvertMesh, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshInPlace, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshToData, + &AbstractSceneConverterTest::abortImplicitlyConvertMeshToFile, + &AbstractSceneConverterTest::abortImplicitlyBegin, + &AbstractSceneConverterTest::abortImplicitlyBeginData, + &AbstractSceneConverterTest::abortImplicitlyBeginFile, + + &AbstractSceneConverterTest::thingNoBegin, + &AbstractSceneConverterTest::endMismatchedBegin, + &AbstractSceneConverterTest::endDataMismatchedBegin, + &AbstractSceneConverterTest::endFileMismatchedBegin, + + &AbstractSceneConverterTest::addScene, + &AbstractSceneConverterTest::addSceneFailed, + &AbstractSceneConverterTest::addSceneNotImplemented, + + &AbstractSceneConverterTest::setSceneFieldName, + &AbstractSceneConverterTest::setSceneFieldNameNotImplemented, + &AbstractSceneConverterTest::setSceneFieldNameNotCustom, + + &AbstractSceneConverterTest::setObjectName, + &AbstractSceneConverterTest::setObjectNameNotImplemented, + + &AbstractSceneConverterTest::setDefaultScene, + &AbstractSceneConverterTest::setDefaultSceneNotImplemented, + &AbstractSceneConverterTest::setDefaultSceneOutOfRange, + + &AbstractSceneConverterTest::addAnimation, + &AbstractSceneConverterTest::addAnimationFailed, + &AbstractSceneConverterTest::addAnimationNotImplemented, + + &AbstractSceneConverterTest::addLight, + &AbstractSceneConverterTest::addLightFailed, + &AbstractSceneConverterTest::addLightNotImplemented, + + &AbstractSceneConverterTest::addCamera, + &AbstractSceneConverterTest::addCameraFailed, + &AbstractSceneConverterTest::addCameraNotImplemented, + + &AbstractSceneConverterTest::addSkin2D, + &AbstractSceneConverterTest::addSkin2DFailed, + &AbstractSceneConverterTest::addSkin2DNotImplemented, + + &AbstractSceneConverterTest::addSkin3D, + &AbstractSceneConverterTest::addSkin3DFailed, + &AbstractSceneConverterTest::addSkin3DNotImplemented, + + &AbstractSceneConverterTest::addMesh, + &AbstractSceneConverterTest::addMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMesh, + &AbstractSceneConverterTest::addMeshThroughConvertMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFile, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes, + &AbstractSceneConverterTest::addMeshNotImplemented, + + &AbstractSceneConverterTest::addMeshLevels, + &AbstractSceneConverterTest::addMeshLevelsFailed, + &AbstractSceneConverterTest::addMeshLevelsNoLevels, + &AbstractSceneConverterTest::addMeshLevelsNotImplemented, + + &AbstractSceneConverterTest::addMeshThroughLevels, + + &AbstractSceneConverterTest::setMeshAttributeName, + &AbstractSceneConverterTest::setMeshAttributeNameNotImplemented, + &AbstractSceneConverterTest::setMeshAttributeNameNotCustom, + + &AbstractSceneConverterTest::addMaterial, + &AbstractSceneConverterTest::addMaterialFailed, + &AbstractSceneConverterTest::addMaterialNotImplemented, + + &AbstractSceneConverterTest::addTexture, + &AbstractSceneConverterTest::addTextureFailed, + &AbstractSceneConverterTest::addTextureNotImplemented, + + &AbstractSceneConverterTest::addImage1D, + &AbstractSceneConverterTest::addImage1DView, + &AbstractSceneConverterTest::addImage1DCompressedView, + &AbstractSceneConverterTest::addImage1DFailed, + &AbstractSceneConverterTest::addImage1DInvalidImage, + &AbstractSceneConverterTest::addImage1DNotImplemented, + + &AbstractSceneConverterTest::addImage2D, + &AbstractSceneConverterTest::addImage2DView, + &AbstractSceneConverterTest::addImage2DCompressedView, + &AbstractSceneConverterTest::addImage2DFailed, + &AbstractSceneConverterTest::addImage2DZeroSize, + &AbstractSceneConverterTest::addImage2DNullptr, + &AbstractSceneConverterTest::addImage2DNotImplemented, + + &AbstractSceneConverterTest::addImage3D, + &AbstractSceneConverterTest::addImage3DView, + &AbstractSceneConverterTest::addImage3DCompressedView, + &AbstractSceneConverterTest::addImage3DFailed, + &AbstractSceneConverterTest::addImage3DInvalidImage, + &AbstractSceneConverterTest::addImage3DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels1D, + &AbstractSceneConverterTest::addImageLevels1DView, + &AbstractSceneConverterTest::addImageLevels1DCompressedView, + &AbstractSceneConverterTest::addImageLevels1DFailed, + &AbstractSceneConverterTest::addImageLevels1DInvalidImage, + &AbstractSceneConverterTest::addImageLevels1DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels2D, + &AbstractSceneConverterTest::addImageLevels2DView, + &AbstractSceneConverterTest::addImageLevels2DCompressedView, + &AbstractSceneConverterTest::addImageLevels2DFailed, + &AbstractSceneConverterTest::addImageLevels2DNoLevels, + &AbstractSceneConverterTest::addImageLevels2DZeroSize, + &AbstractSceneConverterTest::addImageLevels2DNullptr, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFlags, + &AbstractSceneConverterTest::addImageLevels2DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels3D, + &AbstractSceneConverterTest::addImageLevels3DView, + &AbstractSceneConverterTest::addImageLevels3DCompressedView, + &AbstractSceneConverterTest::addImageLevels3DFailed, + &AbstractSceneConverterTest::addImageLevels3DInvalidImage, + &AbstractSceneConverterTest::addImageLevels3DNotImplemented, + + &AbstractSceneConverterTest::addImage1DThroughLevels, + &AbstractSceneConverterTest::addImage2DThroughLevels, + &AbstractSceneConverterTest::addImage3DThroughLevels, + &AbstractSceneConverterTest::debugFeature, + &AbstractSceneConverterTest::debugFeaturePacked, &AbstractSceneConverterTest::debugFeatures, + &AbstractSceneConverterTest::debugFeaturesPacked, &AbstractSceneConverterTest::debugFeaturesSupersets, &AbstractSceneConverterTest::debugFlag, &AbstractSceneConverterTest::debugFlags}); @@ -194,11 +563,19 @@ void AbstractSceneConverterTest::thingNotSupported() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { /* Assuming this bit is unused */ - return SceneConverterFeature(1 << 7); + return SceneConverterFeature(1u << 31); } } converter; - MeshData mesh{MeshPrimitive::Triangles, 3}; + MeshData mesh{MeshPrimitive::Triangles, 0}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; std::ostringstream out; Error redirectError{&out}; @@ -206,11 +583,131 @@ void AbstractSceneConverterTest::thingNotSupported() { converter.convertInPlace(mesh); converter.convertToData(mesh); converter.convertToFile(mesh, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.begin(); + converter.beginData(); + converter.beginFile(Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.add(AnimationData{nullptr, nullptr}); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + converter.add(SkinData2D{nullptr, nullptr}); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.add(mesh); + converter.add({mesh, mesh}); + converter.setMeshAttributeName({}, {}); + + converter.add(MaterialData{{}, nullptr}); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + converter.add(image1D); + converter.add(compressedImage1D); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + + converter.add(image2D); + converter.add(compressedImage2D); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + + converter.add(image3D); + converter.add(compressedImage3D); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convert(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported\n" - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n"); + "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n" + + "Trade::AbstractSceneConverter::begin(): feature not supported\n" + "Trade::AbstractSceneConverter::beginData(): feature not supported\n" + "Trade::AbstractSceneConverter::beginFile(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): scene conversion not supported\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported\n" + "Trade::AbstractSceneConverter::setObjectName(): feature not supported\n" + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): animation conversion not supported\n" + "Trade::AbstractSceneConverter::add(): light conversion not supported\n" + "Trade::AbstractSceneConverter::add(): camera conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): material conversion not supported\n" + "Trade::AbstractSceneConverter::add(): texture conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); +} + +void AbstractSceneConverterTest::thingLevelsNotSupported() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D; + } + } converter; + + MeshData mesh{MeshPrimitive::Triangles, 3}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({mesh, mesh}); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); } void AbstractSceneConverterTest::convertMesh() { @@ -461,6 +958,96 @@ void AbstractSceneConverterTest::convertMeshToDataFailed() { CORRADE_COMPARE(out.str(), ""); } +void AbstractSceneConverterTest::convertMeshToDataThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + Containers::Optional> doConvertToData(const Magnum::Trade::MeshData&) override { + CORRADE_FAIL_IF(true, "doConvertToData() should not be called"); + return {}; + } + + bool doBeginData() override { + _vertexCount = 42; + return true; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount *= mesh.vertexCount(); + return true; + } + + Containers::Optional> doEndData() override { + return Containers::Array{nullptr, _vertexCount}; + } + + std::size_t _vertexCount = 0; + } converter; + + Containers::Optional> data = converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 42*6); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + bool doBeginData() override { return true; } + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + bool doBeginData() override { return true; } + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -536,7 +1123,7 @@ void AbstractSceneConverterTest::convertMeshToFile() { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; } bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { - return Utility::Path::write(filename, Containers::arrayView( {char(mesh.vertexCount())})); + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); } } converter; @@ -629,6 +1216,103 @@ void AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable() { TestSuite::Compare::StringHasSuffix); } +void AbstractSceneConverterTest::convertMeshToFileThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + bool doConvertToFile(const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doConvertToFile() should not be called"); + return {}; + } + + bool doBeginFile(Containers::StringView filename) override { + _filename = filename; + return true; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount = mesh.vertexCount(); + return true; + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_COMPARE(filename, _filename); + return Utility::Path::write(filename, Containers::arrayView({char(_vertexCount)})); + } + + std::size_t _vertexCount = 0; + Containers::StringView _filename; + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, filename)); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, + "\xfc", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + bool doEndFile(Containers::StringView) override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -642,27 +1326,4093 @@ void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeature() { +void AbstractSceneConverterTest::beginEnd() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + return true; + } + + Containers::Pointer doEnd() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + + struct Importer: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + void doClose() override {} + bool doIsOpened() const override { return true; } + + const void* doImporterState() const override { + return reinterpret_cast(0xdeadbeef); + } + }; + + return Containers::Pointer{new Importer}; + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->importerState(), reinterpret_cast(0xdeadbeef)); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return false; } + } converter; + + /* The implementation is expected to print an error message on its own */ std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.begin()); + CORRADE_COMPARE(out.str(), ""); - Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xf0); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xf0)\n"); + CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::debugFeatures() { +void AbstractSceneConverterTest::endFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + + Containers::Pointer doEnd() override { + return nullptr; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + } converter; + std::ostringstream out; + Error redirectError{&out}; + converter.begin(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::begin(): feature advertised but not implemented\n"); +} - Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +void AbstractSceneConverterTest::endNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): feature advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeaturesSupersets() { - /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should - be printed */ - { - std::ostringstream out; - Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); +void AbstractSceneConverterTest::beginEndData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + return true; + } + + Containers::Optional> doEndData() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.beginData()); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Optional> out = converter.endData(); + CORRADE_VERIFY(out); + CORRADE_COMPARE_AS(*out, + Containers::arrayView({'h', 'e', 'l', 'l', 'o'}), + TestSuite::Compare::Container); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return false; } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.beginData()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::endDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::endData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::beginEndDataCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + + Containers::Optional> doEndData() override { + return Containers::Array{data, 1, [](char*, std::size_t) {}}; + } + + char data[1]; + } converter; + + CORRADE_VERIFY(converter.beginData()); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::beginEndFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + bool doBeginFile(Containers::StringView filename) override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + filenameDataPointer = filename.data(); + return true; + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + + /* The filename should stay in scope and be the same pointer */ + CORRADE_COMPARE(filename.data(), filenameDataPointer); + return true; + } + + bool beginCalled = false, endCalled = false; + const void* filenameDataPointer; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.beginFile("file.gltf!"_s.exceptSuffix(1))); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + bool doBeginFile(Containers::StringView) override { return false; } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.beginFile("file.gltf")); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::endFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool doEndFile(Containers::StringView) override { return false; } + } converter; + + CORRADE_VERIFY(converter.beginFile("file.gltf")); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + + /* doEndFile() should call doEndData() */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, "hello", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataNotWritable() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + CORRADE_VERIFY(converter.beginFile("/some/path/that/does/not/exist")); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + /* There's an error from Path::write() before */ + CORRADE_COMPARE_AS(out.str(), + "\nTrade::AbstractSceneConverter::endFile(): cannot write to file /some/path/that/does/not/exist\n", + TestSuite::Compare::StringHasSuffix); +} + +void AbstractSceneConverterTest::beginFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginFile("file.gltf"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + bool doBeginFile(Containers::StringView) override { return true; } + } converter; + + CORRADE_VERIFY(converter.beginFile("file.gltf")); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::abort() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool abortCalled = false; + } converter; + + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(converter.abortCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::abortNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + } converter; + + /* This should work, there's no need for a plugin to implement this */ + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh| + SceneConverterFeature::ConvertMultiple; + } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Lines, 2}; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convert(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshInPlace() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshInPlace| + SceneConverterFeature::ConvertMultiple; + } + + bool doConvertInPlace(MeshData&) override { + return true; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + MeshData mesh{MeshPrimitive::Triangles, 6}; + CORRADE_VERIFY(converter.convertInPlace(mesh)); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData| + SceneConverterFeature::ConvertMultiple; + } + + Containers::Optional> doConvertToData(const MeshData&) override { + return Containers::Array{}; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyConvertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile| + SceneConverterFeature::ConvertMultiple; + } + + bool doConvertToFile(const MeshData&, Containers::StringView) override { + return true; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if there's a batch conversion happening and the + immediate APIs are used */ + CORRADE_VERIFY(converter.convertToFile(MeshData{MeshPrimitive::Triangles, 6}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBegin() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling begin() while another conversion is already + happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBeginData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::ConvertMultipleToData; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool doBeginData() override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling beginData() while another conversion is + already happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.beginData()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::abortImplicitlyBeginFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::ConvertMultipleToFile; + } + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool doBegin() override { return true; } + + bool doBeginFile(Containers::StringView) override { return true; } + + bool abortCalled = false; + } converter; + + /* Shouldn't be called if there's no previous conversion happening */ + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(!converter.abortCalled); + + /* Should be called if calling beginData() while another conversion is + already happening. Then, what is happening is the new conversion. */ + CORRADE_VERIFY(converter.beginFile(Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(converter.isConverting()); + CORRADE_VERIFY(converter.abortCalled); +} + +void AbstractSceneConverterTest::thingNoBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::AddScenes| + SceneConverterFeature::AddAnimations| + SceneConverterFeature::AddLights| + SceneConverterFeature::AddCameras| + SceneConverterFeature::AddSkins2D| + SceneConverterFeature::AddSkins3D| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddMaterials| + SceneConverterFeature::AddTextures| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::MeshLevels| + SceneConverterFeature::ImageLevels; + } + + bool doBeginData() override { return true; } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + converter.endData(); + converter.endFile(); + + converter.sceneCount(); + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.animationCount(); + converter.add(AnimationData{nullptr, nullptr}); + + converter.lightCount(); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + + converter.cameraCount(); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + + converter.skin2DCount(); + converter.add(SkinData2D{nullptr, nullptr}); + + converter.skin3DCount(); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.meshCount(); + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + converter.add({MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0}}); + converter.setMeshAttributeName({}, {}); + + converter.materialCount(); + converter.add(MaterialData{{}, nullptr}); + + converter.textureCount(); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + const char imageData[4]{}; + + converter.image1DCount(); + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + converter.add({ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}}); + + converter.image2DCount(); + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + converter.add({ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}}); + + converter.image3DCount(); + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + converter.add({ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}}); + + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): no conversion in progress\n" + "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n" + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n" + + "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::animationCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::lightCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::meshCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::materialCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::textureCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + bool doBeginData() override { return true; } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endDataMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n"); +} + +void AbstractSceneConverterTest::endFileMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n"); +} + +void AbstractSceneConverterTest::addScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override { + /* Scene count should not be increased before the function + returns */ + CORRADE_COMPARE(id, sceneCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(scene.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.sceneCount(), 0); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.sceneCount(), 1); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.sceneCount(), 2); +} + +void AbstractSceneConverterTest::addSceneFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.sceneCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.sceneCount(), 0); +} + +void AbstractSceneConverterTest::addSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::setSceneFieldName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setSceneFieldNameCalled = true; + } + + bool setSceneFieldNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + CORRADE_VERIFY(converter.begin()); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setSceneFieldNameCalled); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + } converter; + + /* This should work, there's no need for a plugin to implement this */ + CORRADE_VERIFY(converter.begin()); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.setSceneFieldName(SceneField::Transformation, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setSceneFieldName(): Trade::SceneField::Transformation is not custom\n"); +} + +void AbstractSceneConverterTest::setObjectName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + void doSetObjectName(UnsignedLong object, Containers::StringView name) override { + CORRADE_COMPARE(object, 1337); + CORRADE_COMPARE(name, "hey!"); + setObjectNameCalled = true; + } + + bool setObjectNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + CORRADE_VERIFY(converter.begin()); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(converter.setObjectNameCalled); +} + +void AbstractSceneConverterTest::setObjectNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + } converter; + + /* This should work, there's no need for a plugin to implement this */ + CORRADE_VERIFY(converter.begin()); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setDefaultScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + + void doSetDefaultScene(UnsignedInt id) override { + CORRADE_COMPARE(id, 2); + setDefaultSceneCalled = true; + } + + bool setDefaultSceneCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}), 2); + + converter.setDefaultScene(2); + CORRADE_VERIFY(converter.setDefaultSceneCalled); +} + +void AbstractSceneConverterTest::setDefaultSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 1); + + /* This should work, there's no need for a plugin to implement this */ + converter.setDefaultScene(0); +} + +void AbstractSceneConverterTest::setDefaultSceneOutOfRange() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 2); + + std::ostringstream out; + Error redirectError{&out}; + converter.setDefaultScene(2); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setDefaultScene(): index 2 out of range for 2 scenes\n"); +} + +void AbstractSceneConverterTest::addAnimation() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override { + /* Animation count should not be increased before the function + returns */ + CORRADE_COMPARE(id, animationCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(animation.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.animationCount(), 0); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.animationCount(), 1); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.animationCount(), 2); +} + +void AbstractSceneConverterTest::addAnimationFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const AnimationData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.animationCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(AnimationData{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.animationCount(), 0); +} + +void AbstractSceneConverterTest::addAnimationNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(AnimationData{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addLight() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override { + /* Light count should not be increased before the function + returns */ + CORRADE_COMPARE(id, lightCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(light.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.lightCount(), 0); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.lightCount(), 1); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.lightCount(), 2); +} + +void AbstractSceneConverterTest::addLightFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const LightData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.lightCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(LightData{LightData::Type::Point, {}, 0.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.lightCount(), 0); +} + +void AbstractSceneConverterTest::addLightNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addCamera() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, cameraCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(camera.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.cameraCount(), 0); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.cameraCount(), 1); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.cameraCount(), 2); +} + +void AbstractSceneConverterTest::addCameraFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const CameraData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.cameraCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.cameraCount(), 0); +} + +void AbstractSceneConverterTest::addCameraNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.skin2DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin2DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin2DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.skin2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData2D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin2DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData2D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.skin3DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin3DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin3DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.skin3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData3D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin3DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData3D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(mesh.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + + bool doBegin() override { + CORRADE_FAIL_IF(true, "doBegin() should not be called"); + return {}; + } + + Containers::Pointer doEnd() override { + CORRADE_FAIL_IF(true, "doEnd() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + CORRADE_VERIFY(converter.begin()); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Pointer importer = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(importer); + CORRADE_VERIFY(importer->isOpened()); + CORRADE_COMPARE(importer->meshCount(), 1); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); + + /* The mesh is returned only once, second time it will fail (but just an + error, not an assert */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->mesh(0)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh\n"); + } + + /* Verify that it's also possible to close the importer without hitting + some nasty assert */ + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData&) override { + return {}; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); + } + + /* Getting the result should still work */ + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->meshCount(), 1); + + Containers::Optional mesh = out->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + + bool doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + return {}; + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData&) override { + return {}; + } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + } converter; + + CORRADE_VERIFY(converter.beginData()); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); + } + + /* Getting the result should still work */ + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + + bool doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + return {}; + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at endFile(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::array({char(mesh.vertexCount())}); + } + + bool doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + return {}; + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + bool doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + return {}; + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the end(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got 0\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got 0\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.beginFile(filename)); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the endFile(), so the + file exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0xb0})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n"); + } + + /* Getting the result should still work */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 3); + CORRADE_COMPARE(meshLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Lines, 0}, + MeshData{MeshPrimitive::Triangles, 3, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Faces, 0}, + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Faces, 2}, + MeshData{MeshPrimitive::Meshlets, 1, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Points, 0}, + }, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshLevelsFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshLevelsNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(Containers::Iterable{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified\n"); +} + +void AbstractSceneConverterTest::addMeshLevelsNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable meshLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 1); + CORRADE_COMPARE(meshLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); +} + +void AbstractSceneConverterTest::setMeshAttributeName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + + void doSetMeshAttributeName(UnsignedShort field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setMeshAttributeNameCalled = true; + } + + bool setMeshAttributeNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + CORRADE_VERIFY(converter.begin()); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setMeshAttributeNameCalled); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + } converter; + + /* This should work, there's no need for a plugin to implement this */ + CORRADE_VERIFY(converter.begin()); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.setMeshAttributeName(MeshAttribute::ObjectId, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setMeshAttributeName(): Trade::MeshAttribute::ObjectId is not custom\n"); +} + +void AbstractSceneConverterTest::addMaterial() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, materialCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(material.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.materialCount(), 0); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.materialCount(), 1); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.materialCount(), 2); +} + +void AbstractSceneConverterTest::addMaterialFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const MaterialData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.materialCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MaterialData{{}, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.materialCount(), 0); +} + +void AbstractSceneConverterTest::addMaterialNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MaterialData{{}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addTexture() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, textureCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(texture.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.textureCount(), 0); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.textureCount(), 1); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.textureCount(), 2); +} + +void AbstractSceneConverterTest::addTextureFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const TextureData&, Containers::StringView) override { + return false; + } + } converter; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.textureCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(TextureData{{}, {}, {}, {}, {}, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.textureCount(), 0); +} + +void AbstractSceneConverterTest::addTextureNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImage1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), 3); + CORRADE_COMPARE(image.flags(), ImageFlags1D{}); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView1D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), 3); + CORRADE_COMPARE(image.flags(), ImageFlags1D{}); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView1D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData1D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImage1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0)\n"); +} + +void AbstractSceneConverterTest::addImage1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImage2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector2i{3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag2D::Array); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView2D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {3, 1}, imageData, ImageFlag2D::Array}, + "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); + CORRADE_COMPARE(image.flags(), ImageFlag2D::Array); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView2D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 2}, imageData, + ImageFlag2D::Array}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData2D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImage2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[16]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImage2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImage2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImage3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag3D::Array); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView3D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {1, 3, 1}, imageData, ImageFlag3D::Array}, + "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(image.flags(), ImageFlag3D::Array); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView3D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 3, 1}, imageData, + ImageFlag3D::Array}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, const ImageData3D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImage3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0, 0, 0)\n"); +} + +void AbstractSceneConverterTest::addImage3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 3, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlags1D{}); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView1D{PixelFormat::RG8Snorm, 1, imageData}, + ImageView1D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlags1D{}); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc3x3x3RGBASrgb, 1, imageData}, + CompressedImageView1D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{1, 3})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag2D::Array); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData, ImageFlag2D::Array}, + ImageView2D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3}, imageData, ImageFlag2D::Array} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{3, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag2D::Array); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData, ImageFlag2D::Array}, + CompressedImageView2D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1}, imageData, ImageFlag2D::Array} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData}, + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData}, + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels2DNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}; + ImageData2D b{CompressedPixelFormat::Astc10x10RGBAF, {1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({a, b}); + converter.add({b, b, a}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): image level 1 is compressed but previous aren't\n" + "Trade::AbstractSceneConverter::add(): image level 2 is not compressed but previous are\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Srgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected PixelFormat::RGBA8Unorm but got PixelFormat::RGBA8Srgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelStorage{}, 252, 1037, 4, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 1037, 4, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 4467, 4, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same extra format field, expected 1037 but got 4467 for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBASrgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected CompressedPixelFormat::Bc1RGBAUnorm but got CompressedPixelFormat::Bc1RGBASrgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFlags() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same flags, expected ImageFlag2D::Array but got ImageFlags2D{} for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData3D{PixelFormat::RGBA8Unorm, {4, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {2, 2, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {2, 1, 2}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 3, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{1, 3, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag3D::Array); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData, ImageFlag3D::Array}, + ImageView3D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3, 1}, imageData, ImageFlag3D::Array} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{3, 1, 1})); + CORRADE_COMPARE(imageLevels[1].flags(), ImageFlag3D::Array); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData, ImageFlag3D::Array}, + CompressedImageView3D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1, 1}, imageData, ImageFlag3D::Array} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData}, + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData}, + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + bool doBegin() override { return true; } + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + CORRADE_VERIFY(converter.begin()); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::debugFeature() { + std::ostringstream out; + + Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xdeaddead); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xdeaddead)\n"); +} + +void AbstractSceneConverterTest::debugFeaturePacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << SceneConverterFeature::ConvertMeshInPlace << Debug::packed << SceneConverterFeature(0xdeaddead) << SceneConverterFeature::AddCameras; + CORRADE_COMPARE(out.str(), "ConvertMeshInPlace 0xdeaddead Trade::SceneConverterFeature::AddCameras\n"); +} + +void AbstractSceneConverterTest::debugFeatures() { + std::ostringstream out; + + Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +} + +void AbstractSceneConverterTest::debugFeaturesPacked() { + std::ostringstream out; + /* Last is not packed, ones before should not make any flags persistent */ + Debug{&out} << Debug::packed << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << Debug::packed << SceneConverterFeatures{} << SceneConverterFeature::AddLights; + CORRADE_COMPARE(out.str(), "ConvertMesh|ConvertMeshToFile {} Trade::SceneConverterFeature::AddLights\n"); +} + +void AbstractSceneConverterTest::debugFeaturesSupersets() { + /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should + be printed */ + { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); + + /* ConvertMultipleToData is a superset of ConvertMultipleToFile, so only + one should be printed */ + } { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::ConvertMultipleToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMultipleToData\n"); } } diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 967f91aa7..3360dd59c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -54,6 +54,13 @@ corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTr corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeFlatMaterialDataTest FlatMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) + +corrade_add_test(TradeImageConverterTest ImageConverterTest.cpp + LIBRARIES MagnumTrade + FILES + ImageConverterTestFiles/info.txt) +target_include_directories(TradeImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/ImageConverterTest.cpp b/src/Magnum/Trade/Test/ImageConverterTest.cpp new file mode 100644 index 000000000..812caf3aa --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTest.cpp @@ -0,0 +1,192 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 /** @todo remove once Debug is stream-free */ +#include +#include +#include /** @todo remove once Debug is stream-free */ +#include + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct ImageConverterTest: TestSuite::Tester { + explicit ImageConverterTest(); + + void infoImplementation(); + void infoImplementationError(); +}; + +ImageConverterTest::ImageConverterTest() { + addTests({&ImageConverterTest::infoImplementation, + &ImageConverterTest::infoImplementationError}); +} + +void ImageConverterTest::infoImplementation() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* Three 1D images, one with two levels and named, one compressed, + one just to not have two of everything */ + UnsignedInt doImage1DCount() const override { return 3; } + UnsignedInt doImage1DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 2 ? "Third 1D image just so there aren't two" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData1D{CompressedPixelFormat::Astc10x10RGBAF, 1024, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 0) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 16, Containers::Array{NoInit, 64}}; + if(id == 1 && level == 1) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 8, Containers::Array{NoInit, 32}}; + if(id == 2 && level == 0) + return Trade::ImageData1D{PixelFormat::Depth16Unorm, 4, Containers::Array{NoInit, 8}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* Two 2D images, one with three levels and named, the other compressed + and array */ + UnsignedInt doImage2DCount() const override { return 2; } + UnsignedInt doImage2DLevelCount(UnsignedInt id) override { + return id == 0 ? 3 : 1; + } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 0 ? "A very nice mipmapped 2D image" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData2D{PixelFormat::RG16F, {256, 128}, Containers::Array{NoInit, 131072}}; + if(id == 0 && level == 1) + return Trade::ImageData2D{PixelFormat::RG16F, {128, 64}, Containers::Array{NoInit, 32768}}; + if(id == 0 && level == 2) + return Trade::ImageData2D{PixelFormat::RG16F, {64, 32}, Containers::Array{NoInit, 8192}}; + if(id == 1) + return Trade::ImageData2D{CompressedPixelFormat::PvrtcRGB2bppUnorm, {4, 8}, Containers::Array{NoInit, 32}, ImageFlag2D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* One 2D cube map array image, one 3D mipmapped & named and two 2D + array; with one externally owned */ + UnsignedInt doImage3DCount() const override { return 4; } + UnsignedInt doImage3DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 1 ? "Volume kills!" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 12}, Containers::Array{NoInit, 3072}, ImageFlag3D::CubeMap|ImageFlag3D::Array}; + if(id == 1 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 16}, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 1) + return Trade::ImageData3D{PixelFormat::R8Unorm, {8, 8, 6}, Containers::Array{NoInit, 2048}}; + if(id == 2 && level == 0) + return Trade::ImageData3D{CompressedPixelFormat::Bc1RGBSrgb, {4, 1, 1}, Containers::Array{NoInit, 16}, ImageFlag3D::Array}; + if(id == 3 && level == 0) + return Trade::ImageData3D{PixelFormat::R32F, {1, 4, 1}, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, data, ImageFlag3D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + char data[16]; + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + CORRADE_VERIFY(!error); + CORRADE_COMPARE(infos.size(), 13); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImageInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImageInfo(Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/info.txt"), + TestSuite::Compare::StringToFile); +} + +void ImageConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + Error{} << "1D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "2D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + Error{} << "3D image" << id << "error!"; + return {}; + } + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + /* It should return a failure and no output */ + CORRADE_VERIFY(error); + CORRADE_VERIFY(infos.isEmpty()); + /* But it should not exit after first error */ + CORRADE_COMPARE(out.str(), + "1D image 0 error!\n" + "1D image 1 error!\n" + "2D image 0 error!\n" + "2D image 1 error!\n" + "3D image 0 error!\n" + "3D image 1 error!\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageConverterTest) diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt new file mode 100644 index 000000000..bff917a68 --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt @@ -0,0 +1,23 @@ +1D image 0: + Level 0: {1024} @ Astc10x10RGBAF (4.0 kB) +1D image 1: + Level 0: {16} @ RGBA8Snorm (0.1 kB) + Level 1: {8} @ RGBA8Snorm (0.0 kB) +1D image 2: Third 1D image just so there aren't two + Level 0: {4} @ Depth16Unorm (0.0 kB) +2D image 0: A very nice mipmapped 2D image + Level 0: {256, 128} @ RG16F (128.0 kB) + Level 1: {128, 64} @ RG16F (32.0 kB) + Level 2: {64, 32} @ RG16F (8.0 kB) +2D image 1: + Level 0: Array {4, 8} @ PvrtcRGB2bppUnorm (0.0 kB) +3D image 0: + Level 0: Array|CubeMap {16, 16, 12} @ R8Unorm (3.0 kB) +3D image 1: Volume kills! + Level 0: {16, 16, 16} @ R8Unorm (4.0 kB) + Level 1: {8, 8, 6} @ R8Unorm (2.0 kB) +3D image 2: + Level 0: Array {4, 1, 1} @ Bc1RGBSrgb (0.0 kB) +3D image 3: + Level 0: Array {1, 4, 1} @ R32F (0.0 kB, ExternallyOwned|Mutable) +Total image data size: 181.2 kB diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index 1cbd5854c..d071911ef 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -46,6 +46,10 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct MaterialDataTest: TestSuite::Tester { explicit MaterialDataTest(); + void layerName(); + void layerNameInvalid(); + void attributeName(); + void attributeNameInvalid(); void textureSwizzleComponentCount(); void attributeTypeSize(); void attributeTypeSizeInvalid(); @@ -172,10 +176,16 @@ struct MaterialDataTest: TestSuite::Tester { }; MaterialDataTest::MaterialDataTest() { - addTests({&MaterialDataTest::textureSwizzleComponentCount, + addTests({&MaterialDataTest::layerName, + &MaterialDataTest::layerNameInvalid, + &MaterialDataTest::attributeName, + &MaterialDataTest::attributeNameInvalid, + + &MaterialDataTest::textureSwizzleComponentCount, &MaterialDataTest::attributeTypeSize, &MaterialDataTest::attributeTypeSizeInvalid, + &MaterialDataTest::attributeMap, &MaterialDataTest::layerMap, @@ -325,6 +335,38 @@ MaterialDataTest::MaterialDataTest() { using namespace Containers::Literals; using namespace Math::Literals; +void MaterialDataTest::layerName() { + CORRADE_COMPARE(materialLayerName(MaterialLayer::ClearCoat), "ClearCoat"); +} + +void MaterialDataTest::layerNameInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + materialLayerName(MaterialLayer(0x0)); + materialLayerName(MaterialLayer(0xdeadbeef)); + CORRADE_COMPARE(out.str(), + "Trade::materialLayerName(): invalid layer Trade::MaterialLayer(0x0)\n" + "Trade::materialLayerName(): invalid layer Trade::MaterialLayer(0xdeadbeef)\n"); +} + +void MaterialDataTest::attributeName() { + CORRADE_COMPARE(materialAttributeName(MaterialAttribute::BaseColorTextureMatrix), "BaseColorTextureMatrix"); +} + +void MaterialDataTest::attributeNameInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + std::ostringstream out; + Error redirectError{&out}; + materialAttributeName(MaterialAttribute(0x0)); + materialAttributeName(MaterialAttribute(0xdeadbeef)); + CORRADE_COMPARE(out.str(), + "Trade::materialAttributeName(): invalid attribute Trade::MaterialAttribute(0x0)\n" + "Trade::materialAttributeName(): invalid attribute Trade::MaterialAttribute(0xdeadbeef)\n"); +} + void MaterialDataTest::textureSwizzleComponentCount() { CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::B), 1); CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::RG), 2); @@ -885,8 +927,14 @@ void MaterialDataTest::construct() { /* Access by name */ CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::DoubleSided)); CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::AmbientTextureMatrix)); + CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::DiffuseTextureCoordinates)); CORRADE_VERIFY(!data.hasAttribute(MaterialAttribute::TextureMatrix)); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::DoubleSided), 2); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::AmbientTextureMatrix), 0); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::DiffuseTextureCoordinates), 1); + CORRADE_VERIFY(!data.findAttributeId(MaterialAttribute::TextureMatrix)); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::DoubleSided), 2); CORRADE_COMPARE(data.attributeId(MaterialAttribute::AmbientTextureMatrix), 0); CORRADE_COMPARE(data.attributeId(MaterialAttribute::DiffuseTextureCoordinates), 1); @@ -912,8 +960,16 @@ void MaterialDataTest::construct() { /* Access by string */ CORRADE_VERIFY(data.hasAttribute("DoubleSided")); CORRADE_VERIFY(data.hasAttribute("highlightColor")); + CORRADE_VERIFY(data.hasAttribute("DiffuseTextureCoordinates")); + CORRADE_VERIFY(data.hasAttribute("highlightColor")); CORRADE_VERIFY(!data.hasAttribute("TextureMatrix")); + CORRADE_COMPARE(data.findAttributeId("DoubleSided"), 2); + CORRADE_COMPARE(data.findAttributeId("AmbientTextureMatrix"), 0); + CORRADE_COMPARE(data.findAttributeId("DiffuseTextureCoordinates"), 1); + CORRADE_COMPARE(data.findAttributeId("highlightColor"), 3); + CORRADE_VERIFY(!data.findAttributeId("TextureMatrix")); + CORRADE_COMPARE(data.attributeId("DoubleSided"), 2); CORRADE_COMPARE(data.attributeId("AmbientTextureMatrix"), 0); CORRADE_COMPARE(data.attributeId("DiffuseTextureCoordinates"), 1); @@ -1044,6 +1100,13 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(data.hasLayer(MaterialLayer::ClearCoat)); CORRADE_VERIFY(!data.hasLayer("")); CORRADE_VERIFY(!data.hasLayer("DoubleSided")); + /** @todo test hasLayer(MaterialLayer) once there's more than ClearCoat */ + + CORRADE_COMPARE(data.findLayerId("ClearCoat"), 1); + CORRADE_COMPARE(data.findLayerId(MaterialLayer::ClearCoat), 1); + CORRADE_VERIFY(!data.findLayerId("")); + CORRADE_VERIFY(!data.findLayerId("DoubleSided")); + /** @todo test findLayerId(MaterialLayer) once there's more than ClearCoat */ CORRADE_COMPARE(data.layerId("ClearCoat"), 1); CORRADE_COMPARE(data.layerId(MaterialLayer::ClearCoat), 1); @@ -1087,6 +1150,14 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::NormalTexture)); CORRADE_VERIFY(data.hasAttribute(3, MaterialAttribute::NormalTexture)); + CORRADE_COMPARE(data.findAttributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); + CORRADE_VERIFY(!data.findAttributeId(0, MaterialAttribute::AlphaBlend)); + CORRADE_COMPARE(data.findAttributeId(1, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId(1, MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId(2, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.findAttributeId(2, MaterialAttribute::NormalTexture)); + CORRADE_COMPARE(data.findAttributeId(3, MaterialAttribute::NormalTexture), 0); + CORRADE_COMPARE(data.attributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::LayerName), 0); @@ -1124,6 +1195,14 @@ void MaterialDataTest::constructLayers() { CORRADE_VERIFY(!data.hasAttribute(2, "NormalTexture")); CORRADE_VERIFY(data.hasAttribute(3, "NormalTexture")); + CORRADE_COMPARE(data.findAttributeId(0, "DoubleSided"), 1); + CORRADE_VERIFY(!data.findAttributeId(0, "highlightColor")); + CORRADE_COMPARE(data.findAttributeId(1, "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId(1, " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId(2, " LayerName")); + CORRADE_VERIFY(!data.findAttributeId(2, " NormalTexture")); + CORRADE_COMPARE(data.findAttributeId(3, "NormalTexture"), 0); + CORRADE_COMPARE(data.attributeId(0, "DoubleSided"), 1); CORRADE_COMPARE(data.attributeId(1, "highlightColor"), 2); CORRADE_COMPARE(data.attributeId(1, " LayerName"), 0); @@ -1172,6 +1251,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer name and attribute name */ CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend)); CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::BaseColor)); + + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId(MaterialLayer::ClearCoat, MaterialAttribute::BaseColor)); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), 0); @@ -1192,6 +1276,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer name and attribute string */ CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, "highlightColor")); CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, " LayerName")); + CORRADE_VERIFY(!data.hasAttribute(MaterialLayer::ClearCoat, "BaseColor")); + + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId(MaterialLayer::ClearCoat, " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId(MaterialLayer::ClearCoat, "BaseColor")); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, "highlightColor"), 2); CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, " LayerName"), 0); @@ -1229,6 +1318,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer string and attribute name */ CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::AlphaBlend)); CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", MaterialAttribute::BaseColor)); + + CORRADE_COMPARE(data.findAttributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.findAttributeId("ClearCoat", MaterialAttribute::LayerName), 0); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", MaterialAttribute::BaseColor)); CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::LayerName), 0); @@ -1249,6 +1343,11 @@ void MaterialDataTest::constructLayers() { /* Access by layer string and attribute string */ CORRADE_VERIFY(data.hasAttribute("ClearCoat", "highlightColor")); CORRADE_VERIFY(data.hasAttribute("ClearCoat", " LayerName")); + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", "BaseColor")); + + CORRADE_COMPARE(data.findAttributeId("ClearCoat", "highlightColor"), 2); + CORRADE_COMPARE(data.findAttributeId("ClearCoat", " LayerName"), 0); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", "BaseColor")); CORRADE_COMPARE(data.attributeId("ClearCoat", "highlightColor"), 2); CORRADE_COMPARE(data.attributeId("ClearCoat", " LayerName"), 0); @@ -1763,7 +1862,9 @@ void MaterialDataTest::accessNotFound() { {"DiffuseColor", 0xff3366aa_rgbaf} }}; + /* These are fine */ CORRADE_VERIFY(!data.hasAttribute("DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId("DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2325,6 +2426,8 @@ void MaterialDataTest::accessLayerOutOfBounds() { data.attributeCount(2); data.hasAttribute(2, "AlphaMask"); data.hasAttribute(2, MaterialAttribute::AlphaMask); + data.findAttributeId(2, "AlphaMask"); + data.findAttributeId(2, MaterialAttribute::AlphaMask); data.attributeId(2, "AlphaMask"); data.attributeId(2, MaterialAttribute::AlphaMask); data.attributeName(2, 0); @@ -2362,6 +2465,8 @@ void MaterialDataTest::accessLayerOutOfBounds() { "Trade::MaterialData::attributeCount(): index 2 out of range for 2 layers\n" "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::findAttributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::findAttributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" "Trade::MaterialData::attributeName(): index 2 out of range for 2 layers\n" @@ -2398,6 +2503,9 @@ void MaterialDataTest::accessLayerNotFound() { {MaterialAttribute::AlphaMask, 0.5f}, }, {0, 2}}; + /* This is fine */ + CORRADE_VERIFY(!data.findLayerId("ClearCoat")); + std::ostringstream out; Error redirectError{&out}; data.layerId("ClearCoat"); @@ -2410,6 +2518,8 @@ void MaterialDataTest::accessLayerNotFound() { data.attributeCount("ClearCoat"); data.hasAttribute("ClearCoat", "AlphaMask"); data.hasAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.findAttributeId("ClearCoat", "AlphaMask"); + data.findAttributeId("ClearCoat", MaterialAttribute::AlphaMask); data.attributeId("ClearCoat", "AlphaMask"); data.attributeId("ClearCoat", MaterialAttribute::AlphaMask); data.attributeName("ClearCoat", 0); @@ -2447,6 +2557,8 @@ void MaterialDataTest::accessLayerNotFound() { "Trade::MaterialData::attributeCount(): layer ClearCoat not found\n" "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::findAttributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::findAttributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" "Trade::MaterialData::attributeName(): layer ClearCoat not found\n" @@ -2482,7 +2594,8 @@ void MaterialDataTest::accessInvalidLayerName() { std::ostringstream out; Error redirectError{&out}; - data.layerId(MaterialLayer(0x0)); + data.findLayerId(MaterialLayer(0x0)); + data.findLayerId(MaterialLayer(0xfefe)); data.layerId(MaterialLayer(0xfefe)); data.layerFactor(MaterialLayer(0xfefe)); data.layerFactorTexture(MaterialLayer(0xfefe)); @@ -2493,6 +2606,8 @@ void MaterialDataTest::accessInvalidLayerName() { data.attributeCount(MaterialLayer(0xfefe)); data.hasAttribute(MaterialLayer(0xfefe), "AlphaMask"); data.hasAttribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.findAttributeId(MaterialLayer(0xfefe), "AlphaMask"); + data.findAttributeId(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); data.attributeId(MaterialLayer(0xfefe), "AlphaMask"); data.attributeId(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); data.attributeName(MaterialLayer(0xfefe), 0); @@ -2520,7 +2635,8 @@ void MaterialDataTest::accessInvalidLayerName() { data.attributeOr(MaterialLayer(0xfefe), "AlphaMask", false); data.attributeOr(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask, false); CORRADE_COMPARE(out.str(), - "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialData::findLayerId(): invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialData::findLayerId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerFactor(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::layerFactorTexture(): invalid name Trade::MaterialLayer(0xfefe)\n" @@ -2531,6 +2647,8 @@ void MaterialDataTest::accessInvalidLayerName() { "Trade::MaterialData::attributeCount(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" "Trade::MaterialData::attributeName(): invalid name Trade::MaterialLayer(0xfefe)\n" @@ -2624,7 +2742,9 @@ void MaterialDataTest::accessNotFoundInLayerIndex() { {"DiffuseColor", 0xff3366aa_rgbaf} }, {0, 1}}; + /* These are fine */ CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId(1, "DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2651,7 +2771,9 @@ void MaterialDataTest::accessNotFoundInLayerString() { {"DiffuseColor", 0xff3366aa_rgbaf} }, {0, 1}}; - CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + /* These are fine */ + CORRADE_VERIFY(!data.hasAttribute("ClearCoat", "DiffuseColour")); + CORRADE_VERIFY(!data.findAttributeId("ClearCoat", "DiffuseColour")); std::ostringstream out; Error redirectError{&out}; @@ -2682,6 +2804,8 @@ void MaterialDataTest::accessInvalidAttributeName() { Error redirectError{&out}; data.hasAttribute(0, MaterialAttribute(0x0)); data.hasAttribute("Layer", MaterialAttribute(0xfefe)); + data.findAttributeId(0, MaterialAttribute(0x0)); + data.findAttributeId("Layer", MaterialAttribute(0xfefe)); data.attributeId(0, MaterialAttribute(0x0)); data.attributeId("Layer", MaterialAttribute(0xfefe)); data.attributeType(0, MaterialAttribute(0x0)); @@ -2703,6 +2827,8 @@ void MaterialDataTest::accessInvalidAttributeName() { CORRADE_COMPARE(out.str(), "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::findAttributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0x0)\n" "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0x0)\n" @@ -2881,6 +3007,9 @@ void MaterialDataTest::templateLayerAccess() { CORRADE_VERIFY(data.hasAttribute(0, MaterialAttribute::BaseColor)); CORRADE_VERIFY(data.hasAttribute(0, "BaseColor")); + CORRADE_COMPARE(data.findAttributeId(MaterialAttribute::LayerFactorTexture), 2); + CORRADE_COMPARE(data.findAttributeId("LayerFactorTexture"), 2); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::LayerFactorTexture), 2); CORRADE_COMPARE(data.attributeId("LayerFactorTexture"), 2); diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index ecd20e495..bf760404b 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -1367,6 +1367,7 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.mappingBound(), 8); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.fieldCount(), 4); + CORRADE_COMPARE(scene.fieldSizeBound(), 5); CORRADE_COMPARE(scene.importerState(), &importerState); /* is2D() / is3D() exhaustively tested in transformations*DAsArray[TRS]() @@ -1608,6 +1609,7 @@ void SceneDataTest::constructZeroFields() { CORRADE_COMPARE(scene.mappingBound(), 37563); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); CORRADE_COMPARE(scene.fieldCount(), 0); + CORRADE_COMPARE(scene.fieldSizeBound(), 0); CORRADE_VERIFY(!scene.is2D()); CORRADE_VERIFY(!scene.is3D()); } @@ -1626,6 +1628,7 @@ void SceneDataTest::constructZeroObjects() { CORRADE_COMPARE(scene.mappingBound(), 0); CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); CORRADE_COMPARE(scene.fieldCount(), 2); + CORRADE_COMPARE(scene.fieldSizeBound(), 0); /* Field property access by name */ CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedShort); diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index c46e78843..440238313 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -447,7 +448,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; const Int dimensions = args.value("dimensions"); /** @todo make them array options as well? */ @@ -595,47 +596,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") else useColor = Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors; - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: infos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::White); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.dataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!infos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + Trade::Implementation::printImageInfo(useColor, infos, nullptr, nullptr, nullptr); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; @@ -961,7 +922,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : - Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths().back())}; + Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImageConverter::pluginSearchPaths().back()).second())}; /* Assume there's always one passed --converter option less, and the last is implicitly AnyImageConverter. All converters except the last one are @@ -1021,10 +982,8 @@ no -C / --converter is specified, AnyImageConverter is used.)") Trade::ImageConverterFeature::ConvertCompressed3DToFile : Trade::ImageConverterFeature::Convert3DToFile; } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); - /** @todo use a sane flag once the feature enum is ... sane */ - constexpr Trade::ImageConverterFeatures ImageConverterFeatureLevels = - Trade::ImageConverterFeature::ConvertLevels1DToFile & ~Trade::ImageConverterFeature::Convert1DToFile; - if(outputIsMultiLevel) expectedFeatures |= ImageConverterFeatureLevels; + if(outputIsMultiLevel) + expectedFeatures |= Trade::ImageConverterFeature::Levels; if(!(converter->features() >= expectedFeatures)) { Error err; err << converterName << "doesn't support"; diff --git a/src/Magnum/VertexFormat.cpp b/src/Magnum/VertexFormat.cpp index 6010a79b2..3f620b02b 100644 --- a/src/Magnum/VertexFormat.cpp +++ b/src/Magnum/VertexFormat.cpp @@ -25,7 +25,7 @@ #include "VertexFormat.h" -#include +#include #include #include #include @@ -930,14 +930,14 @@ Debug& operator<<(Debug& debug, const VertexFormat value) { namespace Corrade { namespace Utility { -std::string ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { +Containers::String ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::VertexFormatNames)) return Magnum::VertexFormatNames[Magnum::UnsignedInt(value) - 1]; return {}; } -Magnum::VertexFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { +Magnum::VertexFormat ConfigurationValue::fromString(Containers::StringView stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::VertexFormatNames); ++i) if(stringValue == Magnum::VertexFormatNames[i]) return Magnum::VertexFormat(i + 1); diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 9cbcc9108..56dcbc3d2 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -30,7 +30,6 @@ */ #include -#include #include "Magnum/Magnum.h" #include "Magnum/visibility.h" @@ -1473,14 +1472,14 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { * * If the value is invalid, returns empty string. */ - static std::string toString(Magnum::VertexFormat value, ConfigurationValueFlags); + static Containers::String toString(Magnum::VertexFormat value, ConfigurationValueFlags); /** * @brief Read enum value as string * * If the value is invalid, returns a zero (invalid) format. */ - static Magnum::VertexFormat fromString(const std::string& stringValue, ConfigurationValueFlags); + static Magnum::VertexFormat fromString(Containers::StringView stringValue, ConfigurationValueFlags); }; }} diff --git a/src/Magnum/Vk/CommandBuffer.h b/src/Magnum/Vk/CommandBuffer.h index a6bf6af20..7c059f464 100644 --- a/src/Magnum/Vk/CommandBuffer.h +++ b/src/Magnum/Vk/CommandBuffer.h @@ -200,9 +200,10 @@ delimited with @ref beginRenderPass() and @ref endRenderPass(), see @snippet MagnumVk.cpp CommandBuffer-usage -Once recorded, the command buffer can be submitted to a compatible @ref Queue -that was set up at @ref Vk-Device-creation "device creation time". Usually -you'd want to wait on the submit completion with a @link Fence @endlink: +Once recorded, call @ref Queue::submit() to submit the command buffer to a +compatible @ref Queue that was set up at +@ref Vk-Device-creation "device creation time". Usually you'd want to wait on +the submit completion with a @link Fence @endlink: @snippet MagnumVk.cpp CommandBuffer-usage-submit */ diff --git a/src/Magnum/Vk/DescriptorSetLayout.cpp b/src/Magnum/Vk/DescriptorSetLayout.cpp index 31cbac41e..2d604a14a 100644 --- a/src/Magnum/Vk/DescriptorSetLayout.cpp +++ b/src/Magnum/Vk/DescriptorSetLayout.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include "Magnum/Vk/Assert.h" @@ -90,7 +91,7 @@ DescriptorSetLayoutBinding& DescriptorSetLayoutBinding::operator=(DescriptorSetL return *this; } -DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::ArrayView> bindings, const Flags flags): _info{} { +DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::Iterable bindings, const Flags flags): _info{} { /* Check the total count of immutable samplers to allocate them all in a contiguous memory location. Also check if we have any binding flags. If yes, we have to create an additional array and put a structure into the @@ -146,7 +147,7 @@ DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const Containers::A } } -DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const std::initializer_list> bindings, const Flags flags): DescriptorSetLayoutCreateInfo{Containers::arrayView(bindings), flags} {} +DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(const std::initializer_list> bindings, const Flags flags): DescriptorSetLayoutCreateInfo{Containers::Iterable{bindings}, flags} {} DescriptorSetLayoutCreateInfo::DescriptorSetLayoutCreateInfo(NoInitT) noexcept {} diff --git a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h b/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h index 1ba013349..0d07ed378 100644 --- a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h +++ b/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h @@ -46,6 +46,14 @@ #include "Magnum/Vk/Vk.h" #include "Magnum/Vk/Vulkan.h" +#ifdef MAGNUM_BUILD_DEPRECATED +/* DescriptorSetLayoutCreateInfo constructor used to take an + ArrayView>, now it's through the + Iterable class. Include it explicitly until people learn to include it + themselves. */ +#include +#endif + namespace Magnum { namespace Vk { /** @@ -315,8 +323,12 @@ class MAGNUM_VK_EXPORT DescriptorSetLayoutCreateInfo { * - `pBindingFlags` to a list of all * @ref DescriptorSetLayoutBinding::flags() from @p bindings */ - explicit DescriptorSetLayoutCreateInfo(Containers::ArrayView> bindings, Flags flags = {}); + explicit DescriptorSetLayoutCreateInfo(Containers::Iterable bindings, Flags flags = {}); + /** @overload */ + /* Iterable takes std::initializer_list itself but having it also here + allows to do stuff like `DescriptorSetLayoutCreateInfo{a, b}` and + (admittedly weird) `DescriptorSetLayoutCreateInfo{}` as well. */ explicit DescriptorSetLayoutCreateInfo(std::initializer_list> bindings, Flags flags = {}); /** diff --git a/src/Magnum/Vk/DeviceCreateInfo.h b/src/Magnum/Vk/DeviceCreateInfo.h index e7a1fd9b9..c451c1030 100644 --- a/src/Magnum/Vk/DeviceCreateInfo.h +++ b/src/Magnum/Vk/DeviceCreateInfo.h @@ -320,6 +320,9 @@ class MAGNUM_VK_EXPORT DeviceCreateInfo { * @see @ref DeviceProperties::pickQueueFamily() * @todoc link to addQueues(QueueFlags) once doxygen finally GROWS UP * and can link to &-qualified functions FFS + * @todo switch to Iterable once Iterable supports references + * so people can pass a whole array of queues without having to + * build a temporary list of references */ DeviceCreateInfo& addQueues(UnsignedInt family, Containers::ArrayView priorities, Containers::ArrayView> output) &; /** @overload */ diff --git a/src/Magnum/Vk/FramebufferCreateInfo.h b/src/Magnum/Vk/FramebufferCreateInfo.h index 4b0ee3a9f..033b15fa8 100644 --- a/src/Magnum/Vk/FramebufferCreateInfo.h +++ b/src/Magnum/Vk/FramebufferCreateInfo.h @@ -94,6 +94,10 @@ class MAGNUM_VK_EXPORT FramebufferCreateInfo { * - `attachmentCount` and `pAttachments` to a copy of * @p attachments * - `width`, `height` and `layers` to @p size + * + * @todo switch to Iterable once Iterable supports + * references so people can pass a whole array of images without + * having to build a temporary list of references */ explicit FramebufferCreateInfo(VkRenderPass renderPass, Containers::ArrayView> attachments, const Vector3i& size, Flags flags = {}); diff --git a/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp b/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp index 7e229626c..1ba7b9752 100644 --- a/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp +++ b/src/Magnum/Vk/Test/DescriptorSetLayoutTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h" diff --git a/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp b/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp index 4600ba25f..2795abbda 100644 --- a/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp +++ b/src/Magnum/Vk/Test/DescriptorSetLayoutVkTest.cpp @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h" #include "Magnum/Vk/DescriptorType.h" #include "Magnum/Vk/Result.h" diff --git a/src/Magnum/configure.h.cmake b/src/Magnum/configure.h.cmake index 407bbfd8b..f697971ee 100644 --- a/src/Magnum/configure.h.cmake +++ b/src/Magnum/configure.h.cmake @@ -32,16 +32,34 @@ #cmakedefine MAGNUM_TARGET_GLES #cmakedefine MAGNUM_TARGET_GLES2 #cmakedefine MAGNUM_TARGET_GLES3 -#cmakedefine MAGNUM_TARGET_DESKTOP_GLES #cmakedefine MAGNUM_TARGET_WEBGL -#cmakedefine MAGNUM_TARGET_HEADLESS +#cmakedefine MAGNUM_TARGET_EGL #cmakedefine MAGNUM_TARGET_VK #ifdef MAGNUM_BUILD_DEPRECATED #include "Corrade/configure.h" #ifdef CORRADE_BUILD_MULTITHREADED -/* For compatibility only, to be removed at some point */ -#define MAGNUM_BUILD_MULTITHREADED +/* For compatibility only, to be removed at some point. Deliberate double space + after the #define to avoid being unconditionally matched by older FindMagnum + modules. */ +#define MAGNUM_BUILD_MULTITHREADED +#endif +/* The following applies only to desktop platforms */ +#if !defined(CORRADE_TARGET_IOS) && !defined(CORRADE_TARGET_ANDROID) && !defined(CORRADE_TARGET_EMSCRIPTEN) && !defined(CORRADE_TARGET_WINDOWS_RT) +/* MAGNUM_TARGET_HEADLESS used to be an option defined on desktop GL only, not + on ES; MAGNUM_TARGET_EGL is defined implicitly also on all platforms that + use EGL exclusively. Deliberate double space after the #define to avoid + being unconditionally matched by older FindMagnum modules. */ +#if !defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_EGL) +#define MAGNUM_TARGET_HEADLESS +#endif +/* MAGNUM_TARGET_DESKTOP_GLES used to do the opposite of + MAGNUM_TARGET_HEADLESS, i.e. force GLX/WGL instead of EGL on GLES builds. + Deliberate double space after the #define to avoid being unconditionally + matched by older FindMagnum modules. */ +#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_EGL) +#define MAGNUM_TARGET_DESKTOP_GLES +#endif #endif #endif diff --git a/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt b/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt index 843f8aec0..9e6b89b59 100644 --- a/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt +++ b/src/MagnumExternal/OpenGL/GLES2/CMakeLists.txt @@ -27,9 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "MagnumExternal/OpenGL") -# Desktop GLES on Windows still links to opengl32.dll so we have a special +# Non-EGL GLES on Windows still links to opengl32.dll so we have a special # function loading code that queries everything above OpenGL 1.1 -if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) +if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) set(MagnumOpenGL_HEADERS flextGLWindowsDesktop.h) set(MagnumOpenGL_SRCS flextGLWindowsDesktop.cpp) diff --git a/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt b/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt index bff885743..96b7023a9 100644 --- a/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt +++ b/src/MagnumExternal/OpenGL/GLES3/CMakeLists.txt @@ -27,9 +27,9 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "MagnumExternal/OpenGL") -# Desktop GLES on Windows still links to opengl32.dll so we have a special +# Non-EGL GLES on Windows still links to opengl32.dll so we have a special # function loading code that queries everything above OpenGL 1.1 -if(CORRADE_TARGET_WINDOWS AND MAGNUM_TARGET_DESKTOP_GLES) +if(CORRADE_TARGET_WINDOWS AND NOT MAGNUM_TARGET_EGL) set(MagnumOpenGL_HEADERS flextGLWindowsDesktop.h) set(MagnumOpenGL_SRCS flextGLWindowsDesktop.cpp) diff --git a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp index 6e8ce4c32..26db2bfb0 100644 --- a/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp +++ b/src/MagnumPlugins/AnyAudioImporter/Test/AnyAudioImporterTest.cpp @@ -121,24 +121,26 @@ void AnyImporterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->openFile(data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nAudio::AnyImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Audio::AnyImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nAudio::AnyImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Audio::AnyImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } void AnyImporterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyAudioImporter"); - CORRADE_VERIFY(!importer->openFile("sound.mid")); - CORRADE_COMPARE(output.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("sound.mid")); + CORRADE_COMPARE(out.str(), "Audio::AnyImporter::openFile(): cannot determine the format of sound.mid\n"); } void AnyImporterTest::propagateConfiguration() { diff --git a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp index 1a1ba784e..a27e5ce14 100644 --- a/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp +++ b/src/MagnumPlugins/AnyImageConverter/AnyImageConverter.cpp @@ -26,7 +26,6 @@ #include "AnyImageConverter.h" #include -#include /* for PluginManager */ #include #include #include @@ -55,12 +54,7 @@ ImageConverterFeatures AnyImageConverter::doFeatures() const { ImageConverterFeature::ConvertCompressed1DToFile| ImageConverterFeature::ConvertCompressed2DToFile| ImageConverterFeature::ConvertCompressed3DToFile| - ImageConverterFeature::ConvertLevels1DToFile| - ImageConverterFeature::ConvertLevels2DToFile| - ImageConverterFeature::ConvertLevels3DToFile| - ImageConverterFeature::ConvertCompressedLevels1DToFile| - ImageConverterFeature::ConvertCompressedLevels2DToFile| - ImageConverterFeature::ConvertCompressedLevels3DToFile; + ImageConverterFeature::Levels; } bool AnyImageConverter::doConvertToFile(const ImageView1D& image, const Containers::StringView filename) { @@ -627,4 +621,4 @@ bool AnyImageConverter::doConvertToFile(const Containers::ArrayView converter = manager.instantiate("AnyImageConverter"); - converter->configuration().setValue("envmap", "cube"); /* This will make the verbose output print the detected hardware thread count, but also the info about updating global thread count for the first time. Thus run it once w/o a verbose flag and then again with to @@ -1193,7 +1192,6 @@ void AnyImageConverterTest::propagateFlagsLevels3D() { CORRADE_VERIFY(Utility::Path::remove(filename)); Containers::Pointer converter = manager.instantiate("AnyImageConverter"); - converter->configuration().setValue("envmap", "cube"); /* This will make the verbose output print the detected hardware thread count, but also the info about updating global thread count for the first time. Thus run it once w/o a verbose flag and then again with to diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index 2c555b3d0..66e09ea32 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -94,9 +94,31 @@ void AnyImageImporter::doOpenFile(const Containers::StringView filename) { plugin = "JpegImporter"_s; else if(normalizedExtension == ".jp2"_s) plugin = "Jpeg2000Importer"_s; - else if(normalizedExtension == ".ktx2"_s) + else if(normalizedExtension == ".ktx2"_s) { plugin = "KtxImporter"_s; - else if(normalizedExtension == ".mng"_s) + + /* KtxImporter delegates to BasisImporter in case the file is + Basis-compressed, so that's a good default choice. However, if it + isn't available, we should try delegating to BasisImporter instead, + so people that have just Basis-compressed KTX files don't need to + have KtxImporter as well. + + BasisImporter unfortunately can't handle non-Basis-compressed KTX + files, so in case people have just BasisImporter and not + KtxImporter, it'll fail, but with a clear message suggesting to use + KtxImporter. If neither BasisImporter would be available, it'd fail + too (complaining that KtxImporter isn't available), so the behavior + is roughly the same. + + Further discussion and reasoning here: + https://github.com/mosra/magnum-plugins/pull/112#discussion_r734976174 */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openFile(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } + } else if(normalizedExtension == ".mng"_s) plugin = "MngImporter"_s; else if(normalizedExtension == ".pbm"_s) plugin = "PbmImporter"_s; @@ -200,10 +222,19 @@ void AnyImageImporter::doOpenData(Containers::Array&& data, DataFlags) { else if(dataString.hasPrefix("\xff\xd8\xff"_s)) plugin = "JpegImporter"_s; /* https://github.khronos.org/KTX-Specification/#_identifier */ - else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) + else if(dataString.hasPrefix("\xabKTX 20\xbb\r\n\x1a\n"_s)) { plugin = "KtxImporter"_s; + + /* Same logic as in doOpenFile() for *.ktx2 files, see above for more + information */ + if(manager()->loadState("KtxImporter"_s) == PluginManager::LoadState::NotFound && + manager()->loadState("BasisImporter"_s) != PluginManager::LoadState::NotFound) { + if(flags() & ImporterFlag::Verbose) + Debug{} << "Trade::AnyImageImporter::openData(): KtxImporter not found, trying a fallback"; + plugin = "BasisImporter"_s; + } /* https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header */ - else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) + } else if(dataString.hasPrefix("\x89PNG\x0d\x0a\x1a\x0a"_s)) plugin = "PngImporter"_s; /* http://paulbourke.net/dataformats/tiff/, http://paulbourke.net/dataformats/tiff/tiff_summary.pdf */ diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index 7917facc4..4d5f67cbe 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -79,7 +79,8 @@ Supported formats: - JPEG 2000 (`*.jp2`), loaded with any plugin that provides `Jpeg2000Importer` - KTX2 (`*.ktx2` or data with corresponding signature), loaded with - @ref KtxImporter or any other plugin that provides it + @ref KtxImporter or any other plugin that provides it. If not found, + @ref BasisImporter is tried as a fallback. - Multiple-image Network Graphics (`*.mng`), loaded with any plugin that provides `MngImporter` - Portable Bitmap (`*.pbm`), loaded with any plugin that provides `PbmImporter` diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index ccc9e24a0..9409d6faa 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,9 @@ struct AnyImageImporterTest: TestSuite::Tester { void load3D(); void detect(); + void ktxBasisFallbackFile(); + void ktxBasisFallbackData(); + void unknownExtension(); void unknownSignature(); void emptyData(); @@ -129,6 +133,31 @@ constexpr struct { /* Not testing everything, just the most important ones */ }; +const struct { + const char* name; + bool ktxImporterPresent; + bool basisImporterPresent; + bool verbose; + const char* expectedMessage; +} KtxBasisFallbackData[]{ + {"both KtxImporter and BasisImporter present", true, true, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only KtxImporter present", true, false, true, + "Trade::AnyImageImporter::{}(): using KtxImporter\n"}, + {"only BasisImporter present", false, true, true, + "Trade::AnyImageImporter::{0}(): KtxImporter not found, trying a fallback\n" + "Trade::AnyImageImporter::{0}(): using BasisImporter\n"}, + {"only BasisImporter present, verbose output disabled", false, true, false, + nullptr}, + {"neither present", false, false, true, + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + "PluginManager::Manager::load(): plugin KtxImporter is not static and was not found in nonexistent\n" + #else + "PluginManager::Manager::load(): plugin KtxImporter was not found\n" + #endif + "Trade::AnyImageImporter::{}(): cannot load the KtxImporter plugin"} +}; + using namespace Containers::Literals; const struct { @@ -173,6 +202,10 @@ AnyImageImporterTest::AnyImageImporterTest() { addInstancedTests({&AnyImageImporterTest::detect}, Containers::arraySize(DetectData)); + addInstancedTests({&AnyImageImporterTest::ktxBasisFallbackFile, + &AnyImageImporterTest::ktxBasisFallbackData}, + Containers::arraySize(KtxBasisFallbackData)); + addTests({&AnyImageImporterTest::unknownExtension}); addInstancedTests({&AnyImageImporterTest::unknownSignature}, @@ -297,47 +330,126 @@ void AnyImageImporterTest::detect() { CORRADE_VERIFY(read); CORRADE_VERIFY(!importer->openData(*read)); } else CORRADE_VERIFY(!importer->openFile(filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", data.plugin, data.asData ? "openData" : "openFile")); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", + data.plugin, data.asData ? "openData" : "openFile")); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", data.plugin, data.asData ? "openData" : "openFile")); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnyImageImporter::{1}(): cannot load the {0} plugin\n", + data.plugin, data.asData ? "openData" : "openFile")); #endif } -void AnyImageImporterTest::unknownExtension() { - std::ostringstream output; - Error redirectError{&output}; +void AnyImageImporterTest::ktxBasisFallbackFile() { + auto&& data = KtxBasisFallbackData[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 + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openFile(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openFile"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} + +void AnyImageImporterTest::ktxBasisFallbackData() { + auto&& data = KtxBasisFallbackData[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 + + /* Catch also ABI and interface mismatch errors */ + if(data.ktxImporterPresent && !(manager.load("KtxImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("KtxImporter plugin can't be loaded."); + if(data.basisImporterPresent && !(manager.load("BasisImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("BasisImporter plugin can't be loaded."); + + /* Set invalid plugin directory to ensure the remaining plugins don't get + loaded after this point */ + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + manager.setPluginDirectory("nonexistent"); + #endif + + Containers::Pointer importer = manager.instantiate("AnyImageImporter"); + if(data.verbose) + importer->setFlags(ImporterFlag::Verbose); + + Containers::Optional> read = Utility::Path::read(Utility::Path::join(ANYIMAGEIMPORTER_TEST_DIR, "basis.ktx2")); + CORRADE_VERIFY(read); + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* We don't care if the file opens (it won't if BasisImporter isn't + present), just verifying if correct plugin got picked by checking the + message. */ + importer->openData(*read); + if(data.expectedMessage) CORRADE_COMPARE_AS(out.str(), + Utility::formatString(data.expectedMessage, "openData"), + TestSuite::Compare::StringHasPrefix); + else CORRADE_COMPARE(out.str(), ""); +} +void AnyImageImporterTest::unknownExtension() { Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openFile("image.xcf")); - CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openFile(): cannot determine the format of image.xcf\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("image.xcf")); + CORRADE_COMPARE(out.str(), "Trade::AnyImageImporter::openFile(): cannot determine the format of image.xcf\n"); } void AnyImageImporterTest::unknownSignature() { auto&& data = DetectUnknownData[testCaseInstanceId()]; setTestCaseDescription(data.name); - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openData(data.data)); - CORRADE_COMPARE(output.str(), Utility::formatString("Trade::AnyImageImporter::openData(): cannot determine the format from signature 0x{}\n", data.signature)); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openData(data.data)); + CORRADE_COMPARE(out.str(), Utility::formatString("Trade::AnyImageImporter::openData(): cannot determine the format from signature 0x{}\n", data.signature)); } void AnyImageImporterTest::emptyData() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(!importer->openData(nullptr)); - CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openData(nullptr)); + CORRADE_COMPARE(out.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); } void AnyImageImporterTest::propagateFlags() { diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index f6599c548..a5aa3a79e 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -56,6 +56,8 @@ corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp # Generated by AnyImageConverterTest::convert{1D,3D}() 1d.ktx2 3d.ktx2 + # rgb.ktx2 from BasisImporter test data (in magnum-plugins), renamed + basis.ktx2 gray.jpg image.exr image.tiff diff --git a/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 b/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 new file mode 100644 index 000000000..41cc6ed21 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/basis.ktx2 differ diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index 9c5934767..84c5d126e 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -25,16 +25,19 @@ #include "AnySceneConverter.h" +#include +#include #include -#include /* for PluginManager */ #include #include #include #include /* for PluginMetadata::name() */ +#include /* CORRADE_UNUSED */ #include #include -#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" #include "MagnumPlugins/Implementation/propagateConfiguration.h" namespace Magnum { namespace Trade { @@ -48,7 +51,26 @@ AnySceneConverter::AnySceneConverter(PluginManager::AbstractManager& manager, co AnySceneConverter::~AnySceneConverter() = default; SceneConverterFeatures AnySceneConverter::doFeatures() const { - return SceneConverterFeature::ConvertMeshToFile; + /* Report that we can convert meshes and scenes to files, because that the + plugin can do always as it dispatches there. But everything else is + reported only once an actual converter plugin is loaded -- i.e., after beginFile() is called. + + Additionally, to allow using beginFile() + add(MeshData) + endFile() + with converters that only support ConvertMeshToFile, we "fake" report + AddMesh supported as well. If we wouldn't, the base class would try to + go through convertToFile() instead, causing the mesh to not be delegated + to _converter->add() but instead supplied to a whole new plugin + instance. Then, upon delegating to _converter->endFile(), it would fail + due to having 0 meshes. */ + + SceneConverterFeatures delegatedFeatures = + _converter ? _converter->features() : SceneConverterFeatures{}; + if(delegatedFeatures & SceneConverterFeature::ConvertMeshToFile) + delegatedFeatures |= SceneConverterFeature::AddMeshes; + + return SceneConverterFeature::ConvertMeshToFile| + SceneConverterFeature::ConvertMultipleToFile| + delegatedFeatures; } bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { @@ -61,7 +83,10 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: /* Detect the plugin from extension */ Containers::StringView plugin; - if(normalizedExtension == ".ply"_s) + if(normalizedExtension == ".gltf"_s || + normalizedExtension == ".glb"_s) + plugin = "GltfSceneConverter"_s; + else if(normalizedExtension == ".ply"_s) plugin = "StanfordSceneConverter"_s; else { Error{} << "Trade::AnySceneConverter::convertToFile(): cannot determine the format of" << filename; @@ -95,7 +120,169 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: return converter->convertToFile(mesh, filename); } +void AnySceneConverter::doAbort() { + _converter->abort(); + _converter = {}; +} + +bool AnySceneConverter::doBeginFile(const Containers::StringView filename) { + CORRADE_INTERNAL_ASSERT(manager()); + + /* We don't detect any double extensions yet, so we can normalize just the + extension. In case we eventually might, it'd have to be split() instead + to save at least by normalizing just the filename and not the path. */ + const Containers::String normalizedExtension = Utility::String::lowercase(Utility::Path::splitExtension(filename).second()); + + /* Detect the plugin from extension */ + Containers::StringView plugin; + if(normalizedExtension == ".gltf"_s || + normalizedExtension == ".glb"_s) + plugin = "GltfSceneConverter"_s; + else if(normalizedExtension == ".ply"_s) + plugin = "StanfordSceneConverter"_s; + else { + Error{} << "Trade::AnySceneConverter::beginFile(): cannot determine the format of" << filename; + return false; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error{} << "Trade::AnySceneConverter::beginFile(): 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::beginFile(): using" << plugin; + if(plugin != metadata->name()) + d << "(provided by" << metadata->name() << Debug::nospace << ")"; + } + + /* Instantiate the plugin, propagate flags */ + Containers::Pointer converter = static_cast*>(manager())->instantiate(plugin); + converter->setFlags(flags()); + + /* Propagate configuration */ + Magnum::Implementation::propagateConfiguration("Trade::AnySceneConverter::beginFile():", {}, metadata->name(), configuration(), converter->configuration()); + + /* Try to begin the file (error output should be printed by the plugin + itself) */ + if(!converter->beginFile(filename)) return false; + + /* Success, save the instance */ + _converter = std::move(converter); + return true; +} + +bool AnySceneConverter::doEndFile(Containers::StringView) { + /* Destroy the converter instance after the operation finishes to avoid + keeping now-useless state around */ + const bool out = _converter->endFile(); + _converter = {}; + return out; +} + +/* It's easier to use CORRADE_UNUSED than to have #ifndef CORRADE_NO_ASSERT */ + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SceneData& scene, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->sceneCount()); + return !!_converter->add(scene, name); +} + +void AnySceneConverter::doSetSceneFieldName(const UnsignedInt field, const Containers::StringView name) { + return _converter->setSceneFieldName(sceneFieldCustom(field), name); +} + +void AnySceneConverter::doSetObjectName(const UnsignedLong object, const Containers::StringView name) { + return _converter->setObjectName(object, name); +} + +void AnySceneConverter::doSetDefaultScene(const UnsignedInt id) { + return _converter->setDefaultScene(id); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const AnimationData& animation, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->animationCount()); + return !!_converter->add(animation, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const LightData& light, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->lightCount()); + return !!_converter->add(light, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const CameraData& camera, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->cameraCount()); + return !!_converter->add(camera, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SkinData2D& skin, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->skin2DCount()); + return !!_converter->add(skin, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const SkinData3D& skin, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->skin3DCount()); + return !!_converter->add(skin, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const MeshData& mesh, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->meshCount()); + return !!_converter->add(mesh, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable meshLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->meshCount()); + return !!_converter->add(meshLevels, name); +} + +void AnySceneConverter::doSetMeshAttributeName(const UnsignedShort attribute, const Containers::StringView name) { + return _converter->setMeshAttributeName(meshAttributeCustom(attribute), name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const MaterialData& material, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->materialCount()); + return !!_converter->add(material, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const TextureData& texture, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->textureCount()); + return !!_converter->add(texture, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData1D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image1DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image1DCount()); + return !!_converter->add(imageLevels, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData2D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image2DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image2DCount()); + return !!_converter->add(imageLevels, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const ImageData3D& image, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image3DCount()); + return !!_converter->add(image, name); +} + +bool AnySceneConverter::doAdd(CORRADE_UNUSED const UnsignedInt id, const Containers::Iterable imageLevels, const Containers::StringView name) { + CORRADE_INTERNAL_ASSERT(id == _converter->image3DCount()); + return !!_converter->add(imageLevels, name); +} + }} CORRADE_PLUGIN_REGISTER(AnySceneConverter, Magnum::Trade::AnySceneConverter, - "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2") + "cz.mosra.magnum.Trade.AbstractSceneConverter/0.2") diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h index c83e1bf04..8fa18d800 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h @@ -30,6 +30,8 @@ * @m_since{2020,06} */ +#include + #include "Magnum/Trade/AbstractSceneConverter.h" #include "MagnumPlugins/AnySceneConverter/configure.h" @@ -58,6 +60,8 @@ namespace Magnum { namespace Trade { Detects file type based on file extension, loads corresponding plugin and then tries to convert the file with it. Supported formats: +- glTF (`*.gltf`, `*.glb`), converted with @ref GltfSceneConverter or any + other plugin that provides it - Stanford (`*.ply`), converted with @ref StanfordSceneConverter or any other plugin that provides it @@ -96,14 +100,20 @@ 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. +On a call to @ref convertToFile() or @ref beginFile(), 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. A warning is emitted in case an +option set is not present in the default configuration of the target plugin. + +The @ref features() initially report just +@ref SceneConverterFeature::ConvertMeshToFile and @relativeref{SceneConverterFeature,ConvertMultipleToFile} --- i.e., not +advertising support for any actual data types. These are included only once +@ref beginFile() is called, taken from the concrete plugin implementation. -The output of the @ref convertToFile() function called on the concrete -implementation is then proxied back. +Calls to the @ref endFile(), @ref add() and related functions are then proxied +to the concrete implementation. The @ref abort() function aborts and destroys +the internally instantiated plugin; @ref isConverting() works as usual. */ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneConverter { public: @@ -118,6 +128,39 @@ class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneCon private: MAGNUM_ANYSCENECONVERTER_LOCAL SceneConverterFeatures doFeatures() const override; MAGNUM_ANYSCENECONVERTER_LOCAL bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override; + + void doAbort() override; + bool doBeginFile(Containers::StringView filename) override; + bool doEndFile(Containers::StringView filename) override; + + bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override; + void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override; + void doSetObjectName(UnsignedLong object, Containers::StringView name) override; + void doSetDefaultScene(UnsignedInt id) override; + + bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override; + void doSetMeshAttributeName(UnsignedShort attribute, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override; + bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override; + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override; + + Containers::Pointer _converter; }; }} diff --git a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp index 0844e5764..7f2526e30 100644 --- a/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp +++ b/src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -45,13 +46,21 @@ struct AnySceneConverterTest: TestSuite::Tester { explicit AnySceneConverterTest(); void convert(); - void detect(); + void convertBeginEnd(); - void unknown(); + void detectConvert(); + void detectBeginEnd(); - void propagateFlags(); - void propagateConfiguration(); - void propagateConfigurationUnknown(); + void unknownConvert(); + void unknownBeginEnd(); + + void propagateFlagsConvert(); + void propagateFlagsBeginEnd(); + + void propagateConfigurationConvert(); + void propagateConfigurationBeginEnd(); + void propagateConfigurationUnknownConvert(); + void propagateConfigurationUnknownBeginEnd(); /* configuration propagation fully tested in AnySceneImporter, as there the plugins have configuration subgroups as well */ @@ -63,23 +72,46 @@ constexpr struct { const char* name; const char* filename; const char* plugin; -} DetectData[]{ +} DetectConvertData[]{ + {"glTF", "khronos.gltf", "GltfSceneConverter"}, + {"glTF binary", "khronos.glb", "GltfSceneConverter"}, + {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, + /* Have at least one test case with uppercase */ + {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} +}; + +constexpr struct { + const char* name; + const char* filename; + const char* plugin; +} DetectBeginEndData[]{ + {"glTF", "khronos.gltf", "GltfSceneConverter"}, + {"glTF binary", "khronos.glb", "GltfSceneConverter"}, {"Stanford PLY", "bunny.ply", "StanfordSceneConverter"}, /* Have at least one test case with uppercase */ {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordSceneConverter"} }; AnySceneConverterTest::AnySceneConverterTest() { - addTests({&AnySceneConverterTest::convert}); + addTests({&AnySceneConverterTest::convert, + &AnySceneConverterTest::convertBeginEnd}); + + addInstancedTests({&AnySceneConverterTest::detectConvert}, + Containers::arraySize(DetectConvertData)); + + addInstancedTests({&AnySceneConverterTest::detectBeginEnd}, + Containers::arraySize(DetectBeginEndData)); - addInstancedTests({&AnySceneConverterTest::detect}, - Containers::arraySize(DetectData)); + addTests({&AnySceneConverterTest::unknownConvert, + &AnySceneConverterTest::unknownBeginEnd, - addTests({&AnySceneConverterTest::unknown, + &AnySceneConverterTest::propagateFlagsConvert, + &AnySceneConverterTest::propagateFlagsBeginEnd, - &AnySceneConverterTest::propagateFlags, - &AnySceneConverterTest::propagateConfiguration, - &AnySceneConverterTest::propagateConfigurationUnknown}); + &AnySceneConverterTest::propagateConfigurationConvert, + &AnySceneConverterTest::propagateConfigurationBeginEnd, + &AnySceneConverterTest::propagateConfigurationUnknownConvert, + &AnySceneConverterTest::propagateConfigurationUnknownBeginEnd}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -121,8 +153,41 @@ void AnySceneConverterTest::convert() { CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENEIMPORTER_TEST_DIR, "triangle.ply"), TestSuite::Compare::File); } -void AnySceneConverterTest::detect() { - auto&& data = DetectData[testCaseInstanceId()]; +void AnySceneConverterTest::convertBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(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->beginFile(filename)); + CORRADE_COMPARE(converter->add(mesh), 0); + CORRADE_VERIFY(converter->endFile()); + + /* This file is reused in AnySceneImporter tests, so it's worth to save it + here */ + CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENEIMPORTER_TEST_DIR, "triangle.ply"), TestSuite::Compare::File); +} + +void AnySceneConverterTest::detectConvert() { + auto&& data = DetectConvertData[testCaseInstanceId()]; setTestCaseDescription(data.name); Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); @@ -130,27 +195,59 @@ void AnySceneConverterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!converter->convertToFile(MeshData{MeshPrimitive::Triangles, 0}, data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneConverter::convertToFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } -void AnySceneConverterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; +void AnySceneConverterTest::detectBeginEnd() { + auto&& data = DetectConvertData[testCaseInstanceId()]; + setTestCaseDescription(data.name); Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->beginFile(data.filename)); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneConverter::beginFile(): cannot load the {0} plugin\n", + data.plugin)); + #else + CORRADE_COMPARE(out.str(), Utility::formatString( + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneConverter::beginFile(): cannot load the {0} plugin\n", data.plugin)); + #endif +} + +void AnySceneConverterTest::unknownConvert() { + Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); + + std::ostringstream out; + Error redirectError{&out}; CORRADE_VERIFY(!converter->convertToFile(MeshData{MeshPrimitive::Triangles, 0}, "mesh.obj")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); +} - CORRADE_COMPARE(output.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n"); +void AnySceneConverterTest::unknownBeginEnd() { + Containers::Pointer converter = _manager.instantiate("AnySceneConverter"); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter->beginFile("mesh.obj")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::beginFile(): cannot determine the format of mesh.obj\n"); } -void AnySceneConverterTest::propagateFlags() { +void AnySceneConverterTest::propagateFlagsConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -188,7 +285,47 @@ void AnySceneConverterTest::propagateFlags() { CORRADE_SKIP("No plugin with verbose output available to test flag propagation."); } -void AnySceneConverterTest::propagateConfiguration() { +void AnySceneConverterTest::propagateFlagsBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::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->beginFile(filename)); + } + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), + "Trade::AnySceneConverter::beginFile(): 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::propagateConfigurationConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -221,7 +358,42 @@ void AnySceneConverterTest::propagateConfiguration() { CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENECONVERTER_TEST_DIR, "objectid.ply"), TestSuite::Compare::File); } -void AnySceneConverterTest::propagateConfigurationUnknown() { +void AnySceneConverterTest::propagateConfigurationBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("StanfordSceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("StanfordSceneConverter plugin can't be loaded."); + + Containers::String filename = Utility::Path::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->beginFile(filename)); + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + /* Compare to an expected output to ensure the custom attribute name was + used */ + CORRADE_COMPARE_AS(filename, Utility::Path::join(ANYSCENECONVERTER_TEST_DIR, "objectid.ply"), TestSuite::Compare::File); +} + +void AnySceneConverterTest::propagateConfigurationUnknownConvert() { PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); @@ -249,6 +421,38 @@ void AnySceneConverterTest::propagateConfigurationUnknown() { CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::convertToFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); } +void AnySceneConverterTest::propagateConfigurationUnknownBeginEnd() { + PluginManager::Manager manager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + #ifdef ANYSCENECONVERTER_PLUGIN_FILENAME + CORRADE_VERIFY(manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + + /* Catch also ABI and interface mismatch errors */ + if(!(manager.load("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->beginFile(Utility::Path::join(ANYSCENECONVERTER_TEST_OUTPUT_DIR, "file.ply"))); + } + CORRADE_VERIFY(converter->add(mesh)); + CORRADE_VERIFY(converter->endFile()); + CORRADE_COMPARE(out.str(), "Trade::AnySceneConverter::beginFile(): option noSuchOption not recognized by StanfordSceneConverter\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnySceneConverterTest) diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 4887ae510..56d79fe6a 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -91,6 +91,7 @@ constexpr struct { {"3MF", "print.3mf", "3mfImporter"}, {"FBX", "autodesk.fbx", "FbxImporter"}, {"glTF", "khronos.gltf", "GltfImporter"}, + {"glTF binary", "khronos.glb", "GltfImporter"}, {"OpenGEX", "eric.ogex", "OpenGexImporter"}, {"Stanford PLY", "bunny.ply", "StanfordImporter"}, {"Stanford PLY uppercase", "ARMADI~1.PLY", "StanfordImporter"}, @@ -182,24 +183,26 @@ void AnySceneImporterTest::detect() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->openFile(data.filename)); - /* Can't use raw string literals in macros on GCC 4.8 */ #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\nTrade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent\n" + "Trade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #else CORRADE_COMPARE(out.str(), Utility::formatString( -"PluginManager::Manager::load(): plugin {0} was not found\nTrade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", data.plugin)); + "PluginManager::Manager::load(): plugin {0} was not found\n" + "Trade::AnySceneImporter::openFile(): cannot load the {0} plugin\n", + data.plugin)); #endif } void AnySceneImporterTest::unknown() { - std::ostringstream output; - Error redirectError{&output}; - Containers::Pointer importer = _manager.instantiate("AnySceneImporter"); - CORRADE_VERIFY(!importer->openFile("mesh.wtf")); - CORRADE_COMPARE(output.str(), "Trade::AnySceneImporter::openFile(): cannot determine the format of mesh.wtf\n"); + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile("mesh.wtf")); + CORRADE_COMPARE(out.str(), "Trade::AnySceneImporter::openFile(): cannot determine the format of mesh.wtf\n"); } void AnySceneImporterTest::propagateFlags() { diff --git a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp index d2aa04181..106991eec 100644 --- a/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp +++ b/src/MagnumPlugins/AnyShaderConverter/Test/AnyConverterTest.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/src/MagnumPlugins/MagnumFont/CMakeLists.txt b/src/MagnumPlugins/MagnumFont/CMakeLists.txt index a26164f59..77b1f8ef0 100644 --- a/src/MagnumPlugins/MagnumFont/CMakeLists.txt +++ b/src/MagnumPlugins/MagnumFont/CMakeLists.txt @@ -52,6 +52,8 @@ endif() target_link_libraries(MagnumFont PUBLIC Magnum MagnumText MagnumTrade) if(CORRADE_TARGET_WINDOWS) target_link_libraries(MagnumFont PUBLIC TgaImporter) +elseif(MAGNUM_MAGNUMFONT_BUILD_STATIC) + target_link_libraries(MagnumFont INTERFACE TgaImporter) endif() install(FILES MagnumFont.h ${CMAKE_CURRENT_BINARY_DIR}/configure.h diff --git a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp index 327f887b1..69380cc1e 100644 --- a/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp +++ b/src/MagnumPlugins/TgaImageConverter/Test/TgaImageConverterTest.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include /** @todo remove once Debug is stream-free */ @@ -142,6 +142,9 @@ void TgaImageConverterTest::rgb() { setTestCaseDescription(data.name); Containers::Pointer converter = _converterManager.instantiate("TgaImageConverter"); + CORRADE_COMPARE(converter->extension(), "tga"); + CORRADE_COMPARE(converter->mimeType(), "image/x-tga"); + converter->setFlags(data.flags); std::ostringstream out; diff --git a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp index e487fc91c..821a26e3f 100644 --- a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp +++ b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -40,12 +41,23 @@ namespace Magnum { namespace Trade { +using namespace Containers::Literals; + TgaImageConverter::TgaImageConverter() = default; TgaImageConverter::TgaImageConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin): AbstractImageConverter{manager, plugin} {} ImageConverterFeatures TgaImageConverter::doFeatures() const { return ImageConverterFeature::Convert2DToData; } +Containers::String TgaImageConverter::doExtension() const { return "tga"_s; } + +Containers::String TgaImageConverter::doMimeType() const { + /* https://en.wikipedia.org/wiki/Truevision_TGA says there's no registered + MIME type. It probably never will be. Using `file --mime-type` on a TGA + file returns image/x-tga, so using that here as well. */ + return "image/x-tga"_s; +} + Containers::Optional> TgaImageConverter::doConvertToData(const ImageView2D& image) { /* Warn about lost metadata */ if(image.flags() & ImageFlag2D::Array) { @@ -98,4 +110,4 @@ Containers::Optional> TgaImageConverter::doConvertToData }} CORRADE_PLUGIN_REGISTER(TgaImageConverter, Magnum::Trade::TgaImageConverter, - "cz.mosra.magnum.Trade.AbstractImageConverter/0.3.2") + "cz.mosra.magnum.Trade.AbstractImageConverter/0.3.3") diff --git a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h index 43767e24d..1eccd9b35 100644 --- a/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h +++ b/src/MagnumPlugins/TgaImageConverter/TgaImageConverter.h @@ -97,6 +97,10 @@ and have the files smaller, use the @ref StbImageConverter plugin instead. The TGA file format doesn't have a way to distinguish between 2D and 1D array images. If an image has @ref ImageFlag2D::Array set, a warning is printed and the file is saved as a regular 2D image. + +While TGA files can have several extensions, @ref extension() always returns +@cpp "tga" @ce as that's the most common one. As TGA doesn't have a registered +MIME type, @ref mimeType() returns @cpp "image/x-tga" @ce. */ class MAGNUM_TGAIMAGECONVERTER_EXPORT TgaImageConverter: public AbstractImageConverter { public: @@ -107,8 +111,10 @@ class MAGNUM_TGAIMAGECONVERTER_EXPORT TgaImageConverter: public AbstractImageCon explicit TgaImageConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin); private: - ImageConverterFeatures MAGNUM_TGAIMAGECONVERTER_LOCAL doFeatures() const override; - Containers::Optional> MAGNUM_TGAIMAGECONVERTER_LOCAL doConvertToData(const ImageView2D& image) override; + MAGNUM_TGAIMAGECONVERTER_LOCAL ImageConverterFeatures doFeatures() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::String doExtension() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::String doMimeType() const override; + MAGNUM_TGAIMAGECONVERTER_LOCAL Containers::Optional> doConvertToData(const ImageView2D& image) override; }; }} diff --git a/src/MagnumPlugins/TgaImporter/TgaImporter.h b/src/MagnumPlugins/TgaImporter/TgaImporter.h index faf2b586c..8cbfbbf68 100644 --- a/src/MagnumPlugins/TgaImporter/TgaImporter.h +++ b/src/MagnumPlugins/TgaImporter/TgaImporter.h @@ -110,12 +110,12 @@ class MAGNUM_TGAIMPORTER_EXPORT TgaImporter: public AbstractImporter { ~TgaImporter(); private: - ImporterFeatures MAGNUM_TGAIMPORTER_LOCAL doFeatures() const override; - bool MAGNUM_TGAIMPORTER_LOCAL doIsOpened() const override; - void MAGNUM_TGAIMPORTER_LOCAL doOpenData(Containers::Array&& data, DataFlags dataFlags) override; - void MAGNUM_TGAIMPORTER_LOCAL doClose() override; - UnsignedInt MAGNUM_TGAIMPORTER_LOCAL doImage2DCount() const override; - Containers::Optional MAGNUM_TGAIMPORTER_LOCAL doImage2D(UnsignedInt id, UnsignedInt level) override; + MAGNUM_TGAIMPORTER_LOCAL ImporterFeatures doFeatures() const override; + MAGNUM_TGAIMPORTER_LOCAL bool doIsOpened() const override; + MAGNUM_TGAIMPORTER_LOCAL void doOpenData(Containers::Array&& data, DataFlags dataFlags) override; + MAGNUM_TGAIMPORTER_LOCAL void doClose() override; + MAGNUM_TGAIMPORTER_LOCAL UnsignedInt doImage2DCount() const override; + MAGNUM_TGAIMPORTER_LOCAL Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override; Containers::Array _in; }; diff --git a/src/singles/MagnumMath.hpp b/src/singles/MagnumMath.hpp index 3ab46ddc8..fab3cb5f5 100644 --- a/src/singles/MagnumMath.hpp +++ b/src/singles/MagnumMath.hpp @@ -15,6 +15,19 @@ - GitHub project page — https://github.com/mosra/magnum - GitHub Singles repository — https://github.com/mosra/magnum-singles + The library has a separate non-inline implementation part, enable it *just + once* like this: + + #define MAGNUM_MATH_IMPLEMENTATION + #include + + If you need the deinlined symbols to be exported from a shared library, + `#define MAGNUM_EXPORT` as appropriate. In addition, contents of the + GlmIntegration and EigenIntegration libraries are included as well --- + opt-in by specifying either `#define MAGNUM_MATH_GLM_INTEGRATION` or + `#define MAGNUM_MATH_EIGEN_INTEGRATION` before including the file. + Including it multiple times with different macros defined works as well. + v2020.06-0-gfac6f4da2 (2020-06-27) - Various fixes for Clang-CL compatibility - Expanding the APIs to work with Half and long double types