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>
1315 lines
48 KiB
C
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);
|
|
}
|
|
|