From ff7cfa226e59f5d6989582c81c105dcaf20a26cd Mon Sep 17 00:00:00 2001 From: Lynix Date: Fri, 16 Jan 2015 16:26:15 +0100 Subject: [PATCH] Improved Atlas class It now handles layer resize failure and will notify listeners of a pointer change Former-commit-id: df5e2a129897128a4e41e0b3205f6a1dbeb2069c --- .../Graphics/GuillotineTextureAtlas.hpp | 3 +- include/Nazara/Utility/AbstractAtlas.hpp | 2 + .../Nazara/Utility/GuillotineImageAtlas.hpp | 4 +- .../Graphics/GuillotineTextureAtlas.cpp | 38 +++----- src/Nazara/Utility/AbstractAtlas.cpp | 27 ++++++ src/Nazara/Utility/GuillotineImageAtlas.cpp | 88 +++++++++++-------- 6 files changed, 97 insertions(+), 65 deletions(-) diff --git a/include/Nazara/Graphics/GuillotineTextureAtlas.hpp b/include/Nazara/Graphics/GuillotineTextureAtlas.hpp index 3eb14dd68..04197a41b 100644 --- a/include/Nazara/Graphics/GuillotineTextureAtlas.hpp +++ b/include/Nazara/Graphics/GuillotineTextureAtlas.hpp @@ -17,8 +17,7 @@ class NAZARA_API NzGuillotineTextureAtlas : public NzGuillotineImageAtlas ~NzGuillotineTextureAtlas() = default; private: - unsigned int GetMaxAtlasSize() const override; - bool ResizeImage(Layer& layer, const NzVector2ui& size) const override; + NzAbstractImage* ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const override; }; #endif // NAZARA_GUILLOTINETEXTUREATLAS_HPP diff --git a/include/Nazara/Utility/AbstractAtlas.hpp b/include/Nazara/Utility/AbstractAtlas.hpp index 6cb56f41f..2d290153b 100644 --- a/include/Nazara/Utility/AbstractAtlas.hpp +++ b/include/Nazara/Utility/AbstractAtlas.hpp @@ -40,11 +40,13 @@ class NAZARA_API NzAbstractAtlas virtual ~Listener(); virtual bool OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata); + virtual bool OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata); virtual void OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata); }; protected: void NotifyCleared(); + void NotifyLayerChange(NzAbstractImage* oldLayer, NzAbstractImage* newLayer); private: mutable std::unordered_map m_listeners; diff --git a/include/Nazara/Utility/GuillotineImageAtlas.hpp b/include/Nazara/Utility/GuillotineImageAtlas.hpp index b17e2c647..22c50fdcb 100644 --- a/include/Nazara/Utility/GuillotineImageAtlas.hpp +++ b/include/Nazara/Utility/GuillotineImageAtlas.hpp @@ -37,8 +37,8 @@ class NAZARA_API NzGuillotineImageAtlas : public NzAbstractAtlas protected: struct Layer; - virtual unsigned int GetMaxAtlasSize() const; - virtual bool ResizeImage(Layer& layer, const NzVector2ui& size) const; + virtual NzAbstractImage* ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const; + bool ResizeLayer(Layer& layer, const NzVector2ui& size); struct QueuedGlyph { diff --git a/src/Nazara/Graphics/GuillotineTextureAtlas.cpp b/src/Nazara/Graphics/GuillotineTextureAtlas.cpp index 20e4c6db4..0963eddf3 100644 --- a/src/Nazara/Graphics/GuillotineTextureAtlas.cpp +++ b/src/Nazara/Graphics/GuillotineTextureAtlas.cpp @@ -7,49 +7,37 @@ #include #include -bool NzGuillotineTextureAtlas::ResizeImage(Layer& layer, const NzVector2ui& size) const +NzAbstractImage* NzGuillotineTextureAtlas::ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const { - NzTexture newTexture; - if (newTexture.Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF)) + std::unique_ptr newTexture(new NzTexture); + if (newTexture->Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF)) { - if (layer.image) + if (oldImage) { - NzTexture& texture = *static_cast(layer.image.get()); + NzTexture* oldTexture = static_cast(oldImage); // Copie des anciennes données ///TODO: Copie de texture à texture NzImage image; - if (!texture.Download(&image)) + if (!oldTexture->Download(&image)) { NazaraError("Failed to download old texture"); - return false; + return nullptr; } - if (!newTexture.Update(image, NzRectui(0, 0, image.GetWidth(), image.GetHeight()))) + if (!newTexture->Update(image, NzRectui(0, 0, image.GetWidth(), image.GetHeight()))) { NazaraError("Failed to update texture"); - return false; + return nullptr; } - - texture = std::move(newTexture); } - else - layer.image.reset(new NzTexture(std::move(newTexture))); - return true; + return newTexture.release(); } else { - NazaraError("Failed to create texture"); - return false; + // Si on arrive ici c'est que la taille demandée est trop grande pour la carte graphique + // ou que nous manquons de mémoire + return nullptr; } } - -unsigned int NzGuillotineTextureAtlas::GetMaxAtlasSize() const -{ - ///FIXME: D'après la documentation OpenGL, cette valeur n'est qu'une approximation et les texture proxies sont une meilleure solution - /// Cependant le test ne se fait pas au même moment, penser à adapter le code pour gérer ce cas ? - /// (Cela permettrait au passage de gérer le cas où une image ne peut être allouée car il n'y a pas assez de mémoire contigüe pour la contenir) - - return NzRenderer::GetMaxTextureSize(); -} diff --git a/src/Nazara/Utility/AbstractAtlas.cpp b/src/Nazara/Utility/AbstractAtlas.cpp index 5fa7a2ca1..dac73acf7 100644 --- a/src/Nazara/Utility/AbstractAtlas.cpp +++ b/src/Nazara/Utility/AbstractAtlas.cpp @@ -45,6 +45,23 @@ void NzAbstractAtlas::NotifyCleared() m_listenersLocked = false; } +void NzAbstractAtlas::NotifyLayerChange(NzAbstractImage* oldLayer, NzAbstractImage* newLayer) +{ + m_listenersLocked = true; + + auto it = m_listeners.begin(); + while (it != m_listeners.end()) + { + if (!it->first->OnAtlasLayerChange(this, oldLayer, newLayer, it->second)) + m_listeners.erase(it++); + else + ++it; + } + + m_listenersLocked = false; +} + + NzAbstractAtlas::Listener::~Listener() = default; bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata) @@ -55,6 +72,16 @@ bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, voi return true; } +bool NzAbstractAtlas::Listener::OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata) +{ + NazaraUnused(atlas); + NazaraUnused(oldLayer); + NazaraUnused(newLayer); + NazaraUnused(userdata); + + return true; +} + void NzAbstractAtlas::Listener::OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata) { NazaraUnused(atlas); diff --git a/src/Nazara/Utility/GuillotineImageAtlas.cpp b/src/Nazara/Utility/GuillotineImageAtlas.cpp index a04eed25a..bd7486ab7 100644 --- a/src/Nazara/Utility/GuillotineImageAtlas.cpp +++ b/src/Nazara/Utility/GuillotineImageAtlas.cpp @@ -75,8 +75,6 @@ unsigned int NzGuillotineImageAtlas::GetLayerCount() const bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex) { - unsigned int maxAtlasSize = GetMaxAtlasSize(); - if (m_layers.empty()) { // On créé une première couche s'il n'y en a pas @@ -112,30 +110,39 @@ bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* else if (i == m_layers.size() - 1) // Dernière itération ? { // Dernière couche, et le glyphe ne rentre pas, peut-on agrandir la taille de l'image ? - unsigned int size = layer.binPack.GetWidth(); // l'image étant carrée, on ne teste qu'une dimension - if (size < maxAtlasSize) + NzVector2ui newSize = layer.binPack.GetSize()*2; + if (ResizeLayer(layer, newSize)) { - // On peut encore agrandir la couche - size = std::min(size*2, maxAtlasSize); - layer.binPack.Expand(size, size); + // Oui on peut ! + layer.binPack.Expand(newSize); // On ajuste l'atlas virtuel - // On relance la boucle sur la nouvelle dernière couche + // Et on relance la boucle sur la nouvelle dernière couche i--; } else { // On ne peut plus agrandir la dernière couche, il est temps d'en créer une nouvelle - m_layers.resize(m_layers.size() + 1); - Layer& newLayer = m_layers.back(); + newSize.Set(s_atlasStartSize); - newLayer.binPack.Reset(s_atlasStartSize, s_atlasStartSize); + Layer newLayer; + if (!ResizeLayer(newLayer, newSize)) + { + // Impossible d'allouer une nouvelle couche, nous manquons probablement de mémoire (ou le glyphe est trop grand) + NazaraError("Failed to allocate new layer, we are probably out of memory"); + return false; + } + + newLayer.binPack.Reset(newSize); + + m_layers.emplace_back(std::move(newLayer)); // Insertion du layer // On laisse la boucle insérer toute seule le rectangle à la prochaine itération } } } - return false; // Normalement impossible + NazaraInternalError("Unknown error"); // Normalement on ne peut pas arriver ici + return false; } void NzGuillotineImageAtlas::SetRectChoiceHeuristic(NzGuillotineBinPack::FreeRectChoiceHeuristic heuristic) @@ -148,21 +155,46 @@ void NzGuillotineImageAtlas::SetRectSplitHeuristic(NzGuillotineBinPack::Guilloti m_rectSplitHeuristic = heuristic; } -unsigned int NzGuillotineImageAtlas::GetMaxAtlasSize() const +NzAbstractImage* NzGuillotineImageAtlas::ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const { - return 8192; // Valeur totalement arbitraire + std::unique_ptr newImage(new NzImage(nzImageType_2D, nzPixelFormat_A8, size.x, size.y)); + if (oldImage) + { + NzImage& image = *static_cast(oldImage); + newImage->Copy(image, NzRectui(size), NzVector2ui(0, 0)); // Copie des anciennes données + } + + return newImage.release(); +} + +bool NzGuillotineImageAtlas::ResizeLayer(Layer& layer, const NzVector2ui& size) +{ + NzAbstractImage* oldLayer = layer.image.get(); + + std::unique_ptr newImage(ResizeImage(layer.image.get(), size)); + if (!newImage) + return false; // Nous n'avons pas pu allouer + + if (newImage.get() == oldLayer) // Le layer a été agrandi dans le même objet, pas de souci + { + newImage.release(); // On possède déjà un unique_ptr sur cette ressource + return true; + } + + // On indique à ceux que ça intéresse qu'on a changé de pointeur + // (chose très importante pour ceux qui le stockent) + NotifyLayerChange(layer.image.get(), newImage.get()); + + // Et on ne met à jour le pointeur qu'après (car cette ligne libère également l'ancienne image) + layer.image = std::move(newImage); + + return true; } void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const { std::vector pixelBuffer; - // On s'assure que l'image est de la bonne taille - NzVector2ui binPackSize(layer.binPack.GetSize()); - NzVector2ui imageSize((layer.image) ? layer.image->GetSize() : NzVector3ui(0U)); - if (binPackSize != imageSize) - ResizeImage(layer, binPackSize); - for (QueuedGlyph& glyph : layer.queuedGlyphs) { unsigned int glyphWidth = glyph.image.GetWidth(); @@ -226,19 +258,3 @@ void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const layer.queuedGlyphs.clear(); } - -bool NzGuillotineImageAtlas::ResizeImage(Layer& layer, const NzVector2ui& size) const -{ - NzImage newImage(nzImageType_2D, nzPixelFormat_A8, size.x, size.y); - if (layer.image) - { - NzImage& image = *static_cast(layer.image.get()); - newImage.Copy(image, NzRectui(size), NzVector2ui(0, 0)); // Copie des anciennes données - - image = std::move(newImage); - } - else - layer.image.reset(new NzImage(std::move(newImage))); - - return true; -}