574 lines
17 KiB
C
574 lines
17 KiB
C
// accelVid.c -- Accelerated video driver manager
|
|
//
|
|
// Manages registration, detection, and lifecycle of hardware-specific
|
|
// video drivers. Drivers register themselves at startup, then the
|
|
// manager probes each in order to find matching hardware.
|
|
//
|
|
// After a chip driver's init() succeeds, the manager fills in
|
|
// software fallback implementations for any drawing operations
|
|
// the driver left as NULL. This means callers never need to
|
|
// check function pointers -- every operation is always callable.
|
|
// The fallbacks draw directly to the LFB using simple loops.
|
|
|
|
#include "accelVid.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// Maximum number of registered drivers. This is more than enough
|
|
// for all chip families we'll ever support.
|
|
#define MAX_DRIVERS 32
|
|
|
|
// ============================================================
|
|
// Prototypes -- public API
|
|
// ============================================================
|
|
|
|
AccelDriverT *accelDetect(void);
|
|
uint32_t accelGetCaps(const AccelDriverT *drv);
|
|
const char *accelGetName(const AccelDriverT *drv);
|
|
bool accelInit(AccelDriverT *drv, const AccelModeRequestT *req);
|
|
void accelRegisterDriver(AccelDriverT *drv);
|
|
void accelShutdown(AccelDriverT *drv);
|
|
|
|
// ============================================================
|
|
// Prototypes -- software fallbacks
|
|
// ============================================================
|
|
|
|
static void swBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
|
|
static void swColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg);
|
|
static void swHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h);
|
|
static void swLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color);
|
|
static void swRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
|
|
static void swRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg);
|
|
static void swSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h);
|
|
static void swWaitIdle(AccelDriverT *drv);
|
|
static void swInstallFallbacks(AccelDriverT *drv);
|
|
|
|
// ============================================================
|
|
// Inline helpers for software fallbacks
|
|
// ============================================================
|
|
|
|
// Write a pixel at (x, y) in the framebuffer. No bounds checking
|
|
// -- the caller must clip before calling.
|
|
static inline void swPutPixel(AccelDriverT *drv, int32_t x, int32_t y, uint32_t color) {
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
uint8_t *dst = fb + y * drv->mode.pitch + x * bpp;
|
|
|
|
switch (bpp) {
|
|
case 1:
|
|
*dst = (uint8_t)color;
|
|
break;
|
|
case 2:
|
|
*(uint16_t *)dst = (uint16_t)color;
|
|
break;
|
|
case 4:
|
|
*(uint32_t *)dst = color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Module state
|
|
// ============================================================
|
|
|
|
static AccelDriverT *sDrivers[MAX_DRIVERS];
|
|
static int32_t sDriverCount = 0;
|
|
|
|
// Software clip rectangle (used by fallbacks when no hardware clip)
|
|
static int32_t sClipX = 0;
|
|
static int32_t sClipY = 0;
|
|
static int32_t sClipW = 0;
|
|
static int32_t sClipH = 0;
|
|
|
|
// ============================================================
|
|
// accelDetect
|
|
// ============================================================
|
|
//
|
|
// Iterates all registered drivers and calls detect() on each.
|
|
// Returns the first driver that claims the hardware, or NULL
|
|
// if no supported hardware is found.
|
|
//
|
|
// Detection order matters: drivers registered first are tried
|
|
// first. This allows callers to prioritize specific drivers
|
|
// (e.g. prefer S3 over generic VESA).
|
|
|
|
AccelDriverT *accelDetect(void) {
|
|
if (!pciDetect()) {
|
|
fprintf(stderr, "accelVid: PCI bus not detected\n");
|
|
return NULL;
|
|
}
|
|
|
|
for (int32_t i = 0; i < sDriverCount; i++) {
|
|
if (sDrivers[i]->detect(sDrivers[i])) {
|
|
printf("accelVid: Detected %s (PCI %02X:%02X.%X, "
|
|
"vendor=%04X device=%04X)\n",
|
|
sDrivers[i]->name,
|
|
sDrivers[i]->pciDev.bus,
|
|
sDrivers[i]->pciDev.dev,
|
|
sDrivers[i]->pciDev.func,
|
|
sDrivers[i]->pciDev.vendorId,
|
|
sDrivers[i]->pciDev.deviceId);
|
|
return sDrivers[i];
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "accelVid: No supported video hardware found\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// accelGetCaps
|
|
// ============================================================
|
|
|
|
uint32_t accelGetCaps(const AccelDriverT *drv) {
|
|
if (!drv) {
|
|
return 0;
|
|
}
|
|
|
|
return drv->caps;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// accelGetName
|
|
// ============================================================
|
|
|
|
const char *accelGetName(const AccelDriverT *drv) {
|
|
if (!drv) {
|
|
return "none";
|
|
}
|
|
|
|
return drv->name;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// accelInit
|
|
// ============================================================
|
|
|
|
bool accelInit(AccelDriverT *drv, const AccelModeRequestT *req) {
|
|
if (!drv || !drv->init) {
|
|
return false;
|
|
}
|
|
|
|
memset(&drv->mode, 0, sizeof(drv->mode));
|
|
|
|
if (!drv->init(drv, req)) {
|
|
fprintf(stderr, "accelVid: Failed to initialize %s\n", drv->name);
|
|
return false;
|
|
}
|
|
|
|
printf("accelVid: Initialized %s at %ldx%ldx%ld (pitch=%ld, vram=%luKB)\n",
|
|
drv->name,
|
|
(long)drv->mode.width,
|
|
(long)drv->mode.height,
|
|
(long)drv->mode.bpp,
|
|
(long)drv->mode.pitch,
|
|
(unsigned long)(drv->mode.vramSize / 1024));
|
|
|
|
// Report capabilities
|
|
printf("accelVid: Capabilities:");
|
|
|
|
if (drv->caps & ACAP_RECT_FILL) {
|
|
printf(" RectFill");
|
|
}
|
|
if (drv->caps & ACAP_RECT_FILL_PAT) {
|
|
printf(" PatFill");
|
|
}
|
|
if (drv->caps & ACAP_BITBLT) {
|
|
printf(" BitBlt");
|
|
}
|
|
if (drv->caps & ACAP_COLOR_EXPAND) {
|
|
printf(" ColorExpand");
|
|
}
|
|
if (drv->caps & ACAP_LINE_DRAW) {
|
|
printf(" LineDraw");
|
|
}
|
|
if (drv->caps & ACAP_HW_CURSOR) {
|
|
printf(" HwCursor");
|
|
}
|
|
if (drv->caps & ACAP_HOST_BLIT) {
|
|
printf(" HostBlit");
|
|
}
|
|
if (drv->caps & ACAP_CLIP) {
|
|
printf(" Clip");
|
|
}
|
|
if (drv->caps & ACAP_TRANSPARENCY) {
|
|
printf(" Transparency");
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
// Install software fallbacks for any operations the driver
|
|
// didn't implement in hardware
|
|
swInstallFallbacks(drv);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// accelRegisterDriver
|
|
// ============================================================
|
|
|
|
void accelRegisterDriver(AccelDriverT *drv) {
|
|
if (sDriverCount >= MAX_DRIVERS) {
|
|
fprintf(stderr, "accelVid: Too many drivers registered (max %d)\n",
|
|
MAX_DRIVERS);
|
|
return;
|
|
}
|
|
|
|
sDrivers[sDriverCount++] = drv;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// accelShutdown
|
|
// ============================================================
|
|
|
|
void accelShutdown(AccelDriverT *drv) {
|
|
if (!drv) {
|
|
return;
|
|
}
|
|
|
|
if (drv->waitIdle) {
|
|
drv->waitIdle(drv);
|
|
}
|
|
|
|
if (drv->showCursor) {
|
|
drv->showCursor(drv, false);
|
|
}
|
|
|
|
if (drv->shutdown) {
|
|
drv->shutdown(drv);
|
|
}
|
|
|
|
memset(&drv->mode, 0, sizeof(drv->mode));
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Software fallback implementations
|
|
// ============================================================
|
|
//
|
|
// These draw directly to the LFB. They're correct but slow
|
|
// (uncached PCI writes). The point isn't performance -- it's
|
|
// ensuring every operation is always callable so the caller
|
|
// never needs to check for NULL function pointers.
|
|
|
|
|
|
// ============================================================
|
|
// swBitBlt
|
|
// ============================================================
|
|
//
|
|
// Screen-to-screen blit via the LFB. Handles overlapping regions
|
|
// by choosing copy direction.
|
|
|
|
static void swBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t pitch = drv->mode.pitch;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
int32_t rowBytes = w * bpp;
|
|
|
|
if (dstY < srcY || (dstY == srcY && dstX <= srcX)) {
|
|
// Copy forward (top to bottom, left to right)
|
|
for (int32_t row = 0; row < h; row++) {
|
|
uint8_t *src = fb + (srcY + row) * pitch + srcX * bpp;
|
|
uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp;
|
|
memmove(dst, src, rowBytes);
|
|
}
|
|
} else {
|
|
// Copy backward (bottom to top)
|
|
for (int32_t row = h - 1; row >= 0; row--) {
|
|
uint8_t *src = fb + (srcY + row) * pitch + srcX * bpp;
|
|
uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp;
|
|
memmove(dst, src, rowBytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swColorExpand
|
|
// ============================================================
|
|
//
|
|
// Monochrome-to-color expansion via the LFB. Each 1-bit in srcBuf
|
|
// becomes the fg color, each 0-bit becomes the bg color.
|
|
|
|
static void swColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg) {
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t pitch = drv->mode.pitch;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
|
|
for (int32_t row = 0; row < h; row++) {
|
|
const uint8_t *mono = srcBuf + row * srcPitch;
|
|
uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp;
|
|
|
|
for (int32_t col = 0; col < w; col++) {
|
|
int32_t byteIdx = col / 8;
|
|
int32_t bitIdx = 7 - (col % 8);
|
|
uint32_t color = (mono[byteIdx] >> bitIdx) & 1 ? fg : bg;
|
|
|
|
switch (bpp) {
|
|
case 1:
|
|
dst[col] = (uint8_t)color;
|
|
break;
|
|
case 2:
|
|
((uint16_t *)dst)[col] = (uint16_t)color;
|
|
break;
|
|
case 4:
|
|
((uint32_t *)dst)[col] = color;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swHostBlit
|
|
// ============================================================
|
|
//
|
|
// CPU-to-screen blit via the LFB. Just a memcpy per scanline.
|
|
|
|
static void swHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) {
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t pitch = drv->mode.pitch;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
int32_t rowBytes = w * bpp;
|
|
|
|
for (int32_t row = 0; row < h; row++) {
|
|
const uint8_t *src = srcBuf + row * srcPitch;
|
|
uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp;
|
|
memcpy(dst, src, rowBytes);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swLineDraw
|
|
// ============================================================
|
|
//
|
|
// Bresenham line draw via the LFB.
|
|
|
|
static void swLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) {
|
|
int32_t dx = abs(x2 - x1);
|
|
int32_t dy = abs(y2 - y1);
|
|
int32_t sx = (x1 < x2) ? 1 : -1;
|
|
int32_t sy = (y1 < y2) ? 1 : -1;
|
|
int32_t err = dx - dy;
|
|
int32_t x = x1;
|
|
int32_t y = y1;
|
|
|
|
for (;;) {
|
|
if (x >= sClipX && x < sClipX + sClipW &&
|
|
y >= sClipY && y < sClipY + sClipH) {
|
|
swPutPixel(drv, x, y, color);
|
|
}
|
|
|
|
if (x == x2 && y == y2) {
|
|
break;
|
|
}
|
|
|
|
int32_t e2 = 2 * err;
|
|
|
|
if (e2 > -dy) {
|
|
err -= dy;
|
|
x += sx;
|
|
}
|
|
|
|
if (e2 < dx) {
|
|
err += dx;
|
|
y += sy;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swRectFill
|
|
// ============================================================
|
|
//
|
|
// Solid rectangle fill via the LFB.
|
|
|
|
static void swRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t pitch = drv->mode.pitch;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
|
|
for (int32_t row = 0; row < h; row++) {
|
|
uint8_t *dst = fb + (y + row) * pitch + x * bpp;
|
|
|
|
switch (bpp) {
|
|
case 1:
|
|
memset(dst, (uint8_t)color, w);
|
|
break;
|
|
case 2: {
|
|
uint16_t *dst16 = (uint16_t *)dst;
|
|
for (int32_t col = 0; col < w; col++) {
|
|
dst16[col] = (uint16_t)color;
|
|
}
|
|
break;
|
|
}
|
|
case 4: {
|
|
uint32_t *dst32 = (uint32_t *)dst;
|
|
for (int32_t col = 0; col < w; col++) {
|
|
dst32[col] = color;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swRectFillPat
|
|
// ============================================================
|
|
//
|
|
// 8x8 monochrome pattern fill via the LFB. The pattern is 8 bytes,
|
|
// one bit per pixel, MSB-first, row 0 first. Each 1-bit gets the
|
|
// fg color, each 0-bit gets the bg color. The pattern tiles across
|
|
// the destination rectangle with alignment to screen coordinates
|
|
// (so patterns line up across adjacent fills).
|
|
|
|
static void swRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg) {
|
|
if (w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *fb = drv->mode.framebuffer;
|
|
int32_t pitch = drv->mode.pitch;
|
|
int32_t bpp = (drv->mode.bpp + 7) / 8;
|
|
|
|
for (int32_t row = 0; row < h; row++) {
|
|
uint8_t patRow = pattern[(y + row) & 7];
|
|
uint8_t *dst = fb + (y + row) * pitch + x * bpp;
|
|
|
|
for (int32_t col = 0; col < w; col++) {
|
|
int32_t patBit = 7 - ((x + col) & 7);
|
|
uint32_t color = (patRow >> patBit) & 1 ? fg : bg;
|
|
|
|
switch (bpp) {
|
|
case 1:
|
|
dst[col] = (uint8_t)color;
|
|
break;
|
|
case 2:
|
|
((uint16_t *)dst)[col] = (uint16_t)color;
|
|
break;
|
|
case 4:
|
|
((uint32_t *)dst)[col] = color;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swSetClip
|
|
// ============================================================
|
|
//
|
|
// Software clip rectangle for fallback line drawing.
|
|
|
|
static void swSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|
(void)drv;
|
|
sClipX = x;
|
|
sClipY = y;
|
|
sClipW = w;
|
|
sClipH = h;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swWaitIdle
|
|
// ============================================================
|
|
//
|
|
// No-op -- software operations complete synchronously.
|
|
|
|
static void swWaitIdle(AccelDriverT *drv) {
|
|
(void)drv;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// swInstallFallbacks
|
|
// ============================================================
|
|
//
|
|
// Fills in software implementations for any NULL function
|
|
// pointers in the driver struct. Called by accelInit() after
|
|
// the chip driver's init() succeeds. This guarantees that
|
|
// every drawing operation is always callable.
|
|
|
|
static void swInstallFallbacks(AccelDriverT *drv) {
|
|
int32_t count = 0;
|
|
|
|
if (!drv->waitIdle) {
|
|
drv->waitIdle = swWaitIdle;
|
|
}
|
|
|
|
if (!drv->setClip) {
|
|
drv->setClip = swSetClip;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->rectFill) {
|
|
drv->rectFill = swRectFill;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->bitBlt) {
|
|
drv->bitBlt = swBitBlt;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->hostBlit) {
|
|
drv->hostBlit = swHostBlit;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->colorExpand) {
|
|
drv->colorExpand = swColorExpand;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->rectFillPat) {
|
|
drv->rectFillPat = swRectFillPat;
|
|
count++;
|
|
}
|
|
|
|
if (!drv->lineDraw) {
|
|
drv->lineDraw = swLineDraw;
|
|
count++;
|
|
}
|
|
|
|
// Initialize the software clip rect to full screen
|
|
sClipX = 0;
|
|
sClipY = 0;
|
|
sClipW = drv->mode.width;
|
|
sClipH = drv->mode.height;
|
|
|
|
if (count > 0) {
|
|
printf("accelVid: %ld operation(s) using software fallback\n",
|
|
(long)count);
|
|
}
|
|
}
|