diff --git a/CLAUDE.md b/CLAUDE.md index b36f978..3bdb543 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,20 @@ windriver/ pattern-only ROPs. S3TRIO.DRV tolerates it but correct behavior is NULL. - Source dependency check: `ropNeedsSrc = (((rop8 >> 2) ^ rop8) & 0x33) != 0` +## ExtTextOut DDI Notes +- **Font format**: VBESVGA.DRV requires .FNT v3 (fsVersion=0x0300) when BigFontFlags is set + (386 protected mode with WF_PMODE + WF_CPU386). v2 (0x0200) is rejected at runtime. +- **v3 char table**: at file offset 0x94 (fs30CharOffset), 6-byte entries {WORD width, DWORD offset}. + The DWORD offset is absolute from the font segment base. Use FntCharEntry30T. +- **Bitmap layout**: per-character contiguous (16 bytes per char for 8x16 font, stride=1 between rows). + VGA BIOS 8x16 font (INT 10h AH=11h AL=30h BH=06) is already in this format — no transpose needed. +- **lpClipRect must NOT be NULL**: VBESVGA's get_clip unconditionally dereferences lpClipRect + (STRBLT.ASM:1008 "We assume that we will never get passed a null rectangle"). Pass a RECT + covering the full screen (0, 0, 0x7FFF, 0x7FFF). +- **lpTextXForm**: declared but never read by VBESVGA — pass NULL. +- **lp_font offset**: passed as fontSel:0x42 (points to fsType within the .FNT block). +- **Return value**: DX bit 15 = error. AX=0 is NOT necessarily failure. + ## INT 10h ES Translation - Different INT 10h function families use different ES:offset registers: VBE 4Fxx → ES:DI, AH=10h (palette) → ES:DX, AH=11h (fonts) → ES:BP, AH=1Bh → ES:DI @@ -102,6 +116,7 @@ windriver/ - Demo 2: Pixel patterns (Pixel) — works - Demo 3: Lines/starburst (Output/Polyline) — works - Demo 4: Screen-to-screen blit (BitBlt SRCCOPY) — works +- Demo 5: ExtTextOut text rendering — works (VBESVGA.DRV) - 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 diff --git a/demo.c b/demo.c index 129a78e..74ac6a1 100644 --- a/demo.c +++ b/demo.c @@ -308,6 +308,39 @@ static void demoDrawing(WdrvHandleT drv) wdrvBitBlt(drv, &bp); logMsg(" Screen blit done\n"); } + + // Demo 5: Text output using ExtTextOut + if (info.hasExtTextOut) { + logMsg("Demo 5: ExtTextOut text rendering\n"); + int32_t ret; + + // Opaque text: white on blue + 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); + 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); + 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); + 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); + logMsg(" msg4 ret=%" PRId32 "\n", ret); + + logMsg(" Text output done\n"); + } } diff --git a/dosbox-x.conf b/dosbox-x.conf index a148db9..83719eb 100644 --- a/dosbox-x.conf +++ b/dosbox-x.conf @@ -35,6 +35,6 @@ ems = true mount c /home/scott/claude/windriver c: cd bin -DEMO.EXE -d VGA.DRV +DEMO.EXE S3TRIO.DRV rem exit diff --git a/win31drv/winddi.h b/win31drv/winddi.h index 11a4390..9f5af1e 100644 --- a/win31drv/winddi.h +++ b/win31drv/winddi.h @@ -276,4 +276,59 @@ typedef struct __attribute__((packed)) { // ... additional fields follow } DibPDevice16T; +// ============================================================================ +// .FNT v2 file header (117 bytes) +// +// Display drivers expect the full .FNT format with fsVersion at offset 0. +// lp_font passed to ExtTextOut points to fsType at offset 0x42. +// ============================================================================ + +typedef struct __attribute__((packed)) { + uint16_t fsVersion; // 0x00: 0x0200 + uint32_t fsSize; // 0x02: total structure size + char fsCopyright[60]; // 0x06: copyright string + uint16_t fsType; // 0x42: 0 = raster + uint16_t fsPoints; // 0x44: point size + uint16_t fsVertRes; // 0x46: vertical resolution + uint16_t fsHorizRes; // 0x48: horizontal resolution + uint16_t fsAscent; // 0x4A: baseline from top + uint16_t fsInternalLeading; // 0x4C + uint16_t fsExternalLeading; // 0x4E + uint8_t fsItalic; // 0x50 + uint8_t fsUnderline; // 0x51 + uint8_t fsStrikeOut; // 0x52 + uint16_t fsWeight; // 0x53: 400 = normal + uint8_t fsCharSet; // 0x55: 255 = OEM + uint16_t fsPixWidth; // 0x56: 0=proportional, else fixed + uint16_t fsPixHeight; // 0x58: character cell height + uint8_t fsPitchAndFamily; // 0x5A + uint16_t fsAvgWidth; // 0x5B + uint16_t fsMaxWidth; // 0x5D + uint8_t fsFirstChar; // 0x5F + uint8_t fsLastChar; // 0x60 + uint8_t fsDefaultChar; // 0x61: relative to fsFirstChar + uint8_t fsBreakChar; // 0x62: relative to fsFirstChar + uint16_t fsWidthBytes; // 0x63: bytes per bitmap row + uint32_t fsDevice; // 0x65: offset to device name + uint32_t fsFace; // 0x69: offset to face name + uint32_t fsBitsPointer; // 0x6D: far ptr to bitmap data + uint32_t fsBitsOffset; // 0x71: offset to bitmap data +} FntHeader16T; // 0x75 = 117 bytes + +// v2 character table entry (at offset 0x76 after header + pad byte) +typedef struct __attribute__((packed)) { + uint16_t width; // character width in pixels + uint16_t offset; // byte offset from bitmap start +} FntCharEntry16T; // 4 bytes + +// v3 character table entry (at offset 0x94 after v3 extension fields) +typedef struct __attribute__((packed)) { + uint16_t width; // character width in pixels + uint32_t offset; // absolute byte offset from segment base +} FntCharEntry30T; // 6 bytes + +// ExtTextOut option flags +#define ETO_OPAQUE 0x0002 +#define ETO_CLIPPED 0x0004 + #endif // WINDDI_H diff --git a/win31drv/windrv.c b/win31drv/windrv.c index 812c28d..c85b8bf 100644 --- a/win31drv/windrv.c +++ b/win31drv/windrv.c @@ -107,6 +107,12 @@ struct WdrvDriverS { uint16_t drawModeOff; uint32_t drawModeLinear; + // Font (.FNT v3 format, separate 16-bit block) + uint16_t fontSel; + uint32_t fontLinear; + uint32_t fontSize; + bool fontInitialized; + // Current state bool enabled; uint32_t currentColor; @@ -152,6 +158,7 @@ static uint32_t colorToPhys(struct WdrvDriverS *drv, uint32_t colorRef); static void setDisplayStart(struct WdrvDriverS *drv, uint32_t byteOffset); static void freeDrawObjects(struct WdrvDriverS *drv); +static bool initFont(struct WdrvDriverS *drv); static uint16_t alloc16BitBlock(uint32_t size, uint32_t *linearOut); static void free16BitBlock(uint16_t sel, uint32_t linear); static void setError(int32_t err); @@ -2092,6 +2099,132 @@ int32_t wdrvSetPalette(WdrvHandleT handle, int32_t startIndex, int32_t count, co } +// ============================================================================ +// Text output +// ============================================================================ + +int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y, + const char *text, int16_t length, + uint32_t fgColor, uint32_t bgColor, + bool opaque) +{ + if (!handle || !handle->enabled) { + return WDRV_ERR_NOT_ENABLED; + } + if (!handle->ddiEntry[DDI_ORD_EXTTEXTOUT].present) { + return WDRV_ERR_UNSUPPORTED; + } + + // Lazy-init the VGA BIOS font + if (!handle->fontInitialized) { + if (!initFont(handle)) { + logErr("windrv: ExtTextOut: font init failed\n"); + return WDRV_ERR_NO_MEMORY; + } + } + + // Convert colors to physical + uint32_t physFg = colorToPhys(handle, fgColor); + uint32_t physBg = colorToPhys(handle, bgColor); + + // Set up DrawMode for text + DrawMode16T *dm = (DrawMode16T *)handle->drawModeLinear; + dm->rop2 = R2_COPYPEN; + dm->bkMode = opaque ? BM_OPAQUE : BM_TRANSPARENT; + dm->bkColor = physBg; + dm->textColor = physFg; + dm->lbkColor = bgColor; + dm->ltextColor = fgColor; + dm->tBreakExtra = 0; + dm->breakExtra = 0; + dm->breakErr = 0; + dm->breakRem = 0; + dm->breakCount = 0; + dm->charExtra = 0; + + // Copy the string and clip rect into a 16-bit accessible block. + // The driver always dereferences lpClipRect (no NULL check), so we + // must provide a valid rectangle covering the full screen. + uint16_t clipOff = (uint16_t)((length + 1) & ~1); // word-align + uint32_t blockSize = (uint32_t)clipOff + 8; // + RECT (4 WORDs) + uint32_t strLinear; + uint16_t strSel = alloc16BitBlock(blockSize, &strLinear); + if (strSel == 0) { + return WDRV_ERR_NO_MEMORY; + } + memcpy((void *)strLinear, text, length); + + // Write clip rect after the string (left, top, right, bottom) + int16_t *clipRect = (int16_t *)(strLinear + clipOff); + clipRect[0] = 0; + clipRect[1] = 0; + clipRect[2] = 0x7FFF; + clipRect[3] = 0x7FFF; + + // DWORD ExtTextOut( + // LPPDEVICE lpDestDev, 2w + // WORD x, 1w + // WORD y, 1w + // LPRECT lpClipRect, 2w + // LPSTR lpString, 2w + // int count, 1w + // LPFONTINFO lpFont, 2w fontSel:0x42 + // LPDRAWMODE lpDrawMode, 2w + // LPTEXTXFORM lpTextXForm,2w NULL + // LPSHORT lpCharWidths, 2w NULL + // LPRECT lpOpaqueRect, 2w NULL + // WORD wOptions 1w + // ) + // Total: 20 words (Pascal order, left to right) + + uint16_t dgSel = handle->neMod.autoDataSel; + uint16_t params[20]; + int i = 0; + + params[i++] = dgSel; // lpDestDev seg + params[i++] = handle->pdevOff; // lpDestDev off + params[i++] = (uint16_t)x; // x + params[i++] = (uint16_t)(y + handle->dispYOffset); // y + params[i++] = strSel; // lpClipRect seg + params[i++] = clipOff; // lpClipRect off + params[i++] = strSel; // lpString seg + params[i++] = 0; // lpString off + params[i++] = (uint16_t)length; // count + params[i++] = handle->fontSel; // lpFont seg + params[i++] = 0x0042; // lpFont off (fsType) + params[i++] = dgSel; // lpDrawMode seg + params[i++] = handle->drawModeOff; // lpDrawMode off + params[i++] = 0; // lpTextXForm seg (NULL) + params[i++] = 0; // lpTextXForm off + params[i++] = 0; // lpCharWidths seg (NULL) + params[i++] = 0; // lpCharWidths off + params[i++] = 0; // lpOpaqueRect seg (NULL) + params[i++] = 0; // lpOpaqueRect off + params[i++] = 0; // wOptions + + dbg("windrv: ExtTextOut(%d,%d) len=%d font=%04X:0042 dm=%04X:%04X fg=0x%08lX bg=0x%08lX %s\n", + x, y, length, handle->fontSel, dgSel, handle->drawModeOff, + (unsigned long)physFg, (unsigned long)physBg, + opaque ? "OPAQUE" : "TRANSPARENT"); + + waitForEngine(); + + uint32_t result = thunkCall16(&gThunkCtx, + handle->ddiEntry[DDI_ORD_EXTTEXTOUT].sel, + handle->ddiEntry[DDI_ORD_EXTTEXTOUT].off, + params, i); + + waitForEngine(); + + free16BitBlock(strSel, strLinear); + + dbg("windrv: ExtTextOut result=0x%08lX\n", (unsigned long)result); + + // DX bit 15 set = DDI error. AX=0 is not necessarily failure. + return (result & 0x80000000) ? WDRV_ERR_UNSUPPORTED : WDRV_OK; +} + + // ============================================================================ // Framebuffer access // ============================================================================ @@ -2586,6 +2719,128 @@ static void freeDrawObjects(struct WdrvDriverS *drv) // Objects are embedded in DGROUP - freed when module is unloaded drv->brushRealized = false; drv->penRealized = false; + + // Font is a separate 16-bit block + if (drv->fontInitialized) { + free16BitBlock(drv->fontSel, drv->fontLinear); + drv->fontSel = 0; + drv->fontLinear = 0; + drv->fontInitialized = false; + } +} + + +// Build a .FNT v3 font structure from the VGA BIOS 8x16 ROM font. +// +// v3 layout offsets: +// 0x0000: FntHeader16T (117 bytes) +// 0x0075: pad byte +// 0x0076: v3 extension (fsFlags..fsReserved, 30 bytes) +// 0x0094: v3 character table (257 entries x 6 bytes = 1542) +// 0x069A: bitmap data (256 chars x 16 rows = 4096) +// 0x169A: device name "\0" +// 0x169B: face name "System\0" +#define FONT_V3EXT_OFF 0x0076 +#define FONT_CHAR_TABLE_OFF 0x0094 +#define FONT_BITMAP_OFF 0x069A +#define FONT_DEVNAME_OFF 0x169A +#define FONT_FACENAME_OFF 0x169B +#define FONT_TOTAL_SIZE 0x16A2 + +static bool initFont(struct WdrvDriverS *drv) +{ + // Get VGA BIOS 8x16 font pointer via INT 10h AH=11h AL=30h BH=06 + __dpmi_regs regs; + memset(®s, 0, sizeof(regs)); + regs.x.ax = 0x1130; + regs.x.bx = 0x0600; + __dpmi_int(0x10, ®s); + + uint32_t fontRmAddr = ((uint32_t)regs.x.es << 4) + regs.x.bp; + + // Copy 4096 bytes of glyph data (256 chars x 16 bytes each) + uint8_t vgaFont[4096]; + dosmemget(fontRmAddr, 4096, vgaFont); + + dbg("windrv: initFont: VGA 8x16 font at real %04X:%04X (linear 0x%08lX)\n", + regs.x.es, regs.x.bp, (unsigned long)fontRmAddr); + + // Allocate a 16-bit accessible block for the .FNT v3 structure + uint32_t linearOut; + uint16_t sel = alloc16BitBlock(FONT_TOTAL_SIZE, &linearOut); + if (sel == 0) { + logErr("windrv: initFont: failed to allocate font block\n"); + return false; + } + + uint8_t *base = (uint8_t *)linearOut; + memset(base, 0, FONT_TOTAL_SIZE); + + // Fill the .FNT v3 header (same base structure as v2) + FntHeader16T *hdr = (FntHeader16T *)base; + hdr->fsVersion = 0x0300; + hdr->fsSize = FONT_TOTAL_SIZE; + hdr->fsType = 0; // raster + hdr->fsPoints = 16; + hdr->fsVertRes = 96; + hdr->fsHorizRes = 96; + hdr->fsAscent = 14; + hdr->fsInternalLeading = 2; + hdr->fsExternalLeading = 0; + hdr->fsItalic = 0; + hdr->fsUnderline = 0; + hdr->fsStrikeOut = 0; + hdr->fsWeight = 400; // normal + hdr->fsCharSet = 255; // OEM + hdr->fsPixWidth = 8; // fixed width + hdr->fsPixHeight = 16; + hdr->fsPitchAndFamily = 0x30; // FF_MODERN | FIXED_PITCH + hdr->fsAvgWidth = 8; + hdr->fsMaxWidth = 8; + hdr->fsFirstChar = 0; + hdr->fsLastChar = 255; + hdr->fsDefaultChar = 0; // relative to fsFirstChar + hdr->fsBreakChar = 32; // relative to fsFirstChar + hdr->fsWidthBytes = 1; // row stride: 1 byte per row per char + hdr->fsDevice = FONT_DEVNAME_OFF; + hdr->fsFace = FONT_FACENAME_OFF; + hdr->fsBitsPointer = ((uint32_t)sel << 16) | FONT_BITMAP_OFF; + hdr->fsBitsOffset = FONT_BITMAP_OFF; + + // v3 extension fields at 0x76 are already zeroed (fsFlags, fsAspace, + // fsBspace, fsCspace, fsColorPointer, fsReserved) + + // Build v3 character table (257 entries x 6 bytes: 256 chars + sentinel) + // v3 entries have a DWORD absolute offset from segment base. + // Glyph data is per-character contiguous: char C's 16 rows at + // FONT_BITMAP_OFF + C*16. + FntCharEntry30T *charTable = (FntCharEntry30T *)(base + FONT_CHAR_TABLE_OFF); + for (int c = 0; c <= 256; c++) { + charTable[c].width = 8; + charTable[c].offset = FONT_BITMAP_OFF + (uint32_t)(c < 256 ? c : 0) * 16; + } + + // Copy VGA ROM glyphs directly — already in per-character contiguous + // format (16 consecutive bytes per character, stride=1 between rows) + memcpy(base + FONT_BITMAP_OFF, vgaFont, 4096); + + // Device name (empty string) + base[FONT_DEVNAME_OFF] = '\0'; + + // Face name + memcpy(base + FONT_FACENAME_OFF, "System", 7); + + drv->fontSel = sel; + drv->fontLinear = linearOut; + drv->fontSize = FONT_TOTAL_SIZE; + drv->fontInitialized = true; + + dbg("windrv: initFont: v3 font block sel=%04X linear=0x%08lX size=%u\n", + sel, (unsigned long)linearOut, FONT_TOTAL_SIZE); + dbg("windrv: initFont: fsBitsPointer=%08lX charTable@0x%04X bitmap@0x%04X\n", + (unsigned long)hdr->fsBitsPointer, FONT_CHAR_TABLE_OFF, FONT_BITMAP_OFF); + + return true; } diff --git a/win31drv/windrv.h b/win31drv/windrv.h index eb7e2cf..8bd685f 100644 --- a/win31drv/windrv.h +++ b/win31drv/windrv.h @@ -148,6 +148,13 @@ 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 text using the ExtTextOut DDI function. +// Uses a built-in 8x16 VGA bitmap font. +int32_t wdrvExtTextOut(WdrvHandleT handle, int16_t x, int16_t y, + const char *text, int16_t length, + uint32_t fgColor, uint32_t bgColor, + bool opaque); + // ============================================================================ // Palette operations (for 8bpp modes) // ============================================================================