Browse Source

Merge 260dee1565 into ec3e308c9b

pull/427/merge
Vladimír Vondruš 6 years ago committed by GitHub
parent
commit
5a96fbf8df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      CMakeLists.txt
  2. 107
      doc/blob.dox
  3. 2
      doc/building.dox
  4. 8
      doc/changelog.dox
  5. 2
      doc/cmake.dox
  6. 1
      doc/features.dox
  7. 5
      doc/plugins.dox
  8. 6
      doc/snippets/CMakeLists.txt
  9. 41
      doc/snippets/MagnumDebugTools-gl.cpp
  10. 63
      doc/snippets/MagnumDebugTools.cpp
  11. 16
      doc/snippets/MagnumTrade.cpp
  12. 6
      doc/snippets/debugtools-frameprofiler.ansi
  13. 112
      doc/snippets/debugtools-frameprofiler.cpp
  14. 26
      modules/FindMagnum.cmake
  15. 1
      package/archlinux/PKGBUILD
  16. 1
      package/archlinux/PKGBUILD-android-arm64
  17. 1
      package/archlinux/PKGBUILD-clang
  18. 1
      package/archlinux/PKGBUILD-clang-addressanitizer
  19. 1
      package/archlinux/PKGBUILD-clang-analyzer
  20. 1
      package/archlinux/PKGBUILD-clang-libc++
  21. 1
      package/archlinux/PKGBUILD-coverage
  22. 1
      package/archlinux/PKGBUILD-emscripten
  23. 1
      package/archlinux/PKGBUILD-emscripten-wasm
  24. 1
      package/archlinux/PKGBUILD-emscripten-wasm-webgl2
  25. 1
      package/archlinux/PKGBUILD-emscripten-webgl2
  26. 1
      package/archlinux/PKGBUILD-es2
  27. 1
      package/archlinux/PKGBUILD-es2desktop
  28. 1
      package/archlinux/PKGBUILD-es3
  29. 1
      package/archlinux/PKGBUILD-es3desktop
  30. 1
      package/archlinux/PKGBUILD-gcc48
  31. 2
      package/archlinux/PKGBUILD-mingw-w64
  32. 2
      package/archlinux/PKGBUILD-release
  33. 1
      package/archlinux/magnum-git/PKGBUILD
  34. 1
      package/archlinux/magnum/PKGBUILD
  35. 1
      package/ci/appveyor-desktop-gles.bat
  36. 1
      package/ci/appveyor-desktop-mingw.bat
  37. 1
      package/ci/appveyor-desktop-vulkan.bat
  38. 1
      package/ci/appveyor-desktop.bat
  39. 1
      package/ci/appveyor-rt.bat
  40. 1
      package/ci/travis-android-arm.sh
  41. 1
      package/ci/travis-desktop-gles.sh
  42. 1
      package/ci/travis-desktop-vulkan.sh
  43. 1
      package/ci/travis-desktop.sh
  44. 1
      package/ci/travis-emscripten.sh
  45. 1
      package/ci/travis-ios-simulator.sh
  46. 1
      package/debian/rules
  47. 1
      package/gentoo/dev-libs/magnum/magnum-9999.ebuild
  48. 2
      package/homebrew/magnum.rb
  49. 1
      package/msys/PKGBUILD
  50. 1
      package/msys/magnum/PKGBUILD
  51. 13
      src/Magnum/DebugTools/CMakeLists.txt
  52. 715
      src/Magnum/DebugTools/FrameProfiler.cpp
  53. 754
      src/Magnum/DebugTools/FrameProfiler.h
  54. 2
      src/Magnum/DebugTools/Profiler.cpp
  55. 20
      src/Magnum/DebugTools/Profiler.h
  56. 8
      src/Magnum/DebugTools/Test/CMakeLists.txt
  57. 244
      src/Magnum/DebugTools/Test/FrameProfilerGLTest.cpp
  58. 1097
      src/Magnum/DebugTools/Test/FrameProfilerTest.cpp
  59. 198
      src/Magnum/Trade/AbstractSceneConverter.cpp
  60. 323
      src/Magnum/Trade/AbstractSceneConverter.h
  61. 4
      src/Magnum/Trade/CMakeLists.txt
  62. 94
      src/Magnum/Trade/Data.cpp
  63. 193
      src/Magnum/Trade/Data.h
  64. 20
      src/Magnum/Trade/Implementation/converterUtilities.h
  65. 150
      src/Magnum/Trade/MeshData.cpp
  66. 98
      src/Magnum/Trade/MeshData.h
  67. 10
      src/Magnum/Trade/Test/.gitattributes
  68. 484
      src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp
  69. 26
      src/Magnum/Trade/Test/CMakeLists.txt
  70. 214
      src/Magnum/Trade/Test/DataTest.cpp
  71. 293
      src/Magnum/Trade/Test/MeshDataTest.cpp
  72. BIN
      src/Magnum/Trade/Test/mesh-be32.blob
  73. BIN
      src/Magnum/Trade/Test/mesh-be64.blob
  74. BIN
      src/Magnum/Trade/Test/mesh-empty-be32.blob
  75. BIN
      src/Magnum/Trade/Test/mesh-empty-be64.blob
  76. BIN
      src/Magnum/Trade/Test/mesh-empty-le32.blob
  77. BIN
      src/Magnum/Trade/Test/mesh-empty-le64.blob
  78. BIN
      src/Magnum/Trade/Test/mesh-le32.blob
  79. BIN
      src/Magnum/Trade/Test/mesh-le64.blob
  80. BIN
      src/Magnum/Trade/Test/mesh-nonindexed-be32.blob
  81. BIN
      src/Magnum/Trade/Test/mesh-nonindexed-be64.blob
  82. BIN
      src/Magnum/Trade/Test/mesh-nonindexed-le32.blob
  83. BIN
      src/Magnum/Trade/Test/mesh-nonindexed-le64.blob
  84. 5
      src/Magnum/Trade/Trade.h
  85. 2
      src/Magnum/Trade/configure.h.cmake
  86. 5
      src/Magnum/Trade/imageconverter.cpp
  87. 210
      src/Magnum/Trade/sceneconverter.cpp
  88. 0
      src/MagnumPlugins/AnySceneConverter/AnySceneConverter.conf
  89. 80
      src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp
  90. 111
      src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h
  91. 68
      src/MagnumPlugins/AnySceneConverter/CMakeLists.txt
  92. 121
      src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp
  93. 71
      src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt
  94. 27
      src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake
  95. 26
      src/MagnumPlugins/AnySceneConverter/configure.h.cmake
  96. 35
      src/MagnumPlugins/AnySceneConverter/importStaticPlugin.cpp
  97. 2
      src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp
  98. 2
      src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h
  99. 1
      src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp
  100. 4
      src/MagnumPlugins/CMakeLists.txt

8
CMakeLists.txt

@ -80,6 +80,7 @@ option(WITH_AL_INFO "Build magnum-al-info utility" OFF)
option(WITH_ANYIMAGEIMPORTER "Build AnyImageImporter plugin" OFF)
option(WITH_ANYAUDIOIMPORTER "Build AnyAudioImporter plugin" OFF)
option(WITH_ANYIMAGECONVERTER "Build AnyImageConverter plugin" OFF)
option(WITH_ANYSCENECONVERTER "Build AnySceneConverter plugin" OFF)
option(WITH_ANYSCENEIMPORTER "Build AnySceneImporter plugin" OFF)
option(WITH_WAVAUDIOIMPORTER "Build WavAudioImporter plugin" OFF)
option(WITH_MAGNUMFONT "Build MagnumFont plugin" OFF)
@ -356,6 +357,10 @@ set(MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINA
set(MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/audioimporters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/audioimporters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/audioimporters)
@ -394,6 +399,7 @@ if(MAGNUM_PLUGINS_DIR)
set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/fontconverters)
set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/audioimporters)
endif()
if(MAGNUM_PLUGINS_DEBUG_DIR)
@ -402,12 +408,14 @@ if(MAGNUM_PLUGINS_DEBUG_DIR)
set(MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/importers)
set(MAGNUM_PLUGINS_FONT_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fonts)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/audioimporters)
endif()
if(MAGNUM_PLUGINS_RELEASE_DIR)
set(MAGNUM_PLUGINS_FONTCONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fontconverters)
set(MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/audioimporters)
endif()

107
doc/blob.dox

@ -0,0 +1,107 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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.
*/
namespace Magnum {
/** @page blob Magnum's memory-mappable serialization format
@brief Efficient and extensible format for storing binary data
@m_since_latest
@tableofcontents
@m_footernavigation
Apart from various data import and conversion plugins, described in the
@ref plugins "previous chapter", Magnum provides its own binary format. Files
stored in this format have a `*.blob` extension and are identified by various
permutations of the letters `BLOB` in their first few bytes.
The goal of the format is being usable directly without having to process the
data payload in any way. That allows for example the file contents to be
memory-mapped and operated on directly. In order to achieve this, there's four
different variants of the format based on whether it's running on a 32-bit or
64-bit system and whether the machine is Little- or Big-Endian. The @ref Trade
library itself provides serialization and deserialization of blob formats
matching the platform it's running on. Import and conversion of blobs with
different endianness or bitness (as well as compatibility with previous format
versions as the format will evolve) is handled by the
@ref Trade::MagnumImporter "MagnumImporter" and
@ref Trade::MagnumSceneConverter "MagnumSceneConverter" plugins --- since this
functionality is not strictly needed when shipping an application, it's
provided separately.
@section blob-implementation Implementation
The binary format consists of "chunks" similar to [RIFF](https://en.wikipedia.org/wiki/Resource_Interchange_File_Format),
and the main property is an ability to combine arbitrary chunks together in the
most trivial way possible as well as extracting them back. Each chunk has a
@ref Trade::DataChunkHeader containing a [FourCC](https://en.wikipedia.org/wiki/FourCC)-like @ref Trade::DataChunkType identifier and a chunk size, allowing applications to pick chunks that they're interested in and reliably skip the
others. Compared to RIFF the file doesn't have any "global" chunk in order to
make trivial file concatenation possible:
@code{.sh}
cat chair.blob table.blob > furniture.blob
@endcode
@section blob-iteration Chunk iteration
To be designed & written first.
@section blob-meshdata Mesh data
Currently there's just a single serializable data type, @ref Trade::MeshData.
You can create serialized blobs using @ref Trade::MeshData::serialize() or
alternatively using the @ref magnum-sceneconverter "magnum-sceneconverter"
tool, for example:
@code{.sh}
magnum-sceneconverter avocado.glb avocado.blob
@endcode
Deserialization is then done with @ref Trade::MeshData::deserialize(). The
function takes a memory view as an input and returns a @ref Trade::MeshData
instance pointing to that view, without copying or processing the data in any
way. A recommended way to access serialized data is thus via memory-mapping the
file (for example using @ref Utility::Directory::mapRead() or any other way
your platform allows), and keeping it around for as long as you need:
@snippet MagnumTrade.cpp blob-deserialize-mesh
@section blob-custom Custom chunk types
As said above, the format is designed to allow custom chunk types to be mixed
together with data recognized by Magnum. To make a custom chunk, create your
own @ref Trade::DataChunkType using @ref Corrade::Utility::Endianness::fourCC()
--- identifiers starting with an uppercase letter are reserved for Magnum
itself, custom application-specific data types should use a lowercase first
letter instead.
Then write a serialization/deserialization API similar to
@ref Trade::MeshData::serialize() / @ref Trade::MeshData::deserialize() with
the help of low-level @ref Trade::dataChunkHeaderSerializeInto() and
@ref Trade::dataChunkHeaderDeserialize(). Those functions will take care of
properly filling in required chunk header fields when serializing and checking
chunk validity when deserializing. Validation of the chunk data itself is then
up to you.
*/
}

2
doc/building.dox

@ -650,6 +650,8 @@ default.
building of the @ref Trade library.
- `WITH_ANYIMAGEIMPORTER` --- Build the @ref Trade::AnyImageImporter "AnyImageImporter"
plugin. Enables also building of the @ref Trade library.
- `WITH_ANYSCENECONVERTER` --- Build the @ref Trade::AnySceneConverter "AnySceneConverter"
plugin. Enables also building of the @ref Trade library.
- `WITH_ANYSCENEIMPORTER` --- Build the @ref Trade::AnySceneImporter "AnySceneImporter"
plugin. Enables also building of the @ref Trade library.
- `WITH_MAGNUMFONT` --- Build the @ref Text::MagnumFont "MagnumFont" plugin.

8
doc/changelog.dox

@ -72,6 +72,7 @@ See also:
- New @ref DebugTools::ColorMap namespace containing a few presets for
gradient visualization
- New @ref DebugTools::FrameProfiler utility for CPU and GPU profiling
@subsubsection changelog-latest-new-gl GL library
@ -269,6 +270,11 @@ See also:
- New @ref magnum-sceneconverter "magnum-sceneconverter" tool, similar to
@ref magnum-imageconverter "magnum-imageconverter" but suited for general
scene formats
- New @ref Trade::AbstractSceneConverter plugin interface and an
@ref Trade::AnySceneConverter "AnySceneConverter" plugin
- Efficient and extensible memory-mappable serialization format for binary
data. See @ref blob for an introduction, see also
[mosra/magnum#427](https://github.com/mosra/magnum/pull/427).
- Ability to import image mip levels via an additional parameter in
@ref Trade::AbstractImporter::image2D(),
@ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D
@ -630,6 +636,8 @@ See also:
This also means it's no longer
possible to override equality comparison epsilons at compile time, but that
was a rarely (if ever) used feature.
- @cpp DebugTools::Profiler @ce is obsolete, replaced with a much more
flexible and extensible @ref DebugTools::FrameProfiler
@subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs

2
doc/cmake.dox

@ -246,6 +246,8 @@ dependencies, you need to find the dependency and then link to it.
plugin
- `AnyImageImporter` --- @ref Trade::AnyImageImporter "AnyImageImporter"
plugin
- `AnySceneConverter` --- @ref Trade::AnySceneConverter "AnySceneConverter"
plugin
- `AnySceneImporter` --- @ref Trade::AnySceneImporter "AnySceneImporter"
plugin
- `MagnumFont` --- @ref Text::MagnumFont "MagnumFont" plugin

1
doc/features.dox

@ -37,6 +37,7 @@ necessary to read through everything, pick only what you need.
- @subpage transformations --- @copybrief transformations
- @subpage animation --- @copybrief animation
- @subpage plugins --- @copybrief plugins
- @subpage blob --- @copybrief blob
- @subpage opengl-wrapping --- @copybrief opengl-wrapping
- @subpage shaders --- @copybrief shaders
- @subpage scenegraph --- @copybrief scenegraph

5
doc/plugins.dox

@ -64,6 +64,9 @@ of given type. Magnum provides these plugin interfaces:
- @ref Trade::AbstractImageConverter --- conversion among various image
formats. See `*ImageConverter` classes in the @ref Trade namespace for
available image converter plugins.
- @ref Trade::AbstractSceneConverter --- conversion among various scene
formats, mesh optimization etc. See `*SceneConverter` classes in the
@ref Trade namespace for available scene converter plugins.
- @ref Text::AbstractFont --- font loading and glyph layout. See `*Font`
classes in the @ref Text namespace for available font plugins.
- @ref Text::AbstractFontConverter --- font and glyph cache conversion. See
@ -192,6 +195,8 @@ So far, the following plugins have the "any format" ability:
format
- @ref Trade::AnySceneImporter "AnySceneImporter" --- imports any scene
format
- @ref Trade::AnySceneConverter "AnySceneConverter" --- converts to any scene
format
- @ref Audio::AnyImporter "AnyImporter" --- imports any audio format
@section plugins-configuration Plugin-specific configuration

6
doc/snippets/CMakeLists.txt

@ -119,6 +119,12 @@ if(WITH_DEBUGTOOLS)
target_link_libraries(snippets-MagnumDebugTools PRIVATE MagnumDebugTools)
set_target_properties(snippets-MagnumDebugTools PROPERTIES FOLDER "Magnum/doc/snippets")
if(BUILD_GL_TESTS AND NOT MAGNUM_TARGET_GLES)
add_executable(debugtools-frameprofiler debugtools-frameprofiler.cpp)
target_link_libraries(debugtools-frameprofiler PRIVATE
MagnumDebugTools MagnumOpenGLTester)
endif()
# TODO: causes spurious linker errors on Travis iOS build, so I'm disabling it
if(NOT CORRADE_TARGET_IOS)
set(SNIPPETS_DIR ${CMAKE_CURRENT_SOURCE_DIR})

41
doc/snippets/MagnumDebugTools-gl.cpp

@ -37,6 +37,7 @@
#include "Magnum/DebugTools/TextureImage.h"
#include "Magnum/GL/Framebuffer.h"
#include "Magnum/GL/CubeMapTexture.h"
#include "Magnum/GL/SampleQuery.h"
#include "Magnum/GL/Texture.h"
#include "Magnum/GL/TextureFormat.h"
#include "Magnum/Math/Range.h"
@ -48,6 +49,10 @@
#include "Magnum/GL/BufferImage.h"
#endif
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#include "Magnum/DebugTools/FrameProfiler.h"
#endif
using namespace Magnum;
using namespace Magnum::Math::Literals;
@ -103,6 +108,33 @@ new DebugTools::ForceRenderer3D(manager, *object, {0.3f, 1.5f, -0.7f}, force,
/* [ForceRenderer] */
}
#ifndef MAGNUM_TARGET_GLES
{
/* [FrameProfiler-setup-delayed] */
GL::SampleQuery queries[3]{
GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed},
GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed},
GL::SampleQuery{GL::SampleQuery::Target::SamplesPassed}
};
DebugTools::FrameProfiler profiler{{
DebugTools::FrameProfiler::Measurement{"Samples",
DebugTools::FrameProfiler::Units::Count,
UnsignedInt(Containers::arraySize(queries)),
[](void* state, UnsignedInt current) {
static_cast<GL::SampleQuery*>(state)[current].begin();
},
[](void* state, UnsignedInt current) {
static_cast<GL::SampleQuery*>(state)[current].end();
},
[](void* state, UnsignedInt previous, UnsignedInt) {
return static_cast<GL::SampleQuery*>(state)[previous]
.result<UnsignedLong>();
}, queries}
}, 50};
/* [FrameProfiler-setup-delayed] */
}
#endif
{
SceneGraph::Object<SceneGraph::MatrixTransformation3D>* object{};
/* [ObjectRenderer] */
@ -116,6 +148,15 @@ manager.set("my", DebugTools::ObjectRendererOptions{}.setSize(0.3f));
new DebugTools::ObjectRenderer3D{manager, *object, "my", &debugDrawables};
/* [ObjectRenderer] */
}
{
/* [GLFrameProfiler-usage] */
DebugTools::GLFrameProfiler profiler{
DebugTools::GLFrameProfiler::Value::FrameTime|
DebugTools::GLFrameProfiler::Value::GpuDuration, 50};
/* [GLFrameProfiler-usage] */
}
{
GL::Texture2D texture;
Range2Di rect;

63
doc/snippets/MagnumDebugTools.cpp

@ -23,6 +23,7 @@
DEALINGS IN THE SOFTWARE.
*/
#include <chrono>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/PluginManager/Manager.h>
@ -31,6 +32,7 @@
#include "Magnum/ImageView.h"
#include "Magnum/PixelFormat.h"
#include "Magnum/DebugTools/CompareImage.h"
#include "Magnum/DebugTools/FrameProfiler.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Trade/AbstractImporter.h"
@ -101,5 +103,62 @@ CORRADE_COMPARE_WITH(actual.pixels<Color3ub>().flipped<0>(), expected,
}
};
/* To prevent macOS ranlib complaining that there are no symbols */
int main() {}
struct MyApp {
void drawEvent();
void drawEventAgain();
void swapBuffers();
void redraw();
DebugTools::FrameProfiler _profiler;
};
/* [FrameProfiler-usage] */
void MyApp::drawEvent() {
_profiler.beginFrame();
// actual drawing code …
_profiler.endFrame();
// possibly other code (such as UI) you don't want to have included in the
// measurements …
swapBuffers();
redraw();
}
/* [FrameProfiler-usage] */
void MyApp::drawEventAgain() {
/* [FrameProfiler-usage-console] */
_profiler.endFrame();
_profiler.printStatistics(10);
swapBuffers();
if(_profiler.isEnabled()) redraw();
}
/* [FrameProfiler-usage-console] */
int main() {
{
/* [FrameProfiler-setup-immediate] */
using std::chrono::high_resolution_clock;
high_resolution_clock::time_point frameBeginTime;
DebugTools::FrameProfiler profiler{{
DebugTools::FrameProfiler::Measurement{"CPU time",
DebugTools::FrameProfiler::Units::Nanoseconds,
[](void* state) {
*static_cast<high_resolution_clock::time_point*>(state)
= high_resolution_clock::now();
},
[](void* state) {
return UnsignedLong(
std::chrono::duration_cast<std::chrono::nanoseconds>(
*static_cast<high_resolution_clock::time_point*>(state)
- high_resolution_clock::now()).count());
}, &frameBeginTime}
}, 50};
/* [FrameProfiler-setup-immediate] */
}
}

16
doc/snippets/MagnumTrade.cpp

@ -65,6 +65,22 @@ using namespace Magnum::Math::Literals;
int main() {
/* GCC 4.8 and Clang 3.8 has problems with an implicit cast here */
#if (defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT))) && (!defined(CORRADE_TARGET_GCC) || __GNUC__ > 5 || (!defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ >= 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ >= 9))
{
/* [blob-deserialize-mesh] */
Containers::Array<const char, Utility::Directory::MapDeleter> blob =
Utility::Directory::mapRead("extremely-huge-spaceship.blob");
Containers::Optional<Trade::MeshData> spaceship =
Trade::MeshData::deserialize(blob);
if(!spaceship) Fatal{} << "oh no";
// ...
/* [blob-deserialize-mesh] */
}
#endif
{
/* [AbstractImporter-usage] */
PluginManager::Manager<Trade::AbstractImporter> manager;

6
doc/snippets/debugtools-frameprofiler.ansi

@ -0,0 +1,6 @@
Last 50 frames:
 Frame time: 16.65 ms
 CPU duration: 14.72 ms
 GPU duration: 10.89 ms
 Vertex fetch ratio: 0.24
 Primitives clipped: 59.67 %

112
doc/snippets/debugtools-frameprofiler.cpp

@ -0,0 +1,112 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <sstream>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/DebugTools/FrameProfiler.h"
/* Hacking around the fugly windowlessapp setup by including OpenGLTester */
#include "Magnum/GL/OpenGLTester.h"
using namespace Magnum;
class FrameProfiler: public Platform::WindowlessApplication {
public:
explicit FrameProfiler(const Arguments& arguments);
int exec() override { return 0; }
};
FrameProfiler::FrameProfiler(const Arguments& arguments): Platform::WindowlessApplication{arguments} {
/* Enable everything in the GL profiler and then introspect it to fake
its output 1:1 */
DebugTools::GLFrameProfiler glProfiler{
DebugTools::GLFrameProfiler::Value::FrameTime|
DebugTools::GLFrameProfiler::Value::CpuDuration|
DebugTools::GLFrameProfiler::Value::GpuDuration|
DebugTools::GLFrameProfiler::Value::VertexFetchRatio|
DebugTools::GLFrameProfiler::Value::PrimitiveClipRatio
, 50};
DebugTools::FrameProfiler profiler{{
DebugTools::FrameProfiler::Measurement{
glProfiler.measurementName(0),
glProfiler.measurementUnits(0),
glProfiler.measurementDelay(2),
[](void*, UnsignedInt) {},
[](void*, UnsignedInt) {},
[](void*, UnsignedInt, UnsignedInt) {
return UnsignedLong{16651567};
}, nullptr},
DebugTools::FrameProfiler::Measurement{
glProfiler.measurementName(1),
glProfiler.measurementUnits(1),
glProfiler.measurementDelay(2),
[](void*, UnsignedInt) {},
[](void*, UnsignedInt) {},
[](void*, UnsignedInt, UnsignedInt) {
return UnsignedLong{14720000};
}, nullptr},
DebugTools::FrameProfiler::Measurement{
glProfiler.measurementName(2),
glProfiler.measurementUnits(2),
glProfiler.measurementDelay(2),
[](void*, UnsignedInt) {},
[](void*, UnsignedInt) {},
[](void*, UnsignedInt, UnsignedInt) {
return UnsignedLong{10890000};
}, nullptr},
DebugTools::FrameProfiler::Measurement{
glProfiler.measurementName(3),
glProfiler.measurementUnits(3),
glProfiler.measurementDelay(3),
[](void*, UnsignedInt) {},
[](void*, UnsignedInt) {},
[](void*, UnsignedInt, UnsignedInt) {
return UnsignedLong{240};
}, nullptr},
DebugTools::FrameProfiler::Measurement{
glProfiler.measurementName(4),
glProfiler.measurementUnits(4),
glProfiler.measurementDelay(4),
[](void*, UnsignedInt) {},
[](void*, UnsignedInt) {},
[](void*, UnsignedInt, UnsignedInt) {
return UnsignedLong{59670};
}, nullptr},
}, 50};
for(std::size_t i = 0; i != 100; ++i) {
profiler.beginFrame();
profiler.endFrame();
}
std::ostringstream out; /* we don't want a TTY */
profiler.printStatistics(Debug{&out}, 1);
Debug{Debug::Flag::NoNewlineAtTheEnd} << out.str();
}
MAGNUM_WINDOWLESSAPPLICATION_MAIN(FrameProfiler)

26
modules/FindMagnum.cmake

@ -32,6 +32,8 @@
# font converter plugins
# MAGNUM_PLUGINS_IMAGECONVERTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic
# image converter plugins
# MAGNUM_PLUGINS_SCENECONVERTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic
# scene converter plugins
# MAGNUM_PLUGINS_IMPORTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic
# importer plugins
# MAGNUM_PLUGINS_AUDIOIMPORTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic
@ -53,6 +55,7 @@
# AnyAudioImporter - Any audio importer
# AnyImageConverter - Any image converter
# AnyImageImporter - Any image importer
# AnySceneConverter - Any scene converter
# AnySceneImporter - Any scene importer
# Audio - Audio library
# DebugTools - DebugTools library
@ -175,6 +178,10 @@
# plugin binary installation directory
# MAGNUM_PLUGINS_IMPORTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Importer
# plugin library installation directory
# MAGNUM_PLUGINS_SCENECONVERTER_[DEBUG|RELEASE]_BINARY_INSTALL_DIR - Scene
# converter plugin binary installation directory
# MAGNUM_PLUGINS_SCENECONVERTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Scene
# converter plugin library installation directory
# MAGNUM_PLUGINS_AUDIOIMPORTER_[DEBUG|RELEASE]_BINARY_INSTALL_DIR - Audio
# importer plugin binary installation directory
# MAGNUM_PLUGINS_AUDIOIMPORTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Audio
@ -354,9 +361,9 @@ set(_MAGNUM_LIBRARY_COMPONENT_LIST
CglContext EglContext GlxContext WglContext
OpenGLTester)
set(_MAGNUM_PLUGIN_COMPONENT_LIST
AnyAudioImporter AnyImageConverter AnyImageImporter AnySceneImporter
MagnumFont MagnumFontConverter ObjImporter TgaImageConverter TgaImporter
WavAudioImporter)
AnyAudioImporter AnyImageConverter AnyImageImporter AnySceneConverter
AnySceneImporter MagnumFont MagnumFontConverter ObjImporter
TgaImageConverter TgaImporter WavAudioImporter)
set(_MAGNUM_EXECUTABLE_COMPONENT_LIST
distancefieldconverter fontconverter imageconverter sceneconverter gl-info
al-info)
@ -456,7 +463,7 @@ set(_MAGNUM_ObjImporter_DEPENDENCIES MeshTools) # and below
foreach(_component ${_MAGNUM_PLUGIN_COMPONENT_LIST})
if(_component MATCHES ".+AudioImporter")
list(APPEND _MAGNUM_${_component}_DEPENDENCIES Audio)
elseif(_component MATCHES ".+(Importer|ImageConverter)")
elseif(_component MATCHES ".+(Importer|ImageConverter|SceneConverter)")
list(APPEND _MAGNUM_${_component}_DEPENDENCIES Trade)
elseif(_component MATCHES ".+(Font|FontConverter)")
list(APPEND _MAGNUM_${_component}_DEPENDENCIES Text TextureTools)
@ -547,6 +554,10 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
elseif(_component MATCHES ".+ImageConverter$")
set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX imageconverters)
# SceneConverter plugin specific name suffixes
elseif(_component MATCHES ".+SceneConverter$")
set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX sceneconverters)
# FontConverter plugin specific name suffixes
elseif(_component MATCHES ".+FontConverter$")
set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX fontconverters)
@ -1115,6 +1126,10 @@ set(MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINA
set(MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/audioimporters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/audioimporters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/audioimporters)
@ -1141,6 +1156,7 @@ if(MAGNUM_PLUGINS_DIR)
set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/fontconverters)
set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/audioimporters)
endif()
if(MAGNUM_PLUGINS_DEBUG_DIR)
@ -1149,11 +1165,13 @@ if(MAGNUM_PLUGINS_DEBUG_DIR)
set(MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/importers)
set(MAGNUM_PLUGINS_FONT_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fonts)
set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/audioimporters)
endif()
if(MAGNUM_PLUGINS_RELEASE_DIR)
set(MAGNUM_PLUGINS_FONTCONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fontconverters)
set(MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/imageconverters)
set(MAGNUM_PLUGINS_IMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/importers)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/audioimporters)
endif()

1
package/archlinux/PKGBUILD

@ -38,6 +38,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-android-arm64

@ -35,6 +35,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=OFF \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-clang

@ -41,6 +41,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-clang-addressanitizer

@ -42,6 +42,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-clang-analyzer

@ -33,6 +33,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-clang-libc++

@ -44,6 +44,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-coverage

@ -39,6 +39,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/PKGBUILD-emscripten

@ -35,6 +35,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-emscripten-wasm

@ -35,6 +35,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-emscripten-wasm-webgl2

@ -36,6 +36,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-emscripten-webgl2

@ -36,6 +36,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-es2

@ -31,6 +31,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-es2desktop

@ -35,6 +35,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-es3

@ -31,6 +31,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-es3desktop

@ -35,6 +35,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_OBJIMPORTER=ON \

1
package/archlinux/PKGBUILD-gcc48

@ -44,6 +44,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

2
package/archlinux/PKGBUILD-mingw-w64

@ -28,6 +28,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \
@ -61,6 +62,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

2
package/archlinux/PKGBUILD-release

@ -33,6 +33,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \
@ -70,6 +71,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/magnum-git/PKGBUILD

@ -36,6 +36,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/archlinux/magnum/PKGBUILD

@ -25,6 +25,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_AUDIO=ON \
-DWITH_DISTANCEFIELDCONVERTER=ON \

1
package/ci/appveyor-desktop-gles.bat

@ -33,6 +33,7 @@ cmake .. ^
-DWITH_ANYAUDIOIMPORTER=ON ^
-DWITH_ANYIMAGECONVERTER=ON ^
-DWITH_ANYIMAGEIMPORTER=ON ^
-DWITH_ANYSCENECONVERTER=ON ^
-DWITH_ANYSCENEIMPORTER=ON ^
-DWITH_MAGNUMFONT=ON ^
-DWITH_MAGNUMFONTCONVERTER=ON ^

1
package/ci/appveyor-desktop-mingw.bat

@ -32,6 +32,7 @@ cmake .. ^
-DWITH_ANYAUDIOIMPORTER=ON ^
-DWITH_ANYIMAGECONVERTER=ON ^
-DWITH_ANYIMAGEIMPORTER=ON ^
-DWITH_ANYSCENECONVERTER=ON ^
-DWITH_ANYSCENEIMPORTER=ON ^
-DWITH_MAGNUMFONT=ON ^
-DWITH_MAGNUMFONTCONVERTER=ON ^

1
package/ci/appveyor-desktop-vulkan.bat

@ -43,6 +43,7 @@ cmake .. ^
-DWITH_ANYAUDIOIMPORTER=OFF ^
-DWITH_ANYIMAGECONVERTER=OFF ^
-DWITH_ANYIMAGEIMPORTER=OFF ^
-DWITH_ANYSCENECONVERTER=ON ^
-DWITH_ANYSCENEIMPORTER=OFF ^
-DWITH_MAGNUMFONT=OFF ^
-DWITH_MAGNUMFONTCONVERTER=OFF ^

1
package/ci/appveyor-desktop.bat

@ -45,6 +45,7 @@ cmake .. ^
-DWITH_ANYAUDIOIMPORTER=ON ^
-DWITH_ANYIMAGECONVERTER=ON ^
-DWITH_ANYIMAGEIMPORTER=ON ^
-DWITH_ANYSCENECONVERTER=ON ^
-DWITH_ANYSCENEIMPORTER=ON ^
-DWITH_MAGNUMFONT=ON ^
-DWITH_MAGNUMFONTCONVERTER=ON ^

1
package/ci/appveyor-rt.bat

@ -71,6 +71,7 @@ cmake .. ^
-DWITH_ANYAUDIOIMPORTER=OFF ^
-DWITH_ANYIMAGECONVERTER=ON ^
-DWITH_ANYIMAGEIMPORTER=ON ^
-DWITH_ANYSCENECONVERTER=ON ^
-DWITH_ANYSCENEIMPORTER=ON ^
-DWITH_MAGNUMFONT=ON ^
-DWITH_MAGNUMFONTCONVERTER=ON ^

1
package/ci/travis-android-arm.sh

@ -63,6 +63,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=OFF \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/ci/travis-desktop-gles.sh

@ -37,6 +37,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/ci/travis-desktop-vulkan.sh

@ -47,6 +47,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=OFF \
-DWITH_ANYIMAGECONVERTER=OFF \
-DWITH_ANYIMAGEIMPORTER=OFF \
-DWITH_ANYSCENECONVERTER=OFF \
-DWITH_ANYSCENEIMPORTER=OFF \
-DWITH_MAGNUMFONT=OFF \
-DWITH_MAGNUMFONTCONVERTER=OFF \

1
package/ci/travis-desktop.sh

@ -33,6 +33,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/ci/travis-emscripten.sh

@ -56,6 +56,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/ci/travis-ios-simulator.sh

@ -53,6 +53,7 @@ cmake .. \
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/debian/rules vendored

@ -26,6 +26,7 @@ override_dh_auto_configure:
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/gentoo/dev-libs/magnum/magnum-9999.ebuild

@ -35,6 +35,7 @@ src_configure() {
-DWITH_ANYAUDIOIMPORTER=ON
-DWITH_ANYIMAGECONVERTER=ON
-DWITH_ANYIMAGEIMPORTER=ON
-DWITH_ANYSCENECONVERTER=ON
-DWITH_ANYSCENEIMPORTER=ON
-DWITH_MAGNUMFONT=ON
-DWITH_MAGNUMFONTCONVERTER=ON

2
package/homebrew/magnum.rb

@ -14,7 +14,7 @@ class Magnum < Formula
def install
system "mkdir build"
cd "build" do
system "cmake", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=#{prefix}", "-DMAGNUM_PLUGINS_DIR=#{HOMEBREW_PREFIX}/lib/magnum", "-DWITH_AUDIO=ON", "-DWITH_GLFWAPPLICATION=ON", "-DWITH_SDL2APPLICATION=ON", "-DWITH_WINDOWLESSCGLAPPLICATION=ON", "-DWITH_CGLCONTEXT=ON", "-DWITH_OPENGLTESTER=ON", "-DWITH_ANYAUDIOIMPORTER=ON", "-DWITH_ANYIMAGECONVERTER=ON", "-DWITH_ANYIMAGEIMPORTER=ON", "-DWITH_ANYSCENEIMPORTER=ON", "-DWITH_MAGNUMFONT=ON", "-DWITH_MAGNUMFONTCONVERTER=ON", "-DWITH_OBJIMPORTER=ON", "-DWITH_TGAIMAGECONVERTER=ON", "-DWITH_TGAIMPORTER=ON", "-DWITH_WAVAUDIOIMPORTER=ON", "-DWITH_DISTANCEFIELDCONVERTER=ON", "-DWITH_FONTCONVERTER=ON", "-DWITH_IMAGECONVERTER=ON", "-DWITH_SCENECONVERTER=ON", "-DWITH_GL_INFO=ON", "-DWITH_AL_INFO=ON", ".."
system "cmake", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=#{prefix}", "-DMAGNUM_PLUGINS_DIR=#{HOMEBREW_PREFIX}/lib/magnum", "-DWITH_AUDIO=ON", "-DWITH_GLFWAPPLICATION=ON", "-DWITH_SDL2APPLICATION=ON", "-DWITH_WINDOWLESSCGLAPPLICATION=ON", "-DWITH_CGLCONTEXT=ON", "-DWITH_OPENGLTESTER=ON", "-DWITH_ANYAUDIOIMPORTER=ON", "-DWITH_ANYIMAGECONVERTER=ON", "-DWITH_ANYIMAGEIMPORTER=ON", "-DWITH_ANYSCENECONVERTER=ON", "-DWITH_ANYSCENEIMPORTER=ON", "-DWITH_MAGNUMFONT=ON", "-DWITH_MAGNUMFONTCONVERTER=ON", "-DWITH_OBJIMPORTER=ON", "-DWITH_TGAIMAGECONVERTER=ON", "-DWITH_TGAIMPORTER=ON", "-DWITH_WAVAUDIOIMPORTER=ON", "-DWITH_DISTANCEFIELDCONVERTER=ON", "-DWITH_FONTCONVERTER=ON", "-DWITH_IMAGECONVERTER=ON", "-DWITH_SCENECONVERTER=ON", "-DWITH_GL_INFO=ON", "-DWITH_AL_INFO=ON", ".."
system "cmake", "--build", "."
system "cmake", "--build", ".", "--target", "install"
end

1
package/msys/PKGBUILD

@ -40,6 +40,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_MAGNUMFONT=ON \
-DWITH_MAGNUMFONTCONVERTER=ON \

1
package/msys/magnum/PKGBUILD

@ -44,6 +44,7 @@ build() {
-DWITH_ANYAUDIOIMPORTER=ON \
-DWITH_ANYIMAGECONVERTER=ON \
-DWITH_ANYIMAGEIMPORTER=ON \
-DWITH_ANYSCENECONVERTER=ON \
-DWITH_ANYSCENEIMPORTER=ON \
-DWITH_AUDIO=ON \
-DWITH_DISTANCEFIELDCONVERTER=ON \

13
src/Magnum/DebugTools/CMakeLists.txt

@ -24,21 +24,26 @@
#
set(MagnumDebugTools_SRCS
ColorMap.cpp
Profiler.cpp)
ColorMap.cpp)
set(MagnumDebugTools_GracefulAssert_SRCS )
set(MagnumDebugTools_GracefulAssert_SRCS
FrameProfiler.cpp)
set(MagnumDebugTools_HEADERS
ColorMap.h
DebugTools.h
Profiler.h
FrameProfiler.h
visibility.h)
# Header files to display in project view of IDEs only
set(MagnumDebugTools_PRIVATE_HEADERS )
if(MAGNUM_BUILD_DEPRECATED)
list(APPEND MagnumDebugTools_SRCS Profiler.cpp)
list(APPEND MagnumDebugTools_HEADERS Profiler.h)
endif()
if(TARGET_GL)
list(APPEND MagnumDebugTools_SRCS
ResourceManager.cpp

715
src/Magnum/DebugTools/FrameProfiler.cpp

@ -0,0 +1,715 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 "FrameProfiler.h"
#include <chrono>
#include <sstream>
#include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/String.h>
#include "Magnum/Math/Functions.h"
#ifdef MAGNUM_TARGET_GL
#include "Magnum/GL/TimeQuery.h"
#ifndef MAGNUM_TARGET_GLES
#include "Magnum/GL/PipelineStatisticsQuery.h"
#endif
#endif
namespace Magnum { namespace DebugTools {
FrameProfiler::Measurement::Measurement(const std::string& name, const Units units, void(*const begin)(void*), UnsignedLong(*const end)(void*), void* const state): _name{name}, _end{nullptr}, _state{state}, _units{units}, _delay{0} {
_begin.immediate = begin;
_query.immediate = end;
}
FrameProfiler::Measurement::Measurement(const std::string& name, const Units units, const UnsignedInt delay, void(*const begin)(void*, UnsignedInt), void(*const end)(void*, UnsignedInt), UnsignedLong(*const query)(void*, UnsignedInt, UnsignedInt), void* const state): _name{name}, _state{state}, _units{units}, _delay{delay} {
CORRADE_ASSERT(delay >= 1, "DebugTools::FrameProfiler::Measurement: delay can't be zero", );
_begin.delayed = begin;
_end = end;
_query.delayed = query;
}
FrameProfiler::FrameProfiler() noexcept = default;
FrameProfiler::FrameProfiler(Containers::Array<Measurement>&& measurements, std::size_t maxFrameCount) noexcept {
setup(std::move(measurements), maxFrameCount);
}
FrameProfiler::FrameProfiler(const std::initializer_list<Measurement> measurements, const std::size_t maxFrameCount): FrameProfiler{Containers::array(measurements), maxFrameCount} {}
FrameProfiler::FrameProfiler(FrameProfiler&& other) noexcept:
_enabled{other._enabled},
#ifndef CORRADE_NO_ASSERT
_beginFrameCalled{other._beginFrameCalled},
#endif
_currentData{other._currentData},
_maxFrameCount{other._maxFrameCount},
_measuredFrameCount{other._measuredFrameCount},
_measurements{std::move(other._measurements)},
_data{std::move(other._data)}
{
/* For all state pointers that point to &other patch them to point to this
instead, to account for 90% of use cases of derived classes */
for(Measurement& measurement: _measurements)
if(measurement._state == &other) measurement._state = this;
}
FrameProfiler& FrameProfiler::operator=(FrameProfiler&& other) noexcept {
using std::swap;
swap(_enabled, other._enabled);
#ifndef CORRADE_NO_ASSERT
swap(_beginFrameCalled, other._beginFrameCalled);
#endif
swap(_currentData, other._currentData);
swap(_maxFrameCount, other._maxFrameCount);
swap(_measuredFrameCount, other._measuredFrameCount);
swap(_measurements, other._measurements);
swap(_data, other._data);
/* For all state pointers that point to &other patch them to point to this
instead, to account for 90% of use cases of derived classes */
for(Measurement& measurement: _measurements)
if(measurement._state == &other) measurement._state = this;
/* And the same the other way to avoid the other instance accidentally
affecting out measurements */
for(Measurement& measurement: other._measurements)
if(measurement._state == this) measurement._state = &other;
return *this;
}
void FrameProfiler::setup(Containers::Array<Measurement>&& measurements, const std::size_t maxFrameCount) {
CORRADE_ASSERT(maxFrameCount >= 1, "DebugTools::FrameProfiler::setup(): max frame count can't be zero", );
_maxFrameCount = maxFrameCount;
_measurements = std::move(measurements);
arrayReserve(_data, maxFrameCount*_measurements.size());
/* Calculate the max delay, which signalizes when data will be available.
Non-delayed measurements are distinguished by _delay set to 0, so start
with 1 to exclude these. */
for(const Measurement& measurement: _measurements) {
/* Max frame count is always >= 1, so even if _delay is 0 the condition
makes sense and we don't need to do a max() */
CORRADE_ASSERT(maxFrameCount >= measurement._delay,
"DebugTools::FrameProfiler::setup(): max delay" << measurement._delay << "is larger than max frame count" << maxFrameCount, );
}
/* Reset to have a clean slate in case we did some other measurements
before */
enable();
}
void FrameProfiler::setup(const std::initializer_list<Measurement> measurements, const std::size_t maxFrameCount) {
setup(Containers::array(measurements), maxFrameCount);
}
void FrameProfiler::enable() {
_enabled = true;
#ifndef CORRADE_NO_ASSERT
_beginFrameCalled = false;
#endif
_currentData = 0;
_measuredFrameCount = 0;
arrayResize(_data, 0);
/* Wipe out no longer relevant moving sums from all measurements, and
delayed measurement indices as well (tho for these it's not so
important) */
for(Measurement& measurement: _measurements) {
measurement._movingSum = 0;
measurement._current = 0;
}
}
void FrameProfiler::disable() {
_enabled = false;
}
void FrameProfiler::beginFrame() {
if(!_enabled) return;
CORRADE_ASSERT(!_beginFrameCalled, "DebugTools::FrameProfiler::beginFrame(): expected end of frame", );
#ifndef CORRADE_NO_ASSERT
_beginFrameCalled = true;
#endif
/* For all measurements call the begin function */
for(const Measurement& measurement: _measurements) {
if(!measurement._delay)
measurement._begin.immediate(measurement._state);
else
measurement._begin.delayed(measurement._state, measurement._current);
}
}
/* For delay = 1 returns _currentData */
std::size_t FrameProfiler::delayedCurrentData(UnsignedInt delay) const {
CORRADE_INTERNAL_ASSERT(delay >= 1);
/* The delayed frame is current or before current */
if(_currentData >= delay - 1)
return _currentData - delay + 1;
/* If we have all data, wrap around. If we don't have all data yet, such
value doesn't exist and thus this will return an OOB index. If
everything is implemented correctly, it won't be accessed in any way. */
return _maxFrameCount + _currentData - delay + 1;
}
void FrameProfiler::endFrame() {
if(!_enabled) return;
CORRADE_ASSERT(_beginFrameCalled, "DebugTools::FrameProfiler::endFrame(): expected begin of frame", );
#ifndef CORRADE_NO_ASSERT
_beginFrameCalled = false;
#endif
/* If we don't have all frames yet, enlarge the array */
if(++_measuredFrameCount <= _maxFrameCount) {
CORRADE_INTERNAL_ASSERT(_measurements.empty() || _currentData == _data.size()/_measurements.size());
arrayAppend(_data, Containers::NoInit, _measurements.size());
}
/* Wrap up measurements for this frame */
for(std::size_t i = 0; i != _measurements.size(); ++i) {
Measurement& measurement = _measurements[i];
const UnsignedInt measurementDelay = Math::max(1u, measurement._delay);
/* Where to save currently queried data. For _delay of 0 or 1,
delayedCurrentData(Math::max(1u, measurement._delay)) is equal to
_currentData. */
UnsignedLong& currentMeasurementData = _data[delayedCurrentData(measurementDelay)*_measurements.size() + i];
/* If we're wrapping around, subtract the oldest data from the moving
average so we can reuse the memory for currently queried data */
if(_measuredFrameCount > _maxFrameCount + measurementDelay - 1) {
CORRADE_INTERNAL_ASSERT(measurement._movingSum >= currentMeasurementData);
measurement._movingSum -= currentMeasurementData;
}
/* Simply save the data if not delayed */
if(!measurement._delay)
currentMeasurementData = measurement._query.immediate(measurement._state);
/* For delayed measurements call the end function for current frame and
then save the data for the delayed frame */
else {
measurement._end(measurement._state, measurement._current);
/* The slot from which we just retrieved a delayed value will be
reused for a a new value next frame */
const UnsignedInt previous = (measurement._current + 1) % measurement._delay;
if(_measuredFrameCount >= measurement._delay) {
currentMeasurementData =
measurement._query.delayed(measurement._state, previous, measurement._current);
}
measurement._current = previous;
}
}
/* Process the new data if we have enough frames even for the largest
delay */
for(std::size_t i = 0; i != _measurements.size(); ++i) {
Measurement& measurement = _measurements[i];
const UnsignedInt measurementDelay = Math::max(1u, measurement._delay);
/* If we have enough frames, add the new measurement to the moving sum.
For _delay of 0 or 1, delayedCurrentData(Math::max(1u, measurement._delay))
is equal to _currentData. */
if(_measuredFrameCount >= measurementDelay)
_measurements[i]._movingSum += _data[delayedCurrentData(measurementDelay)*_measurements.size() + i];
}
/* Advance & wraparound the index where data will be saved for the next
frame */
_currentData = (_currentData + 1) % _maxFrameCount;
}
std::string FrameProfiler::measurementName(const std::size_t id) const {
CORRADE_ASSERT(id < _measurements.size(),
"DebugTools::FrameProfiler::measurementName(): index" << id << "out of range for" << _measurements.size() << "measurements", {});
return _measurements[id]._name;
}
FrameProfiler::Units FrameProfiler::measurementUnits(const std::size_t id) const {
CORRADE_ASSERT(id < _measurements.size(),
"DebugTools::FrameProfiler::measurementUnits(): index" << id << "out of range for" << _measurements.size() << "measurements", {});
return _measurements[id]._units;
}
UnsignedInt FrameProfiler::measurementDelay(const std::size_t id) const {
CORRADE_ASSERT(id < _measurements.size(),
"DebugTools::FrameProfiler::measurementDelay(): index" << id << "out of range for" << _measurements.size() << "measurements", {});
return Math::max(_measurements[id]._delay, 1u);
}
bool FrameProfiler::isMeasurementAvailable(const std::size_t id) const {
CORRADE_ASSERT(id < _measurements.size(),
"DebugTools::FrameProfiler::measurementDelay(): index" << id << "out of range for" << _measurements.size() << "measurements", {});
return _measuredFrameCount >= Math::max(_measurements[id]._delay, 1u);
}
Double FrameProfiler::measurementDataInternal(const Measurement& measurement) const {
return Double(measurement._movingSum)/
Math::min(_measuredFrameCount - Math::max(measurement._delay, 1u) + 1, _maxFrameCount);
}
Double FrameProfiler::measurementMean(const std::size_t id) const {
CORRADE_ASSERT(id < _measurements.size(),
"DebugTools::FrameProfiler::measurementMean(): index" << id << "out of range for" << _measurements.size() << "measurements", {});
CORRADE_ASSERT(_measuredFrameCount >= Math::max(_measurements[id]._delay, 1u), "DebugTools::FrameProfiler::measurementMean(): measurement data available after" << Math::max(_measurements[id]._delay, 1u) - _measuredFrameCount << "more frames", {});
return measurementDataInternal(_measurements[id]);
}
namespace {
/* Based on Corrade/TestSuite/Implementation/BenchmarkStats.h */
void printValue(Utility::Debug& out, const Double mean, const Double divisor, const char* const unitPrefix, const char* const units) {
out << Debug::boldColor(Debug::Color::Green)
<< Utility::formatString("{:.2f}", mean/divisor) << Debug::resetColor
<< Debug::nospace << unitPrefix << Debug::nospace << units;
}
void printTime(Utility::Debug& out, const Double mean) {
if(mean >= 1000000000.0)
printValue(out, mean, 1000000000.0, " ", "s");
else if(mean >= 1000000.0)
printValue(out, mean, 1000000.0, " m", "s");
else if(mean >= 1000.0)
printValue(out, mean, 1000.0, " µ", "s");
else
printValue(out, mean, 1.0, " n", "s");
}
void printCount(Utility::Debug& out, const Double mean, Double multiplier, const char* const units) {
if(mean >= multiplier*multiplier*multiplier)
printValue(out, mean, multiplier*multiplier*multiplier, " G", units);
else if(mean >= multiplier*multiplier)
printValue(out, mean, multiplier*multiplier, " M", units);
else if(mean >= multiplier)
printValue(out, mean, multiplier, " k", units);
else
printValue(out, mean, 1.0, std::strlen(units) ? " " : "", units);
}
}
void FrameProfiler::printStatisticsInternal(Debug& out) const {
out << Debug::boldColor(Debug::Color::Default) << "Last"
<< Debug::boldColor(Debug::Color::Cyan)
<< Math::min(_measuredFrameCount, _maxFrameCount)
<< Debug::boldColor(Debug::Color::Default) << "frames:";
for(const Measurement& measurement: _measurements) {
out << Debug::newline << " " << Debug::boldColor(Debug::Color::Default)
<< measurement._name << Debug::nospace << ":" << Debug::resetColor;
/* If this measurement is not available yet, print a placeholder */
if(_measuredFrameCount < Math::max(measurement._delay, 1u)) {
const char* units = nullptr;
switch(measurement._units) {
case Units::Count:
case Units::RatioThousandths:
units = "";
break;
case Units::Nanoseconds:
units = "s";
break;
case Units::Bytes:
units = "B";
break;
case Units::PercentageThousandths:
units = "%";
break;
}
CORRADE_INTERNAL_ASSERT(units);
out << Debug::color(Debug::Color::Blue) << "-.--"
<< Debug::resetColor;
if(units[0] != '\0') out << units;
/* Otherwise format the value */
} else {
const Double mean = measurementDataInternal(measurement);
switch(measurement._units) {
case Units::Nanoseconds:
printTime(out, mean);
continue;
case Units::Bytes:
printCount(out, mean, 1024.0, "B");
continue;
case Units::Count:
printCount(out, mean, 1000.0, "");
continue;
case Units::RatioThousandths:
printCount(out, mean/1000.0, 1000.0, "");
continue;
case Units::PercentageThousandths:
printValue(out, mean, 1000.0, " ", "%");
continue;
}
CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
}
}
std::string FrameProfiler::statistics() const {
std::ostringstream out;
Debug d{&out, Debug::Flag::NoNewlineAtTheEnd|Debug::Flag::DisableColors};
printStatisticsInternal(d);
return out.str();
}
void FrameProfiler::printStatistics(const std::size_t frequency) const {
Debug::Flags flags;
if(!Debug::isTty()) flags |= Debug::Flag::DisableColors;
printStatistics(Debug{flags}, frequency);
}
void FrameProfiler::printStatistics(Debug& out, const std::size_t frequency) const {
if(!isEnabled() || _measuredFrameCount % frequency != 0) return;
/* If on a TTY and we printed at least something already, scroll back up to
overwrite previous output */
if(out.isTty() && _measuredFrameCount > frequency)
out << Debug::nospace << "\033[" << Debug::nospace
<< _measurements.size() + 1 << Debug::nospace << "A\033[J"
<< Debug::nospace;
printStatisticsInternal(out);
/* Unconditionally finish with a newline so the TTY scrollback works
correctly */
if(out.flags() & Debug::Flag::NoNewlineAtTheEnd)
out << Debug::newline;
}
Debug& operator<<(Debug& debug, const FrameProfiler::Units value) {
debug << "DebugTools::FrameProfiler::Units" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(v) case FrameProfiler::Units::v: return debug << "::" #v;
_c(Nanoseconds)
_c(Bytes)
_c(Count)
_c(RatioThousandths)
_c(PercentageThousandths)
#undef _c
/* LCOV_EXCL_STOP */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
#ifdef MAGNUM_TARGET_GL
struct GLFrameProfiler::State {
UnsignedShort cpuDurationIndex = 0xffff,
gpuDurationIndex = 0xffff,
frameTimeIndex = 0xffff;
#ifndef MAGNUM_TARGET_GLES
UnsignedShort vertexFetchRatioIndex = 0xffff,
primitiveClipRatioIndex = 0xffff;
#endif
UnsignedLong frameTimeStartFrame[2];
UnsignedLong cpuDurationStartFrame;
GL::TimeQuery timeQueries[3]{GL::TimeQuery{NoCreate}, GL::TimeQuery{NoCreate}, GL::TimeQuery{NoCreate}};
#ifndef MAGNUM_TARGET_GLES
GL::PipelineStatisticsQuery verticesSubmittedQueries[3]{GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}};
GL::PipelineStatisticsQuery vertexShaderInvocationsQueries[3]{GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}};
GL::PipelineStatisticsQuery clippingInputPrimitivesQueries[3]{GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}};
GL::PipelineStatisticsQuery clippingOutputPrimitivesQueries[3]{GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}, GL::PipelineStatisticsQuery{NoCreate}};
#endif
};
GLFrameProfiler::GLFrameProfiler(): _state{Containers::InPlaceInit} {}
GLFrameProfiler::GLFrameProfiler(const Values values, const std::size_t maxFrameCount): GLFrameProfiler{}
{
setup(values, maxFrameCount);
}
GLFrameProfiler::GLFrameProfiler(GLFrameProfiler&&) noexcept = default;
GLFrameProfiler& GLFrameProfiler::operator=(GLFrameProfiler&&) noexcept = default;
GLFrameProfiler::~GLFrameProfiler() = default;
void GLFrameProfiler::setup(const Values values, const std::size_t maxFrameCount) {
UnsignedShort index = 0;
Containers::Array<Measurement> measurements;
if(values & Value::FrameTime) {
/* Fucking hell, STL. When I first saw std::chrono back in 2010 I
should have flipped the table and learn carpentry instead. BUT NO,
I'm still suffering this abomination a decade later! */
arrayAppend(measurements, Containers::InPlaceInit,
"Frame time", Units::Nanoseconds, UnsignedInt(Containers::arraySize(_state->frameTimeStartFrame)),
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->frameTimeStartFrame[current] = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
},
[](void*, UnsignedInt) {},
[](void* state, UnsignedInt previous, UnsignedInt current) {
auto& self = *static_cast<State*>(state);
return self.frameTimeStartFrame[current] -
self.frameTimeStartFrame[previous];
}, _state.get());
_state->frameTimeIndex = index++;
}
if(values & Value::CpuDuration) {
arrayAppend(measurements, Containers::InPlaceInit,
"CPU duration", Units::Nanoseconds,
[](void* state) {
static_cast<State*>(state)->cpuDurationStartFrame = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
},
[](void* state) {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() - static_cast<State*>(state)->cpuDurationStartFrame;
}, _state.get());
_state->cpuDurationIndex = index++;
}
if(values & Value::GpuDuration) {
for(GL::TimeQuery& q: _state->timeQueries)
q = GL::TimeQuery{GL::TimeQuery::Target::TimeElapsed};
arrayAppend(measurements, Containers::InPlaceInit,
"GPU duration", Units::Nanoseconds,
UnsignedInt(Containers::arraySize(_state->timeQueries)),
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->timeQueries[current].begin();
},
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->timeQueries[current].end();
},
[](void* state, UnsignedInt previous, UnsignedInt) {
return static_cast<State*>(state)->timeQueries[previous].result<UnsignedLong>();
}, _state.get());
_state->gpuDurationIndex = index++;
}
#ifndef MAGNUM_TARGET_GLES
if(values & Value::VertexFetchRatio) {
for(GL::PipelineStatisticsQuery& q: _state->verticesSubmittedQueries)
q = GL::PipelineStatisticsQuery{GL::PipelineStatisticsQuery::Target::VerticesSubmitted};
for(GL::PipelineStatisticsQuery& q: _state->vertexShaderInvocationsQueries)
q = GL::PipelineStatisticsQuery{GL::PipelineStatisticsQuery::Target::VertexShaderInvocations};
arrayAppend(measurements, Containers::InPlaceInit,
"Vertex fetch ratio", Units::RatioThousandths,
UnsignedInt(Containers::arraySize(_state->verticesSubmittedQueries)),
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->verticesSubmittedQueries[current].begin();
static_cast<State*>(state)->vertexShaderInvocationsQueries[current].begin();
},
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->verticesSubmittedQueries[current].end();
static_cast<State*>(state)->vertexShaderInvocationsQueries[current].end();
},
[](void* state, UnsignedInt previous, UnsignedInt) {
/* Avoid division by zero if a frame doesn't have any draws */
const auto submitted = static_cast<State*>(state)->verticesSubmittedQueries[previous].result<UnsignedLong>();
if(!submitted) return UnsignedLong{};
return static_cast<State*>(state)->vertexShaderInvocationsQueries[previous].result<UnsignedLong>()*1000/submitted;
}, _state.get());
_state->vertexFetchRatioIndex = index++;
}
if(values & Value::PrimitiveClipRatio) {
for(GL::PipelineStatisticsQuery& q: _state->clippingInputPrimitivesQueries)
q = GL::PipelineStatisticsQuery{GL::PipelineStatisticsQuery::Target::ClippingInputPrimitives};
for(GL::PipelineStatisticsQuery& q: _state->clippingOutputPrimitivesQueries)
q = GL::PipelineStatisticsQuery{GL::PipelineStatisticsQuery::Target::ClippingOutputPrimitives};
arrayAppend(measurements, Containers::InPlaceInit,
"Primitives clipped", Units::PercentageThousandths,
UnsignedInt(Containers::arraySize(_state->clippingInputPrimitivesQueries)),
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->clippingInputPrimitivesQueries[current].begin();
static_cast<State*>(state)->clippingOutputPrimitivesQueries[current].begin();
},
[](void* state, UnsignedInt current) {
static_cast<State*>(state)->clippingInputPrimitivesQueries[current].end();
static_cast<State*>(state)->clippingOutputPrimitivesQueries[current].end();
},
[](void* state, UnsignedInt previous, UnsignedInt) {
/* Avoid division by zero if a frame doesn't have any draws */
const auto input = static_cast<State*>(state)->clippingInputPrimitivesQueries[previous].result<UnsignedLong>();
if(!input) return UnsignedLong{};
return 100000 - static_cast<State*>(state)->clippingOutputPrimitivesQueries[previous].result<UnsignedLong>()*100000/input;
}, _state.get());
_state->primitiveClipRatioIndex = index++;
}
#endif
setup(std::move(measurements), maxFrameCount);
}
auto GLFrameProfiler::values() const -> Values {
Values values;
if(_state->frameTimeIndex != 0xffff) values |= Value::FrameTime;
if(_state->cpuDurationIndex != 0xffff) values |= Value::CpuDuration;
if(_state->gpuDurationIndex != 0xffff) values |= Value::GpuDuration;
#ifndef MAGNUM_TARGET_GLES
if(_state->vertexFetchRatioIndex != 0xffff) values |= Value::VertexFetchRatio;
if(_state->primitiveClipRatioIndex != 0xffff) values |= Value::PrimitiveClipRatio;
#endif
return values;
}
bool GLFrameProfiler::isMeasurementAvailable(const Value value) const {
const UnsignedShort* index = nullptr;
switch(value) {
case Value::FrameTime: index = &_state->frameTimeIndex; break;
case Value::CpuDuration: index = &_state->cpuDurationIndex; break;
case Value::GpuDuration: index = &_state->gpuDurationIndex; break;
#ifndef MAGNUM_TARGET_GLES
case Value::VertexFetchRatio: index = &_state->vertexFetchRatioIndex; break;
case Value::PrimitiveClipRatio: index = &_state->primitiveClipRatioIndex; break;
#endif
}
CORRADE_INTERNAL_ASSERT(index);
CORRADE_ASSERT(*index < measurementCount(),
"DebugTools::GLFrameProfiler::isMeasurementAvailable():" << value << "not enabled", {});
return isMeasurementAvailable(*index);
}
Double GLFrameProfiler::frameTimeMean() const {
CORRADE_ASSERT(_state->frameTimeIndex < measurementCount(),
"DebugTools::GLFrameProfiler::frameTimeMean(): not enabled", {});
return measurementMean(_state->frameTimeIndex);
}
Double GLFrameProfiler::cpuDurationMean() const {
CORRADE_ASSERT(_state->cpuDurationIndex < measurementCount(),
"DebugTools::GLFrameProfiler::cpuDurationMean(): not enabled", {});
return measurementMean(_state->cpuDurationIndex);
}
Double GLFrameProfiler::gpuDurationMean() const {
CORRADE_ASSERT(_state->gpuDurationIndex < measurementCount(),
"DebugTools::GLFrameProfiler::gpuDurationMean(): not enabled", {});
return measurementMean(_state->gpuDurationIndex);
}
#ifndef MAGNUM_TARGET_GLES
Double GLFrameProfiler::vertexFetchRatioMean() const {
CORRADE_ASSERT(_state->vertexFetchRatioIndex < measurementCount(),
"DebugTools::GLFrameProfiler::vertexFetchRatioMean(): not enabled", {});
return measurementMean(_state->vertexFetchRatioIndex);
}
Double GLFrameProfiler::primitiveClipRatioMean() const {
CORRADE_ASSERT(_state->primitiveClipRatioIndex < measurementCount(),
"DebugTools::GLFrameProfiler::primitiveClipRatioMean(): not enabled", {});
return measurementMean(_state->primitiveClipRatioIndex);
}
#endif
namespace {
constexpr const char* GLFrameProfilerValueNames[] {
"FrameTime",
"CpuDuration",
"GpuDuration",
"VertexFetchRatio",
"PrimitiveClipRatio"
};
}
Debug& operator<<(Debug& debug, const GLFrameProfiler::Value value) {
debug << "DebugTools::GLFrameProfiler::Value" << Debug::nospace;
const UnsignedInt bit = Math::log2(UnsignedShort(value));
if(1 << bit == UnsignedShort(value))
return debug << "::" << Debug::nospace << GLFrameProfilerValueNames[bit];
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedShort(value)) << Debug::nospace << ")";
}
Debug& operator<<(Debug& debug, const GLFrameProfiler::Values value) {
return Containers::enumSetDebugOutput(debug, value, "DebugTools::GLFrameProfiler::Values{}", {
GLFrameProfiler::Value::FrameTime,
GLFrameProfiler::Value::CpuDuration,
GLFrameProfiler::Value::GpuDuration,
#ifndef MAGNUM_TARGET_GLES
GLFrameProfiler::Value::VertexFetchRatio,
GLFrameProfiler::Value::PrimitiveClipRatio
#endif
});
}
#endif
}}
namespace Corrade { namespace Utility {
using namespace Magnum;
std::string ConfigurationValue<DebugTools::GLFrameProfiler::Value>::toString(const DebugTools::GLFrameProfiler::Value value, ConfigurationValueFlags) {
const UnsignedInt bit = Math::log2(UnsignedShort(value));
if(1 << bit == UnsignedShort(value))
return DebugTools::GLFrameProfilerValueNames[bit];
return "";
}
DebugTools::GLFrameProfiler::Value ConfigurationValue<DebugTools::GLFrameProfiler::Value>::fromString(const std::string& value, ConfigurationValueFlags) {
for(std::size_t i = 0; i != Containers::arraySize(DebugTools::GLFrameProfilerValueNames); ++i)
if(DebugTools::GLFrameProfilerValueNames[i] == value)
return DebugTools::GLFrameProfiler::Value(1 << i);
return DebugTools::GLFrameProfiler::Value{};
}
std::string ConfigurationValue<DebugTools::GLFrameProfiler::Values>::toString(const DebugTools::GLFrameProfiler::Values value, ConfigurationValueFlags) {
std::string out;
for(std::size_t i = 0; i != Containers::arraySize(DebugTools::GLFrameProfilerValueNames); ++i) {
const auto bit = DebugTools::GLFrameProfiler::Value(1 << i);
if(value & bit) {
if(!out.empty()) out += ' ';
out += DebugTools::GLFrameProfilerValueNames[i];
}
}
return out;
}
DebugTools::GLFrameProfiler::Values ConfigurationValue<DebugTools::GLFrameProfiler::Values>::fromString(const std::string& value, ConfigurationValueFlags) {
const std::vector<std::string> bits = Utility::String::splitWithoutEmptyParts(value);
DebugTools::GLFrameProfiler::Values values;
for(const std::string& bit: bits)
for(std::size_t i = 0; i != Containers::arraySize(DebugTools::GLFrameProfilerValueNames); ++i)
if(DebugTools::GLFrameProfilerValueNames[i] == bit)
values |= DebugTools::GLFrameProfiler::Value(1 << i);
return values;
}
}}

754
src/Magnum/DebugTools/FrameProfiler.h

@ -0,0 +1,754 @@
#ifndef Magnum_DebugTools_FrameProfiler_h
#define Magnum_DebugTools_FrameProfiler_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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::DebugTools::FrameProfiler, @ref Magnum::DebugTools::GLFrameProfiler
* @m_since_latest
*/
#include <string>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Pointer.h>
#include "Magnum/Magnum.h"
#include "Magnum/DebugTools/visibility.h"
namespace Magnum { namespace DebugTools {
/**
@brief Frame profiler
@m_since_latest
A generic implementation of a frame profiler supporting a moving average over
a set of frames as well as delayed measurements to avoid stalls when querying
the results. This class alone doesn't provide any pre-defined measurements, see
for example @ref GLFrameProfiler that provides common measurements like CPU and
GPU time.
@experimental
@section DebugTools-FrameProfiler-usage Basic usage
Measurements are performed by calling @ref beginFrame() and @ref endFrame() at
designated points in the frame:
@snippet MagnumDebugTools.cpp FrameProfiler-usage
In order to have stable profiling results, the application needs to redraw
constantly. However for applications that otherwise redraw only on change it
might be wasteful --- to account for this, it's possible to toggle the profiler
operation using @ref enable() / @ref disable() and then
@ref Platform::Sdl2Application::redraw() "redraw()" can be called only if
@ref isEnabled() returns @cpp true @ce.
Data for all measurements is then available through @ref measurementName(),
@ref measurementUnits() and @ref measurementMean(). For a convenient overview
of all measured values you can call @ref statistics() and feed its output to a
UI library or something that can render text. Alternatively, if you don't want
to bother with text rendering, call @ref printStatistics() to have the output
periodically printed to the console. If an interactive terminal is detected,
the output will be colored and refreshing itself in place. Together with the
on-demand profiling, it could look like this, refreshing the output every 10
frames:
@snippet MagnumDebugTools.cpp FrameProfiler-usage-console
And here's a sample output on the terminal --- using a fully configured
@link GLFrameProfiler @endlink:
@include debugtools-frameprofiler.ansi
@section DebugTools-FrameProfiler-setup Setting up measurements
Unless you're using this class through @ref GLFrameProfiler, measurements
have to be set up by passing @ref Measurement instances to the @ref setup()
function or to the constructor, together with specifying count of frames for
the moving average. A CPU duration measurements using the @ref std::chrono APIs
over last 50 frames can be done like this:
@snippet MagnumDebugTools.cpp FrameProfiler-setup-immediate
In the above case, the measurement result is available immediately on frame
end. That's not always the case, and for example GPU queries need a few frames
delay to avoid stalls from CPU/GPU synchronization. The following snippet sets
up sample count measurement with a delay, using three separate
@ref GL::SampleQuery instances that are cycled through each frame and retrieved
two frames later. The profiler automatically takes care of choosing one of the
three instances for each measurement via additional `current` / `previous`
parameters passed to each callback:
@snippet MagnumDebugTools-gl.cpp FrameProfiler-setup-delayed
<b></b>
@m_class{m-block m-warning}
@par Move construction and state pointers in callbacks
The @ref FrameProfiler class is movable, which could potentially mean that
state pointers passed to callback functions become dangling. It's not a
problem in the above snippets because the state is always external to the
instance, but care has to be taken when passing pointers to subclasses.
@par
When setting up measurements from a subclass, it's recommended to always
pass @cpp this @ce as the state pointer. The base class recognizes it
during a move and patches the state to point to the new instance instead.
If you don't or can't use @cpp this @ce as a state pointer, you need to
either provide a dedicated move constructor and assignment to do the
required patching or disable moves altogether to avoid accidents.
*/
class MAGNUM_DEBUGTOOLS_EXPORT FrameProfiler {
public:
/**
* @brief Measurement units
*
* @see @ref Measurement
*/
enum class Units: UnsignedByte {
/**
* Nanoseconds, measuring for example elapsed time. Depending on
* the magnitude, @ref statistics() can show them as microseconds,
* milliseconds or seconds.
*/
Nanoseconds,
/**
* Bytes, measuring for example memory usage, bandwidth. Depending
* on the magnitude, @ref statistics() can show them as kB, MB, GB
* (with a multiplier of 1024).
*/
Bytes,
/**
* Generic count. For discrete values that don't fit any of the
* above. Depending on the magnitude, @ref statistics() can show
* the value as k, M or G (with a multiplier of 1000).
*/
Count,
/**
* Ratio expressed in 1/1000s. @ref statistics() divides the value
* by 1000 and depending on the magnitude it can show it also as k,
* M or G (with a multiplier of 1000).
*/
RatioThousandths,
/**
* Percentage expressed in 1/1000s. @ref statistics() divides the
* value by 1000 and appends a % sign.
*/
PercentageThousandths
};
class Measurement;
/**
* @brief Default constructor
*
* Call @ref setup() to populate the profiler with measurements.
*/
explicit FrameProfiler() noexcept;
/**
* @brief Constructor
*
* Equivalent to default-constructing an instance and calling
* @ref setup() afterwards.
*/
explicit FrameProfiler(Containers::Array<Measurement>&& measurements, std::size_t maxFrameCount) noexcept;
/** @overload */
explicit FrameProfiler(std::initializer_list<Measurement> measurements, std::size_t maxFrameCount);
/** @brief Copying is not allowed */
FrameProfiler(const FrameProfiler&) = delete;
/** @brief Move constructor */
FrameProfiler(FrameProfiler&&) noexcept;
/** @brief Copying is not allowed */
FrameProfiler& operator=(const FrameProfiler&) = delete;
/** @brief Move assignment */
FrameProfiler& operator=(FrameProfiler&&) noexcept;
/**
* @brief Setup measurements
* @param measurements List of measurements
* @param maxFrameCount Max frame count over which to calculate a
* moving average. Expected to be at least @cpp 1 @ce.
*
* Calling @ref setup() on an already set up profiler will replace
* existing measurements with @p measurements and reset
* @ref measuredFrameCount() back to @cpp 0 @ce.
*/
void setup(Containers::Array<Measurement>&& measurements, std::size_t maxFrameCount);
/** @overload */
void setup(std::initializer_list<Measurement> measurements, std::size_t maxFrameCount);
/** @brief Whether the profiling is enabled */
bool isEnabled() const { return _enabled; }
/**
* @brief Enable the profiler
*
* The profiler is enabled implicitly after construction. When this
* function is called, it discards all measured data, effectively
* making @ref measuredFrameCount() zero. If you want to reset the
* profiler to measure different values as well, call @ref setup().
*/
void enable();
/**
* @brief Disable the profiler
*
* Disabling the profiler will make @ref beginFrame() and
* @ref endFrame() a no-op, effectively freezing all reported
* measurements until the profiler is enabled again.
*/
void disable();
/**
* @brief Begin a frame
*
* Has to be called at the beginning of a frame and be paired with a
* corresponding @ref endFrame(). Calls @p begin functions in all
* @ref Measurement instances passed to @ref setup(). If the profiler
* is disabled, the function is a no-op.
* @see @ref isEnabled()
*/
void beginFrame();
/**
* @brief End a frame
*
* Has to be called at the end of frame, before buffer swap, and be
* paired with a corresponding @ref beginFrame(). Calls @p end
* functions in all @ref Measurement instances passed to @ref setup()
* and @p query functions on all measurements that are sufficiently
* delayed, saving their output. If the profiler is disabled, the
* function is a no-op.
* @see @ref isEnabled()
*/
void endFrame();
/**
* @brief Max count of measured frames
*
* How many frames to calculate a moving average from. At the beginning
* of a measurement when there's not enough frames, the average is
* calculated only from @ref measuredFrameCount(). Always at least
* @cpp 1 @ce.
*/
std::size_t maxFrameCount() const { return _maxFrameCount; }
/**
* @brief Count of measured frames
*
* Count of times @ref endFrame() was called, but at most
* @ref maxFrameCount(), after which the profiler calculates a moving
* average over last @ref maxFrameCount() frames only. Actual data
* availability depends on @ref measurementDelay().
*/
std::size_t measuredFrameCount() const { return _measuredFrameCount; }
/**
* @brief Measurement count
*
* Count of @ref Measurement instances passed to @ref setup(). If
* @ref setup() was not called yet, returns @cpp 0 @ce.
*/
std::size_t measurementCount() const { return _measurements.size(); }
/**
* @brief Measurement name
*
* The @p id corresponds to the index of the measurement in the list
* passed to @ref setup(). Expects that @p id is less than
* @ref measurementCount().
*/
std::string measurementName(std::size_t id) const;
/**
* @brief Measurement units
*
* The @p id corresponds to the index of the measurement in the list
* passed to @ref setup(). Expects that @p id is less than
* @ref measurementCount().
*/
Units measurementUnits(std::size_t id) const;
/**
* @brief Measurement delay
*
* How many @ref beginFrame() / @ref endFrame() call pairs needs to be
* performed before a value for given measurement is available. Always
* at least @cpp 1 @ce. The @p id corresponds to the index of the
* measurement in the list passed to @ref setup(). Expects that @p id
* is less than @ref measurementCount().
*/
UnsignedInt measurementDelay(std::size_t id) const;
/**
* @brief Whether given measurement is available
*
* Returns @cpp true @ce if @ref measuredFrameCount() is at least
* @ref measurementDelay() for given @p id, @cpp false @ce otherwise.
* The @p id corresponds to the index of the measurement in the list
* passed to @ref setup(). Expects that @p id is less than
* @ref measurementCount().
*/
bool isMeasurementAvailable(std::size_t id) const;
/**
* @brief Measurement mean
*
* Returns a moving average of @f$ n @f$ previous measurements out of
* the total @f$ M @f$ values: @f[
* \bar{x}_\text{SM} = \dfrac{1}{n} \sum\limits_{i=0}^{n-1} x_{M -i}
* @f]
*
* The @p id corresponds to the index of the measurement in the list
* passed to @ref setup(). Expects that @p id is less than
* @ref measurementCount() and that the measurement is available.
* @see @ref isMeasurementAvailable()
*/
Double measurementMean(std::size_t id) const;
/**
* @brief Overview of all measurements
*
* Returns a formatted string with names, means and units of all
* measurements in the order they were added. If some measurement data
* is available yet, prints placeholder values for these; if the
* @see @ref isMeasurementAvailable(), @ref isEnabled()
*/
std::string statistics() const;
/**
* @brief Print an overview of all measurements to a console at given rate
*
* Expected to be called every frame. On every `frequency`th frame
* prints the same information as @ref statistics(), but in addition,
* if the output is a TTY, it's colored and overwrites itself instead
* of filling up the terminal history.
* @see @ref isMeasurementAvailable(), @ref isEnabled()
* @ref Corrade::Utility::Debug::isTty()
*/
void printStatistics(std::size_t frequency) const;
/**
* @brief Print an overview of all measurements to given output at given rate
*
* Compared to @ref printStatistics(std::size_t) const prints to given
* @p out (which can be also @ref Corrade::Utility::Warning or
* @ref Corrade::Utility::Error) and uses it to decide whether the
* output is a TTY and whether to print colors.
* @see @ref Corrade::Utility::Debug::isTty(),
* @ref Corrade::Utility::Debug::Flag::DisableColors
*/
void printStatistics(Debug& out, std::size_t frequency) const;
/** @overload */
void printStatistics(Debug&& out, std::size_t frequency) const {
printStatistics(out, frequency);
}
private:
std::size_t delayedCurrentData(UnsignedInt delay) const;
Double measurementDataInternal(const Measurement& measurement) const;
void printStatisticsInternal(Debug& out) const;
bool _enabled = true;
#ifndef CORRADE_NO_ASSERT
/* Here it shouldn't cause the class to have a different size when
asserts get disabled */
bool _beginFrameCalled{};
#endif
std::size_t _currentData{}, _maxFrameCount{1}, _measuredFrameCount{};
Containers::Array<Measurement> _measurements;
Containers::Array<UnsignedLong> _data;
};
/**
@brief Measurement
Describes a single measurement passed to @ref FrameProfiler::setup(). See
@ref DebugTools-FrameProfiler-setup for introduction and examples.
*/
class MAGNUM_DEBUGTOOLS_EXPORT FrameProfiler::Measurement {
public:
/**
* @brief Construct an immediate measurement
* @param name Measurement name, used in
* @ref FrameProfiler::measurementName() and
* @ref FrameProfiler::statistics()
* @param units Measurement units, used in
* @ref FrameProfiler::measurementUnits() and
* @ref FrameProfiler::statistics()
* @param begin Function to call at the beginning of a frame
* @param end Function to call at the end of a frame, returning
* the measured value
* @param state State pointer passed to both @p begin and @p end
* as a first argument
*/
explicit Measurement(const std::string& name, Units units, void(*begin)(void*), UnsignedLong(*end)(void*), void* state);
/**
* @brief Construct a delayed measurement
* @param name Measurement name, used in
* @ref FrameProfiler::measurementName() and
* @ref FrameProfiler::statistics()
* @param units Measurement units, used in
* @ref FrameProfiler::measurementUnits() and
* @ref FrameProfiler::statistics()
* @param delay How many @ref FrameProfiler::endFrame() calls has
* to happen before a measured value can be retrieved using
* @p query. Has to be at least @cpp 1 @ce, delay of @cpp 1 @ce is
* equal in behavior to immediate measurements.
* @param begin Function to call at the beginning of a frame.
* Second argument is a `current` index that's guaranteed to be
* less than @p delay and always different in each consecutive
* call.
* @param end Function to call at the end of a frame.
* Second argument is a `current` index that's guaranteed to be
* less than @p delay and always different in each consecutive
* call.
* @param query Function to call to get the measured value. Called
* after @p delay frames. First argument is a `previous` index
* that is the same as the `current` argument passed to a
* corresponding @p begin / @p end function of the measurement to
* query the value of. Second argument is a `current` index that
* corresponds to current frame.
* @param state State pointer passed to both @p begin and @p end
* as a first argument
*/
explicit Measurement(const std::string& name, Units units, UnsignedInt delay, void(*begin)(void*, UnsignedInt), void(*end)(void*, UnsignedInt), UnsignedLong(*query)(void*, UnsignedInt, UnsignedInt), void* state);
private:
friend FrameProfiler;
std::string _name;
union {
void(*immediate)(void*);
void(*delayed)(void*, UnsignedInt);
} _begin;
void(*_end)(void*, UnsignedInt);
union {
UnsignedLong(*immediate)(void*);
UnsignedLong(*delayed)(void*, UnsignedInt, UnsignedInt);
} _query;
void* _state;
Units _units;
/* Set to 0 to distinguish immediate measurements (first
constructor), however always used as max(_delay, 1) */
UnsignedInt _delay;
UnsignedInt _current{};
UnsignedLong _movingSum{};
};
/**
@debugoperatorclassenum{FrameProfiler,FrameProfiler::Units}
@m_since_latest
*/
MAGNUM_DEBUGTOOLS_EXPORT Debug& operator<<(Debug& debug, FrameProfiler::Units value);
#ifdef MAGNUM_TARGET_GL
/**
@brief OpenGL frame profiler
@m_since_latest
A @ref FrameProfiler with OpenGL-specific measurements. Instantiate with a
desired subset of measured values and then continue the same way as described
in the @ref DebugTools-FrameProfiler-usage "FrameProfiler usage documentation":
@snippet MagnumDebugTools-gl.cpp GLFrameProfiler-usage
If none if @ref Value::GpuDuration, @ref Value::VertexFetchRatio and
@ref Value::PrimitiveClipRatio is not enabled, the class can operate without an
active OpenGL context.
@experimental
*/
class MAGNUM_DEBUGTOOLS_EXPORT GLFrameProfiler: public FrameProfiler {
public:
/**
* @brief Measured value
*
* @see @ref Values, @ref GLFrameProfiler(Values, std::size_t),
* @ref setup()
*/
enum class Value: UnsignedShort {
/**
* Measure total frame time (i.e., time between consecutive
* @ref beginFrame() calls). Reported in @ref Units::Nanoseconds
* with a delay of 2 frames. When converted to seconds, the value
* is an inverse of FPS.
*/
FrameTime = 1 << 0,
/**
* Measure CPU frame duration (i.e., CPU time spent between
* @ref beginFrame() and @ref endFrame()). Reported in
* @ref Units::Nanoseconds with a delay of 1 frame.
*/
CpuDuration = 1 << 1,
/**
* Measure GPU frame duration (i.e., time between @ref beginFrame()
* and @ref endFrame()). Reported in @ref Units::Nanoseconds
* with a delay of 3 frames. This value requires an active OpenGL
* context.
* @requires_gl33 Extension @gl_extension{ARB,timer_query}
* @requires_es_extension Extension @gl_extension{EXT,disjoint_timer_query}
* @requires_webgl_extension Extension @webgl_extension{EXT,disjoint_timer_query}
* on WebGL 1, @gl_extension{EXT,disjoint_timer_query_webgl2}
* on WebGL 2
*/
GpuDuration = 1 << 2,
#ifndef MAGNUM_TARGET_GLES
/**
* Ratio of vertex shader invocations to count of vertices
* submitted. For a non-indexed draw the ratio will be 1, for
* indexed draws ratio is less than 1. The lower the value is, the
* better a mesh is optimized for post-transform vertex cache.
* Reported in @ref Units::RatioThousandths with a delay of 3
* frames. This value requires an active OpenGL context.
* @requires_gl46 Extension @gl_extension{ARB,pipeline_statistics_query}
*/
VertexFetchRatio = 1 << 3,
/**
* Ratio of primitives discarded by the clipping stage to count of
* primitives submitted. The ratio is 0 when all primitives pass
* the clipping stage and 1 when all are discarded. Can be used to
* measure efficiency of a frustum culling algorithm. Reported in
* @ref Units::PercentageThousandths with a delay of 3 frames. This
* value requires an active OpenGL context.
* @requires_gl46 Extension @gl_extension{ARB,pipeline_statistics_query}
*/
PrimitiveClipRatio = 1 << 4
#endif
};
/**
* @brief Measured values
*
* @see @ref GLFrameProfiler(Values, std::size_t), @ref setup()
*/
typedef Containers::EnumSet<Value> Values;
/**
* @brief Default constructor
*
* Call @ref setup() to populate the profiler with measurements.
*/
explicit GLFrameProfiler();
/**
* @brief Constructor
*
* Equivalent to default-constructing an instance and calling
* @ref setup() afterwards.
*/
explicit GLFrameProfiler(Values values, std::size_t maxFrameCount);
/** @brief Copying is not allowed */
GLFrameProfiler(const GLFrameProfiler&) = delete;
/** @brief Move constructor */
GLFrameProfiler(GLFrameProfiler&&) noexcept;
/** @brief Copying is not allowed */
GLFrameProfiler& operator=(const GLFrameProfiler&) = delete;
/** @brief Move assignment */
GLFrameProfiler& operator=(GLFrameProfiler&&) noexcept;
~GLFrameProfiler();
/**
* @brief Setup measured values
* @param values List of measuremed values
* @param maxFrameCount Max frame count over which to calculate a
* moving average. Expected to be at least @cpp 1 @ce.
*
* Calling @ref setup() on an already set up profiler will replace
* existing measurements with @p measurements and reset
* @ref measuredFrameCount() back to @cpp 0 @ce.
*/
void setup(Values values, std::size_t maxFrameCount);
/**
* @brief Measured values
*
* Corresponds to the @p values parameter passed to
* @ref GLFrameProfiler(Values, std::size_t) or @ref setup().
*/
Values values() const;
/**
* @brief Whether given measurement is available
*
* Returns @cpp true @ce if enough frames was captured to calculate
* given @p value, @cpp false @ce otherwise. Expects that @p value was
* enabled.
*/
bool isMeasurementAvailable(Value value) const;
using FrameProfiler::isMeasurementAvailable;
/**
* @brief Mean frame time in nanoseconds
*
* Expects that @ref Value::FrameTime was enabled, and that measurement
* data is available. See the flag documentation for more information.
* @see @ref isMeasurementAvailable(), @ref measurementMean()
*/
Double frameTimeMean() const;
/**
* @brief Mean CPU frame duration in nanoseconds
*
* Expects that @ref Value::CpuDuration was enabled, and that
* measurement data is available. See the flag documentation for more
* information.
* @see @ref isMeasurementAvailable(), @ref measurementMean()
*/
Double cpuDurationMean() const;
/**
* @brief Mean GPU frame duration in nanoseconds
*
* Expects that @ref Value::GpuDuration was enabled, and that
* measurement data is available. See the flag documentation for more
* information.
* @see @ref isMeasurementAvailable(), @ref measurementMean()
*/
Double gpuDurationMean() const;
#ifndef MAGNUM_TARGET_GLES
/**
* @brief Mean vertex fetch ratio in thousandths
*
* Expects that @ref Value::VertexFetchRatio was enabled, and that
* measurement data is available. See the flag documentation for more
* information.
* @requires_gl46 Extension @gl_extension{ARB,pipeline_statistics_query}
* @see @ref isMeasurementAvailable(), @ref measurementMean()
*/
Double vertexFetchRatioMean() const;
/**
* @brief Mean primitive clip ratio in percentage thousandths
*
* Expects that @ref Value::PrimitiveClipRatio was enabled, and that
* measurement data is available. See the flag documentation for more
* information.
* @requires_gl46 Extension @gl_extension{ARB,pipeline_statistics_query}
* @see @ref isMeasurementAvailable(), @ref measurementMean()
*/
Double primitiveClipRatioMean() const;
#endif
private:
using FrameProfiler::setup;
struct State;
Containers::Pointer<State> _state;
};
CORRADE_ENUMSET_OPERATORS(GLFrameProfiler::Values)
/**
@debugoperatorclassenum{GLFrameProfiler,GLFrameProfiler::Value}
@m_since_latest
*/
MAGNUM_DEBUGTOOLS_EXPORT Debug& operator<<(Debug& debug, GLFrameProfiler::Value value);
/**
@debugoperatorclassenum{GLFrameProfiler,GLFrameProfiler::Values}
@m_since_latest
*/
MAGNUM_DEBUGTOOLS_EXPORT Debug& operator<<(Debug& debug, GLFrameProfiler::Values value);
#endif
}}
namespace Corrade { namespace Utility {
/**
@configurationvalue{Magnum::DebugTools::GLFrameProfiler::Value}
@m_since_latest
*/
template<> struct MAGNUM_DEBUGTOOLS_EXPORT ConfigurationValue<Magnum::DebugTools::GLFrameProfiler::Value> {
ConfigurationValue() = delete;
/**
* @brief Writes enum value as a string
*
* If the value is invalid, returns an empty string.
*/
static std::string toString(Magnum::DebugTools::GLFrameProfiler::Value value, ConfigurationValueFlags);
/**
* @brief Reads enum value as a string
*
* If the string is invalid, returns a zero (invalid) value.
*/
static Magnum::DebugTools::GLFrameProfiler::Value fromString(const std::string& stringValue, ConfigurationValueFlags);
};
/**
@configurationvalue{Magnum::DebugTools::GLFrameProfiler::Values}
@m_since_latest
*/
template<> struct MAGNUM_DEBUGTOOLS_EXPORT ConfigurationValue<Magnum::DebugTools::GLFrameProfiler::Values> {
ConfigurationValue() = delete;
/**
* @brief Writes enum set value as a string
*
* Writes the enum set as a sequence of flag names separated by spaces. If
* the value is invalid, returns an empty string.
*/
static std::string toString(Magnum::DebugTools::GLFrameProfiler::Values value, ConfigurationValueFlags);
/**
* @brief Reads enum set value as a string
*
* Assumes the string is a sequence of flag names separated by spaces. If
* the value is invalid, returns an empty set.
*/
static Magnum::DebugTools::GLFrameProfiler::Values fromString(const std::string& stringValue, ConfigurationValueFlags);
};
}}
#endif

2
src/Magnum/DebugTools/Profiler.cpp

@ -23,6 +23,8 @@
DEALINGS IN THE SOFTWARE.
*/
#define _MAGNUM_NO_DEPRECATED_PROFILER
#include "Profiler.h"
#include <algorithm>

20
src/Magnum/DebugTools/Profiler.h

@ -25,22 +25,36 @@
DEALINGS IN THE SOFTWARE.
*/
#ifdef MAGNUM_BUILD_DEPRECATED
/** @file
* @brief Class @ref Magnum::DebugTools::Profiler
* @m_deprecated_since_latest Obsolete, use
* @ref Magnum/DebugTools/FrameProfiler.h and the
* @ref Magnum::DebugTools::FrameProfiler class instead.
*/
#endif
#include "Magnum/configure.h"
#ifdef MAGNUM_BUILD_DEPRECATED
#include <chrono>
#include <initializer_list>
#include <string>
#include <vector>
#include <Corrade/Utility/Macros.h>
#include "Magnum/Types.h"
#include "Magnum/DebugTools/visibility.h"
#ifndef _MAGNUM_NO_DEPRECATED_PROFILER
CORRADE_DEPRECATED_FILE("use Magnum/DebugTools/FrameProfiler.h and the FrameProfiler class instead")
#endif
namespace Magnum { namespace DebugTools {
/**
@brief Profiler
@m_deprecated_since_latest Obsolete, use @ref FrameProfiler instead.
Measures time passed during specified sections of each frame. It's meant to be
used in rendering and event loops (e.g. @ref Platform::Sdl2Application::drawEvent()),
@ -97,11 +111,8 @@ p.printStatistics();
It's possible to start profiler only for certain parts of the code and then
stop it again using @ref stop(), if you are not interested in profiling the
rest.
@todo Some unit testing
@todo More time intervals
*/
class MAGNUM_DEBUGTOOLS_EXPORT Profiler {
class CORRADE_DEPRECATED("use FrameProfiler instead") MAGNUM_DEBUGTOOLS_EXPORT Profiler {
public:
/**
* @brief Section ID
@ -213,5 +224,6 @@ class MAGNUM_DEBUGTOOLS_EXPORT Profiler {
};
}}
#endif
#endif

8
src/Magnum/DebugTools/Test/CMakeLists.txt

@ -23,6 +23,10 @@
# DEALINGS IN THE SOFTWARE.
#
corrade_add_test(DebugToolsFrameProfilerTest FrameProfilerTest.cpp
LIBRARIES MagnumDebugToolsTestLib)
set_target_properties(DebugToolsFrameProfilerTest PROPERTIES FOLDER "Magnum/DebugTools/Test")
if(WITH_TRADE)
# Otherwise CMake complains that Corrade::PluginManager is not found, wtf
find_package(Corrade REQUIRED PluginManager)
@ -94,6 +98,10 @@ if(TARGET_GL)
endif()
if(BUILD_GL_TESTS)
corrade_add_test(DebugToolsFrameProfilerGLTest FrameProfilerGLTest.cpp
LIBRARIES MagnumDebugTools MagnumOpenGLTester)
set_target_properties(DebugToolsFrameProfilerTest PROPERTIES FOLDER "Magnum/DebugTools/Test")
corrade_add_test(DebugToolsTextureImageGLTest TextureImageGLTest.cpp LIBRARIES MagnumDebugTools MagnumOpenGLTester)
set_target_properties(DebugToolsTextureImageGLTest PROPERTIES FOLDER "Magnum/DebugTools/Test")

244
src/Magnum/DebugTools/Test/FrameProfilerGLTest.cpp

@ -0,0 +1,244 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <Corrade/TestSuite/Compare/Numeric.h>
#include <Corrade/Utility/System.h>
#include "Magnum/DebugTools/FrameProfiler.h"
#include "Magnum/GL/Context.h"
#include "Magnum/GL/Extensions.h"
#include "Magnum/GL/Framebuffer.h"
#include "Magnum/GL/Mesh.h"
#include "Magnum/GL/OpenGLTester.h"
#include "Magnum/GL/Renderbuffer.h"
#include "Magnum/GL/RenderbufferFormat.h"
#include "Magnum/MeshTools/Compile.h"
#include "Magnum/Primitives/Cube.h"
#include "Magnum/Shaders/Flat.h"
#include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace DebugTools { namespace Test { namespace {
struct FrameProfilerGLTest: GL::OpenGLTester {
explicit FrameProfilerGLTest();
void test();
#ifndef MAGNUM_TARGET_GLES
void vertexFetchRatioDivisionByZero();
void primitiveClipRatioDivisionByZero();
#endif
};
struct {
const char* name;
GLFrameProfiler::Values values;
} Data[]{
{"gpu duration", GLFrameProfiler::Value::GpuDuration},
{"cpu duration + gpu duration", GLFrameProfiler::Value::CpuDuration|GLFrameProfiler::Value::GpuDuration},
{"frame time + gpu duration", GLFrameProfiler::Value::FrameTime|GLFrameProfiler::Value::GpuDuration},
#ifndef MAGNUM_TARGET_GLES
{"gpu duration + vertex fetch ratio", GLFrameProfiler::Value::GpuDuration|GLFrameProfiler::Value::VertexFetchRatio},
{"vertex fetch ratio + primitive clip ratio", GLFrameProfiler::Value::VertexFetchRatio|GLFrameProfiler::Value::PrimitiveClipRatio}
#endif
};
FrameProfilerGLTest::FrameProfilerGLTest() {
addInstancedTests({&FrameProfilerGLTest::test},
Containers::arraySize(Data));
#ifndef MAGNUM_TARGET_GLES
addTests({&FrameProfilerGLTest::vertexFetchRatioDivisionByZero,
&FrameProfilerGLTest::primitiveClipRatioDivisionByZero});
#endif
}
void FrameProfilerGLTest::test() {
auto&& data = Data[testCaseInstanceId()];
setTestCaseDescription(data.name);
if(data.values & GLFrameProfiler::Value::GpuDuration) {
#ifndef MAGNUM_TARGET_GLES
if(!GL::Context::current().isExtensionSupported<GL::Extensions::ARB::timer_query>())
CORRADE_SKIP(GL::Extensions::ARB::timer_query::string() + std::string(" is not available"));
#elif defined(MAGNUM_TARGET_WEBGL) && !defined(MAGNUM_TARGET_GLES2)
if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::disjoint_timer_query_webgl2>())
CORRADE_SKIP(GL::Extensions::EXT::disjoint_timer_query_webgl2::string() + std::string(" is not available"));
#else
if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::disjoint_timer_query>())
CORRADE_SKIP(GL::Extensions::EXT::disjoint_timer_query::string() + std::string(" is not available"));
#endif
}
#ifndef MAGNUM_TARGET_GLES
if((data.values & GLFrameProfiler::Value::VertexFetchRatio) && !GL::Context::current().isExtensionSupported<GL::Extensions::ARB::pipeline_statistics_query>())
CORRADE_SKIP(GL::Extensions::ARB::pipeline_statistics_query::string() + std::string(" is not available"));
#endif
/* Bind some FB to avoid errors on contexts w/o default FB */
GL::Renderbuffer color;
color.setStorage(
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
GL::RenderbufferFormat::RGBA8,
#else
GL::RenderbufferFormat::RGBA4,
#endif
Vector2i{32});
GL::Framebuffer fb{{{}, Vector2i{32}}};
fb.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, color)
.bind();
Shaders::Flat3D shader;
GL::Mesh mesh = MeshTools::compile(Primitives::cubeSolid());
GLFrameProfiler profiler{data.values, 4};
CORRADE_COMPARE(profiler.maxFrameCount(), 4);
/* MSVC 2015 needs the {} */
for(auto value: {GLFrameProfiler::Value::CpuDuration,
GLFrameProfiler::Value::GpuDuration,
#ifndef MAGNUM_TARGET_GLES
GLFrameProfiler::Value::VertexFetchRatio,
GLFrameProfiler::Value::PrimitiveClipRatio
#endif
}) {
if(data.values & value)
CORRADE_VERIFY(!profiler.isMeasurementAvailable(value));
}
profiler.beginFrame();
shader.draw(mesh);
Utility::System::sleep(1);
profiler.endFrame();
profiler.beginFrame();
shader.draw(mesh);
profiler.endFrame();
Utility::System::sleep(10);
profiler.beginFrame();
shader.draw(mesh);
Utility::System::sleep(1);
profiler.endFrame();
profiler.beginFrame();
shader.draw(mesh);
Utility::System::sleep(1);
profiler.endFrame();
MAGNUM_VERIFY_NO_GL_ERROR();
/* The GPU time should not be a total zero. Can't test upper bound because
(especially on overloaded CIs) it all takes a magnitude more than
expected. */
if(data.values & GLFrameProfiler::Value::GpuDuration) {
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::GpuDuration));
CORRADE_COMPARE_AS(profiler.gpuDurationMean(), 100,
TestSuite::Compare::Greater);
}
/* 3/4 frames took 1 ms, the ideal average is 0.75 ms. Can't test upper
bound because (especially on overloaded CIs) it all takes a magnitude
more than expected. */
if(data.values & GLFrameProfiler::Value::CpuDuration) {
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::CpuDuration));
CORRADE_COMPARE_AS(profiler.cpuDurationMean(), 0.70*1000*1000,
TestSuite::Compare::GreaterOrEqual);
}
#ifndef MAGNUM_TARGET_GLES
/* 24 unique vertices in 12 triangles, ideal ratio is 24/36 */
if(data.values & GLFrameProfiler::Value::VertexFetchRatio) {
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::VertexFetchRatio));
CORRADE_COMPARE_WITH(profiler.vertexFetchRatioMean()/1000, 0.6667,
TestSuite::Compare::around(0.1));
}
/* We use a default transformation, which means the whole cube should be
visible, nothing clipped */
if(data.values & GLFrameProfiler::Value::PrimitiveClipRatio) {
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::PrimitiveClipRatio));
CORRADE_COMPARE(profiler.primitiveClipRatioMean()/1000, 0.0);
}
#endif
}
#ifndef MAGNUM_TARGET_GLES
void FrameProfilerGLTest::vertexFetchRatioDivisionByZero() {
if(!GL::Context::current().isExtensionSupported<GL::Extensions::ARB::pipeline_statistics_query>())
CORRADE_SKIP(GL::Extensions::ARB::pipeline_statistics_query::string() + std::string(" is not available"));
GLFrameProfiler profiler{GLFrameProfiler::Value::VertexFetchRatio, 4};
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
MAGNUM_VERIFY_NO_GL_ERROR();
/* No draws happened, so the ratio should be 0 (and not crashing with a
division by zero) */
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::VertexFetchRatio));
CORRADE_COMPARE(profiler.vertexFetchRatioMean(), 0.0);
}
void FrameProfilerGLTest::primitiveClipRatioDivisionByZero() {
if(!GL::Context::current().isExtensionSupported<GL::Extensions::ARB::pipeline_statistics_query>())
CORRADE_SKIP(GL::Extensions::ARB::pipeline_statistics_query::string() + std::string(" is not available"));
GLFrameProfiler profiler{GLFrameProfiler::Value::PrimitiveClipRatio, 4};
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
profiler.beginFrame();
profiler.endFrame();
MAGNUM_VERIFY_NO_GL_ERROR();
/* No draws happened, so the ratio should be 0 (and not crashing with a
division by zero) */
CORRADE_VERIFY(profiler.isMeasurementAvailable(GLFrameProfiler::Value::PrimitiveClipRatio));
CORRADE_COMPARE(profiler.primitiveClipRatioMean(), 0.0);
}
#endif
}}}}
CORRADE_TEST_MAIN(Magnum::DebugTools::Test::FrameProfilerGLTest)

1097
src/Magnum/DebugTools/Test/FrameProfilerTest.cpp

File diff suppressed because it is too large Load Diff

198
src/Magnum/Trade/AbstractSceneConverter.cpp

@ -0,0 +1,198 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 "AbstractSceneConverter.h"
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/Trade/ArrayAllocator.h"
#include "Magnum/Trade/MeshData.h"
#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
#include "Magnum/Trade/configure.h"
#endif
namespace Magnum { namespace Trade {
std::string AbstractSceneConverter::pluginInterface() {
return "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1";
}
#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
std::vector<std::string> AbstractSceneConverter::pluginSearchPaths() {
return PluginManager::implicitPluginSearchPaths(
#ifndef MAGNUM_BUILD_STATIC
Utility::Directory::libraryLocation(&pluginInterface),
#else
{},
#endif
#ifdef CORRADE_IS_DEBUG_BUILD
MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR,
#else
MAGNUM_PLUGINS_SCENECONVERTER_DIR,
#endif
#ifdef CORRADE_IS_DEBUG_BUILD
"magnum-d/"
#else
"magnum/"
#endif
"sceneconverters");
}
#endif
AbstractSceneConverter::AbstractSceneConverter() = default;
AbstractSceneConverter::AbstractSceneConverter(PluginManager::Manager<AbstractSceneConverter>& manager): PluginManager::AbstractManagingPlugin<AbstractSceneConverter>{manager} {}
AbstractSceneConverter::AbstractSceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin): PluginManager::AbstractManagingPlugin<AbstractSceneConverter>{manager, plugin} {}
SceneConverterFeatures AbstractSceneConverter::features() const {
const SceneConverterFeatures features = doFeatures();
CORRADE_ASSERT(features, "Trade::AbstractSceneConverter::features(): implementation reported no features", {});
return features;
}
void AbstractSceneConverter::setFlags(SceneConverterFlags flags) {
_flags = flags;
doSetFlags(flags);
}
void AbstractSceneConverter::doSetFlags(SceneConverterFlags) {}
Containers::Optional<MeshData> AbstractSceneConverter::convert(const MeshData& mesh) {
CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMesh,
"Trade::AbstractSceneConverter::convert(): mesh conversion not supported", {});
Containers::Optional<MeshData> out = doConvert(mesh);
CORRADE_ASSERT(!out || (
(!out->_indexData.deleter() || out->_indexData.deleter() == Implementation::nonOwnedArrayDeleter || out->_indexData.deleter() == ArrayAllocator<char>::deleter) &&
(!out->_vertexData.deleter() || out->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || out->_vertexData.deleter() == ArrayAllocator<char>::deleter) &&
(!out->_attributes.deleter() || out->_attributes.deleter() == reinterpret_cast<void(*)(MeshAttributeData*, std::size_t)>(Implementation::nonOwnedArrayDeleter))),
"Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter", {});
return out;
}
Containers::Optional<MeshData> AbstractSceneConverter::doConvert(const MeshData&) {
CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convert(): mesh conversion advertised but not implemented", {});
}
bool AbstractSceneConverter::convertInPlace(MeshData& mesh) {
CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshInPlace,
"Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported", {});
return doConvertInPlace(mesh);
}
bool AbstractSceneConverter::doConvertInPlace(MeshData&) {
CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertInPlace(): mesh conversion advertised but not implemented", {});
}
Containers::Array<char> AbstractSceneConverter::convertToData(const MeshData& mesh) {
CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshToData,
"Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {});
Containers::Array<char> out = doConvertToData(mesh);
CORRADE_ASSERT(!out || !out.deleter() || out.deleter() == Implementation::nonOwnedArrayDeleter || out.deleter() == ArrayAllocator<char>::deleter,
"Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {});
return out;
}
Containers::Array<char> AbstractSceneConverter::doConvertToData(const MeshData&) {
CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion advertised but not implemented", {});
}
bool AbstractSceneConverter::convertToFile(const std::string& filename, const MeshData& mesh) {
CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToFile,
"Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {});
return doConvertToFile(filename, mesh);
}
bool AbstractSceneConverter::doConvertToFile(const std::string& filename, const MeshData& mesh) {
CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented", false);
const auto data = doConvertToData(mesh);
/* No deleter checks as it doesn't matter here */
if(!data) return false;
/* Open file */
if(!Utility::Directory::write(filename, data)) {
Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename;
return false;
}
return true;
}
Debug& operator<<(Debug& debug, const SceneConverterFeature value) {
debug << "Trade::SceneConverterFeature" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(v) case SceneConverterFeature::v: return debug << "::" #v;
_c(ConvertMesh)
_c(ConvertMeshInPlace)
_c(ConvertMeshToData)
_c(ConvertMeshToFile)
#undef _c
/* LCOV_EXCL_STOP */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
Debug& operator<<(Debug& debug, const SceneConverterFeatures value) {
return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFeatures{}", {
SceneConverterFeature::ConvertMesh,
SceneConverterFeature::ConvertMeshInPlace,
SceneConverterFeature::ConvertMeshToData,
/* Implied by ConvertMeshToData, has to be after */
SceneConverterFeature::ConvertMeshToFile});
}
Debug& operator<<(Debug& debug, const SceneConverterFlag value) {
debug << "Trade::SceneConverterFlag" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(v) case SceneConverterFlag::v: return debug << "::" #v;
_c(Verbose)
#undef _c
/* LCOV_EXCL_STOP */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
Debug& operator<<(Debug& debug, const SceneConverterFlags value) {
return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFlags{}", {
SceneConverterFlag::Verbose});
}
}}

323
src/Magnum/Trade/AbstractSceneConverter.h

@ -0,0 +1,323 @@
#ifndef Magnum_Trade_AbstractSceneConverter_h
#define Magnum_Trade_AbstractSceneConverter_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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::Trade::AbstractSceneConverter, enum @ref Magnum::Trade::SceneConverterFeature, enum set @ref Magnum::Trade::SceneConverterFeatures
* @m_since_latest
*/
#include <Corrade/PluginManager/AbstractManagingPlugin.h>
#include "Magnum/Magnum.h"
#include "Magnum/Trade/Trade.h"
#include "Magnum/Trade/visibility.h"
namespace Magnum { namespace Trade {
/**
@brief Features supported by a scene converter
@m_since_latest
@see @ref SceneConverterFeatures, @ref AbstractSceneConverter::features()
*/
enum class SceneConverterFeature: UnsignedByte {
/**
* Convert a mesh with
* @ref AbstractSceneConverter::convert(const MeshData&).
*/
ConvertMesh = 1 << 0,
/**
* Convert a mesh in-place with
* @ref AbstractSceneConverter::convertInPlace(MeshData&).
*/
ConvertMeshInPlace = 1 << 1,
/**
* Converting a mesh to a file with
* @ref AbstractSceneConverter::convertToFile(const std::string&, const MeshData&).
*/
ConvertMeshToFile = 1 << 2,
/**
* Converting a mesh to raw data with
* @ref AbstractSceneConverter::convertToData(const MeshData&). Implies
* @ref SceneConverterFeature::ConvertMeshToFile.
*/
ConvertMeshToData = ConvertMeshToFile|(1 << 3)
};
/**
@brief Features supported by a scene converter
@m_since_latest
@see @ref AbstractSceneConverter::features()
*/
typedef Containers::EnumSet<SceneConverterFeature> SceneConverterFeatures;
CORRADE_ENUMSET_OPERATORS(SceneConverterFeatures)
/** @debugoperatorenum{SceneConverterFeature} */
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFeature value);
/** @debugoperatorenum{SceneConverterFeatures} */
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFeatures value);
/**
@brief Scene converter flag
@m_since_latest
@see @ref SceneConverterFlags, @ref AbstractSceneConverter::setFlags()
*/
enum class SceneConverterFlag: UnsignedByte {
/**
* Print verbose diagnostic during import. By default the importer only
* prints messages on error or when some operation might cause unexpected
* data modification or loss.
*/
Verbose = 1 << 0
/** @todo Y flip */
};
/**
@brief Scene converter flags
@m_since_latest
@see @ref AbstractImporter::setFlags()
*/
typedef Containers::EnumSet<SceneConverterFlag> SceneConverterFlags;
CORRADE_ENUMSET_OPERATORS(SceneConverterFlags)
/**
@debugoperatorenum{SceneConverterFlag}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFlag value);
/**
@debugoperatorenum{SceneConverterFlags}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFlags value);
/**
@brief Base for scene converter plugins
@m_since_latest
Provides functionality for converting meshes and other scene data between
various formats or performing optimizations and other operations on them. See
@ref plugins for more information and `*SceneConverter` classes in the
@ref Trade namespace for available scene converter plugins.
@section Trade-AbstractSceneConverter-data-dependency Data dependency
The instances returned from various functions *by design* have no dependency on
the importer instance and neither on the dynamic plugin module. In other words,
you don't need to keep the importer instance (or the plugin manager instance)
around in order to have the `*Data` instances valid. Moreover, all
@ref Corrade::Containers::Array instances returned through @ref MeshData and
others are only allowed to have default deleters --- this is to avoid potential
dangling function pointer calls when destructing such instances after the
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.
You don't need to do most of the redundant sanity checks, these things are
checked by the implementation:
- The function @ref doConvert(const MeshData&) is called only if
@ref SceneConverterFeature::ConvertMesh is supported.
- The function @ref doConvertInPlace(MeshData&) is called only if
@ref SceneConverterFeature::ConvertMeshInPlace is supported.
- The function @ref doConvertToData(const MeshData&) is called only if
@ref SceneConverterFeature::ConvertMeshToData is supported.
- The function @ref doConvertToFile(const std::string&, const MeshData&) is
called only if @ref SceneConverterFeature::ConvertMeshToFile is supported.
@m_class{m-block m-warning}
@par Dangling function pointers on plugin unload
As @ref Trade-AbstractSceneConverter-data-dependency "mentioned above",
@ref Corrade::Containers::Array instances returned from plugin
implementations are not allowed to use anything else than the default
deleter or the deleter used by @ref Trade::ArrayAllocator, otherwise this
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.
*/
class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::AbstractManagingPlugin<AbstractSceneConverter> {
public:
/**
* @brief Plugin interface
*
* @code{.cpp}
* "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1"
* @endcode
*/
static std::string pluginInterface();
#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
/**
* @brief Plugin search paths
*
* Looks into `magnum/sceneconverters/` or `magnum-d/sceneconverters/`
* next to the dynamic @ref Trade library, next to the executable and
* elsewhere according to the rules documented in
* @ref Corrade::PluginManager::implicitPluginSearchPaths(). The search
* directory can be also hardcoded using the `MAGNUM_PLUGINS_DIR` CMake
* variables, see @ref building for more information.
*
* Not defined on platforms without
* @ref CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT "dynamic plugin support".
*/
static std::vector<std::string> pluginSearchPaths();
#endif
/** @brief Default constructor */
explicit AbstractSceneConverter();
/** @brief Constructor with access to plugin manager */
explicit AbstractSceneConverter(PluginManager::Manager<AbstractSceneConverter>& manager);
/** @brief Plugin manager constructor */
explicit AbstractSceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin);
/** @brief Features supported by this converter */
SceneConverterFeatures features() const;
/** @brief Converter flags */
SceneConverterFlags flags() const { return _flags; }
/**
* @brief Set converter flags
*
* Some flags can be set only if the converter supports particular
* features, see documentation of each @ref SceneConverterFlag for more
* information. By default no flags are set.
*/
void setFlags(SceneConverterFlags flags);
/**
* @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.
* @see @ref features(), @ref convertInPlace(MeshData&)
*/
Containers::Optional<MeshData> 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. Returns @cpp true @ce if the operation succeeded. On
* failure the function prints an error message and returns
* @cpp false @ce, @p mesh is guaranteed to stay unchanged.
* @see @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 the function prints an error message and returns
* @cpp nullptr @ce.
* @see @ref features(), @ref convertToFile()
*/
Containers::Array<char> convertToData(const MeshData& mesh);
/**
* @brief Convert a mesh to a file
*
* Available only if @ref SceneConverterFeature::ConvertMeshToFile or
* @ref SceneConverterFeature::ConvertMeshToData is supported. Returns
* @cpp true @ce on success, prints an error message and returns
* @cpp false @ce otherwise.
* @see @ref features(), @ref convertToData()
*/
bool convertToFile(const std::string& filename, const MeshData& mesh);
private:
/**
* @brief Implementation of @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 of @ref convert(const MeshData&) */
virtual Containers::Optional<MeshData> doConvert(const MeshData& mesh);
/** @brief Implementation of @ref convertInPlace(MeshData&) */
virtual bool doConvertInPlace(MeshData& mesh);
/** @brief Implementation of @ref convertToData(const MeshData&) */
virtual Containers::Array<char> doConvertToData(const MeshData& mesh);
/**
* @brief Implementation of @ref convertToFile(const std::string&, const MeshData&)
*
* If @ref SceneConverterFeature::ConvertMeshToData is supported,
* default implementation calls @ref doConvertToData(const MeshData&)
* and saves the result to given file.
*/
virtual bool doConvertToFile(const std::string& filename, const MeshData& mesh);
SceneConverterFlags _flags;
};
}}
#endif

4
src/Magnum/Trade/CMakeLists.txt

@ -28,7 +28,6 @@ find_package(Corrade REQUIRED PluginManager)
set(MagnumTrade_SRCS
AbstractMaterialData.cpp
ArrayAllocator.cpp
Data.cpp
LightData.cpp
MeshObjectData2D.cpp
MeshObjectData3D.cpp
@ -38,8 +37,10 @@ set(MagnumTrade_SRCS
set(MagnumTrade_GracefulAssert_SRCS
AbstractImageConverter.cpp
AbstractImporter.cpp
AbstractSceneConverter.cpp
AnimationData.cpp
CameraData.cpp
Data.cpp
ImageData.cpp
MeshData.cpp
ObjectData2D.cpp
@ -50,6 +51,7 @@ set(MagnumTrade_HEADERS
AbstractImporter.h
AbstractImageConverter.h
AbstractMaterialData.h
AbstractSceneConverter.h
AnimationData.h
ArrayAllocator.h
CameraData.h

94
src/Magnum/Trade/Data.cpp

@ -25,10 +25,17 @@
#include "Data.h"
#include <cctype>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/EnumSet.hpp>
namespace Magnum { namespace Trade {
static_assert(sizeof(DataChunkHeader) == (sizeof(void*) == 4 ? 20 : 24),
"DataChunkHeader has unexpected size");
static_assert(alignof(DataChunkHeader) == sizeof(std::size_t),
"DataChunkHeader has unexpected alignment");
Debug& operator<<(Debug& debug, const DataFlag value) {
debug << "Trade::DataFlag" << Debug::nospace;
@ -50,6 +57,93 @@ Debug& operator<<(Debug& debug, const DataFlags value) {
DataFlag::Mutable});
}
namespace {
Debug& printFourCC(Debug& debug, UnsignedInt value, const char* name) {
debug << name << Debug::nospace;
for(std::size_t i = 0; i != 4; ++i) {
if(i) debug << Debug::nospace << ",";
const int c = value & 255;
if(std::isprint(c)) {
const char data[] = {'\'', char(c), '\'', '\0'};
debug << data;
} else {
debug << reinterpret_cast<void*>(c);
}
value >>= 8;
}
return debug << Debug::nospace << ")";
}
}
Debug& operator<<(Debug& debug, const DataChunkType value) {
return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkType(");
}
Debug& operator<<(Debug& debug, const DataChunkSignature value) {
return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkSignature(");
}
namespace {
constexpr DataChunkHeader DataChunkHeaderPrefix{
128, {'\x0a'}, {'\x0d', '\x0a'}, DataChunkSignature::Current, 0, 0,
/* Type and size isn't checked when validating and gets overwritten
when serializing */
DataChunkType{}, 0
};
static_assert(DataChunkHeaderPrefix.version & 0x80,
"version needs the high bit set to prevent detection as a text file");
}
bool isDataChunk(Containers::ArrayView<const void> data) {
return data && data.size() >= sizeof(DataChunkHeader) &&
std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) == 0 &&
reinterpret_cast<const DataChunkHeader*>(data.data())->size <= data.size();
}
const DataChunkHeader* dataChunkHeaderDeserialize(const Containers::ArrayView<const void> data) {
if(data.size() < sizeof(DataChunkHeader)) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << sizeof(DataChunkHeader) << "bytes for a header but got" << data.size();
return nullptr;
}
const auto& header = *reinterpret_cast<const DataChunkHeader*>(data.data());
if(header.version != DataChunkHeaderPrefix.version) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected version" << DataChunkHeaderPrefix.version << "but got" << header.version;
return nullptr;
}
if(header.signature != DataChunkSignature::Current) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected signature" << DataChunkSignature::Current << "but got" << header.signature;
return nullptr;
}
if(std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) != 0) {
Error{} << "Trade::dataChunkHeaderDeserialize(): invalid header check bytes";
return nullptr;
}
if(header.size > data.size()) {
Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << header.size << "bytes but got" << data.size();
return nullptr;
}
return reinterpret_cast<const DataChunkHeader*>(data.data());
}
std::size_t dataChunkHeaderSerializeInto(const Containers::ArrayView<char> out, const DataChunkType type, const UnsignedShort typeVersion) {
CORRADE_ASSERT(out.size() >= sizeof(DataChunkHeader),
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least" << sizeof(DataChunkHeader) << "bytes but got" << out.size(), {});
auto& header = *reinterpret_cast<DataChunkHeader*>(out.data());
header = DataChunkHeaderPrefix;
header.typeVersion = typeVersion;
header.type = type;
header.size = out.size();
return sizeof(DataChunkHeader);
}
namespace Implementation {
void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ }
}

193
src/Magnum/Trade/Data.h

@ -26,11 +26,13 @@
*/
/** @file
* @brief Enum @ref Magnum::Trade::DataFlag, enum set @ref Magnum::Trade::DataFlags
* @brief Struct @ref Magnum::Trade::DataChunkHeader, enum @ref Magnum::Trade::DataFlag, @ref Magnum::Trade::DataChunkSignature, @ref Magnum::Trade::DataChunkType, enum set @ref Magnum::Trade::DataFlags, function @ref Magnum::Trade::isDataChunk(), @ref Magnum::Trade::dataChunkHeaderDeserialize(), @ref Magnum::Trade::dataChunkHeaderSerializeInto()
* @m_since_latest
*/
#include <cstddef>
#include <Corrade/Containers/EnumSet.h>
#include <Corrade/Utility/Endianness.h>
#include "Magnum/Magnum.h"
#include "Magnum/Trade/visibility.h"
@ -85,6 +87,195 @@ CORRADE_ENUMSET_OPERATORS(DataFlags)
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value);
/**
@brief Memory-mappable data chunk type
@m_since_latest
A [FourCC](https://en.wikipedia.org/wiki/FourCC)-like identifier of the data
contained in the chunk. Used together with @ref DataChunkHeader::typeVersion to
identify version of a particular chunk type.
@section Trade-DataChunkType-custom Custom data chunk types
All identifiers starting with an uppercase leter are reserved for Magnum
itself, custom application-specific data types should use a lowercase first
letter instead. Casing of the three remaining characters doesn't have any
specified effect in the current version of the header. It doesn't need to be
alphanumeric either, but for additional versioning of a particular chunk type
it's recommended to use @ref DataChunkHeader::typeVersion, keeping the chunk
type FourCC clearly recognizable.
@see @ref blob
*/
enum class DataChunkType: UnsignedInt {
/**
* Serialized @ref MeshData. The letters `Mesh`.
*
* Current version is @cpp 0 @ce.
*/
Mesh = Utility::Endianness::fourCC('M', 'e', 's', 'h'),
#if 0
/* None of these used yet, here just to lay out the naming scheme */
Animation = Utility::Endianness::fourCC('A', 'n', 'i', 'm'),
Camera = Utility::Endianness::fourCC('C', 'a', 'm', 0),
Image1D = Utility::Endianness::fourCC('I', 'm', 'g', '1'),
Image2D = Utility::Endianness::fourCC('I', 'm', 'g', '2'),
Image3D = Utility::Endianness::fourCC('I', 'm', 'g', '3'),
Light = Utility::Endianness::fourCC('L', 'i', 'g', 't'),
Material = Utility::Endianness::fourCC('M', 't', 'l', 0),
Scene = Utility::Endianness::fourCC('S', 'c', 'n', 0),
Texture = Utility::Endianness::fourCC('T', 'e', 'x', 0)
#endif
};
/**
@debugoperatorenum{DataChunkType}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkType value);
/**
@brief Memory-mappable data chunk signature
@m_since_latest
Reads as `BLOB` letters for a Little-Endian 64 bit data chunk. For Big-Endian
the order is reversed (thus `BOLB`), 32-bit data have the `L` letter lowercase.
@see @ref blob, @ref DataChunkHeader::signature
*/
enum class DataChunkSignature: UnsignedInt {
/** Little-Endian 32-bit data. The letters `BlOB`. */
Little32 = Utility::Endianness::fourCC('B', 'l', 'O', 'B'),
/** Little-Endian 64-bit data. The letters `BLOB`. */
Little64 = Utility::Endianness::fourCC('B', 'L', 'O', 'B'),
/** Big-Endian 32-bit data. The letters `BOlB`. */
Big32 = Utility::Endianness::fourCC('B', 'O', 'l', 'B'),
/** Big-Endian 64-bit data. The letters `BOLB`. */
Big64 = Utility::Endianness::fourCC('B', 'O', 'L', 'B'),
/** Signature matching this platform. Alias to one of the above. */
Current
#ifndef DOXYGEN_GENERATING_OUTPUT
=
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(std::size_t) == 8 ? Little64 : Little32
#else
sizeof(std::size_t) == 8 ? Big64 : Big32
#endif
#endif
};
/**
@debugoperatorenum{DataChunkSignature}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkSignature value);
/**
@brief Header for memory-mappable data chunks
@m_since_latest
See @ref blob for an introduction.
Since the goal of the serialization format is to be a direct equivalent to the
in-memory data layout, there's four different variants of the header based on
whether it's running on a 32-bit or 64-bit system and whether the machine is
Little- or Big-Endian. A 64-bit variant of the header has 24 bytes to support
data larger than 4 GB, the 32-bit variant is 20 bytes. Apart from the @ref size
member, the header is designed to contain the same amount of information on
both, and its size is chosen so the immediately following data can be aligned
to either 4 or 8 bytes without needing to add extra padding.
The header contents are as follows, vaguely inspired by the
[PNG file header](https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header).
All fields except @ref typeVersion and @ref size (marked with
@m_class{m-label m-primary} **E**) are stored in an endian-independent way,
otherwise the endian matches the signature field.
@m_class{m-row m-container-inflate}
@parblock
@m_class{m-fullwidth}
Byte offset | Byte size&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Member | Contents
----------- | --------- | ------------- | -------------------------------------
0 | 1 | @ref DataChunkHeader::version "version" | Header version. Has the high bit set to prevent the file from being detected as text. Currently set to @cpp 128 @ce.
1 | 1 | @ref eolUnix | Unix EOL (LF, @cpp '\x0a' @ce), to detect unwanted Unix-to-DOS line ending conversion
2 | 2 | @ref eolDos | DOS EOL (CR+LF, @cpp '\x0d', '\x0a' @ce), to detect unwanted DOS-to-Unix line ending conversion
4 | 4 | @ref signature | File signature. Differs based on bitness and endianness, see @ref DataChunkSignature for more information.
8 | 2 | @ref zero | Two zero bytes (@cpp '\x00', '\x00' @ce), to prevent the data from being treated and copied as a null-terminated (wide) string.
10 | 2 @m_class{m-label m-primary} **E** | @ref typeVersion | Data chunk type version. Use is chunk-specific, see @ref DataChunkType for more information.
12 | 4 | @ref type | Data chunk type, see @ref DataChunkType for more information
16 | 4 or 8 @m_class{m-label m-primary} **E** | @ref size | Data chunk size, including the header size. Stored in size matching the signature field.
@endparblock
For a particular header variant the first 10 bytes is static and thus can be
used for file validation. After the header are directly the chunk data. For performance reasons it's recommended to have the data padded to be a multiple
of 4 or 8 bytes to ensure the immediately following chunk is correctly aligned
as well, but it's not a strict recommendation and not enforced in any way in
current version of the format.
Current version of the header doesn't have any checksum field in order to make
it easy to modify the data in-place, this might change in the future.
@see @ref DataChunkSignature, @ref DataChunkType, @ref isDataChunk(),
@ref dataChunkHeaderDeserialize(), @ref dataChunkHeaderSerializeInto()
*/
struct DataChunkHeader {
UnsignedByte version; /**< @brief Header version */
char eolUnix[1]; /**< @brief Unix EOL */
char eolDos[2]; /**< @brief Dos EOL */
DataChunkSignature signature; /**< @brief Signature */
UnsignedShort zero; /**< @brief Two zero bytes */
UnsignedShort typeVersion; /**< @brief Chunk type version */
DataChunkType type; /**< @brief Chunk type */
std::size_t size; /**< @brief Chunk size */
};
/**
@brief Check if given data blob is a valid data chunk
@m_since_latest
Returns @cpp true @ce if @p data is a valid @ref DataChunkHeader, matches
current platform and @p data is large enough to contain the whole chunk,
@cpp false @ce otherwise. The function doesn't print any diagnostic messages on
validation failure, use @ref dataChunkHeaderDeserialize() instead if you need
to know why.
@see @ref blob
*/
MAGNUM_TRADE_EXPORT bool isDataChunk(Containers::ArrayView<const void> data);
/**
@brief Try to deserialize a data chunk from a memory-mappable representation
@m_since_latest
Checks that @p data is large enough to contain a valid data chunk, validates
the header and then returns @p data reinterpreted as a @ref DataChunkHeader
pointer. On failure prints an error message and returns @cpp nullptr @ce.
@see @ref blob, @ref isDataChunk(), @ref dataChunkHeaderSerializeInto()
*/
MAGNUM_TRADE_EXPORT const DataChunkHeader* dataChunkHeaderDeserialize(Containers::ArrayView<const void> data);
/**
@brief Serialize a data chunk header into existing array
@param[out] out Where to write the output
@param[out] type Data chunk type
@param[out] typeVersion Data chunk type version
@return Number of bytes written. Same as size of @ref DataChunkHeader.
@m_since_latest
Expects that @p data is at least the size of @ref DataChunkHeader. Fills in
@ref DataChunkHeader::typeVersion and @ref DataChunkHeader::type with passed
values used in constructor, and @ref DataChunkHeader::size with @p data size.
@see @ref blob, @ref dataChunkHeaderDeserialize()
*/
MAGNUM_TRADE_EXPORT std::size_t dataChunkHeaderSerializeInto(Containers::ArrayView<char> out, DataChunkType type, UnsignedShort typeVersion);
namespace Implementation {
/* Used internally by MeshData */
MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t);

20
src/Magnum/Trade/Implementation/converterUtilities.h

@ -46,20 +46,34 @@ void setOptions(PluginManager::AbstractPlugin& plugin, const std::string& option
Utility::String::trimInPlace(keyValue[0]);
Utility::String::trimInPlace(keyValue[2]);
std::vector<std::string> keyParts = Utility::String::split(keyValue[0], '/');
CORRADE_INTERNAL_ASSERT(!keyParts.empty());
Utility::ConfigurationGroup* group = &plugin.configuration();
bool groupNotRecognized = false;
for(std::size_t i = 0; i != keyParts.size() - 1; ++i) {
Utility::ConfigurationGroup* subgroup = group->group(keyParts[i]);
if(!subgroup) {
groupNotRecognized = true;
subgroup = group->addGroup(keyParts[i]);
}
group = subgroup;
}
/* Provide a warning message in case the plugin doesn't define given
option in its default config. The plugin is not *required* to have
those tho (could be backward compatibility entries, for example), so
not an error. */
if(!plugin.configuration().valueCount(keyValue[0]))
if(groupNotRecognized || !group->hasValue(keyParts.back())) {
Warning{} << "Option" << keyValue[0] << "not recognized by" << plugin.plugin();
}
/* If the option doesn't have an =, treat it as a boolean flag that's
set to true. While there's no similar way to do an inverse, it's
still nicer than causing a fatal error with those. */
if(keyValue[1].empty())
plugin.configuration().setValue(keyValue[0], true);
group->setValue(keyParts.back(), true);
else
plugin.configuration().setValue(keyValue[0], keyValue[2]);
group->setValue(keyParts.back(), keyValue[2]);
}
}

150
src/Magnum/Trade/MeshData.cpp

@ -795,6 +795,156 @@ Containers::Array<char> MeshData::releaseVertexData() {
return out;
}
namespace {
struct MeshDataHeader: DataChunkHeader {
UnsignedInt indexCount;
UnsignedInt vertexCount;
MeshPrimitive primitive;
MeshIndexType indexType;
Byte:8;
UnsignedShort attributeCount;
std::size_t indexOffset;
std::size_t indexDataSize;
std::size_t vertexDataSize;
};
static_assert(sizeof(MeshDataHeader) == (sizeof(void*) == 4 ? 48 : 64),
"MeshDataHeader has unexpected size");
}
Containers::Optional<MeshData> MeshData::deserialize(Containers::ArrayView<const void> data) {
/* Validate the header. If that fails, the error has been already printed,
so just propagate */
const DataChunkHeader* chunk = dataChunkHeaderDeserialize(data);
if(!chunk) return Containers::NullOpt;
/* Basic header validity */
if(chunk->type != DataChunkType::Mesh) {
Error{} << "Trade::MeshData::deserialize(): expected data chunk type" << DataChunkType::Mesh << "but got" << chunk->type;
return Containers::NullOpt;
}
if(chunk->typeVersion != 0) {
Error{} << "Trade::MeshData::deserialize(): invalid chunk type version, expected 0 but got" << chunk->typeVersion;
return Containers::NullOpt;
}
if(chunk->size < sizeof(MeshDataHeader)) {
Error{} << "Trade::MeshData::deserialize(): expected at least a" << sizeof(MeshDataHeader) << Debug::nospace << "-byte chunk for a header but got" << chunk->size;
return Containers::NullOpt;
}
/* Reinterpret as a mesh data and check that everything can fit */
const MeshDataHeader& header = static_cast<const MeshDataHeader&>(*chunk);
const std::size_t size = sizeof(MeshDataHeader) + header.attributeCount*sizeof(MeshAttributeData) + header.indexDataSize + header.vertexDataSize;
if(chunk->size != size) {
Error{} << "Trade::MeshData::deserialize(): expected a" << size << Debug::nospace << "-byte chunk but got" << chunk->size;
return Containers::NullOpt;
}
Containers::ArrayView<const MeshAttributeData> attributeData{reinterpret_cast<const MeshAttributeData*>(reinterpret_cast<const char*>(data.data()) + sizeof(MeshDataHeader)), header.attributeCount};
Containers::ArrayView<const char> vertexData{reinterpret_cast<const char*>(data.data()) + sizeof(MeshDataHeader) + header.attributeCount*sizeof(MeshAttributeData) + header.indexDataSize, header.vertexDataSize};
/* Check bounds of indices and all attributes */
/** @todo this will assert on invalid index type */
Containers::ArrayView<const char> indexData;
MeshIndexData indices;
if(header.indexType != MeshIndexType{}) {
const std::size_t indexEnd = header.indexOffset + header.indexCount*meshIndexTypeSize(header.indexType);
if(indexEnd > header.indexDataSize) {
Error{} << "Trade::MeshData::deserialize(): indices [" << Debug::nospace << header.indexOffset << Debug::nospace << ":" << Debug::nospace << indexEnd << Debug::nospace << "] out of range for" << header.indexDataSize << "bytes of index data";
return Containers::NullOpt;
}
indexData = Containers::ArrayView<const char>{reinterpret_cast<const char*>(data.data()) + sizeof(MeshDataHeader) + header.attributeCount*sizeof(MeshAttributeData), header.indexDataSize};
indices = MeshIndexData{header.indexType, indexData.suffix(header.indexOffset)};
}
for(std::size_t i = 0; i != attributeData.size(); ++i) {
const MeshAttributeData& attribute = attributeData[i];
/** @todo this will assert on invalid vertex format */
/** @todo check also consistency of vertex count and _isOffsetOnly? */
/* Check that the view fits into the provided vertex data array. For
implementation-specific formats we don't know the size so use 0 to
check at least partially. */
const UnsignedInt typeSize =
isVertexFormatImplementationSpecific(attribute._format) ? 0 :
vertexFormatSize(attribute._format);
const std::size_t attributeEnd = attribute._data.offset + (header.vertexCount - 1)*attribute._stride + typeSize;
if(header.vertexCount && attributeEnd > header.vertexDataSize) {
Error{} << "Trade::MeshData::deserialize(): attribute" << i << "[" << Debug::nospace << attribute._data.offset << Debug::nospace << ":" << Debug::nospace << attributeEnd << Debug::nospace << "] out of range for" << header.vertexDataSize << "bytes of vertex data";
return Containers::NullOpt;
}
}
return MeshData{header.primitive,
{}, indexData, indices,
{}, vertexData, meshAttributeDataNonOwningArray(attributeData),
header.vertexCount};
}
std::size_t MeshData::serializedSize() const {
return sizeof(MeshDataHeader) + sizeof(MeshAttributeData)*_attributes.size() +
_indexData.size() + _vertexData.size();
}
std::size_t MeshData::serializeInto(Containers::ArrayView<char> out) const {
#ifndef CORRADE_NO_DEBUG
const std::size_t size = serializedSize();
CORRADE_ASSERT(out.size() == size, "Trade::MeshData::serializeInto(): data too small, expected at least" << size << "bytes but got" << out.size(), {});
#endif
/* Serialize the header */
dataChunkHeaderSerializeInto(out, DataChunkType::Mesh, 0);
/* Memset the header to avoid padding getting random values */
std::memset(out.data() + sizeof(DataChunkHeader), 0, sizeof(MeshDataHeader) + _attributes.size()*sizeof(MeshAttributeData) - sizeof(DataChunkHeader));
MeshDataHeader& header = *reinterpret_cast<MeshDataHeader*>(out.data());
header.indexCount = _indexCount;
header.vertexCount = _vertexCount;
header.primitive = _primitive;
header.indexType = _indexType;
header.attributeCount = _attributes.size();
header.indexOffset = _indices - _indexData.data();
header.indexDataSize = _indexData.size();
header.vertexDataSize = _vertexData.size();
std::size_t offset = sizeof(MeshDataHeader);
/* Copy the attribute data, turning them into offset-only */
auto outAttributeData = Containers::arrayCast<MeshAttributeData>(out.slice(offset, offset + sizeof(MeshAttributeData)*_attributes.size()));
for(std::size_t i = 0; i != outAttributeData.size(); ++i) {
if(_attributes[i]._isOffsetOnly)
outAttributeData[i]._data.offset = _attributes[i]._data.offset;
else
outAttributeData[i]._data.offset = reinterpret_cast<const char*>(_attributes[i]._data.pointer) - _vertexData;
outAttributeData[i]._vertexCount = _attributes[i]._vertexCount;
outAttributeData[i]._format = _attributes[i]._format;
outAttributeData[i]._stride = _attributes[i]._stride;
outAttributeData[i]._name = _attributes[i]._name;
outAttributeData[i]._arraySize = _attributes[i]._arraySize;
outAttributeData[i]._isOffsetOnly = true;
}
offset += sizeof(MeshAttributeData)*_attributes.size();
/* Copy the index data */
Utility::copy(_indexData, out.slice(offset, offset + _indexData.size()));
offset += _indexData.size();
/* Copy the vertex data */
Utility::copy(_vertexData, out.slice(offset, offset + _vertexData.size()));
offset += _vertexData.size();
/* Check we calculated correctly, return number of bytes written */
CORRADE_INTERNAL_ASSERT(offset == size);
return offset;
}
Containers::Array<char> MeshData::serialize() const {
Containers::Array<char> out{Containers::NoInit, serializedSize()};
serializeInto(out);
return out;
}
Debug& operator<<(Debug& debug, const MeshAttribute value) {
debug << "Trade::MeshAttribute" << Debug::nospace;

98
src/Magnum/Trade/MeshData.h

@ -31,6 +31,7 @@
*/
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Mesh.h"
@ -709,6 +710,54 @@ you can also supply implementation-specific values that are not available in
the generic @ref MeshPrimitive enum, similarly see also
@ref Trade-MeshAttributeData-custom-vertex-format for details on
implementation-specific @ref VertexFormat values.
@section Trade-MeshData-serialization Memory-mappable serialization format
Using @ref serialize(), an instance of this class can be serialized into
Magnum's memory-mappable serialization format, and deserialized back using
@ref deserialize(). See @ref blob for a high-level introduction.
The deserialization only involves various sanity checks followed by a creation
of a new @ref MeshData instance referencing the index, vertex and attribute
data in the original memory view. The binary representation begins with
@ref DataChunkHeader of type @ref DataChunkType::Mesh and type version
@cpp 0 @ce, the rest is defined like below, with bitness and endianness
matching the header signature. Fields that are stored in an endian-dependent
way are marked with @m_class{m-label m-primary} **E**:
@m_class{m-fullwidth}
Byte offset | Byte size | Contents
----------- | --------- | -----------------------------------------------------
20 or 24 | 4 @m_class{m-label m-primary} **E** | Index count, or @cpp 0 @ce if the mesh has no indices
24 or 28 | 4 @m_class{m-label m-primary} **E** | Vertex count, or @cpp 0 @ce if the mesh has no vertices
28 or 32 | 4 @m_class{m-label m-primary} **E** | Mesh primitive, defined with @ref MeshPrimitive
32 or 36 | 1 | Index type, defined with @ref MeshIndexType, or zero if the mesh is not indexed
33 or 37 | 1 | @m_class{m-text m-dim} *Padding / reserved*
34 or 38 | 2 @m_class{m-label m-primary} **E** | Attribute count
36 or 40 | 4 or 8 @m_class{m-label m-primary} **E** | Index offset in the index data array
40 or 44 | 4 or 8 @m_class{m-label m-primary} **E** | Index data size in bytes
44 or 56 | 4 or 8 @m_class{m-label m-primary} **E** | Vertex data size in bytes
48 or 64 | ... @m_class{m-label m-primary} **E** | List of @ref MeshAttributeData entries, count defined by attribute count above
... | ... @m_class{m-label m-primary} **E** | Index data, byte count defined by index data size above
... | ... @m_class{m-label m-primary} **E** | Vertex data, byte count defined by vertex data size above
For the attribute list, each @ref MeshAttributeData entry is either 20 or 24
bytes, with fields defined like this. In this case it exactly matches the
internals of @ref MeshAttributeData to allow the attribute array to be
referenced directly from the original memory:
Byte offset | Byte size | Contents
----------- | --------- | -----------------------------------------------------
0 | 4 @m_class{m-label m-primary} **E** | Vertex format, defined with @ref VertexFormat
4 | 2 @m_class{m-label m-primary} **E** | Mesh attribute name, defined with @ref MeshAttribute
6 | 1 | Whether the attribute is offset-only. Always @cpp 1 @ce.
7 | 1 | @m_class{m-text m-dim} *Padding / reserved*
8 | 4 @m_class{m-label m-primary} **E** | Vertex count. Same value as the vertex count field above.
12 | 2 @m_class{m-label m-primary} **E** | Vertex stride. Always positive and not larger than @cpp 32767 @ce.
14 | 2 @m_class{m-label m-primary} **E** | Attribute array size
16 | 4 or 8 @m_class{m-label m-primary} **E** | Attribute offset in the vertex data array
@see @ref AbstractImporter::mesh()
*/
class MAGNUM_TRADE_EXPORT MeshData {
@ -721,6 +770,30 @@ class MAGNUM_TRADE_EXPORT MeshData {
ImplicitVertexCount = ~UnsignedInt{}
};
/**
* @brief Try to deserialize from a memory-mappable representation
*
* If @p data is a valid serialized representation of @ref MeshData
* matching current platform, returns a @ref MeshData instance
* referencing the original data. On failure prints an error message
* and returns @ref Containers::NullOpt.
*
* The returned instance doesn't provide mutable access to the original
* data, pass a non-const view to the overload below to get that.
* @see @ref serialize()
*/
static Containers::Optional<MeshData> deserialize(Containers::ArrayView<const void> data);
/** @overload */
template<class T, class = typename std::enable_if<std::is_convertible<T&&, Containers::ArrayView<void>>::value>::type> static Containers::Optional<MeshData> deserialize(T&& data) {
Containers::Optional<MeshData> out = deserialize(Containers::ArrayView<const void>{data});
if(out) {
out->_indexDataFlags = DataFlag::Mutable;
out->_vertexDataFlags = DataFlag::Mutable;
}
return out;
}
/**
* @brief Construct an indexed mesh data
* @param primitive Primitive
@ -1775,11 +1848,36 @@ class MAGNUM_TRADE_EXPORT MeshData {
*/
const void* importerState() const { return _importerState; }
/**
* @brief Size of serialized data
*
* Amount of bytes written by @ref serializeInto() or @ref serialize().
*/
std::size_t serializedSize() const;
/**
* @brief Serialize to a memory-mappable representation
*
* @see @ref serializeInto(), @ref deserialize()
*/
Containers::Array<char> serialize() const;
/**
* @brief Serialize to a memory-mappable representation into an existing array
* @param[out] out Where to write the output
* @return Number of bytes written. Same as @ref serializedSize().
*
* Expects that @p data is at least @ref serializedSize().
* @see @ref serialize(), @ref deserialize()
*/
std::size_t serializeInto(Containers::ArrayView<char> out) const;
private:
/* For custom deleter checks. Not done in the constructors here because
the restriction is pointless when used outside of plugin
implementations. */
friend AbstractImporter;
friend AbstractSceneConverter;
/* Internal helper that doesn't assert, unlike attributeId() */
UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const;

10
src/Magnum/Trade/Test/.gitattributes vendored

@ -0,0 +1,10 @@
# You have to add the following to your .git/config or global
# ~/.gitconfig to make the binary diffs work (without the comment
# character, of course):
#
# [diff "hex"]
# textconv = hexdump -v -C
# binary = true
#
*.blob binary diff=hex

484
src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp

@ -0,0 +1,484 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <sstream>
#include <Corrade/Containers/Optional.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/TestSuite/Compare/FileToString.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/Math/Vector3.h"
#include "Magnum/Trade/ArrayAllocator.h"
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "Magnum/Trade/MeshData.h"
#include "configure.h"
namespace Magnum { namespace Trade { namespace Test { namespace {
struct AbstractSceneConverterTest: TestSuite::Tester {
explicit AbstractSceneConverterTest();
void featuresNone();
void setFlags();
void setFlagsNotImplemented();
void thingNotSupported();
void convertMesh();
void convertMeshNotImplemented();
void convertMeshNonOwningDeleters();
void convertMeshGrowableDeleters();
void convertMeshCustomIndexDataDeleter();
void convertMeshCustomVertexDataDeleter();
void convertMeshCustomAttributeDataDeleter();
void convertMeshInPlace();
void convertMeshInPlaceNotImplemented();
void convertMeshToData();
void convertMeshToDataNotImplemented();
void convertMeshToDataCustomDeleter();
void convertMeshToFile();
void convertMeshToFileThroughData();
void convertMeshToFileThroughDataNotWritable();
void convertMeshToFileNotImplemented();
void debugFeature();
void debugFeatures();
void debugFlag();
void debugFlags();
};
AbstractSceneConverterTest::AbstractSceneConverterTest() {
addTests({&AbstractSceneConverterTest::featuresNone,
&AbstractSceneConverterTest::setFlags,
&AbstractSceneConverterTest::setFlagsNotImplemented,
&AbstractSceneConverterTest::thingNotSupported,
&AbstractSceneConverterTest::convertMesh,
&AbstractSceneConverterTest::convertMeshNotImplemented,
&AbstractSceneConverterTest::convertMeshNonOwningDeleters,
&AbstractSceneConverterTest::convertMeshGrowableDeleters,
&AbstractSceneConverterTest::convertMeshCustomIndexDataDeleter,
&AbstractSceneConverterTest::convertMeshCustomVertexDataDeleter,
&AbstractSceneConverterTest::convertMeshCustomAttributeDataDeleter,
&AbstractSceneConverterTest::convertMeshInPlace,
&AbstractSceneConverterTest::convertMeshInPlaceNotImplemented,
&AbstractSceneConverterTest::convertMeshToData,
&AbstractSceneConverterTest::convertMeshToDataNotImplemented,
&AbstractSceneConverterTest::convertMeshToDataCustomDeleter,
&AbstractSceneConverterTest::convertMeshToFile,
&AbstractSceneConverterTest::convertMeshToFileThroughData,
&AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable,
&AbstractSceneConverterTest::convertMeshToFileNotImplemented,
&AbstractSceneConverterTest::debugFeature,
&AbstractSceneConverterTest::debugFeatures,
&AbstractSceneConverterTest::debugFlag,
&AbstractSceneConverterTest::debugFlags});
/* Create testing dir */
Utility::Directory::mkpath(TRADE_TEST_OUTPUT_DIR);
}
void AbstractSceneConverterTest::featuresNone() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return {}; }
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.features();
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::features(): implementation reported no features\n");
}
void AbstractSceneConverterTest::setFlags() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override {
/* Assuming this bit is unused */
return SceneConverterFeature(1 << 7);
}
void doSetFlags(SceneConverterFlags flags) override {
_flags = flags;
}
SceneConverterFlags _flags;
} converter;
CORRADE_COMPARE(converter.flags(), SceneConverterFlags{});
CORRADE_COMPARE(converter._flags, SceneConverterFlags{});
converter.setFlags(SceneConverterFlag::Verbose);
CORRADE_COMPARE(converter.flags(), SceneConverterFlag::Verbose);
CORRADE_COMPARE(converter._flags, SceneConverterFlag::Verbose);
}
void AbstractSceneConverterTest::setFlagsNotImplemented() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override {
/* Assuming this bit is unused */
return SceneConverterFeature(1 << 7);
}
} converter;
CORRADE_COMPARE(converter.flags(), SceneConverterFlags{});
converter.setFlags(SceneConverterFlag::Verbose);
CORRADE_COMPARE(converter.flags(), SceneConverterFlag::Verbose);
/* Should just work, no need to implement the function */
}
void AbstractSceneConverterTest::thingNotSupported() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override {
/* Assuming this bit is unused */
return SceneConverterFeature(1 << 7);
}
} converter;
MeshData mesh{MeshPrimitive::Triangles, 3};
std::ostringstream out;
Error redirectError{&out};
converter.convert(mesh);
converter.convertInPlace(mesh);
converter.convertToData(mesh);
converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), mesh);
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");
}
void AbstractSceneConverterTest::convertMesh() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData& mesh) override {
if(mesh.primitive() == MeshPrimitive::Triangles)
return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2};
return {};
}
} converter;
Containers::Optional<MeshData> out = converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_VERIFY(out);
CORRADE_COMPARE(out->primitive(), MeshPrimitive::Lines);
CORRADE_COMPARE(out->vertexCount(), 12);
}
void AbstractSceneConverterTest::convertMeshNotImplemented() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convert(): mesh conversion advertised but not implemented\n");
}
void AbstractSceneConverterTest::convertMeshNonOwningDeleters() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData&) override {
return MeshData{MeshPrimitive::Triangles,
Containers::Array<char>{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData},
Containers::Array<char>{nullptr, 0, Implementation::nonOwnedArrayDeleter},
meshAttributeDataNonOwningArray(attributes)};
}
char indexData[1];
MeshAttributeData attributes[1]{
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}
};
} converter;
Containers::Optional<MeshData> out = converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_VERIFY(out);
CORRADE_COMPARE(static_cast<const void*>(out->indexData()), converter.indexData);
}
void AbstractSceneConverterTest::convertMeshGrowableDeleters() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData&) override {
Containers::Array<char> indexData;
Containers::arrayAppend<ArrayAllocator>(indexData, '\xab');
Containers::Array<Vector3> vertexData;
Containers::arrayAppend<ArrayAllocator>(vertexData, Vector3{});
MeshIndexData indices{MeshIndexType::UnsignedByte, indexData};
MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)};
return MeshData{MeshPrimitive::Triangles,
std::move(indexData), indices,
Containers::arrayAllocatorCast<char, ArrayAllocator>(std::move(vertexData)), {positions}};
}
char indexData[1];
MeshAttributeData attributes[1]{
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}
};
} converter;
Containers::Optional<MeshData> out = converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_VERIFY(out);
CORRADE_COMPARE(out->indexData()[0], '\xab');
CORRADE_COMPARE(out->vertexData().size(), 12);
}
void AbstractSceneConverterTest::convertMeshCustomIndexDataDeleter() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData&) override {
return MeshData{MeshPrimitive::Triangles, Containers::Array<char>{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1};
}
char data[1];
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(),
"Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractSceneConverterTest::convertMeshCustomVertexDataDeleter() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData&) override {
return MeshData{MeshPrimitive::Triangles, Containers::Array<char>{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1};
}
char data[1];
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(),
"Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractSceneConverterTest::convertMeshCustomAttributeDataDeleter() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; }
Containers::Optional<MeshData> doConvert(const MeshData&) override {
return MeshData{MeshPrimitive::Triangles, Containers::Array<char>{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1};
}
char data[1];
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convert(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(),
"Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractSceneConverterTest::convertMeshInPlace() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshInPlace; }
bool doConvertInPlace(MeshData& mesh) override {
auto indices = mesh.mutableIndices<UnsignedInt>();
for(std::size_t i = 0; i != indices.size()/2; ++i)
std::swap(indices[i], indices[indices.size() - i -1]);
return true;
}
} converter;
UnsignedInt indices[]{1, 2, 3, 4, 2, 0};
MeshData mesh{MeshPrimitive::Triangles,
DataFlag::Mutable, indices, MeshIndexData{indices}, 5};
CORRADE_VERIFY(converter.convertInPlace(mesh));
CORRADE_COMPARE_AS(mesh.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({0, 2, 4, 3, 2, 1}),
TestSuite::Compare::Container);
}
void AbstractSceneConverterTest::convertMeshInPlaceNotImplemented() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshInPlace; }
} converter;
MeshData mesh{MeshPrimitive::Triangles, 3};
std::ostringstream out;
Error redirectError{&out};
converter.convertInPlace(mesh);
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion advertised but not implemented\n");
}
void AbstractSceneConverterTest::convertMeshToData() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; }
Containers::Array<char> doConvertToData(const MeshData& mesh) override {
return Containers::Array<char>{nullptr, mesh.vertexCount()};
}
} converter;
Containers::Array<char> data = converter.convertToData(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(data.size(), 6);
}
void AbstractSceneConverterTest::convertMeshToDataNotImplemented() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; }
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convertToData(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToData(): mesh conversion advertised but not implemented\n");
}
void AbstractSceneConverterTest::convertMeshToDataCustomDeleter() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; }
Containers::Array<char> doConvertToData(const MeshData&) override {
return Containers::Array<char>{data, 1, [](char*, std::size_t) {}};
}
char data[1];
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convertToData(MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractSceneConverterTest::convertMeshToFile() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; }
bool doConvertToFile(const std::string& filename, const MeshData& mesh) override {
return Utility::Directory::write(filename, Containers::arrayView( {char(mesh.vertexCount())}));
}
} converter;
/* Remove previous file */
Utility::Directory::rm(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"));
CORRADE_VERIFY(converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 0xef}));
CORRADE_COMPARE_AS(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"),
"\xef", TestSuite::Compare::FileToString);
}
void AbstractSceneConverterTest::convertMeshToFileThroughData() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; }
Containers::Array<char> doConvertToData(const Magnum::Trade::MeshData & mesh) override {
return Containers::array({char(mesh.vertexCount())});
}
} converter;
/* Remove previous file */
Utility::Directory::rm(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"));
CORRADE_VERIFY(converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 0xef}));
CORRADE_COMPARE_AS(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"),
"\xef", TestSuite::Compare::FileToString);
}
void AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; }
Containers::Array<char> doConvertToData(const Magnum::Trade::MeshData & mesh) override {
return Containers::array({char(mesh.vertexCount())});
}
} converter;
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!converter.convertToFile("/some/path/that/does/not/exist", MeshData{MeshPrimitive::Triangles, 0xef}));
CORRADE_COMPARE(out.str(),
"Utility::Directory::write(): can't open /some/path/that/does/not/exist\n"
"Trade::AbstractSceneConverter::convertToFile(): cannot write to file /some/path/that/does/not/exist\n");
}
void AbstractSceneConverterTest::convertMeshToFileNotImplemented() {
struct: AbstractSceneConverter {
SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; }
} converter;
std::ostringstream out;
Error redirectError{&out};
converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 6});
CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented\n");
}
void AbstractSceneConverterTest::debugFeature() {
std::ostringstream out;
Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xf0);
CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xf0)\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::debugFlag() {
std::ostringstream out;
Debug{&out} << SceneConverterFlag::Verbose << SceneConverterFlag(0xf0);
CORRADE_COMPARE(out.str(), "Trade::SceneConverterFlag::Verbose Trade::SceneConverterFlag(0xf0)\n");
}
void AbstractSceneConverterTest::debugFlags() {
std::ostringstream out;
Debug{&out} << (SceneConverterFlag::Verbose|SceneConverterFlag(0xf0)) << SceneConverterFlags{};
CORRADE_COMPARE(out.str(), "Trade::SceneConverterFlag::Verbose|Trade::SceneConverterFlag(0xf0) Trade::SceneConverterFlags{}\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::AbstractSceneConverterTest)

26
src/Magnum/Trade/Test/CMakeLists.txt

@ -42,13 +42,34 @@ corrade_add_test(TradeAbstractImporterTest AbstractImporterTest.cpp
FILES file.bin)
target_include_directories(TradeAbstractImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(TradeAbstractSceneConverterTest AbstractSceneConverterTest.cpp
LIBRARIES MagnumTradeTestLib)
target_include_directories(TradeAbstractSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeMeshDataTest MeshDataTest.cpp
LIBRARIES MagnumTradeTestLib
FILES
mesh-be32.blob
mesh-be64.blob
mesh-le32.blob
mesh-le64.blob
mesh-empty-be32.blob
mesh-empty-be64.blob
mesh-empty-le32.blob
mesh-empty-le64.blob
mesh-nonindexed-be32.blob
mesh-nonindexed-be64.blob
mesh-nonindexed-le32.blob
mesh-nonindexed-le64.blob)
target_include_directories(TradeMeshDataTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeObjectData3DTest ObjectData3DTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTrade)
@ -62,6 +83,7 @@ set_property(TARGET
set_target_properties(
TradeAbstractImageConverterTest
TradeAbstractImporterTest
TradeAbstractSceneConverterTest
TradeAnimationDataTest
TradeCameraDataTest
TradeImageDataTest

214
src/Magnum/Trade/Test/DataTest.cpp

@ -24,8 +24,12 @@
*/
#include <sstream>
#include <Corrade/Containers/Array.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Trade/Data.h"
@ -34,13 +38,207 @@ namespace Magnum { namespace Trade { namespace Test { namespace {
struct DataTest: TestSuite::Tester {
explicit DataTest();
void dataChunkHeaderDeserialize();
void dataChunkHeaderDeserializeInvalid();
void dataChunkHeaderSerialize();
void dataChunkHeaderSerializeTooShort();
void debugDataFlag();
void debugDataFlags();
void debugDataChunkType();
void debugDataChunkSignature();
};
constexpr char Data32[]{
'\x80', '\x0a', '\x0d', '\x0a', 'B',
#ifndef CORRADE_TARGET_BIG_ENDIAN
'l', 'O',
#else
'O', 'l',
#endif
'B', 0, 0,
#ifndef CORRADE_TARGET_BIG_ENDIAN
42, 0,
#else
0, 42,
#endif
'W', 'a', 'v', 'e',
#ifndef CORRADE_TARGET_BIG_ENDIAN
20 + 5, 0, 0, 0,
#else
0, 0, 0, 20 + 5,
#endif
'h', 'e', 'l', 'l', 'o'
};
constexpr char Data64[]{
'\x80', '\x0a', '\x0d', '\x0a', 'B',
#ifndef CORRADE_TARGET_BIG_ENDIAN
'L', 'O',
#else
'O', 'L',
#endif
'B', 0, 0,
#ifndef CORRADE_TARGET_BIG_ENDIAN
42, 0,
#else
0, 42,
#endif
'W', 'a', 'v', 'e',
#ifndef CORRADE_TARGET_BIG_ENDIAN
24 + 5, 0, 0, 0, 0, 0, 0, 0,
#else
0, 0, 0, 0, 0, 0, 0, 24 + 5,
#endif
'h', 'e', 'l', 'l', 'o'
};
constexpr Containers::ArrayView<const char> Data = sizeof(void*) == 4 ?
Containers::arrayView(Data32) : Containers::arrayView(Data64);
const struct {
const char* name;
std::size_t size;
std::size_t offset;
Containers::Array<char> replace;
const char* message;
} DataChunkDeserializeInvalidData[] {
{"too short header",
sizeof(void*) == 4 ? 19 : 23, 0, {},
sizeof(void*) == 4 ?
"expected at least 20 bytes for a header but got 19" :
"expected at least 24 bytes for a header but got 23"},
{"too short chunk",
sizeof(void*) == 4 ? 24 : 28, 0, {},
sizeof(void*) == 4 ?
"expected at least 25 bytes but got 24" :
"expected at least 29 bytes but got 28"},
{"wrong version",
0, 0, Containers::array<char>({'\x7f'}),
"expected version 128 but got 127"},
{"invalid signature",
0, 4,
/* Using the 32-bit signature on 64-bit and vice versa */
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ?
Containers::array<char>({'B', 'L', 'O', 'B'}) :
Containers::array<char>({'B', 'l', 'O', 'B'}),
sizeof(void*) == 4 ?
"expected signature Trade::DataChunkSignature('B', 'l', 'O', 'B') but got Trade::DataChunkSignature('B', 'L', 'O', 'B')" :
"expected signature Trade::DataChunkSignature('B', 'L', 'O', 'B') but got Trade::DataChunkSignature('B', 'l', 'O', 'B')"
#else
sizeof(void*) == 4 ?
Containers::array<char>({'B', 'O', 'L', 'B'}) :
Containers::array<char>({'B', 'O', 'l', 'B'}),
sizeof(void*) == 4 ?
"expected signature Trade::DataChunkSignature('B', 'O', 'l', 'B') but got Trade::DataChunkSignature('B', 'O', 'L', 'B')" :
"expected signature Trade::DataChunkSignature('B', 'O', 'L', 'B') but got Trade::DataChunkSignature('B', 'O', 'l', 'B')"
#endif
},
{"invalid check bytes",
0, 8, Containers::array<char>({1, 0}),
"invalid header check bytes"},
};
constexpr struct {
const char* name;
std::size_t size;
} DataChunkSerializeData[] {
{"no extra data", sizeof(DataChunkHeader)},
{"1735 bytes extra data", sizeof(DataChunkHeader) + 1735}
};
DataTest::DataTest() {
addTests({&DataTest::dataChunkHeaderDeserialize});
addInstancedTests({&DataTest::dataChunkHeaderDeserializeInvalid},
Containers::arraySize(DataChunkDeserializeInvalidData));
addInstancedTests({&DataTest::dataChunkHeaderSerialize},
Containers::arraySize(DataChunkSerializeData));
addTests({&DataTest::dataChunkHeaderSerializeTooShort});
addTests({&DataTest::debugDataFlag,
&DataTest::debugDataFlags});
&DataTest::debugDataFlags,
&DataTest::debugDataChunkType,
&DataTest::debugDataChunkSignature});
}
void DataTest::dataChunkHeaderDeserialize() {
CORRADE_VERIFY(isDataChunk(Data));
const DataChunkHeader* chunk = Trade::dataChunkHeaderDeserialize(Data);
CORRADE_VERIFY(chunk);
}
void DataTest::dataChunkHeaderDeserializeInvalid() {
auto&& data = DataChunkDeserializeInvalidData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> blob{Containers::NoInit, Data.size()};
Utility::copy(Data, blob);
Containers::ArrayView<char> view = blob;
if(data.size) view = view.prefix(data.size);
if(data.replace) Utility::copy(data.replace, view.slice(data.offset, data.offset + data.replace.size()));
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!isDataChunk(view));
CORRADE_VERIFY(!Trade::dataChunkHeaderDeserialize(view));
CORRADE_COMPARE(out.str(),
Utility::formatString("Trade::dataChunkHeaderDeserialize(): {}\n", data.message));
}
void DataTest::dataChunkHeaderSerialize() {
auto&& data = DataChunkSerializeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> out{Containers::NoInit, data.size};
std::size_t size = dataChunkHeaderSerializeInto(out, DataChunkType(Utility::Endianness::fourCC('r', 't', 'F', 'm')), 0xfeed);
CORRADE_COMPARE(size, sizeof(DataChunkHeader));
#ifndef CORRADE_TARGET_BIG_ENDIAN
if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'l', 'O', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0,
}), TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'L', 'O', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0, 0, 0, 0, 0
}), TestSuite::Compare::Container);
#else
if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'l', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff)
}), TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(out.prefix(size),
Containers::arrayView<char>({
'\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'L', 'B', 0, 0,
'\xed', '\xfe', 'r', 't', 'F', 'm',
0, 0, 0, 0, 0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff)
}), TestSuite::Compare::Container);
#endif
}
void DataTest::dataChunkHeaderSerializeTooShort() {
std::ostringstream out;
Error redirectError{&out};
char data[sizeof(DataChunkHeader) - 1];
dataChunkHeaderSerializeInto(data, DataChunkType{}, 0);
CORRADE_COMPARE(out.str(), sizeof(void*) == 4 ?
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 20 bytes but got 19\n" :
"Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 24 bytes but got 23\n");
}
void DataTest::debugDataFlag() {
@ -57,6 +255,20 @@ void DataTest::debugDataFlags() {
CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned|Trade::DataFlag::Mutable Trade::DataFlags{}\n");
}
void DataTest::debugDataChunkType() {
std::ostringstream out;
Debug{&out} << DataChunkType(Utility::Endianness::fourCC('M', 's', 'h', '\xab')) << DataChunkType{};
CORRADE_COMPARE(out.str(), "Trade::DataChunkType('M', 's', 'h', 0xab) Trade::DataChunkType(0x0, 0x0, 0x0, 0x0)\n");
}
void DataTest::debugDataChunkSignature() {
std::ostringstream out;
Debug{&out} << DataChunkSignature::Little64 << DataChunkSignature{};
CORRADE_COMPARE(out.str(), "Trade::DataChunkSignature('B', 'L', 'O', 'B') Trade::DataChunkSignature(0x0, 0x0, 0x0, 0x0)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::DataTest)

293
src/Magnum/Trade/Test/MeshDataTest.cpp

@ -26,12 +26,18 @@
#include <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/TestSuite/Compare/StringToFile.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Half.h"
#include "Magnum/Trade/MeshData.h"
#include "configure.h"
namespace Magnum { namespace Trade { namespace Test { namespace {
struct MeshDataTest: TestSuite::Tester {
@ -164,6 +170,13 @@ struct MeshDataTest: TestSuite::Tester {
void releaseIndexData();
void releaseAttributeData();
void releaseVertexData();
void serialize();
void serializeEmpty();
void serializeIntoTooSmall();
void deserialize();
void deserializeInvalid();
};
const struct {
@ -194,6 +207,87 @@ const struct {
{"mutable", DataFlag::Mutable}
};
const struct {
const char* name;
const char* filePrefix;
bool indexed;
} SerializeData[] {
{"", "mesh", true},
{"non-indexed", "mesh-nonindexed", false}
};
const struct {
const char* name;
std::size_t size;
std::size_t offset;
Containers::Array<char> replace;
const char* message;
} DeserializeInvalidData[] {
/* This checks we correctly propagate chunk header errors, the rest is
verified in DataTest */
{"too short to contain a chunk header",
sizeof(void*) == 4 ? 19 : 23, 0, nullptr,
sizeof(void*) == 4 ?
"dataChunkHeaderDeserialize(): expected at least 20 bytes for a header but got 19" :
"dataChunkHeaderDeserialize(): expected at least 24 bytes for a header but got 23"},
{"chunk too short to contain a meshdata header",
0, 16, /* not cutting the file, only adapting header */
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ? Containers::array<char>({0x2f, 0, 0, 0}) :
Containers::array<char>({0x3f, 0, 0, 0, 0, 0, 0, 0}),
#else
sizeof(void*) == 4 ? Containers::array<char>({0, 0, 0, 0x2f}) :
Containers::array<char>({0, 0, 0, 0, 0, 0, 0, 0x3f}),
#endif
sizeof(void*) == 4 ?
"MeshData::deserialize(): expected at least a 48-byte chunk for a header but got 47" :
"MeshData::deserialize(): expected at least a 64-byte chunk for a header but got 63"},
{"chunk too short to contain all data",
0, 16, /* not cutting the file, only adapting header */
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ? Containers::array<char>({'\xd3', 0, 0, 0}) :
Containers::array<char>({'\xf3', 0, 0, 0, 0, 0, 0, 0}),
#else
sizeof(void*) == 4 ? Containers::array<char>({0, 0, 0, '\xd3'}) :
Containers::array<char>({0, 0, 0, 0, 0, 0, 0, '\xf3'}),
#endif
sizeof(void*) == 4 ?
"MeshData::deserialize(): expected a 212-byte chunk but got 211" :
"MeshData::deserialize(): expected a 244-byte chunk but got 243"},
{"invalid type",
0, 12, Containers::array({'M', 'e', 'h', 'h'}),
"MeshData::deserialize(): expected data chunk type Trade::DataChunkType('M', 'e', 's', 'h') but got Trade::DataChunkType('M', 'e', 'h', 'h')"},
{"invalid type version",
0, 10,
#ifndef CORRADE_TARGET_BIG_ENDIAN
Containers::array<char>({1, 0}),
#else
Containers::array<char>({0, 1}),
#endif
"MeshData::deserialize(): invalid chunk type version, expected 0 but got 1"},
{"index array out of bounds",
0, sizeof(void*) == 4 ? 36 : 40,
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ? Containers::array<char>({5, 0, 0, 0}) :
Containers::array<char>({5, 0, 0, 0, 0, 0, 0, 0}),
#else
sizeof(void*) == 4 ? Containers::array<char>({0, 0, 0, 5}) :
Containers::array<char>({0, 0, 0, 0, 0, 0, 0, 5}),
#endif
"MeshData::deserialize(): indices [5:13] out of range for 12 bytes of index data"},
{"attribute out of bounds",
0, sizeof(void*) == 4 ? 48 + 20 + 16 : 64 + 24 + 16,
#ifndef CORRADE_TARGET_BIG_ENDIAN
sizeof(void*) == 4 ? Containers::array<char>({23, 0, 0, 0}) :
Containers::array<char>({23, 0, 0, 0, 0, 0, 0, 0}),
#else
sizeof(void*) == 4 ? Containers::array<char>({0, 0, 0, 23}) :
Containers::array<char>({0, 0, 0, 0, 0, 0, 0, 23}),
#endif
"MeshData::deserialize(): attribute 1 [23:73] out of range for 72 bytes of vertex data"}
};
MeshDataTest::MeshDataTest() {
addTests({&MeshDataTest::customAttributeName,
&MeshDataTest::customAttributeNameTooLarge,
@ -382,6 +476,18 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::releaseIndexData,
&MeshDataTest::releaseAttributeData,
&MeshDataTest::releaseVertexData});
addInstancedTests({&MeshDataTest::serialize},
Containers::arraySize(SerializeData));
addTests({&MeshDataTest::serializeEmpty,
&MeshDataTest::serializeIntoTooSmall});
addInstancedTests({&MeshDataTest::deserialize},
Containers::arraySize(SerializeData));
addInstancedTests({&MeshDataTest::deserializeInvalid},
Containers::arraySize(DeserializeInvalidData));
}
void MeshDataTest::customAttributeName() {
@ -2963,6 +3069,193 @@ void MeshDataTest::releaseVertexData() {
CORRADE_COMPARE(data.attributeOffset(0), 48);
}
constexpr char BlobFileSuffix[] {
'-',
#ifndef CORRADE_TARGET_BIG_ENDIAN
'l',
#else
'b',
#endif
'e', sizeof(void*) == 4 ? '3' : '6', sizeof(void*) == 4 ? '2' : '4',
'.', 'b', 'l', 'o', 'b', '\0'
};
void MeshDataTest::serialize() {
auto&& data = SerializeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
/* Clang on iOS and Android doesn't like constexpr here */
constexpr struct Vertex {
Vector2 position;
Vector2ub textureCoordinates;
UnsignedShort props[2];
/* I'd use UnsignedShort:16 here but (at least on Android) the bytes
get random values, breaking the test. On iOS and Android that would
also make the compiler complain about constexpr, and finally MSVC
2015 chokes on the : if this is an inline struct. */
UnsignedShort _padding;
Double weight;
} vertexData[] {
{{1.0f, 0.5f}, {23, 15}, {3247, 1256}, 0, 1.1},
{{2.0f, 1.5f}, {232, 144}, {6243, 1241}, 0, 1.2},
{{3.0f, 2.5f}, {17, 242}, {15, 2323}, 0, 1.3}
};
constexpr UnsignedShort indexData[] {
2555, 3241, 1, 0, 1, 0
};
Containers::ArrayView<const void> indexView;
MeshIndexData indices;
if(data.indexed) {
indexView = indexData;
indices = MeshIndexData{Containers::arrayView(indexData).suffix(2)};
}
MeshData meshData{MeshPrimitive::TriangleFan,
{}, indexView, indices,
{}, vertexData, {
/* Test all attribute type sizes (2, 4, 8) for endian swapping in
the MagnumImporter / MagnumSceneConverter plugins */
MeshAttributeData{MeshAttribute::Position,
Containers::StridedArrayView1D<const Vector2>{vertexData, &vertexData[0].position, 3, sizeof(Vertex)}},
MeshAttributeData{MeshAttribute::TextureCoordinates,
Containers::StridedArrayView1D<const Vector2ub>{vertexData, &vertexData[0].textureCoordinates, 3, sizeof(Vertex)}},
/* Test array attribs */
MeshAttributeData{meshAttributeCustom(23),
VertexFormat::UnsignedShort, 2,
Containers::StridedArrayView1D<const UnsignedShort>{vertexData, &vertexData[0].props[0], 3, sizeof(Vertex)}},
/* Test offset-only attribs as well */
MeshAttributeData{meshAttributeCustom(14), VertexFormat::Double,
16, 3, sizeof(Vertex)}
}};
Containers::Array<char> blob = meshData.serialize();
CORRADE_COMPARE_AS((std::string{blob.data(), blob.size()}),
Utility::Directory::join(TRADE_TEST_DIR, std::string{data.filePrefix} + BlobFileSuffix),
TestSuite::Compare::StringToFile);
}
void MeshDataTest::serializeEmpty() {
MeshData meshData{MeshPrimitive::Edges, 1256};
Containers::Array<char> blob = meshData.serialize();
CORRADE_COMPARE_AS((std::string{blob.data(), blob.size()}),
Utility::Directory::join(TRADE_TEST_DIR, std::string{"mesh-empty"} + BlobFileSuffix),
TestSuite::Compare::StringToFile);
}
void MeshDataTest::serializeIntoTooSmall() {
constexpr UnsignedInt indexData[]{0, 1, 0};
MeshData meshData{MeshPrimitive::Faces,
{}, indexData, MeshIndexData{indexData}, 2};
std::ostringstream out;
Error redirectError{&out};
char blob[sizeof(void*) == 4 ? 59 : 75];
meshData.serializeInto(blob);
if(sizeof(void*) == 4) CORRADE_COMPARE(out.str(),
"Trade::MeshData::serializeInto(): data too small, expected at least 60 bytes but got 59\n");
else CORRADE_COMPARE(out.str(),
"Trade::MeshData::serializeInto(): data too small, expected at least 76 bytes but got 75\n");
}
void MeshDataTest::deserialize() {
auto&& data = SerializeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> blob = Utility::Directory::read(Utility::Directory::join(TRADE_TEST_DIR, std::string{data.filePrefix} + BlobFileSuffix));
Containers::Optional<MeshData> meshData = MeshData::deserialize(blob);
CORRADE_VERIFY(meshData);
CORRADE_COMPARE(meshData->attributeCount(), 4);
CORRADE_COMPARE(meshData->vertexCount(), 3);
CORRADE_COMPARE(meshData->indexDataFlags(), DataFlag::Mutable);
CORRADE_COMPARE(meshData->vertexDataFlags(), DataFlag::Mutable);
CORRADE_COMPARE(meshData->attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(meshData->attributeFormat(0), VertexFormat::Vector2);
CORRADE_COMPARE(meshData->attributeOffset(0), 0);
CORRADE_COMPARE(meshData->attributeStride(0), 24);
CORRADE_COMPARE(meshData->attributeArraySize(0), 0);
CORRADE_COMPARE_AS(meshData->attribute<Vector2>(0),
Containers::arrayView<Vector2>({
{1.0f, 0.5f}, {2.0f, 1.5f}, {3.0f, 2.5f}
}), TestSuite::Compare::Container);
CORRADE_COMPARE(meshData->attributeName(1), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(meshData->attributeFormat(1), VertexFormat::Vector2ub);
CORRADE_COMPARE(meshData->attributeOffset(1), 8);
CORRADE_COMPARE(meshData->attributeStride(1), 24);
CORRADE_COMPARE(meshData->attributeArraySize(1), 0);
CORRADE_COMPARE_AS(meshData->attribute<Vector2ub>(1),
Containers::arrayView<Vector2ub>({
{23, 15}, {232, 144}, {17, 242}
}), TestSuite::Compare::Container);
CORRADE_COMPARE(meshData->attributeName(2), meshAttributeCustom(23));
CORRADE_COMPARE(meshData->attributeFormat(2), VertexFormat::UnsignedShort);
CORRADE_COMPARE(meshData->attributeOffset(2), 10);
CORRADE_COMPARE(meshData->attributeStride(2), 24);
CORRADE_COMPARE(meshData->attributeArraySize(2), 2);
CORRADE_COMPARE_AS((meshData->attribute<UnsignedShort[]>(2).transposed<0, 1>()[0]),
Containers::arrayView<UnsignedShort>({3247, 6243, 15}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS((meshData->attribute<UnsignedShort[]>(2).transposed<0, 1>()[1]),
Containers::arrayView<UnsignedShort>({1256, 1241, 2323}), TestSuite::Compare::Container);
CORRADE_COMPARE(meshData->attributeName(3), meshAttributeCustom(14));
CORRADE_COMPARE(meshData->attributeFormat(3), VertexFormat::Double);
CORRADE_COMPARE(meshData->attributeOffset(3), 16);
CORRADE_COMPARE(meshData->attributeStride(3), 24);
CORRADE_COMPARE(meshData->attributeArraySize(3), 0);
CORRADE_COMPARE_AS(meshData->attribute<Double>(3),
Containers::arrayView<Double>({
1.1, 1.2, 1.3
}), TestSuite::Compare::Container);
if(data.indexed) {
CORRADE_VERIFY(meshData->isIndexed());
CORRADE_COMPARE(meshData->indexCount(), 4);
CORRADE_COMPARE(meshData->indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(meshData->indexOffset(), 4);
CORRADE_COMPARE_AS(meshData->indices<UnsignedShort>(),
Containers::arrayView<UnsignedShort>({1, 0, 1, 0}),
TestSuite::Compare::Container);
} else CORRADE_VERIFY(!meshData->isIndexed());
/* Constant data should not have mutable flags set. Test just basics
otherwise, as all this should be mostly handled by the same code. */
meshData = MeshData::deserialize(Containers::arrayView<const char>(blob));
CORRADE_VERIFY(meshData);
CORRADE_COMPARE(meshData->attributeCount(), 4);
CORRADE_COMPARE(meshData->vertexCount(), 3);
CORRADE_COMPARE(meshData->indexDataFlags(), DataFlags{});
CORRADE_COMPARE(meshData->vertexDataFlags(), DataFlags{});
if(data.indexed) {
CORRADE_VERIFY(meshData->isIndexed());
CORRADE_COMPARE(meshData->indexCount(), 4);
}
}
void MeshDataTest::deserializeInvalid() {
auto&& data = DeserializeInvalidData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> blob = Utility::Directory::read(Utility::Directory::join(TRADE_TEST_DIR, std::string{"mesh"} + BlobFileSuffix));
CORRADE_VERIFY(blob);
Containers::ArrayView<char> view = blob;
if(data.size) view = view.prefix(data.size);
if(data.replace) Utility::copy(data.replace, view.slice(data.offset, data.offset + data.replace.size()));
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!MeshData::deserialize(view));
CORRADE_COMPARE(out.str(),
Utility::formatString("Trade::{}\n", data.message));
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshDataTest)

BIN
src/Magnum/Trade/Test/mesh-be32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-be64.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-empty-be32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-empty-be64.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-empty-le32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-empty-le64.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-le32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-le64.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-nonindexed-be32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-nonindexed-be64.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-nonindexed-le32.blob

Binary file not shown.

BIN
src/Magnum/Trade/Test/mesh-nonindexed-le64.blob

Binary file not shown.

5
src/Magnum/Trade/Trade.h

@ -42,6 +42,7 @@ namespace Magnum { namespace Trade {
#ifndef DOXYGEN_GENERATING_OUTPUT
class AbstractImageConverter;
class AbstractImporter;
class AbstractSceneConverter;
#ifdef MAGNUM_BUILD_DEPRECATED
typedef CORRADE_DEPRECATED("use InputFileCallbackPolicy instead") InputFileCallbackPolicy ImporterFileCallbackPolicy;
@ -61,6 +62,10 @@ class CameraData;
enum class DataFlag: UnsignedByte;
typedef Containers::EnumSet<DataFlag> DataFlags;
struct DataChunkHeader;
class DataChunk;
enum class DataChunkSignature: UnsignedInt;
enum class DataChunkType: UnsignedInt;
template<UnsignedInt> class ImageData;
typedef ImageData<1> ImageData1D;

2
src/Magnum/Trade/configure.h.cmake

@ -27,3 +27,5 @@
#define MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR "${MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR}"
#define MAGNUM_PLUGINS_IMAGECONVERTER_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DIR}"
#define MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR}"
#define MAGNUM_PLUGINS_SCENECONVERTER_DIR "${MAGNUM_PLUGINS_SCENECONVERTER_DIR}"
#define MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR "${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR}"

5
src/Magnum/Trade/imageconverter.cpp

@ -99,7 +99,8 @@ need to be specified.
The `-i` / `--importer-options` and `-c` / `--converter-options` arguments
accept a comma-separated list of key/value pairs to set in the importer /
converter plugin configuration. If the `=` character is omitted, it's
equivalent to saying `key=true`.
equivalent to saying `key=true`; configuration subgroups are delimited with
`/`.
@section magnum-imageconverter-example Example usage
@ -168,7 +169,7 @@ be specified.
The -i / --importer-options and -c / --converter-options arguments accept a
comma-separated list of key/value pairs to set in the importer / converter
plugin configuration. If the = character is omitted, it's equivalent to saying
key=true.)")
key=true; configuration subgroups are delimited with /.)")
.parse(argc, argv);
PluginManager::Manager<Trade::AbstractImporter> importerManager{

210
src/Magnum/Trade/sceneconverter.cpp

@ -24,6 +24,7 @@
*/
#include <algorithm>
#include <chrono>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Arguments.h>
#include <Corrade/Utility/DebugStl.h>
@ -34,6 +35,7 @@
#include "Magnum/PixelFormat.h"
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/MeshData.h"
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "Magnum/Trade/Implementation/converterUtilities.h"
namespace Magnum {
@ -62,78 +64,162 @@ information.
@section magnum-sceneconverter-usage Usage
@code{.sh}
magnum-sceneconverter [-h|--help] [--importer IMPORTER] [--plugin-dir DIR]
[-i|--importer-options key=val,key2=val2,] [--info] [-v|--verbose] [--]
input
magnum-sceneconverter [-h|--help] [--importer IMPORTER] [--converter CONVERTER]
[--plugin-dir DIR] [-i|--importer-options key=val,key2=val2,]
[-c|--converter-options key=val,key2=val2,] [--info] [-v|--verbose]
[--profile] [--] input output
@endcode
Arguments:
- `input` --- input file
- `output` --- output file
- `-h`, `--help` --- display this help message and exit
- `--importer IMPORTER` --- scene importer plugin (default:
@ref Trade::AnySceneImporter "AnySceneImporter")
- `--converter CONVERTER` --- scene converter plugin (default:
@ref Trade::AnyImageConverter "AnySceneConverter")
- `--plugin-dir DIR` --- override base plugin dir
- `-i`, `--importer-options key=val,key2=val2,` --- configuration options to
pass to the importer
- `-c`, `--converter-options key=val,key2=val2,` --- configuration options
to pass to the converter
- `--info` --- print info about the input file and exit
- `-v`, `--verbose` --- verbose output from importer plugins
- `--profile` --- measure import and conversion time
If `--info` is given, the utility will print information about all meshes
and images present in the file. **This option is currently mandatory.**
The `-i` / `--importer-options` argument accepts a comma-separated list of
key/value pairs to set in the importer plugin configuration. If the `=`
character is omitted, it's equivalent to saying `key=true`.
The `-i` / `--importer-options` and `-c` / `--converter-options` arguments
accept a comma-separated list of key/value pairs to set in the importer /
converter plugin configuration. If the `=` character is omitted, it's
equivalent to saying `key=true`; configuration subgroups are delimited with
`/`.
@see @ref magnum-imageconverter
*/
}
using namespace Magnum;
namespace {
struct Duration {
explicit Duration(std::chrono::high_resolution_clock::duration& output): _output(output), _t{std::chrono::high_resolution_clock::now()} {}
~Duration() {
_output += std::chrono::high_resolution_clock::now() - _t;
}
private:
std::chrono::high_resolution_clock::duration& _output;
std::chrono::high_resolution_clock::time_point _t;
};
/* Direct shims for fast deserialization / serialization of blob data. Compared
to MagnumImporter / MagnumSceneConverter these don't make the whole file
resident in memory, so *much* faster. */
class BlobImporter: public Trade::AbstractImporter {
Trade::ImporterFeatures doFeatures() const override { return {}; }
bool doIsOpened() const override { return _in; }
void doClose() override { _in = nullptr; }
void doOpenFile(const std::string& filename) override {
_in = Utility::Directory::mapRead(filename);
}
UnsignedInt doMeshCount() const override { return 1; }
Containers::Optional<Trade::MeshData> doMesh(UnsignedInt, UnsignedInt) override {
/* GCC 4.8 and old Clang has problems with an implicit cast here */
return Trade::MeshData::deserialize(Containers::ArrayView<const void>(_in));
}
Containers::Array<const char, Utility::Directory::MapDeleter> _in;
};
class BlobSceneConverter: public Trade::AbstractSceneConverter {
Trade::SceneConverterFeatures doFeatures() const override {
return Trade::SceneConverterFeature::ConvertMeshToFile;
}
bool doConvertToFile(const std::string& filename, const Magnum::Trade::MeshData& mesh) override {
Containers::Array<char, Utility::Directory::MapDeleter> out = Utility::Directory::mapWrite(filename, mesh.serializedSize());
if(!out) return false;
mesh.serializeInto(out);
return true;
}
};
}
int main(int argc, char** argv) {
Utility::Arguments args;
args.addArgument("input").setHelp("input", "input file")
.addArgument("output").setHelp("output", "output file")
.addOption("importer", "AnySceneImporter").setHelp("importer", "scene importer plugin")
.addOption("converter", "AnySceneConverter").setHelp("converter", "scene converter plugin")
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR")
.addOption('i', "importer-options").setHelp("importer-options", "configuration options to pass to the importer", "key=val,key2=val2,…")
.addOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter", "key=val,key2=val2,…")
.addBooleanOption("info").setHelp("info", "print info about the input file and exit")
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer plugins")
/** @todo add the parse error callback from imageconverter once there's
an output argument, also remove the "mandatory" from all docs */
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from importer and converter plugins")
.addBooleanOption("profile").setHelp("profile", "measure import and conversion time")
.setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) {
/* If --info is passed, we don't need the output argument */
if(error == Utility::Arguments::ParseError::MissingArgument &&
key == "output" && args.isSet("info")) return true;
/* Handle all other errors as usual */
return false;
})
.setGlobalHelp(R"(Converts scenes of different formats.
If --info is given, the utility will print information about all meshes and
images present in the file. This option is currently mandatory.
images present in the file.
The -i / --importer-options argument accepts a comma-separated list of
key/value pairs to set in the importer plugin configuration. If the = character
is omitted, it's equivalent to saying key=true.)")
The -i / --importer-options and -c / --converter-options arguments accept a
comma-separated list of key/value pairs to set in the importer / converter
plugin configuration. If the = character is omitted, it's equivalent to saying
key=true; configuration subgroups are delimited with /.)")
.parse(argc, argv);
PluginManager::Manager<Trade::AbstractImporter> importerManager{
/* Load importer plugin, or use the blob shim in case the extension
matches and we're not overriding the converter to something specific */
Containers::Optional<PluginManager::Manager<Trade::AbstractImporter>> importerManager;
Containers::Pointer<Trade::AbstractImporter> importer;
if(Utility::String::endsWith(args.value("input"), ".blob") && args.value("importer") == "AnySceneImporter") {
importer.reset(new BlobImporter);
if(!args.value("importer-options").empty())
Warning{} << "Importer options" << args.value("importer-options") << "ignored when loading a blob file";
} else {
importerManager.emplace(
args.value("plugin-dir").empty() ? std::string{} :
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])};
Containers::Pointer<Trade::AbstractImporter> importer = importerManager.loadAndInstantiate(args.value("importer"));
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0]));
importer = importerManager->loadAndInstantiate(args.value("importer"));
if(!importer) {
Debug{} << "Available importer plugins:" << Utility::String::join(importerManager.aliasList(), ", ");
Debug{} << "Available importer plugins:" << Utility::String::join(importerManager->aliasList(), ", ");
return 1;
}
/* Set options, if passed */
if(args.isSet("verbose")) importer->setFlags(Trade::ImporterFlag::Verbose);
Trade::Implementation::setOptions(*importer, args.value("importer-options"));
}
/* Print file info, if requested */
if(args.isSet("info")) {
/* Open the file, but don't fail when an image can't be opened */
std::chrono::high_resolution_clock::duration importTime;
/* Open the file */
{
Duration d{importTime};
if(!importer->openFile(args.value("input"))) {
Error() << "Cannot open file" << args.value("input");
return 3;
}
}
/* Print file info, if requested */
if(args.isSet("info")) {
if(!importer->meshCount() && !importer->image1DCount() && !importer->image2DCount() && !importer->image2DCount()) {
Debug{} << "No meshes or images found.";
return 0;
@ -162,11 +248,14 @@ is omitted, it's equivalent to saying key=true.)")
Containers::Array<MeshInfo> meshInfos;
for(UnsignedInt i = 0; i != importer->meshCount(); ++i) {
for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) {
Containers::Optional<Trade::MeshData> mesh = importer->mesh(i, j);
if(!mesh) {
Containers::Optional<Trade::MeshData> mesh;
{
Duration d{importTime};
if(!(mesh = importer->mesh(i, j))) {
error = true;
continue;
}
}
MeshInfo info{};
info.mesh = i;
@ -248,9 +337,80 @@ is omitted, it's equivalent to saying key=true.)")
else d << Math::Vector<1, Int>(info.size.x());
}
if(args.isSet("profile")) {
Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast<std::chrono::milliseconds>(importTime).count())/1.0e3f << "seconds";
}
return error ? 1 : 0;
}
Error{} << "Sorry, only the --info option is currently implemented";
Containers::Optional<Trade::MeshData> mesh;
{
Duration d{importTime};
if(!importer->meshCount() || !(mesh = importer->mesh(0))) {
Error{} << "Cannot import mesh 0";
return 4;
}
}
/* Load converter plugin, or use the blob shim in case the extension
matches and we're not overriding the converter to something specific */
Containers::Optional<PluginManager::Manager<Trade::AbstractSceneConverter>> converterManager;
Containers::Pointer<Trade::AbstractSceneConverter> converter;
if(Utility::String::endsWith(args.value("output"), ".blob") && args.value("converter") == "AnySceneConverter") {
converter.reset(new BlobSceneConverter);
if(!args.value("converter-options").empty())
Warning{} << "Converter options" << args.value("converter-options") << "ignored when writing a blob file";
} else {
converterManager.emplace(
args.value("plugin-dir").empty() ? std::string{} :
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractSceneConverter::pluginSearchPaths()[0]));
converter = converterManager->loadAndInstantiate(args.value("converter"));
if(!converter) {
Debug{} << "Available converter plugins:" << Utility::String::join(converterManager->aliasList(), ", ");
return 2;
}
if(args.isSet("verbose")) converter->setFlags(Trade::SceneConverterFlag::Verbose);
Trade::Implementation::setOptions(*converter, args.value("converter-options"));
}
std::chrono::high_resolution_clock::duration conversionTime;
/* Save output file directly, if it supports that */
if(converter->features() & Trade::SceneConverterFeature::ConvertMeshToFile) {
Duration d{conversionTime};
if(!converter->convertToFile(args.value("output"), *mesh)) {
Error{} << "Cannot save file" << args.value("output");
return 5;
}
/* Otherwise convert the meshdata and then save as a blob */
} else if(converter->features() & (Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshInPlace)) {
if(converter->features() & Trade::SceneConverterFeature::ConvertMesh) {
Duration d{conversionTime};
if(!(mesh = converter->convert(*mesh))) {
Error{} << "Cannot convert the mesh";
return 5;
}
} else if(converter->features() & Trade::SceneConverterFeature::ConvertMeshInPlace) {
Duration d{conversionTime};
if(!converter->convertInPlace(*mesh)) {
Error{} << "Cannot convert the mesh in-place";
return 5;
}
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
Containers::Array<char, Utility::Directory::MapDeleter> out = Utility::Directory::mapWrite(args.value("output"), mesh->serializedSize());
if(!out) {
Error{} << "Cannot save file" << args.value("output");
return 6;
}
mesh->serializeInto(out);
}
if(args.isSet("profile")) {
Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast<std::chrono::milliseconds>(importTime).count())/1.0e3f << "seconds, conversion"
<< UnsignedInt(std::chrono::duration_cast<std::chrono::milliseconds>(conversionTime).count())/1.0e3f << "seconds";
}
}

0
src/MagnumPlugins/AnySceneConverter/AnySceneConverter.conf

80
src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp

@ -0,0 +1,80 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 "AnySceneConverter.h"
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/Assert.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/String.h>
#include "Magnum/Trade/ImageData.h"
namespace Magnum { namespace Trade {
AnySceneConverter::AnySceneConverter(PluginManager::Manager<AbstractSceneConverter>& manager): AbstractSceneConverter{manager} {}
AnySceneConverter::AnySceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractSceneConverter{manager, plugin} {}
AnySceneConverter::~AnySceneConverter() = default;
SceneConverterFeatures AnySceneConverter::doFeatures() const {
return SceneConverterFeature::ConvertMeshToFile;
}
bool AnySceneConverter::doConvertToFile(const std::string& filename, const MeshData& mesh) {
CORRADE_INTERNAL_ASSERT(manager());
/** @todo lowercase only the extension, once Directory::split() is done */
const std::string normalized = Utility::String::lowercase(filename);
/* Detect the plugin from extension */
std::string plugin;
if(Utility::String::endsWith(normalized, ".blob"))
plugin = "MagnumSceneConverter";
else {
Error{} << "Trade::AnySceneConverter::convertToFile(): cannot determine the format of" << filename;
return false;
}
/* Try to load the plugin */
if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) {
Error{} << "Trade::AnySceneConverter::convertToFile(): cannot load the" << plugin << "plugin";
return false;
}
/* Instantiate the plugin, propagate flags */
Containers::Pointer<AbstractSceneConverter> converter = static_cast<PluginManager::Manager<AbstractSceneConverter>*>(manager())->instantiate(plugin);
converter->setFlags(flags());
/* Try to convert the file (error output should be printed by the plugin
itself) */
return converter->convertToFile(filename, mesh);
}
}}
CORRADE_PLUGIN_REGISTER(AnySceneConverter, Magnum::Trade::AnySceneConverter,
"cz.mosra.magnum.Trade.AbstractSceneConverter/0.1")

111
src/MagnumPlugins/AnySceneConverter/AnySceneConverter.h

@ -0,0 +1,111 @@
#ifndef Magnum_Trade_AnySceneConverter_h
#define Magnum_Trade_AnySceneConverter_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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::Trade::AnySceneConverter
*/
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "MagnumPlugins/AnySceneConverter/configure.h"
#ifndef DOXYGEN_GENERATING_OUTPUT
#ifndef MAGNUM_ANYSCENECONVERTER_BUILD_STATIC
#ifdef AnySceneConverter_EXPORTS
#define MAGNUM_ANYSCENECONVERTER_EXPORT CORRADE_VISIBILITY_EXPORT
#else
#define MAGNUM_ANYSCENECONVERTER_EXPORT CORRADE_VISIBILITY_IMPORT
#endif
#else
#define MAGNUM_ANYSCENECONVERTER_EXPORT CORRADE_VISIBILITY_STATIC
#endif
#define MAGNUM_ANYSCENECONVERTER_LOCAL CORRADE_VISIBILITY_LOCAL
#else
#define MAGNUM_ANYSCENECONVERTER_EXPORT
#define MAGNUM_ANYSCENECONVERTER_LOCAL
#endif
namespace Magnum { namespace Trade {
/**
@brief Any scene converter plugin
Detects file type based on file extension, loads corresponding plugin and then
tries to convert the file with it. Supported formats:
- @ref blob "Magnum's memory-mappable serialization format" (`*.blob`),
converted with @ref MagnumSceneConverter
Only converting to files is supported.
@section Trade-AnySceneConverter-usage Usage
This plugin depends on the @ref Trade library and is built if
`WITH_ANYSCENECONVERTER` is enabled when building Magnum. To use as a dynamic
plugin, load @cpp "AnySceneConverter" @ce via
@ref Corrade::PluginManager::Manager.
Additionally, if you're using Magnum as a CMake subproject, do the following:
@code{.cmake}
set(WITH_ANYSCENECONVERTER ON CACHE BOOL "" FORCE)
add_subdirectory(magnum EXCLUDE_FROM_ALL)
# So the dynamically loaded plugin gets built implicitly
add_dependencies(your-app Magnum::AnySceneConverter)
@endcode
To use as a static plugin or as a dependency of another plugin with CMake, you
need to request the `AnySceneConverter` component of the `Magnum` package and
link to the `Magnum::AnySceneConverter` target:
@code{.cmake}
find_package(Magnum REQUIRED AnySceneConverter)
# ...
target_link_libraries(your-app PRIVATE Magnum::AnySceneConverter)
@endcode
See @ref building, @ref cmake and @ref plugins for more information.
*/
class MAGNUM_ANYSCENECONVERTER_EXPORT AnySceneConverter: public AbstractSceneConverter {
public:
/** @brief Constructor with access to plugin manager */
explicit AnySceneConverter(PluginManager::Manager<AbstractSceneConverter>& manager);
/** @brief Plugin manager constructor */
explicit AnySceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin);
~AnySceneConverter();
private:
MAGNUM_ANYSCENECONVERTER_LOCAL SceneConverterFeatures doFeatures() const override;
MAGNUM_ANYSCENECONVERTER_LOCAL bool doConvertToFile(const std::string& filename, const MeshData& mesh) override;
};
}}
#endif

68
src/MagnumPlugins/AnySceneConverter/CMakeLists.txt

@ -0,0 +1,68 @@
#
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
# Vladimír Vondruš <mosra@centrum.cz>
#
# 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.
#
find_package(Corrade REQUIRED PluginManager)
if(BUILD_PLUGINS_STATIC)
set(MAGNUM_ANYSCENECONVERTER_BUILD_STATIC 1)
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/configure.h)
# AnySceneConverter plugin
add_plugin(AnySceneConverter
"${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR};${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}"
"${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR};${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}"
AnySceneConverter.conf
AnySceneConverter.cpp
AnySceneConverter.h)
if(BUILD_PLUGINS_STATIC AND BUILD_STATIC_PIC)
set_target_properties(AnySceneConverter PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
target_link_libraries(AnySceneConverter PUBLIC MagnumTrade)
# Modify output location only if all are set, otherwise it makes no sense
if(CMAKE_RUNTIME_OUTPUT_DIRECTORY AND CMAKE_LIBRARY_OUTPUT_DIRECTORY AND CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set_target_properties(AnySceneConverter PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/magnum$<$<CONFIG:Debug>:-d>/imageconverters
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/magnum$<$<CONFIG:Debug>:-d>/imageconverters
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/magnum$<$<CONFIG:Debug>:-d>/imageconverters)
endif()
install(FILES AnySceneConverter.h ${CMAKE_CURRENT_BINARY_DIR}/configure.h
DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/AnySceneConverter)
# Automatic static plugin import
if(BUILD_PLUGINS_STATIC)
install(FILES importStaticPlugin.cpp DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/AnySceneConverter)
target_sources(AnySceneConverter INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/importStaticPlugin.cpp)
endif()
if(BUILD_TESTS)
add_subdirectory(Test)
endif()
# Magnum AnySceneConverter target alias for superprojects
add_library(Magnum::AnySceneConverter ALIAS AnySceneConverter)

121
src/MagnumPlugins/AnySceneConverter/Test/AnySceneConverterTest.cpp

@ -0,0 +1,121 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <sstream>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/Directory.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "Magnum/Trade/MeshData.h"
#include "configure.h"
namespace Magnum { namespace Trade { namespace Test { namespace {
struct AnySceneConverterTest: TestSuite::Tester {
explicit AnySceneConverterTest();
void load();
void detect();
void unknown();
void propagateFlags();
/* Explicitly forbid system-wide plugin dependencies */
PluginManager::Manager<AbstractSceneConverter> _manager{"nonexistent"};
};
constexpr struct {
const char* name;
const char* filename;
const char* plugin;
} DetectData[]{
{"BLOB", "huge.blob", "MagnumSceneConverter"}
};
AnySceneConverterTest::AnySceneConverterTest() {
addTests({&AnySceneConverterTest::load});
addInstancedTests({&AnySceneConverterTest::detect},
Containers::arraySize(DetectData));
addTests({&AnySceneConverterTest::unknown,
&AnySceneConverterTest::propagateFlags});
/* Load the plugin directly from the build tree. Otherwise it's static and
already loaded. */
#ifdef ANYSCENECONVERTER_PLUGIN_FILENAME
CORRADE_INTERNAL_ASSERT(_manager.load(ANYSCENECONVERTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded);
#endif
/* Create the output directory if it doesn't exist yet */
CORRADE_INTERNAL_ASSERT(Utility::Directory::mkpath(ANYSCENECONVERTER_TEST_DIR));
}
void AnySceneConverterTest::load() {
CORRADE_SKIP("No scene converter plugin available to test.");
}
void AnySceneConverterTest::detect() {
auto&& data = DetectData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Pointer<AbstractSceneConverter> converter = _manager.instantiate("AnySceneConverter");
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!converter->convertToFile(data.filename, MeshData{MeshPrimitive::Triangles, 0}));
/* 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));
#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));
#endif
}
void AnySceneConverterTest::unknown() {
std::ostringstream output;
Error redirectError{&output};
Containers::Pointer<AbstractSceneConverter> converter = _manager.instantiate("AnySceneConverter");
CORRADE_VERIFY(!converter->convertToFile("mesh.obj", MeshData{MeshPrimitive::Triangles, 0}));
CORRADE_COMPARE(output.str(), "Trade::AnySceneConverter::convertToFile(): cannot determine the format of mesh.obj\n");
}
void AnySceneConverterTest::propagateFlags() {
CORRADE_SKIP("No plugin with verbose output available to test.");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::AnySceneConverterTest)

71
src/MagnumPlugins/AnySceneConverter/Test/CMakeLists.txt

@ -0,0 +1,71 @@
#
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
# Vladimír Vondruš <mosra@centrum.cz>
#
# 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.
#
if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID)
set(ANYSCENECONVERTER_TEST_DIR "write")
else()
set(ANYSCENECONVERTER_TEST_DIR ${CMAKE_CURRENT_BINARY_DIR})
endif()
# CMake before 3.8 has broken $<TARGET_FILE*> expressions for iOS (see
# https://gitlab.kitware.com/cmake/cmake/merge_requests/404) and since Corrade
# doesn't support dynamic plugins on iOS, this sorta works around that. Should
# be revisited when updating Travis to newer Xcode (xcode7.3 has CMake 3.6).
if(NOT BUILD_PLUGINS_STATIC)
set(ANYSCENECONVERTER_PLUGIN_FILENAME $<TARGET_FILE:AnySceneConverter>)
if(WITH_TGAIMAGECONVERTER)
set(TGAIMAGECONVERTER_PLUGIN_FILENAME $<TARGET_FILE:TgaImageConverter>)
endif()
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}/$<CONFIG>/configure.h
INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in)
corrade_add_test(AnySceneConverterTest AnySceneConverterTest.cpp
LIBRARIES MagnumTrade)
target_include_directories(AnySceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>)
if(BUILD_PLUGINS_STATIC)
target_link_libraries(AnySceneConverterTest PRIVATE AnySceneConverter)
if(WITH_TGAIMAGECONVERTER)
target_link_libraries(AnySceneConverterTest PRIVATE TgaImageConverter)
endif()
else()
# So the plugins get properly built when building the test
add_dependencies(AnySceneConverterTest AnySceneConverter)
if(WITH_TGAIMAGECONVERTER)
add_dependencies(AnySceneConverterTest TgaImageConverter)
endif()
endif()
set_target_properties(AnySceneConverterTest PROPERTIES FOLDER "MagnumPlugins/AnySceneConverter/Test")
if(CORRADE_BUILD_STATIC AND NOT BUILD_PLUGINS_STATIC)
# CMake < 3.4 does this implicitly, but 3.4+ not anymore (see CMP0065).
# That's generally okay, *except if* the build is static, the executable
# uses a plugin manager and needs to share globals with the plugins (such
# as output redirection and so on).
set_target_properties(AnySceneConverterTest PROPERTIES ENABLE_EXPORTS ON)
endif()

27
src/MagnumPlugins/AnySceneConverter/Test/configure.h.cmake

@ -0,0 +1,27 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#cmakedefine ANYSCENECONVERTER_PLUGIN_FILENAME "${ANYSCENECONVERTER_PLUGIN_FILENAME}"
#define ANYSCENECONVERTER_TEST_DIR "${ANYSCENECONVERTER_TEST_DIR}"

26
src/MagnumPlugins/AnySceneConverter/configure.h.cmake

@ -0,0 +1,26 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#cmakedefine MAGNUM_ANYSCENECONVERTER_BUILD_STATIC

35
src/MagnumPlugins/AnySceneConverter/importStaticPlugin.cpp

@ -0,0 +1,35 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 "MagnumPlugins/AnySceneConverter/configure.h"
#ifdef MAGNUM_ANYSCENECONVERTER_BUILD_STATIC
#include <Corrade/PluginManager/AbstractManager.h>
static int magnumAnySceneConverterStaticImporter() {
CORRADE_PLUGIN_IMPORT(AnySceneConverter)
return 1;
} CORRADE_AUTOMATIC_INITIALIZER(magnumAnySceneConverterStaticImporter)
#endif

2
src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp

@ -80,6 +80,8 @@ void AnySceneImporter::doOpenFile(const std::string& filename) {
plugin = "Ac3dImporter";
else if(Utility::String::endsWith(normalized, ".blend"))
plugin = "BlenderImporter";
else if(Utility::String::endsWith(normalized, ".blob"))
plugin = "MagnumImporter";
else if(Utility::String::endsWith(normalized, ".bvh"))
plugin = "BvhImporter";
else if(Utility::String::endsWith(normalized, ".csm"))

2
src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h

@ -61,6 +61,8 @@ tries to open the file with it. Supported formats:
- AC3D (`*.ac`), loaded with any plugin that provides `Ac3dImporter`
- Blender 3D (`*.blend`), loaded with any plugin that provides
`BlenderImporter`
- @ref blob "Magnum's memory-mappable serialization format" (`*.blob`),
loaded with @ref MagnumImporter
- Biovision BVH (`*.bvh`), loaded with any plugin that provides `BvhImporter`
- CharacterStudio Motion (`*.csm`), loaded with any plugin that provides
`CsmImporter`

1
src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp

@ -75,6 +75,7 @@ constexpr struct {
const char* plugin;
} DetectData[]{
{"Blender", "suzanne.blend", "BlenderImporter"},
{"BLOB", "messy.blob", "MagnumImporter"},
{"COLLADA", "xml.dae", "ColladaImporter"},
{"FBX", "autodesk.fbx", "FbxImporter"},
{"glTF", "khronos.gltf", "GltfImporter"},

4
src/MagnumPlugins/CMakeLists.txt

@ -46,6 +46,10 @@ if(WITH_ANYIMAGECONVERTER)
add_subdirectory(AnyImageConverter)
endif()
if(WITH_ANYSCENECONVERTER)
add_subdirectory(AnySceneConverter)
endif()
if(WITH_ANYSCENEIMPORTER)
add_subdirectory(AnySceneImporter)
endif()

Loading…
Cancel
Save