DOS_Video/accelVid.c
2026-04-13 19:40:45 -05:00

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);
}
}