Platform specific code isolated to make porting easier.
This commit is contained in:
parent
d0e308d7cf
commit
5c00bc8ae2
8 changed files with 1012 additions and 796 deletions
27
dvx/Makefile
27
dvx/Makefile
|
|
@ -9,10 +9,13 @@ CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
|
|||
|
||||
OBJDIR = ../obj/dvx
|
||||
WOBJDIR = ../obj/dvx/widgets
|
||||
POBJDIR = ../obj/dvx/platform
|
||||
LIBDIR = ../lib
|
||||
|
||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c
|
||||
|
||||
PSRCS = platform/dvxPlatformDos.c
|
||||
|
||||
WSRCS = widgets/widgetAnsiTerm.c \
|
||||
widgets/widgetClass.c \
|
||||
widgets/widgetCore.c \
|
||||
|
|
@ -46,6 +49,7 @@ WSRCS = widgets/widgetAnsiTerm.c \
|
|||
widgets/widgetTreeView.c
|
||||
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS))
|
||||
WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS))
|
||||
TARGET = $(LIBDIR)/libdvx.a
|
||||
|
||||
|
|
@ -53,19 +57,25 @@ TARGET = $(LIBDIR)/libdvx.a
|
|||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(WOBJS) | $(LIBDIR)
|
||||
$(AR) rcs $@ $(OBJS) $(WOBJS)
|
||||
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR)
|
||||
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(POBJDIR)/%.o: platform/%.c | $(POBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(POBJDIR):
|
||||
mkdir -p $(POBJDIR)
|
||||
|
||||
$(WOBJDIR):
|
||||
mkdir -p $(WOBJDIR)
|
||||
|
||||
|
|
@ -73,14 +83,17 @@ $(LIBDIR):
|
|||
mkdir -p $(LIBDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h dvxTypes.h dvxPalette.h
|
||||
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h dvxTypes.h
|
||||
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h dvxTypes.h
|
||||
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h
|
||||
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
|
||||
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
|
||||
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h
|
||||
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
|
||||
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h
|
||||
|
||||
# Platform file dependencies
|
||||
$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h
|
||||
|
||||
# Widget file dependencies
|
||||
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
|
|
|
|||
127
dvx/dvxApp.c
127
dvx/dvxApp.c
|
|
@ -6,11 +6,11 @@
|
|||
#include "dvxFont.h"
|
||||
#include "dvxCursor.h"
|
||||
|
||||
#include "platform/dvxPlatform.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <dpmi.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "thirdparty/stb_image_write.h"
|
||||
|
||||
|
|
@ -43,7 +43,6 @@ static void executeSysMenuCmd(AppContextT *ctx, int32_t cmd);
|
|||
static WindowT *findWindowById(AppContextT *ctx, int32_t id);
|
||||
static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons);
|
||||
static void initColorScheme(AppContextT *ctx);
|
||||
static void initMouse(AppContextT *ctx);
|
||||
static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY);
|
||||
static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx);
|
||||
static void openSubMenu(AppContextT *ctx);
|
||||
|
|
@ -60,22 +59,6 @@ static void updateTooltip(AppContextT *ctx);
|
|||
// Button pressed via keyboard — shared with widgetEvent.c for Space/Enter
|
||||
WidgetT *sKeyPressedBtn = NULL;
|
||||
|
||||
// Alt+key scan code to ASCII lookup table (indexed by scan code)
|
||||
// BIOS INT 16h returns these scan codes with ascii=0 for Alt+key combos
|
||||
static const char sAltScanToAscii[256] = {
|
||||
// Alt+letters
|
||||
[0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r',
|
||||
[0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i',
|
||||
[0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's',
|
||||
[0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h',
|
||||
[0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z',
|
||||
[0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b',
|
||||
[0x31] = 'n', [0x32] = 'm',
|
||||
// Alt+digits
|
||||
[0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4',
|
||||
[0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8',
|
||||
[0x80] = '9', [0x81] = '0',
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -1260,8 +1243,8 @@ const BitmapFontT *dvxGetFont(const AppContextT *ctx) {
|
|||
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
// Disable Ctrl+C/Break termination
|
||||
signal(SIGINT, SIG_IGN);
|
||||
// Platform-specific initialization (signal handling, etc.)
|
||||
platformInit();
|
||||
|
||||
// Initialize video
|
||||
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
|
||||
|
|
@ -1292,7 +1275,11 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
|
|||
ctx->cursorBg = packColor(&ctx->display, 0, 0, 0);
|
||||
|
||||
// Initialize mouse
|
||||
initMouse(ctx);
|
||||
platformMouseInit(ctx->display.width, ctx->display.height);
|
||||
ctx->mouseX = ctx->display.width / 2;
|
||||
ctx->mouseY = ctx->display.height / 2;
|
||||
ctx->prevMouseX = ctx->mouseX;
|
||||
ctx->prevMouseY = ctx->mouseY;
|
||||
|
||||
ctx->running = true;
|
||||
ctx->lastIconClickId = -1;
|
||||
|
|
@ -1395,7 +1382,7 @@ bool dvxUpdate(AppContextT *ctx) {
|
|||
} else if (ctx->idleCallback) {
|
||||
ctx->idleCallback(ctx->idleCtx);
|
||||
} else {
|
||||
__dpmi_yield();
|
||||
platformYield();
|
||||
}
|
||||
|
||||
// After compositing, release key-pressed button (one frame of animation)
|
||||
|
|
@ -1919,45 +1906,6 @@ static void initColorScheme(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// initMouse
|
||||
// ============================================================
|
||||
|
||||
static void initMouse(AppContextT *ctx) {
|
||||
__dpmi_regs r;
|
||||
|
||||
// Reset mouse driver
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0000;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Set horizontal range to match screen width
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0007;
|
||||
r.x.cx = 0;
|
||||
r.x.dx = ctx->display.width - 1;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Set vertical range to match screen height
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0008;
|
||||
r.x.cx = 0;
|
||||
r.x.dx = ctx->display.height - 1;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Position cursor at center of screen
|
||||
ctx->mouseX = ctx->display.width / 2;
|
||||
ctx->mouseY = ctx->display.height / 2;
|
||||
ctx->prevMouseX = ctx->mouseX;
|
||||
ctx->prevMouseY = ctx->mouseY;
|
||||
|
||||
// Set mouse position
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0004;
|
||||
r.x.cx = ctx->mouseX;
|
||||
r.x.dx = ctx->mouseY;
|
||||
__dpmi_int(0x33, &r);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -2228,40 +2176,17 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win)
|
|||
// ============================================================
|
||||
|
||||
static void pollKeyboard(AppContextT *ctx) {
|
||||
__dpmi_regs r;
|
||||
|
||||
// Read shift state once per poll (INT 16h, AH=12h)
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x1200;
|
||||
__dpmi_int(0x16, &r);
|
||||
int32_t shiftFlags = r.x.ax & 0xFF;
|
||||
// Read modifier state once per poll
|
||||
int32_t shiftFlags = platformKeyboardGetModifiers();
|
||||
ctx->keyModifiers = shiftFlags;
|
||||
bool shiftHeld = (shiftFlags & 0x03) != 0; // left or right shift
|
||||
|
||||
// Process buffered keys
|
||||
while (1) {
|
||||
// Check if key is available (INT 16h, enhanced function 11h)
|
||||
r.x.ax = 0x1100;
|
||||
__dpmi_int(0x16, &r);
|
||||
PlatformKeyEventT evt;
|
||||
|
||||
// Zero flag set = no key available
|
||||
if (r.x.flags & 0x40) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the key (INT 16h, enhanced function 10h)
|
||||
r.x.ax = 0x1000;
|
||||
__dpmi_int(0x16, &r);
|
||||
|
||||
int32_t scancode = (r.x.ax >> 8) & 0xFF;
|
||||
int32_t ascii = r.x.ax & 0xFF;
|
||||
|
||||
// Enhanced INT 16h returns ascii=0xE0 for grey/extended keys
|
||||
// (arrows, Home, End, Insert, Delete, etc. on 101-key keyboards).
|
||||
// Normalize to 0 so all extended key checks work uniformly.
|
||||
if (ascii == 0xE0) {
|
||||
ascii = 0;
|
||||
}
|
||||
while (platformKeyboardRead(&evt)) {
|
||||
int32_t scancode = evt.scancode;
|
||||
int32_t ascii = evt.ascii;
|
||||
|
||||
// Alt+Tab / Shift+Alt+Tab — cycle windows
|
||||
// Alt+Tab: scancode=0xA5, ascii=0x00
|
||||
|
|
@ -2453,7 +2378,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
// System menu keyboard navigation
|
||||
if (ctx->sysMenu.active) {
|
||||
// Alt+key — close system menu and let it fall through to accel dispatch
|
||||
if (ascii == 0 && scancode < 256 && sAltScanToAscii[scancode]) {
|
||||
if (ascii == 0 && platformAltScanToChar(scancode)) {
|
||||
closeSysMenu(ctx);
|
||||
// Fall through to dispatchAccelKey below
|
||||
} else if (ascii == 0x1B) {
|
||||
|
|
@ -2541,8 +2466,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
// Check for Alt+key (BIOS returns ascii=0 with specific scancodes)
|
||||
if (ascii == 0 && scancode < 256 && sAltScanToAscii[scancode]) {
|
||||
char accelKey = sAltScanToAscii[scancode];
|
||||
if (ascii == 0 && platformAltScanToChar(scancode)) {
|
||||
char accelKey = platformAltScanToChar(scancode);
|
||||
|
||||
if (dispatchAccelKey(ctx, accelKey)) {
|
||||
continue;
|
||||
|
|
@ -2896,15 +2821,15 @@ nextKey:;
|
|||
// ============================================================
|
||||
|
||||
static void pollMouse(AppContextT *ctx) {
|
||||
__dpmi_regs r;
|
||||
int32_t mx;
|
||||
int32_t my;
|
||||
int32_t buttons;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0003;
|
||||
__dpmi_int(0x33, &r);
|
||||
platformMousePoll(&mx, &my, &buttons);
|
||||
|
||||
ctx->mouseX = r.x.cx;
|
||||
ctx->mouseY = r.x.dx;
|
||||
ctx->mouseButtons = r.x.bx;
|
||||
ctx->mouseX = mx;
|
||||
ctx->mouseY = my;
|
||||
ctx->mouseButtons = buttons;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// dvx_comp.c — Layer 3: Dirty rectangle compositor for DV/X GUI (optimized)
|
||||
|
||||
#include "dvxComp.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -109,62 +110,7 @@ void dirtyListMerge(DirtyListT *dl) {
|
|||
// ============================================================
|
||||
|
||||
void flushRect(DisplayT *d, const RectT *r) {
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
|
||||
// Caller (compositeAndFlush) already clips to screen bounds
|
||||
int32_t x = r->x;
|
||||
int32_t y = r->y;
|
||||
int32_t w = r->w;
|
||||
int32_t h = r->h;
|
||||
|
||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) { return; }
|
||||
|
||||
int32_t rowBytes = w * bpp;
|
||||
int32_t pitch = d->pitch;
|
||||
uint8_t *src = d->backBuf + y * pitch + x * bpp;
|
||||
uint8_t *dst = d->lfb + y * pitch + x * bpp;
|
||||
|
||||
// Full-width flush: single large copy (write-combined memory loves sequential writes)
|
||||
if (rowBytes == pitch) {
|
||||
// Entire scanlines — copy as one contiguous block
|
||||
int32_t totalBytes = pitch * h;
|
||||
int32_t dwords = totalBytes >> 2;
|
||||
int32_t remainder = totalBytes & 3;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwords)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
// Handle any trailing bytes (unlikely for aligned pitch)
|
||||
while (remainder-- > 0) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
} else {
|
||||
// Partial scanlines — copy row by row with rep movsd
|
||||
int32_t dwords = rowBytes >> 2;
|
||||
int32_t remainder = rowBytes & 3;
|
||||
for (int32_t i = 0; i < h; i++) {
|
||||
int32_t dc = dwords;
|
||||
uint8_t *s = src;
|
||||
uint8_t *dd = dst;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dd), "+S"(s), "+c"(dc)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
// Trailing bytes (dd and s already advanced by rep movsl)
|
||||
if (__builtin_expect(remainder > 0, 0)) {
|
||||
int32_t rem = remainder;
|
||||
while (rem-- > 0) {
|
||||
*dd++ = *s++;
|
||||
}
|
||||
}
|
||||
src += pitch;
|
||||
dst += pitch;
|
||||
}
|
||||
}
|
||||
platformFlushRect(d, r);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// dvxDialog.c — Modal dialogs for DV/X GUI
|
||||
|
||||
#include "dvxDialog.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgets/widgetInternal.h"
|
||||
|
||||
|
|
@ -753,81 +754,13 @@ static void fdNavigate(const char *path) {
|
|||
// ============================================================
|
||||
|
||||
static bool fdValidateFilename(const char *name) {
|
||||
// DOS 8.3 validation
|
||||
static const char *reserved[] = {
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
NULL
|
||||
};
|
||||
const char *error = platformValidateFilename(name);
|
||||
|
||||
if (!name || name[0] == '\0') {
|
||||
if (error) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", error, MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split into base and extension
|
||||
const char *dot = strrchr(name, '.');
|
||||
int32_t baseLen;
|
||||
int32_t extLen;
|
||||
|
||||
if (dot) {
|
||||
baseLen = (int32_t)(dot - name);
|
||||
extLen = (int32_t)strlen(dot + 1);
|
||||
} else {
|
||||
baseLen = (int32_t)strlen(name);
|
||||
extLen = 0;
|
||||
}
|
||||
|
||||
if (baseLen < 1 || baseLen > 8) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename must be 1-8 characters before the extension.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extLen > 3) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "Extension must be 3 characters or fewer.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for invalid characters
|
||||
for (const char *p = name; *p; p++) {
|
||||
if (*p == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*p < '!' || *p > '~') {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename contains invalid characters.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strchr(" \"*+,/:;<=>?[\\]|", *p)) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename contains invalid characters.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for multiple dots
|
||||
if (dot && strchr(name, '.') != dot) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename may contain only one dot.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check reserved device names (compare base only, case-insensitive)
|
||||
char base[9];
|
||||
int32_t copyLen = baseLen < 8 ? baseLen : 8;
|
||||
|
||||
for (int32_t i = 0; i < copyLen; i++) {
|
||||
base[i] = toupper((unsigned char)name[i]);
|
||||
}
|
||||
|
||||
base[copyLen] = '\0';
|
||||
|
||||
for (const char **r = reserved; *r; r++) {
|
||||
if (strcmp(base, *r) == 0) {
|
||||
dvxMessageBox(sFd.ctx, "Invalid Filename", "That name is a reserved device name.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
199
dvx/dvxDraw.c
199
dvx/dvxDraw.c
|
|
@ -1,6 +1,7 @@
|
|||
// dvx_draw.c — Layer 2: Drawing primitives for DV/X GUI (optimized)
|
||||
|
||||
#include "dvxDraw.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -11,12 +12,6 @@
|
|||
char accelParse(const char *text);
|
||||
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h);
|
||||
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp);
|
||||
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count);
|
||||
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
|
||||
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
|
||||
|
||||
// Bit lookup tables — avoids per-pixel shift on 486 (40+ cycle savings per shift)
|
||||
static const uint8_t sGlyphBit[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
|
@ -571,20 +566,20 @@ void drawInit(BlitOpsT *ops, const DisplayT *d) {
|
|||
|
||||
switch (d->format.bytesPerPixel) {
|
||||
case 1:
|
||||
ops->spanFill = spanFill8;
|
||||
ops->spanCopy = spanCopy8;
|
||||
ops->spanFill = platformSpanFill8;
|
||||
ops->spanCopy = platformSpanCopy8;
|
||||
break;
|
||||
case 2:
|
||||
ops->spanFill = spanFill16;
|
||||
ops->spanCopy = spanCopy16;
|
||||
ops->spanFill = platformSpanFill16;
|
||||
ops->spanCopy = platformSpanCopy16;
|
||||
break;
|
||||
case 4:
|
||||
ops->spanFill = spanFill32;
|
||||
ops->spanCopy = spanCopy32;
|
||||
ops->spanFill = platformSpanFill32;
|
||||
ops->spanCopy = platformSpanCopy32;
|
||||
break;
|
||||
default:
|
||||
ops->spanFill = spanFill8;
|
||||
ops->spanCopy = spanCopy8;
|
||||
ops->spanFill = platformSpanFill8;
|
||||
ops->spanCopy = platformSpanCopy8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -990,188 +985,14 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
uint8_t *row = d->backBuf + y * d->pitch + x * bpp;
|
||||
uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel;
|
||||
int32_t pitch = d->pitch;
|
||||
|
||||
// Inline rep stosl for 32bpp — avoids function pointer call overhead
|
||||
// per scanline which is significant for the many 1px fills (Item 10)
|
||||
if (__builtin_expect(bpp == 4, 1)) {
|
||||
for (int32_t i = 0; i < h; i++) {
|
||||
int32_t count = w;
|
||||
uint8_t *dst = row;
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(count)
|
||||
: "a"(color)
|
||||
: "memory"
|
||||
);
|
||||
row += pitch;
|
||||
}
|
||||
} else {
|
||||
for (int32_t i = 0; i < h; i++) {
|
||||
ops->spanFill(row, color, w);
|
||||
row += pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanCopy8
|
||||
// ============================================================
|
||||
|
||||
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
// Align to 4 bytes
|
||||
while (((uintptr_t)dst & 3) && count > 0) {
|
||||
*dst++ = *src++;
|
||||
count--;
|
||||
}
|
||||
|
||||
if (count >= 4) {
|
||||
int32_t dwordCount = count >> 2;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
src += dwordCount * 4;
|
||||
}
|
||||
|
||||
int32_t rem = count & 3;
|
||||
while (rem-- > 0) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanCopy16
|
||||
// ============================================================
|
||||
|
||||
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
// Handle odd leading pixel for dword alignment
|
||||
if (((uintptr_t)dst & 2) && count > 0) {
|
||||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||||
dst += 2;
|
||||
src += 2;
|
||||
count--;
|
||||
}
|
||||
|
||||
if (count >= 2) {
|
||||
int32_t dwordCount = count >> 1;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
src += dwordCount * 4;
|
||||
}
|
||||
|
||||
if (count & 1) {
|
||||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanCopy32
|
||||
// ============================================================
|
||||
|
||||
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(count)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanFill8
|
||||
// ============================================================
|
||||
|
||||
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
uint8_t c = (uint8_t)color;
|
||||
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
||||
|
||||
// Align to 4 bytes — skip if already aligned (Item 11)
|
||||
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
|
||||
while (((uintptr_t)dst & 3) && count > 0) {
|
||||
*dst++ = c;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= 4) {
|
||||
int32_t dwordCount = count >> 2;
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(dwordCount)
|
||||
: "a"(dword)
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
}
|
||||
|
||||
int32_t rem = count & 3;
|
||||
while (rem-- > 0) {
|
||||
*dst++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanFill16
|
||||
// ============================================================
|
||||
|
||||
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
uint16_t c = (uint16_t)color;
|
||||
|
||||
// Handle odd leading pixel for dword alignment
|
||||
if (((uintptr_t)dst & 2) && count > 0) {
|
||||
*(uint16_t *)dst = c;
|
||||
dst += 2;
|
||||
count--;
|
||||
}
|
||||
|
||||
// Fill pairs of pixels as 32-bit dwords
|
||||
if (count >= 2) {
|
||||
uint32_t dword = ((uint32_t)c << 16) | c;
|
||||
int32_t dwordCount = count >> 1;
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(dwordCount)
|
||||
: "a"(dword)
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
}
|
||||
|
||||
// Handle trailing odd pixel
|
||||
if (count & 1) {
|
||||
*(uint16_t *)dst = c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spanFill32
|
||||
// ============================================================
|
||||
|
||||
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(count)
|
||||
: "a"(color)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
377
dvx/dvxVideo.c
377
dvx/dvxVideo.c
|
|
@ -1,262 +1,15 @@
|
|||
// dvx_video.c — Layer 1: VESA VBE video backend for DV/X GUI
|
||||
// dvx_video.c — Layer 1: Video backend for DV/X GUI
|
||||
//
|
||||
// Platform-independent video utilities. The actual VESA/VBE code
|
||||
// now lives in dvxPlatformDos.c (or the platform file for whatever
|
||||
// OS we're targeting).
|
||||
|
||||
#include "dvxVideo.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
#include "dvxPalette.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <pc.h>
|
||||
#include <sys/nearptr.h>
|
||||
#include <sys/farptr.h>
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
|
||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
|
||||
static int32_t setVesaMode(uint16_t mode);
|
||||
static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// findBestMode
|
||||
// ============================================================
|
||||
|
||||
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) {
|
||||
__dpmi_regs r;
|
||||
uint16_t bestMode = 0;
|
||||
int32_t bestScore = -1;
|
||||
DisplayT bestDisplay;
|
||||
|
||||
memset(&bestDisplay, 0, sizeof(bestDisplay));
|
||||
|
||||
// Get VBE controller info
|
||||
// Put "VBE2" signature at ES:DI in conventional memory
|
||||
uint32_t infoSeg = __tb >> 4;
|
||||
uint32_t infoOff = __tb & 0x0F;
|
||||
|
||||
// Write "VBE2" at transfer buffer to request VBE 2.0 info
|
||||
_farpokeb(_dos_ds, __tb + 0, 'V');
|
||||
_farpokeb(_dos_ds, __tb + 1, 'B');
|
||||
_farpokeb(_dos_ds, __tb + 2, 'E');
|
||||
_farpokeb(_dos_ds, __tb + 3, '2');
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F00;
|
||||
r.x.es = infoSeg;
|
||||
r.x.di = infoOff;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify VBE signature
|
||||
char sig[5];
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
sig[i] = _farpeekb(_dos_ds, __tb + i);
|
||||
}
|
||||
sig[4] = '\0';
|
||||
|
||||
if (strcmp(sig, "VESA") != 0) {
|
||||
fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check VBE version (need 2.0+)
|
||||
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
|
||||
if (vbeVersion < 0x0200) {
|
||||
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
|
||||
vbeVersion >> 8, vbeVersion & 0xFF);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get mode list pointer (far pointer at offset 14)
|
||||
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
|
||||
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
|
||||
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
|
||||
|
||||
// Walk mode list
|
||||
for (int32_t i = 0; i < 256; i++) {
|
||||
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
|
||||
|
||||
if (mode == 0xFFFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
DisplayT candidate;
|
||||
int32_t score = 0;
|
||||
|
||||
memset(&candidate, 0, sizeof(candidate));
|
||||
getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp);
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMode = mode;
|
||||
bestDisplay = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore < 0) {
|
||||
fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*outMode = bestMode;
|
||||
*d = bestDisplay;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// getModeInfo
|
||||
// ============================================================
|
||||
|
||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||
__dpmi_regs r;
|
||||
|
||||
*score = -1;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F01;
|
||||
r.x.cx = mode;
|
||||
r.x.es = __tb >> 4;
|
||||
r.x.di = __tb & 0x0F;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read mode attributes
|
||||
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
|
||||
|
||||
// Must have LFB support (bit 7)
|
||||
if (!(attr & 0x0080)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be a graphics mode (bit 4)
|
||||
if (!(attr & 0x0010)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t w = _farpeekw(_dos_ds, __tb + 18);
|
||||
int32_t h = _farpeekw(_dos_ds, __tb + 20);
|
||||
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
|
||||
int32_t pitch = _farpeekw(_dos_ds, __tb + 16);
|
||||
uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);
|
||||
|
||||
// Must match or exceed requested resolution
|
||||
if (w < requestedW || h < requestedH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be a supported bpp
|
||||
if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Score this mode
|
||||
int32_t s = 0;
|
||||
|
||||
if (bpp == 16) {
|
||||
s = 100;
|
||||
} else if (bpp == 15) {
|
||||
s = 90;
|
||||
} else if (bpp == 32) {
|
||||
s = 85;
|
||||
} else if (bpp == 8) {
|
||||
s = 70;
|
||||
}
|
||||
|
||||
// Prefer the user's preferred bpp
|
||||
if (bpp == preferredBpp) {
|
||||
s += 20;
|
||||
}
|
||||
|
||||
// Exact resolution match is preferred
|
||||
if (w == requestedW && h == requestedH) {
|
||||
s += 10;
|
||||
} else {
|
||||
s -= 10;
|
||||
}
|
||||
|
||||
*score = s;
|
||||
|
||||
// Fill in display info
|
||||
d->width = w;
|
||||
d->height = h;
|
||||
d->pitch = pitch;
|
||||
|
||||
d->format.bitsPerPixel = bpp;
|
||||
d->format.bytesPerPixel = (bpp + 7) / 8;
|
||||
|
||||
// Read color masks from mode info
|
||||
if (bpp >= 15) {
|
||||
int32_t redSize = _farpeekb(_dos_ds, __tb + 31);
|
||||
int32_t redPos = _farpeekb(_dos_ds, __tb + 32);
|
||||
int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
|
||||
int32_t greenPos = _farpeekb(_dos_ds, __tb + 34);
|
||||
int32_t blueSize = _farpeekb(_dos_ds, __tb + 35);
|
||||
int32_t bluePos = _farpeekb(_dos_ds, __tb + 36);
|
||||
|
||||
d->format.redBits = redSize;
|
||||
d->format.redShift = redPos;
|
||||
d->format.redMask = ((1U << redSize) - 1) << redPos;
|
||||
d->format.greenBits = greenSize;
|
||||
d->format.greenShift = greenPos;
|
||||
d->format.greenMask = ((1U << greenSize) - 1) << greenPos;
|
||||
d->format.blueBits = blueSize;
|
||||
d->format.blueShift = bluePos;
|
||||
d->format.blueMask = ((1U << blueSize) - 1) << bluePos;
|
||||
}
|
||||
|
||||
// Store physical address in lfb field temporarily (will be remapped)
|
||||
d->lfb = (uint8_t *)(uintptr_t)physAddr;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// mapLfb
|
||||
// ============================================================
|
||||
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
|
||||
__dpmi_meminfo info;
|
||||
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
|
||||
|
||||
info.address = physAddr;
|
||||
info.size = fbSize;
|
||||
|
||||
if (__dpmi_physical_address_mapping(&info) != 0) {
|
||||
fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lock the region to prevent paging
|
||||
__dpmi_meminfo lockInfo;
|
||||
lockInfo.address = info.address;
|
||||
lockInfo.size = fbSize;
|
||||
__dpmi_lock_linear_region(&lockInfo);
|
||||
|
||||
// Enable near pointers for direct access
|
||||
if (__djgpp_nearptr_enable() == 0) {
|
||||
fprintf(stderr, "VBE: Failed to enable near pointers\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// packColor
|
||||
|
|
@ -307,108 +60,12 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setVesaMode
|
||||
// ============================================================
|
||||
|
||||
static int32_t setVesaMode(uint16_t mode) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F02;
|
||||
r.x.bx = mode | 0x4000; // bit 14 = use LFB
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// videoInit
|
||||
// ============================================================
|
||||
|
||||
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||
uint16_t bestMode;
|
||||
uint32_t physAddr;
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
|
||||
// Find the best VESA mode
|
||||
if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Save the physical address before we overwrite it
|
||||
physAddr = (uint32_t)(uintptr_t)d->lfb;
|
||||
|
||||
// Set the mode
|
||||
if (setVesaMode(bestMode) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Map the LFB
|
||||
if (mapLfb(d, physAddr) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate backbuffer
|
||||
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
|
||||
d->backBuf = (uint8_t *)malloc(fbSize);
|
||||
|
||||
if (!d->backBuf) {
|
||||
fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
|
||||
__djgpp_nearptr_disable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(d->backBuf, 0, fbSize);
|
||||
|
||||
// Set up palette for 8-bit mode
|
||||
if (d->format.bitsPerPixel == 8) {
|
||||
d->palette = (uint8_t *)malloc(768);
|
||||
|
||||
if (!d->palette) {
|
||||
fprintf(stderr, "VBE: Failed to allocate palette\n");
|
||||
free(d->backBuf);
|
||||
d->backBuf = NULL;
|
||||
__djgpp_nearptr_disable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
dvxGeneratePalette(d->palette);
|
||||
videoSetPalette(d->palette, 0, 256);
|
||||
}
|
||||
|
||||
// Initialize clip rect to full display
|
||||
resetClipRect(d);
|
||||
|
||||
fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n",
|
||||
bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// videoSetPalette
|
||||
// ============================================================
|
||||
|
||||
static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
|
||||
// Set VGA DAC registers directly via port I/O
|
||||
// Port 0x3C8 = write index, Port 0x3C9 = data (R, G, B in 6-bit values)
|
||||
outportb(0x3C8, (uint8_t)firstEntry);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
int32_t idx = (firstEntry + i) * 3;
|
||||
outportb(0x3C9, pal[idx + 0] >> 2); // VGA DAC uses 6-bit values
|
||||
outportb(0x3C9, pal[idx + 1] >> 2);
|
||||
outportb(0x3C9, pal[idx + 2] >> 2);
|
||||
}
|
||||
return platformVideoInit(d, requestedW, requestedH, preferredBpp);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -417,23 +74,5 @@ static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t coun
|
|||
// ============================================================
|
||||
|
||||
void videoShutdown(DisplayT *d) {
|
||||
// Restore text mode (mode 3)
|
||||
__dpmi_regs r;
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0003;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (d->backBuf) {
|
||||
free(d->backBuf);
|
||||
d->backBuf = NULL;
|
||||
}
|
||||
|
||||
if (d->palette) {
|
||||
free(d->palette);
|
||||
d->palette = NULL;
|
||||
}
|
||||
|
||||
d->lfb = NULL;
|
||||
|
||||
__djgpp_nearptr_disable();
|
||||
platformVideoShutdown(d);
|
||||
}
|
||||
|
|
|
|||
98
dvx/platform/dvxPlatform.h
Normal file
98
dvx/platform/dvxPlatform.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// dvxPlatform.h — Platform abstraction layer for DV/X GUI
|
||||
//
|
||||
// All OS-specific and CPU-specific code is isolated behind this
|
||||
// interface. To port DV/X to a new platform, implement a new
|
||||
// dvxPlatformXxx.c against this header.
|
||||
#ifndef DVX_PLATFORM_H
|
||||
#define DVX_PLATFORM_H
|
||||
|
||||
#include "../dvxTypes.h"
|
||||
|
||||
// ============================================================
|
||||
// Keyboard event
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
int32_t ascii; // ASCII value, 0 for extended/function keys
|
||||
int32_t scancode; // PC scan code (0x48=Up, 0x50=Down, etc.)
|
||||
} PlatformKeyEventT;
|
||||
|
||||
// ============================================================
|
||||
// System lifecycle
|
||||
// ============================================================
|
||||
|
||||
// One-time platform initialisation (signal handling, etc.)
|
||||
void platformInit(void);
|
||||
|
||||
// Cooperative multitasking yield (give up CPU when idle)
|
||||
void platformYield(void);
|
||||
|
||||
// ============================================================
|
||||
// Video
|
||||
// ============================================================
|
||||
|
||||
// Initialise video mode, map framebuffer, allocate backbuffer.
|
||||
// Fills in all DisplayT fields on success. Returns 0/-1.
|
||||
int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
|
||||
// Shut down video — restore text mode, unmap framebuffer, free backbuffer.
|
||||
void platformVideoShutdown(DisplayT *d);
|
||||
|
||||
// Set palette entries (8-bit mode). pal is R,G,B triplets.
|
||||
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
|
||||
|
||||
// ============================================================
|
||||
// Framebuffer flush
|
||||
// ============================================================
|
||||
|
||||
// Copy a rectangle from d->backBuf to the display surface (d->lfb).
|
||||
void platformFlushRect(const DisplayT *d, const RectT *r);
|
||||
|
||||
// ============================================================
|
||||
// Optimised memory operations (span fill / copy)
|
||||
// ============================================================
|
||||
|
||||
void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count);
|
||||
void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count);
|
||||
void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count);
|
||||
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||
|
||||
// ============================================================
|
||||
// Input — Mouse
|
||||
// ============================================================
|
||||
|
||||
// Initialise mouse driver, set movement range, centre cursor.
|
||||
void platformMouseInit(int32_t screenW, int32_t screenH);
|
||||
|
||||
// Read current mouse position and button state.
|
||||
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
|
||||
|
||||
// ============================================================
|
||||
// Input — Keyboard
|
||||
// ============================================================
|
||||
|
||||
// Return current modifier flags (BIOS shift-state format:
|
||||
// bits 0-1 = shift, bit 2 = ctrl, bit 3 = alt).
|
||||
int32_t platformKeyboardGetModifiers(void);
|
||||
|
||||
// Read the next key from the keyboard buffer.
|
||||
// Returns true if a key was available, false if the buffer was empty.
|
||||
// Normalises extended-key markers (e.g. 0xE0 → 0).
|
||||
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
||||
|
||||
// Map a scan code to its Alt+letter ASCII character.
|
||||
// Returns the lowercase letter if the scan code corresponds to an
|
||||
// Alt+letter/digit combo, or 0 if it does not.
|
||||
char platformAltScanToChar(int32_t scancode);
|
||||
|
||||
// ============================================================
|
||||
// File system
|
||||
// ============================================================
|
||||
|
||||
// Validate a filename for the current platform.
|
||||
// Returns NULL if valid, or a human-readable error string if invalid.
|
||||
const char *platformValidateFilename(const char *name);
|
||||
|
||||
#endif // DVX_PLATFORM_H
|
||||
841
dvx/platform/dvxPlatformDos.c
Normal file
841
dvx/platform/dvxPlatformDos.c
Normal file
|
|
@ -0,0 +1,841 @@
|
|||
// dvxPlatformDos.c — DOS/DJGPP platform implementation for DV/X GUI
|
||||
//
|
||||
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
|
||||
// DOS-specific file handling are isolated in this single file.
|
||||
|
||||
#include "dvxPlatform.h"
|
||||
#include "../dvxPalette.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <pc.h>
|
||||
#include <sys/nearptr.h>
|
||||
#include <sys/farptr.h>
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
|
||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
|
||||
static int32_t setVesaMode(uint16_t mode);
|
||||
|
||||
// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code).
|
||||
// INT 16h returns these scan codes with ascii=0 for Alt+key combos.
|
||||
static const char sAltScanToAscii[256] = {
|
||||
// Alt+letters
|
||||
[0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r',
|
||||
[0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i',
|
||||
[0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's',
|
||||
[0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h',
|
||||
[0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z',
|
||||
[0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b',
|
||||
[0x31] = 'n', [0x32] = 'm',
|
||||
// Alt+digits
|
||||
[0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4',
|
||||
[0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8',
|
||||
[0x80] = '9', [0x81] = '0',
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// findBestMode
|
||||
// ============================================================
|
||||
|
||||
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) {
|
||||
__dpmi_regs r;
|
||||
uint16_t bestMode = 0;
|
||||
int32_t bestScore = -1;
|
||||
DisplayT bestDisplay;
|
||||
|
||||
memset(&bestDisplay, 0, sizeof(bestDisplay));
|
||||
|
||||
// Get VBE controller info
|
||||
uint32_t infoSeg = __tb >> 4;
|
||||
uint32_t infoOff = __tb & 0x0F;
|
||||
|
||||
// Write "VBE2" at transfer buffer to request VBE 2.0 info
|
||||
_farpokeb(_dos_ds, __tb + 0, 'V');
|
||||
_farpokeb(_dos_ds, __tb + 1, 'B');
|
||||
_farpokeb(_dos_ds, __tb + 2, 'E');
|
||||
_farpokeb(_dos_ds, __tb + 3, '2');
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F00;
|
||||
r.x.es = infoSeg;
|
||||
r.x.di = infoOff;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify VBE signature
|
||||
char sig[5];
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
sig[i] = _farpeekb(_dos_ds, __tb + i);
|
||||
}
|
||||
sig[4] = '\0';
|
||||
|
||||
if (strcmp(sig, "VESA") != 0) {
|
||||
fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check VBE version (need 2.0+)
|
||||
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
|
||||
if (vbeVersion < 0x0200) {
|
||||
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
|
||||
vbeVersion >> 8, vbeVersion & 0xFF);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get mode list pointer (far pointer at offset 14)
|
||||
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
|
||||
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
|
||||
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
|
||||
|
||||
// Walk mode list
|
||||
for (int32_t i = 0; i < 256; i++) {
|
||||
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
|
||||
|
||||
if (mode == 0xFFFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
DisplayT candidate;
|
||||
int32_t score = 0;
|
||||
|
||||
memset(&candidate, 0, sizeof(candidate));
|
||||
getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp);
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMode = mode;
|
||||
bestDisplay = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore < 0) {
|
||||
fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*outMode = bestMode;
|
||||
*d = bestDisplay;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// getModeInfo
|
||||
// ============================================================
|
||||
|
||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||
__dpmi_regs r;
|
||||
|
||||
*score = -1;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F01;
|
||||
r.x.cx = mode;
|
||||
r.x.es = __tb >> 4;
|
||||
r.x.di = __tb & 0x0F;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read mode attributes
|
||||
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
|
||||
|
||||
// Must have LFB support (bit 7)
|
||||
if (!(attr & 0x0080)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be a graphics mode (bit 4)
|
||||
if (!(attr & 0x0010)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t w = _farpeekw(_dos_ds, __tb + 18);
|
||||
int32_t h = _farpeekw(_dos_ds, __tb + 20);
|
||||
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
|
||||
int32_t pitch = _farpeekw(_dos_ds, __tb + 16);
|
||||
uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);
|
||||
|
||||
// Must match or exceed requested resolution
|
||||
if (w < requestedW || h < requestedH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be a supported bpp
|
||||
if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Score this mode
|
||||
int32_t s = 0;
|
||||
|
||||
if (bpp == 16) {
|
||||
s = 100;
|
||||
} else if (bpp == 15) {
|
||||
s = 90;
|
||||
} else if (bpp == 32) {
|
||||
s = 85;
|
||||
} else if (bpp == 8) {
|
||||
s = 70;
|
||||
}
|
||||
|
||||
// Prefer the user's preferred bpp
|
||||
if (bpp == preferredBpp) {
|
||||
s += 20;
|
||||
}
|
||||
|
||||
// Exact resolution match is preferred
|
||||
if (w == requestedW && h == requestedH) {
|
||||
s += 10;
|
||||
} else {
|
||||
s -= 10;
|
||||
}
|
||||
|
||||
*score = s;
|
||||
|
||||
// Fill in display info
|
||||
d->width = w;
|
||||
d->height = h;
|
||||
d->pitch = pitch;
|
||||
|
||||
d->format.bitsPerPixel = bpp;
|
||||
d->format.bytesPerPixel = (bpp + 7) / 8;
|
||||
|
||||
// Read color masks from mode info
|
||||
if (bpp >= 15) {
|
||||
int32_t redSize = _farpeekb(_dos_ds, __tb + 31);
|
||||
int32_t redPos = _farpeekb(_dos_ds, __tb + 32);
|
||||
int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
|
||||
int32_t greenPos = _farpeekb(_dos_ds, __tb + 34);
|
||||
int32_t blueSize = _farpeekb(_dos_ds, __tb + 35);
|
||||
int32_t bluePos = _farpeekb(_dos_ds, __tb + 36);
|
||||
|
||||
d->format.redBits = redSize;
|
||||
d->format.redShift = redPos;
|
||||
d->format.redMask = ((1U << redSize) - 1) << redPos;
|
||||
d->format.greenBits = greenSize;
|
||||
d->format.greenShift = greenPos;
|
||||
d->format.greenMask = ((1U << greenSize) - 1) << greenPos;
|
||||
d->format.blueBits = blueSize;
|
||||
d->format.blueShift = bluePos;
|
||||
d->format.blueMask = ((1U << blueSize) - 1) << bluePos;
|
||||
}
|
||||
|
||||
// Store physical address in lfb field temporarily (will be remapped)
|
||||
d->lfb = (uint8_t *)(uintptr_t)physAddr;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// mapLfb
|
||||
// ============================================================
|
||||
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
|
||||
__dpmi_meminfo info;
|
||||
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
|
||||
|
||||
info.address = physAddr;
|
||||
info.size = fbSize;
|
||||
|
||||
if (__dpmi_physical_address_mapping(&info) != 0) {
|
||||
fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lock the region to prevent paging
|
||||
__dpmi_meminfo lockInfo;
|
||||
lockInfo.address = info.address;
|
||||
lockInfo.size = fbSize;
|
||||
__dpmi_lock_linear_region(&lockInfo);
|
||||
|
||||
// Enable near pointers for direct access
|
||||
if (__djgpp_nearptr_enable() == 0) {
|
||||
fprintf(stderr, "VBE: Failed to enable near pointers\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformAltScanToChar
|
||||
// ============================================================
|
||||
|
||||
char platformAltScanToChar(int32_t scancode) {
|
||||
if (scancode < 0 || scancode > 255) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sAltScanToAscii[scancode];
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformFlushRect
|
||||
// ============================================================
|
||||
|
||||
void platformFlushRect(const DisplayT *d, const RectT *r) {
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
|
||||
int32_t x = r->x;
|
||||
int32_t y = r->y;
|
||||
int32_t w = r->w;
|
||||
int32_t h = r->h;
|
||||
|
||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t rowBytes = w * bpp;
|
||||
int32_t pitch = d->pitch;
|
||||
uint8_t *src = d->backBuf + y * pitch + x * bpp;
|
||||
uint8_t *dst = d->lfb + y * pitch + x * bpp;
|
||||
|
||||
// Full-width flush: single large copy
|
||||
if (rowBytes == pitch) {
|
||||
int32_t totalBytes = pitch * h;
|
||||
int32_t dwords = totalBytes >> 2;
|
||||
int32_t remainder = totalBytes & 3;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwords)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
while (remainder-- > 0) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
} else {
|
||||
// Partial scanlines — copy row by row with rep movsd
|
||||
int32_t dwords = rowBytes >> 2;
|
||||
int32_t remainder = rowBytes & 3;
|
||||
for (int32_t i = 0; i < h; i++) {
|
||||
int32_t dc = dwords;
|
||||
uint8_t *s = src;
|
||||
uint8_t *dd = dst;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dd), "+S"(s), "+c"(dc)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
if (__builtin_expect(remainder > 0, 0)) {
|
||||
int32_t rem = remainder;
|
||||
while (rem-- > 0) {
|
||||
*dd++ = *s++;
|
||||
}
|
||||
}
|
||||
src += pitch;
|
||||
dst += pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformInit
|
||||
// ============================================================
|
||||
|
||||
void platformInit(void) {
|
||||
// Disable Ctrl+C/Break termination
|
||||
signal(SIGINT, SIG_IGN);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformKeyboardGetModifiers
|
||||
// ============================================================
|
||||
|
||||
int32_t platformKeyboardGetModifiers(void) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x1200;
|
||||
__dpmi_int(0x16, &r);
|
||||
|
||||
return r.x.ax & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformKeyboardRead
|
||||
// ============================================================
|
||||
|
||||
bool platformKeyboardRead(PlatformKeyEventT *evt) {
|
||||
__dpmi_regs r;
|
||||
|
||||
// Check if key is available (INT 16h, enhanced function 11h)
|
||||
r.x.ax = 0x1100;
|
||||
__dpmi_int(0x16, &r);
|
||||
|
||||
// Zero flag set = no key available
|
||||
if (r.x.flags & 0x40) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the key (INT 16h, enhanced function 10h)
|
||||
r.x.ax = 0x1000;
|
||||
__dpmi_int(0x16, &r);
|
||||
|
||||
evt->scancode = (r.x.ax >> 8) & 0xFF;
|
||||
evt->ascii = r.x.ax & 0xFF;
|
||||
|
||||
// Enhanced INT 16h returns ascii=0xE0 for grey/extended keys
|
||||
// (arrows, Home, End, Insert, Delete, etc. on 101-key keyboards).
|
||||
// Normalize to 0 so all extended key checks work uniformly.
|
||||
if (evt->ascii == 0xE0) {
|
||||
evt->ascii = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformMouseInit
|
||||
// ============================================================
|
||||
|
||||
void platformMouseInit(int32_t screenW, int32_t screenH) {
|
||||
__dpmi_regs r;
|
||||
|
||||
// Reset mouse driver
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0000;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Set horizontal range
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0007;
|
||||
r.x.cx = 0;
|
||||
r.x.dx = screenW - 1;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Set vertical range
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0008;
|
||||
r.x.cx = 0;
|
||||
r.x.dx = screenH - 1;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
// Position cursor at center
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0004;
|
||||
r.x.cx = screenW / 2;
|
||||
r.x.dx = screenH / 2;
|
||||
__dpmi_int(0x33, &r);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformMousePoll
|
||||
// ============================================================
|
||||
|
||||
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0003;
|
||||
__dpmi_int(0x33, &r);
|
||||
|
||||
*mx = r.x.cx;
|
||||
*my = r.x.dx;
|
||||
*buttons = r.x.bx;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanCopy8
|
||||
// ============================================================
|
||||
|
||||
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
// Align to 4 bytes
|
||||
while (((uintptr_t)dst & 3) && count > 0) {
|
||||
*dst++ = *src++;
|
||||
count--;
|
||||
}
|
||||
|
||||
if (count >= 4) {
|
||||
int32_t dwordCount = count >> 2;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
src += dwordCount * 4;
|
||||
}
|
||||
|
||||
int32_t rem = count & 3;
|
||||
while (rem-- > 0) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanCopy16
|
||||
// ============================================================
|
||||
|
||||
void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
// Handle odd leading pixel for dword alignment
|
||||
if (((uintptr_t)dst & 2) && count > 0) {
|
||||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||||
dst += 2;
|
||||
src += 2;
|
||||
count--;
|
||||
}
|
||||
|
||||
if (count >= 2) {
|
||||
int32_t dwordCount = count >> 1;
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(dwordCount)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
src += dwordCount * 4;
|
||||
}
|
||||
|
||||
if (count & 1) {
|
||||
*(uint16_t *)dst = *(const uint16_t *)src;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanCopy32
|
||||
// ============================================================
|
||||
|
||||
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||
__asm__ __volatile__ (
|
||||
"rep movsl"
|
||||
: "+D"(dst), "+S"(src), "+c"(count)
|
||||
:
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanFill8
|
||||
// ============================================================
|
||||
|
||||
void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
uint8_t c = (uint8_t)color;
|
||||
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
||||
|
||||
// Align to 4 bytes — skip if already aligned
|
||||
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
|
||||
while (((uintptr_t)dst & 3) && count > 0) {
|
||||
*dst++ = c;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= 4) {
|
||||
int32_t dwordCount = count >> 2;
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(dwordCount)
|
||||
: "a"(dword)
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
}
|
||||
|
||||
int32_t rem = count & 3;
|
||||
while (rem-- > 0) {
|
||||
*dst++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanFill16
|
||||
// ============================================================
|
||||
|
||||
void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
uint16_t c = (uint16_t)color;
|
||||
|
||||
// Handle odd leading pixel for dword alignment
|
||||
if (((uintptr_t)dst & 2) && count > 0) {
|
||||
*(uint16_t *)dst = c;
|
||||
dst += 2;
|
||||
count--;
|
||||
}
|
||||
|
||||
// Fill pairs of pixels as 32-bit dwords
|
||||
if (count >= 2) {
|
||||
uint32_t dword = ((uint32_t)c << 16) | c;
|
||||
int32_t dwordCount = count >> 1;
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(dwordCount)
|
||||
: "a"(dword)
|
||||
: "memory"
|
||||
);
|
||||
dst += dwordCount * 4;
|
||||
}
|
||||
|
||||
// Handle trailing odd pixel
|
||||
if (count & 1) {
|
||||
*(uint16_t *)dst = c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformSpanFill32
|
||||
// ============================================================
|
||||
|
||||
void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
|
||||
__asm__ __volatile__ (
|
||||
"rep stosl"
|
||||
: "+D"(dst), "+c"(count)
|
||||
: "a"(color)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformValidateFilename — DOS 8.3 filename validation
|
||||
// ============================================================
|
||||
|
||||
const char *platformValidateFilename(const char *name) {
|
||||
static const char *reserved[] = {
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
NULL
|
||||
};
|
||||
|
||||
if (!name || name[0] == '\0') {
|
||||
return "Filename must not be empty.";
|
||||
}
|
||||
|
||||
// Split into base and extension
|
||||
const char *dot = strrchr(name, '.');
|
||||
int32_t baseLen;
|
||||
int32_t extLen;
|
||||
|
||||
if (dot) {
|
||||
baseLen = (int32_t)(dot - name);
|
||||
extLen = (int32_t)strlen(dot + 1);
|
||||
} else {
|
||||
baseLen = (int32_t)strlen(name);
|
||||
extLen = 0;
|
||||
}
|
||||
|
||||
if (baseLen < 1 || baseLen > 8) {
|
||||
return "Filename must be 1-8 characters before the extension.";
|
||||
}
|
||||
|
||||
if (extLen > 3) {
|
||||
return "Extension must be 3 characters or fewer.";
|
||||
}
|
||||
|
||||
// Check for invalid characters
|
||||
for (const char *p = name; *p; p++) {
|
||||
if (*p == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*p < '!' || *p > '~') {
|
||||
return "Filename contains invalid characters.";
|
||||
}
|
||||
|
||||
if (strchr(" \"*+,/:;<=>?[\\]|", *p)) {
|
||||
return "Filename contains invalid characters.";
|
||||
}
|
||||
}
|
||||
|
||||
// Check for multiple dots
|
||||
if (dot && strchr(name, '.') != dot) {
|
||||
return "Filename may contain only one dot.";
|
||||
}
|
||||
|
||||
// Check reserved device names (compare base only, case-insensitive)
|
||||
char base[9];
|
||||
int32_t copyLen = baseLen < 8 ? baseLen : 8;
|
||||
|
||||
for (int32_t i = 0; i < copyLen; i++) {
|
||||
base[i] = toupper((unsigned char)name[i]);
|
||||
}
|
||||
|
||||
base[copyLen] = '\0';
|
||||
|
||||
for (const char **r = reserved; *r; r++) {
|
||||
if (strcmp(base, *r) == 0) {
|
||||
return "That name is a reserved device name.";
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformVideoInit
|
||||
// ============================================================
|
||||
|
||||
int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||
uint16_t bestMode;
|
||||
uint32_t physAddr;
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
|
||||
// Find the best VESA mode
|
||||
if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Save the physical address before we overwrite it
|
||||
physAddr = (uint32_t)(uintptr_t)d->lfb;
|
||||
|
||||
// Set the mode
|
||||
if (setVesaMode(bestMode) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Map the LFB
|
||||
if (mapLfb(d, physAddr) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate backbuffer
|
||||
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
|
||||
d->backBuf = (uint8_t *)malloc(fbSize);
|
||||
|
||||
if (!d->backBuf) {
|
||||
fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
|
||||
__djgpp_nearptr_disable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(d->backBuf, 0, fbSize);
|
||||
|
||||
// Set up palette for 8-bit mode
|
||||
if (d->format.bitsPerPixel == 8) {
|
||||
d->palette = (uint8_t *)malloc(768);
|
||||
|
||||
if (!d->palette) {
|
||||
fprintf(stderr, "VBE: Failed to allocate palette\n");
|
||||
free(d->backBuf);
|
||||
d->backBuf = NULL;
|
||||
__djgpp_nearptr_disable();
|
||||
return -1;
|
||||
}
|
||||
|
||||
dvxGeneratePalette(d->palette);
|
||||
platformVideoSetPalette(d->palette, 0, 256);
|
||||
}
|
||||
|
||||
// Initialize clip rect to full display
|
||||
d->clipX = 0;
|
||||
d->clipY = 0;
|
||||
d->clipW = d->width;
|
||||
d->clipH = d->height;
|
||||
|
||||
fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n",
|
||||
bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformVideoSetPalette
|
||||
// ============================================================
|
||||
|
||||
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
|
||||
// Set VGA DAC registers directly via port I/O
|
||||
outportb(0x3C8, (uint8_t)firstEntry);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
int32_t idx = (firstEntry + i) * 3;
|
||||
outportb(0x3C9, pal[idx + 0] >> 2);
|
||||
outportb(0x3C9, pal[idx + 1] >> 2);
|
||||
outportb(0x3C9, pal[idx + 2] >> 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformVideoShutdown
|
||||
// ============================================================
|
||||
|
||||
void platformVideoShutdown(DisplayT *d) {
|
||||
// Restore text mode (mode 3)
|
||||
__dpmi_regs r;
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x0003;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (d->backBuf) {
|
||||
free(d->backBuf);
|
||||
d->backBuf = NULL;
|
||||
}
|
||||
|
||||
if (d->palette) {
|
||||
free(d->palette);
|
||||
d->palette = NULL;
|
||||
}
|
||||
|
||||
d->lfb = NULL;
|
||||
|
||||
__djgpp_nearptr_disable();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformYield
|
||||
// ============================================================
|
||||
|
||||
void platformYield(void) {
|
||||
__dpmi_yield();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setVesaMode
|
||||
// ============================================================
|
||||
|
||||
static int32_t setVesaMode(uint16_t mode) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F02;
|
||||
r.x.bx = mode | 0x4000; // bit 14 = use LFB
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue