retrograph 0.5-x86-mswin32

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 (42) hide show
  1. data/COPYING +19 -0
  2. data/README +264 -0
  3. data/Rakefile +71 -0
  4. data/ext/retrograph/extconf.rb +6 -0
  5. data/ext/retrograph/retrograph.c +324 -0
  6. data/lib/retrograph/sdl.rb +26 -0
  7. data/lib/retrograph.rb +29 -0
  8. data/lib/retrograph.so +0 -0
  9. data/spec/image-specs/color-blue-bits.case +2 -0
  10. data/spec/image-specs/color-blue-bits.png +0 -0
  11. data/spec/image-specs/color-green-bits.case +2 -0
  12. data/spec/image-specs/color-green-bits.png +0 -0
  13. data/spec/image-specs/color-red-bits.case +2 -0
  14. data/spec/image-specs/color-red-bits.png +0 -0
  15. data/spec/image-specs/default-screen-black.case +1 -0
  16. data/spec/image-specs/default-screen-black.png +0 -0
  17. data/spec/image-specs/default-screen-color-0.case +2 -0
  18. data/spec/image-specs/default-screen-color-0.png +0 -0
  19. data/spec/image-specs/scroll-mode1.case +7 -0
  20. data/spec/image-specs/scroll-mode1.png +0 -0
  21. data/spec/image-specs/scroll-mode2.case +10 -0
  22. data/spec/image-specs/scroll-mode2.png +0 -0
  23. data/spec/image-specs/scroll-mode3.case +10 -0
  24. data/spec/image-specs/scroll-mode3.png +0 -0
  25. data/spec/image-specs/sprites-doubling.case +5 -0
  26. data/spec/image-specs/sprites-doubling.png +0 -0
  27. data/spec/image-specs/sprites-mode2.case +8 -0
  28. data/spec/image-specs/sprites-mode2.png +0 -0
  29. data/spec/image-specs/sprites-mode3.case +8 -0
  30. data/spec/image-specs/sprites-mode3.png +0 -0
  31. data/spec/image-specs/sprites-ordering.case +5 -0
  32. data/spec/image-specs/sprites-ordering.png +0 -0
  33. data/spec/image-specs/text-colors.case +5 -0
  34. data/spec/image-specs/text-colors.png +0 -0
  35. data/spec/image-specs/tile-attributes-mode2.case +9 -0
  36. data/spec/image-specs/tile-attributes-mode2.png +0 -0
  37. data/spec/image-specs/tile-attributes-mode3.case +9 -0
  38. data/spec/image-specs/tile-attributes-mode3.png +0 -0
  39. data/spec/retrograph_spec.rb +134 -0
  40. data/src/retrograph.c +697 -0
  41. data/src/retrograph.h +67 -0
  42. metadata +99 -0
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 MenTaLguY <mental@rydia.net>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,264 @@
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 currently supports Ruby/SDL for output; other
10
+ output targets may be added in the future.
11
+
12
+ == Feature Overview ==
13
+
14
+ * 256x244 pixel display
15
+ * 16k VRAM
16
+ * 64 colors total
17
+ * max 31 simultaneous colors (16 background, 15 sprite)
18
+ * 40x25 and 32x28 text modes
19
+ * 32x32 tile background, 8x8 pixel tiles
20
+ * 64 8x8 sprites, max 8 per scanline
21
+
22
+ == Usage ==
23
+
24
+ Retrograph's simulated Video Display Unit (or VDU) is
25
+ represented by an instance of the Retrograph::VDU class.
26
+ You can interact with the VDU by writing byte strings
27
+ into its memory using Retrograph::VDU#write, which takes
28
+ a start address (in the VDU's 16-bit address space) and
29
+ a string to write. The method Retrograph::VDU#write_byte
30
+ is also provided for writing individual bytes.
31
+
32
+ (See the end of this document for a map of registers and
33
+ memory locations within the VDU's address space.)
34
+
35
+ To render a frame of video with SDL, use
36
+ Retrograph::VDU#render_frame_sdl, which returns an
37
+ SDL::Surface containing the VDU output. The returned
38
+ surface remains valid until the next call to
39
+ Retrograph::VDU#render_frame_sdl on the same VDU instance.
40
+
41
+ Scanline-based effects are possible if you provide a block
42
+ to Retrograph::VDU#render_frame_sdl. Ordinarily, rendering
43
+ will not begin until the block completes, but within the
44
+ block you can call Retrograph::VDU#wait_scanlines to render
45
+ a given number of scanlines before continuing. This allows
46
+ you to make changes to the VDU state part-way through
47
+ rendering a frame.
48
+
49
+ For example, if we wanted palette entry 0 to be blue within
50
+ the top half of the display and red within the bottom half
51
+ of the display, we could write:
52
+
53
+ require 'retrograph/sdl'
54
+
55
+ output = vdu.render_frame_sdl do
56
+ vdu.write_byte(0x7f00, 0x03)
57
+ vdu.wait_scanlines(Retrograph::DISPLAY_HEIGHT/2)
58
+ vdu.write_byte(0x7f00, 0x30)
59
+ end
60
+
61
+ == Important Notes ==
62
+
63
+ Nothing is displayed by default. To be shown, the
64
+ background layer (and the sprite, in graphical modes) must
65
+ be explicitly enabled by setting bit 2 (bit 3 for sprites)
66
+ of register $7FF8.
67
+
68
+ Patterns and name tables may overlap in memory (which is
69
+ in fact unavoidable in graphics modes); typically you
70
+ cannot have simultaneously valid pattern and name data at
71
+ the same location, so in such cases you will need to
72
+ sacrifice particular name or pattern entries.
73
+
74
+ In text modes, you will need to load your own font into the
75
+ pattern table; the VDU does not provide one by default.
76
+
77
+ Sprites patterns are always 4bpp, even in the 2bpp mode.
78
+ In Graphics A, one half of VRAM is occupied by 512 2bpp
79
+ patterns (for the background), and the other half by 256
80
+ 4bpp patterns for sprites. In Graphics B, the entirety
81
+ of VRAM is occupied by just 512 4bpp patterns, with the
82
+ upper half of those available to sprites.
83
+
84
+ When scrolling horizontally, the leftmost background
85
+ column will not be displayed if it is partway offscreen,
86
+ a (mis)feature shared with the Sega Master System.
87
+
88
+ Unlike most authentic 8-bit systems, any register or memory
89
+ location may be changed in between scanlines, including the
90
+ vertical scroll register and even the video mode. This
91
+ permits fairly powerful split-screen effects.
92
+
93
+ Also unlike most authentic 8-bit systems, Retrograph uses a
94
+ packed rather than a planar representation for pattern
95
+ data. Additionally, within each byte of pattern data, the
96
+ leftmost pixels are in the least significant position,
97
+ which may be backwards from what you expect.
98
+
99
+ Lastly, unlike a real 8-bit system, there is no deadline
100
+ imposed on code running in between scanlines or in between
101
+ frames. On real hardware, if you have code running
102
+ in between scanlines (that is, during horizontal retrace)
103
+ you will only have a short period of time available before
104
+ the next scanline will begin rendering, whether or not you
105
+ are finished what you are doing. However, such a
106
+ limitation would be very difficult to recreate in Ruby.
107
+
108
+ == VDU Memory Map ==
109
+
110
+ $0000 - $3FFF: VRAM (16 kbytes)
111
+ $4000 - $7DFF: VRAM (mirror) (16 kbytes - 512 bytes)
112
+ $7E00 - $7EFF: Sprite Table* (256 bytes)
113
+ $7F00 - $7F0F: BG Palette (16 bytes)
114
+ $7F10 - $7F1F: BG/Sprite Palette* (16 bytes)
115
+ $7F20 - $7FF7: unused (216 bytes)
116
+ $7FF8 - $7FFF: VDU Registers (8 bytes)
117
+ $7FF8: Flags
118
+ 4-7: unused
119
+ 3: Enable Sprites*
120
+ 2: Enable BG
121
+ 0-1: Mode
122
+ 00 - Text A (40x25)
123
+ 01 - Text B (32x28)
124
+ 10 - Graphics A (2bpp)
125
+ 11 - Graphics B (4bpp)
126
+ $7FF9: Pattern Table Base
127
+ Text A + B:
128
+ addr = (base & 0b00110000) << 8
129
+ addr may be one of:
130
+ $0000, $1000, $2000, or $3000
131
+ Graphics A + B:
132
+ addr = (base & 0b00100000) << 8
133
+ addr may be one of:
134
+ $0000 or $2000
135
+ $7FFA: Name Table Base
136
+ addr = ((base & 0b00110000) |
137
+ 0b00001000) << 8
138
+ addr may be one of:
139
+ $0800, $1800, $2800, or $3800
140
+ $7FFB: unused
141
+ $7FFC: BG scroll X*
142
+ $7FFD: BG scroll Y**
143
+ $7FFE-7FFF: unused
144
+ $8000 - $FFFF: mirror of $0000 - $7FFF
145
+
146
+ Notes:
147
+ *Ignored in text modes
148
+ **In text modes, the lower 3 bits of the vertical scroll
149
+ register are ignored and it wraps at 200 (Text A) or
150
+ 224 (Text B)
151
+
152
+ === Pattern Table ===
153
+
154
+ The starting address of the pattern table is determined by
155
+ register $7FF9.
156
+
157
+ The rows constituting each pattern in the table are given
158
+ in sequence from top to bottom. Within each byte, the
159
+ leftmost pixel is in the least significant position. For
160
+ example, for a 4bpp tile, the following row would display
161
+ with the pixels of color 1 on the *left* side, and the
162
+ pixels of color 0 on the *right* side:
163
+
164
+ 0x00001111
165
+
166
+ (little-endian byte ordering is assumed)
167
+
168
+ Different display modes have different pattern bit depths:
169
+
170
+ Text A: 256 patterns * 8 bytes per pattern = 2k
171
+ 6x8 pixel patterns
172
+ 1 byte per pattern row, 1 bit per pixel
173
+ most significant 2 bits ignored
174
+
175
+ Text B: 256 patterns * 8 bytes per pattern = 2k
176
+ 8x8 pixel patterns
177
+ 1 byte per patern row, 1 bit per pixel
178
+
179
+ Graphics A: 512 bg patterns * 16 bytes per pattern +
180
+ 256 sprite patterns * 32 bytes/pattern = 16k
181
+ 8x8 pixel patterns
182
+ 2 bytes/pattern row, 2 bits/pixel (bg)
183
+ 4 bytes/pattern row, 4 bits/pixel (sprites)
184
+
185
+ Graphics B: 512 patterns * 32 bytes per pattern = 16k
186
+ 8x8 pixel patterns
187
+ 4 bytes/pattern row, 4 bits/pixel
188
+ last 256 patterns shared with sprites
189
+
190
+ === Name Table ===
191
+
192
+ The starting address of the name table is determined by
193
+ register $7FFA.
194
+
195
+ Table sizes:
196
+
197
+ Text A: 40x25 characters * 2 bytes/character = 2000 bytes
198
+ Text B: 32x28 characters * 2 bytes/character = 1792 bytes
199
+ Graphics A + B: 32x32 tiles * 2 bytes per tile = 2k
200
+
201
+ Cell formats:
202
+
203
+ Text: pppppppp bbbbffff
204
+ p - pattern index
205
+ f - foreground color
206
+ b - background color
207
+
208
+ Graphics: pppppppp -bhvcccP
209
+ p - low byte of pattern index
210
+ P - high bit of pattern index
211
+ c - high bits of color
212
+ h - horizontal flip
213
+ v - vertical flip
214
+ b - background priority
215
+
216
+ In graphics modes, the color is computed by taking the high
217
+ color bits and oring them with the pattern color to get a
218
+ color in the range 0-31 (with the upper 16 colors coming
219
+ from the sprite palette):
220
+
221
+ color = pattern_color | (high_bits << 2)
222
+
223
+ (However, a pattern color of 0 is always transparent
224
+ regardless of the high color bits.)
225
+
226
+ === Sprite Table ===
227
+
228
+ Sprite patterns start 2048 bytes after the pattern table
229
+ base address specified in register $7FF9. The sprite
230
+ table itself always begins at $7E00.
231
+
232
+ 4 bytes * 64 sprites = 256 bytes
233
+
234
+ xxxxxxxx yyyyyyyy pppppppp --hvHVcc
235
+
236
+ x - X position (left edge)
237
+ y - Y position + 1 (top edge) (0 = hidden)
238
+ p - pattern index
239
+ h - horizontal flip
240
+ v - vertical flip
241
+ H - double size horizontally
242
+ V - double size vertically
243
+ c - high bits of color
244
+
245
+ Sprite colors are computed as follows:
246
+
247
+ color = pattern_color | (high_bits << 2) | 16;
248
+
249
+ (As with tiles, a pattern color of 0 is always transparent.)
250
+
251
+ === Palette Table ===
252
+
253
+ The palette table always begins at $7F00.
254
+
255
+ 1 byte * 32 colors (16 bg, 16 bg/sprite)
256
+
257
+ --rrggbb
258
+
259
+ r - red
260
+ g - green
261
+ b - blue
262
+
263
+ Note that the first sprite color (color 16) is effectively
264
+ never used.
data/Rakefile ADDED
@@ -0,0 +1,71 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ begin
4
+ require 'rake/extensiontask'
5
+ have_rake_compiler = true
6
+ rescue LoadError
7
+ $stderr.puts "*** The rake-compiler gem is required to compile. ***"
8
+ have_rake_compiler = false
9
+ end
10
+ begin
11
+ require 'spec/rake/spectask'
12
+ have_rspec = true
13
+ rescue LoadError
14
+ $stderr.puts "*** The rspec gem is required to run specs. ***"
15
+ have_rspec = false
16
+ end
17
+ require 'rake/clean'
18
+
19
+ GEM_VERSION = '0.5'
20
+
21
+ CLEAN.include("spec/**/*-output.bmp")
22
+ CLOBBER.include("lib/1.8")
23
+ CLOBBER.include("lib/1.9")
24
+
25
+ task :clobber => [:clean]
26
+
27
+ gemspec = Gem::Specification.new do |gemspec|
28
+ gemspec.name = "retrograph"
29
+ gemspec.version = GEM_VERSION
30
+ gemspec.author = "MenTaLguY <mental@rydia.net>"
31
+ gemspec.summary = "Retrograph, a retrodisplay."
32
+ gemspec.description = <<EOS
33
+ Retrograph is a software scanline renderer which simulates a late-era 8-bit
34
+ display. Its graphical capabilities are similar to those of the Sega Master
35
+ System.
36
+ EOS
37
+ gemspec.homepage = "http://rubyforge.org/projects/retrograph"
38
+ gemspec.email = "mental@rydia.net"
39
+ gemspec.rubyforge_project = 'retrograph'
40
+ gemspec.files = FileList['Rakefile', 'README', 'COPYING',
41
+ 'src/**/*.h', 'src/**/*.c', 'lib/**/*.rb',
42
+ 'ext/**/extconf.rb', 'ext/**/*.c', 'ext/**/*.h',
43
+ 'spec/**/*_spec.rb', 'spec/**/*.case',
44
+ 'spec/**/*.png']
45
+ gemspec.extensions = FileList['ext/**/extconf.rb']
46
+ gemspec.require_paths = ['lib']
47
+ gemspec.platform = Gem::Platform::RUBY
48
+ end
49
+
50
+ Rake::GemPackageTask.new(gemspec) do |pkg|
51
+ pkg.need_tar = true
52
+ end
53
+
54
+ if have_rake_compiler
55
+ Rake::ExtensionTask.new('retrograph', gemspec) do |ext|
56
+ ext.cross_compile = true
57
+ ext.cross_platform = 'i386-mswin32'
58
+ end
59
+ end
60
+
61
+ if have_rspec
62
+ Spec::Rake::SpecTask.new do |task|
63
+ task.ruby_opts << '-rrubygems'
64
+ task.libs << 'lib'
65
+ task.spec_files = FileList["spec/**/*_spec.rb"]
66
+ end
67
+ task :spec => [:compile]
68
+ task :default => [:clean, :spec]
69
+ elsif have_rake_compiler
70
+ task :default => [:clean, :compile]
71
+ end
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_VERSION =~ /^1\.8\./
4
+ $CPPFLAGS += ' -DRUBY_1_8'
5
+ end
6
+ create_makefile('retrograph')
@@ -0,0 +1,324 @@
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
+ #ifdef RUBY_1_8
25
+ #include <intern.h>
26
+ #else
27
+ #include <ruby/intern.h>
28
+ #endif
29
+ #include <string.h>
30
+
31
+ #include "../../src/retrograph.h"
32
+ #include "../../src/retrograph.c"
33
+
34
+ #ifndef RSTRING_LEN
35
+ #define RSTRING_LEN(str) RSTRING(str)->len
36
+ #define RSTRING_PTR(str) RSTRING(str)->ptr
37
+ #endif
38
+
39
+ #define UPSCALE_FACTOR 2
40
+ #define UPSCALE_REPEAT(stmts) stmts; stmts;
41
+ #define UPSCALE_REPEAT_MINUS1(stmts) stmts;
42
+
43
+ /* First few fields of SDL_Surface */
44
+ typedef struct surface_header_tag {
45
+ uint32_t flags;
46
+ struct pixelformat_tag *format; /* abstract */
47
+ int w, h;
48
+ uint16_t pitch;
49
+ void *pixels;
50
+ } surface_header_t;
51
+
52
+ /* for Ruby/SDL 2.x, which indirectly wraps surfaces */
53
+ typedef struct rubysdl2_surface_tag {
54
+ surface_header_t *surface;
55
+ } rubysdl2_surface_t;
56
+
57
+ typedef struct vdu_wrapper_tag {
58
+ retrograph_vdu_t vdu;
59
+ VALUE surface;
60
+ retrograph_pixel_t buffer[RETROGRAPH_DISPLAY_WIDTH];
61
+ int scanline;
62
+ } vdu_wrapper_t;
63
+
64
+ #define NOT_IN_FRAME -1
65
+
66
+ static VALUE mRetrograph = Qnil;
67
+ static VALUE cVDU = Qnil;
68
+ static VALUE eRenderError = Qnil;
69
+ static VALUE mSDL = Qnil;
70
+ static VALUE cSurface = Qnil;
71
+ static VALUE CREATE_SURFACE_METHOD_NAME = Qnil;
72
+
73
+ static surface_header_t *get_rubysdl_surface_1_x(VALUE surface_r) {
74
+ surface_header_t *surface;
75
+ Data_Get_Struct(surface_r, surface_header_t, surface);
76
+ return surface;
77
+ }
78
+
79
+ static surface_header_t *get_rubysdl_surface_2_x(VALUE surface_r) {
80
+ rubysdl2_surface_t *surface;
81
+ Data_Get_Struct(surface_r, rubysdl2_surface_t, surface);
82
+ return surface->surface;
83
+ }
84
+
85
+ static surface_header_t *(*get_rubysdl_surface)(VALUE surface_r) = NULL;
86
+
87
+ static void check_errors(retrograph_vdu_t vdu) {
88
+ retrograph_error_t error;
89
+ error = retrograph_get_error(vdu);
90
+ switch (error) {
91
+ case RETROGRAPH_OK:
92
+ return;
93
+ case RETROGRAPH_NO_MEMORY:
94
+ rb_memerror();
95
+ case RETROGRAPH_INVALID_ARGUMENT:
96
+ rb_raise(rb_const_get(rb_cObject, rb_intern("ArgumentError")),
97
+ "Invalid argument");
98
+ default:
99
+ rb_raise(rb_eRuntimeError, "Unknown error");
100
+ }
101
+ }
102
+
103
+ static void mark_vdu(vdu_wrapper_t *vdu) {
104
+ rb_gc_mark(vdu->surface);
105
+ }
106
+
107
+ static void free_vdu(vdu_wrapper_t *vdu) {
108
+ if (vdu->vdu) {
109
+ retrograph_destroy(vdu->vdu);
110
+ }
111
+ xfree(vdu);
112
+ }
113
+
114
+ static VALUE rescue_free_vdu(VALUE data, VALUE exc) {
115
+ free_vdu((vdu_wrapper_t *)data);
116
+ rb_exc_raise(exc);
117
+ return Qnil;
118
+ }
119
+
120
+ static VALUE alloc_vdu2(VALUE data);
121
+
122
+ static VALUE alloc_vdu(VALUE klass) {
123
+ vdu_wrapper_t *vdu;
124
+ vdu = ALLOC(vdu_wrapper_t);
125
+ vdu->vdu = NULL;
126
+ vdu->surface = Qnil;
127
+ vdu->scanline = NOT_IN_FRAME;
128
+ rb_rescue2(alloc_vdu2, (VALUE)vdu,
129
+ rescue_free_vdu, (VALUE)vdu,
130
+ rb_eException, (VALUE)0);
131
+ return Data_Wrap_Struct(klass, mark_vdu, free_vdu, vdu);
132
+ }
133
+
134
+ VALUE alloc_vdu2(VALUE data) {
135
+ vdu_wrapper_t *vdu=(vdu_wrapper_t *)data;
136
+ vdu->vdu = retrograph_new();
137
+ check_errors(vdu->vdu);
138
+ return Qnil;
139
+ }
140
+
141
+ void alloc_sdl_surface(vdu_wrapper_t *vdu) {
142
+ VALUE surface;
143
+ surface = rb_funcall(cSurface, SYM2ID(CREATE_SURFACE_METHOD_NAME), 8,
144
+ INT2FIX(0), /* flags */
145
+ INT2FIX(RETROGRAPH_DISPLAY_WIDTH * UPSCALE_FACTOR),
146
+ INT2FIX(RETROGRAPH_DISPLAY_HEIGHT * UPSCALE_FACTOR),
147
+ INT2FIX(RETROGRAPH_BITS_PER_PIXEL),
148
+ INT2FIX(RETROGRAPH_RED_MASK),
149
+ INT2FIX(RETROGRAPH_GREEN_MASK),
150
+ INT2FIX(RETROGRAPH_BLUE_MASK),
151
+ INT2FIX(RETROGRAPH_ALPHA_MASK));
152
+ vdu->surface = surface;
153
+ }
154
+
155
+ static VALUE vdu_write(VALUE self, VALUE start_address_r, VALUE data_r) {
156
+ vdu_wrapper_t *vdu;
157
+ retrograph_addr_t start_address;
158
+ Check_Type(data_r, T_STRING);
159
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
160
+ start_address = (retrograph_addr_t)NUM2INT(start_address_r);
161
+ retrograph_write(vdu->vdu, start_address, RSTRING_PTR(data_r),
162
+ (size_t)RSTRING_LEN(data_r));
163
+ return Qnil;
164
+ }
165
+
166
+ static VALUE vdu_write_byte(VALUE self, VALUE start_address_r, VALUE byte_r) {
167
+ vdu_wrapper_t *vdu;
168
+ uint8_t byte;
169
+ retrograph_addr_t start_address;
170
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
171
+ start_address = (retrograph_addr_t)NUM2INT(start_address_r);
172
+ byte = (uint8_t)NUM2INT(byte_r);
173
+ retrograph_write(vdu->vdu, start_address, &byte, 1);
174
+ return Qnil;
175
+ }
176
+
177
+ static inline copy_upscale(uint8_t *output, uint8_t const * input, size_t count) {
178
+ for (; count; count--) {
179
+ UPSCALE_REPEAT(
180
+ memcpy(output, input, RETROGRAPH_BYTES_PER_PIXEL);
181
+ output += RETROGRAPH_BYTES_PER_PIXEL;
182
+ )
183
+ input += RETROGRAPH_BYTES_PER_PIXEL;
184
+ }
185
+ }
186
+
187
+ static VALUE vdu_render_scanlines(VALUE self, VALUE n_scanlines_r) {
188
+ vdu_wrapper_t *vdu;
189
+ surface_header_t *surface;
190
+ uint8_t *current_line;
191
+ int max_scanline;
192
+
193
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
194
+
195
+ if (vdu->scanline == NOT_IN_FRAME) {
196
+ return Qnil;
197
+ }
198
+
199
+ surface = get_rubysdl_surface(vdu->surface);
200
+
201
+ max_scanline = (int)NUM2UINT(n_scanlines_r) + vdu->scanline;
202
+ if (max_scanline > RETROGRAPH_DISPLAY_HEIGHT) {
203
+ max_scanline = RETROGRAPH_DISPLAY_HEIGHT;
204
+ }
205
+
206
+ current_line = (uint8_t *)surface->pixels + vdu->scanline * surface->pitch * UPSCALE_FACTOR;
207
+ for (; vdu->scanline < max_scanline; vdu->scanline++) {
208
+ uint8_t *output;
209
+ uint8_t const *input;
210
+ retrograph_render_scanline(vdu->vdu, vdu->buffer, sizeof(vdu->buffer));
211
+ check_errors(vdu->vdu);
212
+ copy_upscale(current_line, (uint8_t const *)vdu->buffer,
213
+ RETROGRAPH_DISPLAY_WIDTH);
214
+ current_line += surface->pitch;
215
+ UPSCALE_REPEAT_MINUS1(
216
+ memcpy(current_line, current_line - surface->pitch,
217
+ RETROGRAPH_DISPLAY_WIDTH * RETROGRAPH_BYTES_PER_PIXEL *
218
+ UPSCALE_FACTOR);
219
+ current_line += surface->pitch;
220
+ )
221
+ }
222
+
223
+ return Qnil;
224
+ }
225
+
226
+ static VALUE vdu_render_frame_inner(VALUE self) {
227
+ vdu_wrapper_t *vdu;
228
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
229
+ retrograph_begin_frame(vdu->vdu);
230
+ check_errors(vdu->vdu);
231
+ vdu->scanline = 0;
232
+ if (rb_block_given_p()) {
233
+ rb_yield(Qundef);
234
+ }
235
+ vdu_render_scanlines(self, INT2FIX(RETROGRAPH_DISPLAY_HEIGHT));
236
+ return Qnil;
237
+ }
238
+
239
+ static VALUE vdu_render_frame_cleanup(VALUE self) {
240
+ vdu_wrapper_t *vdu;
241
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
242
+ vdu->scanline = NOT_IN_FRAME;
243
+ return Qnil;
244
+ }
245
+
246
+ static VALUE vdu_render_frame(VALUE self) {
247
+ vdu_wrapper_t *vdu;
248
+ Data_Get_Struct(self, vdu_wrapper_t, vdu);
249
+ if (vdu->scanline != NOT_IN_FRAME) {
250
+ rb_raise(eRenderError, "Already in frame");
251
+ }
252
+ if (vdu->surface == Qnil) {
253
+ alloc_sdl_surface(vdu);
254
+ }
255
+ rb_ensure(vdu_render_frame_inner, self, vdu_render_frame_cleanup, self);
256
+ return vdu->surface;
257
+ }
258
+
259
+ static VALUE init_sdl(VALUE self) {
260
+ VALUE rubysdl_version;
261
+ mSDL = rb_const_get(rb_cObject, rb_intern("SDL"));
262
+
263
+ rubysdl_version = rb_const_get(mSDL, rb_intern("VERSION"));
264
+ if (!strncmp(StringValueCStr(rubysdl_version), "1.", 2)) {
265
+ CREATE_SURFACE_METHOD_NAME = ID2SYM(rb_intern("new"));
266
+ get_rubysdl_surface = get_rubysdl_surface_1_x;
267
+ } else if (!strncmp(StringValueCStr(rubysdl_version), "2.", 2)) {
268
+ CREATE_SURFACE_METHOD_NAME = ID2SYM(rb_intern("createWithFormat"));
269
+ get_rubysdl_surface = get_rubysdl_surface_2_x;
270
+ } else {
271
+ rb_raise(rb_eLoadError, "Unrecognized Ruby/SDL version");
272
+ }
273
+
274
+ cSurface = rb_const_get(mSDL, rb_intern("Surface"));
275
+ rb_define_method(cVDU, "render_frame_sdl", vdu_render_frame, 0);
276
+ }
277
+
278
+ void Init_retrograph(void) {
279
+ rb_global_variable(&mSDL);
280
+ rb_global_variable(&cSurface);
281
+ rb_global_variable(&CREATE_SURFACE_METHOD_NAME);
282
+
283
+ rb_global_variable(&mRetrograph);
284
+ mRetrograph = rb_define_module("Retrograph");
285
+
286
+ #define DEFINE_CONSTANT(base_name) \
287
+ rb_const_set(mRetrograph, rb_intern(#base_name), \
288
+ INT2FIX(RETROGRAPH_##base_name))
289
+
290
+ DEFINE_CONSTANT(DISPLAY_WIDTH);
291
+ DEFINE_CONSTANT(DISPLAY_HEIGHT);
292
+ DEFINE_CONSTANT(BYTES_PER_PIXEL);
293
+ DEFINE_CONSTANT(BITS_PER_PIXEL);
294
+
295
+ DEFINE_CONSTANT(RED_SHIFT);
296
+ DEFINE_CONSTANT(GREEN_SHIFT);
297
+ DEFINE_CONSTANT(BLUE_SHIFT);
298
+
299
+ DEFINE_CONSTANT(RED_MASK);
300
+ DEFINE_CONSTANT(GREEN_MASK);
301
+ DEFINE_CONSTANT(BLUE_MASK);
302
+ DEFINE_CONSTANT(ALPHA_MASK);
303
+
304
+ #undef DEFINE_CONSTANT
305
+
306
+ rb_const_set(mRetrograph, rb_intern("OUTPUT_WIDTH"),
307
+ INT2FIX(RETROGRAPH_DISPLAY_WIDTH * UPSCALE_FACTOR));
308
+ rb_const_set(mRetrograph, rb_intern("OUTPUT_HEIGHT"),
309
+ INT2FIX(RETROGRAPH_DISPLAY_HEIGHT * UPSCALE_FACTOR));
310
+
311
+ rb_global_variable(&cVDU);
312
+ cVDU = rb_define_class_under(mRetrograph, "VDU", rb_cObject);
313
+
314
+ rb_global_variable(&eRenderError);
315
+ eRenderError = rb_define_class_under(mRetrograph, "RenderError",
316
+ rb_eRuntimeError);
317
+
318
+ rb_define_alloc_func(cVDU, alloc_vdu);
319
+ rb_define_method(cVDU, "write", vdu_write, 2);
320
+ rb_define_method(cVDU, "write_byte", vdu_write_byte, 2);
321
+ rb_define_method(cVDU, "wait_scanlines", vdu_render_scanlines, 1);
322
+
323
+ rb_define_singleton_method(mRetrograph, "_init_sdl", init_sdl, 0);
324
+ }