rnes 0.1.0

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.
@@ -0,0 +1,99 @@
1
+ require 'rnes/cpu_bus'
2
+ require 'rnes/cpu'
3
+ require 'rnes/dma_controller'
4
+ require 'rnes/interrupt_line'
5
+ require 'rnes/keypad'
6
+ require 'rnes/ppu'
7
+ require 'rnes/ppu_bus'
8
+ require 'rnes/ram'
9
+ require 'rnes/rom'
10
+ require 'rnes/terminal_renderer'
11
+
12
+ module Rnes
13
+ class PartsFactory
14
+ CHARACTER_RAM_BYTESIZE = 2**12
15
+
16
+ VIDEO_RAM_BYTESIZE = 2**13
17
+
18
+ WORKING_RAM_BYTESIZE = 2**11
19
+
20
+ # @return [Rnes::Ram]
21
+ def character_ram
22
+ @character_ram ||= ::Rnes::Ram.new(bytesize: CHARACTER_RAM_BYTESIZE)
23
+ end
24
+
25
+ # @return [Rnes::Cpu]
26
+ def cpu
27
+ @cpu ||= ::Rnes::Cpu.new(
28
+ bus: cpu_bus,
29
+ interrupt_line: interrupt_line,
30
+ )
31
+ end
32
+
33
+ # @return [Rnes::CpuBus]
34
+ def cpu_bus
35
+ @cpu_bus ||= ::Rnes::CpuBus.new(
36
+ dma_controller: dma_controller,
37
+ keypad1: keypad1,
38
+ keypad2: keypad2,
39
+ ppu: ppu,
40
+ ram: working_ram,
41
+ )
42
+ end
43
+
44
+ # @return [Rnes::DmaController]
45
+ def dma_controller
46
+ @dma_controller ||= ::Rnes::DmaController.new(
47
+ ppu: ppu,
48
+ working_ram: working_ram,
49
+ )
50
+ end
51
+
52
+ # @return [Rnes::InterruptLine]
53
+ def interrupt_line
54
+ @interrupt_line ||= ::Rnes::InterruptLine.new
55
+ end
56
+
57
+ # @return [Rnes::Keypad]
58
+ def keypad1
59
+ @keypad1 ||= ::Rnes::Keypad.new
60
+ end
61
+
62
+ # @return [Rnes::Keypad]
63
+ def keypad2
64
+ @keypad2 ||= ::Rnes::Keypad.new
65
+ end
66
+
67
+ # @return [Rnes::Ppu]
68
+ def ppu
69
+ @ppu ||= ::Rnes::Ppu.new(
70
+ bus: ppu_bus,
71
+ interrupt_line: interrupt_line,
72
+ renderer: renderer,
73
+ )
74
+ end
75
+
76
+ # @return [Rnes::PpuBus]
77
+ def ppu_bus
78
+ @ppu_bus ||= ::Rnes::PpuBus.new(
79
+ character_ram: character_ram,
80
+ video_ram: video_ram,
81
+ )
82
+ end
83
+
84
+ # @return [Rnes::TerminalRenderer]
85
+ def renderer
86
+ @renderer ||= ::Rnes::TerminalRenderer.new
87
+ end
88
+
89
+ # @return [Rnes::Ram]
90
+ def video_ram
91
+ @video_ram ||= ::Rnes::Ram.new(bytesize: VIDEO_RAM_BYTESIZE)
92
+ end
93
+
94
+ # @return [Rnes::Ram]
95
+ def working_ram
96
+ @working_ram ||= ::Rnes::Ram.new(bytesize: WORKING_RAM_BYTESIZE)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,383 @@
1
+ require 'rnes/errors'
2
+ require 'rnes/image'
3
+ require 'rnes/ppu_registers'
4
+ require 'rnes/ppu/colors'
5
+ require 'rnes/ram'
6
+
7
+ module Rnes
8
+ class Ppu
9
+ ADDRESS_TO_START_ATTRIBUTE_TABLE = 0x23C0
10
+
11
+ ADDRESS_TO_START_NAME_TABLE = 0x2000
12
+
13
+ ADDRESS_TO_START_BACKGROUND_PALETTE_TABLE = 0x3F00
14
+
15
+ ADDRESS_TO_START_SPRITE_PALETTE_TABLE = 0x3F10
16
+
17
+ BLOCK_HEIGHT = 16
18
+
19
+ BLOCK_WIDTH = 16
20
+
21
+ CYCLES_PER_LINE = 341
22
+
23
+ SPRITE_RAM_BYTESIZE = 2**8
24
+
25
+ SPRITES_COUNT = 64
26
+
27
+ TILE_HEIGHT = 8
28
+
29
+ TILE_WIDTH = 8
30
+
31
+ V_BLANK_HEIGHT = 21
32
+
33
+ VISIBLE_WINDOW_HEIGHT = 240
34
+
35
+ VISIBLE_WINDOW_WIDTH = 256
36
+
37
+ # @note For debug use.
38
+ # @param [Integer]
39
+ # @return [Integer]
40
+ attr_accessor :cycle
41
+
42
+ # @note For debug use.
43
+ # @param [Integer]
44
+ # @return [Integer]
45
+ attr_accessor :line
46
+
47
+ # @note For debug use.
48
+ # @return [Array<Rnes::Image>]
49
+ attr_reader :image
50
+
51
+ # @note For debug use.
52
+ # @return [Rnes::PpuRegisters]
53
+ attr_reader :registers
54
+
55
+ # @param [Rnes::PpuBus] bus
56
+ # @param [Rnes::InterruptLine] interrupt_line
57
+ # @param [Rnes::TerminalRenderer] renderer
58
+ def initialize(bus:, interrupt_line:, renderer:)
59
+ @bus = bus
60
+ @cycle = 0
61
+ @image = ::Rnes::Image.new(height: VISIBLE_WINDOW_HEIGHT, width: VISIBLE_WINDOW_WIDTH)
62
+ @interrupt_line = interrupt_line
63
+ @line = 0
64
+ @registers = ::Rnes::PpuRegisters.new
65
+ @renderer = renderer
66
+ @sprite_ram = ::Rnes::Ram.new(bytesize: SPRITE_RAM_BYTESIZE)
67
+ @sprite_ram_address = 0x00
68
+ @video_ram_address = 0x0000
69
+ @writing_to_scroll_registers = false
70
+ @writing_video_ram_address = false
71
+ end
72
+
73
+ # @param [Integer] address
74
+ # @return [Integer]
75
+ def read(address)
76
+ case address
77
+ when 0x0002
78
+ registers.status
79
+ when 0x0007
80
+ read_from_video_ram
81
+ else
82
+ raise ::Rnes::Errors::InvalidPpuAddressError, address
83
+ end
84
+ end
85
+
86
+ def tick
87
+ if on_visible_cycle? && x_in_tile.zero?
88
+ draw_background_8pixels
89
+ end
90
+ if on_right_end_cycle?
91
+ self.cycle = 0
92
+ if on_bottom_end_line?
93
+ self.line = 0
94
+ deassert_nmi
95
+ clear_sprite_hit
96
+ clear_v_blank
97
+ draw_sprites
98
+ render_image
99
+ else
100
+ self.line += 1
101
+ if on_line_to_start_v_blank?
102
+ set_v_blank
103
+ if v_blank_interrupt_enabled?
104
+ assert_nmi
105
+ end
106
+ end
107
+ end
108
+ else
109
+ self.cycle += 1
110
+ end
111
+ end
112
+
113
+ # @param [Integer] index
114
+ # @param [Integer] value
115
+ def transfer_sprite_data(index:, value:)
116
+ address = (@sprite_ram_address + index) % SPRITE_RAM_BYTESIZE
117
+ @sprite_ram.write(address, value)
118
+ end
119
+
120
+ # @param [Integer] address
121
+ # @param [Integer] value
122
+ # @return [Integer]
123
+ def write(address, value)
124
+ case address
125
+ when 0x0000
126
+ registers.control1 = value
127
+ when 0x0001
128
+ registers.control2 = value
129
+ when 0x0003
130
+ write_sprite_ram_address(value)
131
+ when 0x0004
132
+ write_to_sprite_ram(value)
133
+ when 0x0005
134
+ write_to_scroll_registers(value)
135
+ when 0x0006
136
+ write_video_ram_address(value)
137
+ when 0x0007
138
+ write_to_video_ram(value)
139
+ else
140
+ raise ::Rnes::Errors::InvalidPpuAddressError, address
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def assert_nmi
147
+ @interrupt_line.assert_nmi
148
+ end
149
+
150
+ def clear_sprite_hit
151
+ registers.toggle_sprite_hit_bit(false)
152
+ end
153
+
154
+ def clear_v_blank
155
+ registers.toggle_in_v_blank_bit(false)
156
+ end
157
+
158
+ def deassert_nmi
159
+ @interrupt_line.deassert_nmi
160
+ end
161
+
162
+ def draw_background_8pixels
163
+ character_address_offset = registers.has_background_bank_bit? ? 0x1000 : 0
164
+ character_index = read_from_name_table(tile_index)
165
+ character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_tile + character_address_offset
166
+ character_line_low_byte = read_from_character_rom(character_line_low_byte_address)
167
+ character_line_high_byte = read_from_character_rom(character_line_low_byte_address + 8)
168
+
169
+ block_id = 0
170
+ block_id |= 0b01 if (x % BLOCK_WIDTH).odd?
171
+ block_id |= 0b10 if (y % BLOCK_HEIGHT).odd?
172
+ mini_palette_ids_byte = read_from_attribute_table(tile_index)
173
+ mini_palette_id = (mini_palette_ids_byte >> (block_id * 2)) & 0b11
174
+
175
+ TILE_WIDTH.times do |x_in_character|
176
+ index_in_character_line_byte = TILE_WIDTH - 1 - x_in_character
177
+ background_palette_index = character_line_low_byte[index_in_character_line_byte] | character_line_high_byte[index_in_character_line_byte] << 1 | mini_palette_id << 2
178
+ color_id = read_from_background_palette_table(background_palette_index)
179
+ @image.write(
180
+ value: ::Rnes::Ppu::COLORS[color_id],
181
+ x: x + x_in_character,
182
+ y: y,
183
+ )
184
+ end
185
+ end
186
+
187
+ # @note
188
+ # struct Sprite {
189
+ # U8 y;
190
+ # U8 tile;
191
+ # U8 attr;
192
+ # U8 x;
193
+ # }
194
+ #
195
+ # attr 76543210
196
+ # ||| `+- palette
197
+ # ||`------ priority (0: front, 1: back)
198
+ # |`------- horizontal flip
199
+ # `-------- vertical flip
200
+ def draw_sprites
201
+ character_address_offset = registers.has_sprite_bank_bit? ? 0x1000 : 0
202
+ SPRITES_COUNT.times do |i|
203
+ base_sprite_ram_address = i * 4
204
+ y_for_sprite = (read_from_sprite_ram(base_sprite_ram_address) - TILE_HEIGHT)
205
+ next if y_for_sprite.negative?
206
+ name_table_index = read_from_sprite_ram(base_sprite_ram_address + 1)
207
+ sprite_attribute_byte = read_from_sprite_ram(base_sprite_ram_address + 2)
208
+ x_for_sprite = read_from_sprite_ram(base_sprite_ram_address + 3)
209
+
210
+ character_index = read_from_name_table(name_table_index)
211
+
212
+ mini_palette_id = sprite_attribute_byte & 0b11
213
+
214
+ TILE_HEIGHT.times do |y_in_character|
215
+ character_line_low_byte_address = TILE_HEIGHT * 2 * character_index + y_in_character + character_address_offset
216
+ character_line_low_byte = read_from_character_rom(character_line_low_byte_address)
217
+ character_line_high_byte = read_from_character_rom(character_line_low_byte_address + 8)
218
+ TILE_WIDTH.times do |x_in_character|
219
+ index_in_character_line_byte = TILE_WIDTH - 1 - x_in_character
220
+ background_palette_index = character_line_low_byte[index_in_character_line_byte] | character_line_high_byte[index_in_character_line_byte] << 1 | mini_palette_id << 2
221
+ color_id = read_from_background_palette_table(background_palette_index)
222
+ @image.write(
223
+ value: ::Rnes::Ppu::COLORS[color_id],
224
+ x: x_for_sprite + x_in_character,
225
+ y: y_for_sprite + y_in_character,
226
+ )
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ # @return [Boolean]
233
+ def on_bottom_end_line?
234
+ line == VISIBLE_WINDOW_HEIGHT + V_BLANK_HEIGHT
235
+ end
236
+
237
+ # @return [Boolean]
238
+ def on_line_to_start_v_blank?
239
+ line == VISIBLE_WINDOW_HEIGHT
240
+ end
241
+
242
+ # @return [Boolean]
243
+ def on_right_end_cycle?
244
+ cycle == CYCLES_PER_LINE - 1
245
+ end
246
+
247
+ # @return [Boolean]
248
+ def on_visible_cycle?
249
+ (0...VISIBLE_WINDOW_WIDTH).cover?(x) && (0...VISIBLE_WINDOW_HEIGHT).cover?(y)
250
+ end
251
+
252
+ # @param [Integer] index
253
+ # @return [Integer] 4-color-palette IDs of 4 blocks, as 8 bit data.
254
+ def read_from_attribute_table(index)
255
+ @bus.read(ADDRESS_TO_START_ATTRIBUTE_TABLE + index)
256
+ end
257
+
258
+ # @param [Integer] index
259
+ # @return [Integer]
260
+ def read_from_name_table(index)
261
+ @bus.read(ADDRESS_TO_START_NAME_TABLE + index)
262
+ end
263
+
264
+ # @param [Integer] index
265
+ # @return [Integer]
266
+ def read_from_background_palette_table(index)
267
+ @bus.read(ADDRESS_TO_START_BACKGROUND_PALETTE_TABLE + index)
268
+ end
269
+
270
+ # @param [Integer] index
271
+ # @return [Integer]
272
+ def read_from_character_rom(index)
273
+ @bus.read(index)
274
+ end
275
+
276
+ # @return [Integer]
277
+ def read_from_video_ram
278
+ value = @bus.read(@video_ram_address)
279
+ @video_ram_address += video_ram_address_offset
280
+ value
281
+ end
282
+
283
+ # @param [Integer] address
284
+ # @return [Integer]
285
+ def read_from_sprite_ram(address)
286
+ @sprite_ram.read(address)
287
+ end
288
+
289
+ def render_image
290
+ @renderer.render(@image)
291
+ end
292
+
293
+ def set_v_blank
294
+ registers.set_in_v_blank_bit
295
+ end
296
+
297
+ # @return [Integer]
298
+ def tile_index
299
+ y_of_tile * (VISIBLE_WINDOW_WIDTH / TILE_WIDTH) + x_of_tile
300
+ end
301
+
302
+ # @return [Boolean]
303
+ def v_blank_interrupt_enabled?
304
+ @registers.has_v_blank_irq_enabled_bit?
305
+ end
306
+
307
+ # @return [Integer]
308
+ def video_ram_address_offset
309
+ if registers.has_large_video_ram_address_offset_bit?
310
+ 32
311
+ else
312
+ 1
313
+ end
314
+ end
315
+
316
+ # @param [Integer] address
317
+ def write_sprite_ram_address(address)
318
+ @sprite_ram_address = address
319
+ end
320
+
321
+ # @param [Integer] value
322
+ def write_to_scroll_registers(value)
323
+ if @writing_to_scroll_registers
324
+ @registers.scroll_vertical = value
325
+ else
326
+ @registers.scroll_horizontal = value
327
+ end
328
+ @writing_to_scroll_registers = !@writing_to_scroll_registers
329
+ end
330
+
331
+ # @param [Integer] value
332
+ def write_to_sprite_ram(value)
333
+ @sprite_ram.write(@sprite_ram_address, value)
334
+ @sprite_ram_address += 1
335
+ end
336
+
337
+ # @param [Integer] address
338
+ def write_video_ram_address(address)
339
+ if @writing_video_ram_address
340
+ @video_ram_address |= address
341
+ else
342
+ @video_ram_address = address << 8
343
+ end
344
+ @writing_video_ram_address = !@writing_video_ram_address
345
+ end
346
+
347
+ # @param [Integer] value
348
+ def write_to_video_ram(value)
349
+ @bus.write(@video_ram_address, value)
350
+ @video_ram_address += video_ram_address_offset
351
+ end
352
+
353
+ # @return [Integer]
354
+ def x
355
+ cycle - 1
356
+ end
357
+
358
+ # @return [Integer]
359
+ def x_in_tile
360
+ x % TILE_WIDTH
361
+ end
362
+
363
+ # @return [Integer]
364
+ def x_of_tile
365
+ x / TILE_WIDTH
366
+ end
367
+
368
+ # @return [Integer]
369
+ def y
370
+ line
371
+ end
372
+
373
+ # @return [Integer]
374
+ def y_in_tile
375
+ y % TILE_HEIGHT
376
+ end
377
+
378
+ # @return [Integer]
379
+ def y_of_tile
380
+ y / TILE_HEIGHT
381
+ end
382
+ end
383
+ end