DVX_GUI/dvx/dvxVideo.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);
}