Improved Atlas class

It now handles layer resize failure and will notify listeners of a
pointer change


Former-commit-id: df5e2a129897128a4e41e0b3205f6a1dbeb2069c
This commit is contained in:
Lynix 2015-01-16 16:26:15 +01:00
parent 5eec0d2abe
commit ff7cfa226e
6 changed files with 97 additions and 65 deletions

View File

@ -17,8 +17,7 @@ class NAZARA_API NzGuillotineTextureAtlas : public NzGuillotineImageAtlas
~NzGuillotineTextureAtlas() = default; ~NzGuillotineTextureAtlas() = default;
private: private:
unsigned int GetMaxAtlasSize() const override; NzAbstractImage* ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const override;
bool ResizeImage(Layer& layer, const NzVector2ui& size) const override;
}; };
#endif // NAZARA_GUILLOTINETEXTUREATLAS_HPP #endif // NAZARA_GUILLOTINETEXTUREATLAS_HPP

View File

@ -40,11 +40,13 @@ class NAZARA_API NzAbstractAtlas
virtual ~Listener(); virtual ~Listener();
virtual bool OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata); 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); virtual void OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata);
}; };
protected: protected:
void NotifyCleared(); void NotifyCleared();
void NotifyLayerChange(NzAbstractImage* oldLayer, NzAbstractImage* newLayer);
private: private:
mutable std::unordered_map<Listener*, void*> m_listeners; mutable std::unordered_map<Listener*, void*> m_listeners;

View File

@ -37,8 +37,8 @@ class NAZARA_API NzGuillotineImageAtlas : public NzAbstractAtlas
protected: protected:
struct Layer; struct Layer;
virtual unsigned int GetMaxAtlasSize() const; virtual NzAbstractImage* ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const;
virtual bool ResizeImage(Layer& layer, const NzVector2ui& size) const; bool ResizeLayer(Layer& layer, const NzVector2ui& size);
struct QueuedGlyph struct QueuedGlyph
{ {

View File

@ -7,49 +7,37 @@
#include <Nazara/Renderer/Texture.hpp> #include <Nazara/Renderer/Texture.hpp>
#include <Nazara/Graphics/Debug.hpp> #include <Nazara/Graphics/Debug.hpp>
bool NzGuillotineTextureAtlas::ResizeImage(Layer& layer, const NzVector2ui& size) const NzAbstractImage* NzGuillotineTextureAtlas::ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const
{ {
NzTexture newTexture; std::unique_ptr<NzTexture> newTexture(new NzTexture);
if (newTexture.Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF)) if (newTexture->Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF))
{ {
if (layer.image) if (oldImage)
{ {
NzTexture& texture = *static_cast<NzTexture*>(layer.image.get()); NzTexture* oldTexture = static_cast<NzTexture*>(oldImage);
// Copie des anciennes données // Copie des anciennes données
///TODO: Copie de texture à texture ///TODO: Copie de texture à texture
NzImage image; NzImage image;
if (!texture.Download(&image)) if (!oldTexture->Download(&image))
{ {
NazaraError("Failed to download old texture"); 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"); 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 else
{ {
NazaraError("Failed to create texture"); // Si on arrive ici c'est que la taille demandée est trop grande pour la carte graphique
return false; // 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();
}

View File

@ -45,6 +45,23 @@ void NzAbstractAtlas::NotifyCleared()
m_listenersLocked = false; 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; NzAbstractAtlas::Listener::~Listener() = default;
bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata) bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata)
@ -55,6 +72,16 @@ bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, voi
return true; 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) void NzAbstractAtlas::Listener::OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata)
{ {
NazaraUnused(atlas); NazaraUnused(atlas);

View File

@ -75,8 +75,6 @@ unsigned int NzGuillotineImageAtlas::GetLayerCount() const
bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex) bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex)
{ {
unsigned int maxAtlasSize = GetMaxAtlasSize();
if (m_layers.empty()) if (m_layers.empty())
{ {
// On créé une première couche s'il n'y en a pas // 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 ? 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 ? // 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 NzVector2ui newSize = layer.binPack.GetSize()*2;
if (size < maxAtlasSize) if (ResizeLayer(layer, newSize))
{ {
// On peut encore agrandir la couche // Oui on peut !
size = std::min(size*2, maxAtlasSize); layer.binPack.Expand(newSize); // On ajuste l'atlas virtuel
layer.binPack.Expand(size, size);
// On relance la boucle sur la nouvelle dernière couche // Et on relance la boucle sur la nouvelle dernière couche
i--; i--;
} }
else else
{ {
// On ne peut plus agrandir la dernière couche, il est temps d'en créer une nouvelle // 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); newSize.Set(s_atlasStartSize);
Layer& newLayer = m_layers.back();
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 // 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) void NzGuillotineImageAtlas::SetRectChoiceHeuristic(NzGuillotineBinPack::FreeRectChoiceHeuristic heuristic)
@ -148,21 +155,46 @@ void NzGuillotineImageAtlas::SetRectSplitHeuristic(NzGuillotineBinPack::Guilloti
m_rectSplitHeuristic = heuristic; 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<NzImage> newImage(new NzImage(nzImageType_2D, nzPixelFormat_A8, size.x, size.y));
if (oldImage)
{
NzImage& image = *static_cast<NzImage*>(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<NzAbstractImage> 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 void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const
{ {
std::vector<nzUInt8> pixelBuffer; std::vector<nzUInt8> 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) for (QueuedGlyph& glyph : layer.queuedGlyphs)
{ {
unsigned int glyphWidth = glyph.image.GetWidth(); unsigned int glyphWidth = glyph.image.GetWidth();
@ -226,19 +258,3 @@ void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const
layer.queuedGlyphs.clear(); 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<NzImage*>(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;
}