Fix FontAtlas texture deletion bug - text now renders in WebGL

The FontAtlas class was missing move semantics, causing the GPU texture
to be deleted immediately after creation. When FontAtlas was moved into
the cache with std::move(), the default move constructor copied textureId_,
then the original's destructor deleted the texture the cache was using.

Added:
- Move constructor that transfers ownership and clears source textureId_
- Move assignment operator with proper cleanup of existing resources
- Deleted copy operations since GPU textures can't be shared

Also cleaned up the text fragment shader to use proper alpha sampling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-31 13:54:48 -05:00
commit 0811b76946
2 changed files with 54 additions and 4 deletions

View file

@ -96,7 +96,7 @@ void main() {
}
)";
// Text shader is same as sprite for now
// Text shader - uses alpha from texture, color from vertex
static const char* TEXT_VERTEX_SHADER = SPRITE_VERTEX_SHADER;
static const char* TEXT_FRAGMENT_SHADER = R"(
#ifdef GL_ES
@ -107,9 +107,11 @@ varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
// Text rendering: use texture alpha as coverage
float alpha = texture2D(u_texture, v_texcoord).a;
gl_FragColor = vec4(v_color.rgb, v_color.a * alpha);
// Font atlas stores glyph alpha in texture alpha channel
// RGB is white (255,255,255), alpha varies per glyph pixel
vec4 texSample = texture2D(u_texture, v_texcoord);
// Use vertex color for RGB, texture alpha for transparency
gl_FragColor = vec4(v_color.rgb, v_color.a * texSample.a);
}
)";
@ -1862,6 +1864,46 @@ bool Shader::isAvailable() {
FontAtlas::FontAtlas() = default;
FontAtlas::FontAtlas(FontAtlas&& other) noexcept
: textureId_(other.textureId_)
, fontSize_(other.fontSize_)
, ascent_(other.ascent_)
, descent_(other.descent_)
, lineHeight_(other.lineHeight_)
, glyphCache_(std::move(other.glyphCache_))
, stbFontInfo_(other.stbFontInfo_)
{
// Clear source to prevent double-deletion
other.textureId_ = 0;
other.stbFontInfo_ = nullptr;
}
FontAtlas& FontAtlas::operator=(FontAtlas&& other) noexcept {
if (this != &other) {
// Clean up existing resources
if (textureId_) {
SDL2Renderer::getInstance().deleteTexture(textureId_);
}
if (stbFontInfo_) {
delete static_cast<stbtt_fontinfo*>(stbFontInfo_);
}
// Transfer ownership
textureId_ = other.textureId_;
fontSize_ = other.fontSize_;
ascent_ = other.ascent_;
descent_ = other.descent_;
lineHeight_ = other.lineHeight_;
glyphCache_ = std::move(other.glyphCache_);
stbFontInfo_ = other.stbFontInfo_;
// Clear source to prevent double-deletion
other.textureId_ = 0;
other.stbFontInfo_ = nullptr;
}
return *this;
}
FontAtlas::~FontAtlas() {
if (textureId_) {
SDL2Renderer::getInstance().deleteTexture(textureId_);

View file

@ -154,6 +154,14 @@ public:
FontAtlas();
~FontAtlas();
// Move semantics - transfer ownership of GPU resources
FontAtlas(FontAtlas&& other) noexcept;
FontAtlas& operator=(FontAtlas&& other) noexcept;
// Disable copy - texture resources can't be shared
FontAtlas(const FontAtlas&) = delete;
FontAtlas& operator=(const FontAtlas&) = delete;
// Load font data
bool load(const unsigned char* fontData, size_t dataSize, float fontSize);