diff --git a/CMakeLists.txt b/CMakeLists.txt index 49d3625..265f910 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,10 +269,6 @@ if(EMSCRIPTEN) --preload-file=${CMAKE_SOURCE_DIR}/src/scripts@/scripts # Preload assets --preload-file=${CMAKE_SOURCE_DIR}/assets@/assets - # Use custom HTML shell for crisp pixel rendering - --shell-file=${CMAKE_SOURCE_DIR}/src/shell.html - # Pre-JS to fix browser zoom causing undefined values in events - --pre-js=${CMAKE_SOURCE_DIR}/src/emscripten_pre.js ) # Add SDL2 options if using SDL2 backend @@ -292,9 +288,6 @@ if(EMSCRIPTEN) target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS}) - # Output as HTML to use the shell file - set_target_properties(mcrogueface PROPERTIES SUFFIX ".html") - # Set Python home for the embedded interpreter target_compile_definitions(mcrogueface PRIVATE MCRF_WASM_PYTHON_HOME="/lib/python3.14" diff --git a/src/emscripten_pre.js b/src/emscripten_pre.js deleted file mode 100644 index a4b4108..0000000 --- a/src/emscripten_pre.js +++ /dev/null @@ -1,69 +0,0 @@ -// Pre-JS file for McRogueFace Emscripten build -// This runs BEFORE Emscripten's code, allowing us to patch browser quirks - -// Fix for browser zoom causing undefined values in resize events -// When browser zoom changes, some event/window properties can become undefined -// which causes Emscripten's HEAP32 writes to fail with assertion errors - -(function() { - 'use strict'; - - // Store original addEventListener - var originalAddEventListener = EventTarget.prototype.addEventListener; - - // Properties that Emscripten's uiEventHandlerFunc reads - // These need to be integers, not undefined - var windowIntegerProps = [ - 'innerWidth', 'innerHeight', - 'outerWidth', 'outerHeight', - 'pageXOffset', 'pageYOffset' - ]; - - // Ensure window properties return integers even during zoom transitions - windowIntegerProps.forEach(function(prop) { - var descriptor = Object.getOwnPropertyDescriptor(window, prop); - if (descriptor && descriptor.get) { - var originalGetter = descriptor.get; - Object.defineProperty(window, prop, { - get: function() { - var val = originalGetter.call(this); - // Return 0 if undefined/null, otherwise floor to integer - return (val === undefined || val === null) ? 0 : Math.floor(val); - }, - configurable: true - }); - } - }); - - // Wrap addEventListener to intercept resize/scroll events - EventTarget.prototype.addEventListener = function(type, listener, options) { - if (type === 'resize' || type === 'scroll') { - var wrappedListener = function(e) { - // Ensure e.detail is an integer - if (e.detail === undefined || e.detail === null) { - // Create a new event with detail = 0 - try { - Object.defineProperty(e, 'detail', { - value: 0, - writable: false - }); - } catch (ex) { - // If we can't modify, create a proxy event - e = new Proxy(e, { - get: function(target, prop) { - if (prop === 'detail') return 0; - var val = target[prop]; - return typeof val === 'function' ? val.bind(target) : val; - } - }); - } - } - return listener.call(this, e); - }; - return originalAddEventListener.call(this, type, wrappedListener, options); - } - return originalAddEventListener.call(this, type, listener, options); - }; - - console.log('McRogueFace: Emscripten pre-JS patches applied'); -})(); diff --git a/src/platform/SDL2Renderer.cpp b/src/platform/SDL2Renderer.cpp index fa7bc01..24c1c68 100644 --- a/src/platform/SDL2Renderer.cpp +++ b/src/platform/SDL2Renderer.cpp @@ -96,7 +96,7 @@ void main() { } )"; -// Text shader - uses alpha from texture, color from vertex +// Text shader is same as sprite for now static const char* TEXT_VERTEX_SHADER = SPRITE_VERTEX_SHADER; static const char* TEXT_FRAGMENT_SHADER = R"( #ifdef GL_ES @@ -107,11 +107,9 @@ varying vec2 v_texcoord; uniform sampler2D u_texture; void main() { - // 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); + // 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); } )"; @@ -1602,6 +1600,8 @@ void Sprite::draw(RenderTarget& target, RenderStates states) const { // Static cache for font atlases - keyed by (font data pointer, character size) static std::map, FontAtlas> s_fontAtlasCache; +static int textDebugCount = 0; + void Text::draw(RenderTarget& target, RenderStates states) const { if (!font_ || string_.empty() || !font_->isLoaded()) return; @@ -1611,102 +1611,96 @@ void Text::draw(RenderTarget& target, RenderStates states) const { if (it == s_fontAtlasCache.end()) { FontAtlas atlas; if (!atlas.load(font_->getData(), font_->getDataSize(), static_cast(characterSize_))) { + std::cerr << "Text::draw: Failed to create font atlas!" << std::endl; return; // Failed to create atlas } + std::cout << "Text::draw: Created font atlas, textureId=" << atlas.getTextureId() << std::endl; it = s_fontAtlasCache.emplace(key, std::move(atlas)).first; } const FontAtlas& atlas = it->second; - Transform combined = states.transform * getTransform(); - - // Helper lambda to build glyph geometry with a given color and offset - auto buildGlyphs = [&](const Color& color, float offsetX, float offsetY, - std::vector& verts, std::vector& uvs, std::vector& cols) { - float x = 0; - float y = atlas.getAscent(); - - float r = color.r / 255.0f; - float g = color.g / 255.0f; - float b = color.b / 255.0f; - float a = color.a / 255.0f; - - for (size_t i = 0; i < string_.size(); ++i) { - char c = string_[i]; - - if (c == '\n') { - x = 0; - y += atlas.getLineHeight(); - continue; - } - - FontAtlas::GlyphInfo glyph; - if (!atlas.getGlyph(static_cast(c), glyph)) { - if (!atlas.getGlyph(' ', glyph)) { - continue; - } - } - - // Calculate quad corners with offset - float x0 = x + glyph.xoff + offsetX; - float y0 = y + glyph.yoff + offsetY; - float x1 = x0 + glyph.width; - float y1 = y0 + glyph.height; - - // Transform to world space - Vector2f p0 = combined.transformPoint(x0, y0); - Vector2f p1 = combined.transformPoint(x1, y0); - Vector2f p2 = combined.transformPoint(x1, y1); - Vector2f p3 = combined.transformPoint(x0, y1); - - verts.insert(verts.end(), { - p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, - p0.x, p0.y, p2.x, p2.y, p3.x, p3.y - }); - - uvs.insert(uvs.end(), { - glyph.u0, glyph.v0, glyph.u1, glyph.v0, glyph.u1, glyph.v1, - glyph.u0, glyph.v0, glyph.u1, glyph.v1, glyph.u0, glyph.v1 - }); - - for (int v = 0; v < 6; ++v) { - cols.insert(cols.end(), {r, g, b, a}); - } - - x += glyph.xadvance; - } - }; - - // Draw outline first (if any) - if (outlineThickness_ > 0 && outlineColor_.a > 0) { - std::vector outlineVerts, outlineUVs, outlineCols; - - // Draw at 8 positions around each glyph for outline effect - float t = outlineThickness_; - float offsets[][2] = { - {-t, -t}, {0, -t}, {t, -t}, - {-t, 0}, {t, 0}, - {-t, t}, {0, t}, {t, t} - }; - - for (auto& off : offsets) { - buildGlyphs(outlineColor_, off[0], off[1], outlineVerts, outlineUVs, outlineCols); - } - - if (!outlineVerts.empty()) { - SDL2Renderer::getInstance().drawTriangles( - outlineVerts.data(), outlineVerts.size() / 2, - outlineCols.data(), outlineUVs.data(), - atlas.getTextureId(), - SDL2Renderer::ShaderType::Text - ); - } + // Debug: log first few text draws + if (textDebugCount < 3) { + std::cout << "Text::draw: string='" << string_.substr(0, 20) << "' atlasTexId=" << atlas.getTextureId() + << " fillColor=(" << (int)fillColor_.r << "," << (int)fillColor_.g << "," << (int)fillColor_.b << ")" << std::endl; + textDebugCount++; } - // Draw fill text on top - std::vector vertices, texcoords, colors; - buildGlyphs(fillColor_, 0, 0, vertices, texcoords, colors); + Transform combined = states.transform * getTransform(); + + // Build vertex data for all glyphs + std::vector vertices; + std::vector texcoords; + std::vector colors; + + float x = 0; + float y = atlas.getAscent(); // Start at baseline + + float r = fillColor_.r / 255.0f; + float g = fillColor_.g / 255.0f; + float b = fillColor_.b / 255.0f; + float a = fillColor_.a / 255.0f; + + for (size_t i = 0; i < string_.size(); ++i) { + char c = string_[i]; + + // Handle newlines + if (c == '\n') { + x = 0; + y += atlas.getLineHeight(); + continue; + } + + FontAtlas::GlyphInfo glyph; + if (!atlas.getGlyph(static_cast(c), glyph)) { + // Try space for unknown glyphs + if (!atlas.getGlyph(' ', glyph)) { + continue; + } + } + + // Calculate quad corners in local space + float x0 = x + glyph.xoff; + float y0 = y + glyph.yoff; + float x1 = x0 + glyph.width; + float y1 = y0 + glyph.height; + + // Transform to world space + Vector2f p0 = combined.transformPoint(x0, y0); + Vector2f p1 = combined.transformPoint(x1, y0); + Vector2f p2 = combined.transformPoint(x1, y1); + Vector2f p3 = combined.transformPoint(x0, y1); + + // Two triangles for quad + vertices.insert(vertices.end(), { + p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, // Triangle 1 + p0.x, p0.y, p2.x, p2.y, p3.x, p3.y // Triangle 2 + }); + + texcoords.insert(texcoords.end(), { + glyph.u0, glyph.v0, glyph.u1, glyph.v0, glyph.u1, glyph.v1, // Triangle 1 + glyph.u0, glyph.v0, glyph.u1, glyph.v1, glyph.u0, glyph.v1 // Triangle 2 + }); + + // 6 vertices * 4 color components + for (int v = 0; v < 6; ++v) { + colors.insert(colors.end(), {r, g, b, a}); + } + + x += glyph.xadvance; + } if (!vertices.empty()) { + // Debug: log first glyph's UVs + static int uvDebugCount = 0; + if (uvDebugCount < 2 && texcoords.size() >= 12) { + std::cout << "Text UVs for first glyph: u0=" << texcoords[0] << " v0=" << texcoords[1] + << " u1=" << texcoords[4] << " v1=" << texcoords[5] + << " (vertexCount=" << (vertices.size()/2) << ")" << std::endl; + uvDebugCount++; + } + + // Use text shader (uses alpha from texture, not full RGBA multiply) SDL2Renderer::getInstance().drawTriangles( vertices.data(), vertices.size() / 2, colors.data(), texcoords.data(), @@ -1868,46 +1862,6 @@ 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(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_); diff --git a/src/platform/SDL2Renderer.h b/src/platform/SDL2Renderer.h index 3a34238..8246175 100644 --- a/src/platform/SDL2Renderer.h +++ b/src/platform/SDL2Renderer.h @@ -154,14 +154,6 @@ 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); diff --git a/src/shell.html b/src/shell.html deleted file mode 100644 index a1ab612..0000000 --- a/src/shell.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - McRogueFace - WebGL - - - -

McRogueFace

-
Downloading...
-
- -
- - - {{{ SCRIPT }}} - -