From c63ae8061997b8d86800eeffee4bb7d3b672a5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 25 Sep 2023 17:07:55 +0200 Subject: [PATCH] TextureTools: doc snippet and sample output for atlasArrayPowerOfTwo(). Finally, sorry it took over a year. I did the visualization for the article, it'd be sad to not use it. And add a link to the article too. --- doc/generated/CMakeLists.txt | 8 +- doc/generated/atlas.cpp | 118 ++++++++++++++++++++++ doc/snippets/CMakeLists.txt | 9 ++ doc/snippets/MagnumTextureTools.cpp | 67 ++++++++++++ doc/snippets/atlas-array-power-of-two.svg | 35 +++++++ src/Magnum/TextureTools/Atlas.h | 12 ++- 6 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 doc/generated/atlas.cpp create mode 100644 doc/snippets/MagnumTextureTools.cpp create mode 100644 doc/snippets/atlas-array-power-of-two.svg diff --git a/doc/generated/CMakeLists.txt b/doc/generated/CMakeLists.txt index 85262dec69..0a0ffc01bc 100644 --- a/doc/generated/CMakeLists.txt +++ b/doc/generated/CMakeLists.txt @@ -34,7 +34,8 @@ find_package(Magnum REQUIRED MeshTools Primitives Shaders - Sdl2Application) + Sdl2Application + TextureTools) set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON) @@ -73,3 +74,8 @@ target_link_libraries(primitives Magnum::Primitives Magnum::Shaders Magnum::WindowlessApplication) + +add_executable(atlas atlas.cpp) +target_link_libraries(atlas + Magnum::DebugTools + Magnum::TextureTools) diff --git a/doc/generated/atlas.cpp b/doc/generated/atlas.cpp new file mode 100644 index 0000000000..36804f219d --- /dev/null +++ b/doc/generated/atlas.cpp @@ -0,0 +1,118 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include /** @todo remove once formatString() isn't used */ +#include /** @todo remove once growable String exists */ +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Range.h" +#include "Magnum/DebugTools/ColorMap.h" +#include "Magnum/TextureTools/Atlas.h" + +using namespace Magnum; + +int main() { + /* For a random set of colors */ + std::random_device rd; + std::uniform_int_distribution colorDist{0, 255}; + + /* atlasArrayPowerOfTwo() */ + { + constexpr Int maxSize = 7; + constexpr Int padding = 4; + constexpr Int layerPadding = 4; + constexpr Float displaySizeDivisor = 1.0f; + + /* Random set of texture sizes would be stupid, so have a say on how + many is actually generated */ + std::size_t sizeCount[]{ + 0, + 0, + 9, /* 4 */ + 7, /* 8 */ + 4, /* 16 */ + 5, /* 32 */ + 6, /* 64 */ + 2, /* 128 */ + }; + std::size_t count = 0; + for(std::size_t i: sizeCount) + count += i; + + struct Data { + Vector2i size; + Vector3i offset; + }; + Containers::Array data{NoInit, count}; + Vector2i size; + std::size_t currentSize = 0; + std::size_t currentSizeCount = 0; + for(std::size_t i = 0; i != count; ++i) { + while(currentSizeCount >= sizeCount[currentSize]) { + ++currentSize; + currentSizeCount = 0; + } + + ++currentSizeCount; + + data[i].size = Vector2i{1 << currentSize}; + + if(i) size.x() += padding; + size.x() += data[i].size.x(); + size.y() = Math::max(size.y(), data[i].size.y()); + } + + Int layerCount = TextureTools::atlasArrayPowerOfTwo(Vector2i{1 << maxSize}, + stridedArrayView(data).slice(&Data::size), + stridedArrayView(data).slice(&Data::offset)); + + Range2Di viewBox{{}, {layerCount*(1 << maxSize) + (layerCount - 1)*layerPadding, 1 << maxSize}}; + + std::string out; + Utility::formatInto(out, out.size(), R"( +)", + viewBox.left(), viewBox.bottom(), viewBox.sizeX(), viewBox.sizeY(), viewBox.sizeX()/displaySizeDivisor, viewBox.sizeY()/displaySizeDivisor); + + for(std::size_t i = 0; i != data.size(); ++i) { + const Vector2i size = data[i].size; + const Vector2i offset = data[i].offset.xy(); + const Int layer = data[i].offset.z(); + const Color4ub color = DebugTools::ColorMap::turbo()[colorDist(rd)]; + + Utility::formatInto(out, out.size(), R"( +)", + layer*((1 << maxSize) + layerPadding) + offset.x(), (1 << maxSize) - size.y() - offset.y(), size.x(), size.y(), color.r(), color.g(), color.b()); + } + + Utility::formatInto(out, out.size(), R"( +)"); + + CORRADE_INTERNAL_ASSERT_OUTPUT(Utility::Path::write("atlas-array-power-of-two.svg", Containers::StringView{out})); + } +} diff --git a/doc/snippets/CMakeLists.txt b/doc/snippets/CMakeLists.txt index 927fe701a0..3254eb7ebb 100644 --- a/doc/snippets/CMakeLists.txt +++ b/doc/snippets/CMakeLists.txt @@ -117,6 +117,15 @@ if(MAGNUM_WITH_SHADERTOOLS) endif() endif() +if(MAGNUM_WITH_TEXTURETOOLS) + add_library(snippets-MagnumTextureTools STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} + MagnumTextureTools.cpp) + target_link_libraries(snippets-MagnumTextureTools PRIVATE MagnumTextureTools) + if(CORRADE_TESTSUITE_TEST_TARGET) + add_dependencies(${CORRADE_TESTSUITE_TEST_TARGET} snippets-MagnumTextureTools) + endif() +endif() + if(MAGNUM_WITH_TRADE) add_library(snippets-MagnumTrade STATIC ${EXCLUDE_FROM_ALL_IF_TEST_TARGET} plugins.cpp diff --git a/doc/snippets/MagnumTextureTools.cpp b/doc/snippets/MagnumTextureTools.cpp new file mode 100644 index 0000000000..0af35df687 --- /dev/null +++ b/doc/snippets/MagnumTextureTools.cpp @@ -0,0 +1,67 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Image.h" +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/TextureTools/Atlas.h" + +#define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ + +using namespace Magnum; + +int main() { +{ +/* [atlasArrayPowerOfTwo] */ +Containers::ArrayView input; +Containers::StridedArrayView1D sizes = + stridedArrayView(input).slice(&ImageView2D::size); +Containers::Array offsets{NoInit, input.size()}; + +/* Size the atlas based on the largest image and fill it */ +Vector2i layerSize = Math::max(sizes); +Int layerCount = TextureTools::atlasArrayPowerOfTwo(layerSize, sizes, offsets); + +/* Copy the image data to the atlas, assuming all are RGBA8Unorm as well */ +Vector3i outputSize{layerSize, layerCount}; +Image3D output{PixelFormat::RGBA8Unorm, outputSize, + Containers::Array{ValueInit, std::size_t(outputSize.product())}}; +Containers::StridedArrayView3D dst = output.pixels(); +for(std::size_t i = 0; i != input.size(); ++i) { + Containers::StridedArrayView3D src = input[i].pixels(); + Utility::copy(src, dst.sliceSize( + {std::size_t(offsets[i].z()), + std::size_t(offsets[i].y()), + std::size_t(offsets[i].x())}, src.size())); +} +/* [atlasArrayPowerOfTwo] */ +} +} diff --git a/doc/snippets/atlas-array-power-of-two.svg b/doc/snippets/atlas-array-power-of-two.svg new file mode 100644 index 0000000000..7c704a4417 --- /dev/null +++ b/doc/snippets/atlas-array-power-of-two.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Magnum/TextureTools/Atlas.h b/src/Magnum/TextureTools/Atlas.h index 4c0076978e..224d85f722 100644 --- a/src/Magnum/TextureTools/Atlas.h +++ b/src/Magnum/TextureTools/Atlas.h @@ -67,11 +67,19 @@ The @p sizes and @p offsets views are expected to have the same size. The in all but the last layer. Setting @p layerSize to the size of the largest texture in the set will lead to the least wasted space in the last layer. +@htmlinclude atlas-array-power-of-two.svg + +Example usage is shown below. + +@snippet MagnumTextureTools.cpp atlasArrayPowerOfTwo + The algorithm first sorts the textures by size using @ref std::stable_sort(), which is usually @f$ \mathcal{O}(n \log{} n) @f$, and then performs the actual atlasing in a single @f$ \mathcal{O}(n) @f$ operation. Memory complexity is -@f$ \mathcal{0}(n) @f$ with @f$ n @f$ being a sorted copy of the input size -array, additionally @ref std::stable_sort() performs its own allocation. +@f$ \mathcal{O}(n) @f$ with @f$ n @f$ being a sorted copy of the input size +array, additionally @ref std::stable_sort() performs its own allocation. See +the [Zero-waste single-pass packing of power-of-two textures](https://blog.magnum.graphics/backstage/pot-array-packing/) +article for a detailed description of the algorithm. */ MAGNUM_TEXTURETOOLS_EXPORT Int atlasArrayPowerOfTwo(const Vector2i& layerSize, const Containers::StridedArrayView1D& sizes, const Containers::StridedArrayView1D& offsets);