From ece18bf4729de579bd9d30ffc5196775c58958c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Tue, 7 Sep 2021 18:45:10 +0200 Subject: [PATCH] Implement back text rendering (WIP) --- .../Graphics/GuillotineTextureAtlas.hpp | 35 +++ .../Graphics/GuillotineTextureAtlas.inl | 16 + include/Nazara/Graphics/TextSprite.hpp | 103 +++++++ include/Nazara/Graphics/TextSprite.inl | 23 ++ include/Nazara/Utility/AbstractAtlas.hpp | 3 +- include/Nazara/Utility/Enums.hpp | 7 + include/Nazara/Utility/Font.hpp | 6 +- .../Nazara/Utility/GuillotineImageAtlas.hpp | 6 +- include/Nazara/Utility/Image.hpp | 17 +- src/Nazara/Graphics/Graphics.cpp | 31 +- .../Graphics/GuillotineTextureAtlas.cpp | 72 +++++ src/Nazara/Graphics/TextSprite.cpp | 279 ++++++++++++++++++ src/Nazara/Utility/Font.cpp | 35 +-- src/Nazara/Utility/Formats/FreeTypeLoader.cpp | 33 +-- src/Nazara/Utility/GuillotineImageAtlas.cpp | 21 +- xmake.lua | 3 +- 16 files changed, 613 insertions(+), 77 deletions(-) create mode 100644 include/Nazara/Graphics/GuillotineTextureAtlas.hpp create mode 100644 include/Nazara/Graphics/GuillotineTextureAtlas.inl create mode 100644 include/Nazara/Graphics/TextSprite.hpp create mode 100644 include/Nazara/Graphics/TextSprite.inl create mode 100644 src/Nazara/Graphics/GuillotineTextureAtlas.cpp create mode 100644 src/Nazara/Graphics/TextSprite.cpp diff --git a/include/Nazara/Graphics/GuillotineTextureAtlas.hpp b/include/Nazara/Graphics/GuillotineTextureAtlas.hpp new file mode 100644 index 000000000..6a84be93d --- /dev/null +++ b/include/Nazara/Graphics/GuillotineTextureAtlas.hpp @@ -0,0 +1,35 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_GUILLOTINETEXTUREATLAS_HPP +#define NAZARA_GUILLOTINETEXTUREATLAS_HPP + +#include +#include +#include + +namespace Nz +{ + class RenderDevice; + + class NAZARA_GRAPHICS_API GuillotineTextureAtlas : public GuillotineImageAtlas + { + public: + inline GuillotineTextureAtlas(RenderDevice& renderDevice); + ~GuillotineTextureAtlas() = default; + + DataStoreFlags GetStorage() const override; + + private: + std::shared_ptr ResizeImage(const std::shared_ptr& oldImage, const Vector2ui& size) const override; + + RenderDevice& m_renderDevice; + }; +} + +#include + +#endif // NAZARA_GUILLOTINETEXTUREATLAS_HPP diff --git a/include/Nazara/Graphics/GuillotineTextureAtlas.inl b/include/Nazara/Graphics/GuillotineTextureAtlas.inl new file mode 100644 index 000000000..3a3b18bf9 --- /dev/null +++ b/include/Nazara/Graphics/GuillotineTextureAtlas.inl @@ -0,0 +1,16 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline GuillotineTextureAtlas::GuillotineTextureAtlas(RenderDevice& renderDevice) : + m_renderDevice(renderDevice) + { + } +} + +#include diff --git a/include/Nazara/Graphics/TextSprite.hpp b/include/Nazara/Graphics/TextSprite.hpp new file mode 100644 index 000000000..c4bb8950d --- /dev/null +++ b/include/Nazara/Graphics/TextSprite.hpp @@ -0,0 +1,103 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TEXTSPRITE_HPP +#define NAZARA_TEXTSPRITE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class AbstractTextDrawer; + + class NAZARA_GRAPHICS_API TextSprite : public InstancedRenderable + { + public: + TextSprite(std::shared_ptr material); + TextSprite(const TextSprite&) = delete; + TextSprite(TextSprite&&) noexcept = default; + ~TextSprite() = default; + + void BuildElement(std::size_t passIndex, const WorldInstance& worldInstance, std::vector>& elements) const override; + + inline void Clear(); + + const std::shared_ptr& GetMaterial(std::size_t i) const; + std::size_t GetMaterialCount() const; + + inline void SetMaterial(std::shared_ptr material); + + void Update(const AbstractTextDrawer& drawer, float scale = 1.f); + + TextSprite& operator=(const TextSprite&) = delete; + TextSprite& operator=(TextSprite&&) noexcept = default; + + private: + void OnAtlasInvalidated(const AbstractAtlas* atlas); + void OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer); + + struct AtlasSlots + { + bool used; + NazaraSlot(AbstractAtlas, OnAtlasCleared, clearSlot); + NazaraSlot(AbstractAtlas, OnAtlasLayerChange, layerChangeSlot); + NazaraSlot(AbstractAtlas, OnAtlasRelease, releaseSlot); + }; + + struct RenderData + { + + }; + + struct RenderKey + { + Texture* texture; + int renderOrder; + + bool operator==(const RenderKey& rhs) const + { + return texture == rhs.texture && renderOrder == rhs.renderOrder; + } + + bool operator!=(const RenderKey& rhs) const + { + return !operator==(rhs); + } + }; + + struct HashRenderKey + { + std::size_t operator()(const RenderKey& key) const + { + // Since renderOrder will be very small, this will be enough + return std::hash()(key.texture) + key.renderOrder; + } + }; + + struct RenderIndices + { + unsigned int first; + unsigned int count; + }; + + std::unordered_map m_atlases; + mutable std::unordered_map m_renderInfos; + std::shared_ptr m_material; + std::vector m_data; + std::vector m_vertices; + }; +} + +#include + +#endif diff --git a/include/Nazara/Graphics/TextSprite.inl b/include/Nazara/Graphics/TextSprite.inl new file mode 100644 index 000000000..a43b8367c --- /dev/null +++ b/include/Nazara/Graphics/TextSprite.inl @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + inline void TextSprite::Clear() + { + m_atlases.clear(); + m_vertices.clear(); + } + + inline void TextSprite::SetMaterial(std::shared_ptr material) + { + m_material = std::move(material); + } +} + +#include diff --git a/include/Nazara/Utility/AbstractAtlas.hpp b/include/Nazara/Utility/AbstractAtlas.hpp index dfb370af8..c4c4461eb 100644 --- a/include/Nazara/Utility/AbstractAtlas.hpp +++ b/include/Nazara/Utility/AbstractAtlas.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace Nz { @@ -30,7 +31,7 @@ namespace Nz virtual void Free(SparsePtr rects, SparsePtr layers, unsigned int count) = 0; virtual AbstractImage* GetLayer(unsigned int layerIndex) const = 0; virtual std::size_t GetLayerCount() const = 0; - virtual UInt32 GetStorage() const = 0; + virtual DataStoreFlags GetStorage() const = 0; virtual bool Insert(const Image& image, Rectui* rect, bool* flipped, unsigned int* layerIndex) = 0; AbstractAtlas& operator=(const AbstractAtlas&) = delete; diff --git a/include/Nazara/Utility/Enums.hpp b/include/Nazara/Utility/Enums.hpp index 96bffcd50..e75fa69e8 100644 --- a/include/Nazara/Utility/Enums.hpp +++ b/include/Nazara/Utility/Enums.hpp @@ -127,6 +127,13 @@ namespace Nz Max = Software }; + template<> + struct EnumAsFlags + { + static constexpr DataStorage max = DataStorage::Max; + }; + + using DataStoreFlags = Flags; constexpr std::size_t DataStorageCount = static_cast(DataStorage::Max) + 1; enum class FaceFilling diff --git a/include/Nazara/Utility/Font.hpp b/include/Nazara/Utility/Font.hpp index 636a7a456..f5fc96596 100644 --- a/include/Nazara/Utility/Font.hpp +++ b/include/Nazara/Utility/Font.hpp @@ -53,7 +53,7 @@ namespace Nz void ClearKerningCache(); void ClearSizeInfoCache(); - bool Create(FontData* data); + bool Create(std::unique_ptr data); void Destroy(); bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* glyph) const; @@ -74,7 +74,7 @@ namespace Nz bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const; bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const std::string& characterSet) const; - void SetAtlas(const std::shared_ptr& atlas); + void SetAtlas(std::shared_ptr atlas); void SetGlyphBorder(unsigned int borderSize); void SetMinimumStepSize(unsigned int minimumStepSize); @@ -90,7 +90,7 @@ namespace Nz static std::shared_ptr OpenFromMemory(const void* data, std::size_t size, const FontParams& params = FontParams()); static std::shared_ptr OpenFromStream(Stream& stream, const FontParams& params = FontParams()); - static void SetDefaultAtlas(const std::shared_ptr& atlas); + static void SetDefaultAtlas(std::shared_ptr atlas); static void SetDefaultGlyphBorder(unsigned int borderSize); static void SetDefaultMinimumStepSize(unsigned int minimumStepSize); diff --git a/include/Nazara/Utility/GuillotineImageAtlas.hpp b/include/Nazara/Utility/GuillotineImageAtlas.hpp index af9ba94ce..27cbe79f2 100644 --- a/include/Nazara/Utility/GuillotineImageAtlas.hpp +++ b/include/Nazara/Utility/GuillotineImageAtlas.hpp @@ -33,7 +33,7 @@ namespace Nz GuillotineBinPack::GuillotineSplitHeuristic GetRectSplitHeuristic() const; AbstractImage* GetLayer(unsigned int layerIndex) const override; std::size_t GetLayerCount() const override; - UInt32 GetStorage() const override; + DataStoreFlags GetStorage() const override; bool Insert(const Image& image, Rectui* rect, bool* flipped, unsigned int* layerIndex) override; @@ -46,7 +46,7 @@ namespace Nz protected: struct Layer; - virtual AbstractImage* ResizeImage(AbstractImage* oldImage, const Vector2ui& size) const; + virtual std::shared_ptr ResizeImage(const std::shared_ptr& oldImage, const Vector2ui& size) const; bool ResizeLayer(Layer& layer, const Vector2ui& size); struct QueuedGlyph @@ -59,7 +59,7 @@ namespace Nz struct Layer { std::vector queuedGlyphs; - std::unique_ptr image; + std::shared_ptr image; GuillotineBinPack binPack; unsigned int freedRectangles = 0; }; diff --git a/include/Nazara/Utility/Image.hpp b/include/Nazara/Utility/Image.hpp index cfde858ae..7776ca864 100644 --- a/include/Nazara/Utility/Image.hpp +++ b/include/Nazara/Utility/Image.hpp @@ -70,18 +70,18 @@ namespace Nz bool FlipVertically(); const UInt8* GetConstPixels(unsigned int x = 0, unsigned int y = 0, unsigned int z = 0, UInt8 level = 0) const; - unsigned int GetDepth(UInt8 level = 0) const override; + unsigned int GetDepth(UInt8 level = 0) const; PixelFormat GetFormat() const override; - unsigned int GetHeight(UInt8 level = 0) const override; + unsigned int GetHeight(UInt8 level = 0) const; UInt8 GetLevelCount() const override; - UInt8 GetMaxLevel() const override; - std::size_t GetMemoryUsage() const override; - std::size_t GetMemoryUsage(UInt8 level) const override; + UInt8 GetMaxLevel() const; + std::size_t GetMemoryUsage() const; + std::size_t GetMemoryUsage(UInt8 level) const; Color GetPixelColor(unsigned int x, unsigned int y = 0, unsigned int z = 0) const; UInt8* GetPixels(unsigned int x = 0, unsigned int y = 0, unsigned int z = 0, UInt8 level = 0); Vector3ui GetSize(UInt8 level = 0) const override; ImageType GetType() const override; - unsigned int GetWidth(UInt8 level = 0) const override; + unsigned int GetWidth(UInt8 level = 0) const; bool HasAlpha() const; @@ -101,9 +101,8 @@ namespace Nz void SetLevelCount(UInt8 levelCount); bool SetPixelColor(const Color& color, unsigned int x, unsigned int y = 0, unsigned int z = 0); - bool Update(const UInt8* pixels, unsigned int srcWidth = 0, unsigned int srcHeight = 0, UInt8 level = 0) override; - bool Update(const UInt8* pixels, const Boxui& box, unsigned int srcWidth = 0, unsigned int srcHeight = 0, UInt8 level = 0) override; - bool Update(const UInt8* pixels, const Rectui& rect, unsigned int z = 0, unsigned int srcWidth = 0, unsigned int srcHeight = 0, UInt8 level = 0) override; + using AbstractImage::Update; + bool Update(const void* pixels, const Boxui& box, unsigned int srcWidth = 0, unsigned int srcHeight = 0, UInt8 level = 0) override; Image& operator=(const Image& image); inline Image& operator=(Image&& image) noexcept; diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index c7f0fdfd4..ceec16cd8 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -4,8 +4,10 @@ #include #include +#include #include #include +#include #include #include @@ -77,10 +79,37 @@ namespace Nz BuildBlitPipeline(); RegisterMaterialPasses(); SelectDepthStencilFormats(); + + Font::SetDefaultAtlas(std::make_shared(*m_renderDevice)); } Graphics::~Graphics() { + // Free of atlas if it is ours + std::shared_ptr defaultAtlas = Font::GetDefaultAtlas(); + if (defaultAtlas && defaultAtlas->GetStorage() == DataStorage::Hardware) + { + Font::SetDefaultAtlas(nullptr); + + // The default police can make live one hardware atlas after the free of a module (which could be problematic) + // So, if the default police use a hardware atlas, we stole it. + // I don't like this solution, but I don't have any better + if (!defaultAtlas.unique()) + { + // Still at least one police use the atlas + const std::shared_ptr& defaultFont = Font::GetDefault(); + defaultFont->SetAtlas(nullptr); + + if (!defaultAtlas.unique()) + { + // Still not the only one to own it ? Then crap. + NazaraWarning("Default font atlas uses hardware storage and is still used"); + } + } + } + + defaultAtlas.reset(); + MaterialPipeline::Uninitialize(); m_renderPassCache.reset(); m_samplerCache.reset(); @@ -159,7 +188,7 @@ namespace Nz { Nz::TextureInfo texInfo; texInfo.width = texInfo.height = texInfo.depth = texInfo.mipmapLevel = 1; - texInfo.pixelFormat = PixelFormat::BGRA8; + texInfo.pixelFormat = PixelFormat::L8; texInfo.type = ImageType::E2D; std::array texData = { 0xFF, 0xFF, 0xFF, 0xFF }; diff --git a/src/Nazara/Graphics/GuillotineTextureAtlas.cpp b/src/Nazara/Graphics/GuillotineTextureAtlas.cpp new file mode 100644 index 000000000..5d6c35d1a --- /dev/null +++ b/src/Nazara/Graphics/GuillotineTextureAtlas.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + /*! + * \ingroup graphics + * \class Nz::GuillotineTextureAtlas + * \brief Graphics class that represents an atlas texture for guillotine + */ + + /*! + * \brief Gets the underlying data storage + * \return Value of the enumeration of the underlying data storage + */ + DataStoreFlags GuillotineTextureAtlas::GetStorage() const + { + return DataStorage::Hardware; + } + + /*! + * \brief Resizes the image + * \return Updated texture + * + * \param oldImage Old image to resize + * \param size New image size + * + * \remark Produces a NazaraError if resize failed + */ + + std::shared_ptr GuillotineTextureAtlas::ResizeImage(const std::shared_ptr& oldImage, const Vector2ui& size) const + { + TextureInfo textureInfo; + textureInfo.width = size.x; + textureInfo.height = size.y; + textureInfo.pixelFormat = PixelFormat::A8; + textureInfo.type = ImageType::E2D; + + std::shared_ptr newTexture = m_renderDevice.InstantiateTexture(textureInfo); + if (!newTexture) + return nullptr; + + if (oldImage) + { + return nullptr; + /*const Texture& oldTexture = static_cast(*oldImage); + + // Copy of old data + ///TODO: Copy from texture to texture + Image image; + if (!oldTexture->Download(&image)) + { + NazaraError("Failed to download old texture"); + return nullptr; + } + + if (!newTexture->Update(&image, Rectui(0, 0, image.GetWidth(), image.GetHeight()))) + { + NazaraError("Failed to update texture"); + return nullptr; + }*/ + } + + return newTexture; + } +} diff --git a/src/Nazara/Graphics/TextSprite.cpp b/src/Nazara/Graphics/TextSprite.cpp new file mode 100644 index 000000000..806076ab2 --- /dev/null +++ b/src/Nazara/Graphics/TextSprite.cpp @@ -0,0 +1,279 @@ +// Copyright (C) 2017 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + TextSprite::TextSprite(std::shared_ptr material) : + InstancedRenderable(Nz::Boxf(-10000.f, -10000.f, -10000.f, 20000.f, 20000.f, 20000.f)), + m_material(std::move(material)) + { + } + + void TextSprite::BuildElement(std::size_t passIndex, const WorldInstance& worldInstance, std::vector>& elements) const + { + MaterialPass* materialPass = m_material->GetPass(passIndex); + if (!materialPass) + return; + + const std::shared_ptr& vertexDeclaration = VertexDeclaration::Get(VertexLayout::XYZ_Color_UV); + + std::vector vertexBufferData = { + { + { + 0, + vertexDeclaration + } + } + }; + const auto& renderPipeline = materialPass->GetPipeline()->GetRenderPipeline(vertexBufferData); + + for (auto& pair : m_renderInfos) + { + const RenderKey& key = pair.first; + RenderIndices& indices = pair.second; + + if (indices.count > 0) + elements.emplace_back(std::make_unique(0, renderPipeline, vertexDeclaration, key.texture->shared_from_this(), indices.count, &m_vertices[indices.first * 4], materialPass->GetShaderBinding(), worldInstance.GetShaderBinding())); + } + } + + const std::shared_ptr& TextSprite::GetMaterial(std::size_t i) const + { + assert(i == 0); + NazaraUnused(i); + + return m_material; + } + + std::size_t TextSprite::GetMaterialCount() const + { + return 1; + } + + void TextSprite::Update(const AbstractTextDrawer& drawer, float scale) + { + CallOnExit clearOnFail([this]() + { + Clear(); + }); + + // Mark every atlas as unused... + for (auto& pair : m_atlases) + pair.second.used = false; + + // ... until they are marked as used by the drawer + std::size_t fontCount = drawer.GetFontCount(); + for (std::size_t i = 0; i < fontCount; ++i) + { + Font& font = *drawer.GetFont(i); + const AbstractAtlas* atlas = font.GetAtlas().get(); + NazaraAssert(atlas->GetStorage() == DataStorage::Hardware, "Font uses a non-hardware atlas which cannot be used by text sprites"); + + auto it = m_atlases.find(atlas); + if (it == m_atlases.end()) + { + it = m_atlases.emplace(std::make_pair(atlas, AtlasSlots())).first; + AtlasSlots& atlasSlots = it->second; + + atlasSlots.clearSlot.Connect(atlas->OnAtlasCleared, this, &TextSprite::OnAtlasInvalidated); + atlasSlots.layerChangeSlot.Connect(atlas->OnAtlasLayerChange, this, &TextSprite::OnAtlasLayerChange); + atlasSlots.releaseSlot.Connect(atlas->OnAtlasRelease, this, &TextSprite::OnAtlasInvalidated); + } + + it->second.used = true; + } + + // Remove unused atlas slots + auto atlasIt = m_atlases.begin(); + while (atlasIt != m_atlases.end()) + { + if (!atlasIt->second.used) + m_atlases.erase(atlasIt++); + else + ++atlasIt; + } + + std::size_t glyphCount = drawer.GetGlyphCount(); + + // Reset glyph count for every texture to zero + for (auto& pair : m_renderInfos) + pair.second.count = 0; + + // Count glyph count for each texture + RenderKey lastRenderKey{ nullptr, 0 }; + unsigned int* count = nullptr; + + // Iterate over visible (non-space) glyphs + std::size_t visibleGlyphCount = 0; + for (std::size_t i = 0; i < glyphCount; ++i) + { + const AbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i); + if (!glyph.atlas) + continue; + + Texture* texture = static_cast(glyph.atlas); + RenderKey renderKey{ texture, glyph.renderOrder }; + if (lastRenderKey != renderKey) + { + auto it = m_renderInfos.find(renderKey); + if (it == m_renderInfos.end()) + it = m_renderInfos.insert(std::make_pair(renderKey, RenderIndices{ 0U, 0U })).first; + + count = &it->second.count; + lastRenderKey = renderKey; + } + + (*count)++; + visibleGlyphCount++; + } + + m_vertices.resize(visibleGlyphCount * 4); + + // Attributes indices and reinitialize glyph count to zero to use it as a counter in the next loop + // This is because the 1st glyph can use texture A, the 2nd glyph can use texture B and the 3th glyph C can use texture A again + // so we need a counter to know where to write informations + // also remove unused render infos + unsigned int index = 0; + auto infoIt = m_renderInfos.begin(); + while (infoIt != m_renderInfos.end()) + { + RenderIndices& indices = infoIt->second; + if (indices.count == 0) + infoIt = m_renderInfos.erase(infoIt); //< No glyph uses this texture, remove from indices + else + { + indices.first = index; + + index += indices.count; + indices.count = 0; + ++infoIt; + } + } + + Rectf bounds = drawer.GetBounds(); + + lastRenderKey = { nullptr, 0 }; + RenderIndices* indices = nullptr; + for (unsigned int i = 0; i < glyphCount; ++i) + { + const AbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i); + if (!glyph.atlas) + continue; + + Texture* texture = static_cast(glyph.atlas); + RenderKey renderKey{ texture, glyph.renderOrder }; + if (lastRenderKey != renderKey) + { + indices = &m_renderInfos[renderKey]; //< We changed texture, adjust the pointer + lastRenderKey = renderKey; + } + + // First, compute the uv coordinates from our atlas rect + Vector2ui size(texture->GetSize()); + float invWidth = 1.f / size.x; + float invHeight = 1.f / size.y; + + Rectf uvRect(glyph.atlasRect); + uvRect.x *= invWidth; + uvRect.y *= invHeight; + uvRect.width *= invWidth; + uvRect.height *= invHeight; + + // Our glyph may be flipped in the atlas, to render it correctly we need to change the uv coordinates accordingly + const RectCorner normalCorners[4] = { RectCorner::LeftTop, RectCorner::RightTop, RectCorner::LeftBottom, RectCorner::RightBottom }; + const RectCorner flippedCorners[4] = { RectCorner::LeftBottom, RectCorner::LeftTop, RectCorner::RightBottom, RectCorner::RightTop }; + + // Set the position, color and UV of our vertices + for (unsigned int j = 0; j < 4; ++j) + { + // Remember that indices->count is a counter here, not a count value + std::size_t offset = (indices->first + indices->count) * 4 + j; + m_vertices[offset].color = glyph.color; + m_vertices[offset].position = glyph.corners[j]; + m_vertices[offset].position.y = bounds.height - m_vertices[offset].position.y; + m_vertices[offset].position *= scale; + m_vertices[offset].uv.Set(uvRect.GetCorner((glyph.flipped) ? flippedCorners[j] : normalCorners[j])); + } + + // Increment the counter, go to next glyph + indices->count++; + } + + /*m_localBounds = drawer.GetBounds(); + + InvalidateBoundingVolume(); + InvalidateInstanceData(0);*/ + + clearOnFail.Reset(); + } + + /*! + * \brief Handle the invalidation of an atlas + * + * \param atlas Atlas being invalidated + */ + void TextSprite::OnAtlasInvalidated(const AbstractAtlas* atlas) + { + assert(m_atlases.find(atlas) != m_atlases.end()); + + NazaraWarning("TextSprite " + PointerToString(this) + " has been cleared because atlas " + PointerToString(atlas) + " has been invalidated (cleared or released)"); + Clear(); + } + + /*! + * \brief Handle the size change of an atlas layer + * + * \param atlas Atlas being invalidated + * \param oldLayer Pointer to the previous layer + * \param newLayer Pointer to the new layer + */ + void TextSprite::OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer) + { + NazaraUnused(atlas); + + assert(m_atlases.find(atlas) != m_atlases.end()); + + if (!oldLayer) + return; + + assert(newLayer); + + // The texture of an atlas have just been recreated (size change) + // we have to adjust the coordinates of the texture and the rendering texture + Texture* oldTexture = static_cast(oldLayer); + Texture* newTexture = static_cast(newLayer); + + Vector2ui oldSize(oldTexture->GetSize()); + Vector2ui newSize(newTexture->GetSize()); + Vector2f scale = Vector2f(oldSize) / Vector2f(newSize); // ratio of the old one to the new one + + // It is possible we actually use that texture multiple times, check them all + for (auto it = m_renderInfos.begin(); it != m_renderInfos.end(); ++it) + { + const RenderKey& renderKey = it->first; + const RenderIndices& indices = it->second; + + // Adjust texture coordinates by size ratio + SparsePtr texCoordPtr(&m_vertices[indices.first].uv, sizeof(VertexStruct_XY_Color_UV)); + for (unsigned int i = 0; i < indices.count; ++i) + { + for (unsigned int j = 0; j < 4; ++j) + m_vertices[i * 4 + j].uv *= scale; + } + + // Erase and re-insert with the new texture handle + m_renderInfos.erase(it); + m_renderInfos.insert(std::make_pair(RenderKey{ newTexture, renderKey.renderOrder }, indices)); + it = m_renderInfos.begin(); //< std::unordered_map::insert may invalidate all iterators, start from the beginning... + } + } +} diff --git a/src/Nazara/Utility/Font.cpp b/src/Nazara/Utility/Font.cpp index b27750050..ecb91990a 100644 --- a/src/Nazara/Utility/Font.cpp +++ b/src/Nazara/Utility/Font.cpp @@ -37,7 +37,7 @@ namespace Nz OnFontRelease(this); Destroy(); - SetAtlas(nullptr); // On libère l'atlas proprement + SetAtlas({}); // On libère l'atlas proprement } void Font::ClearGlyphCache() @@ -81,19 +81,12 @@ namespace Nz OnFontSizeInfoCacheCleared(this); } - bool Font::Create(FontData* data) + bool Font::Create(std::unique_ptr data) { + NazaraAssert(data, "invalid font data"); + Destroy(); - - #if NAZARA_UTILITY_SAFE - if (!data) - { - NazaraError("Invalid font data"); - return false; - } - #endif - - m_data.reset(data); + m_data = std::move(data); return true; } @@ -230,7 +223,7 @@ namespace Nz else { NazaraWarning("Failed to extract space character from font, using half the character size"); - sizeInfo.spaceAdvance = characterSize/2; + sizeInfo.spaceAdvance = characterSize / 2; } it = m_sizeInfoCache.insert(std::make_pair(characterSize, sizeInfo)).first; @@ -281,13 +274,13 @@ namespace Nz return true; } - void Font::SetAtlas(const std::shared_ptr& atlas) + void Font::SetAtlas(std::shared_ptr atlas) { if (m_atlas != atlas) { ClearGlyphCache(); - m_atlas = atlas; + m_atlas = std::move(atlas); if (m_atlas) { m_atlasClearedSlot.Connect(m_atlas->OnAtlasCleared, this, &Font::OnAtlasCleared); @@ -380,9 +373,9 @@ namespace Nz return utility->GetFontLoader().LoadFromStream(stream, params); } - void Font::SetDefaultAtlas(const std::shared_ptr& atlas) + void Font::SetDefaultAtlas(std::shared_ptr atlas) { - s_defaultAtlas = atlas; + s_defaultAtlas = std::move(atlas); } void Font::SetDefaultGlyphBorder(unsigned int borderSize) @@ -392,13 +385,7 @@ namespace Nz void Font::SetDefaultMinimumStepSize(unsigned int minimumStepSize) { - #if NAZARA_UTILITY_SAFE - if (minimumStepSize == 0) - { - NazaraError("Minimum step size cannot be zero as it implies division by zero"); - return; - } - #endif + NazaraAssert(minimumStepSize, "minimum step size cannot be zero as it implies a division by zero"); s_defaultMinimumStepSize = minimumStepSize; } diff --git a/src/Nazara/Utility/Formats/FreeTypeLoader.cpp b/src/Nazara/Utility/Formats/FreeTypeLoader.cpp index c6ce3b504..f1ba08c6e 100644 --- a/src/Nazara/Utility/Formats/FreeTypeLoader.cpp +++ b/src/Nazara/Utility/Formats/FreeTypeLoader.cpp @@ -26,8 +26,8 @@ namespace Nz { class FreeTypeLibrary; - FT_Library s_library; - FT_Stroker s_stroker; + FT_Library s_library = nullptr; + FT_Stroker s_stroker = nullptr; std::shared_ptr s_libraryOwner; constexpr float s_scaleFactor = 1 << 6; constexpr float s_invScaleFactor = 1.f / s_scaleFactor; @@ -427,20 +427,17 @@ namespace Nz } std::shared_ptr font = std::make_shared(); - if (font->Create(face.get())) - { - face.release(); - return font; - } - else + if (!font->Create(std::move(face))) return nullptr; + + return font; } std::shared_ptr LoadMemory(const void* data, std::size_t size, const FontParams& parameters) { NazaraUnused(parameters); - std::unique_ptr face(new FreeTypeStream); + std::unique_ptr face = std::make_unique(); face->SetMemory(data, size); if (!face->Open()) @@ -450,13 +447,10 @@ namespace Nz } std::shared_ptr font = std::make_shared(); - if (font->Create(face.get())) - { - face.release(); - return font; - } - else + if (!font->Create(std::move(face))) return nullptr; + + return font; } std::shared_ptr LoadStream(Stream& stream, const FontParams& parameters) @@ -473,13 +467,10 @@ namespace Nz } std::shared_ptr font = std::make_shared(); - if (font->Create(face.get())) - { - face.release(); - return font; - } - else + if (!font->Create(std::move(face))) return nullptr; + + return font; } } diff --git a/src/Nazara/Utility/GuillotineImageAtlas.cpp b/src/Nazara/Utility/GuillotineImageAtlas.cpp index 1876bbfa4..aa508a6b3 100644 --- a/src/Nazara/Utility/GuillotineImageAtlas.cpp +++ b/src/Nazara/Utility/GuillotineImageAtlas.cpp @@ -73,9 +73,9 @@ namespace Nz return m_layers.size(); } - UInt32 GuillotineImageAtlas::GetStorage() const + DataStoreFlags GuillotineImageAtlas::GetStorage() const { - return static_cast(DataStorage::Software); + return DataStorage::Software; } bool GuillotineImageAtlas::Insert(const Image& image, Rectui* rect, bool* flipped, unsigned int* layerIndex) @@ -159,30 +159,23 @@ namespace Nz m_rectSplitHeuristic = heuristic; } - AbstractImage* GuillotineImageAtlas::ResizeImage(AbstractImage* oldImage, const Vector2ui& size) const + std::shared_ptr GuillotineImageAtlas::ResizeImage(const std::shared_ptr& oldImage, const Vector2ui& size) const { - std::unique_ptr newImage(new Image(ImageType::E2D, PixelFormat::A8, size.x, size.y)); + std::shared_ptr newImage = std::make_shared(ImageType::E2D, PixelFormat::A8, size.x, size.y); if (oldImage) - { newImage->Copy(static_cast(*oldImage), Rectui(size), Vector2ui(0, 0)); // Copie des anciennes données - } - return newImage.release(); + return newImage; } bool GuillotineImageAtlas::ResizeLayer(Layer& layer, const Vector2ui& size) { - AbstractImage* oldLayer = layer.image.get(); - - std::unique_ptr newImage(ResizeImage(layer.image.get(), size)); + std::shared_ptr newImage = ResizeImage(layer.image, 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 + if (newImage == layer.image) // Le layer a été agrandi dans le même objet, pas de souci return true; - } // On indique à ceux que ça intéresse qu'on a changé de pointeur // (chose très importante pour ceux qui le stockent) diff --git a/xmake.lua b/xmake.lua index eba3d2c0a..90fc86036 100644 --- a/xmake.lua +++ b/xmake.lua @@ -103,7 +103,8 @@ set_xmakever("2.5.6") add_repositories("local-repo xmake-repo") -add_requires("chipmunk2d", "dr_wav", "entt", "freetype", "libflac", "libsdl", "minimp3", "stb") +add_requires("chipmunk2d", "dr_wav", "entt", "libflac", "libsdl", "minimp3", "stb") +add_requires("freetype", { configs = { bzip2 = true, png = true, woff2 = true, zlib = true, debug = is_mode("debug") } }) add_requires("libvorbis", { configs = { with_vorbisenc = false } }) add_requires("openal-soft", { configs = { shared = true }}) add_requires("newtondynamics", { debug = is_plat("windows") and is_mode("debug") }) -- Newton doesn't like compiling in Debug on Linux