// dvx_video.c — Layer 1: VESA VBE video backend for DV/X GUI #include "dvxVideo.h" #include "dvxPalette.h" #include #include #include #include #include #include #include #include // ============================================================ // 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(); }