retrograph 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +248 -0
- data/Rakefile +54 -0
- data/ext/extconf.rb +3 -0
- data/ext/retrograph.c +275 -0
- data/spec/image-specs/color-blue-bits.case +2 -0
- data/spec/image-specs/color-blue-bits.png +0 -0
- data/spec/image-specs/color-green-bits.case +2 -0
- data/spec/image-specs/color-green-bits.png +0 -0
- data/spec/image-specs/color-red-bits.case +2 -0
- data/spec/image-specs/color-red-bits.png +0 -0
- data/spec/image-specs/default-screen-black.case +1 -0
- data/spec/image-specs/default-screen-black.png +0 -0
- data/spec/image-specs/default-screen-color-0.case +2 -0
- data/spec/image-specs/default-screen-color-0.png +0 -0
- data/spec/image-specs/scroll-mode1.case +7 -0
- data/spec/image-specs/scroll-mode1.png +0 -0
- data/spec/image-specs/scroll-mode2.case +10 -0
- data/spec/image-specs/scroll-mode2.png +0 -0
- data/spec/image-specs/scroll-mode3.case +10 -0
- data/spec/image-specs/scroll-mode3.png +0 -0
- data/spec/image-specs/sprites-doubling.case +5 -0
- data/spec/image-specs/sprites-doubling.png +0 -0
- data/spec/image-specs/sprites-mode2.case +8 -0
- data/spec/image-specs/sprites-mode2.png +0 -0
- data/spec/image-specs/sprites-mode3.case +8 -0
- data/spec/image-specs/sprites-mode3.png +0 -0
- data/spec/image-specs/sprites-ordering.case +5 -0
- data/spec/image-specs/sprites-ordering.png +0 -0
- data/spec/image-specs/text-colors.case +5 -0
- data/spec/image-specs/text-colors.png +0 -0
- data/spec/image-specs/tile-attributes-mode2.case +9 -0
- data/spec/image-specs/tile-attributes-mode2.png +0 -0
- data/spec/image-specs/tile-attributes-mode3.case +9 -0
- data/spec/image-specs/tile-attributes-mode3.png +0 -0
- data/spec/retrograph_spec.rb +133 -0
- data/src/retrograph.c +695 -0
- data/src/retrograph.h +66 -0
- 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
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
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
should fill the screen with black by default
|
Binary file
|
Binary file
|
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
|
Binary file
|
@@ -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
|