retrograph 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/README +248 -0
  2. data/Rakefile +54 -0
  3. data/ext/extconf.rb +3 -0
  4. data/ext/retrograph.c +275 -0
  5. data/spec/image-specs/color-blue-bits.case +2 -0
  6. data/spec/image-specs/color-blue-bits.png +0 -0
  7. data/spec/image-specs/color-green-bits.case +2 -0
  8. data/spec/image-specs/color-green-bits.png +0 -0
  9. data/spec/image-specs/color-red-bits.case +2 -0
  10. data/spec/image-specs/color-red-bits.png +0 -0
  11. data/spec/image-specs/default-screen-black.case +1 -0
  12. data/spec/image-specs/default-screen-black.png +0 -0
  13. data/spec/image-specs/default-screen-color-0.case +2 -0
  14. data/spec/image-specs/default-screen-color-0.png +0 -0
  15. data/spec/image-specs/scroll-mode1.case +7 -0
  16. data/spec/image-specs/scroll-mode1.png +0 -0
  17. data/spec/image-specs/scroll-mode2.case +10 -0
  18. data/spec/image-specs/scroll-mode2.png +0 -0
  19. data/spec/image-specs/scroll-mode3.case +10 -0
  20. data/spec/image-specs/scroll-mode3.png +0 -0
  21. data/spec/image-specs/sprites-doubling.case +5 -0
  22. data/spec/image-specs/sprites-doubling.png +0 -0
  23. data/spec/image-specs/sprites-mode2.case +8 -0
  24. data/spec/image-specs/sprites-mode2.png +0 -0
  25. data/spec/image-specs/sprites-mode3.case +8 -0
  26. data/spec/image-specs/sprites-mode3.png +0 -0
  27. data/spec/image-specs/sprites-ordering.case +5 -0
  28. data/spec/image-specs/sprites-ordering.png +0 -0
  29. data/spec/image-specs/text-colors.case +5 -0
  30. data/spec/image-specs/text-colors.png +0 -0
  31. data/spec/image-specs/tile-attributes-mode2.case +9 -0
  32. data/spec/image-specs/tile-attributes-mode2.png +0 -0
  33. data/spec/image-specs/tile-attributes-mode3.case +9 -0
  34. data/spec/image-specs/tile-attributes-mode3.png +0 -0
  35. data/spec/retrograph_spec.rb +133 -0
  36. data/src/retrograph.c +695 -0
  37. data/src/retrograph.h +66 -0
  38. metadata +103 -0
data/README ADDED
@@ -0,0 +1,248 @@
1
+ == What is Retrograph? ==
2
+
3
+ Retrograph is a Ruby library which emulates the video
4
+ display unit from a hypothetical late-80s 8-bit game
5
+ console. It is similar in capability to the Sega Master
6
+ System's VDP, with some additional features and direct
7
+ support for text modes.
8
+
9
+ Retrograph requires Ruby/SDL and outputs to an SDL
10
+ surface.
11
+
12
+ == Feature Overview ==
13
+
14
+ * 256x244 pixel display
15
+ * 16k VRAM
16
+ * 40x25 and 32x28 text modes
17
+ * 2bpp and 4bpp graphics modes
18
+ * 64 colors total
19
+ * 31 simultaneous colors (16 background, 15 sprite)
20
+ * 32x32 background layer
21
+ * 64 sprites, max 8 per scanline
22
+
23
+ == Usage ==
24
+
25
+ Retrograph's simulated Video Display Unit (or VDU) is
26
+ represented by an instance of the Retrograph::VDU class.
27
+ You can interact with the VDU by writing byte strings
28
+ into its memory using Retrograph::VDU#write, which takes
29
+ a start address (in the VDU's 16-bit address space) and
30
+ a string to write.
31
+
32
+ To render a frame of video, use
33
+ Retrograph::VDU#render_frame. The SDL surface which
34
+ serves as an output buffer is available via
35
+ Retrograph::VDU#surface.
36
+
37
+ Scanline-based effects are possible if you provide a block
38
+ to Retrograph::VDU#render_frame and call
39
+ Retrograph::VDU#render_scanlines to render scanlines in
40
+ between the execution of Ruby code. For example, if we
41
+ wanted palette entry 0 to be blue within the top half of
42
+ the display and red within the bottom half of the display,
43
+ we could write:
44
+
45
+ vdu.render_frame do
46
+ vdu.write(0x7f00, "\x03")
47
+ vdu.render_scanlines(Retrograph::DISPLAY_HEIGHT/2)
48
+ vdu.write(0x7f00, "\x30")
49
+ end
50
+
51
+ The integer passed to Retrograph::VDU#render_scanlines is
52
+ an integer count of scanlines to render; each call renders
53
+ an additional number of scanlines. Any remaining scanlines
54
+ not rendered by calls to Retrograph::VDU#render_scanlines
55
+ are rendered by Retrograph::VDU#render_frame when the block
56
+ completes.
57
+
58
+ See the end of this document for a map of registers and
59
+ memory locations within the VDU's address space.
60
+
61
+ == Important Notes ==
62
+
63
+ Both background (and sprites, in graphics modes) must be
64
+ explicitly enabled by setting bits 2 (and 3) of register
65
+ $7FF8 or else they will not be displayed.
66
+
67
+ Patterns and name tables may overlap in memory (they must
68
+ do so in graphics modes); typically you cannot have
69
+ simultaneously valid pattern and name data at the same
70
+ location, so in those cases you will need to sacrifice
71
+ particular name or pattern entries.
72
+
73
+ Unlike most authentic 8-bit systems, any register or memory
74
+ location may be changed in between scanlines, including the
75
+ vertical scroll register and even the video mode. This
76
+ permits fairly powerful split-screen effects, and in some
77
+ cases may be used to exceed simultaneous color, sprite, or
78
+ pattern limitations.
79
+
80
+ In text modes, you will need to load your own font into the
81
+ pattern table; the VDU does not provide one by default.
82
+
83
+ Also unlike most authentic 8-bit systems, Retrograph uses a
84
+ packed rather than a planar representation for pattern
85
+ data. Additionally, within each byte of pattern data, the
86
+ leftmost pixels are in the least significant position,
87
+ which may be backwards from what you expect.
88
+
89
+ Sprites patterns are always 4bpp, even in the 2bpp mode.
90
+ In Graphics A, one half of VRAM is occupied by 512 2bpp
91
+ patterns (for the background), and the other half by 256
92
+ 4bpp patterns for sprites. In Graphics B, the entirety
93
+ of VRAM is occupied by just 512 4bpp patterns, with the
94
+ upper half of those available to sprites.
95
+
96
+ When scrolling horizontally, the leftmost background
97
+ column will not be displayed if it is partway offscreen.
98
+
99
+ == VDU Memory Map ==
100
+
101
+ $0000 - $3FFF: VRAM (16 kbytes)
102
+ $4000 - $7DFF: VRAM (mirror) (16 kbytes - 512 bytes)
103
+ $7E00 - $7EFF: Sprite Table* (256 bytes)
104
+ $7F00 - $7F0F: BG Palette (16 bytes)
105
+ $7F10 - $7F1F: BG/Sprite Palette* (16 bytes)
106
+ $7F20 - $7FF7: unused (216 bytes)
107
+ $7FF8 - $7FFF: VDU Registers (8 bytes)
108
+ $7FF8: Flags
109
+ 4-7: unused
110
+ 3: Enable Sprites*
111
+ 2: Enable BG
112
+ 0-1: Mode
113
+ 00 - Text A (40x25)
114
+ 01 - Text B (32x28)
115
+ 10 - Graphics A (2bpp)
116
+ 11 - Graphics B (4bpp)
117
+ $7FF9: Pattern Table Base
118
+ Text A + B:
119
+ addr = (base & 0b00110000) << 8
120
+ base = $00, $10, $20, or $30
121
+ addr = $0000, $1000, $2000, or
122
+ $3000
123
+ Graphics A + B:
124
+ addr = (base & 0b00100000) << 8
125
+ base = $00 or $20
126
+ addr = $0000 or $2000
127
+ $7FFA: Name Table Base
128
+ addr = ((base & 0b00110000) |
129
+ 0b00001000) << 8
130
+ base = $00, $10, $20, or $30
131
+ addr = $0800, $1800, $2800, or
132
+ $3800
133
+ $7FFB: unused
134
+ $7FFC: BG scroll X*
135
+ $7FFD: BG scroll Y**
136
+ $7FFE-7FFF: unused
137
+ $8000 - $FFFF: mirror of $0000 - $7FFF
138
+
139
+ Notes:
140
+ *Ignored in text modes
141
+ **In text modes, the lower 3 bits of the vertical scroll
142
+ register are ignored and it wraps at 200 (Text A) or
143
+ 224 (Text B)
144
+
145
+ === Pattern Table ===
146
+
147
+ The starting address of the pattern table is determined by
148
+ register $7FF9.
149
+
150
+ The rows constituting each pattern in the table are given
151
+ in sequence from top to bottom. Within each byte, the
152
+ leftmost pixel is in the least significant position.
153
+
154
+ Text A: 256 patterns * 8 bytes per pattern = 2k
155
+ 6x8 pixel patterns
156
+ 1 byte per pattern row, 1 bit per pixel
157
+ most significant 2 bits ignored
158
+
159
+ Text B: 256 patterns * 8 bytes per pattern = 2k
160
+ 8x8 pixel patterns
161
+ 1 byte per patern row, 1 bit per pixel
162
+
163
+ Graphics A: 512 bg patterns * 16 bytes per pattern +
164
+ 256 sprite patterns * 32 bytes/pattern = 16k
165
+ 8x8 pixel patterns
166
+ 2 bytes/pattern row, 2 bits/pixel (bg)
167
+ 4 bytes/pattern row, 4 bits/pixel (sprites)
168
+
169
+ Graphics B: 512 patterns * 32 bytes per pattern = 16k
170
+ 8x8 pixel patterns
171
+ 4 bytes/pattern row, 4 bits/pixel
172
+ last 256 patterns shared with sprites
173
+
174
+ === Name Table ===
175
+
176
+ The starting address of the name table is determined by
177
+ register $7FFA.
178
+
179
+ Table sizes:
180
+
181
+ Text A: 40x25 characters * 2 bytes/character = 2000 bytes
182
+ Text B: 32x28 characters * 2 bytes/character = 1792 bytes
183
+ Graphics A + B: 32x32 tiles * 2 bytes per tile = 2k
184
+
185
+ Cell formats:
186
+
187
+ Text: pppppppp bbbbffff
188
+ p - pattern index
189
+ f - foreground color
190
+ b - background color
191
+
192
+ Graphics: pppppppp -bhvcccP
193
+ p - low byte of pattern index
194
+ P - high bit of pattern index
195
+ c - high bits of color
196
+ h - horizontal flip
197
+ v - vertical flip
198
+ b - background priority
199
+
200
+ In graphics modes, the color is computed by taking the high
201
+ color bits and oring them with the pattern color to get a
202
+ color in the range 0-31 (with the upper 16 colors coming
203
+ from the sprite palette):
204
+
205
+ color = pattern_color | (high_bits << 2)
206
+
207
+ (However, a pattern color of 0 is always transparent
208
+ regardless of the high color bits.)
209
+
210
+ === Sprite Table ===
211
+
212
+ Sprite patterns start 2k bytes after the pattern table base
213
+ address specified in register $7FF9. The sprite table
214
+ itself always begins at $7E00.
215
+
216
+ 4 bytes * 64 sprites = 256 bytes
217
+
218
+ xxxxxxxx yyyyyyyy pppppppp --hvHVcc
219
+
220
+ x - X position (left edge)
221
+ y - Y position + 1 (top edge) (0 = hidden)
222
+ p - pattern index
223
+ h - horizontal flip
224
+ v - vertical flip
225
+ H - double size horizontally
226
+ V - double size vertically
227
+ c - high bits of color
228
+
229
+ Sprite colors are computed as follows:
230
+
231
+ color = pattern_color | (high_bits << 2) | 16;
232
+
233
+ (As with tiles, a pattern color of 0 is always transparent.)
234
+
235
+ === Palette Table ===
236
+
237
+ The palette table always begins at $7F00.
238
+
239
+ 1 byte * 32 colors (16 bg, 16 bg/sprite)
240
+
241
+ --rrggbb
242
+
243
+ r - red
244
+ g - green
245
+ b - blue
246
+
247
+ Note that the first sprite color (color 16) is effectively
248
+ never used.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ require 'spec/rake/spectask'
4
+ require 'rake/clean'
5
+
6
+ GEM_VERSION = '0.2.1'
7
+
8
+ CLEAN.include("**/*.o")
9
+ CLEAN.include("**/*.so")
10
+ CLEAN.include("spec/**/*-output.bmp")
11
+ CLOBBER.include("ext/Makefile")
12
+
13
+ task :clobber => [:clean]
14
+
15
+ Spec::Rake::SpecTask.new do |task|
16
+ task.ruby_opts << '-rrubygems'
17
+ task.libs << 'ext'
18
+ task.spec_files = FileList["spec/**/*_spec.rb"]
19
+ end
20
+
21
+ task :build do
22
+ sh "cd ext && #{Gem.ruby} extconf.rb && make"
23
+ end
24
+
25
+ gemspec = Gem::Specification.new do |gemspec|
26
+ gemspec.name = "retrograph"
27
+ gemspec.version = GEM_VERSION
28
+ gemspec.author = "MenTaLguY <mental@rydia.net>"
29
+ gemspec.summary = "Retrograph, a retrodisplay."
30
+ gemspec.description = <<EOS
31
+ Retrograph is a software scanline renderer which simulates a late-era 8-bit display.
32
+ Its graphical capabilities are similar to those of the Sega Master System.
33
+ EOS
34
+ gemspec.homepage = "http://rubyforge.org/projects/retrograph"
35
+ gemspec.email = "mental@rydia.net"
36
+ gemspec.rubyforge_project = 'retrograph'
37
+ gemspec.files = FileList['Rakefile', 'README',
38
+ 'src/retrograph.h', 'src/retrograph.c',
39
+ 'ext/extconf.rb', 'ext/retrograph.c',
40
+ 'spec/**/*_spec.rb', 'spec/**/*.case',
41
+ 'spec/**/*.png']
42
+ gemspec.extensions = ['ext/extconf.rb']
43
+ gemspec.require_paths = ['ext']
44
+ gemspec.platform = Gem::Platform::RUBY
45
+ gemspec.add_dependency("rubysdl", ">= 1.3.0")
46
+ end
47
+
48
+ Rake::GemPackageTask.new(gemspec) do |pkg|
49
+ pkg.need_tar = true
50
+ end
51
+
52
+ task :spec => [:build]
53
+ task :package => [:clean, :spec]
54
+ task :default => [:clean, :spec]
data/ext/extconf.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('retrograph')
data/ext/retrograph.c ADDED
@@ -0,0 +1,275 @@
1
+ /* retrograph
2
+ *
3
+ * Copyright (c) 2009 MenTaLguY <mental@rydia.net>
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in
13
+ * all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ * THE SOFTWARE.
22
+ */
23
+ #include <ruby.h>
24
+ #include <intern.h>
25
+ #include <string.h>
26
+
27
+ #include "../src/retrograph.h"
28
+ #include "../src/retrograph.c"
29
+
30
+ /* First few fields of SDL_Surface */
31
+ typedef struct surface_header_tag {
32
+ uint32_t flags;
33
+ struct pixelformat_tag *format; /* abstract */
34
+ int w, h;
35
+ uint16_t pitch;
36
+ void *pixels;
37
+ } surface_header_t;
38
+
39
+ /* for Ruby/SDL 2.x, which indirectly wraps surfaces */
40
+ typedef struct rubysdl2_surface_tag {
41
+ surface_header_t *surface;
42
+ } rubysdl2_surface_t;
43
+
44
+ typedef struct vdu_wrapper_tag {
45
+ retrograph_vdu_t vdu;
46
+ VALUE surface;
47
+ int scanline;
48
+ } vdu_wrapper_t;
49
+
50
+ #define NOT_IN_FRAME -1
51
+
52
+ static VALUE mRetrograph = Qnil;
53
+ static VALUE cVDU = Qnil;
54
+ static VALUE eRenderError = Qnil;
55
+ static VALUE mSDL = Qnil;
56
+ static VALUE cSurface = Qnil;
57
+ static VALUE CREATE_SURFACE_METHOD_NAME = Qnil;
58
+
59
+ static surface_header_t *get_rubysdl_surface_1_x(VALUE surface_r) {
60
+ surface_header_t *surface;
61
+ Data_Get_Struct(surface_r, surface_header_t, surface);
62
+ return surface;
63
+ }
64
+
65
+ static surface_header_t *get_rubysdl_surface_2_x(VALUE surface_r) {
66
+ rubysdl2_surface_t *surface;
67
+ Data_Get_Struct(surface_r, rubysdl2_surface_t, surface);
68
+ return surface->surface;
69
+ }
70
+
71
+ static surface_header_t *(*get_rubysdl_surface)(VALUE surface_r) = NULL;
72
+
73
+ static void check_errors(retrograph_vdu_t vdu) {
74
+ retrograph_error_t error;
75
+ error = retrograph_get_error(vdu);
76
+ switch (error) {
77
+ case RETROGRAPH_OK:
78
+ return;
79
+ case RETROGRAPH_NO_MEMORY:
80
+ rb_memerror();
81
+ case RETROGRAPH_INVALID_ARGUMENT:
82
+ rb_raise(rb_const_get(rb_cObject, rb_intern("ArgumentError")),
83
+ "Invalid argument");
84
+ default:
85
+ rb_raise(rb_eRuntimeError, "Unknown error");
86
+ }
87
+ }
88
+
89
+ static void mark_vdu(vdu_wrapper_t *vdu) {
90
+ rb_gc_mark(vdu->surface);
91
+ }
92
+
93
+ static void free_vdu(vdu_wrapper_t *vdu) {
94
+ if (vdu->vdu) {
95
+ retrograph_destroy(vdu->vdu);
96
+ }
97
+ xfree(vdu);
98
+ }
99
+
100
+ static VALUE rescue_free_vdu(VALUE data, VALUE exc) {
101
+ free_vdu((vdu_wrapper_t *)data);
102
+ rb_exc_raise(exc);
103
+ return Qnil;
104
+ }
105
+
106
+ static VALUE alloc_vdu2(VALUE data);
107
+
108
+ static VALUE alloc_vdu(VALUE klass) {
109
+ vdu_wrapper_t *vdu;
110
+ VALUE vdu_r;
111
+ vdu = ALLOC(vdu_wrapper_t);
112
+ vdu->vdu = NULL;
113
+ vdu->surface = Qnil;
114
+ vdu->scanline = NOT_IN_FRAME;
115
+ rb_rescue2(alloc_vdu2, (VALUE)vdu,
116
+ rescue_free_vdu, (VALUE)vdu,
117
+ rb_eException, (VALUE)0);
118
+ return Data_Wrap_Struct(klass, mark_vdu, free_vdu, vdu);
119
+ }
120
+
121
+ VALUE alloc_vdu2(VALUE data) {
122
+ vdu_wrapper_t *vdu=(vdu_wrapper_t *)data;
123
+ VALUE surface;
124
+ surface = rb_funcall(cSurface, SYM2ID(CREATE_SURFACE_METHOD_NAME), 8,
125
+ INT2FIX(0), /* flags */
126
+ INT2FIX(RETROGRAPH_DISPLAY_WIDTH),
127
+ INT2FIX(RETROGRAPH_DISPLAY_HEIGHT),
128
+ INT2FIX(RETROGRAPH_BITS_PER_PIXEL),
129
+ INT2FIX(RETROGRAPH_RED_MASK),
130
+ INT2FIX(RETROGRAPH_GREEN_MASK),
131
+ INT2FIX(RETROGRAPH_BLUE_MASK),
132
+ INT2FIX(RETROGRAPH_ALPHA_MASK));
133
+ vdu->surface = surface;
134
+ vdu->vdu = retrograph_new();
135
+ check_errors(vdu->vdu);
136
+ return Qnil;
137
+ }
138
+
139
+ static VALUE vdu_write(VALUE self, VALUE start_address_r, VALUE data_r) {
140
+ vdu_wrapper_t *vdu;
141
+ retrograph_addr_t start_address;
142
+ Check_Type(data_r, T_STRING);
143
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
144
+ start_address = (retrograph_addr_t)NUM2INT(start_address_r);
145
+ retrograph_write(vdu->vdu, start_address, RSTRING(data_r)->ptr,
146
+ (size_t)RSTRING(data_r)->len);
147
+ return Qnil;
148
+ }
149
+
150
+ static VALUE vdu_get_surface(VALUE self) {
151
+ vdu_wrapper_t *vdu;
152
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
153
+ return vdu->surface;
154
+ }
155
+
156
+ static VALUE vdu_render_scanlines(VALUE self, VALUE n_scanlines_r) {
157
+ vdu_wrapper_t *vdu;
158
+ surface_header_t *surface;
159
+ uint8_t *current_line;
160
+ int max_scanline;
161
+
162
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
163
+
164
+ if (vdu->scanline == NOT_IN_FRAME) {
165
+ rb_raise(eRenderError, "Not in frame");
166
+ }
167
+
168
+ surface = get_rubysdl_surface(vdu->surface);
169
+
170
+ max_scanline = (int)NUM2UINT(n_scanlines_r) + vdu->scanline;
171
+ if (max_scanline > RETROGRAPH_DISPLAY_HEIGHT) {
172
+ max_scanline = RETROGRAPH_DISPLAY_HEIGHT;
173
+ }
174
+
175
+ current_line = (uint8_t *)surface->pixels + vdu->scanline * surface->pitch;
176
+ for (; vdu->scanline < max_scanline; vdu->scanline++) {
177
+ retrograph_render_scanline(vdu->vdu, current_line,
178
+ RETROGRAPH_DISPLAY_WIDTH *
179
+ RETROGRAPH_BYTES_PER_PIXEL);
180
+ check_errors(vdu->vdu);
181
+ current_line += surface->pitch;
182
+ }
183
+
184
+ return Qnil;
185
+ }
186
+
187
+ static VALUE vdu_render_frame_inner(VALUE self) {
188
+ vdu_wrapper_t *vdu;
189
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
190
+ retrograph_begin_frame(vdu->vdu);
191
+ check_errors(vdu->vdu);
192
+ vdu->scanline = 0;
193
+ if (rb_block_given_p()) {
194
+ rb_yield(Qundef);
195
+ }
196
+ vdu_render_scanlines(self, INT2FIX(RETROGRAPH_DISPLAY_HEIGHT));
197
+ return Qnil;
198
+ }
199
+
200
+ static VALUE vdu_render_frame_cleanup(VALUE self) {
201
+ vdu_wrapper_t *vdu;
202
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
203
+ vdu->scanline = NOT_IN_FRAME;
204
+ return Qnil;
205
+ }
206
+
207
+ static VALUE vdu_render_frame(VALUE self) {
208
+ vdu_wrapper_t *vdu;
209
+ int block_given;
210
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
211
+ block_given = rb_block_given_p();
212
+ if (vdu->scanline != NOT_IN_FRAME) {
213
+ rb_raise(eRenderError, "Already in frame");
214
+ }
215
+ rb_ensure(vdu_render_frame_inner, self, vdu_render_frame_cleanup, self);
216
+ return vdu->surface;
217
+ }
218
+
219
+ void Init_retrograph(void) {
220
+ VALUE rubysdl_version;
221
+ rb_require("sdl");
222
+
223
+ rb_global_variable(&mSDL);
224
+ mSDL = rb_const_get(rb_cObject, rb_intern("SDL"));
225
+ rb_global_variable(&cSurface);
226
+ cSurface = rb_const_get(mSDL, rb_intern("Surface"));
227
+
228
+ rubysdl_version = rb_const_get(mSDL, rb_intern("VERSION"));
229
+ rb_global_variable(&CREATE_SURFACE_METHOD_NAME);
230
+ if (!strncmp(StringValueCStr(rubysdl_version), "1.", 2)) {
231
+ CREATE_SURFACE_METHOD_NAME = ID2SYM(rb_intern("new"));
232
+ get_rubysdl_surface = get_rubysdl_surface_1_x;
233
+ } else if (!strncmp(StringValueCStr(rubysdl_version), "2.", 2)) {
234
+ CREATE_SURFACE_METHOD_NAME = ID2SYM(rb_intern("createWithFormat"));
235
+ get_rubysdl_surface = get_rubysdl_surface_2_x;
236
+ } else {
237
+ rb_raise(rb_eLoadError, "Unrecognized Ruby/SDL version");
238
+ }
239
+
240
+ rb_global_variable(&mRetrograph);
241
+ mRetrograph = rb_define_module("Retrograph");
242
+
243
+ #define DEFINE_CONSTANT(base_name) \
244
+ rb_const_set(mRetrograph, rb_intern(#base_name), \
245
+ INT2FIX(RETROGRAPH_##base_name))
246
+
247
+ DEFINE_CONSTANT(DISPLAY_WIDTH);
248
+ DEFINE_CONSTANT(DISPLAY_HEIGHT);
249
+ DEFINE_CONSTANT(BYTES_PER_PIXEL);
250
+ DEFINE_CONSTANT(BITS_PER_PIXEL);
251
+
252
+ DEFINE_CONSTANT(RED_SHIFT);
253
+ DEFINE_CONSTANT(GREEN_SHIFT);
254
+ DEFINE_CONSTANT(BLUE_SHIFT);
255
+
256
+ DEFINE_CONSTANT(RED_MASK);
257
+ DEFINE_CONSTANT(GREEN_MASK);
258
+ DEFINE_CONSTANT(BLUE_MASK);
259
+ DEFINE_CONSTANT(ALPHA_MASK);
260
+
261
+ #undef DEFINE_CONSTANT
262
+
263
+ rb_global_variable(&cVDU);
264
+ cVDU = rb_define_class_under(mRetrograph, "VDU", rb_cObject);
265
+
266
+ rb_global_variable(&eRenderError);
267
+ eRenderError = rb_define_class_under(mRetrograph, "RenderError",
268
+ rb_eRuntimeError);
269
+
270
+ rb_define_alloc_func(cVDU, alloc_vdu);
271
+ rb_define_method(cVDU, "write", vdu_write, 2);
272
+ rb_define_method(cVDU, "surface", vdu_get_surface, 0);
273
+ rb_define_method(cVDU, "render_frame", vdu_render_frame, 0);
274
+ rb_define_method(cVDU, "render_scanlines", vdu_render_scanlines, 1);
275
+ }
@@ -0,0 +1,2 @@
1
+ should use color bits 0-1 for blue
2
+ 7f00: 03
@@ -0,0 +1,2 @@
1
+ should use color bits 2-3 for green
2
+ 7f00: 0c
@@ -0,0 +1,2 @@
1
+ should use color bits 4-5 for red
2
+ 7f00: 30
Binary file
@@ -0,0 +1 @@
1
+ should fill the screen with black by default
@@ -0,0 +1,2 @@
1
+ should fill the screen with color 0 by default
2
+ 7f00: ff
@@ -0,0 +1,7 @@
1
+ should support text-style scrolling in mode 1
2
+ 7ff8: 05
3
+ 7ffc: 0c 0c
4
+ 7f00: 00 ff
5
+ 0008: 01 03 07 0f 1f 3f 7f ff ff 7f 3f 1f 0f 07 03 01
6
+ 0800: 0101
7
+ 0840: 0102
Binary file
@@ -0,0 +1,10 @@
1
+ should support smooth scrolling in mode 2
2
+ 7ff8: 06
3
+ 7ffc: 0c 84
4
+ 7f00: 00 ff
5
+ 0010: 0001 0005 0015 0055 0155 0555 1555 5555
6
+ 0020: 5555 1555 0555 0155 0055 0015 0005 0001
7
+ 0030: 1111 4444 1111 4444 1111 4444 1111 4444
8
+ 0c00: 0002
9
+ 0ac0: 0001
10
+ 0b00: 0003
Binary file
@@ -0,0 +1,10 @@
1
+ should support smooth scrolling in mode 3
2
+ 7ff8: 07
3
+ 7ffc: 0c 84
4
+ 7f00: 00 ff
5
+ 0020: 00000001 00000011 00000111 00001111 00011111 00111111 01111111 11111111
6
+ 0040: 11111111 01111111 00111111 00011111 00001111 00000111 00000011 00000001
7
+ 0060: 01010101 10101010 01010101 10101010 01010101 10101010 01010101 10101010
8
+ 0c00: 0002
9
+ 0ac0: 0001
10
+ 0b00: 0003
Binary file
@@ -0,0 +1,5 @@
1
+ should render doubled sprites
2
+ 7ff8: 0b
3
+ 7f10: 00 3f
4
+ 7e00: 00 01 0001 10 01 0801 00 11 0401 10 11 0c01
5
+ 2020: 10101010 01010101 10101010 01010101 10101010 01010101 10101010 01010101
@@ -0,0 +1,8 @@
1
+ should show first 8 sprites per line in mode 2
2
+ 7ff8: 0a
3
+ 7f10: 00 02 08 0a 20 22 38 2a 15 17 1d 1f 35 37 3d 3f
4
+ 7e00: 00 01 0001 08 01 0002 10 01 0101 18 01 0102
5
+ 7e10: 20 01 0201 28 01 0202 30 01 0301 38 01 0302
6
+ 7e20: 40 01 0001 48 01 0002 50 01 0001 58 01 0002
7
+ 2020: 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
8
+ 2040: 20202020 02020202 20202020 02020202 20202020 02020202 20202020 02020202
Binary file
@@ -0,0 +1,8 @@
1
+ should show first 8 sprites per line in mode 3
2
+ 7ff8: 0b
3
+ 7f10: 00 02 08 0a 20 22 38 2a 15 17 1d 1f 35 37 3d 3f
4
+ 7e00: 00 01 0001 08 01 0002 10 01 0101 18 01 0102
5
+ 7e10: 20 01 0201 28 01 0202 30 01 0301 38 01 0302
6
+ 7e20: 40 01 0001 48 01 0002 50 01 0001 58 01 0002
7
+ 2020: 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
8
+ 2040: 20202020 02020202 20202020 02020202 20202020 02020202 20202020 02020202
Binary file