// 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 // ============================================================ // 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); }