WinDriver/demo.c
Scott Duensing e8a7812233 Add loadable font support: .FON/.FNT loading with v2→v3 conversion
Load Windows 3.x .FON (NE resource containers) and raw .FNT font files
for use with ExtTextOut. Multiple fonts can be active simultaneously.
All available .FON files contain v2 fonts but VBESVGA.DRV requires v3
in 386 protected mode, so the loader converts on load.

- Add NE resource table structures (NeResourceTypeT, NeResourceEntryT)
- Add WdrvFontT opaque type with load/unload API
- Implement buildFontFromFnt() v2→v3 converter
- Implement wdrvLoadFontFon() NE resource parser
- Move font from per-driver to global singleton (wdrvLoadFontBuiltin)
- Add WdrvFontT parameter to wdrvExtTextOut (NULL = built-in)
- Add Demo 6: Courier, Sans Serif, System fonts side by side
- Copy fon/*.FON to bin/ during build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:04:30 -06:00

410 lines
14 KiB
C

// ============================================================================
// demo.c - Demonstration of using Windows 3.x display drivers from DOS
//
// This program loads a Windows 3.x accelerated display driver (.DRV file)
// and uses its hardware-accelerated functions to draw on screen.
//
// Usage: demo [-d] <driver.drv>
// -d Enable debug output
//
// Example:
// demo vga.drv
// demo -d s3trio.drv
// ============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pc.h>
#include <dpmi.h>
#include "win31drv/windrv.h"
#include "win31drv/wintypes.h"
#include "win31drv/log.h"
__attribute__((noinline))
static void demoDrawing(WdrvHandleT drv);
static void printDriverInfo(WdrvHandleT drv);
static void printUsage(const char *progName);
static void setupPalette(WdrvHandleT drv);
int main(int argc, char *argv[])
{
const char *driverPath = NULL;
bool debug = false;
// Parse arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
debug = true;
} else if (argv[i][0] != '-') {
driverPath = argv[i];
} else {
printUsage(argv[0]);
return 1;
}
}
if (!driverPath) {
printUsage(argv[0]);
return 1;
}
logInit("OUTPUT.LOG");
// Initialize the library
logMsg("Initializing windrv library...\n");
int32_t err = wdrvInit();
if (err != WDRV_OK) {
logErr("Failed to initialize: %s\n", wdrvGetLastErrorString());
return 1;
}
if (debug) {
wdrvSetDebug(true);
}
// Load the driver
logMsg("Loading driver: %s\n", driverPath);
WdrvHandleT drv = wdrvLoadDriver(driverPath);
if (!drv) {
logErr("Failed to load driver: %s\n", wdrvGetLastErrorString());
wdrvShutdown();
return 1;
}
// Print driver info
printDriverInfo(drv);
// Redirect stdout to the log file before entering SVGA mode.
// In graphics mode, console output through INT 21h can crash
// because the console handler tries to access VGA text memory
// that's now part of the SVGA framebuffer.
freopen("OUTPUT.LOG", "a", stdout);
// Enable the driver (set video mode)
logMsg("Enabling driver...\n");
err = wdrvEnable(drv, 0, 0, 0); // Use driver defaults
if (err != WDRV_OK) {
logErr("Failed to enable driver: %s\n", wdrvGetLastErrorString());
wdrvUnloadDriver(drv);
wdrvShutdown();
return 1;
}
// Set up a 256-color palette via the driver's SetPalette DDI.
// This updates both the driver's internal color table (used by
// RealizeObject for color matching) and the VGA DAC hardware.
setupPalette(drv);
// Run drawing demos
demoDrawing(drv);
logMsg("Drawing complete. Press any key...\n");
// Wait for a keypress so we can see the output
while (!kbhit()) {
// Busy-wait; kbhit() does keyboard I/O which keeps DOSBox-X responsive
}
(void)getkey(); // consume the key
// Disable the driver (restore text mode)
logMsg("Calling wdrvDisable...\n");
err = wdrvDisable(drv);
if (err != WDRV_OK) {
logMsg("wdrvDisable failed (err=%d), forcing text mode via INT 10h\n", (int)err);
__dpmi_regs dr;
memset(&dr, 0, sizeof(dr));
dr.x.ax = 0x0003; // INT 10h AH=00 AL=03: set 80x25 text mode
__dpmi_int(0x10, &dr);
}
// Clean up
logMsg("Unloading driver...\n");
wdrvUnloadDriver(drv);
wdrvShutdown();
logMsg("Done.\n");
logShutdown();
return 0;
}
static void printUsage(const char *progName)
{
logErr("Usage: %s [-d] <driver.drv>\n", progName);
logErr(" -d Enable debug output\n");
logErr("\nLoads a Windows 3.x display driver and demonstrates\n");
logErr("its accelerated drawing functions from DOS.\n");
}
static void printDriverInfo(WdrvHandleT drv)
{
WdrvInfoT info;
wdrvGetInfo(drv, &info);
logMsg("\n=== Driver Information ===\n");
logMsg(" Name: %s\n", info.driverName);
logMsg(" Version: %u.%u\n", info.driverVersion >> 8,
info.driverVersion & 0xFF);
logMsg(" Resolution: %" PRId32 "x%" PRId32 "\n", info.maxWidth, info.maxHeight);
logMsg(" Color depth: %" PRId32 " bpp\n", info.maxBpp);
logMsg(" Colors: %" PRId32 "\n", info.numColors);
logMsg(" Raster caps: 0x%04" PRIX32 "\n", info.rasterCaps);
logMsg(" Capabilities:\n");
logMsg(" BitBlt: %s\n", info.hasBitBlt ? "yes" : "no");
logMsg(" Output: %s\n", info.hasOutput ? "yes" : "no");
logMsg(" Pixel: %s\n", info.hasPixel ? "yes" : "no");
logMsg(" StretchBlt: %s\n", info.hasStretchBlt ? "yes" : "no");
logMsg(" ExtTextOut: %s\n", info.hasExtTextOut ? "yes" : "no");
logMsg(" SetPalette: %s\n", info.hasSetPalette ? "yes" : "no");
logMsg(" SetCursor: %s\n", info.hasSetCursor ? "yes" : "no");
}
// noinline: when inlined into main with -O2, the optimizer mishandles
// callee-saved registers across the thunk calls in the Demo 2 → Demo 3
// transition, causing the handle pointer to be corrupted.
__attribute__((noinline))
static void demoDrawing(WdrvHandleT drv)
{
WdrvInfoT info;
wdrvGetInfo(drv, &info);
int16_t screenW = (int16_t)info.maxWidth;
int16_t screenH = (int16_t)info.maxHeight;
if (screenW == 0) {
screenW = 640;
}
if (screenH == 0) {
screenH = 480;
}
logMsg("demoDrawing: screenW=%d screenH=%d hasBitBlt=%d hasOutput=%d hasPixel=%d\n",
screenW, screenH, info.hasBitBlt, info.hasOutput, info.hasPixel);
// Demo 1: Fill rectangles
if (info.hasBitBlt) {
logMsg("Demo 1: Fill rectangles\n");
// Clear screen to white
wdrvFillRect(drv, 0, 0, screenW, screenH, MAKE_RGB(255, 255, 255));
static const uint32_t vgaColors[] = {
MAKE_RGB( 0, 0, 0), // black
MAKE_RGB( 0, 0, 255), // blue
MAKE_RGB( 0, 255, 0), // green
MAKE_RGB( 0, 255, 255), // cyan
MAKE_RGB(255, 0, 0), // red
MAKE_RGB(255, 0, 255), // magenta
MAKE_RGB(255, 255, 0), // yellow
MAKE_RGB(170, 170, 170), // light gray
MAKE_RGB( 85, 85, 85), // dark gray
MAKE_RGB( 85, 85, 255), // light blue
MAKE_RGB( 85, 255, 85), // light green
MAKE_RGB( 85, 255, 255), // light cyan
MAKE_RGB(255, 85, 85), // light red
MAKE_RGB(255, 85, 255), // light magenta
MAKE_RGB(255, 255, 85), // yellow-ish
MAKE_RGB(128, 128, 128), // mid gray
};
int16_t boxW = screenW / 4;
int16_t boxH = screenH / 4;
for (int16_t row = 0; row < 4; row++) {
for (int16_t col = 0; col < 4; col++) {
int idx = row * 4 + col;
wdrvFillRect(drv, col * boxW + 4, row * boxH + 4,
boxW - 8, boxH - 8, vgaColors[idx]);
}
}
logMsg(" Drew %d colored rectangles\n", 16);
}
// Demo 2: Draw pixel patterns
if (info.hasPixel) {
logMsg("Demo 2: Pixel patterns (8x8)\n");
int pixCount = 0;
for (int16_t y = 0; y < 8 && y < screenH; y++) {
for (int16_t x = 0; x < 8 && x < screenW; x++) {
uint8_t r = (uint8_t)(x * 32);
uint8_t g = (uint8_t)(y * 32);
uint8_t b = (uint8_t)((x + y) * 16);
wdrvSetPixel(drv, x + screenW - 18, y + 10, MAKE_RGB(r, g, b));
pixCount++;
}
}
logMsg(" Drew %d pixels\n", pixCount);
}
// Demo 3: Draw lines using Output (polyline with realized pen)
if (info.hasOutput) {
logMsg("Demo 3: Lines (starburst)\n");
int16_t cx = screenW / 2;
int16_t cy = screenH / 2;
int16_t radius = (screenH < screenW ? screenH : screenW) / 3;
int lineCount = 0;
for (int angle = 0; angle < 360; angle += 15) {
int32_t dx = 0;
int32_t dy = 0;
int a = angle % 360;
int qa = a % 90;
int32_t s = (int32_t)qa * radius / 90;
int32_t c = (int32_t)(90 - qa) * radius / 90;
if (a < 90) {
dx = s;
dy = -c;
} else if (a < 180) {
dx = c;
dy = s;
} else if (a < 270) {
dx = -s;
dy = c;
} else {
dx = -c;
dy = -s;
}
Point16T pts[2];
pts[0].x = cx;
pts[0].y = cy;
pts[1].x = (int16_t)(cx + dx);
pts[1].y = (int16_t)(cy + dy);
uint32_t lineColor = MAKE_RGB(
(uint8_t)(angle * 255 / 360),
(uint8_t)(255 - angle * 255 / 360),
128);
wdrvPolyline(drv, pts, 2, lineColor);
lineCount++;
}
logMsg(" Drew %d lines\n", lineCount);
}
// Demo 4: Screen-to-screen blit test
if (info.hasBitBlt) {
logMsg("Demo 4: Screen-to-screen blit\n");
WdrvBitBltParamsT bp;
memset(&bp, 0, sizeof(bp));
bp.srcX = 0;
bp.srcY = 0;
bp.dstX = screenW / 2;
bp.dstY = screenH / 2;
bp.width = screenW / 4;
bp.height = screenH / 4;
bp.rop3 = SRCCOPY;
wdrvBitBlt(drv, &bp);
logMsg(" Screen blit done\n");
}
// Demo 5: Text output using ExtTextOut
if (info.hasExtTextOut) {
logMsg("Demo 5: ExtTextOut text rendering\n");
int32_t ret;
// Opaque text: white on blue (built-in font via NULL)
const char *msg1 = "Hello from ExtTextOut!";
ret = wdrvExtTextOut(drv, 10, 10, msg1, (int16_t)strlen(msg1),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 170), true, NULL);
logMsg(" msg1 ret=%" PRId32 "\n", ret);
// Opaque text: yellow on dark red
const char *msg2 = "Win3.x DDI Text Output";
ret = wdrvExtTextOut(drv, 10, 30, msg2, (int16_t)strlen(msg2),
MAKE_RGB(255, 255, 0), MAKE_RGB(170, 0, 0), true, NULL);
logMsg(" msg2 ret=%" PRId32 "\n", ret);
// Transparent text: green on existing background
const char *msg3 = "Transparent mode";
ret = wdrvExtTextOut(drv, 10, 50, msg3, (int16_t)strlen(msg3),
MAKE_RGB(0, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
logMsg(" msg3 ret=%" PRId32 "\n", ret);
// Show resolution info
char buf[64];
int len = sprintf(buf, "Mode: %dx%d", screenW, screenH);
ret = wdrvExtTextOut(drv, 10, 70, buf, (int16_t)len,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), true, NULL);
logMsg(" msg4 ret=%" PRId32 "\n", ret);
logMsg(" Text output done\n");
}
// Demo 6: Multiple loaded fonts
if (info.hasExtTextOut) {
logMsg("Demo 6: Loaded fonts from .FON files\n");
int32_t ret;
// Load fonts from .FON files
WdrvFontT courFont = wdrvLoadFontFon("COURE.FON", 1); // 9x16 Courier
WdrvFontT sansFont = wdrvLoadFontFon("SSERIFE.FON", 0); // MS Sans Serif
WdrvFontT sysFont = wdrvLoadFontFon("VGASYS.FON", 0); // System
int16_t textY = 100;
if (courFont) {
const char *msg = "Courier: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 128), true, courFont);
logMsg(" Courier ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" COURE.FON not loaded\n");
}
if (sansFont) {
const char *msg = "Sans Serif: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 0), MAKE_RGB(128, 0, 0), true, sansFont);
logMsg(" Sans ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" SSERIFE.FON not loaded\n");
}
if (sysFont) {
const char *msg = "System: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, sysFont);
logMsg(" System ret=%" PRId32 "\n", ret);
textY += 20;
} else {
logMsg(" VGASYS.FON not loaded\n");
}
// Built-in font for comparison
const char *msg = "Built-in: The quick brown fox jumps over the lazy dog";
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 128, 0), true, NULL);
logMsg(" Built-in ret=%" PRId32 "\n", ret);
wdrvUnloadFont(courFont);
wdrvUnloadFont(sansFont);
wdrvUnloadFont(sysFont);
logMsg(" Font demo done\n");
}
}
static void setupPalette(WdrvHandleT drv)
{
// The driver sets up its own palette during Enable (via VBE 4F09)
// and stores an internal color table that RealizeObject uses for
// color matching. We leave the palette as-is so the DAC and the
// internal table stay in sync. RealizeObject will find the best
// matching index, and the DAC will display the correct color.
(void)drv;
}