Video Subsystem - CRT Basic

Table of Contents

Back to main documentation

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

Framebuffer (indices) 15 15 1 9 0 15 9 9 1 0 0 0 1 15 15 one byte per pixel = palette index Palette (index → RGB) 0 1 9 15 #000000 #0000AA #00AAAA #FFFFFF PALETTE / OUT &H3C9 remap entries Screen (RGB) indices resolve to colors only at display time

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

PRINT "A" paints the whole 8x8 cell glyph stroke → foreground color everything else → background color There is no transparent text: every cell pixel is written, in every mode. Printing spaces therefore erases whatever was on screen before.

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:

  • PRINT works 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

BASIC program Page 0 Page 1 (active) Page 2 Monitor visual page (0) drawing displayed PCOPY 1, 0: instant page copy draw on a hidden page, flip visual page — flicker-free animation (SCREEN 7–10)

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 ,,apage or 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. B outlines, BF fills inclusive corners — LINE (x,y)-(x+1,y+1),c,BF is 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 --screenshot CLI option).
  • pageData(page) returns the raw indexed pixels of a page, row-major — the exact equivalent of a BSAVE of mode-13h video memory, for byte-level comparison (the --raw CLI option).
  • toImage(page) converts a page to an AWT image for the Swing frontend.

Created: 2026-08-31 ma 01:26

Validate