DVX_GUI/dvx/dvxVideo.c
2026-03-09 20:55:12 -05:00

445 lines
12 KiB
C

// dvx_video.c — Layer 1: VESA VBE video backend for DV/X GUI
#include "dvxVideo.h"
#include "dvxPalette.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <sys/nearptr.h>
#include <sys/farptr.h>
// ============================================================
// Prototypes
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
static int32_t setVesaMode(uint16_t mode);
// ============================================================
// findBestMode
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp,
uint16_t *outMode, DisplayT *d) {
__dpmi_regs r;
uint16_t bestMode = 0;
int32_t bestScore = -1;
DisplayT bestDisplay;
memset(&bestDisplay, 0, sizeof(bestDisplay));
// Get VBE controller info
// Put "VBE2" signature at ES:DI in conventional memory
uint32_t infoSeg = __tb >> 4;
uint32_t infoOff = __tb & 0x0F;
// Write "VBE2" at transfer buffer to request VBE 2.0 info
_farpokeb(_dos_ds, __tb + 0, 'V');
_farpokeb(_dos_ds, __tb + 1, 'B');
_farpokeb(_dos_ds, __tb + 2, 'E');
_farpokeb(_dos_ds, __tb + 3, '2');
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F00;
r.x.es = infoSeg;
r.x.di = infoOff;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
return -1;
}
// Verify VBE signature
char sig[5];
for (int32_t i = 0; i < 4; i++) {
sig[i] = _farpeekb(_dos_ds, __tb + i);
}
sig[4] = '\0';
if (strcmp(sig, "VESA") != 0) {
fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
return -1;
}
// Check VBE version (need 2.0+)
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
if (vbeVersion < 0x0200) {
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
vbeVersion >> 8, vbeVersion & 0xFF);
return -1;
}
// Get mode list pointer (far pointer at offset 14)
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
// Walk mode list
for (int32_t i = 0; i < 256; i++) {
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
if (mode == 0xFFFF) {
break;
}
DisplayT candidate;
int32_t score = 0;
memset(&candidate, 0, sizeof(candidate));
getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp);
if (score > bestScore) {
bestScore = score;
bestMode = mode;
bestDisplay = candidate;
}
}
if (bestScore < 0) {
fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
return -1;
}
*outMode = bestMode;
*d = bestDisplay;
return 0;
}
// ============================================================
// getModeInfo
// ============================================================
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score,
int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
__dpmi_regs r;
*score = -1;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F01;
r.x.cx = mode;
r.x.es = __tb >> 4;
r.x.di = __tb & 0x0F;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
return;
}
// Read mode attributes
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
// Must have LFB support (bit 7)
if (!(attr & 0x0080)) {
return;
}
// Must be a graphics mode (bit 4)
if (!(attr & 0x0010)) {
return;
}
int32_t w = _farpeekw(_dos_ds, __tb + 18);
int32_t h = _farpeekw(_dos_ds, __tb + 20);
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
int32_t pitch = _farpeekw(_dos_ds, __tb + 16);
uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);
// Must match or exceed requested resolution
if (w < requestedW || h < requestedH) {
return;
}
// Must be a supported bpp
if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
// Some cards report 24-bit; skip those
if (bpp == 24) {
return;
}
return;
}
// Score this mode
int32_t s = 0;
if (bpp == 16) {
s = 100;
} else if (bpp == 15) {
s = 90;
} else if (bpp == 32) {
s = 85;
} else if (bpp == 8) {
s = 70;
}
// Prefer the user's preferred bpp
if (bpp == preferredBpp) {
s += 20;
}
// Exact resolution match is preferred
if (w == requestedW && h == requestedH) {
s += 10;
} else {
s -= 10;
}
*score = s;
// Fill in display info
d->width = w;
d->height = h;
d->pitch = pitch;
d->format.bitsPerPixel = bpp;
d->format.bytesPerPixel = (bpp + 7) / 8;
// Read color masks from mode info
if (bpp >= 15) {
int32_t redSize = _farpeekb(_dos_ds, __tb + 31);
int32_t redPos = _farpeekb(_dos_ds, __tb + 32);
int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
int32_t greenPos = _farpeekb(_dos_ds, __tb + 34);
int32_t blueSize = _farpeekb(_dos_ds, __tb + 35);
int32_t bluePos = _farpeekb(_dos_ds, __tb + 36);
d->format.redBits = redSize;
d->format.redShift = redPos;
d->format.redMask = ((1U << redSize) - 1) << redPos;
d->format.greenBits = greenSize;
d->format.greenShift = greenPos;
d->format.greenMask = ((1U << greenSize) - 1) << greenPos;
d->format.blueBits = blueSize;
d->format.blueShift = bluePos;
d->format.blueMask = ((1U << blueSize) - 1) << bluePos;
} else {
// 8-bit mode — masks not used, packColor returns palette index
d->format.redBits = 0;
d->format.redShift = 0;
d->format.redMask = 0;
d->format.greenBits = 0;
d->format.greenShift = 0;
d->format.greenMask = 0;
d->format.blueBits = 0;
d->format.blueShift = 0;
d->format.blueMask = 0;
}
// Store physical address in lfb field temporarily (will be remapped)
d->lfb = (uint8_t *)(uintptr_t)physAddr;
}
// ============================================================
// mapLfb
// ============================================================
static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
__dpmi_meminfo info;
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
info.address = physAddr;
info.size = fbSize;
if (__dpmi_physical_address_mapping(&info) != 0) {
fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
return -1;
}
// Lock the region to prevent paging
__dpmi_meminfo lockInfo;
lockInfo.address = info.address;
lockInfo.size = fbSize;
__dpmi_lock_linear_region(&lockInfo);
// Enable near pointers for direct access
if (__djgpp_nearptr_enable() == 0) {
fprintf(stderr, "VBE: Failed to enable near pointers\n");
return -1;
}
d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);
return 0;
}
// ============================================================
// packColor
// ============================================================
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b) {
if (d->format.bitsPerPixel == 8) {
return dvxNearestPalEntry(d->palette, r, g, b);
}
uint32_t rv = ((uint32_t)r >> (8 - d->format.redBits)) << d->format.redShift;
uint32_t gv = ((uint32_t)g >> (8 - d->format.greenBits)) << d->format.greenShift;
uint32_t bv = ((uint32_t)b >> (8 - d->format.blueBits)) << d->format.blueShift;
return rv | gv | bv;
}
// ============================================================
// resetClipRect
// ============================================================
void resetClipRect(DisplayT *d) {
d->clipX = 0;
d->clipY = 0;
d->clipW = d->width;
d->clipH = d->height;
}
// ============================================================
// setClipRect
// ============================================================
void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
d->clipX = x;
d->clipY = y;
d->clipW = w;
d->clipH = h;
}
// ============================================================
// setVesaMode
// ============================================================
static int32_t setVesaMode(uint16_t mode) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F02;
r.x.bx = mode | 0x4000; // bit 14 = use LFB
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
return -1;
}
return 0;
}
// ============================================================
// videoInit
// ============================================================
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
uint16_t bestMode;
uint32_t physAddr;
memset(d, 0, sizeof(*d));
// Find the best VESA mode
if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
return -1;
}
// Save the physical address before we overwrite it
physAddr = (uint32_t)(uintptr_t)d->lfb;
// Set the mode
if (setVesaMode(bestMode) != 0) {
return -1;
}
// Map the LFB
if (mapLfb(d, physAddr) != 0) {
return -1;
}
// Allocate backbuffer
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
d->backBuf = (uint8_t *)malloc(fbSize);
if (!d->backBuf) {
fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
return -1;
}
memset(d->backBuf, 0, fbSize);
// Set up palette for 8-bit mode
if (d->format.bitsPerPixel == 8) {
d->palette = (uint8_t *)malloc(768);
if (!d->palette) {
fprintf(stderr, "VBE: Failed to allocate palette\n");
free(d->backBuf);
d->backBuf = NULL;
return -1;
}
dvxGeneratePalette(d->palette);
videoSetPalette(d->palette, 0, 256);
}
// Initialize clip rect to full display
resetClipRect(d);
fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n",
bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch);
return 0;
}
// ============================================================
// videoSetPalette
// ============================================================
void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
// Set VGA DAC registers directly via port I/O
// Port 0x3C8 = write index, Port 0x3C9 = data (R, G, B in 6-bit values)
outportb(0x3C8, (uint8_t)firstEntry);
for (int32_t i = 0; i < count; i++) {
int32_t idx = (firstEntry + i) * 3;
outportb(0x3C9, pal[idx + 0] >> 2); // VGA DAC uses 6-bit values
outportb(0x3C9, pal[idx + 1] >> 2);
outportb(0x3C9, pal[idx + 2] >> 2);
}
}
// ============================================================
// videoShutdown
// ============================================================
void videoShutdown(DisplayT *d) {
// Restore text mode (mode 3)
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x10, &r);
if (d->backBuf) {
free(d->backBuf);
d->backBuf = NULL;
}
if (d->palette) {
free(d->palette);
d->palette = NULL;
}
d->lfb = NULL;
__djgpp_nearptr_disable();
}