973 lines
28 KiB
C
973 lines
28 KiB
C
// dvx_draw.c — Layer 2: Drawing primitives for DV/X GUI (optimized)
|
||
|
||
#include "dvxDraw.h"
|
||
|
||
#include <string.h>
|
||
|
||
// ============================================================
|
||
// Prototypes
|
||
// ============================================================
|
||
|
||
char accelParse(const char *text);
|
||
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h);
|
||
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp);
|
||
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
|
||
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
|
||
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
||
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count);
|
||
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
|
||
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
|
||
|
||
|
||
// ============================================================
|
||
// accelParse
|
||
// ============================================================
|
||
|
||
char accelParse(const char *text) {
|
||
if (!text) {
|
||
return 0;
|
||
}
|
||
|
||
while (*text) {
|
||
if (*text == '&') {
|
||
text++;
|
||
|
||
if (*text == '&') {
|
||
// Escaped && — literal &, not an accelerator
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
if (*text && *text != '&') {
|
||
char ch = *text;
|
||
|
||
if (ch >= 'A' && ch <= 'Z') {
|
||
return (char)(ch + 32);
|
||
}
|
||
|
||
if (ch >= 'a' && ch <= 'z') {
|
||
return ch;
|
||
}
|
||
|
||
if (ch >= '0' && ch <= '9') {
|
||
return ch;
|
||
}
|
||
|
||
return ch;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
text++;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// clipRect
|
||
// ============================================================
|
||
|
||
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h) {
|
||
int32_t cx2 = d->clipX + d->clipW;
|
||
int32_t cy2 = d->clipY + d->clipH;
|
||
|
||
int32_t rx1 = *x;
|
||
int32_t ry1 = *y;
|
||
int32_t rx2 = rx1 + *w;
|
||
int32_t ry2 = ry1 + *h;
|
||
|
||
if (__builtin_expect(rx1 < d->clipX, 0)) { rx1 = d->clipX; }
|
||
if (__builtin_expect(ry1 < d->clipY, 0)) { ry1 = d->clipY; }
|
||
if (__builtin_expect(rx2 > cx2, 0)) { rx2 = cx2; }
|
||
if (__builtin_expect(ry2 > cy2, 0)) { ry2 = cy2; }
|
||
|
||
*x = rx1;
|
||
*y = ry1;
|
||
*w = rx2 - rx1;
|
||
*h = ry2 - ry1;
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawBevel
|
||
// ============================================================
|
||
|
||
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style) {
|
||
int32_t bw = style->width;
|
||
|
||
// Fill interior if requested
|
||
if (style->face != 0) {
|
||
rectFill(d, ops, x + bw, y + bw, w - bw * 2, h - bw * 2, style->face);
|
||
}
|
||
|
||
// Fast path for the common bevel widths (1 and 2)
|
||
// Directly emit spans instead of calling drawHLine->rectFill->clipRect per line
|
||
if (bw == 2) {
|
||
// Top 2 highlight lines
|
||
rectFill(d, ops, x, y, w, 1, style->highlight);
|
||
rectFill(d, ops, x + 1, y + 1, w - 2, 1, style->highlight);
|
||
// Left 2 highlight columns
|
||
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
|
||
rectFill(d, ops, x + 1, y + 2, 1, h - 3, style->highlight);
|
||
// Bottom 2 shadow lines
|
||
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
|
||
rectFill(d, ops, x + 1, y + h - 2, w - 2, 1, style->shadow);
|
||
// Right 2 shadow columns
|
||
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
|
||
rectFill(d, ops, x + w - 2, y + 2, 1, h - 4, style->shadow);
|
||
} else if (bw == 1) {
|
||
rectFill(d, ops, x, y, w, 1, style->highlight);
|
||
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
|
||
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
|
||
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
|
||
} else {
|
||
for (int32_t i = 0; i < bw; i++) {
|
||
rectFill(d, ops, x + i, y + i, w - i * 2, 1, style->highlight);
|
||
}
|
||
for (int32_t i = 0; i < bw; i++) {
|
||
rectFill(d, ops, x + i, y + i + 1, 1, h - i * 2 - 1, style->highlight);
|
||
}
|
||
for (int32_t i = 0; i < bw; i++) {
|
||
rectFill(d, ops, x + i, y + h - 1 - i, w - i * 2, 1, style->shadow);
|
||
}
|
||
for (int32_t i = 0; i < bw; i++) {
|
||
rectFill(d, ops, x + w - 1 - i, y + i + 1, 1, h - i * 2 - 2, style->shadow);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawChar
|
||
// ============================================================
|
||
|
||
int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, char ch, uint32_t fg, uint32_t bg, bool opaque) {
|
||
int32_t cw = font->charWidth;
|
||
int32_t chh = font->charHeight;
|
||
|
||
// Quick reject: entirely outside clip rect
|
||
if (__builtin_expect(x + cw <= d->clipX || x >= d->clipX + d->clipW || y + chh <= d->clipY || y >= d->clipY + d->clipH, 0)) {
|
||
return cw;
|
||
}
|
||
|
||
int32_t idx = (uint8_t)ch - font->firstChar;
|
||
if (__builtin_expect(idx < 0 || idx >= font->numChars, 0)) {
|
||
if (opaque) {
|
||
rectFill(d, ops, x, y, cw, chh, bg);
|
||
}
|
||
return cw;
|
||
}
|
||
|
||
const uint8_t *glyph = font->glyphData + idx * chh;
|
||
int32_t bpp = ops->bytesPerPixel;
|
||
int32_t pitch = d->pitch;
|
||
|
||
// Calculate clipped row/col bounds once
|
||
int32_t clipX1 = d->clipX;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
int32_t clipY1 = d->clipY;
|
||
int32_t clipY2 = d->clipY + d->clipH;
|
||
|
||
int32_t rowStart = 0;
|
||
int32_t rowEnd = chh;
|
||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||
if (y + chh > clipY2) { rowEnd = clipY2 - y; }
|
||
|
||
int32_t colStart = 0;
|
||
int32_t colEnd = cw;
|
||
if (x < clipX1) { colStart = clipX1 - x; }
|
||
if (x + cw > clipX2) { colEnd = clipX2 - x; }
|
||
|
||
if (opaque) {
|
||
// Opaque mode: fill entire cell with bg, then overwrite fg pixels
|
||
// This avoids a branch per pixel for the common case
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
int32_t py = y + row;
|
||
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
|
||
int32_t span = colEnd - colStart;
|
||
|
||
// Fill row with background
|
||
ops->spanFill(dst, bg, span);
|
||
|
||
// Overwrite foreground pixels
|
||
uint8_t bits = glyph[row];
|
||
if (bits == 0) {
|
||
continue; // entirely background row — already filled
|
||
}
|
||
|
||
// Shift bits to account for colStart
|
||
dst = d->backBuf + py * pitch + x * bpp;
|
||
|
||
if (bpp == 2) {
|
||
uint16_t fg16 = (uint16_t)fg;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
*(uint16_t *)(dst + col * 2) = fg16;
|
||
}
|
||
}
|
||
} else if (bpp == 4) {
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
*(uint32_t *)(dst + col * 4) = fg;
|
||
}
|
||
}
|
||
} else {
|
||
uint8_t fg8 = (uint8_t)fg;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
dst[col] = fg8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// Transparent mode: only write foreground pixels
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
uint8_t bits = glyph[row];
|
||
if (bits == 0) {
|
||
continue;
|
||
}
|
||
|
||
int32_t py = y + row;
|
||
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
|
||
|
||
if (bpp == 2) {
|
||
uint16_t fg16 = (uint16_t)fg;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
*(uint16_t *)(dst + col * 2) = fg16;
|
||
}
|
||
}
|
||
} else if (bpp == 4) {
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
*(uint32_t *)(dst + col * 4) = fg;
|
||
}
|
||
}
|
||
} else {
|
||
uint8_t fg8 = (uint8_t)fg;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
if (bits & (0x80 >> col)) {
|
||
dst[col] = fg8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return cw;
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawFocusRect
|
||
// ============================================================
|
||
|
||
void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
|
||
int32_t bpp = ops->bytesPerPixel;
|
||
int32_t pitch = d->pitch;
|
||
|
||
int32_t clipX1 = d->clipX;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
int32_t clipY1 = d->clipY;
|
||
int32_t clipY2 = d->clipY + d->clipH;
|
||
|
||
int32_t x2 = x + w - 1;
|
||
int32_t y2 = y + h - 1;
|
||
|
||
// Top edge
|
||
if (y >= clipY1 && y < clipY2) {
|
||
for (int32_t px = x; px <= x2; px += 2) {
|
||
if (px >= clipX1 && px < clipX2) {
|
||
putPixel(d->backBuf + y * pitch + px * bpp, color, bpp);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bottom edge
|
||
if (y2 >= clipY1 && y2 < clipY2 && y2 != y) {
|
||
int32_t parity = (y2 - y) & 1;
|
||
|
||
for (int32_t px = x + parity; px <= x2; px += 2) {
|
||
if (px >= clipX1 && px < clipX2) {
|
||
putPixel(d->backBuf + y2 * pitch + px * bpp, color, bpp);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Left edge (skip corners already drawn)
|
||
if (x >= clipX1 && x < clipX2) {
|
||
for (int32_t py = y + 2; py < y2; py += 2) {
|
||
if (py >= clipY1 && py < clipY2) {
|
||
putPixel(d->backBuf + py * pitch + x * bpp, color, bpp);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Right edge (skip corners already drawn)
|
||
if (x2 >= clipX1 && x2 < clipX2 && x2 != x) {
|
||
int32_t parity = (x2 - x) & 1;
|
||
|
||
for (int32_t py = y + 2 - parity; py < y2; py += 2) {
|
||
if (py >= clipY1 && py < clipY2) {
|
||
putPixel(d->backBuf + py * pitch + x2 * bpp, color, bpp);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawHLine
|
||
// ============================================================
|
||
|
||
void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color) {
|
||
rectFill(d, ops, x, y, w, 1, color);
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawInit
|
||
// ============================================================
|
||
|
||
void drawInit(BlitOpsT *ops, const DisplayT *d) {
|
||
ops->bytesPerPixel = d->format.bytesPerPixel;
|
||
ops->pitch = d->pitch;
|
||
|
||
switch (d->format.bytesPerPixel) {
|
||
case 1:
|
||
ops->spanFill = spanFill8;
|
||
ops->spanCopy = spanCopy8;
|
||
break;
|
||
case 2:
|
||
ops->spanFill = spanFill16;
|
||
ops->spanCopy = spanCopy16;
|
||
break;
|
||
case 4:
|
||
ops->spanFill = spanFill32;
|
||
ops->spanCopy = spanCopy32;
|
||
break;
|
||
default:
|
||
ops->spanFill = spanFill8;
|
||
ops->spanCopy = spanCopy8;
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawMaskedBitmap
|
||
// ============================================================
|
||
|
||
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *andMask, const uint16_t *xorData, uint32_t fgColor, uint32_t bgColor) {
|
||
int32_t bpp = ops->bytesPerPixel;
|
||
int32_t pitch = d->pitch;
|
||
|
||
// Pre-clip row/col bounds
|
||
int32_t clipX1 = d->clipX;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
int32_t clipY1 = d->clipY;
|
||
int32_t clipY2 = d->clipY + d->clipH;
|
||
|
||
int32_t rowStart = 0;
|
||
int32_t rowEnd = h;
|
||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||
if (y + h > clipY2) { rowEnd = clipY2 - y; }
|
||
|
||
int32_t colStart = 0;
|
||
int32_t colEnd = w;
|
||
if (x < clipX1) { colStart = clipX1 - x; }
|
||
if (x + w > clipX2) { colEnd = clipX2 - x; }
|
||
|
||
if (colStart >= colEnd || rowStart >= rowEnd) {
|
||
return;
|
||
}
|
||
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
uint16_t mask = andMask[row];
|
||
uint16_t data = xorData[row];
|
||
|
||
// Skip fully transparent rows
|
||
uint16_t colMask = 0;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
colMask |= (0x8000 >> col);
|
||
}
|
||
if ((mask & colMask) == colMask) {
|
||
continue; // all visible columns are transparent
|
||
}
|
||
|
||
int32_t py = y + row;
|
||
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
|
||
|
||
if (bpp == 2) {
|
||
uint16_t fg16 = (uint16_t)fgColor;
|
||
uint16_t bg16 = (uint16_t)bgColor;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
uint16_t bit = 0x8000 >> col;
|
||
if (!(mask & bit)) {
|
||
*(uint16_t *)(dst + col * 2) = (data & bit) ? fg16 : bg16;
|
||
}
|
||
}
|
||
} else if (bpp == 4) {
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
uint16_t bit = 0x8000 >> col;
|
||
if (!(mask & bit)) {
|
||
*(uint32_t *)(dst + col * 4) = (data & bit) ? fgColor : bgColor;
|
||
}
|
||
}
|
||
} else {
|
||
uint8_t fg8 = (uint8_t)fgColor;
|
||
uint8_t bg8 = (uint8_t)bgColor;
|
||
for (int32_t col = colStart; col < colEnd; col++) {
|
||
uint16_t bit = 0x8000 >> col;
|
||
if (!(mask & bit)) {
|
||
dst[col] = (data & bit) ? fg8 : bg8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawTermRow
|
||
// ============================================================
|
||
//
|
||
// Renders an entire row of terminal character cells in one pass.
|
||
// lineData points to (ch, attr) pairs. palette is a 16-entry
|
||
// packed-color table. This avoids per-character function call
|
||
// overhead, redundant clip calculation, and spanFill startup
|
||
// costs that make drawChar expensive when called 80× per row.
|
||
|
||
void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, int32_t cols, const uint8_t *lineData, const uint32_t *palette, bool blinkVisible, int32_t cursorCol) {
|
||
int32_t cw = font->charWidth;
|
||
int32_t ch = font->charHeight;
|
||
int32_t bpp = ops->bytesPerPixel;
|
||
int32_t pitch = d->pitch;
|
||
|
||
// Row-level clip: reject if entirely outside vertically
|
||
int32_t clipX1 = d->clipX;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
int32_t clipY1 = d->clipY;
|
||
int32_t clipY2 = d->clipY + d->clipH;
|
||
|
||
if (y + ch <= clipY1 || y >= clipY2) {
|
||
return;
|
||
}
|
||
|
||
// Vertical clip for glyph scanlines
|
||
int32_t rowStart = 0;
|
||
int32_t rowEnd = ch;
|
||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
|
||
|
||
// Horizontal clip: find first and last visible column
|
||
int32_t rowW = cols * cw;
|
||
int32_t firstCol = 0;
|
||
int32_t lastCol = cols;
|
||
|
||
if (x + rowW <= clipX1 || x >= clipX2) {
|
||
return;
|
||
}
|
||
|
||
if (x < clipX1) {
|
||
firstCol = (clipX1 - x) / cw;
|
||
}
|
||
|
||
if (x + rowW > clipX2) {
|
||
lastCol = (clipX2 - x + cw - 1) / cw;
|
||
if (lastCol > cols) { lastCol = cols; }
|
||
}
|
||
|
||
// Per-column clip for partially visible edge cells
|
||
int32_t edgeColStart = 0;
|
||
|
||
if (x + firstCol * cw < clipX1) {
|
||
edgeColStart = clipX1 - (x + firstCol * cw);
|
||
}
|
||
|
||
// Render each visible cell
|
||
for (int32_t col = firstCol; col < lastCol; col++) {
|
||
uint8_t gch = lineData[col * 2];
|
||
uint8_t attr = lineData[col * 2 + 1];
|
||
uint32_t fg = palette[attr & 0x0F];
|
||
uint32_t bg = palette[(attr >> 4) & 0x07];
|
||
|
||
// Blink: hide text during off phase
|
||
if ((attr & 0x80) && !blinkVisible) {
|
||
fg = bg;
|
||
}
|
||
|
||
// Cursor: invert colors
|
||
if (col == cursorCol) {
|
||
uint32_t tmp = fg;
|
||
fg = bg;
|
||
bg = tmp;
|
||
}
|
||
|
||
int32_t cx = x + col * cw;
|
||
|
||
// Determine per-cell horizontal clip
|
||
int32_t cStart = 0;
|
||
int32_t cEnd = cw;
|
||
|
||
if (col == firstCol) {
|
||
cStart = edgeColStart;
|
||
}
|
||
|
||
if (cx + cw > clipX2) {
|
||
cEnd = clipX2 - cx;
|
||
}
|
||
|
||
// Look up glyph data
|
||
int32_t idx = (uint8_t)gch - font->firstChar;
|
||
const uint8_t *glyph = NULL;
|
||
|
||
if (idx >= 0 && idx < font->numChars) {
|
||
glyph = font->glyphData + idx * ch;
|
||
}
|
||
|
||
// Render scanlines
|
||
if (bpp == 2) {
|
||
uint16_t fg16 = (uint16_t)fg;
|
||
uint16_t bg16 = (uint16_t)bg;
|
||
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
|
||
uint8_t bits = glyph ? glyph[row] : 0;
|
||
|
||
for (int32_t p = cStart; p < cEnd; p++) {
|
||
dst[p] = (bits & (0x80 >> p)) ? fg16 : bg16;
|
||
}
|
||
}
|
||
} else if (bpp == 4) {
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
|
||
uint8_t bits = glyph ? glyph[row] : 0;
|
||
|
||
for (int32_t p = cStart; p < cEnd; p++) {
|
||
dst[p] = (bits & (0x80 >> p)) ? fg : bg;
|
||
}
|
||
}
|
||
} else {
|
||
uint8_t fg8 = (uint8_t)fg;
|
||
uint8_t bg8 = (uint8_t)bg;
|
||
|
||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
|
||
uint8_t bits = glyph ? glyph[row] : 0;
|
||
|
||
for (int32_t p = cStart; p < cEnd; p++) {
|
||
dst[p] = (bits & (0x80 >> p)) ? fg8 : bg8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawText
|
||
// ============================================================
|
||
|
||
void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque) {
|
||
int32_t cw = font->charWidth;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
|
||
while (*text) {
|
||
// Early out if we've moved past the right clip edge
|
||
if (__builtin_expect(x >= clipX2, 0)) {
|
||
break;
|
||
}
|
||
|
||
// Skip characters entirely to the left of clip
|
||
if (__builtin_expect(x + cw <= d->clipX, 0)) {
|
||
x += cw;
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
x += drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
|
||
text++;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawTextAccel
|
||
// ============================================================
|
||
|
||
void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque) {
|
||
int32_t cw = font->charWidth;
|
||
int32_t clipX2 = d->clipX + d->clipW;
|
||
|
||
while (*text) {
|
||
if (__builtin_expect(x >= clipX2, 0)) {
|
||
break;
|
||
}
|
||
|
||
if (*text == '&') {
|
||
text++;
|
||
|
||
if (*text == '&') {
|
||
// Escaped && — draw literal &
|
||
if (x + cw > d->clipX) {
|
||
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
|
||
}
|
||
|
||
x += cw;
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
if (*text) {
|
||
// Accelerator character — draw it then underline
|
||
if (x + cw > d->clipX) {
|
||
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
|
||
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
|
||
}
|
||
|
||
x += cw;
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
if (x + cw > d->clipX) {
|
||
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
|
||
}
|
||
|
||
x += cw;
|
||
text++;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// drawVLine
|
||
// ============================================================
|
||
|
||
void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color) {
|
||
(void)ops;
|
||
|
||
// Inline single-pixel-wide fill to avoid rectFill overhead for narrow lines
|
||
if (__builtin_expect(x < d->clipX || x >= d->clipX + d->clipW, 0)) {
|
||
return;
|
||
}
|
||
|
||
int32_t y1 = y;
|
||
int32_t y2 = y + h;
|
||
if (y1 < d->clipY) { y1 = d->clipY; }
|
||
if (y2 > d->clipY + d->clipH) { y2 = d->clipY + d->clipH; }
|
||
if (y1 >= y2) { return; }
|
||
|
||
int32_t bpp = d->format.bytesPerPixel;
|
||
uint8_t *dst = d->backBuf + y1 * d->pitch + x * bpp;
|
||
int32_t pitch = d->pitch;
|
||
|
||
if (bpp == 2) {
|
||
uint16_t c16 = (uint16_t)color;
|
||
for (int32_t i = y1; i < y2; i++) {
|
||
*(uint16_t *)dst = c16;
|
||
dst += pitch;
|
||
}
|
||
} else if (bpp == 4) {
|
||
for (int32_t i = y1; i < y2; i++) {
|
||
*(uint32_t *)dst = color;
|
||
dst += pitch;
|
||
}
|
||
} else {
|
||
uint8_t c8 = (uint8_t)color;
|
||
for (int32_t i = y1; i < y2; i++) {
|
||
*dst = c8;
|
||
dst += pitch;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// putPixel
|
||
// ============================================================
|
||
|
||
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
|
||
if (bpp == 2) {
|
||
*(uint16_t *)dst = (uint16_t)color;
|
||
} else if (bpp == 4) {
|
||
*(uint32_t *)dst = color;
|
||
} else {
|
||
*dst = (uint8_t)color;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// rectCopy
|
||
// ============================================================
|
||
|
||
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
|
||
int32_t bpp = ops->bytesPerPixel;
|
||
|
||
// Clip to display clip rect
|
||
int32_t origDstX = dstX;
|
||
int32_t origDstY = dstY;
|
||
|
||
clipRect(d, &dstX, &dstY, &w, &h);
|
||
|
||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||
return;
|
||
}
|
||
|
||
// Adjust source position by the amount we clipped
|
||
srcX += dstX - origDstX;
|
||
srcY += dstY - origDstY;
|
||
|
||
const uint8_t *srcRow = srcBuf + srcY * srcPitch + srcX * bpp;
|
||
uint8_t *dstRow = d->backBuf + dstY * d->pitch + dstX * bpp;
|
||
int32_t rowBytes = w * bpp;
|
||
int32_t dstPitch = d->pitch;
|
||
|
||
// For full-width copies aligned to pitch, use memcpy (may optimize to rep movsd)
|
||
if (rowBytes == dstPitch && rowBytes == srcPitch) {
|
||
memcpy(dstRow, srcRow, rowBytes * h);
|
||
} else {
|
||
for (int32_t i = 0; i < h; i++) {
|
||
memcpy(dstRow, srcRow, rowBytes);
|
||
srcRow += srcPitch;
|
||
dstRow += dstPitch;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// rectFill
|
||
// ============================================================
|
||
|
||
void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
|
||
clipRect(d, &x, &y, &w, &h);
|
||
|
||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||
return;
|
||
}
|
||
|
||
uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel;
|
||
int32_t pitch = d->pitch;
|
||
|
||
for (int32_t i = 0; i < h; i++) {
|
||
ops->spanFill(row, color, w);
|
||
row += pitch;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanCopy8
|
||
// ============================================================
|
||
|
||
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||
// Align to 4 bytes
|
||
while (((uintptr_t)dst & 3) && count > 0) {
|
||
*dst++ = *src++;
|
||
count--;
|
||
}
|
||
|
||
if (count >= 4) {
|
||
int32_t dwordCount = count >> 2;
|
||
__asm__ __volatile__ (
|
||
"rep movsl"
|
||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||
:
|
||
: "memory"
|
||
);
|
||
dst += dwordCount * 4;
|
||
src += dwordCount * 4;
|
||
}
|
||
|
||
int32_t rem = count & 3;
|
||
while (rem-- > 0) {
|
||
*dst++ = *src++;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanCopy16
|
||
// ============================================================
|
||
|
||
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||
// Handle odd leading pixel for dword alignment
|
||
if (((uintptr_t)dst & 2) && count > 0) {
|
||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||
dst += 2;
|
||
src += 2;
|
||
count--;
|
||
}
|
||
|
||
if (count >= 2) {
|
||
int32_t dwordCount = count >> 1;
|
||
__asm__ __volatile__ (
|
||
"rep movsl"
|
||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||
:
|
||
: "memory"
|
||
);
|
||
dst += dwordCount * 4;
|
||
src += dwordCount * 4;
|
||
}
|
||
|
||
if (count & 1) {
|
||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanCopy32
|
||
// ============================================================
|
||
|
||
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||
__asm__ __volatile__ (
|
||
"rep movsl"
|
||
: "+D"(dst), "+S"(src), "+c"(count)
|
||
:
|
||
: "memory"
|
||
);
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanFill8
|
||
// ============================================================
|
||
|
||
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) {
|
||
uint8_t c = (uint8_t)color;
|
||
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
||
|
||
// Align to 4 bytes
|
||
while (((uintptr_t)dst & 3) && count > 0) {
|
||
*dst++ = c;
|
||
count--;
|
||
}
|
||
|
||
if (count >= 4) {
|
||
int32_t dwordCount = count >> 2;
|
||
__asm__ __volatile__ (
|
||
"rep stosl"
|
||
: "+D"(dst), "+c"(dwordCount)
|
||
: "a"(dword)
|
||
: "memory"
|
||
);
|
||
dst += dwordCount * 4;
|
||
}
|
||
|
||
int32_t rem = count & 3;
|
||
while (rem-- > 0) {
|
||
*dst++ = c;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanFill16
|
||
// ============================================================
|
||
|
||
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count) {
|
||
uint16_t c = (uint16_t)color;
|
||
|
||
// Handle odd leading pixel for dword alignment
|
||
if (((uintptr_t)dst & 2) && count > 0) {
|
||
*(uint16_t *)dst = c;
|
||
dst += 2;
|
||
count--;
|
||
}
|
||
|
||
// Fill pairs of pixels as 32-bit dwords
|
||
if (count >= 2) {
|
||
uint32_t dword = ((uint32_t)c << 16) | c;
|
||
int32_t dwordCount = count >> 1;
|
||
__asm__ __volatile__ (
|
||
"rep stosl"
|
||
: "+D"(dst), "+c"(dwordCount)
|
||
: "a"(dword)
|
||
: "memory"
|
||
);
|
||
dst += dwordCount * 4;
|
||
}
|
||
|
||
// Handle trailing odd pixel
|
||
if (count & 1) {
|
||
*(uint16_t *)dst = c;
|
||
}
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// spanFill32
|
||
// ============================================================
|
||
|
||
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count) {
|
||
__asm__ __volatile__ (
|
||
"rep stosl"
|
||
: "+D"(dst), "+c"(count)
|
||
: "a"(color)
|
||
: "memory"
|
||
);
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// textWidth
|
||
// ============================================================
|
||
|
||
int32_t textWidth(const BitmapFontT *font, const char *text) {
|
||
int32_t w = 0;
|
||
|
||
while (*text) {
|
||
w += font->charWidth;
|
||
text++;
|
||
}
|
||
|
||
return w;
|
||
}
|
||
|
||
|
||
// ============================================================
|
||
// textWidthAccel
|
||
// ============================================================
|
||
|
||
int32_t textWidthAccel(const BitmapFontT *font, const char *text) {
|
||
int32_t w = 0;
|
||
|
||
while (*text) {
|
||
if (*text == '&') {
|
||
text++;
|
||
|
||
if (*text == '&') {
|
||
// Escaped && — counts as one character
|
||
w += font->charWidth;
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
if (*text) {
|
||
// Accelerator character — counts as one character, & is skipped
|
||
w += font->charWidth;
|
||
text++;
|
||
continue;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
w += font->charWidth;
|
||
text++;
|
||
}
|
||
|
||
return w;
|
||
}
|