WinDriver/demo.c
Scott Duensing 5527130145 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>
2026-02-23 18:55:57 -06:00

1315 lines
48 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.\n");
// 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");
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");
}
// 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);
wdrvScreenshot(drv, "DEMO01.PNG");
}
// 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);
wdrvScreenshot(drv, "DEMO02.PNG");
}
// 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);
wdrvScreenshot(drv, "DEMO03.PNG");
}
// 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");
wdrvScreenshot(drv, "DEMO04.PNG");
}
// 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");
wdrvScreenshot(drv, "DEMO05.PNG");
}
// 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");
wdrvScreenshot(drv, "DEMO06.PNG");
}
// 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");
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)
{
// 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);
}