From fc3b28e94910de4f3cd2c2f4a0075e6077712a7f Mon Sep 17 00:00:00 2001 From: jpirnay Date: Sat, 21 Feb 2026 12:35:53 +0100 Subject: [PATCH] Deal with reader fonts (about 8-10% faster) --- lib/GfxRenderer/GfxRenderer.cpp | 152 +++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 20 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 1f839477..8a20e7de 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -213,8 +213,6 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b const bool pixelState, const GfxRenderer::Orientation orientation) { switch (orientation) { case GfxRenderer::LandscapeCounterClockwise: { - // phyX = screenXBase+glyphX, phyY = screenYBase+glyphY (identity mapping) - // Each glyph row is a contiguous physical h-span — read and write 8 px at a time. for (int glyphY = 0; glyphY < glyphHeight; glyphY++) { const int phyY = screenYBase + glyphY; if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; @@ -233,9 +231,6 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b } case GfxRenderer::LandscapeClockwise: { - // phyX = W-1-screenXBase-glyphX, phyY = H-1-screenYBase-glyphY (180° flip) - // glyphX=0 is rightmost; iterate glyph row right-to-left in 8-px chunks so each - // chunk writes a contiguous left-to-right physical h-span after bit-reversal. for (int glyphY = 0; glyphY < glyphHeight; glyphY++) { const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - (screenYBase + glyphY); if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; @@ -244,8 +239,6 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b for (int chunkEnd = glyphWidth - 1; chunkEnd >= 0; chunkEnd -= 8) { const int chunkStart = std::max(0, chunkEnd - 7); const int count = chunkEnd - chunkStart + 1; - // Read chunk in glyph (left-to-right) order then reverse bits so MSB maps to - // glyphX=chunkEnd, which is the leftmost physical pixel of this chunk. const uint8_t gbyte_fwd = bitmapExtract(bitmap, rowBitStart + chunkStart, count); const uint8_t gbyte = reverseBits8(gbyte_fwd >> (8 - count)); if (gbyte == 0) continue; @@ -258,13 +251,9 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b } case GfxRenderer::Portrait: { - // phyX = screenYBase+glyphY, phyY = H-1-screenXBase-glyphX (90° CW) - // A glyph column maps to a physical row. Process in 8-row × 8-col blocks: - // pack 8 glyph rows (sequential reads) into uint64_t → transpose8x8 → - // each output byte is one glyph column's bits, MSB = row 0 = smallest phyX. for (int glyphY = 0; glyphY < glyphHeight; glyphY += 8) { const int rowCount = std::min(8, glyphHeight - glyphY); - const int phyBitPos = screenYBase + glyphY; // leftmost phyX of this row-chunk + const int phyBitPos = screenYBase + glyphY; if (phyBitPos + rowCount <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; for (int glyphX = 0; glyphX < glyphWidth; glyphX += 8) { const int colCount = std::min(8, glyphWidth - glyphX); @@ -274,7 +263,6 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b pack |= static_cast(bitmapExtract(bitmap, bitStart, colCount)) << (56 - 8 * n); } pack = transpose8x8(pack); - // Byte k of pack = column (glyphX+k) bits, MSB = row 0 = leftmost phyX. for (int k = 0; k < colCount; k++) { const uint8_t cols_k = static_cast(pack >> (56 - 8 * k)); if (cols_k == 0) continue; @@ -288,25 +276,18 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b } case GfxRenderer::PortraitInverted: { - // phyX = W-1-screenYBase-glyphY, phyY = screenXBase+glyphX (90° CCW) - // Like Portrait but glyphY=0 is the rightmost physical pixel. Pack rows in - // reverse order (last row at uint64_t MSB) so the transposed column bytes already - // have MSB = last row = leftmost phyX — no bit-reversal step needed. for (int glyphY = 0; glyphY < glyphHeight; glyphY += 8) { const int rowCount = std::min(8, glyphHeight - glyphY); - // Leftmost phyX = W-1-screenYBase-(glyphY+rowCount-1). const int phyBitPos = HalDisplay::DISPLAY_WIDTH - 1 - screenYBase - (glyphY + rowCount - 1); if (phyBitPos + rowCount <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; for (int glyphX = 0; glyphX < glyphWidth; glyphX += 8) { const int colCount = std::min(8, glyphWidth - glyphX); - // Pack row (rowCount-1) at MSB down to row 0 at the lowest active byte. uint64_t pack = 0; int bitStart = glyphY * glyphWidth + glyphX; for (int n = 0; n < rowCount; n++, bitStart += glyphWidth) { pack |= static_cast(bitmapExtract(bitmap, bitStart, colCount)) << (56 - 8 * (rowCount - 1 - n)); } pack = transpose8x8(pack); - // Byte k = column (glyphX+k) bits, MSB = last row = leftmost phyX. for (int k = 0; k < colCount; k++) { const uint8_t cols_k = static_cast(pack >> (56 - 8 * k)); if (cols_k == 0) continue; @@ -321,6 +302,128 @@ static void renderGlyphFastBW(uint8_t* const frameBuffer, const uint8_t* const b } } +static inline uint8_t build2BitRowMask(const uint8_t* const bitmap, const int rowStartPixel, + const int glyphXStartOrEnd, const int count, + const bool reverseXInChunk, const GfxRenderer::RenderMode renderMode) { + // drawMask uses raw 2-bit glyph values directly from font bitmaps: + // raw 0=white, 1=light gray, 2=dark gray, 3=black. + // Bit N set means: draw/update when raw==N. + // This avoids per-pixel remap (bmpVal = 3 - raw) and branch chains in the hot loop. + const uint8_t drawMask = + (renderMode == GfxRenderer::BW) ? 0x0E : ((renderMode == GfxRenderer::GRAYSCALE_MSB) ? 0x06 : 0x04); + + uint8_t mask = 0; + for (int i = 0; i < count; i++) { + const int logicalX = reverseXInChunk ? (glyphXStartOrEnd - i) : (glyphXStartOrEnd + i); + const int pixelPosition = rowStartPixel + logicalX; + const uint8_t byte = bitmap[pixelPosition >> 2]; + const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2; + const uint8_t raw = static_cast((byte >> bit_index) & 0x3); + if ((drawMask >> raw) & 0x01) mask |= static_cast(1u << (7 - i)); + } + return mask; +} + +static void renderGlyphFast2Bit(uint8_t* const frameBuffer, const uint8_t* const bitmap, const int glyphWidth, + const int glyphHeight, const int screenXBase, const int screenYBase, + const bool pixelState, const GfxRenderer::Orientation orientation, + const GfxRenderer::RenderMode renderMode) { + // Non-rotated text fast path for 2-bit glyphs. Writes compact masks directly to framebuffer rows. + // TextRotation::Rotated90CW keeps the legacy per-pixel fallback path for safety and readability. + const bool writeState = (renderMode == GfxRenderer::BW) ? pixelState : false; + const uint8_t drawMask = + (renderMode == GfxRenderer::BW) ? 0x0E : ((renderMode == GfxRenderer::GRAYSCALE_MSB) ? 0x06 : 0x04); + + switch (orientation) { + case GfxRenderer::LandscapeCounterClockwise: { + for (int glyphY = 0; glyphY < glyphHeight; glyphY++) { + const int phyY = screenYBase + glyphY; + if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; + uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES; + const int rowStartPixel = glyphY * glyphWidth; + for (int glyphX = 0; glyphX < glyphWidth; glyphX += 8) { + const int count = std::min(8, glyphWidth - glyphX); + const uint8_t mask = build2BitRowMask(bitmap, rowStartPixel, glyphX, count, false, renderMode); + if (mask == 0) continue; + const int phyBitPos = screenXBase + glyphX; + if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; + writeRowBits(row, phyBitPos, mask, writeState); + } + } + break; + } + + case GfxRenderer::LandscapeClockwise: { + for (int glyphY = 0; glyphY < glyphHeight; glyphY++) { + const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - (screenYBase + glyphY); + if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; + uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES; + const int rowStartPixel = glyphY * glyphWidth; + for (int chunkEnd = glyphWidth - 1; chunkEnd >= 0; chunkEnd -= 8) { + const int chunkStart = std::max(0, chunkEnd - 7); + const int count = chunkEnd - chunkStart + 1; + const uint8_t mask = build2BitRowMask(bitmap, rowStartPixel, chunkEnd, count, true, renderMode); + if (mask == 0) continue; + const int phyBitPos = HalDisplay::DISPLAY_WIDTH - 1 - screenXBase - chunkEnd; + if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; + writeRowBits(row, phyBitPos, mask, writeState); + } + } + break; + } + + case GfxRenderer::Portrait: { + for (int glyphX = 0; glyphX < glyphWidth; glyphX++) { + const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - (screenXBase + glyphX); + if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; + uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES; + for (int glyphY = 0; glyphY < glyphHeight; glyphY += 8) { + const int count = std::min(8, glyphHeight - glyphY); + uint8_t mask = 0; + for (int i = 0; i < count; i++) { + const int logicalY = glyphY + i; + const int pixelPosition = logicalY * glyphWidth + glyphX; + const uint8_t byte = bitmap[pixelPosition >> 2]; + const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2; + const uint8_t raw = static_cast((byte >> bit_index) & 0x3); + if ((drawMask >> raw) & 0x01) mask |= static_cast(1u << (7 - i)); + } + if (mask == 0) continue; + const int phyBitPos = screenYBase + glyphY; + if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; + writeRowBits(row, phyBitPos, mask, writeState); + } + } + break; + } + + case GfxRenderer::PortraitInverted: { + for (int glyphX = 0; glyphX < glyphWidth; glyphX++) { + const int phyY = screenXBase + glyphX; + if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue; + uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES; + for (int glyphY = 0; glyphY < glyphHeight; glyphY += 8) { + const int count = std::min(8, glyphHeight - glyphY); + uint8_t mask = 0; + for (int i = 0; i < count; i++) { + const int logicalY = glyphY + (count - 1 - i); + const int pixelPosition = logicalY * glyphWidth + glyphX; + const uint8_t byte = bitmap[pixelPosition >> 2]; + const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2; + const uint8_t raw = static_cast((byte >> bit_index) & 0x3); + if ((drawMask >> raw) & 0x01) mask |= static_cast(1u << (7 - i)); + } + if (mask == 0) continue; + const int phyBitPos = HalDisplay::DISPLAY_WIDTH - 1 - screenYBase - (glyphY + count - 1); + if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue; + writeRowBits(row, phyBitPos, mask, writeState); + } + } + break; + } + } +} + // Shared glyph rendering logic for normal and rotated text. // Coordinate mapping and cursor advance direction are selected at compile time via the template parameter. template @@ -359,6 +462,15 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode } if (is2Bit) { + if constexpr (rotation == TextRotation::None) { + // Fast path for normal text orientation. Handles all device orientations via renderGlyphFast2Bit. + renderGlyphFast2Bit(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, pixelState, + renderer.getOrientation(), renderMode); + *cursorX += glyph->advanceX; + return; + } + + // Rotated text fallback: keep explicit per-pixel behavior. int pixelPosition = 0; for (int glyphY = 0; glyphY < height; glyphY++) { const int outerCoord = outerBase + glyphY;