Started work on Font Glyph Atlas

This commit is contained in:
OmniaX-Dev 2026-04-13 06:48:19 +02:00
parent 15d9d1265f
commit 20c40d87e4
11 changed files with 383 additions and 21 deletions

View file

@ -1,2 +1,3 @@
CompileFlags:
CompilationDatabase: bin
CompilationDatabase: bin
Add: ["--target=x86_64-w64-mingw32"]

View file

@ -95,6 +95,7 @@ list(APPEND OGFX_SOURCE_FILES
# render
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/BasicRenderer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/PixelRenderer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/FontGlyphAtlas.cpp
# resources
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/resources/Image.cpp

View file

@ -1 +1 @@
2056
2057

View file

@ -67,7 +67,7 @@ namespace ogfx
gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth);
gfx.drawRoundRect({ getGlobalContentPosition(), m_checkSize }, getCheckBorderColor(), m_checkBorderRadius, m_checkBorderWidth);
if (isChecked())
gfx.fillRoundRect({ getGlobalContentPosition() + 5, m_checkSize - 10 }, getCheckBoxColor(), m_checkBorderRadius / 2);
gfx.fillRoundRect({ getGlobalContentPosition() + 5, m_checkSize - 10 }, getCheckBoxColor(), m_checkBorderRadius / 2.0f);
gfx.drawString(getText(), getGlobalContentPosition() + ostd::Vec2 { m_checkSize.x + m_spacing, 0 }, getTextColor(), getFontSize());
}

View file

@ -21,6 +21,7 @@
#include "BasicRenderer.hpp"
#include "../gui/Window.hpp"
#include "../resources/UbuntuMonoRegularTTF.hpp"
#include "../gui/Window.hpp"
#define COLOR_CAST(ostd_color) std::bit_cast<SDL_FColor>(ostd_color.getNormalizedColor())
#define QUAD_INDICES_ARR { 0, 1, 2, 2, 3, 0 }
@ -60,10 +61,17 @@ namespace ogfx
m_window = &window;
if (m_initialized) return set_error_state(tErrors::NoError);
init_arrays();
loadDefaultFont(20);
m_fontGlyphAtlas.init(*this);
m_initialized = true;
return set_error_state(tErrors::NoError);
}
SDL_Renderer* BasicRenderer2D::getSDLRenderer(void) const
{
return m_window != nullptr ? m_window->getSDLRenderer() : nullptr;
}
void BasicRenderer2D::flushBatch(void)
{
if (!m_initialized || m_vertexCount == 0)

View file

@ -20,10 +20,12 @@
#pragma once
#include <ogfx/render/FontUtils.hpp>
#include <ogfx/utils/Animation.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/data/Color.hpp>
#include <ogfx/resources/Image.hpp>
#include <ostd/io/IOHandlers.hpp>
#include <ogfx/render/FontGlyphAtlas.hpp>
namespace ogfx
{
@ -56,6 +58,7 @@ namespace ogfx
BasicRenderer2D(void) = default;
~BasicRenderer2D(void);
int32_t init(WindowCore& window);
SDL_Renderer* getSDLRenderer(void) const;
void flushBatch(void);
void endFrame(void);
void pushClippingRect(const ostd::Rectangle& rect, bool additive = false);
@ -70,11 +73,12 @@ namespace ogfx
inline uint32_t getDrawCallCount(void) { return m_drawCallCount; }
inline bool hasOpenFont(void) { return m_fontOpen; }
inline TTF_Font* getSDLFont(void) { return m_font; }
inline bool isValid(void) { return m_initialized && m_fontOpen && (m_font != nullptr || m_fontFromMemory); }
inline bool isValid(void) const { return m_initialized && m_fontOpen && (m_font != nullptr || m_fontFromMemory); }
inline int32_t geterrorState(void) { return m_errorState; }
inline int32_t getFontSize(void) { return m_fontSize; }
inline WindowCore& getWindow(void) { return *m_window; }
inline bool isInitialized(void) { return m_initialized; }
inline FontGlyphAtlas& getFontGlyphAtlas(void) { return m_fontGlyphAtlas; }
void drawImage(const ogfx::Image& image, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 }, const ostd::Rectangle& srcRect = { 0, 0, 0, 0 });
void drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 });
@ -138,6 +142,7 @@ namespace ogfx
int32_t m_errorState { tErrors::NoError };
int32_t m_fontSize { DefaultFontSize };
bool m_fontFromMemory { false };
FontGlyphAtlas m_fontGlyphAtlas;
inline static constexpr int32_t DefaultFontSize { 16 };
};

View file

@ -0,0 +1,224 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2025 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "FontGlyphAtlas.hpp"
#include "BasicRenderer.hpp"
#define DEBUG(n) std::cout << (int)n << "\n";
namespace ogfx
{
FontGlyphAtlas FontGlyphAtlas::init(BasicRenderer2D& renderer)
{
m_renderer = &renderer;
m_uvs.reserve(4096);
for (int32_t i = 0; i < FontGlyphAtlas::MaxAtlasCount; i++)
m_atlases[i] = nullptr;
m_currentAtlasCount = 0;
create_new_atlas();
return *this;
}
bool FontGlyphAtlas::rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize)
{
DEBUG(1);
std::cout << (uint64_t)font << "\n";
if (!font || m_currentAtlasCount <= 0)
return false;
DEBUG(2);
// 1. Extract codepoint from the ostd::String
auto cps = glyphStr.getUTF8Codepoints();
if (cps.size() != 1)
return false; // must be exactly one character
DEBUG(3);
uint32_t codepoint = cps[0];
// 2. Convert the ostd::String to UTF-8 for SDL_ttf
std::string utf8 = glyphStr.cpp_str();
// 3. Rasterize glyph using SDL_ttf
SDL_Surface* surf = TTF_RenderText_Blended(font, utf8.c_str(), utf8.length(), SDL_Color{255, 255, 255, 255});
if (!surf)
return false;
DEBUG(4);
int gw = surf->w;
int gh = surf->h;
// 4. Get current atlas
SDL_Texture* atlas = m_atlases[m_currentAtlasCount - 1];
// 5. Shelf packing: move to next row if needed
if (m_penX + gw > int(AtlasTextureDimension))
{
m_penX = 0;
m_penY += m_rowHeight;
m_rowHeight = 0;
}
// 6. If still doesn't fit → create new atlas
if (m_penY + gh > int(AtlasTextureDimension))
{
SDL_DestroySurface(surf);
if (!create_new_atlas())
return false; // no more atlases available
atlas = m_atlases[m_currentAtlasCount - 1];
m_penX = 0;
m_penY = 0;
m_rowHeight = 0;
}
DEBUG(5);
// 7. Upload glyph bitmap into atlas
SDL_Rect dstRect { m_penX, m_penY, gw, gh };
SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
SDL_DestroySurface(surf);
// 8. Compute UVs
float u0 = float(m_penX) / float(AtlasTextureDimension);
float v0 = float(m_penY) / float(AtlasTextureDimension);
float u1 = float(m_penX + gw) / float(AtlasTextureDimension);
float v1 = float(m_penY + gh) / float(AtlasTextureDimension);
GlyphUV uv;
uv.atlas = atlas;
uv.uvs[0] = { u0, v0 };
uv.uvs[1] = { u1, v0 };
uv.uvs[2] = { u1, v1 };
uv.uvs[3] = { u0, v1 };
// 9. Store in hashmap
GlyphKey key { codepoint, uint64_t(font), fontSize };
m_uvs[key] = uv;
// 10. Advance shelf packing cursor
m_penX += gw;
m_rowHeight = std::max(m_rowHeight, gh);
return true;
}
bool FontGlyphAtlas::create_new_atlas(void)
{
// 1. Check if we can create another atlas
if (m_currentAtlasCount >= int(MaxAtlasCount))
return false;
if (!m_renderer)
return false;
SDL_Renderer* sdlRenderer = m_renderer->getSDLRenderer();
if (!sdlRenderer)
return false;
// 2. Create the texture
SDL_Texture* tex = SDL_CreateTexture(
sdlRenderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
AtlasTextureDimension,
AtlasTextureDimension
);
if (!tex)
return false;
// 3. Set blend mode (important for alpha glyphs)
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
// 4. Clear the texture to transparent
{
void* pixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0)
{
memset(pixels, 0, pitch * AtlasTextureDimension);
SDL_UnlockTexture(tex);
}
}
// 5. Store the atlas
m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++;
// 6. Reset packing cursor for this new atlas
m_penX = 0;
m_penY = 0;
m_rowHeight = 0;
return true;
}
bool FontGlyphAtlas::save_atlas_png(int atlasIndex, const char* filename)
{
if (atlasIndex < 0 || atlasIndex >= m_currentAtlasCount)
return false;
SDL_Texture* tex = m_atlases[atlasIndex];
if (!tex)
return false;
// --- 1. Query texture properties (SDL3 way) ---
SDL_PropertiesID props = SDL_GetTextureProperties(tex);
if (!props)
return false;
int width = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
int height = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
Uint32 format = (Uint32)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_FORMAT_NUMBER, SDL_PIXELFORMAT_RGBA32);
if (width <= 0 || height <= 0)
return false;
// --- 2. Create a surface to receive the pixels ---
SDL_Surface* surf = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32);
if (!surf)
return false;
// --- 3. Lock texture and copy pixels ---
void* pixels = nullptr;
int pitch = 0;
if (!SDL_LockTexture(tex, nullptr, &pixels, &pitch))
{
SDL_DestroySurface(surf);
return false;
}
// Copy row-by-row (pitch may differ)
uint8_t* dst = static_cast<uint8_t*>(surf->pixels);
uint8_t* src = static_cast<uint8_t*>(pixels);
for (int y = 0; y < height; y++)
memcpy(dst + y * surf->pitch, src + y * pitch, width * 4);
SDL_UnlockTexture(tex);
// --- 4. Save PNG ---
bool ok = (IMG_SavePNG(surf, filename) == 0);
SDL_DestroySurface(surf);
return ok;
}
}

View file

@ -0,0 +1,84 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2025 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/string/String.hpp>
#include <ostd/math/Geometry.hpp>
#include <ogfx/utils/SDLInclude.hpp>
namespace ogfx
{
class BasicRenderer2D;
class FontGlyphAtlas
{
public: struct GlyphUV
{
ostd::Vec2 uvs[4];
SDL_Texture* atlas { nullptr };
};
struct GlyphKey
{
uint32_t codepoint;
uint64_t fontID;
uint32_t pixelSize;
bool operator==(const GlyphKey& other) const noexcept
{
return codepoint == other.codepoint &&
fontID == other.fontID &&
pixelSize == other.pixelSize;
}
};
struct GlyphKeyHasher
{
inline size_t operator()(const GlyphKey& k) const noexcept
{
uint64_t v = 0;
v |= uint64_t(k.codepoint);
v |= uint64_t(k.fontID) << 32;
v |= uint64_t(k.pixelSize) << 48;
return std::hash<uint64_t>()(v);
}
};
public:
inline FontGlyphAtlas(void) { }
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer);
bool save_atlas_png(int atlasIndex, const char* filename);
public:
bool rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize);
bool create_new_atlas(void);
public:
inline static constexpr uint32_t AtlasTextureDimension { 8192 };
inline static constexpr uint32_t MaxAtlasCount { 16 };
private:
std::unordered_map<GlyphKey, GlyphUV, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
int32_t m_currentAtlasCount { 0 };
BasicRenderer2D* m_renderer { nullptr };
int32_t m_penX { 0 };
int32_t m_penY { 0 };
int32_t m_rowHeight { 0 };
};
}

View file

@ -646,7 +646,54 @@ namespace ostd
return str;
}
std::vector<uint32_t> String::decodeUTF8(const ostd::String& s)
{
std::vector<uint32_t> out;
const char* p = s.m_data.data();
const char* end = p + s.m_data.size();
while (p < end)
out.push_back(utf8_next(p, end));
return out;
}
uint32_t String::utf8_next(const char*& p, const char* end)
{
uint8_t c = static_cast<uint8_t>(*p++);
// ASCII fast path
if (c < 0x80)
return c;
// 2-byte sequence
if ((c >> 5) == 0x6) {
uint32_t cp = (c & 0x1F) << 6;
cp |= (static_cast<uint8_t>(*p++) & 0x3F);
return cp;
}
// 3-byte sequence
if ((c >> 4) == 0xE) {
uint32_t cp = (c & 0x0F) << 12;
cp |= (static_cast<uint8_t>(*p++) & 0x3F) << 6;
cp |= (static_cast<uint8_t>(*p++) & 0x3F);
return cp;
}
// 4-byte sequence
if ((c >> 3) == 0x1E) {
uint32_t cp = (c & 0x07) << 18;
cp |= (static_cast<uint8_t>(*p++) & 0x3F) << 12;
cp |= (static_cast<uint8_t>(*p++) & 0x3F) << 6;
cp |= (static_cast<uint8_t>(*p++) & 0x3F);
return cp;
}
// Invalid byte → return replacement character
return 0xFFFD;
}
String operator+(const cpp_string& str1, const String& str)
{

View file

@ -83,6 +83,7 @@ namespace ostd
inline operator std::filesystem::path() const { return cpp_str(); }
inline String& clr(void) { m_data = ""; return *this; }
inline String& set(const cpp_string& str) { m_data = str; return *this; }
inline std::vector<uint32_t> getUTF8Codepoints(void) const { return decodeUTF8(m_data); }
inline auto begin(void) { return m_data.begin(); }
inline auto end(void) { return m_data.end(); }
@ -174,9 +175,13 @@ namespace ostd
static String getHexStr(uint64_t value, bool prefix = true, uint8_t nbytes = 1);
static String getBinStr(uint64_t value, bool prefix = true, uint8_t nbytes = 1);
static String duplicateChar(unsigned char c, uint16_t count);
static std::vector<uint32_t> decodeUTF8(const ostd::String& s);
friend std::ostream& operator<<(std::ostream& out, const String& val);
private:
static uint32_t utf8_next(const char*& p, const char* end);
private:
ostd::cpp_string m_data;
};

View file

@ -30,20 +30,6 @@ class Window : public ogfx::gui::Window
inline Window(void) { }
inline void onInitialize(void) override
{
test.loadFromFile("./test.png", m_gfx);
ogfx::Animation::AnimationData ad;
ad.backwards = false;
ad.columns = 9;
ad.rows = 4;
ad.frame_width = 256;
ad.frame_height = 256;
ad.length = 36;
ad.random = false;
ad.still = false;
ad.speed = 0;
m_anim.create(ad);
m_anim.setSpriteSheet(test);
m_panel1.setSize(300, 140);
m_panel1.setPosition(350, 50);
m_panel1.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void {
@ -131,6 +117,9 @@ class Window : public ogfx::gui::Window
setTheme(m_theme);
m_gfx.getFontGlyphAtlas().rasterize_glyph("A", m_gfx.getSDLFont(), 30);
m_gfx.getFontGlyphAtlas().save_atlas_png(0, "./atlas.png");
// std::cout << m_theme.get<ostd::String>("panel.titlebarType", "", {}, {}) << " \n";
}
@ -167,8 +156,6 @@ class Window : public ogfx::gui::Window
ogfx::gui::widgets::CheckBox m_check1 { *this };
ostd::Stylesheet m_theme;
ostd::Vec2 pos { 0, 0 };
ogfx::Image test;
ogfx::Animation m_anim;
};
int main(int argc, char** argv)