Latest commit 96eb5cd
Jun 1, 2014
|Failed to load latest commit information.|
== 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. 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 most significant bits contribute to the leftmost pixels. For example: 0x10110000 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) --rrggbb r - red g - green b - blue Note that the first sprite color (color 16) is effectively never used.