What is Retrograph? ==

Retrograph is a Ruby library which emulates the video display unit from a hypothetical late-80s 8-bit game console. It is similar in capability to the Sega Master System’s VDP, with some additional features and direct support for text modes.

Retrograph doesn’t do its own screen output but rather works with other libraries. Currently, it supports Ruby/SDL, but other output targets are planned for the future.

Where can I get it? ==

Retrograph is available via RubyGems, or via Rubyforge downloads:

http://rubyforge.org/frs/?group_id=8410

Cursory documentation is available on the corresponding github wiki:

http://wiki.github.com/mental/retrograph

Feature Overview ==

* 256x244 pixel display
* 16k VRAM
* 64 colors total
* max 31 simultaneous colors (16 background, 15 sprite)
* 40x25 and 32x28 text modes
* 32x32 tile background, 8x8 pixel tiles
* 64 8x8 sprites, max 8 per scanline

Usage ==

Retrograph’s simulated Video Display Unit (or VDU) is represented by an instance of the Retrograph::VDU class. You can interact with the VDU by writing byte strings into its memory using Retrograph::VDU#write, which takes a start address (in the VDU’s 16-bit address space) and a string to write. The method Retrograph::VDU#write_byte is also provided for writing individual bytes.

(See the end of this document for a map of registers and memory locations within the VDU’s address space.)

To render a frame of video with SDL, use Retrograph::VDU#render_frame_sdl, which returns an SDL::Surface containing the VDU output. The returned surface remains valid until the next call to Retrograph::VDU#render_frame_sdl on the same VDU instance.

Scanline-based effects are possible if you provide a block to Retrograph::VDU#render_frame_sdl. Ordinarily, rendering will not begin until the block completes, but within the block you can call Retrograph::VDU#wait_scanlines to render a given number of scanlines before continuing. This allows you to make changes to the VDU state part-way through rendering a frame.

For example, if we wanted palette entry 0 to be blue within the top half of the display and red within the bottom half of the display, we could write:

require 'retrograph/sdl'

vdu = Retrograph::VDU.new
vdu.write_byte(0x7f00, 0x03)
output = vdu.render_frame_sdl do
  vdu.wait_scanlines(Retrograph::DISPLAY_HEIGHT/2)
  vdu.write_byte(0x7f00, 0x30)
end

(It is still necessary at this point to copy output to the screen.)

Important Notes ==

Nothing is displayed by default. To be shown, the background layer (and the sprite, in graphical modes) must be explicitly enabled by setting bit 2 (bit 3 for sprites) of register $7FF8.

Patterns and name tables may overlap in memory (which is in fact unavoidable in graphics modes); typically you cannot have simultaneously valid pattern and name data at the same location, so in such cases you will need to sacrifice particular name or pattern entries.

In text modes, you will need to load your own font into the pattern table; the VDU does not provide one by default.

Sprites patterns are always 4bpp, even in the 2bpp mode. In Graphics A, one half of VRAM is occupied by 512 2bpp patterns (for the background), and the other half by 256 4bpp patterns for sprites. In Graphics B, the entirety of VRAM is occupied by just 512 4bpp patterns, with the upper half of those available to sprites.

When scrolling horizontally, the leftmost background column will not be displayed if it is partway offscreen, a (mis)feature shared with the Sega Master System.

Unlike most authentic 8-bit systems, any register or memory location may be changed in between scanlines, including the vertical scroll register and even the video mode. This permits fairly powerful split-screen effects.

Also unlike most authentic 8-bit systems, Retrograph uses a packed rather than a planar representation for pattern data. Additionally, within each byte of pattern data, the leftmost pixels are in the least significant position, which may be backwards from what you expect.

Lastly, unlike a real 8-bit system, there is no deadline imposed on code running in between scanlines or in between frames. On real hardware, if you have code running in between scanlines (that is, during horizontal retrace) you will only have a short period of time available before the next scanline will begin rendering, whether or not you are finished what you are doing. However, such a limitation would be very difficult to recreate in Ruby.

VDU Memory Map ==

$0000 - $3FFF: VRAM (16 kbytes) $4000 - $7DFF: VRAM (mirror) (16 kbytes - 512 bytes) $7E00 - $7EFF: Sprite Table* (256 bytes) $7F00 - $7F0F: BG Palette (16 bytes) $7F10 - $7F1F: BG/Sprite Palette* (16 bytes) $7F20 - $7FF7: unused (216 bytes) $7FF8 - $7FFF: VDU Registers (8 bytes)

$7FF8: Flags
       4-7: unused
       3: Enable Sprites*
       2: Enable BG
       0-1: Mode
              00 - Text A (40x25)
              01 - Text B (32x28)
              10 - Graphics A (2bpp)
              11 - Graphics B (4bpp)
$7FF9: Pattern Table Base
       Text A + B:
         addr = (base & 0b00110000) << 8
         addr may be one of:
           $0000, $1000, $2000, or $3000
       Graphics A + B:
         addr = (base & 0b00100000) << 8
         addr may be one of:
           $0000 or $2000
$7FFA: Name Table Base
       addr = ((base & 0b00110000) |
               0b00001000) << 8
       addr may be one of:
         $0800, $1800, $2800, or $3800
$7FFB: unused
$7FFC: BG scroll X*
$7FFD: BG scroll Y**
$7FFE-7FFF: unused

$8000 - $FFFF: mirror of $0000 - $7FFF

Notes:

*Ignored in text modes
**In text modes, the lower 3 bits of the vertical scroll
  register are ignored and it wraps at 200 (Text A) or
  224 (Text B)

Pattern Table ===

The starting address of the pattern table is determined by register $7FF9.

The rows constituting each pattern in the table are given in sequence from top to bottom. Within each byte, the leftmost pixel is in the least significant position. For example, for a 4bpp tile, the following row would display with the pixels of color 1 on the left side, and the pixels of color 0 on the right side:

0x00001111

(little-endian byte ordering is assumed)

Different display modes have different pattern bit depths:

Text A: 256 patterns * 8 bytes per pattern = 2k

6x8 pixel patterns
1 byte per pattern row, 1 bit per pixel
most significant 2 bits ignored

Text B: 256 patterns * 8 bytes per pattern = 2k

8x8 pixel patterns
1 byte per patern row, 1 bit per pixel

Graphics A: 512 bg patterns * 16 bytes per pattern +

256 sprite patterns * 32 bytes/pattern = 16k
8x8 pixel patterns
2 bytes/pattern row, 2 bits/pixel (bg)
4 bytes/pattern row, 4 bits/pixel (sprites)

Graphics B: 512 patterns * 32 bytes per pattern = 16k

8x8 pixel patterns
4 bytes/pattern row, 4 bits/pixel
last 256 patterns shared with sprites

Name Table ===

The starting address of the name table is determined by register $7FFA.

Table sizes:

Text A: 40x25 characters * 2 bytes/character = 2000 bytes
Text B: 32x28 characters * 2 bytes/character = 1792 bytes
Graphics A + B: 32x32 tiles * 2 bytes per tile = 2k

Cell formats:

Text: pppppppp bbbbffff
  p - pattern index
  f - foreground color
  b - background color

Graphics: pppppppp -bhvcccP
  p - low byte of pattern index
  P - high bit of pattern index
  c - high bits of color
  h - horizontal flip
  v - vertical flip
  b - background priority

In graphics modes, the color is computed by taking the high color bits and oring them with the pattern color to get a color in the range 0-31 (with the upper 16 colors coming from the sprite palette):

color = pattern_color | (high_bits << 2)

(However, a pattern color of 0 is always transparent regardless of the high color bits.)

Sprite Table ===

Sprite patterns start 2048 bytes after the pattern table base address specified in register $7FF9. The sprite table itself always begins at $7E00.

4 bytes * 64 sprites = 256 bytes

xxxxxxxx yyyyyyyy pppppppp –hvHVcc

x - X position (left edge)
y - Y position + 1 (top edge) (0 = hidden)
p - pattern index
h - horizontal flip
v - vertical flip
H - double size horizontally
V - double size vertically
c - high bits of color

Sprite colors are computed as follows:

color = pattern_color | (high_bits << 2) | 16;

(As with tiles, a pattern color of 0 is always transparent.)

Palette Table ===

The palette table always begins at $7F00.

1 byte * 32 colors (16 bg, 16 bg/sprite)

–rrggbb

r - red g - green b - blue

Note that the first sprite color (color 16) is effectively never used.