rnes 0.1.0

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