Video Subsystem - CRT Basic
Table of Contents
1. The virtual VGA
Everything a BASIC program draws lands on a virtual VGA card
(package eu.svjatoslav.crtbasic.video). The card is modeled after
real hardware closely enough that classic programs behave the way
they were written to behave: palette tricks work, text erases the
pixels under it, page flipping is flicker-free, and the full video
memory can be dumped byte-for-byte for external inspection.
| Class | Role |
|---|---|
VgaDevice |
Facade the interpreter calls: modes, text, drawing, palette, pages |
ScreenMode |
Table of modes 0–13: resolution, colors, pages, glyph height |
Framebuffer |
Indexed pixels (one byte = one palette index), pages, PNG/raw dumps |
TextConsole |
PRINT / LOCATE / CLS / VIEW PRINT on top of the framebuffer |
VgaFont |
CP437 bitmap fonts (8x8, 8x14, 8x16), 256 glyphs each |
DefaultPalettes |
The 16 EGA colors and the mode-13h BIOS-style palette |
CapturedRegion |
What GET stores and PUT blits |
2. Screen modes
SCREEN n switches the card between modes. Every graphics mode
doubles as a text mode — characters are blitted as 8-pixel-wide
glyph cells into the same framebuffer that pixel primitives draw
into.
| SCREEN | Pixels | Colors | Pages | Glyph cell | Text grid |
|---|---|---|---|---|---|
| 0 | 640x400 | 16 | 1 | 8x16 | 80x25 |
| 1 | 320x200 | 4 | 1 | 8x8 | 40x25 |
| 2 | 640x200 | 2 | 1 | 8x8 | 80x25 |
| 7 | 320x200 | 16 | 8 | 8x8 | 40x25 |
| 8 | 640x200 | 16 | 4 | 8x8 | 80x25 |
| 9 | 640x350 | 16 | 2 | 8x14 | 80x25 |
| 10 | 640x350 | 4 | 2 | 8x14 | 80x25 |
| 11 | 640x480 | 2 | 1 | 8x16 | 80x30 |
| 12 | 640x480 | 16 | 1 | 8x16 | 80x30 |
| 13 | 320x200 | 256 | 1 | 8x8 | 40x25 |
Switching modes reallocates video memory, resets the palette to the
mode defaults and clears the screen. After SCREEN, the default text
foreground is 7 (light gray) in SCREEN 0 and 15 (bright white) in
graphics modes — a small detail real programs visibly depend on.
Modes 3–6 do not exist in this dialect's target hardware and raise an error.
3. Indexed pixels and palettes
The framebuffer stores palette indices, not colors: one byte per pixel, exactly like mode 13h video memory. Indices resolve to RGB only when a frame is displayed or dumped to PNG. This single decision makes the classic effects work for free:
PALETTE(via the DAC ports, see below) remaps an entry and every pixel using it changes color on the next frame.- Color cycling — rotating a range of palette entries for cheap animation — needs no pixel writes at all.
The default palette holds the 16 standard EGA colors in entries 0–15. SCREEN 13 additionally gets a BIOS-style 256-color extension: a grayscale ramp followed by hue wheels at decreasing saturation. SCREEN 1 is the exception: being a CGA mode, it starts on CGA palette 1 high intensity (cyan / magenta / bright white).
3.1. The DAC ports
Programs of the era often skip PALETTE and poke the VGA DAC
directly:
OUT &H3C8, index ' select palette entry OUT &H3C9, red ' 0-63 per component OUT &H3C9, green OUT &H3C9, blue
OUT to port &H3C8 selects the color index; three writes to
&H3C9 supply red, green, blue (6 bits each, scaled to 0–255). The
index auto-increments after each complete color, exactly like
hardware. All other ports are out of scope and ignored.
4. Text is pixels
There is no separate text plane. TextConsole blits CP437 glyph
bitmaps straight into the pixel framebuffer — the same thing the BIOS
does on real hardware. Consequences that fall out naturally:
PRINTworks identically in text and graphics modes.- The whole glyph cell is painted: stroke pixels in the foreground color, all other pixels in the background color, in every mode. Printing spaces therefore erases text even over a drawn scene — the standard technique for removing a score display before reprinting it.
- Scrolling is a pixel-copy of the scroll region shifted up by one cell height; the freed bottom line is filled with the background color.
Cursor positions are 1-based (LOCATE row, col, CSRLIN, POS(0)).
The console reproduces the classic bottom-right corner behavior:
printing into the last cell of the screen does not scroll by itself —
the cursor parks on the final column and wraps only when the next
character arrives.
VIEW PRINT top TO bottom confines printing and scrolling to a band
of text rows and homes the cursor into the band.
The fonts are CP437 bitmap fonts (256 glyphs each, one byte per glyph scanline) in the three hardware heights 8, 14 and 16, extracted from the public-domain VGA fonts shipped with SeaBIOS and embedded as jar resources.
5. Video pages
SCREEN 7, 8, 9 and 10 have multiple video pages. Two page selectors are independent:
- The active page is what drawing and printing target
(
SCREEN ,,apageor the active-page argument). - The visual page is what the monitor (and screenshots) show.
The classic flicker-free animation loop draws the next frame on a
hidden page and flips the visual page when done. PCOPY source,
target copies a whole page instantly — the standard way to stamp a
pre-drawn background before each frame.
6. Drawing primitives
All drawing targets the active page and updates the graphics cursor (the "last point referenced"), which relative coordinates build on.
| Primitive | Statement form |
|---|---|
pset / point |
PSET (x,y), c / POINT(x,y) |
line |
LINE (x1,y1)-(x2,y2), c |
lineBox / lineBoxFilled |
…, c, B / …, c, BF |
circle |
CIRCLE (x,y), r, c, , , aspect |
paint |
PAINT (x,y), fill, border |
getRegion / putRegion |
GET / PUT with PSET, XOR, OR, AND |
6.1. LINE is pixel-exact
LINE reproduces the original rasterizer bit-for-bit:
- The segment is clipped to the screen before rasterizing (Liang–Barsky). The clipped endpoint's off-axis coordinate rounds to nearest, with exact .5 ties going toward the surviving part of the line. This differs from per-pixel clipping for lines that leave the screen.
- The walk is a DDA along the major axis with an initial error bias
of
floor(3M/4)(or its complement when stepping left), which makes a reversed endpoint order plot the identical pixel set. - Both endpoints are drawn.
Boutlines,BFfills inclusive corners —LINE (x,y)-(x+1,y+1),c,BFis the era's "magnify pixel" idiom and covers exactly 2x2 pixels.
6.2. CIRCLE is pixel-exact
CIRCLE draws axis-aligned ellipses. With no aspect given, the
mode's default aspect (4/3 corrected for the pixel resolution)
makes circles look round on the period-correct display. An explicit
aspect below 1 makes radius the x radius; 1 or more makes it the y
radius.
Rasterization reproduces the classic pixel output exactly: the ellipse is computed in "circle space" and mapped through the aspect as the union of two scan passes, mirrored to four quadrants. All rounding is half-up and the aspect value is SINGLE precision — both details are load-bearing. Arcs (start/end angles) are parsed but not yet implemented and raise an explicit error.
6.3. PAINT, GET and PUT
PAINT flood-fills the contiguous region of the start pixel's color,
stopping at the border color; painting a pixel that already is the
border color does nothing.
GET captures a screen rectangle into a variable; PUT blits it
back with an action: PSET overwrites, XOR / OR / AND combine
palette indices bitwise. (The original packs the image into a numeric
array in a binary format; since arrays never leave this interpreter,
the capture rides in the variable slot as a dedicated object —
GET/PUT round-trips are pixel-identical.)
7. Reading the screen back
Because the whole point is agent-observable output:
dumpPng(path)writes the visual page as a PNG — the headless equivalent of looking at the monitor (the--screenshotCLI option).pageData(page)returns the raw indexed pixels of a page, row-major — the exact equivalent of aBSAVEof mode-13h video memory, for byte-level comparison (the--rawCLI option).toImage(page)converts a page to an AWT image for the Swing frontend.