retrograph 0.5-x86-mswin32

Sign up to get free protection for your applications and to get access to all the features.
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
+ }