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;
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Listener*, void*> m_listeners;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,49 +7,37 @@
|
|||
#include <Nazara/Renderer/Texture.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;
|
||||
if (newTexture.Create(nzImageType_2D, nzPixelFormat_A8, size.x, size.y, 1, 0xFF))
|
||||
std::unique_ptr<NzTexture> 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<NzTexture*>(layer.image.get());
|
||||
NzTexture* oldTexture = static_cast<NzTexture*>(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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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<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