// dvx_draw.c — Layer 2: Drawing primitives for DV/X GUI (optimized) #include "dvxDraw.h" #include // ============================================================ // Prototypes // ============================================================ 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); // ============================================================ // 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; } // ============================================================ // 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; } } } } } // ============================================================ // 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++; } } // ============================================================ // 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; }