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:
parent
5eec0d2abe
commit
ff7cfa226e
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue