Implemented batched text rendering

This commit is contained in:
OmniaX-Dev 2026-04-14 06:46:31 +02:00
parent ceb25e8e38
commit 17fdf08a20
10 changed files with 722 additions and 943 deletions

BIN
extra/FreeSans.ttf Normal file

Binary file not shown.

View file

@ -5,7 +5,7 @@ $accentColor = Color(rgba(255, 0, 255, 255))
const $color2 = Color(#0000FFFF) const $color2 = Color(#0000FFFF)
% ------------------- % -------------------
window.backgroundColor = Color(rgba(0, 0, 0, 255)) window.backgroundColor = Color(rgba(160, 160, 160, 255))
$accentColor = Color(rgb(255, 0, 0)) $accentColor = Color(rgb(255, 0, 0))
@ -13,7 +13,7 @@ $accentColor = Color(rgb(255, 0, 0))
textColor = $accentColor textColor = $accentColor
backgroundColor = Color(#000000FF) backgroundColor = Color(#000000FF)
borderColor = $accentColor borderColor = $accentColor
fontSize = 50 fontSize = 20
borderRadius = 0 borderRadius = 0
borderWidth = 2 borderWidth = 2
showBackground = true showBackground = true
@ -65,7 +65,7 @@ $accentColor = Color(rgb(255, 0, 0))
(@testLabel label) { (@testLabel label) {
textColor = $color2 textColor = $color2
backgroundColor = Color(#000000FF) backgroundColor = Color(#000000FF)
fontSize = 20 fontSize = 50
} }
(@testLabel2 label) { (@testLabel2 label) {
@ -108,7 +108,6 @@ $accentColor = Color(rgb(255, 0, 0))
(@label3 label) { (@label3 label) {
textColor = $color2 textColor = $color2
backgroundColor = Color(#000000FF) backgroundColor = Color(#000000FF)
fontSize = 20
borderColor = Color(#FF0000FF) borderColor = Color(#FF0000FF)
borderWidth = 4 borderWidth = 4
borderRadius = 15 borderRadius = 15

View file

@ -14,6 +14,11 @@ Add theme caching
Implement callback for state changes in checkbox Implement callback for state changes in checkbox
FIX: WindowOutputHandler text rendering FIX: WindowOutputHandler text rendering
find a way to add fixed update to gui::Window find a way to add fixed update to gui::Window
Implement global scale (probably query desktop scale and adjust accordingly)
Fix: Text rendering bug in labels
Implement show/hide functionality for widgets
Implement wrappers in Renderer2D to take a center and size for rects
Implement following widgets: Implement following widgets:

View file

@ -1 +1 @@
2060 2061

View file

@ -73,6 +73,8 @@ namespace ogfx
void CheckBox::onMouseReleased(const Event& event) void CheckBox::onMouseReleased(const Event& event)
{ {
if (!isMouseInside())
return;
m_checked = !m_checked; m_checked = !m_checked;
setThemeQualifier("active", m_checked); setThemeQualifier("active", m_checked);
} }

View file

@ -211,44 +211,30 @@ namespace ogfx
return set_error_state(tErrors::NoError); return set_error_state(tErrors::NoError);
} }
ostd::IPoint BasicRenderer2D::getStringDimensions(const ostd::String& message, int32_t fontSize) ostd::Vec2 BasicRenderer2D::getStringDimensions(const ostd::String& message, int32_t fontSize, TTF_Font* font)
{ {
if (!m_initialized) if (!isValid()) return { 0, 0 };
{ if (fontSize <= 0) fontSize = m_fontSize;
set_error_state(tErrors::Uninitialized); if (!font) font = m_font;
return { 0, 0 }; int32_t oldFontSize = getFontSize();
}
if (!m_fontOpen)
{
set_error_state(tErrors::NoFont);
return { 0, 0 };
}
if (m_font == nullptr)
{
set_error_state(tErrors::NullFont);
return { 0, 0 };
}
setFontSize(fontSize); setFontSize(fontSize);
SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { 0, 0, 0, 255 });
if (surf == nullptr)
{
set_error_state(tErrors::TTFRenderTextBlendedFail);
print_ttf_error("TTF_RenderText_Blended");
return { 0, 0 };
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(m_window->getSDLRenderer(), surf);
if (texture == nullptr)
{
set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
print_ttf_error("SDL_CreateTextureFromSurface");
return { 0, 0 };
}
SDL_DestroySurface(surf);
float w = 0, h = 0; auto glyphs = m_fontGlyphAtlas.processString(message, font, fontSize);
SDL_GetTextureSize(texture, &w, &h); if (glyphs.empty()) return { 0, 0 };
SDL_DestroyTexture(texture);
return { static_cast<int32_t>(w), static_cast<int32_t>(h) }; float totalWidth = 0;
for (size_t i = 0; i < glyphs.size(); i++)
{
totalWidth += glyphs[i]->advance;
if (i > 0)
{
int kern = 0;
TTF_GetGlyphKerning(font, glyphs[i - 1]->codepoint, glyphs[i]->codepoint, &kern);
totalWidth += kern;
}
}
setFontSize(oldFontSize);
return { totalWidth, glyphs[0]->size.y };
} }
// ===================================================== UTILS ===================================================== // ===================================================== UTILS =====================================================
@ -326,14 +312,53 @@ namespace ogfx
drawImage(img, position, size, anim.getFrameRect()); drawImage(img, position, size, anim.getFrameRect());
} }
void BasicRenderer2D::drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize) void BasicRenderer2D::drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize, float scale)
{ {
if (!m_initialized) return; if (!isValid()) return;
if (fontSize <= 0)
fontSize = m_fontSize;
setFontSize(fontSize);
auto glyphs = m_fontGlyphAtlas.processString(str, m_font, fontSize);
if (glyphs.empty()) return;
float x = position.x;
float y = position.y;
for (size_t i = 0; i < glyphs.size(); i++)
{
auto& g = glyphs[i];
if (i > 0)
{
int kern = 0;
TTF_GetGlyphKerning(m_font, glyphs[i - 1]->codepoint, g->codepoint, &kern);
x += (kern * scale);
} }
void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize) ostd::Vec2 verts[4] = {
{ x, y },
{ x + g->size.x * scale, y },
{ x + g->size.x * scale, y + g->size.y * scale },
{ x, y + g->size.y * scale }
};
uint32_t inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, g->uvs, 4, inds, 6, color, g->atlas);
x += (g->advance * scale);
}
}
void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize, float scale)
{ {
if (!m_initialized) return; auto dims = getStringDimensions(str, fontSize);
drawString(str, { center.x - (dims.x * scale) * 0.5f, center.y - (dims.y * scale) * 0.5f }, color, fontSize, scale);
}
void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Rectangle& bounds, const ostd::Color& color, int32_t fontSize, float scale)
{
drawCenteredString(str, ostd::Vec2 { bounds.x + bounds.w * 0.5f, bounds.y + bounds.h * 0.5f }, color, fontSize, scale);
} }
// ===================================================== SPECIALIZED ===================================================== // ===================================================== SPECIALIZED =====================================================
@ -1059,99 +1084,5 @@ namespace ogfx
{ {
m_out.fg(ostd::ConsoleColors::Red).p(funcName).p("(...) failed: ").p(ostd::String(SDL_GetError())).reset().nl(); m_out.fg(ostd::ConsoleColors::Red).p(funcName).p("(...) failed: ").p(ostd::String(SDL_GetError())).reset().nl();
} }
void BasicRenderer2D::renderText(const ostd::String& message, int32_t x, int32_t y, const ostd::Color& color, int32_t fontSize)
{
if (!m_initialized)
{
set_error_state(tErrors::Uninitialized);
return;
}
if (!m_fontOpen)
{
set_error_state(tErrors::NoFont);
return;
}
if (m_font == nullptr)
{
set_error_state(tErrors::NullFont);
return;
}
setFontSize(fontSize);
SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { color.r, color.g, color.b, color.a });
if (surf == nullptr)
{
set_error_state(tErrors::TTFRenderTextBlendedFail);
print_ttf_error("TTF_RenderText_Blended");
return;
}
auto renderer = m_window->getSDLRenderer();
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surf);
if (texture == nullptr)
{
set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
print_ttf_error("SDL_CreateTextureFromSurface");
return;
}
SDL_DestroySurface(surf);
float w = 0, h = 0;
SDL_GetTextureSize(texture, &w, &h);
SDL_FRect dst;
dst.x = x;
dst.y = y;
dst.w = w;
dst.h = h;
SDL_RenderTexture(renderer, texture, NULL, &dst);
SDL_DestroyTexture(texture);
}
void BasicRenderer2D::renderCenteredText(const ostd::String& message, int32_t center_x, int32_t center_y, const ostd::Color& color, int32_t fontSize)
{
if (!m_initialized)
{
set_error_state(tErrors::Uninitialized);
return;
}
if (!m_fontOpen)
{
set_error_state(tErrors::NoFont);
return;
}
if (m_font == nullptr)
{
set_error_state(tErrors::NullFont);
return;
}
setFontSize(fontSize);
auto renderer = m_window->getSDLRenderer();
SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { color.r, color.g, color.b, color.a });
if (surf == nullptr)
{
set_error_state(tErrors::TTFRenderTextBlendedFail);
print_ttf_error("TTF_RenderText_Blended");
return;
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surf);
if (texture == nullptr)
{
set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
print_ttf_error("SDL_CreateTextureFromSurface");
return;
}
SDL_DestroySurface(surf);
float w = 0, h = 0;
SDL_GetTextureSize(texture, &w, &h);
SDL_FRect dst;
dst.x = center_x - (w / 2);
dst.y = center_y - (h / 2);
dst.w = w;
dst.h = h;
SDL_RenderTexture(renderer, texture, NULL, &dst);
SDL_DestroyTexture(texture);
}
// ===================================================== PRIVATE HELPERS ===================================================== // ===================================================== PRIVATE HELPERS =====================================================
} }

View file

@ -68,7 +68,7 @@ namespace ogfx
int32_t openFont(const ostd::String& fontPath, int32_t fontSize = 0); int32_t openFont(const ostd::String& fontPath, int32_t fontSize = 0);
int32_t openFont(const ostd::UByte resource_data[], uint32_t data_size, int32_t fontSize = 0); int32_t openFont(const ostd::UByte resource_data[], uint32_t data_size, int32_t fontSize = 0);
int32_t setFontSize(int32_t fontSize); int32_t setFontSize(int32_t fontSize);
ostd::IPoint getStringDimensions(const ostd::String& message, int32_t fontSize = 0); ostd::Vec2 getStringDimensions(const ostd::String& message, int32_t fontSize = 0, TTF_Font* font = nullptr);
inline uint32_t getDrawCallCount(void) { return m_drawCallCount; } inline uint32_t getDrawCallCount(void) { return m_drawCallCount; }
inline bool hasOpenFont(void) { return m_fontOpen; } inline bool hasOpenFont(void) { return m_fontOpen; }
@ -83,8 +83,10 @@ namespace ogfx
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 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 }); void drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 });
void drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize = 0); void drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
void drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize = 0); void drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
void drawCenteredString(const ostd::String& str, const ostd::Rectangle& bounds, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
void drawLine(const ostd::FLine& line, const ostd::Color& color, int32_t thickness = 1, bool rounded = true); void drawLine(const ostd::FLine& line, const ostd::Color& color, int32_t thickness = 1, bool rounded = true);
@ -106,7 +108,7 @@ namespace ogfx
void outlinedCircle(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1); 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); void outlinedEllipse(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1);
public: private:
void init_arrays(void); 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_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); void generate_quarter_circle(const ostd::Vec2& center, float radius, float thickness, float startAngle, const ostd::Color& color, int segments);
@ -117,8 +119,6 @@ namespace ogfx
void push_polygon(const ostd::Vec2* verts, const ostd::Vec2* texCoords, uint32_t vertCount, const uint32_t* inds, uint32_t indexCount, const ostd::Color& color, SDL_Texture* texture); void push_polygon(const ostd::Vec2* verts, const ostd::Vec2* texCoords, uint32_t vertCount, const uint32_t* inds, uint32_t indexCount, const ostd::Color& color, SDL_Texture* texture);
void print_ttf_error(const ostd::String& funcName); void print_ttf_error(const ostd::String& funcName);
inline int32_t set_error_state(int32_t err) { m_errorState = err; return m_errorState; } inline int32_t set_error_state(int32_t err) { m_errorState = err; return m_errorState; }
void renderText(const ostd::String& message, int32_t x, int32_t y, const ostd::Color& color, int32_t fontSize = 0);
void renderCenteredText(const ostd::String& message, int32_t center_x, int32_t center_y, const ostd::Color& color, int32_t fontSize = 0);
public: public:
inline static constexpr int32_t MaxVertices { 8192 }; inline static constexpr int32_t MaxVertices { 8192 };

View file

@ -23,6 +23,7 @@
#include <SDL3/SDL_pixels.h> #include <SDL3/SDL_pixels.h>
#include <SDL3/SDL_render.h> #include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h> #include <SDL3/SDL_surface.h>
#include <SDL3_ttf/SDL_ttf.h>
namespace ogfx namespace ogfx
{ {
@ -37,45 +38,67 @@ namespace ogfx
return *this; return *this;
} }
bool FontGlyphAtlas::rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize) const std::vector<const FontGlyphAtlas::GlyphInfo*> FontGlyphAtlas::processString(const ostd::String& str, TTF_Font* font, uint32_t fontSize)
{
if (m_currentAtlasCount <= 0)
return {};
// Pass 1: ensure all glyphs are rasterized (map may rehash here)
for (auto& c : str)
{
const GlyphInfo* dummy;
if (!rasterize_glyph(ostd::String("").addChar(c), font, fontSize, &dummy))
return {};
}
// Pass 2: collect stable pointers (no more insertions, no rehash risk)
std::vector<const GlyphInfo*> glyphs;
glyphs.reserve(str.len());
for (auto& c : str)
{
auto cps = ostd::String("").addChar(c).getUTF8Codepoints();
if (cps.size() != 1) return {};
GlyphKey key { cps[0], uint64_t(font), fontSize };
glyphs.push_back(&m_uvs[key]);
}
return glyphs;
}
bool FontGlyphAtlas::rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize, const GlyphInfo** outGlyph)
{ {
if (!font || m_currentAtlasCount <= 0) if (!font || m_currentAtlasCount <= 0)
return false; return false;
// 1. Extract codepoint from the ostd::String
auto cps = glyphStr.getUTF8Codepoints(); auto cps = glyphStr.getUTF8Codepoints();
if (cps.size() != 1) if (cps.size() != 1)
return false; // must be exactly one character return false;
uint32_t codepoint = cps[0]; uint32_t codepoint = cps[0];
GlyphKey key { codepoint, uint64_t(font), fontSize }; GlyphKey key { codepoint, uint64_t(font), fontSize };
if (auto it = m_uvs.find(key) != m_uvs.end()) auto it = m_uvs.find(key);
if (it != m_uvs.end())
{
*outGlyph = &(it->second);
return true; return true;
}
// 2. Convert the ostd::String to UTF-8 for SDL_ttf SDL_Surface* surf = TTF_RenderText_Blended(font, glyphStr.c_str(), glyphStr.len(), SDL_Color{255, 255, 255, 255});
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) if (!surf)
return false; return false;
// SDL_Surface* converted = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32);
// SDL_DestroySurface(surf);
// surf = converted;
int gw = surf->w; int gw = surf->w;
int gh = surf->h; int gh = surf->h;
// 4. Get current atlas int minx, maxx, miny, maxy, advance;
TTF_GetGlyphMetrics(font, codepoint, &minx, &maxx, &miny, &maxy, &advance);
GlyphInfo glyph;
glyph.advance = advance;
glyph.codepoint = codepoint;
SDL_Texture* atlas = m_atlases[m_currentAtlasCount - 1]; 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)) if (m_penX + gw > int(AtlasTextureDimension))
{ {
m_penX = 0; m_penX = 0;
@ -83,13 +106,12 @@ namespace ogfx
m_rowHeight = 0; m_rowHeight = 0;
} }
// 6. If still doesn't fit → create new atlas
if (m_penY + gh > int(AtlasTextureDimension)) if (m_penY + gh > int(AtlasTextureDimension))
{ {
SDL_DestroySurface(surf); SDL_DestroySurface(surf);
if (!create_new_atlas()) if (!create_new_atlas())
return false; // no more atlases available return false;
atlas = m_atlases[m_currentAtlasCount - 1]; atlas = m_atlases[m_currentAtlasCount - 1];
m_penX = 0; m_penX = 0;
@ -97,74 +119,28 @@ namespace ogfx
m_rowHeight = 0; m_rowHeight = 0;
} }
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_Rect dstRect { m_penX, m_penY, gw, gh };
SDL_LockSurface(surf); SDL_LockSurface(surf);
SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch); SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
SDL_UnlockSurface(surf); SDL_UnlockSurface(surf);
SDL_DestroySurface(surf); SDL_DestroySurface(surf);
// void* lockedPixels = nullptr;
// int lockedPitch = 0;
// if (SDL_LockTexture(atlas, &dstRect, &lockedPixels, &lockedPitch))
// {
// SDL_LockSurface(surf);
// 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_UnlockSurface(surf);
// SDL_UnlockTexture(atlas);
// }
// // SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
// SDL_DestroySurface(surf);
// 8. Compute UVs
float u0 = float(m_penX) / float(AtlasTextureDimension); float u0 = float(m_penX) / float(AtlasTextureDimension);
float v0 = float(m_penY) / float(AtlasTextureDimension); float v0 = float(m_penY) / float(AtlasTextureDimension);
float u1 = float(m_penX + gw) / float(AtlasTextureDimension); float u1 = float(m_penX + gw) / float(AtlasTextureDimension);
float v1 = float(m_penY + gh) / float(AtlasTextureDimension); float v1 = float(m_penY + gh) / float(AtlasTextureDimension);
GlyphUV uv; glyph.atlas = atlas;
uv.atlas = atlas; glyph.uvs[0] = { u0, v0 };
uv.uvs[0] = { u0, v0 }; glyph.uvs[1] = { u1, v0 };
uv.uvs[1] = { u1, v0 }; glyph.uvs[2] = { u1, v1 };
uv.uvs[2] = { u1, v1 }; glyph.uvs[3] = { u0, v1 };
uv.uvs[3] = { u0, v1 }; glyph.size = { static_cast<float>(gw), static_cast<float>(gh) };
uv.size = { static_cast<float>(gw), static_cast<float>(gh) };
// 9. Store in hashmap m_uvs[key] = glyph;
m_uvs[key] = uv; *outGlyph = &(m_uvs[key]);
// 10. Advance shelf packing cursor
m_penX += gw; m_penX += gw;
m_rowHeight = std::max(m_rowHeight, gh); m_rowHeight = std::max(m_rowHeight, gh);
return true; return true;
@ -172,7 +148,6 @@ namespace ogfx
bool FontGlyphAtlas::create_new_atlas(void) bool FontGlyphAtlas::create_new_atlas(void)
{ {
// 1. Check if we can create another atlas
if (m_currentAtlasCount >= int(MaxAtlasCount)) if (m_currentAtlasCount >= int(MaxAtlasCount))
return false; return false;
@ -183,18 +158,15 @@ namespace ogfx
if (!sdlRenderer) if (!sdlRenderer)
return false; return false;
// 2. Create the texture SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
if (!tex) if (!tex)
return false; return false;
// 3. Set blend mode (important for alpha glyphs)
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR); SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
// 4. Clear the texture to transparent
{ { // Clear the texture to transparent
void* pixels = nullptr; void* pixels = nullptr;
int pitch = 0; int pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0) if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0)
@ -204,52 +176,13 @@ namespace ogfx
} }
} }
// 5. Store the atlas
m_atlases[m_currentAtlasCount] = tex; m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++; m_currentAtlasCount++;
// 6. Reset packing cursor for this new atlas
m_penX = 0; m_penX = 0;
m_penY = 0; m_penY = 0;
m_rowHeight = 0; m_rowHeight = 0;
return true; return true;
} }
void FontGlyphAtlas::save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename)
{
// 1. Get texture dimensions and format
float w, h;
SDL_GetTextureSize(atlas, &w, &h);
int iw = (int)w, ih = (int)h;
// 2. Create a target surface
SDL_Surface* surf = SDL_CreateSurface(iw, ih, SDL_PIXELFORMAT_ARGB8888);
if (!surf) return;
// 3. Read pixels back from the texture
SDL_Rect rect { 0, 0, iw, ih };
// 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);
}
// 4. Save
IMG_SavePNG(surf, filename); // needs SDL3_image
// or: SDL_SaveBMP(surf, "atlas_debug.bmp");
SDL_DestroySurface(surf);
}
} }

View file

@ -29,11 +29,13 @@ namespace ogfx
class BasicRenderer2D; class BasicRenderer2D;
class FontGlyphAtlas class FontGlyphAtlas
{ {
public: struct GlyphUV public: struct GlyphInfo
{ {
ostd::Vec2 uvs[4]; ostd::Vec2 uvs[4];
SDL_Texture* atlas { nullptr }; SDL_Texture* atlas { nullptr };
ostd::Vec2 size; ostd::Vec2 size;
uint32_t codepoint { 0 };
int32_t advance { 0 };
}; };
struct GlyphKey struct GlyphKey
{ {
@ -62,18 +64,18 @@ namespace ogfx
inline FontGlyphAtlas(void) { } inline FontGlyphAtlas(void) { }
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); } inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer); FontGlyphAtlas init(BasicRenderer2D& renderer);
const std::vector<const GlyphInfo*> processString(const ostd::String& str, TTF_Font* font, uint32_t fontSize);
public: private:
bool rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize); bool rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize, const GlyphInfo** outGlyph);
bool create_new_atlas(void); bool create_new_atlas(void);
void save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename);
public: public:
inline static constexpr uint32_t AtlasTextureDimension { 8192 }; inline static constexpr uint32_t AtlasTextureDimension { 8192 };
inline static constexpr uint32_t MaxAtlasCount { 16 }; inline static constexpr uint32_t MaxAtlasCount { 16 };
public: public:
std::unordered_map<GlyphKey, GlyphUV, GlyphKeyHasher> m_uvs; std::unordered_map<GlyphKey, GlyphInfo, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount]; SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
int32_t m_currentAtlasCount { 0 }; int32_t m_currentAtlasCount { 0 };
BasicRenderer2D* m_renderer { nullptr }; BasicRenderer2D* m_renderer { nullptr };

View file

@ -25,10 +25,7 @@
ostd::ConsoleOutputHandler out; ostd::ConsoleOutputHandler out;
void drawTexturedQuad(SDL_Renderer* renderer, void drawTexturedQuad(SDL_Renderer* renderer, SDL_Texture* tex, const ostd::Rectangle& rect, const ostd::Vec2 uvs[4])
SDL_Texture* tex,
const ostd::Rectangle& rect,
const ostd::Vec2 uvs[4])
{ {
SDL_Vertex verts[4]; SDL_Vertex verts[4];
@ -105,14 +102,14 @@ class Window : public ogfx::gui::Window
m_label1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void { m_label1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void {
std::cout << "SCROLL!\n"; std::cout << "SCROLL!\n";
}); });
// addWidget(m_label1); addWidget(m_label1);
m_panel2.addChild(m_panel1); m_panel2.addChild(m_panel1);
// addWidget(m_panel2); addWidget(m_panel2);
m_check1.setPosition(30, 30); m_check1.setPosition(30, 30);
m_check1.setText("Check this out!"); m_check1.setText("Check this out!");
// addWidget(m_check1); addWidget(m_check1);
m_label2.setPosition(0, 0); m_label2.setPosition(0, 0);
m_label2.setText("Ciccia Bella!"); m_label2.setText("Ciccia Bella!");
@ -156,62 +153,6 @@ class Window : public ogfx::gui::Window
m_panel1.addChild(m_label3); m_panel1.addChild(m_label3);
setTheme(m_theme); setTheme(m_theme);
m_gfx.setFontSize(fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('H'), m_gfx.getSDLFont(), fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('e'), m_gfx.getSDLFont(), fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('l'), m_gfx.getSDLFont(), fs);
m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('o'), m_gfx.getSDLFont(), fs);
// 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);
// }
// }
// 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 inline void onSignal(ostd::Signal& signal) override
@ -226,39 +167,6 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override void onRedraw(ogfx::BasicRenderer2D& gfx) override
{ {
// auto l_rndPoint = [&](void) -> ostd::FPoint {
// using rnd = ostd::Random;
// return rnd::getVec2({ 0, (float)getWindowWidth() / 2 }, { 0, (float)getWindowHeight() / 2 });
// };
// for (int32_t i = 0; i < 1000; i++)
// gfx.drawLine({ l_rndPoint(), l_rndPoint() }, ostd::Colors::Crimson, 10);
// gfx.drawLine({ { 10, 10 }, { 200, 100 } }, ostd::Colors::Crimson, 3);
// 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, fs);
} }
private: private:
@ -270,7 +178,6 @@ class Window : public ogfx::gui::Window
ogfx::gui::widgets::CheckBox m_check1 { *this }; ogfx::gui::widgets::CheckBox m_check1 { *this };
ostd::Stylesheet m_theme; ostd::Stylesheet m_theme;
ostd::Vec2 pos { 0, 0 }; ostd::Vec2 pos { 0, 0 };
uint32_t fs = 30;
}; };
int main(int argc, char** argv) int main(int argc, char** argv)