Implemented Font GLyph Atlas overflow

This commit is contained in:
OmniaX-Dev 2026-04-13 15:11:25 +02:00
parent 20c40d87e4
commit 64cd4598f2
10 changed files with 221 additions and 84 deletions

View file

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

View file

@ -5,7 +5,7 @@ $accentColor = Color(rgba(255, 0, 255, 255))
const $color2 = Color(#0000FFFF)
% -------------------
window.backgroundColor = Color(rgba(120, 120, 120, 255))
window.backgroundColor = Color(rgba(0, 0, 0, 255))
$accentColor = Color(rgb(255, 0, 0))

View file

@ -1 +1 @@
2057
2058

View file

@ -35,7 +35,7 @@ namespace ogfx
{
if (m_window != nullptr) return;
m_window = &window;
m_renderer.init(window);
// m_renderer.init(window); //TODO: Fix
}
void GraphicsWindowOutputHandler::setMonospaceFont(const String& filePath)

View file

@ -61,7 +61,9 @@ namespace ogfx
m_window = &window;
if (m_initialized) return set_error_state(tErrors::NoError);
init_arrays();
m_initialized = true;
loadDefaultFont(20);
m_initialized = false;
m_fontGlyphAtlas.init(*this);
m_initialized = true;
return set_error_state(tErrors::NoError);

View file

@ -55,7 +55,7 @@ namespace ogfx
BasicRenderer2D& m_parent;
};
public:
BasicRenderer2D(void) = default;
inline BasicRenderer2D(void) { }
~BasicRenderer2D(void);
int32_t init(WindowCore& window);
SDL_Renderer* getSDLRenderer(void) const;
@ -106,7 +106,7 @@ namespace ogfx
void outlinedCircle(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1);
void outlinedEllipse(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1);
private:
public:
void init_arrays(void);
void generate_half_circle(const ostd::Vec2& center, const ostd::Vec2& dir, float radius, int segments, const ostd::Color& color);
void generate_quarter_circle(const ostd::Vec2& center, float radius, float thickness, float startAngle, const ostd::Color& color, int segments);

View file

@ -20,8 +20,9 @@
#include "FontGlyphAtlas.hpp"
#include "BasicRenderer.hpp"
#define DEBUG(n) std::cout << (int)n << "\n";
#include <SDL3/SDL_pixels.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
namespace ogfx
{
@ -38,20 +39,20 @@ namespace ogfx
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];
GlyphKey key { codepoint, uint64_t(font), fontSize };
if (auto it = m_uvs.find(key) != m_uvs.end())
return true;
// 2. Convert the ostd::String to UTF-8 for SDL_ttf
std::string utf8 = glyphStr.cpp_str();
@ -61,13 +62,19 @@ namespace ogfx
if (!surf)
return false;
DEBUG(4);
// SDL_Surface* converted = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32);
// SDL_DestroySurface(surf);
// surf = converted;
int gw = surf->w;
int gh = surf->h;
// 4. Get current atlas
SDL_Texture* atlas = m_atlases[m_currentAtlasCount - 1];
// SDL_Log("atlas format = %s", SDL_GetPixelFormatName(atlas->format));
// SDL_Log("surf format = %s", SDL_GetPixelFormatName(surf->format));
// 5. Shelf packing: move to next row if needed
if (m_penX + gw > int(AtlasTextureDimension))
{
@ -90,10 +97,47 @@ namespace ogfx
m_rowHeight = 0;
}
DEBUG(5);
uint8_t* pixels = (uint8_t*)surf->pixels;
int pitch = surf->pitch;
// for (int y = 0; y < surf->h; y++)
// {
// uint32_t* row = (uint32_t*)(pixels + y * pitch);
// for (int x = 0; x < surf->w; x++)
// {
// uint32_t px = row[x];
// uint8_t a = (px >> 24) & 0xFF; // ABGR → A is highest byte
// uint8_t r = (px >> 16) & 0xFF;
// uint8_t g = (px >> 8) & 0xFF;
// uint8_t b = (px) & 0xFF;
// std::cout << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << " ";
// row[x] = (a << 24) | (r << 16) | (g << 8) | b;
// }
// }
// 7. Upload glyph bitmap into atlas
SDL_Rect dstRect { m_penX, m_penY, gw, gh };
SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
void* lockedPixels = nullptr;
int lockedPitch = 0;
if (SDL_LockTexture(atlas, &dstRect, &lockedPixels, &lockedPitch))
{
uint8_t* src = (uint8_t*)surf->pixels;
uint8_t* dst = (uint8_t*)lockedPixels;
for (int y = 0; y < gh; y++)
{
memcpy(dst, src, gw * 4); // 4 bytes per pixel (ARGB8888)
src += surf->pitch;
dst += lockedPitch;
}
SDL_UnlockTexture(atlas);
}
// SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
SDL_DestroySurface(surf);
// 8. Compute UVs
@ -108,9 +152,9 @@ namespace ogfx
uv.uvs[1] = { u1, v0 };
uv.uvs[2] = { u1, v1 };
uv.uvs[3] = { u0, v1 };
uv.size = { static_cast<float>(gw), static_cast<float>(gh) };
// 9. Store in hashmap
GlyphKey key { codepoint, uint64_t(font), fontSize };
m_uvs[key] = uv;
// 10. Advance shelf packing cursor
@ -133,19 +177,14 @@ namespace ogfx
return false;
// 2. Create the texture
SDL_Texture* tex = SDL_CreateTexture(
sdlRenderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
AtlasTextureDimension,
AtlasTextureDimension
);
SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
if (!tex)
return false;
// 3. Set blend mode (important for alpha glyphs)
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
// 4. Clear the texture to transparent
{
@ -153,7 +192,7 @@ namespace ogfx
int pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0)
{
memset(pixels, 0, pitch * AtlasTextureDimension);
memset(pixels, 0x00, pitch * AtlasTextureDimension);
SDL_UnlockTexture(tex);
}
}
@ -170,55 +209,40 @@ namespace ogfx
return true;
}
bool FontGlyphAtlas::save_atlas_png(int atlasIndex, const char* filename)
void FontGlyphAtlas::save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename)
{
if (atlasIndex < 0 || atlasIndex >= m_currentAtlasCount)
return false;
// 1. Get texture dimensions and format
float w, h;
SDL_GetTextureSize(atlas, &w, &h);
int iw = (int)w, ih = (int)h;
SDL_Texture* tex = m_atlases[atlasIndex];
if (!tex)
return false;
// 2. Create a target surface
SDL_Surface* surf = SDL_CreateSurface(iw, ih, SDL_PIXELFORMAT_ARGB8888);
if (!surf) return;
// --- 1. Query texture properties (SDL3 way) ---
SDL_PropertiesID props = SDL_GetTextureProperties(tex);
if (!props)
return false;
// 3. Read pixels back from the texture
SDL_Rect rect { 0, 0, iw, ih };
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);
// For STREAMING textures, lock/read directly:
void* pixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(atlas, nullptr, &pixels, &pitch))
{
uint8_t* src = (uint8_t*)pixels;
uint8_t* dst = (uint8_t*)surf->pixels;
for (int y = 0; y < ih; y++)
{
memcpy(dst, src, iw * 4);
src += pitch;
dst += surf->pitch;
}
SDL_UnlockTexture(atlas);
}
if (width <= 0 || height <= 0)
return false;
// 4. Save
IMG_SavePNG(surf, filename); // needs SDL3_image
// or: SDL_SaveBMP(surf, "atlas_debug.bmp");
// --- 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;
SDL_DestroySurface(surf);
}
}

View file

@ -33,6 +33,7 @@ namespace ogfx
{
ostd::Vec2 uvs[4];
SDL_Texture* atlas { nullptr };
ostd::Vec2 size;
};
struct GlyphKey
{
@ -49,30 +50,29 @@ namespace ogfx
};
struct GlyphKeyHasher
{
inline size_t operator()(const GlyphKey& k) const noexcept
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);
}
size_t h = std::hash<uint32_t>()(k.codepoint);
h ^= std::hash<uint64_t>()(k.fontID) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<uint32_t>()(k.pixelSize) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
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);
void save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename);
public:
inline static constexpr uint32_t AtlasTextureDimension { 8192 };
inline static constexpr uint32_t MaxAtlasCount { 16 };
private:
public:
std::unordered_map<GlyphKey, GlyphUV, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
int32_t m_currentAtlasCount { 0 };

View file

@ -50,7 +50,10 @@
#endif
#ifndef OX_DEBUG_BUILD
#define _DEBUG(n)
#ifndef OX_RELEASE_BUILD
#define OX_RELEASE_BUILD
#endif
#else
#define _DEBUG(n) std::cout << (int)n << "\n";
#endif

View file

@ -19,11 +19,51 @@
*/
#include "gui/Events.hpp"
#include <SDL3/SDL_rect.h>
#include <ogfx/utils/Keycodes.hpp>
#include <ogfx/ogfx.hpp>
ostd::ConsoleOutputHandler out;
void drawTexturedQuad(SDL_Renderer* renderer,
SDL_Texture* tex,
const ostd::Rectangle& rect,
const ostd::Vec2 uvs[4])
{
SDL_Vertex verts[4];
// Expand rectangle into quad corners
const float x = rect.x;
const float y = rect.y;
const float w = rect.w;
const float h = rect.h;
const ostd::Vec2 quad[4] = {
{ x, y },
{ x + w, y },
{ x + w, y + h },
{ x, y + h }
};
for (int i = 0; i < 4; i++)
{
verts[i].position.x = quad[i].x;
verts[i].position.y = quad[i].y;
verts[i].tex_coord.x = uvs[i].x;
verts[i].tex_coord.y = uvs[i].y;
auto crim = ostd::Colors::Crimson.getNormalizedColor();
verts[i].color = SDL_FColor{crim.r, crim.g, crim.b, crim.a};
}
// Two triangles: (0,1,2) and (2,3,0)
const int indices[6] = { 0, 1, 2, 2, 3, 0 };
SDL_RenderGeometry(renderer, tex, verts, 4, indices, 6);
}
class Window : public ogfx::gui::Window
{
public:
@ -65,14 +105,14 @@ class Window : public ogfx::gui::Window
m_label1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void {
std::cout << "SCROLL!\n";
});
addWidget(m_label1);
// addWidget(m_label1);
m_panel2.addChild(m_panel1);
addWidget(m_panel2);
// addWidget(m_panel2);
m_check1.setPosition(30, 30);
m_check1.setText("Check this out!");
addWidget(m_check1);
// addWidget(m_check1);
m_label2.setPosition(0, 0);
m_label2.setText("Ciccia Bella!");
@ -117,10 +157,55 @@ 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");
int32_t count = 0;
for (char c = 'A'; c <= 'Z'; c++)
{
for (int32_t i = 0; i < 200; i++)
{
count++;
uint32_t _fs = ostd::Random::getui32(8, 200);
m_gfx.setFontSize(_fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
}
}
for (char c = '0'; c <= '9'; c++)
{
for (int32_t i = 0; i < 200; i++)
{
count++;
uint32_t _fs = ostd::Random::getui32(8, 200);
m_gfx.setFontSize(_fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
}
}
for (char c = 'a'; c <= 'z'; c++)
{
count++;
for (int32_t i = 0; i < 200; i++)
{
uint32_t _fs = ostd::Random::getui32(8, 200);
m_gfx.setFontSize(_fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
}
}
for (char c = '0'; c <= '9'; c++)
{
for (int32_t i = 0; i < 200; i++)
{
count++;
uint32_t _fs = ostd::Random::getui32(8, 200);
m_gfx.setFontSize(_fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
}
}
// std::cout << m_theme.get<ostd::String>("panel.titlebarType", "", {}, {}) << " \n";
for (int32_t i = 0; i < m_gfx.getFontGlyphAtlas().m_currentAtlasCount; i++)
{
ostd::String file("./atlas");
file.add(i).add(".png");
m_gfx.getFontGlyphAtlas().save_atlas_to_png(m_gfx.getSDLRenderer(), m_gfx.getFontGlyphAtlas().m_atlases[i], file);
}
std::cout << "Atlas cound: " << (int)count << "\n";
}
inline void onSignal(ostd::Signal& signal) override
@ -145,6 +230,29 @@ class Window : public ogfx::gui::Window
// gfx.drawRect({ 50, 50, 200, 120 }, ostd::Colors::Aquamarine, 1);
// std::cout << (int)(gfx.getDrawCallCount() + 1) << "\n";
gfx.endFrame();
auto l_renderGlyph = [&](char c, float x, float y, float scale = 1) -> ostd::Vec2 {
auto& tex = m_gfx.getFontGlyphAtlas().m_atlases[0];
auto glyph = m_gfx.getFontGlyphAtlas().m_uvs[ { static_cast<uint32_t>(c), (uint64_t)gfx.getSDLFont(), fs }];
drawTexturedQuad(getSDLRenderer(), tex, { x, y, glyph.size.x * scale, glyph.size.y * scale }, glyph.uvs);
return glyph.size;
};
auto l_renderString = [&](const ostd::String& str, const ostd::Vec2& pos, float scale = 1.0f) -> void {
float x = pos.x;
float y = pos.y;
for (auto& c : str)
{
auto size = l_renderGlyph(c, x, y, scale);
x += (size.x * scale);
}
};
l_renderString("Hello", { 10, 140 });
gfx.renderText("Hello", 10, 100, ostd::Colors::Crimson, 30);
}
private:
@ -156,6 +264,7 @@ class Window : public ogfx::gui::Window
ogfx::gui::widgets::CheckBox m_check1 { *this };
ostd::Stylesheet m_theme;
ostd::Vec2 pos { 0, 0 };
uint32_t fs = 30;
};
int main(int argc, char** argv)