Optimize 1bit font rendering
This commit is contained in:
@@ -61,6 +61,250 @@ static inline void rotateCoordinates(const GfxRenderer::Orientation orientation,
|
|||||||
|
|
||||||
enum class TextRotation { None, Rotated90CW };
|
enum class TextRotation { None, Rotated90CW };
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Fast-path glyph rendering helpers (1-bit BW fonts, TextRotation::None)
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// OVERVIEW
|
||||||
|
// --------
|
||||||
|
// The legacy path called drawPixel() once per set glyph pixel. drawPixel()
|
||||||
|
// invokes rotateCoordinates() (a switch), does a bounds check, logs on OOB,
|
||||||
|
// then writes one bit. For a typical 10×14 UI glyph that is ~100 calls.
|
||||||
|
//
|
||||||
|
// This fast path eliminates drawPixel() entirely by writing directly to the
|
||||||
|
// framebuffer in up to 8-pixel chunks via writeRowBits().
|
||||||
|
//
|
||||||
|
// FRAMEBUFFER LAYOUT
|
||||||
|
// ------------------
|
||||||
|
// 1 bpp, MSB-first, DISPLAY_WIDTH (800) pixels per row stored in
|
||||||
|
// DISPLAY_WIDTH_BYTES (100) bytes. Bit 7 of byte 0 = leftmost pixel of
|
||||||
|
// row 0. "Physical row" phyY occupies bytes [phyY*100 .. phyY*100+99].
|
||||||
|
// A set bit (1) is WHITE; a cleared bit (0) is BLACK.
|
||||||
|
//
|
||||||
|
// LANDSCAPE ORIENTATIONS (2.5–3.1× speedup vs legacy)
|
||||||
|
// -------------------------------------------------------
|
||||||
|
// phyX and phyY are both linear functions of glyphX/glyphY in these modes,
|
||||||
|
// so each glyph row maps directly to a physical framebuffer row.
|
||||||
|
//
|
||||||
|
// LandscapeCounterClockwise: phyX = screenXBase+glyphX, phyY = screenYBase+glyphY
|
||||||
|
// LandscapeClockwise: phyX = W-1-screenXBase-glyphX, phyY = H-1-screenYBase-glyphY
|
||||||
|
//
|
||||||
|
// Strategy: outer loop over glyphY (one physical row per iteration), inner
|
||||||
|
// loop reads 8-pixel chunks of that glyph row with bitmapExtract() and writes
|
||||||
|
// them with writeRowBits(). Bitmap access is purely sequential — fastest.
|
||||||
|
// LandscapeClockwise iterates glyph chunks right-to-left and applies
|
||||||
|
// reverseBits8() to flip horizontal direction.
|
||||||
|
//
|
||||||
|
// PORTRAIT ORIENTATIONS (~2× speedup vs legacy)
|
||||||
|
// -----------------------------------------------
|
||||||
|
// Portrait (90° CW panel rotation):
|
||||||
|
// phyX = screenYBase+glyphY, phyY = H-1-screenXBase-glyphX
|
||||||
|
// PortraitInverted (90° CCW panel rotation):
|
||||||
|
// phyX = W-1-screenYBase-glyphY, phyY = screenXBase+glyphX
|
||||||
|
//
|
||||||
|
// Here glyph COLUMNS map to physical rows. Naively iterating column-by-column
|
||||||
|
// reads the bitmap with stride glyphWidth — cache-unfriendly and one bit at a
|
||||||
|
// time. Instead we use an 8×8 bit-matrix transpose:
|
||||||
|
//
|
||||||
|
// For each 8-row × 8-column glyph block:
|
||||||
|
// 1. Read 8 consecutive glyph rows (sequential bitmap access) into the
|
||||||
|
// top 8 bytes of a uint64_t (one bitmapExtract per row).
|
||||||
|
// 2. Call transpose8x8() — an O(log 8) butterfly transform — to swap
|
||||||
|
// the role of rows and columns in 3 passes of XOR-masking.
|
||||||
|
// 3. The resulting uint64_t holds 8 column bytes: byte k contains the
|
||||||
|
// bits for glyph column glyphX+k, one per physical row, MSB-aligned.
|
||||||
|
// 4. Write each column byte with writeRowBits() to its physical row.
|
||||||
|
//
|
||||||
|
// For PortraitInverted the glyph rows are packed in reverse order (last row
|
||||||
|
// at MSB of the uint64_t) before transposing. This ensures the post-transpose
|
||||||
|
// column bytes are already correctly ordered (MSB = leftmost phyX) without any
|
||||||
|
// per-column bit-reversal step.
|
||||||
|
//
|
||||||
|
// PARAMETERS
|
||||||
|
// ----------
|
||||||
|
// screenXBase = cursorX + glyph->left (logical X of glyph pixel [0,0])
|
||||||
|
// screenYBase = cursorY - glyph->top (logical Y of glyph pixel [0,0])
|
||||||
|
|
||||||
|
// Reverse all 8 bits of a byte (bit 7 ↔ bit 0).
|
||||||
|
static inline uint8_t reverseBits8(uint8_t b) {
|
||||||
|
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||||
|
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||||
|
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transpose an 8×8 bit matrix packed into a uint64_t.
|
||||||
|
//
|
||||||
|
// Input layout (row-major, row 0 at MSB):
|
||||||
|
// bit (63 - 8*r - c) = matrix[r][c] (r=row 0..7, c=col 0..7)
|
||||||
|
//
|
||||||
|
// After transposition:
|
||||||
|
// bit (63 - 8*c - r) = matrix[r][c]
|
||||||
|
// i.e. byte k = bits [63-8k .. 56-8k] holds column k, MSB = row 0.
|
||||||
|
//
|
||||||
|
// Uses the classic 3-pass butterfly (Warren, "Hacker's Delight" §7-3):
|
||||||
|
// pass 1 swaps adjacent bit-pairs across a stride of 7 (nibble level),
|
||||||
|
// pass 2 swaps across stride 14 (byte level),
|
||||||
|
// pass 3 swaps across stride 28 (half-word level).
|
||||||
|
static inline uint64_t transpose8x8(uint64_t x) {
|
||||||
|
uint64_t t;
|
||||||
|
t = (x ^ (x >> 7)) & 0x00AA00AA00AA00AAULL;
|
||||||
|
x ^= t ^ (t << 7);
|
||||||
|
t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL;
|
||||||
|
x ^= t ^ (t << 14);
|
||||||
|
t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL;
|
||||||
|
x ^= t ^ (t << 28);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract up to 8 bits from a 1-bit MSB-first packed bitmap starting at bit
|
||||||
|
// position 'bitPos'. Returns them MSB-aligned (bit 7 = first extracted bit);
|
||||||
|
// the lower (8-count) bits are zeroed.
|
||||||
|
// All 'count' bits must lie within the valid bitmap byte range.
|
||||||
|
static inline uint8_t bitmapExtract(const uint8_t* bitmap, const int bitPos, const int count) {
|
||||||
|
const int byteIdx = bitPos >> 3;
|
||||||
|
const int bitOff = bitPos & 7;
|
||||||
|
uint8_t result;
|
||||||
|
if (bitOff == 0) {
|
||||||
|
result = bitmap[byteIdx];
|
||||||
|
} else if (count <= 8 - bitOff) {
|
||||||
|
result = bitmap[byteIdx] << bitOff; // all bits inside first byte
|
||||||
|
} else {
|
||||||
|
result = (uint8_t)(((uint16_t)bitmap[byteIdx] << 8 | bitmap[byteIdx + 1]) >> (8 - bitOff));
|
||||||
|
}
|
||||||
|
if (count < 8) result &= static_cast<uint8_t>(0xFF << (8 - count));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write up to 8 foreground bits into a physical framebuffer row.
|
||||||
|
// bits — MSB-aligned; bit 7 = pixel at phyBitPos, lower (8-count) bits are zero.
|
||||||
|
// phyBitPos — physical X of the MSB pixel; must be in [0, DISPLAY_WIDTH).
|
||||||
|
// pixelState true → black (clear bits to 0), false → white (set bits to 1).
|
||||||
|
static inline void writeRowBits(uint8_t* const row, const int phyBitPos, const uint8_t bits, const bool pixelState) {
|
||||||
|
const int byteIdx = phyBitPos >> 3;
|
||||||
|
const int shift = phyBitPos & 7;
|
||||||
|
if (pixelState) {
|
||||||
|
row[byteIdx] &= ~(bits >> shift);
|
||||||
|
if (shift > 0 && byteIdx + 1 < HalDisplay::DISPLAY_WIDTH_BYTES) row[byteIdx + 1] &= ~(uint8_t)(bits << (8 - shift));
|
||||||
|
} else {
|
||||||
|
row[byteIdx] |= (bits >> shift);
|
||||||
|
if (shift > 0 && byteIdx + 1 < HalDisplay::DISPLAY_WIDTH_BYTES) row[byteIdx + 1] |= (uint8_t)(bits << (8 - shift));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void renderGlyphFastBW(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) {
|
||||||
|
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;
|
||||||
|
uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES;
|
||||||
|
const int rowBitStart = glyphY * glyphWidth;
|
||||||
|
for (int glyphX = 0; glyphX < glyphWidth; glyphX += 8) {
|
||||||
|
const int count = std::min(8, glyphWidth - glyphX);
|
||||||
|
const uint8_t gbyte = bitmapExtract(bitmap, rowBitStart + glyphX, count);
|
||||||
|
if (gbyte == 0) continue;
|
||||||
|
const int phyBitPos = screenXBase + glyphX;
|
||||||
|
if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue;
|
||||||
|
writeRowBits(row, phyBitPos, gbyte, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
uint8_t* const row = frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES;
|
||||||
|
const int rowBitStart = 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;
|
||||||
|
// 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;
|
||||||
|
const int phyBitPos = HalDisplay::DISPLAY_WIDTH - 1 - screenXBase - chunkEnd;
|
||||||
|
if (phyBitPos + count <= 0 || phyBitPos >= HalDisplay::DISPLAY_WIDTH) continue;
|
||||||
|
writeRowBits(row, phyBitPos, gbyte, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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);
|
||||||
|
uint64_t pack = 0;
|
||||||
|
int bitStart = glyphY * glyphWidth + glyphX;
|
||||||
|
for (int n = 0; n < rowCount; n++, bitStart += glyphWidth) {
|
||||||
|
pack |= static_cast<uint64_t>(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<uint8_t>(pack >> (56 - 8 * k));
|
||||||
|
if (cols_k == 0) continue;
|
||||||
|
const int phyY = HalDisplay::DISPLAY_HEIGHT - 1 - (screenXBase + glyphX + k);
|
||||||
|
if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue;
|
||||||
|
writeRowBits(frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES, phyBitPos, cols_k, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<uint64_t>(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<uint8_t>(pack >> (56 - 8 * k));
|
||||||
|
if (cols_k == 0) continue;
|
||||||
|
const int phyY = screenXBase + glyphX + k;
|
||||||
|
if (phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue;
|
||||||
|
writeRowBits(frameBuffer + phyY * HalDisplay::DISPLAY_WIDTH_BYTES, phyBitPos, cols_k, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shared glyph rendering logic for normal and rotated text.
|
// Shared glyph rendering logic for normal and rotated text.
|
||||||
// Coordinate mapping and cursor advance direction are selected at compile time via the template parameter.
|
// Coordinate mapping and cursor advance direction are selected at compile time via the template parameter.
|
||||||
template <TextRotation rotation>
|
template <TextRotation rotation>
|
||||||
@@ -133,6 +377,16 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Fast path: 1-bit BW mode, non-rotated text — byte-level framebuffer writes, no drawPixel() per pixel.
|
||||||
|
if constexpr (rotation == TextRotation::None) {
|
||||||
|
if (renderMode == GfxRenderer::BW) {
|
||||||
|
renderGlyphFastBW(renderer.getFrameBuffer(), bitmap, width, height, innerBase, outerBase, pixelState,
|
||||||
|
renderer.getOrientation());
|
||||||
|
*cursorX += glyph->advanceX;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback: rotated text or non-BW render mode — per-pixel drawPixel().
|
||||||
int pixelPosition = 0;
|
int pixelPosition = 0;
|
||||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
const int outerCoord = outerBase + glyphY;
|
const int outerCoord = outerBase + glyphY;
|
||||||
@@ -231,6 +485,52 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_RENDERCHAR_BENCHMARK
|
||||||
|
// Legacy per-pixel rendering path — mirrors the old renderCharImpl 1-bit BW loop.
|
||||||
|
// Used only by the renderChar benchmark to establish the baseline.
|
||||||
|
void GfxRenderer::drawTextBWLegacy(const int fontId, const int x, const int y, const char* text) const {
|
||||||
|
if (text == nullptr || *text == '\0') return;
|
||||||
|
const auto fontIt = fontMap.find(fontId);
|
||||||
|
if (fontIt == fontMap.end()) return;
|
||||||
|
const auto& fontFamily = fontIt->second;
|
||||||
|
|
||||||
|
int yPos = y + getFontAscenderSize(fontId);
|
||||||
|
int xPos = x;
|
||||||
|
uint32_t cp;
|
||||||
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
|
const EpdGlyph* glyph = fontFamily.getGlyph(cp, EpdFontFamily::REGULAR);
|
||||||
|
if (!glyph) glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, EpdFontFamily::REGULAR);
|
||||||
|
if (!glyph) continue;
|
||||||
|
const EpdFontData* fontData = fontFamily.getData(EpdFontFamily::REGULAR);
|
||||||
|
if (fontData->is2Bit) {
|
||||||
|
xPos += glyph->advanceX;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint8_t* bitmap = getGlyphBitmap(fontData, glyph);
|
||||||
|
if (bitmap != nullptr) {
|
||||||
|
const int screenYBase = yPos - glyph->top;
|
||||||
|
const int screenXBase = xPos + glyph->left;
|
||||||
|
int pixelPosition = 0;
|
||||||
|
for (int glyphY = 0; glyphY < glyph->height; glyphY++) {
|
||||||
|
for (int glyphX = 0; glyphX < glyph->width; glyphX++, pixelPosition++) {
|
||||||
|
const uint8_t bit = (bitmap[pixelPosition >> 3] >> (7 - (pixelPosition & 7))) & 1;
|
||||||
|
if (!bit) continue;
|
||||||
|
// Inline drawPixel without OOB logging — mirrors the old per-pixel path but clips silently,
|
||||||
|
// matching the fast path's behaviour so the benchmark measures rendering cost only.
|
||||||
|
int phyX, phyY;
|
||||||
|
rotateCoordinates(orientation, screenXBase + glyphX, screenYBase + glyphY, &phyX, &phyY);
|
||||||
|
if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) continue;
|
||||||
|
const uint16_t byteIndex = phyY * HalDisplay::DISPLAY_WIDTH_BYTES + (phyX / 8);
|
||||||
|
const uint8_t bitPosition = 7 - (phyX % 8);
|
||||||
|
frameBuffer[byteIndex] &= ~(1 << bitPosition); // black pixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xPos += glyph->advanceX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // ENABLE_RENDERCHAR_BENCHMARK
|
||||||
|
|
||||||
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
||||||
if (x1 == x2) {
|
if (x1 == x2) {
|
||||||
if (y2 < y1) {
|
if (y2 < y1) {
|
||||||
|
|||||||
@@ -137,4 +137,9 @@ class GfxRenderer {
|
|||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
static size_t getBufferSize();
|
static size_t getBufferSize();
|
||||||
|
|
||||||
|
#ifdef ENABLE_RENDERCHAR_BENCHMARK
|
||||||
|
// Legacy (per-pixel drawPixel) text rendering — used only by the renderChar benchmark.
|
||||||
|
void drawTextBWLegacy(int fontId, int x, int y, const char* text) const;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user