Integrate stb_truetype.h to rasterize TTF glyphs into 1-bit .FNT v3 format consumed by Win3.x display drivers. Key implementation detail: .FNT bitmaps use column-major byte order (all rows of byte-column 0 first, then byte-column 1, etc.), not row-major. New API: wdrvLoadFontTtf(path, pointSize) loads any TTF at any size. Demo 7 renders Liberation Sans/Serif/Mono at 16/20/24pt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
456 lines
15 KiB
C
456 lines
15 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");
|
|
}
|
|
|
|
// Demo 7: TrueType font rendering
|
|
if (info.hasExtTextOut) {
|
|
logMsg("Demo 7: TrueType fonts\n");
|
|
int32_t ret;
|
|
int16_t textY = 180;
|
|
|
|
WdrvFontT ttfSmall = wdrvLoadFontTtf("LIBSANS.TTF", 16);
|
|
WdrvFontT ttfMedium = wdrvLoadFontTtf("LIBSERIF.TTF", 20);
|
|
WdrvFontT ttfLarge = wdrvLoadFontTtf("LIBMONO.TTF", 24);
|
|
|
|
if (ttfSmall) {
|
|
const char *msg = "TrueType Sans 16pt: 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, ttfSmall);
|
|
logMsg(" TTF small ret=%" PRId32 "\n", ret);
|
|
textY += 22;
|
|
} else {
|
|
logMsg(" LIBSANS.TTF 16pt not loaded\n");
|
|
}
|
|
|
|
if (ttfMedium) {
|
|
const char *msg = "TrueType Serif 20pt: ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
|
|
MAKE_RGB(255, 255, 0), MAKE_RGB(128, 0, 0), true, ttfMedium);
|
|
logMsg(" TTF medium ret=%" PRId32 "\n", ret);
|
|
textY += 26;
|
|
} else {
|
|
logMsg(" LIBSERIF.TTF 20pt not loaded\n");
|
|
}
|
|
|
|
if (ttfLarge) {
|
|
const char *msg = "TrueType Mono 24pt: 0123456789";
|
|
ret = wdrvExtTextOut(drv, 10, textY, msg, (int16_t)strlen(msg),
|
|
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, ttfLarge);
|
|
logMsg(" TTF large ret=%" PRId32 "\n", ret);
|
|
textY += 30;
|
|
} else {
|
|
logMsg(" LIBMONO.TTF 24pt not loaded\n");
|
|
}
|
|
|
|
wdrvUnloadFont(ttfSmall);
|
|
wdrvUnloadFont(ttfMedium);
|
|
wdrvUnloadFont(ttfLarge);
|
|
logMsg(" TTF 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;
|
|
}
|
|
|