mirror of https://github.com/mosra/magnum.git
100 changed files with 6129 additions and 57 deletions
@ -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. |
||||||
|
*/ |
||||||
|
} |
||||||
@ -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) |
||||||
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
}} |
||||||
@ -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 |
||||||
@ -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) |
||||||
File diff suppressed because it is too large
Load Diff
@ -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}); |
||||||
|
} |
||||||
|
|
||||||
|
}} |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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) |
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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") |
||||||
@ -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 |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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() |
||||||
@ -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}" |
||||||
@ -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 |
||||||
@ -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 |
||||||
Loading…
Reference in new issue