160 lines
6.7 KiB
C
160 lines
6.7 KiB
C
// dvx_video.c -- Layer 1: Video backend for DVX 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).
|
|
//
|
|
// This is the lowest layer of the 5-layer DVX compositor stack
|
|
// (video -> draw -> comp -> wm -> app). It owns the DisplayT context
|
|
// that threads through every layer above: screen dimensions, pixel
|
|
// format, the backbuffer pointer, and the current clip rectangle.
|
|
//
|
|
// Design decisions at this layer:
|
|
//
|
|
// LFB-only (no bank switching): VBE 2.0 LFB gives the CPU a flat
|
|
// address window over the entire framebuffer. Bank switching (the
|
|
// VBE 1.x fallback) requires an INT 10h or port-I/O call every 64 KB,
|
|
// which is lethal to throughput in a compositor that redraws hundreds
|
|
// of dirty rects per frame. Requiring LFB eliminates per-scanline
|
|
// bank math, allows rep movsd bulk copies, and simplifies every span
|
|
// operation in the draw layer. The tradeoff is dropping cards that
|
|
// only expose banked modes -- acceptable since the target is 486+ with
|
|
// a VESA 2.0 BIOS (virtually universal by 1994-95).
|
|
//
|
|
// System-RAM backbuffer with dirty-rect flushing: Drawing directly
|
|
// to the LFB is slow because VESA framebuffers sit behind the PCI/ISA
|
|
// bus and every write-combine flush costs dozens of cycles. Reads from
|
|
// LFB are catastrophically slow (uncached MMIO). Instead, all draw
|
|
// operations target a malloc'd backbuffer in system RAM, which lives
|
|
// in the CPU's L1/L2 cache hierarchy. The compositor (layer 3) then
|
|
// copies only the dirty rectangles to the LFB with rep movsd, making
|
|
// the bus transfer a single sequential burst per rect. This also
|
|
// means overdraw from occluded windows never touches the bus at all.
|
|
//
|
|
// Clip rectangle on DisplayT: Rather than passing a clip rect to
|
|
// every draw call (4 extra parameters, extra register pressure on
|
|
// i386 calling convention where only a few args go in registers), the
|
|
// clip rect is set once on DisplayT before a batch of draw calls. The
|
|
// window manager sets it to the current window's content area before
|
|
// calling onPaint, and the compositor sets it to each dirty rect during
|
|
// the composite pass. This keeps the draw API narrow and the common
|
|
// case (many draws within the same clip) free of redundant parameter
|
|
// shuffling.
|
|
|
|
#include "dvxVideo.h"
|
|
#include "platform/dvxPlatform.h"
|
|
#include "dvxPalette.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
// ============================================================
|
|
// packColor
|
|
// ============================================================
|
|
//
|
|
// Converts an 8-bit-per-channel RGB triple into whatever pixel
|
|
// representation the current display mode uses. The result is
|
|
// always returned as a uint32_t regardless of actual pixel width,
|
|
// so callers can store it and pass it through the draw layer without
|
|
// caring about the active format.
|
|
//
|
|
// For 8-bit indexed mode, this falls through to dvxNearestPalEntry
|
|
// which searches the 6x6x6 color cube, grey ramp, and chrome slots.
|
|
// For direct-color modes (15/16/32-bit), we right-shift each channel
|
|
// to discard the low bits that don't fit in the mode's color field,
|
|
// then left-shift into position. This approach truncates rather than
|
|
// rounds, which is a deliberate simplicity tradeoff -- dithering or
|
|
// rounding would add per-pixel branches on a 486 for minimal visual
|
|
// benefit in a desktop GUI.
|
|
|
|
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b) {
|
|
if (d->format.bitsPerPixel == 8) {
|
|
return dvxNearestPalEntry(d->palette, r, g, b);
|
|
}
|
|
|
|
// Shift each channel: first discard the low bits that won't fit in
|
|
// the mode's field width (e.g. 8-bit red -> 5-bit red for 565),
|
|
// then shift left to the field's position within the pixel word.
|
|
uint32_t rv = ((uint32_t)r >> (8 - d->format.redBits)) << d->format.redShift;
|
|
uint32_t gv = ((uint32_t)g >> (8 - d->format.greenBits)) << d->format.greenShift;
|
|
uint32_t bv = ((uint32_t)b >> (8 - d->format.blueBits)) << d->format.blueShift;
|
|
|
|
return rv | gv | bv;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// resetClipRect
|
|
// ============================================================
|
|
//
|
|
// Restores the clip rect to the full screen. Called after the
|
|
// compositor finishes flushing dirty rects, and during init.
|
|
|
|
void resetClipRect(DisplayT *d) {
|
|
d->clipX = 0;
|
|
d->clipY = 0;
|
|
d->clipW = d->width;
|
|
d->clipH = d->height;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// setClipRect
|
|
// ============================================================
|
|
//
|
|
// Sets the active clip rectangle, clamping to screen bounds.
|
|
// The clip rect is stored directly on DisplayT so every draw call
|
|
// in layer 2 can read it without an extra parameter. This is the
|
|
// central mechanism that makes per-window and per-dirty-rect
|
|
// clipping cheap: the WM sets clip to the window's content area
|
|
// before calling onPaint, and the compositor narrows it further
|
|
// to each dirty rect during the composite pass.
|
|
//
|
|
// Coordinates are converted to min/max form internally, clamped,
|
|
// then stored back as x/y/w/h. If the caller passes a rect that
|
|
// lies entirely outside the screen, clipW or clipH will be zero
|
|
// and all subsequent draw calls will trivially reject.
|
|
|
|
void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
|
|
int32_t x2 = x + w;
|
|
int32_t y2 = y + h;
|
|
|
|
if (x < 0) { x = 0; }
|
|
if (y < 0) { y = 0; }
|
|
if (x2 > d->width) { x2 = d->width; }
|
|
if (y2 > d->height) { y2 = d->height; }
|
|
|
|
d->clipX = x;
|
|
d->clipY = y;
|
|
d->clipW = (x2 > x) ? x2 - x : 0;
|
|
d->clipH = (y2 > y) ? y2 - y : 0;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// videoInit
|
|
// ============================================================
|
|
//
|
|
// Thin wrapper that delegates to the platform layer. All the heavy
|
|
// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping,
|
|
// backbuffer allocation -- lives in dvxPlatformDos.c so this file
|
|
// stays portable. The platform layer fills in every field of
|
|
// DisplayT (dimensions, pitch, pixel format, lfb pointer,
|
|
// backBuf pointer, palette, initial clip rect).
|
|
|
|
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
|
return platformVideoInit(d, requestedW, requestedH, preferredBpp);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// videoShutdown
|
|
// ============================================================
|
|
//
|
|
// Restores text mode, frees the backbuffer and palette, unmaps the
|
|
// LFB, and disables DJGPP near pointers. Must be called before
|
|
// exit or the console will be left in graphics mode.
|
|
|
|
void videoShutdown(DisplayT *d) {
|
|
platformVideoShutdown(d);
|
|
}
|