Skip to content


Subversion checkout URL

You can clone with
Download ZIP
8-bit style graphics library
C Ruby JavaScript
Failed to load latest commit information.
ext/retrograph minor clean-ups
lib move javascript to js/ directory
spec straighten out endianness and sprite rasterization
src straighten out endianness and sprite rasterization
README update readme
Rakefile bump major version for very incompatible change


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

== Where can I get it? ==

Retrograph is available via RubyGems, or via Rubyforge

Cursory documentation is available on the
corresponding github wiki:

== 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 =
 vdu.write_byte(0x7f00, 0x03)
 output = vdu.render_frame_sdl do
   vdu.write_byte(0x7f00, 0x30)

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

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

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

  *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
most significant bits contribute to the leftmost pixels.

For example:


In a 4bpp mode where each byte determines the color for two
adjacent pixels, the left pixel has color 0x1011, and the
right pixel has color 0x0000.

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)


r - red
g - green
b - blue

Note that the first sprite color (color 16) is effectively
never used.
Something went wrong with that request. Please try again.