Add wdrvScreenshot, auto-demo mode, palette fixes, and DAC width detection

Add wdrvScreenshot() to capture the screen to PNG via stb_image_write.h,
reading the framebuffer (or DDI bitmap fallback) and VGA DAC palette.

Convert demo.c to non-interactive mode with automatic screenshots after
each demo (DEMO01-15.PNG) and no keypress waits, plus per-driver
DOSBox-X configs for automated testing.

Set a standard Windows 3.1 256-color palette (8R x 8G x 4B color cube
with 20 static system colors) to ensure consistent output across drivers.

Fix wdrvSetPalette to also program the VGA DAC directly, since VBESVGA's
SetPalette DDI updates its internal color table but not the hardware.

Detect DAC width via VBE 4F08 (S3TRIO=6-bit, VBESVGA=8-bit) and use
correct shift in both DAC writes and reads — fixes dark display on
VBESVGA where 6-bit values in 8-bit DAC produced 1/4 brightness.

Fix S3 dispYOffset: extend PDEVICE deHeight by the offset so the
driver's internal clipping allows the full 600-row logical screen,
rather than incorrectly reducing dpVertRes to 590.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Duensing 2026-02-23 18:55:57 -06:00
parent fbb1cce5c3
commit 5527130145
11 changed files with 4541 additions and 152 deletions

3
.gitignore vendored
View file

@ -7,6 +7,9 @@ bin/
# Runtime logs
OUTPUT.LOG
# Screenshots
screenshots/
# Editor backups
*~
*.swp

View file

@ -50,14 +50,37 @@ windriver/
AND driver is not VGA-class (1bpp/4planes).
- **Pattern scratch artifact**: S3 driver writes 8x8 dithered brush pattern to VRAM at fixed
position (~(144,1)-(151,8)) during accelerated pattern fills. Fixed by shifting CRTC display
start down 10 scanlines (`dispYOffset`) so the scratch area is off-screen.
start down 10 scanlines (`dispYOffset`) so the scratch area is off-screen. All drawing Y
coordinates are offset by dispYOffset. The full dpVertRes (600) is reported and usable —
the shift just consumes slightly more VRAM.
- **S3TRIO BitBlt source corruption**: S3TRIO's accelerated BitBlt corrupts source VRAM
during source-dependent ROP operations (SRCINVERT, NOTSRCCOPY, SRCAND, SRCPAINT).
In Windows 3.1, GDI uses intermediate off-screen bitmaps. Our direct DDI calls must
work around this by redrawing source rects after the ROP, or using separate source areas.
Off-screen VRAM (y >= screenH) is NOT usable — the driver clips to screen dimensions.
- **`-fno-gcse` required for windrv.c**: With -O2 GCSE, stack layout causes issues during
16-bit driver calls. Only windrv.c needs this. See `WINDRV_CFLAGS` in win31drv/Makefile.
- Output DDI (polylines/rectangles) requires a **physical pen** from RealizeObject, not a
raw LogPen16T. The pen must be in DGROUP (same as brush, drawMode, PDEVICE).
- **Curve primitives removed**: Ellipses, polygons, roundrects, arcs, and pies were removed
because DIB engine drivers (VBESVGA/ET4000) hang (expect GDI curve decomposition callback)
and S3TRIO only partially renders them. Only polyline is reliable via Output DDI.
- **OS_RECTANGLE crashes DIB engine drivers**: VBESVGA/ET4000 Output(OS_RECTANGLE) crashes
like curve primitives. `wdrvRectangleEx` draws rectangles as two 3-point polylines instead.
- **Output DDI lpClipRect**: DIB engine drivers (VBESVGA) dereference lpClipRect
unconditionally in polyline paths too. Always pass a valid clip rect (0,0,0x7FFF,0x7FFF).
- `wdrvUnloadDriver` does NOT auto-call Disable — caller must handle text mode restore
- `sleep()` hangs under DOSBox-X because BIOS timer ticks don't advance without I/O
- Debug output: `-d` flag enables verbose logging in neload, winstub, thunk, and windrv
- **SetPalette DDI vs VGA DAC**: VBESVGA's SetPalette DDI updates the driver's internal
color table (used by ColorInfo for RGB→index matching) but does NOT program the VGA
DAC hardware. In real Windows 3.1, GDI programs the DAC separately. `wdrvSetPalette`
works around this by also writing DAC registers directly (ports 0x3C8/0x3C9) after
the DDI call. This is idempotent on drivers like S3TRIO that already program the DAC.
- **DAC width**: S3TRIO uses 6-bit DAC (values 0-63), VBESVGA uses 8-bit DAC (values
0-255). Detected at Enable via VBE 4F08 subfunc 01. Both `wdrvSetPalette` (port write)
and `readDacPalette` (screenshot) use the detected width for correct shift amounts.
Wrong shift causes dark display (6-bit values in 8-bit DAC = 1/4 brightness).
- Known issue: mode mismatch HW=800x600 vs GDIINFO=640x480
## DGROUP Stack Management
@ -111,6 +134,11 @@ windriver/
- DOSBox-X machine type: `svga_et4000` for ET4000 hardware emulation
- ET4000 is 640x480 8bpp, software-rendered (no accelerator engine in DOSBox-X)
- CR30=0x00 on ET4000 → isS3=false → no S3 engine wait, no display start shift
- **GetPixel breaks ScanLR on ET4000**: The Pixel DDI with color=-1 (get mode) leaves
VGA hardware state (likely GR5 read mode) that causes ScanLR to not match the pixel
just read. GetPixel itself returns correct values; only the ScanLR interaction is
broken. Flood fill software path avoids GetPixel entirely, using ScanLR for all
pixel-color queries.
## Font Loading Notes
- .FON files are NE containers with RT_FONT (type 8) resources; each resource is raw .FNT data
@ -133,6 +161,42 @@ windriver/
- Demo 4: Screen-to-screen blit (BitBlt SRCCOPY) — works
- Demo 5: ExtTextOut text rendering — works (VBESVGA.DRV)
- Demo 7: TrueType font rendering at multiple sizes — works
- Demo 8: Color text showcase (fg/bg colors, opaque/transparent, palette grid) — works
- Demo 9: ROP3 operations (DSTINVERT, SRCINVERT, NOTSRCCOPY, SRCAND, SRCPAINT) — works
- Demo 10: ScanLR + Flood Fill (FB or software fallback) — works on all drivers
- Demo 11: Text measurement (GetCharWidth DDI, wdrvMeasureText) — works
- Demo 12: Styled pen lines (software Bresenham, all drivers) — works
- Demo 13: Pixel buffer blit (FB or software fallback) — works on all drivers
- Demo 14: Hardware cursor (arrow, crosshair, ibeam, hand; animated circle) — works
- Demo 15: Screen save/restore (screen-to-screen BitBlt stash/restore) — works
- VGA.DRV: 640x480 4-plane 16-color mode; limited color palette but functional
- ET4000.DRV: 640x480 8bpp on svga_et4000; software-only, no hw acceleration
- Drivers stored in `drivers/` directory, copied to `bin/` during build
## Software Rendering Fallbacks
- **Styled pens**: Always software-rendered via Bresenham + Pixel DDI (PS_SOLID uses HW Output DDI).
S3TRIO silently accepts styled pens but doesn't render them; software path gives identical output everywhere.
- **Flood fill**: Uses direct FB when available, falls back to ScanLR DDI + FillRect.
Cannot use GetPixel — ET4000 DIB engine's Pixel DDI (color=-1) corrupts VRAM,
causing subsequent ScanLR to not match the read pixel. Seed color is determined by
probing ScanLR with each palette index until a match is found.
- **Pixel blit**: Uses direct FB memcpy when available, falls back to per-pixel SetPixel with PALETTEINDEX.
- All four drivers (S3TRIO, VBESVGA, ET4000, VGA) now have identical feature sets.
## New API Functions (added with demos 9-15)
- `wdrvScanLR(handle, x, y, color, style)` — ScanLR DDI wrapper (ordinal 12)
- `wdrvFloodFill(handle, x, y, fillColor)` — scanline flood fill (FB or software)
- `wdrvGetCharWidths(handle, font, firstChar, lastChar, widths)` — GetCharWidth DDI
- `wdrvMeasureText(handle, font, text, length)` — sum char widths for string
- `wdrvPolylineEx(handle, points, count, color, penStyle)` — polyline with pen style
- `wdrvRectangleEx(handle, x, y, w, h, color, penStyle)` — rectangle with pen style
- `wdrvBlitPixels(handle, x, y, w, h, pixels, srcPitch)` — pixel blit (FB or software)
- `wdrvBlitBmp(handle, x, y, bmpPath, setPalette)` — load+display 8bpp BMP
- `wdrvSetCursor(handle, shape)` — built-in cursor shapes (arrow/crosshair/ibeam/hand/none)
- `wdrvSetCursorCustom(handle, hotX, hotY, andMask, xorMask)` — custom 32x32 mono cursor
- `wdrvMoveCursor(handle, x, y)` — move hardware cursor
- `wdrvCreateBitmap(handle, width, height)` — CreateBitmap DDI
- `wdrvDeleteBitmap(handle, bitmap)` — DeleteBitmap DDI
- `wdrvBitmapSetPixels/GetPixels(handle, bitmap, data, size)` — BitmapBits DDI
- `wdrvBitBltFromBitmap/ToBitmap(handle, bitmap, ...)` — BitBlt with bitmap PDEVICE
- `wdrvScreenshot(handle, filename)` — capture screen to PNG (FB or DDI fallback)

885
demo.c
View file

@ -102,13 +102,7 @@ int main(int argc, char *argv[])
// 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
logMsg("Drawing complete.\n");
// Disable the driver (restore text mode)
logMsg("Calling wdrvDisable...\n");
@ -162,6 +156,10 @@ static void printDriverInfo(WdrvHandleT drv)
logMsg(" ExtTextOut: %s\n", info.hasExtTextOut ? "yes" : "no");
logMsg(" SetPalette: %s\n", info.hasSetPalette ? "yes" : "no");
logMsg(" SetCursor: %s\n", info.hasSetCursor ? "yes" : "no");
logMsg(" MoveCursor: %s\n", info.hasMoveCursor ? "yes" : "no");
logMsg(" ScanLR: %s\n", info.hasScanLR ? "yes" : "no");
logMsg(" GetCharWidth:%s\n", info.hasGetCharWidth ? "yes" : "no");
logMsg(" CreateBitmap:%s\n", info.hasCreateBitmap ? "yes" : "no");
}
@ -225,6 +223,7 @@ static void demoDrawing(WdrvHandleT drv)
}
logMsg(" Drew %d colored rectangles\n", 16);
wdrvScreenshot(drv, "DEMO01.PNG");
}
// Demo 2: Draw pixel patterns
@ -241,6 +240,7 @@ static void demoDrawing(WdrvHandleT drv)
}
}
logMsg(" Drew %d pixels\n", pixCount);
wdrvScreenshot(drv, "DEMO02.PNG");
}
// Demo 3: Draw lines using Output (polyline with realized pen)
@ -290,6 +290,7 @@ static void demoDrawing(WdrvHandleT drv)
lineCount++;
}
logMsg(" Drew %d lines\n", lineCount);
wdrvScreenshot(drv, "DEMO03.PNG");
}
// Demo 4: Screen-to-screen blit test
@ -307,6 +308,7 @@ static void demoDrawing(WdrvHandleT drv)
bp.rop3 = SRCCOPY;
wdrvBitBlt(drv, &bp);
logMsg(" Screen blit done\n");
wdrvScreenshot(drv, "DEMO04.PNG");
}
// Demo 5: Text output using ExtTextOut
@ -340,6 +342,7 @@ static void demoDrawing(WdrvHandleT drv)
logMsg(" msg4 ret=%" PRId32 "\n", ret);
logMsg(" Text output done\n");
wdrvScreenshot(drv, "DEMO05.PNG");
}
// Demo 6: Multiple loaded fonts
@ -394,6 +397,7 @@ static void demoDrawing(WdrvHandleT drv)
wdrvUnloadFont(sansFont);
wdrvUnloadFont(sysFont);
logMsg(" Font demo done\n");
wdrvScreenshot(drv, "DEMO06.PNG");
}
// Demo 7: TrueType font rendering
@ -440,17 +444,872 @@ static void demoDrawing(WdrvHandleT drv)
wdrvUnloadFont(ttfMedium);
wdrvUnloadFont(ttfLarge);
logMsg(" TTF demo done\n");
wdrvScreenshot(drv, "DEMO07.PNG");
}
// Demo 8: Color text showcase
if (info.hasBitBlt && info.hasExtTextOut) {
logMsg("Demo 8: Color text showcase\n");
int32_t ret;
// Clear screen to black
WdrvBitBltParamsT bp;
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
// Load fonts for this demo
WdrvFontT ttfSans20 = wdrvLoadFontTtf("LIBSANS.TTF", 20);
WdrvFontT ttfSans16 = wdrvLoadFontTtf("LIBSANS.TTF", 16);
WdrvFontT courFont = wdrvLoadFontFon("COURE.FON", 1);
WdrvFontT sansFont = wdrvLoadFontFon("SSERIFE.FON", 0);
WdrvFontT ttfSerif16 = wdrvLoadFontTtf("LIBSERIF.TTF", 16);
// --- Title row (y=10) ---
if (ttfSans20) {
const char *title = "Color Text Demo";
ret = wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, ttfSans20);
logMsg(" title ret=%" PRId32 "\n", ret);
}
// --- Foreground color row (y=40) ---
if (ttfSans16) {
static const struct { const char *text; uint32_t color; } fgItems[] = {
{ "Red", MAKE_RGB(255, 0, 0) },
{ "Green", MAKE_RGB( 0, 255, 0) },
{ "Blue", MAKE_RGB( 0, 128, 255) },
{ "Yellow", MAKE_RGB(255, 255, 0) },
{ "Cyan", MAKE_RGB( 0, 255, 255) },
{ "Magenta", MAKE_RGB(255, 0, 255) },
{ "White", MAKE_RGB(255, 255, 255) },
};
int16_t fx = 10;
for (int i = 0; i < 7; i++) {
ret = wdrvExtTextOut(drv, fx, 40, fgItems[i].text, (int16_t)strlen(fgItems[i].text),
fgItems[i].color, MAKE_RGB(0, 0, 0), false, ttfSans16);
logMsg(" fg[%d] ret=%" PRId32 "\n", i, ret);
fx += (int16_t)(strlen(fgItems[i].text) * 10 + 16);
}
}
// --- Opaque background row (y=65) ---
if (ttfSans16) {
static const struct { const char *text; uint32_t fg; uint32_t bg; } bgItems[] = {
{ "White on Blue", MAKE_RGB(255, 255, 255), MAKE_RGB( 0, 0, 200) },
{ "Black on Yellow", MAKE_RGB( 0, 0, 0), MAKE_RGB(255, 255, 0) },
{ "White on Red", MAKE_RGB(255, 255, 255), MAKE_RGB(200, 0, 0) },
{ "Black on Green", MAKE_RGB( 0, 0, 0), MAKE_RGB( 0, 200, 0) },
{ "Yellow on Magenta", MAKE_RGB(255, 255, 0), MAKE_RGB(180, 0, 180) },
{ "Black on Cyan", MAKE_RGB( 0, 0, 0), MAKE_RGB( 0, 200, 200) },
};
int16_t bx = 10;
for (int i = 0; i < 6; i++) {
ret = wdrvExtTextOut(drv, bx, 65, bgItems[i].text, (int16_t)strlen(bgItems[i].text),
bgItems[i].fg, bgItems[i].bg, true, ttfSans16);
logMsg(" bg[%d] ret=%" PRId32 "\n", i, ret);
bx += (int16_t)(strlen(bgItems[i].text) * 9 + 12);
}
}
// --- Transparent over colored rectangles (y=95) ---
// Dark blue strip
wdrvFillRect(drv, 10, 95, 600, 25, MAKE_RGB(0, 0, 128));
if (ttfSans16) {
const char *msg = "Transparent text over dark blue rectangle";
ret = wdrvExtTextOut(drv, 20, 98, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, ttfSans16);
logMsg(" trans1 ret=%" PRId32 "\n", ret);
}
// Dark green strip
wdrvFillRect(drv, 10, 125, 600, 25, MAKE_RGB(0, 128, 0));
if (ttfSans16) {
const char *msg = "Yellow text over dark green rectangle";
ret = wdrvExtTextOut(drv, 20, 128, msg, (int16_t)strlen(msg),
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, ttfSans16);
logMsg(" trans2 ret=%" PRId32 "\n", ret);
}
// --- Multiple fonts in color (y=160) ---
{
int16_t fy = 160;
const char *fontMsg = "The quick brown fox jumps over the lazy dog";
int16_t fontLen = (int16_t)strlen(fontMsg);
// Built-in: white on dark red
ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen,
MAKE_RGB(255, 255, 255), MAKE_RGB(170, 0, 0), true, NULL);
logMsg(" font-builtin ret=%" PRId32 "\n", ret);
fy += 20;
// Courier .FON: cyan on black
if (courFont) {
ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen,
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 0, 0), true, courFont);
logMsg(" font-cour ret=%" PRId32 "\n", ret);
fy += 20;
}
// Sans .FON: yellow on dark blue
if (sansFont) {
ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen,
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 128), true, sansFont);
logMsg(" font-sans ret=%" PRId32 "\n", ret);
fy += 20;
}
// TTF Sans: green on dark gray
if (ttfSans16) {
ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen,
MAKE_RGB(0, 255, 0), MAKE_RGB(64, 64, 64), true, ttfSans16);
logMsg(" font-ttfsans ret=%" PRId32 "\n", ret);
fy += 20;
}
// TTF Serif: magenta on dark green
if (ttfSerif16) {
ret = wdrvExtTextOut(drv, 10, fy, fontMsg, fontLen,
MAKE_RGB(255, 0, 255), MAKE_RGB(0, 100, 0), true, ttfSerif16);
logMsg(" font-ttfserif ret=%" PRId32 "\n", ret);
}
}
// --- Color palette grid (y=280) ---
{
static const struct { const char *name; uint32_t color; } fgPal[] = {
{ "Bk", MAKE_RGB( 0, 0, 0) },
{ "Rd", MAKE_RGB(255, 0, 0) },
{ "Gn", MAKE_RGB( 0, 255, 0) },
{ "Bl", MAKE_RGB( 0, 128, 255) },
{ "Yw", MAKE_RGB(255, 255, 0) },
{ "Cn", MAKE_RGB( 0, 255, 255) },
{ "Mg", MAKE_RGB(255, 0, 255) },
{ "Wh", MAKE_RGB(255, 255, 255) },
};
static const struct { const char *name; uint32_t color; } bgPal[] = {
{ "Black", MAKE_RGB( 0, 0, 0) },
{ "Blue", MAKE_RGB( 0, 0, 200) },
{ "Red", MAKE_RGB(200, 0, 0) },
{ "White", MAKE_RGB(255, 255, 255) },
};
int16_t gy = 280;
// Column headers (background names)
int16_t hdrX = 10 + 30; // offset past row labels
for (int bg = 0; bg < 4; bg++) {
ret = wdrvExtTextOut(drv, (int16_t)(hdrX + bg * 75), gy,
bgPal[bg].name, (int16_t)strlen(bgPal[bg].name),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
gy += 18;
// Grid cells
for (int fg = 0; fg < 8; fg++) {
// Row label
ret = wdrvExtTextOut(drv, 10, gy,
fgPal[fg].name, (int16_t)strlen(fgPal[fg].name),
MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL);
for (int bg = 0; bg < 4; bg++) {
ret = wdrvExtTextOut(drv, (int16_t)(40 + bg * 75), gy,
"Aa", 2,
fgPal[fg].color, bgPal[bg].color, true, NULL);
}
gy += 18;
}
logMsg(" palette grid done\n");
}
wdrvUnloadFont(ttfSans20);
wdrvUnloadFont(ttfSans16);
wdrvUnloadFont(courFont);
wdrvUnloadFont(sansFont);
wdrvUnloadFont(ttfSerif16);
logMsg(" Color text demo done\n");
wdrvScreenshot(drv, "DEMO08.PNG");
}
// Demo 9: ROP3 Operations
if (info.hasBitBlt) {
logMsg("Demo 9: ROP3 Operations\n");
int32_t ret;
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
// Title
if (info.hasExtTextOut) {
const char *title = "Demo 9: ROP3 Operations";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Layout: 5 columns, 3 rows
// Row 1 (r1): source color A (also used as BitBlt source)
// Row 2 (r2): operand color B
// Row 3 (r3): ROP result
// S3TRIO's accelerated BitBlt corrupts source VRAM during source-
// dependent ROPs, so each source rect is redrawn after the ROP.
int16_t bx = 20;
int16_t by = 40;
int16_t bw = 100;
int16_t bh = 50;
int16_t gap = 5;
int16_t col = bw + 20;
int16_t r1 = by;
int16_t r2 = by + bh + gap;
int16_t r3 = by + 2 * (bh + gap);
// Column 0: DSTINVERT (no source needed)
wdrvFillRect(drv, bx, r1, bw, bh, MAKE_RGB(255, 0, 0));
memset(&bp, 0, sizeof(bp));
bp.dstX = bx + 10;
bp.dstY = r1 + 10;
bp.width = bw - 20;
bp.height = bh - 20;
bp.rop3 = DSTINVERT;
ret = wdrvBitBlt(drv, &bp);
logMsg(" DSTINVERT ret=%" PRId32 "\n", ret);
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, bx, r3 + bh + 5, "DSTINVERT", 9,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Column 1: SRCINVERT (green XOR yellow)
// Fill source (r1) with green, copy to result (r3) via SRCCOPY
wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0));
memset(&bp, 0, sizeof(bp));
bp.srcX = bx + col;
bp.srcY = r1;
bp.dstX = bx + col;
bp.dstY = r3;
bp.width = bw;
bp.height = bh;
bp.rop3 = SRCCOPY;
wdrvBitBlt(drv, &bp);
wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0));
// Fill source (r1) with yellow, SRCINVERT into result (r3)
wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(255, 255, 0));
memset(&bp, 0, sizeof(bp));
bp.srcX = bx + col;
bp.srcY = r1;
bp.dstX = bx + col;
bp.dstY = r3;
bp.width = bw;
bp.height = bh;
bp.rop3 = SRCINVERT;
ret = wdrvBitBlt(drv, &bp);
logMsg(" SRCINVERT ret=%" PRId32 "\n", ret);
// Redraw display rows (source may be corrupted by S3TRIO)
wdrvFillRect(drv, bx + col, r1, bw, bh, MAKE_RGB(0, 255, 0));
wdrvFillRect(drv, bx + col, r2, bw, bh, MAKE_RGB(255, 255, 0));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, bx + col, r3 + bh + 5, "SRCINVERT", 9,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Column 2: NOTSRCCOPY (NOT blue)
wdrvFillRect(drv, bx + 2 * col, r1, bw, bh, MAKE_RGB(0, 0, 255));
memset(&bp, 0, sizeof(bp));
bp.srcX = bx + 2 * col;
bp.srcY = r1;
bp.dstX = bx + 2 * col;
bp.dstY = r3;
bp.width = bw;
bp.height = bh;
bp.rop3 = NOTSRCCOPY;
ret = wdrvBitBlt(drv, &bp);
logMsg(" NOTSRCCOPY ret=%" PRId32 "\n", ret);
// Redraw source display
wdrvFillRect(drv, bx + 2 * col, r1, bw, bh, MAKE_RGB(0, 0, 255));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, bx + 2 * col, r3 + bh + 5, "NOTSRCCOPY", 10,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Column 3: SRCAND (yellow AND cyan)
// Fill result (r3) with cyan, fill source (r1) with yellow, SRCAND
wdrvFillRect(drv, bx + 3 * col, r3, bw, bh, MAKE_RGB(0, 255, 255));
wdrvFillRect(drv, bx + 3 * col, r1, bw, bh, MAKE_RGB(255, 255, 0));
memset(&bp, 0, sizeof(bp));
bp.srcX = bx + 3 * col;
bp.srcY = r1;
bp.dstX = bx + 3 * col;
bp.dstY = r3;
bp.width = bw;
bp.height = bh;
bp.rop3 = SRCAND;
ret = wdrvBitBlt(drv, &bp);
logMsg(" SRCAND ret=%" PRId32 "\n", ret);
// Redraw display rows
wdrvFillRect(drv, bx + 3 * col, r1, bw, bh, MAKE_RGB(255, 255, 0));
wdrvFillRect(drv, bx + 3 * col, r2, bw, bh, MAKE_RGB(0, 255, 255));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, bx + 3 * col, r3 + bh + 5, "SRCAND", 6,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Column 4: SRCPAINT (red OR blue)
// Fill result (r3) with blue, fill source (r1) with red, SRCPAINT
wdrvFillRect(drv, bx + 4 * col, r3, bw, bh, MAKE_RGB(0, 0, 255));
wdrvFillRect(drv, bx + 4 * col, r1, bw, bh, MAKE_RGB(255, 0, 0));
memset(&bp, 0, sizeof(bp));
bp.srcX = bx + 4 * col;
bp.srcY = r1;
bp.dstX = bx + 4 * col;
bp.dstY = r3;
bp.width = bw;
bp.height = bh;
bp.rop3 = SRCPAINT;
ret = wdrvBitBlt(drv, &bp);
logMsg(" SRCPAINT ret=%" PRId32 "\n", ret);
// Redraw display rows
wdrvFillRect(drv, bx + 4 * col, r1, bw, bh, MAKE_RGB(255, 0, 0));
wdrvFillRect(drv, bx + 4 * col, r2, bw, bh, MAKE_RGB(0, 0, 255));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, bx + 4 * col, r3 + bh + 5, "SRCPAINT", 8,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
logMsg(" ROP3 demo done\n");
wdrvScreenshot(drv, "DEMO09.PNG");
}
// Demo 10: ScanLR + Flood Fill
if (info.hasBitBlt) {
logMsg("Demo 10: ScanLR + Flood Fill\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 10: ScanLR + Flood Fill";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Draw outlined shapes using wdrvRectangle
if (info.hasOutput) {
wdrvRectangle(drv, 50, 50, 150, 100, MAKE_RGB(255, 255, 255));
wdrvRectangle(drv, 250, 50, 150, 100, MAKE_RGB(255, 255, 0));
wdrvRectangle(drv, 450, 50, 150, 100, MAKE_RGB(0, 255, 255));
}
// Try flood fill (FB-based, skips multi-plane VGA)
int32_t ret = wdrvFloodFill(drv, 125, 100, MAKE_RGB(255, 0, 0));
logMsg(" FloodFill rect1 ret=%" PRId32 "\n", ret);
ret = wdrvFloodFill(drv, 325, 100, MAKE_RGB(0, 255, 0));
logMsg(" FloodFill rect2 ret=%" PRId32 "\n", ret);
ret = wdrvFloodFill(drv, 525, 100, MAKE_RGB(0, 0, 255));
logMsg(" FloodFill rect3 ret=%" PRId32 "\n", ret);
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, 80, 155, "Red Fill", 8,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
wdrvExtTextOut(drv, 280, 155, "Green Fill", 10,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
wdrvExtTextOut(drv, 480, 155, "Blue Fill", 9,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Test ScanLR if available
if (info.hasScanLR) {
int16_t leftX = wdrvScanLR(drv, 125, 100, MAKE_RGB(255, 0, 0), WDRV_SCAN_LEFT);
int16_t rightX = wdrvScanLR(drv, 125, 100, MAKE_RGB(255, 0, 0), WDRV_SCAN_RIGHT);
logMsg(" ScanLR at (125,100): left=%d right=%d\n", leftX, rightX);
if (info.hasExtTextOut) {
char buf[64];
int len = sprintf(buf, "ScanLR: L=%d R=%d", leftX, rightX);
wdrvExtTextOut(drv, 10, 200, buf, (int16_t)len,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
}
logMsg(" Flood fill demo done\n");
wdrvScreenshot(drv, "DEMO10.PNG");
}
// Demo 11: Text Measurement
if (info.hasExtTextOut && info.hasGetCharWidth) {
logMsg("Demo 11: Text Measurement\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 11: Text Measurement";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
int16_t ty = 40;
// Measure built-in font
const char *testStr = "Hello, World!";
int16_t testLen = (int16_t)strlen(testStr);
int32_t measuredW = wdrvMeasureText(drv, NULL, testStr, testLen);
logMsg(" Built-in '%s' width=%" PRId32 "\n", testStr, measuredW);
wdrvExtTextOut(drv, 10, ty, testStr, testLen,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 128), true, NULL);
// Draw underline at measured width
if (info.hasOutput) {
Point16T pts[2];
pts[0].x = 10;
pts[0].y = ty + 18;
pts[1].x = 10 + (int16_t)measuredW;
pts[1].y = ty + 18;
wdrvPolyline(drv, pts, 2, MAKE_RGB(255, 0, 0));
}
char buf[80];
int len = sprintf(buf, "Width = %" PRId32 " pixels", measuredW);
wdrvExtTextOut(drv, 10 + (int16_t)measuredW + 10, ty,
buf, (int16_t)len,
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
ty += 30;
// Measure TTF font
WdrvFontT ttfFont = wdrvLoadFontTtf("LIBSANS.TTF", 16);
if (ttfFont) {
const char *ttfStr = "TrueType measurement test";
int16_t ttfLen = (int16_t)strlen(ttfStr);
int32_t ttfW = wdrvMeasureText(drv, ttfFont, ttfStr, ttfLen);
logMsg(" TTF '%s' width=%" PRId32 "\n", ttfStr, ttfW);
wdrvExtTextOut(drv, 10, ty, ttfStr, ttfLen,
MAKE_RGB(0, 255, 255), MAKE_RGB(0, 64, 64), true, ttfFont);
if (info.hasOutput) {
Point16T pts[2];
pts[0].x = 10;
pts[0].y = ty + 20;
pts[1].x = 10 + (int16_t)ttfW;
pts[1].y = ty + 20;
wdrvPolyline(drv, pts, 2, MAKE_RGB(255, 0, 0));
}
len = sprintf(buf, "Width = %" PRId32 " pixels", ttfW);
wdrvExtTextOut(drv, 10 + (int16_t)ttfW + 10, ty,
buf, (int16_t)len,
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
ty += 30;
wdrvUnloadFont(ttfFont);
}
// Show individual char widths
int16_t charWidths[26];
int32_t ret = wdrvGetCharWidths(drv, NULL, 'A', 'Z', charWidths);
if (ret == WDRV_OK) {
int16_t cx = 10;
for (int ci = 0; ci < 26; ci++) {
char ch[2] = { (char)('A' + ci), 0 };
wdrvExtTextOut(drv, cx, ty, ch, 1,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
cx += charWidths[ci];
}
ty += 20;
len = sprintf(buf, "Char widths A-Z from GetCharWidth DDI");
wdrvExtTextOut(drv, 10, ty, buf, (int16_t)len,
MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL);
}
logMsg(" Text measurement demo done\n");
wdrvScreenshot(drv, "DEMO11.PNG");
}
// Demo 12: Styled Pen Lines
if (info.hasOutput) {
logMsg("Demo 12: Styled Pen Lines\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 12: Styled Pen Lines";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
static const struct { const char *name; int16_t style; } penStyles[] = {
{ "SOLID", WDRV_PEN_SOLID },
{ "DASH", WDRV_PEN_DASH },
{ "DOT", WDRV_PEN_DOT },
{ "DASHDOT", WDRV_PEN_DASHDOT },
{ "DASHDOTDOT", WDRV_PEN_DASHDOTDOT },
};
int16_t ly = 40;
for (int si = 0; si < 5; si++) {
// Draw horizontal line
Point16T pts[2];
pts[0].x = 120;
pts[0].y = ly + 8;
pts[1].x = screenW - 20;
pts[1].y = ly + 8;
int32_t ret = wdrvPolylineEx(drv, pts, 2, MAKE_RGB(255, 255, 255), penStyles[si].style);
logMsg(" %s ret=%" PRId32 "\n", penStyles[si].name, ret);
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, 10, ly, penStyles[si].name, (int16_t)strlen(penStyles[si].name),
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
}
ly += 25;
}
// Draw styled rectangles
ly += 10;
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, 10, ly, "Styled Rectangles:", 18,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
ly += 20;
int16_t rx = 20;
for (int si = 0; si < 5; si++) {
wdrvRectangleEx(drv, rx, ly, 100, 60, MAKE_RGB(0, 255, 255), penStyles[si].style);
rx += 120;
}
logMsg(" Styled pen demo done\n");
wdrvScreenshot(drv, "DEMO12.PNG");
}
// Demo 13: Pixel Buffer Blit
if (info.hasBitBlt) {
logMsg("Demo 13: Pixel Buffer Blit\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 13: Pixel Buffer Blit";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Generate a 256-color gradient pattern
int16_t patW = 256;
int16_t patH = 200;
uint8_t *pattern = (uint8_t *)malloc(patW * patH);
if (pattern) {
for (int16_t py = 0; py < patH; py++) {
for (int16_t px = 0; px < patW; px++) {
pattern[py * patW + px] = (uint8_t)((px + py) & 0xFF);
}
}
int32_t ret = wdrvBlitPixels(drv, 20, 40, patW, patH, pattern, patW);
logMsg(" BlitPixels gradient ret=%" PRId32 "\n", ret);
free(pattern);
}
// Generate a color bars pattern
int16_t barW = 256;
int16_t barH = 100;
uint8_t *bars = (uint8_t *)malloc(barW * barH);
if (bars) {
for (int16_t py = 0; py < barH; py++) {
for (int16_t px = 0; px < barW; px++) {
bars[py * barW + px] = (uint8_t)(px);
}
}
int32_t ret = wdrvBlitPixels(drv, 300, 40, barW, barH, bars, barW);
logMsg(" BlitPixels bars ret=%" PRId32 "\n", ret);
free(bars);
}
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, 20, 250, "Gradient pattern", 16,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
wdrvExtTextOut(drv, 300, 250, "Color bars", 10,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
logMsg(" Pixel blit demo done\n");
wdrvScreenshot(drv, "DEMO13.PNG");
}
// Demo 14: Hardware Cursor
if (info.hasSetCursor && info.hasMoveCursor) {
logMsg("Demo 14: Hardware Cursor\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 14: Hardware Cursor";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
wdrvExtTextOut(drv, 10, 30, "Cursor shapes cycling automatically.", 36,
MAKE_RGB(170, 170, 170), MAKE_RGB(0, 0, 0), false, NULL);
}
// Draw some background content
wdrvFillRect(drv, 100, 100, 200, 200, MAKE_RGB(0, 0, 128));
wdrvFillRect(drv, 350, 100, 200, 200, MAKE_RGB(0, 128, 0));
static const WdrvCursorShapeE shapes[] = {
WDRV_CURSOR_ARROW,
WDRV_CURSOR_CROSSHAIR,
WDRV_CURSOR_IBEAM,
WDRV_CURSOR_HAND,
};
static const char *shapeNames[] = {
"Arrow", "Crosshair", "I-Beam", "Hand",
};
for (int si = 0; si < 4; si++) {
int32_t ret = wdrvSetCursor(drv, shapes[si]);
logMsg(" SetCursor(%s) ret=%" PRId32 "\n", shapeNames[si], ret);
if (info.hasExtTextOut) {
// Clear label area
wdrvFillRect(drv, 10, 50, 400, 20, MAKE_RGB(0, 0, 0));
char buf[64];
int len = sprintf(buf, "Cursor: %s", shapeNames[si]);
wdrvExtTextOut(drv, 10, 50, buf, (int16_t)len,
MAKE_RGB(255, 255, 0), MAKE_RGB(0, 0, 0), false, NULL);
}
// Animate cursor in a circle
int16_t cx = screenW / 2;
int16_t cy = screenH / 2;
int16_t radius = 120;
for (int angle = 0; angle < 360; angle += 5) {
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;
int16_t mx = cx;
int16_t my = cy;
if (a < 90) {
mx = (int16_t)(cx + s);
my = (int16_t)(cy - c);
} else if (a < 180) {
mx = (int16_t)(cx + c);
my = (int16_t)(cy + s);
} else if (a < 270) {
mx = (int16_t)(cx - s);
my = (int16_t)(cy + c);
} else {
mx = (int16_t)(cx - c);
my = (int16_t)(cy - s);
}
wdrvMoveCursor(drv, mx, my);
// Small delay via port I/O
for (int d = 0; d < 500; d++) {
(void)inportb(0x80);
}
}
}
// Hide cursor
wdrvSetCursor(drv, WDRV_CURSOR_NONE);
logMsg(" Cursor demo done\n");
wdrvScreenshot(drv, "DEMO14.PNG");
}
// Demo 15: Screen Save/Restore (via screen-to-screen BitBlt)
if (info.hasBitBlt) {
logMsg("Demo 15: Screen Save/Restore\n");
WdrvBitBltParamsT bp;
// Clear screen
memset(&bp, 0, sizeof(bp));
bp.width = screenW;
bp.height = screenH;
bp.rop3 = BLACKNESS;
wdrvBitBlt(drv, &bp);
if (info.hasExtTextOut) {
const char *title = "Demo 15: Screen Save/Restore";
wdrvExtTextOut(drv, 10, 10, title, (int16_t)strlen(title),
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Draw source content at (20,40)
int16_t saveX = 20;
int16_t saveY = 40;
int16_t saveW = 100;
int16_t saveH = 80;
wdrvFillRect(drv, saveX, saveY, saveW, saveH, MAKE_RGB(255, 0, 0));
wdrvFillRect(drv, saveX + 20, saveY + 10, 60, 60, MAKE_RGB(0, 255, 0));
wdrvFillRect(drv, saveX + 30, saveY + 20, 40, 30, MAKE_RGB(0, 0, 255));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, saveX, saveY + saveH + 5, "Source", 6,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
// Save: blit source region to a hidden screen area (bottom of screen)
int16_t stashY = screenH - saveH - 1;
memset(&bp, 0, sizeof(bp));
bp.srcX = saveX;
bp.srcY = saveY;
bp.dstX = 0;
bp.dstY = stashY;
bp.width = saveW;
bp.height = saveH;
bp.rop3 = SRCCOPY;
int32_t ret = wdrvBitBlt(drv, &bp);
logMsg(" Save to stash(%d,%d) ret=%" PRId32 "\n", 0, stashY, ret);
// Overwrite the source area
wdrvFillRect(drv, saveX, saveY, saveW, saveH, MAKE_RGB(64, 64, 64));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, saveX + 5, saveY + 35, "Cleared", 7,
MAKE_RGB(255, 255, 255), MAKE_RGB(64, 64, 64), true, NULL);
}
// Restore: blit from stash to two different positions
memset(&bp, 0, sizeof(bp));
bp.srcX = 0;
bp.srcY = stashY;
bp.dstX = 200;
bp.dstY = saveY;
bp.width = saveW;
bp.height = saveH;
bp.rop3 = SRCCOPY;
ret = wdrvBitBlt(drv, &bp);
logMsg(" Restore copy1 ret=%" PRId32 "\n", ret);
memset(&bp, 0, sizeof(bp));
bp.srcX = 0;
bp.srcY = stashY;
bp.dstX = 350;
bp.dstY = saveY;
bp.width = saveW;
bp.height = saveH;
bp.rop3 = SRCCOPY;
ret = wdrvBitBlt(drv, &bp);
logMsg(" Restore copy2 ret=%" PRId32 "\n", ret);
// Clear the stash area
wdrvFillRect(drv, 0, stashY, saveW, saveH, MAKE_RGB(0, 0, 0));
if (info.hasExtTextOut) {
wdrvExtTextOut(drv, 200, saveY + saveH + 5, "Copy 1", 6,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
wdrvExtTextOut(drv, 350, saveY + saveH + 5, "Copy 2", 6,
MAKE_RGB(255, 255, 255), MAKE_RGB(0, 0, 0), false, NULL);
}
logMsg(" Screen save/restore done\n");
wdrvScreenshot(drv, "DEMO15.PNG");
}
}
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;
// Set the standard Windows 3.1 default 256-color palette.
// This is an 8R x 8G x 4B color cube with the 20 static system
// colors overwriting indices 0-9 and 246-255.
// Using a consistent palette ensures identical output across all
// drivers regardless of their built-in default palettes.
uint8_t pal[1024]; // 256 entries x 4 bytes (R, G, B, flags)
// Generate 8R x 8G x 4B color cube
for (int32_t b = 0; b < 4; b++) {
for (int32_t g = 0; g < 8; g++) {
for (int32_t r = 0; r < 8; r++) {
int32_t idx = b * 64 + g * 8 + r;
pal[idx * 4 + 0] = (uint8_t)(r * 0x20);
pal[idx * 4 + 1] = (uint8_t)(g * 0x20);
pal[idx * 4 + 2] = (uint8_t)(b * 0x40);
pal[idx * 4 + 3] = 0;
}
}
}
// Overwrite first 10 entries with static system colors
static const uint8_t sysFirst[10][3] = {
{ 0x00, 0x00, 0x00 }, // 0: Black
{ 0x80, 0x00, 0x00 }, // 1: Dark Red
{ 0x00, 0x80, 0x00 }, // 2: Dark Green
{ 0x80, 0x80, 0x00 }, // 3: Dark Yellow
{ 0x00, 0x00, 0x80 }, // 4: Dark Blue
{ 0x80, 0x00, 0x80 }, // 5: Dark Magenta
{ 0x00, 0x80, 0x80 }, // 6: Dark Cyan
{ 0xC0, 0xC0, 0xC0 }, // 7: Light Gray
{ 0xC0, 0xDC, 0xC0 }, // 8: Money Green
{ 0xA6, 0xCA, 0xF0 }, // 9: Sky Blue
};
for (int32_t i = 0; i < 10; i++) {
pal[i * 4 + 0] = sysFirst[i][0];
pal[i * 4 + 1] = sysFirst[i][1];
pal[i * 4 + 2] = sysFirst[i][2];
pal[i * 4 + 3] = 0;
}
// Overwrite last 10 entries (246-255) with static system colors
static const uint8_t sysLast[10][3] = {
{ 0xFF, 0xFB, 0xF0 }, // 246: Cream
{ 0xA0, 0xA0, 0xA4 }, // 247: Medium Gray
{ 0x80, 0x80, 0x80 }, // 248: Dark Gray
{ 0xFF, 0x00, 0x00 }, // 249: Red
{ 0x00, 0xFF, 0x00 }, // 250: Green
{ 0xFF, 0xFF, 0x00 }, // 251: Yellow
{ 0x00, 0x00, 0xFF }, // 252: Blue
{ 0xFF, 0x00, 0xFF }, // 253: Magenta
{ 0x00, 0xFF, 0xFF }, // 254: Cyan
{ 0xFF, 0xFF, 0xFF }, // 255: White
};
for (int32_t i = 0; i < 10; i++) {
pal[(246 + i) * 4 + 0] = sysLast[i][0];
pal[(246 + i) * 4 + 1] = sysLast[i][1];
pal[(246 + i) * 4 + 2] = sysLast[i][2];
pal[(246 + i) * 4 + 3] = 0;
}
wdrvSetPalette(drv, 0, 256, pal);
}

37
dosbox-et4000.conf Normal file
View file

@ -0,0 +1,37 @@
# DOSBox-X config for ET4000.DRV testing
[sdl]
output = surface
windowresolution = 1024x768
[dosbox]
machine = svga_et4000
memsize = 64
quit warning = false
[cpu]
core = normal
cputype = pentium
cycles = max
[render]
aspect = true
scaler = none
[video]
vmemsize = 8
vmemsizekb = 0
vesa oldvbe = false
vesa oldvbe10 = false
[dos]
umb = true
xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE ET4000.DRV
exit

37
dosbox-s3trio.conf Normal file
View file

@ -0,0 +1,37 @@
# DOSBox-X config for S3TRIO.DRV testing
[sdl]
output = surface
windowresolution = 1024x768
[dosbox]
machine = svga_s3trio64
memsize = 64
quit warning = false
[cpu]
core = normal
cputype = pentium
cycles = max
[render]
aspect = true
scaler = none
[video]
vmemsize = 8
vmemsizekb = 0
vesa oldvbe = false
vesa oldvbe10 = false
[dos]
umb = true
xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE S3TRIO.DRV
exit

37
dosbox-vbesvga.conf Normal file
View file

@ -0,0 +1,37 @@
# DOSBox-X config for VBESVGA.DRV testing
[sdl]
output = surface
windowresolution = 1024x768
[dosbox]
machine = svga_s3trio64
memsize = 64
quit warning = false
[cpu]
core = normal
cputype = pentium
cycles = max
[render]
aspect = true
scaler = none
[video]
vmemsize = 8
vmemsizekb = 0
vesa oldvbe = false
vesa oldvbe10 = false
[dos]
umb = true
xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE VBESVGA.DRV
exit

37
dosbox-vga.conf Normal file
View file

@ -0,0 +1,37 @@
# DOSBox-X config for VGA.DRV testing
[sdl]
output = surface
windowresolution = 1024x768
[dosbox]
machine = svga_s3trio64
memsize = 64
quit warning = false
[cpu]
core = normal
cputype = pentium
cycles = max
[render]
aspect = true
scaler = none
[video]
vmemsize = 8
vmemsizekb = 0
vesa oldvbe = false
vesa oldvbe10 = false
[dos]
umb = true
xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE VGA.DRV
exit

View file

@ -2,7 +2,7 @@
# S3 Trio64 SVGA with VESA support
[sdl]
output = opengl
output = surface
windowresolution = 1024x768
[dosbox]
@ -31,10 +31,4 @@ xms = true
ems = true
[autoexec]
@echo off
mount c /home/scott/claude/windriver
c:
cd bin
DEMO.EXE S3TRIO.DRV
rem exit

1724
win31drv/stb_image_write.h Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -73,6 +73,10 @@ typedef struct {
bool hasExtTextOut; // Driver exports ExtTextOut
bool hasSetPalette; // Driver exports SetPalette
bool hasSetCursor; // Driver exports SetCursor
bool hasMoveCursor; // Driver exports MoveCursor
bool hasScanLR; // Driver exports ScanLR
bool hasGetCharWidth; // Driver exports GetCharWidth
bool hasCreateBitmap; // Driver exports CreateBitmap
} WdrvInfoT;
// ============================================================================
@ -150,6 +154,12 @@ int32_t wdrvPolyline(WdrvHandleT handle, Point16T *points, int16_t count, uint32
// Draw a rectangle outline.
int32_t wdrvRectangle(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color);
// Draw a polyline with a specific pen style.
int32_t wdrvPolylineEx(WdrvHandleT handle, Point16T *points, int16_t count, uint32_t color, int16_t penStyle);
// Draw a rectangle outline with a specific pen style.
int32_t wdrvRectangleEx(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color, int16_t penStyle);
// Draw text using the ExtTextOut DDI function.
// Pass NULL for font to use the built-in 8x16 VGA bitmap font.
int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
@ -157,6 +167,104 @@ int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y,
uint32_t fgColor, uint32_t bgColor,
bool opaque, WdrvFontT font);
// ============================================================================
// Pen styles (for wdrvPolylineEx / wdrvRectangleEx)
// ============================================================================
#define WDRV_PEN_SOLID 0
#define WDRV_PEN_DASH 1
#define WDRV_PEN_DOT 2
#define WDRV_PEN_DASHDOT 3
#define WDRV_PEN_DASHDOTDOT 4
// ============================================================================
// ScanLR and Flood Fill
// ============================================================================
// ScanLR style bits
#define WDRV_SCAN_RIGHT 0x00 // Scan right, find not-matching
#define WDRV_SCAN_RIGHT_MATCH 0x01 // Scan right, find matching
#define WDRV_SCAN_LEFT 0x02 // Scan left, find not-matching
#define WDRV_SCAN_LEFT_MATCH 0x03 // Scan left, find matching
// Scan left or right from (x,y) for color match/non-match.
// style is a bitfield: bit 0=FIND_COLOR (match), bit 1=STEP_LEFT.
// Returns X where scan stopped, or -1/screenW if not found.
int16_t wdrvScanLR(WdrvHandleT handle, int16_t x, int16_t y, uint32_t color, int16_t style);
// Flood fill starting at (x,y) with fillColor.
// Uses framebuffer when available, falls back to ScanLR + FillRect.
int32_t wdrvFloodFill(WdrvHandleT handle, int16_t x, int16_t y, uint32_t fillColor);
// ============================================================================
// Text measurement
// ============================================================================
// Get character widths for a range of characters.
// widths array must have room for (lastChar - firstChar + 1) entries.
int32_t wdrvGetCharWidths(WdrvHandleT handle, WdrvFontT font, uint8_t firstChar, uint8_t lastChar, int16_t *widths);
// Measure total pixel width of a text string.
int32_t wdrvMeasureText(WdrvHandleT handle, WdrvFontT font, const char *text, int16_t length);
// ============================================================================
// Pixel buffer blitting (direct framebuffer)
// ============================================================================
// Blit an 8bpp pixel buffer to the screen.
// Uses framebuffer when available, falls back to Pixel DDI per pixel.
int32_t wdrvBlitPixels(WdrvHandleT handle, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, int32_t srcPitch);
// Load and display a BMP file at (x,y). If setPalette is true, applies
// the BMP's palette via wdrvSetPalette.
int32_t wdrvBlitBmp(WdrvHandleT handle, int16_t x, int16_t y, const char *bmpPath, bool setPalette);
// ============================================================================
// Hardware cursor
// ============================================================================
typedef enum {
WDRV_CURSOR_ARROW,
WDRV_CURSOR_CROSSHAIR,
WDRV_CURSOR_IBEAM,
WDRV_CURSOR_HAND,
WDRV_CURSOR_NONE
} WdrvCursorShapeE;
// Set cursor to a built-in shape. WDRV_CURSOR_NONE hides the cursor.
int32_t wdrvSetCursor(WdrvHandleT handle, WdrvCursorShapeE shape);
// Set cursor to a custom 32x32 mono bitmap.
// andMask and xorMask are each 128 bytes (32 rows x 4 bytes/row).
int32_t wdrvSetCursorCustom(WdrvHandleT handle, int16_t hotX, int16_t hotY, const uint8_t *andMask, const uint8_t *xorMask);
// Move the hardware cursor to screen position (x,y).
int32_t wdrvMoveCursor(WdrvHandleT handle, int16_t x, int16_t y);
// ============================================================================
// Off-screen bitmaps
// ============================================================================
typedef struct WdrvBitmapS *WdrvBitmapT;
// Create an off-screen bitmap via the driver's CreateBitmap DDI.
WdrvBitmapT wdrvCreateBitmap(WdrvHandleT handle, int16_t width, int16_t height);
// Delete an off-screen bitmap.
void wdrvDeleteBitmap(WdrvHandleT handle, WdrvBitmapT bitmap);
// Set bitmap pixel data via BitmapBits DDI.
int32_t wdrvBitmapSetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, const uint8_t *data, uint32_t dataSize);
// Get bitmap pixel data via BitmapBits DDI.
int32_t wdrvBitmapGetPixels(WdrvHandleT handle, WdrvBitmapT bitmap, uint8_t *data, uint32_t dataSize);
// BitBlt from an off-screen bitmap to the screen.
int32_t wdrvBitBltFromBitmap(WdrvHandleT handle, WdrvBitmapT srcBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3);
// BitBlt from the screen to an off-screen bitmap.
int32_t wdrvBitBltToBitmap(WdrvHandleT handle, WdrvBitmapT dstBitmap, int16_t srcX, int16_t srcY, int16_t dstX, int16_t dstY, int16_t w, int16_t h, uint32_t rop3);
// ============================================================================
// Font loading
// ============================================================================
@ -201,6 +309,10 @@ void *wdrvGetFramebuffer(WdrvHandleT handle);
// Get the framebuffer pitch (bytes per scanline).
int32_t wdrvGetPitch(WdrvHandleT handle);
// Save the current screen to a PNG file.
// Reads the framebuffer (or uses DDI fallback) and DAC palette.
int32_t wdrvScreenshot(WdrvHandleT handle, const char *filename);
// ============================================================================
// Error reporting
// ============================================================================