== 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.