445 lines
12 KiB
C
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);
|
|
__djgpp_nearptr_disable();
|
|
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;
|
|
__djgpp_nearptr_disable();
|
|
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();
|
|
}
|