@tmustier/pi-nes 0.2.4 → 0.2.5

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 (32) hide show
  1. package/AGENTS.md +89 -1
  2. package/README.md +78 -49
  3. package/extensions/nes/native/nes-core/Cargo.lock +0 -2
  4. package/extensions/nes/native/nes-core/Cargo.toml +1 -1
  5. package/extensions/nes/native/nes-core/index.d.ts +5 -0
  6. package/extensions/nes/native/nes-core/index.node +0 -0
  7. package/extensions/nes/native/nes-core/native.d.ts +5 -0
  8. package/extensions/nes/native/nes-core/src/lib.rs +25 -0
  9. package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo-ok +1 -0
  10. package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo_vcs_info.json +5 -0
  11. package/extensions/nes/native/nes-core/vendor/nes_rust/.travis.yml +3 -0
  12. package/extensions/nes/native/nes-core/vendor/nes_rust/Cargo.toml +23 -0
  13. package/extensions/nes/native/nes-core/vendor/nes_rust/LICENSE +21 -0
  14. package/extensions/nes/native/nes-core/vendor/nes_rust/README.md +59 -0
  15. package/extensions/nes/native/nes-core/vendor/nes_rust/src/apu.rs +1114 -0
  16. package/extensions/nes/native/nes-core/vendor/nes_rust/src/audio.rs +6 -0
  17. package/extensions/nes/native/nes-core/vendor/nes_rust/src/button.rs +23 -0
  18. package/extensions/nes/native/nes-core/vendor/nes_rust/src/cpu.rs +2364 -0
  19. package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_audio.rs +49 -0
  20. package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_display.rs +47 -0
  21. package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_input.rs +33 -0
  22. package/extensions/nes/native/nes-core/vendor/nes_rust/src/display.rs +10 -0
  23. package/extensions/nes/native/nes-core/vendor/nes_rust/src/input.rs +7 -0
  24. package/extensions/nes/native/nes-core/vendor/nes_rust/src/joypad.rs +86 -0
  25. package/extensions/nes/native/nes-core/vendor/nes_rust/src/lib.rs +168 -0
  26. package/extensions/nes/native/nes-core/vendor/nes_rust/src/mapper.rs +502 -0
  27. package/extensions/nes/native/nes-core/vendor/nes_rust/src/memory.rs +73 -0
  28. package/extensions/nes/native/nes-core/vendor/nes_rust/src/ppu.rs +1378 -0
  29. package/extensions/nes/native/nes-core/vendor/nes_rust/src/register.rs +560 -0
  30. package/extensions/nes/native/nes-core/vendor/nes_rust/src/rom.rs +231 -0
  31. package/extensions/nes/nes-core.ts +27 -4
  32. package/package.json +1 -1
@@ -0,0 +1,1378 @@
1
+ use register::Register;
2
+ use memory::Memory;
3
+ use rom::Rom;
4
+ use rom::Mirrorings;
5
+ use display::Display;
6
+
7
+ /**
8
+ * RP2A03
9
+ * The comments about PPU spec are based on https://wiki.nesdev.com/w/index.php/PPU
10
+ */
11
+ pub struct Ppu {
12
+ pub frame: u32,
13
+
14
+ // 341 cycles per scan line. 0-340.
15
+ pub cycle: u16,
16
+
17
+ // 262 scan lines per frame. 0-261
18
+ scanline: u16,
19
+
20
+ // manage a case where vblank doesn't set due to 0x2002 read
21
+ suppress_vblank: bool,
22
+
23
+ // -- For background pixels
24
+
25
+ //
26
+ register_first_store: bool,
27
+
28
+ //
29
+ fine_x_scroll: u8,
30
+
31
+ // 8-bit register and its latch for nametable
32
+ name_table_latch: u8,
33
+ name_table: Register<u8>,
34
+
35
+ // Two 16-bit shift registers and their latches for two pattern table tiles
36
+ pattern_table_low_latch: u8,
37
+ pattern_table_high_latch: u8,
38
+ pattern_table_low: Register<u16>,
39
+ pattern_table_high: Register<u16>,
40
+
41
+ // Two 16-bit shift registers and their latches for palette attributes.
42
+ attribute_table_low_latch: u8,
43
+ attribute_table_high_latch: u8,
44
+ attribute_table_low: Register<u16>,
45
+ attribute_table_high: Register<u16>,
46
+
47
+ //
48
+ current_vram_address: u16,
49
+ temporal_vram_address: u16,
50
+
51
+ // Internal VRAM read buffer used for VRAM read(ppudata load).
52
+ vram_read_buffer: u8,
53
+
54
+ vram: Memory,
55
+
56
+ // -- For sprites pixels
57
+
58
+ sprite_availables: [bool; 256],
59
+ sprite_ids: [u8; 256],
60
+ sprite_palette_addresses: [u16; 256],
61
+ sprite_priorities: [u8; 256],
62
+
63
+ // Primary OAM, holds 64 sprites for the frame
64
+ primary_oam: SpritesManager,
65
+
66
+ // Secondary OAM, holds 8 sprites for the current scanline
67
+ secondary_oam: SpritesManager,
68
+
69
+ // -- Eight + one CPU memory-mapped registers
70
+
71
+ // 0x2000. See PpuControlRegister comment.
72
+ ppuctrl: PpuControlRegister,
73
+
74
+ // 0x2001. See PpuMaskRegister comment.
75
+ ppumask: PpuMaskRegister,
76
+
77
+ // 0x2002. See PpuStatusRegister comment.
78
+ ppustatus: PpuStatusRegister,
79
+
80
+ // 0x2003. OAM address 8-bit register.
81
+ // Used for OAM access.
82
+ // Write-only.
83
+ oamaddr: Register<u8>,
84
+
85
+ // 0x2004. OAM Data 8-bit register.
86
+ // Used for data transfer from/to OAM[oamaddr].
87
+ // Writes increments oamaddr after the write.
88
+ oamdata: Register<u8>,
89
+
90
+ // 0x2005. PPU scrolling position 8-bit register.
91
+ // Used to change the scroll position.
92
+ // Write-twice.
93
+ ppuscroll: Register<u8>,
94
+
95
+ // 0x2006. PPU address 8-bit register.
96
+ // Used to set vram_address (0x0000-0x3FFF).
97
+ // First store sets the higher byte while
98
+ // second store sets the lower byte of the address.
99
+ // Write-twice.
100
+ ppuaddr: Register<u8>,
101
+
102
+ // 0x2007. PPU data 8-bit register.
103
+ // Used to transfer data from/to VRAM[vram_address].
104
+ // The access increments vram_addr after the read/write.
105
+ ppudata: Register<u8>,
106
+
107
+ // 0x4014. OAM DMA 8-bit register.
108
+ // This port is located on the CPU.
109
+ // Writing 0xXX will upload 256 bytes of data from CPU page
110
+ // 0xXX00-0xXXFF to the internal PPU OAM.
111
+ // Write-only.
112
+ oamdma: Register<u8>,
113
+
114
+ // Internal data bus used for communication with CPU.
115
+ // This bus behaves as an 8-bit dynamic latch due to large capacitance.
116
+ // Witing to any PPU port(register) or reading any readable
117
+ // PPU port will fill this latch. Reading write-only register returns
118
+ // the latch's current value.
119
+ // @TODO: Support decay. Decaying after a frame or so.
120
+ data_bus: u8,
121
+
122
+ // --
123
+
124
+ display: Box<dyn Display>,
125
+
126
+ pub nmi_interrupted: bool,
127
+ pub irq_interrupted: bool
128
+ }
129
+
130
+ static PALETTES: [u32; 0x40] = [
131
+ /* 0x00 */ 0xff757575,
132
+ /* 0x01 */ 0xff8f1b27,
133
+ /* 0x02 */ 0xffab0000,
134
+ /* 0x03 */ 0xff9f0047,
135
+ /* 0x04 */ 0xff77008f,
136
+ /* 0x05 */ 0xff1300ab,
137
+ /* 0x06 */ 0xff0000a7,
138
+ /* 0x07 */ 0xff000b7f,
139
+ /* 0x08 */ 0xff002f43,
140
+ /* 0x09 */ 0xff004700,
141
+ /* 0x0a */ 0xff005100,
142
+ /* 0x0b */ 0xff173f00,
143
+ /* 0x0c */ 0xff5f3f1b,
144
+ /* 0x0d */ 0xff000000,
145
+ /* 0x0e */ 0xff000000,
146
+ /* 0x0f */ 0xff000000,
147
+ /* 0x10 */ 0xffbcbcbc,
148
+ /* 0x11 */ 0xffef7300,
149
+ /* 0x12 */ 0xffef3b23,
150
+ /* 0x13 */ 0xfff30083,
151
+ /* 0x14 */ 0xffbf00bf,
152
+ /* 0x15 */ 0xff5b00e7,
153
+ /* 0x16 */ 0xff002bdb,
154
+ /* 0x17 */ 0xff0f4fcb,
155
+ /* 0x18 */ 0xff00738b,
156
+ /* 0x19 */ 0xff009700,
157
+ /* 0x1a */ 0xff00ab00,
158
+ /* 0x1b */ 0xff3b9300,
159
+ /* 0x1c */ 0xff8b8300,
160
+ /* 0x1d */ 0xff000000,
161
+ /* 0x1e */ 0xff000000,
162
+ /* 0x1f */ 0xff000000,
163
+ /* 0x20 */ 0xffffffff,
164
+ /* 0x21 */ 0xffffbf3f,
165
+ /* 0x22 */ 0xffff975f,
166
+ /* 0x23 */ 0xfffd8ba7,
167
+ /* 0x24 */ 0xffff7bf7,
168
+ /* 0x25 */ 0xffb777ff,
169
+ /* 0x26 */ 0xff6377ff,
170
+ /* 0x27 */ 0xff3b9bff,
171
+ /* 0x28 */ 0xff3fbff3,
172
+ /* 0x29 */ 0xff13d383,
173
+ /* 0x2a */ 0xff4bdf4f,
174
+ /* 0x2b */ 0xff98f858,
175
+ /* 0x2c */ 0xffdbeb00,
176
+ /* 0x2d */ 0xff000000,
177
+ /* 0x2e */ 0xff000000,
178
+ /* 0x2f */ 0xff000000,
179
+ /* 0x30 */ 0xffffffff,
180
+ /* 0x31 */ 0xffffe7ab,
181
+ /* 0x32 */ 0xffffd7c7,
182
+ /* 0x33 */ 0xffffcbd7,
183
+ /* 0x34 */ 0xffffc7ff,
184
+ /* 0x35 */ 0xffdbc7ff,
185
+ /* 0x36 */ 0xffb3bfff,
186
+ /* 0x37 */ 0xffabdbff,
187
+ /* 0x38 */ 0xffa3e7ff,
188
+ /* 0x39 */ 0xffa3ffe3,
189
+ /* 0x3a */ 0xffbff3ab,
190
+ /* 0x3b */ 0xffcfffb3,
191
+ /* 0x3c */ 0xfff3ff9f,
192
+ /* 0x3d */ 0xff000000,
193
+ /* 0x3e */ 0xff000000,
194
+ /* 0x3f */ 0xff000000
195
+ ];
196
+
197
+ impl Ppu {
198
+ pub fn new(display: Box<dyn Display>) -> Self {
199
+ Ppu {
200
+ frame: 0,
201
+ cycle: 0,
202
+ scanline: 0,
203
+ suppress_vblank: false,
204
+ fine_x_scroll: 0,
205
+ current_vram_address: 0,
206
+ temporal_vram_address: 0,
207
+ vram: Memory::new(vec![0; 16 * 1024]), // 16KB
208
+ data_bus: 0,
209
+ name_table_latch: 0,
210
+ attribute_table_low_latch: 0,
211
+ attribute_table_high_latch: 0,
212
+ pattern_table_low_latch: 0,
213
+ pattern_table_high_latch: 0,
214
+ register_first_store: true,
215
+ sprite_availables: [false; 256],
216
+ sprite_ids: [0; 256],
217
+ sprite_palette_addresses: [0; 256],
218
+ sprite_priorities: [0; 256],
219
+ oamaddr: Register::<u8>::new(),
220
+ oamdata: Register::<u8>::new(),
221
+ oamdma: Register::<u8>::new(),
222
+ primary_oam: SpritesManager::new(Memory::new(vec![0; 256])), // primary 256B
223
+ secondary_oam: SpritesManager::new(Memory::new(vec![0; 32])), // secondary 32B
224
+ vram_read_buffer: 0,
225
+ ppuaddr: Register::<u8>::new(),
226
+ ppudata: Register::<u8>::new(),
227
+ ppuctrl: PpuControlRegister::new(),
228
+ ppumask: PpuMaskRegister::new(),
229
+ ppustatus: PpuStatusRegister::new(),
230
+ ppuscroll: Register::<u8>::new(),
231
+ name_table: Register::<u8>::new(),
232
+ attribute_table_low: Register::<u16>::new(),
233
+ attribute_table_high: Register::<u16>::new(),
234
+ pattern_table_low: Register::<u16>::new(),
235
+ pattern_table_high: Register::<u16>::new(),
236
+ display: display,
237
+ nmi_interrupted: false,
238
+ irq_interrupted: false
239
+ }
240
+ }
241
+
242
+ pub fn bootup(&mut self) {
243
+ self.ppustatus.store(0x80);
244
+ }
245
+
246
+ pub fn reset(&mut self) {
247
+ self.ppuctrl.store(0x00);
248
+ self.ppumask.store(0x00);
249
+ self.ppuscroll.store(0x00);
250
+ self.ppudata.store(0x00);
251
+ self.register_first_store = true;
252
+ self.frame = 0;
253
+ // not sure if I should really reset scanline and cycle
254
+ // but I do for now
255
+ self.scanline = 0;
256
+ self.cycle = 0;
257
+ }
258
+
259
+ pub fn step(&mut self, rom: &mut Rom) {
260
+ self.render_pixel(rom);
261
+ self.shift_registers();
262
+ self.fetch(rom);
263
+ self.evaluate_sprites(rom);
264
+ self.update_flags(rom);
265
+ self.countup_scroll_counters();
266
+ self.countup_cycle();
267
+ }
268
+
269
+ pub fn load_register(&mut self, address: u16, rom: &Rom) -> u8 {
270
+ match address {
271
+ // ppustatus load
272
+ 0x2002 => {
273
+ let value = self.ppustatus.load();
274
+
275
+ // clear vblank after reading 0x2002
276
+ self.ppustatus.clear_vblank();
277
+
278
+ self.register_first_store = true;
279
+
280
+ // unused 4 lsb bits don't override data bus.
281
+ self.data_bus = (value & 0xE0) | (self.data_bus & 0x1F);
282
+
283
+ // reading 0x2002 at cycle=0 and scanline=241
284
+ // won't set vblank flag (7-bit) or fire NMI.
285
+ // reading 0x2002 at cycle=1or2 and scanline=241
286
+ // returns the data as vblank flag is set,
287
+ // clears the flag, and won't fire NMI
288
+
289
+ // Note: update_flags() which can set vblank is called
290
+ // after this method in the same cycle, so set supress_vblank true
291
+ // even at cycle=1 not only cycle=0
292
+
293
+ if self.scanline == 241 && (self.cycle == 0 || self.cycle == 1) {
294
+ self.suppress_vblank = true;
295
+ }
296
+
297
+ value | match self.scanline == 241 && (self.cycle == 1 || self.cycle == 2) {
298
+ true => 0x80,
299
+ false => 0x00
300
+ }
301
+ },
302
+ // oamdata load
303
+ 0x2004 => {
304
+ let value = self.primary_oam.load(self.oamaddr.load());
305
+ self.data_bus = value;
306
+ value
307
+ },
308
+ // ppudata load
309
+ 0x2007 => {
310
+ // Reading ppudata updates the VRAM read buffer with VRAM[vram_address]
311
+ // and returns the internal VRAM read buffer.
312
+ // They work differently depending on the reading address.
313
+ // 0x0000-0x3EFF: Update the buffer after returning the content of the buffer
314
+ // 0x3F00-0x3FFF: Immediately update the buffer before returning the content of the buffer
315
+ let value = self.load(self.current_vram_address, rom);
316
+ let return_value = match self.current_vram_address {
317
+ 0..=0x3EFF => self.vram_read_buffer,
318
+ _ => value
319
+ };
320
+ self.vram_read_buffer = value;
321
+
322
+ // @TODO: Support greyscale if needed
323
+
324
+ // Accessing ppudata increments vram_address
325
+ self.increment_vram_address();
326
+ self.data_bus = return_value;
327
+ self.data_bus
328
+ },
329
+ _ => self.data_bus
330
+ }
331
+ }
332
+
333
+ pub fn store_register(&mut self, address: u16, value: u8, rom: &mut Rom) {
334
+ // Writing to any PPU port(register) from CPU fills the latch (data_bus).
335
+ self.data_bus = value;
336
+
337
+ match address {
338
+ // ppuctrl store
339
+ 0x2000 => {
340
+ // Ignore the write to this register
341
+ // for about 30k cycles after power/reset.
342
+ // But I found some test roms writes to 0x2000
343
+ // right after power on so commenting out so far.
344
+
345
+ //if self.frame == 0 && self.scanline <= 88 {
346
+ // return;
347
+ //}
348
+
349
+ let previous_nmi_enabled = self.ppuctrl.is_nmi_enabled();
350
+ self.ppuctrl.store(value);
351
+
352
+ // Immediately generate an NMI if the PPU is currently
353
+ // in vertical blank, PPUSTATUS vblank flag is still set,
354
+ // and changing the NMI flag from 0 to 1
355
+ if self.ppustatus.is_vblank() &&
356
+ !previous_nmi_enabled &&
357
+ self.ppuctrl.is_nmi_enabled() {
358
+ self.nmi_interrupted = true;
359
+ }
360
+
361
+ // Copy the 1-0 bits of value to 11-10 bits of temporal vram_address for scrolling
362
+ // Refer to http://wiki.nesdev.com/w/index.php/PPU_scrolling
363
+ self.temporal_vram_address &= 0xF3FF;
364
+ self.temporal_vram_address |= ((value as u16) & 0x3) << 10;
365
+ },
366
+ // ppumask store
367
+ 0x2001 => {
368
+ self.ppumask.store(value);
369
+ },
370
+ // oamaddr store
371
+ 0x2003 => {
372
+ self.oamaddr.store(value);
373
+ },
374
+ // oamdata store
375
+ 0x2004 => {
376
+ self.oamdata.store(value);
377
+ self.primary_oam.store(self.oamaddr.load(), value);
378
+ self.oamaddr.increment();
379
+ },
380
+ // ppuscroll store
381
+ 0x2005 => {
382
+ self.ppuscroll.store(value);
383
+
384
+ if self.register_first_store {
385
+ // Copy 2-0 bits of the value to fine_x_scroll and
386
+ // 7-3 bits of the value to 4-0 bits of the temporal vram_address for scrolling
387
+ // Refer to http://wiki.nesdev.com/w/index.php/PPU_scrolling
388
+ self.fine_x_scroll = value & 0x7;
389
+ self.temporal_vram_address &= 0xFFE0;
390
+ self.temporal_vram_address |= ((value as u16) >> 3) & 0x1F;
391
+ } else {
392
+ // Copy 2-0 bits of the value to 14-12 bits of the temporal vram_address and
393
+ // 7-3 bits of the value to 9-5 bits of the temporal vram_address for scrolling
394
+ // Refer to http://wiki.nesdev.com/w/index.php/PPU_scrolling
395
+ self.temporal_vram_address &= 0x8C1F;
396
+ self.temporal_vram_address |= ((value as u16) & 0xF8) << 2;
397
+ self.temporal_vram_address |= ((value as u16) & 0x7) << 12;
398
+ }
399
+
400
+ self.register_first_store = !self.register_first_store;
401
+ },
402
+ // ppuaddr store
403
+ 0x2006 => {
404
+ // First store sets the higher byte of the vram_address.
405
+ // Second store sets the lower byte of the vram_address.
406
+ // Refer to http://wiki.nesdev.com/w/index.php/PPU_scrolling
407
+ if self.register_first_store {
408
+ self.temporal_vram_address &= 0x00FF;
409
+ self.temporal_vram_address |= ((value as u16) & 0x3F) << 8;
410
+ } else {
411
+ self.ppuaddr.store(value);
412
+ self.temporal_vram_address &= 0xFF00;
413
+ self.temporal_vram_address |= value as u16;
414
+ self.current_vram_address = self.temporal_vram_address;
415
+ }
416
+
417
+ self.register_first_store = !self.register_first_store;
418
+ },
419
+ // ppudata store
420
+ 0x2007 => {
421
+ self.ppudata.store(value);
422
+ self.store(self.current_vram_address, value, rom);
423
+ // Accessing ppudata increments vram_address
424
+ self.increment_vram_address();
425
+ },
426
+ // oamdma store
427
+ 0x4014 => {
428
+ self.oamdma.store(value);
429
+ // DMA is processed in Cpu
430
+ },
431
+ _ => {}
432
+ }
433
+ }
434
+
435
+ fn load(&self, mut address: u16, rom: &Rom) -> u8 {
436
+ address = address & 0x3FFF; // just in case
437
+
438
+ // 0x0000 - 0x1FFF is mapped with cartridge's CHR-ROM if exists.
439
+ // Otherwise load from VRAM.
440
+
441
+ match address < 0x2000 && rom.has_chr_rom() {
442
+ true => rom.load(address as u32),
443
+ false => self.vram.load(self.convert_vram_address(address, rom) as u32)
444
+ }
445
+ }
446
+
447
+ fn store(&mut self, mut address: u16, value: u8, rom: &mut Rom) {
448
+ address = address & 0x3FFF; // just in case
449
+
450
+ // 0x0000 - 0x1FFF is mapped with cartridge's CHR-ROM if exists.
451
+ // Otherwise store to VRAM.
452
+
453
+ match address < 0x2000 && rom.has_chr_rom() {
454
+ true => rom.store(address as u32, value),
455
+ false => self.vram.store(self.convert_vram_address(address, rom) as u32, value)
456
+ };
457
+ }
458
+
459
+ fn convert_vram_address(&self, address: u16, rom: &Rom) -> u16 {
460
+ // 0x0000 - 0x0FFF: pattern table 0
461
+ // 0x1000 - 0x1FFF: pattern table 1
462
+ // 0x2000 - 0x23FF: nametable 0
463
+ // 0x2400 - 0x27FF: nametable 1
464
+ // 0x2800 - 0x2BFF: nametable 2
465
+ // 0x2C00 - 0x2FFF: nametable 3
466
+ // 0x3000 - 0x3EFF: Mirrors of 0x2000 - 0x2EFF
467
+ // 0x3F00 - 0x3F1F: Palette RAM indices
468
+ // 0x3F20 - 0x3FFF: Mirrors of 0x3F00 - 0x3F1F
469
+
470
+ match address {
471
+ 0..=0x1FFF => address,
472
+ 0x2000..=0x3EFF => self.get_name_table_address_with_mirroring(address & 0x2FFF, rom),
473
+ _ /* 0x3F00..=0x3FFF */ => {
474
+ // Addresses for palette
475
+ // 0x3F10/0x3F14/0x3F18/0x3F1C are mirrors of
476
+ // 0x3F00/0x3F04/0x3F08/0x3F0C.
477
+ match address {
478
+ 0x3F10 => 0x3F00,
479
+ 0x3F14 => 0x3F04,
480
+ 0x3F18 => 0x3F08,
481
+ 0x3F1C => 0x3F0C,
482
+ _ => address
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ fn render_pixel(&mut self, rom: &Rom) {
489
+ // Note: this comparison order is for performance.
490
+ if self.cycle >= 257 || self.scanline >= 240 || self.cycle == 0 {
491
+ return;
492
+ }
493
+
494
+ // guaranteed that cycle is equal to or greater than 1 here, see the above
495
+ let x = (self.cycle - 1) % 256; // @TODO: Somehow -2 generates pixel at right position, why?
496
+ let y = self.scanline;
497
+
498
+ let background_visible = self.ppumask.is_background_visible() &&
499
+ (self.ppumask.is_left_most_background_visible() || x >= 8);
500
+ let sprites_visible = self.ppumask.is_sprites_visible() &&
501
+ (self.ppumask.is_left_most_sprites_visible() || x >= 8);
502
+ let background_palette_address = self.get_background_palette_address();
503
+ let sprite_available = self.sprite_availables[x as usize];
504
+ let sprite_palette_address = self.sprite_palette_addresses[x as usize];
505
+ let sprite_id = self.sprite_ids[x as usize];
506
+ let sprite_priority = self.sprite_priorities[x as usize];
507
+
508
+ let is_background_pixel_zero = !background_visible || (background_palette_address & 0x3) == 0;
509
+ let is_sprite_pixel_zero = !sprites_visible || !sprite_available || (sprite_palette_address & 0x3) == 0;
510
+
511
+ // Select output color
512
+ // | bg | sprite | pri | out |
513
+ // | --- | ------ | --- | ---------- |
514
+ // | 0 | 0 | - | bg(0x3F00) |
515
+ // | 0 | 1-3 | - | sprite |
516
+ // | 1-3 | 0 | - | bg |
517
+ // | 1-3 | 1-3 | 0 | sprite |
518
+ // | 1-3 | 1-3 | 1 | bg |
519
+
520
+ let palette_address = match is_background_pixel_zero {
521
+ true => match is_sprite_pixel_zero {
522
+ true => 0x3F00, // universal_background_palette_address
523
+ false => sprite_palette_address
524
+ },
525
+ false => match is_sprite_pixel_zero {
526
+ true => background_palette_address,
527
+ false => match sprite_priority == 0 {
528
+ true => sprite_palette_address,
529
+ false => background_palette_address
530
+ }
531
+ }
532
+ };
533
+
534
+ let c = self.get_emphasis_color(self.load_palette(self.load(palette_address, rom)));
535
+
536
+ // Sprite zero hit test.
537
+ // Set zero hit flag when a nonzero pixel of sprite 0 overlaps
538
+ // a nonzero background pixel.
539
+ // Hit doesn't trigger in any area where the background or sprites are hidden.
540
+ // Sprite priority doesn't have effect to zero hit.
541
+ if sprite_id == 0 &&
542
+ !is_sprite_pixel_zero &&
543
+ !is_background_pixel_zero {
544
+ self.ppustatus.set_zero_hit();
545
+ }
546
+
547
+ self.display.render_pixel(x, y, c);
548
+ }
549
+
550
+ fn get_background_palette_address(&self) -> u16 {
551
+ // fine_x_scroll selects 16-bit shifts register.
552
+ let pos = 15 - (self.fine_x_scroll & 0xF);
553
+
554
+ let offset = (self.attribute_table_high.load_bit(pos) << 3) |
555
+ (self.attribute_table_low.load_bit(pos) << 2) |
556
+ (self.pattern_table_high.load_bit(pos) << 1) |
557
+ self.pattern_table_low.load_bit(pos);
558
+
559
+ // background palette indices are in 0x3F00-0x3F0F
560
+ 0x3F00 + offset as u16
561
+ }
562
+
563
+ fn shift_registers(&mut self) {
564
+ if self.scanline >= 240 && self.scanline <= 260 {
565
+ return;
566
+ }
567
+
568
+ if (self.cycle >= 1 && self.cycle <= 256) ||
569
+ (self.cycle >= 329 && self.cycle <= 336) {
570
+ self.pattern_table_low.shift(0);
571
+ self.pattern_table_high.shift(0);
572
+ self.attribute_table_low.shift(0);
573
+ self.attribute_table_high.shift(0);
574
+ }
575
+ }
576
+
577
+ fn fetch(&mut self, rom: &Rom) {
578
+ // No fetch during post-rendering scanline 240 and vblank interval 241-260
579
+ if self.scanline >= 240 && self.scanline <= 260 {
580
+ return;
581
+ }
582
+
583
+ // In visible scanlines 0-239
584
+ // Cycle 0:
585
+ // Idle
586
+ // Cycle 1-256:
587
+ // The data for each tile is fetched during this phase.
588
+ // Each memory access takes 2 cycles to complete,
589
+ // and 4 must be performed per tile
590
+ // - Nametable byte
591
+ // - Attribute table byte
592
+ // - Pattern table tile low
593
+ // - Pattern table tile high
594
+ // Cycle 257-320: @TODO
595
+ // Cycle 321-336: @TODO
596
+ // Cycle 337-340: @TODO
597
+
598
+ if self.cycle == 0 {
599
+ return;
600
+ }
601
+
602
+ if (self.cycle >= 257 && self.cycle <= 320) || self.cycle >= 337 {
603
+ return;
604
+ }
605
+
606
+ // Every 8 cycles, the data for the next tile is loaded into the upper 8 bits
607
+ // of this shift register. Meanwhile, the pixel to render is fetched from one
608
+ // of the lower 8 bits.
609
+
610
+ // These registers are fed by a latch which contains the palette attribute
611
+ // for the next tile. Every 8 cycles, the latch is loaded with the palette
612
+ // attribute for the next tile.
613
+
614
+ // In each 8 cycles,
615
+ // 0-1: @TODO
616
+ // 2-3: @TODO
617
+ // 4-5: @TODO
618
+ // 6-7: @TODO
619
+
620
+ // self.cycle is equal to or greater than 1 here, see the above.
621
+
622
+ match (self.cycle - 1) % 8 {
623
+ 0 => {
624
+ self.fetch_name_table(rom);
625
+ self.name_table.store(self.name_table_latch);
626
+ self.attribute_table_low.store_lower_byte(self.attribute_table_low_latch);
627
+ self.attribute_table_high.store_lower_byte(self.attribute_table_high_latch);
628
+ self.pattern_table_low.store_lower_byte(self.pattern_table_low_latch);
629
+ self.pattern_table_high.store_lower_byte(self.pattern_table_high_latch);
630
+ },
631
+ 2 => self.fetch_attribute_table(rom),
632
+ 4 => self.fetch_pattern_table_low(rom),
633
+ 6 => self.fetch_pattern_table_high(rom),
634
+ _ => {}
635
+ };
636
+ }
637
+
638
+ fn fetch_name_table(&mut self, rom: &Rom) {
639
+ // A nametable is a 1024 byte area of memory used by the PPU to lay out backgrounds.
640
+ // Each byte in the nametable controls one 8x8 pixel character cell, and each nametable
641
+ // has 30 rows of 32 tiles each, for 960 (0x3C0) bytes; the rest is used by each nametable's
642
+ // attribute table. With each tile being 8x8 pixels, this makes a total of 256x240 pixels
643
+ // in one map, the same size as one full screen.
644
+
645
+ // Name table entries are in 0x2000-0x2FFF.
646
+ // Four name tables and 0x400 bytes per name table.
647
+ // The last 64 bytes of each include attribute table.
648
+ // Nametable 0: 0x2000-0x23FF (attribute table 0x23C0-0x23FF)
649
+ // Nametable 1: 0x2400-0x27FF (attribute table 0x27C0-0x27FF)
650
+ // Nametable 2: 0x2800-0x2BFF (attribute table 0x2BC0-0x2BFF)
651
+ // Nametable 3: 0x2C00-0x2FFF (attribute table 0x2FC0-0x2FFF)
652
+
653
+ // Here fetches a tile of a nametable.
654
+
655
+ // address is from http://wiki.nesdev.com/w/index.php/PPU_scrolling
656
+ self.name_table_latch = self.load(0x2000 | (self.current_vram_address & 0x0FFF), rom);
657
+ }
658
+
659
+ fn fetch_attribute_table(&mut self, rom: &Rom) {
660
+ // @TODO: Implement properly
661
+
662
+ // The attribute table is a 64-byte array at the end of each nametable
663
+ // that controls which palette is assigned to each part of the background.
664
+ // Each attribute table, starting at 0x23C0, 0x27C0, 0x2BC0, or 0x2FC0,
665
+ // is arranged as an 8x8 byte array.
666
+
667
+ // vram_address
668
+ // 13-12: fine y scroll
669
+ // 11-10: nametable select
670
+ // 9-5: coarse y scroll
671
+ // 4-0: coarse x scroll
672
+ let v = self.current_vram_address;
673
+ let coarse_y = (v >> 5) & 0x1F;
674
+ let coarse_x = v & 0x1F;
675
+
676
+ // attribute address the lower 12-bits
677
+ // 11-10: name table select
678
+ // 9-6: attribute offset (960 bytes)
679
+ // 5-3: high 3bits of coarse y
680
+ // 2-0: high 3bits of coarse x
681
+ // From http://wiki.nesdev.com/w/index.php/PPU_scrolling
682
+ let byte = self.load(0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07), rom);
683
+
684
+ // byte includes four two bits
685
+ // 7-6: bottom right
686
+ // 5-4: bottom left
687
+ // 3-2: top right
688
+ // 1-0: top left
689
+
690
+ // @TODO: Optimize pos calculation with bit wise operation?
691
+ let is_bottom = (coarse_y & 0x3) >= 2;
692
+ let is_right = (coarse_x & 0x3) >= 2;
693
+ let pos = match is_bottom {
694
+ true => {
695
+ match is_right {
696
+ true => 6, // bottomright
697
+ false => 4 // bottomleft
698
+ }
699
+ },
700
+ false => {
701
+ match is_right {
702
+ true => 2, // topright
703
+ false => 0 // topleft
704
+ }
705
+ }
706
+ };
707
+
708
+ let value = (byte >> pos) & 0x3;
709
+
710
+ self.attribute_table_high_latch = match value & 2 {
711
+ 2 => 0xff,
712
+ _ => 0
713
+ };
714
+
715
+ self.attribute_table_low_latch = match value & 1 {
716
+ 1 => 0xff,
717
+ _ => 0
718
+ };
719
+ }
720
+
721
+ fn fetch_pattern_table_low(&mut self, rom: &Rom) {
722
+ let fine_scroll_y = (self.current_vram_address >> 12) & 0x7;
723
+ let index = self.ppuctrl.background_pattern_table_base_address() +
724
+ ((self.name_table.load() as u16) << 4) + fine_scroll_y;
725
+ self.pattern_table_low_latch = self.load(index, rom);
726
+ }
727
+
728
+ fn fetch_pattern_table_high(&mut self, rom: &Rom) {
729
+ let fine_scroll_y = (self.current_vram_address >> 12) & 0x7;
730
+ let index = self.ppuctrl.background_pattern_table_base_address() +
731
+ ((self.name_table.load() as u16) << 4) + fine_scroll_y;
732
+ self.pattern_table_high_latch = self.load(index + 0x8, rom);
733
+ }
734
+
735
+ fn update_flags(&mut self, rom: &mut Rom) {
736
+ if self.cycle == 1 {
737
+ if self.scanline == 241 {
738
+ // set vblank and occur NMI at cycle 1 in scanline 241
739
+ if !self.suppress_vblank {
740
+ self.ppustatus.set_vblank();
741
+ }
742
+ self.suppress_vblank = false;
743
+ // Pixels for this frame should be ready so update the display
744
+ self.display.vblank();
745
+ } else if self.scanline == 261 {
746
+ // clear vblank, sprite zero hit flag,
747
+ // and sprite overflow flags at cycle 1 in pre-render line 261
748
+ self.ppustatus.clear_vblank();
749
+ self.ppustatus.clear_zero_hit();
750
+ self.ppustatus.clear_overflow();
751
+ }
752
+ }
753
+
754
+ // According to http://wiki.nesdev.com/w/index.php/PPU_frame_timing#VBL_Flag_Timing
755
+ // reading 0x2002 at cycle=2 and scanline=241 can suppress NMI
756
+ // so firing NMI at some cycles away not at cycle=1 so far
757
+
758
+ // There is a chance that CPU 0x2002 read gets the data vblank flag set
759
+ // before CPU starts NMI interrupt routine.
760
+ // CPU instructions take multiple CPU clocks to complete.
761
+ // If CPU starts an operation of an istruction including 0x2002 read right before
762
+ // PPU sets vblank flag and fires NMI,
763
+ // the 0x2002 read gets the data with vblank flag set even before
764
+ // CPU starts NMI routine.
765
+ //
766
+ // CPU PPU
767
+ // 1. instruction operation start
768
+ // 2. - doing something vblank start and fire NMI
769
+ // 3. - read 0x2002 with
770
+ // vblank flag set
771
+ // 4. - doing something
772
+ // 5. Notice NMI and start
773
+ // NMI routine
774
+ //
775
+ // It seems some games rely on this behavior.
776
+ // To simulate this behavior we fire NMI at cycle=20 so far.
777
+ // If CPU reads 0x2002 between PPU cycle 3~20 it gets data
778
+ // vblank flag set before NMI routine.
779
+ // (reading at cycle 1~2 suppresses NMI, see load_register())
780
+ // @TODO: Safer and more appropriate approach.
781
+
782
+ if self.cycle == 20 && self.scanline == 241 {
783
+ if self.ppustatus.is_vblank() &&
784
+ self.ppuctrl.is_nmi_enabled() {
785
+ self.nmi_interrupted = true;
786
+ }
787
+ }
788
+
789
+ // @TODO: check this driving IRQ counter for MMC3Mapper timing is correct
790
+ // @TODO: This is MMC3Mapper specific. Should this be here?
791
+
792
+ if self.cycle == 340 && self.scanline <= 240 &&
793
+ self.ppumask.is_background_visible() &&
794
+ rom.irq_interrupted() {
795
+ self.irq_interrupted = true
796
+ }
797
+ }
798
+
799
+ fn countup_scroll_counters(&mut self) {
800
+ if !self.ppumask.is_background_visible() && !self.ppumask.is_sprites_visible() {
801
+ return;
802
+ }
803
+
804
+ if self.scanline >= 240 && self.scanline <= 260 {
805
+ return;
806
+ }
807
+
808
+ if self.scanline == 261 {
809
+ if self.cycle >= 280 && self.cycle <= 304 {
810
+ self.current_vram_address &= !0x7BE0;
811
+ self.current_vram_address |= self.temporal_vram_address & 0x7BE0;
812
+ }
813
+ }
814
+
815
+ if self.cycle == 0 || (self.cycle >= 258 && self.cycle <= 320) {
816
+ return;
817
+ }
818
+
819
+ if (self.cycle % 8) == 0 {
820
+ let mut v = self.current_vram_address;
821
+
822
+ // this is from http://wiki.nesdev.com/w/index.php/PPU_scrolling
823
+ if (v & 0x1F) == 31 {
824
+ v &= !0x1F;
825
+ v ^= 0x400;
826
+ } else {
827
+ v += 1;
828
+ }
829
+
830
+ self.current_vram_address = v;
831
+ }
832
+
833
+ if self.cycle == 256 {
834
+ // Increments the vertical position of vram_address
835
+ // @TODO: Only if rendering is enabled?
836
+ let mut v = self.current_vram_address;
837
+
838
+ // From http://wiki.nesdev.com/w/index.php/PPU_scrolling
839
+ if (v & 0x7000) != 0x7000 {
840
+ v += 0x1000;
841
+ } else {
842
+ v &= !0x7000;
843
+ let mut y = (v & 0x3E0) >> 5;
844
+
845
+ if y == 29 {
846
+ y = 0;
847
+ v ^= 0x800;
848
+ } else if y == 31 {
849
+ y = 0;
850
+ } else {
851
+ y += 1;
852
+ }
853
+
854
+ v = (v & !0x3E0) | (y << 5);
855
+ }
856
+
857
+ self.current_vram_address = v;
858
+ } else if self.cycle == 257 {
859
+ // Copies all bits related to horizontal position from
860
+ // temporal to current vram_address
861
+ // @TODO: Only if rendering is enabled?
862
+ // From http://wiki.nesdev.com/w/index.php/PPU_scrolling
863
+ self.current_vram_address &= !0x41F;
864
+ self.current_vram_address |= self.temporal_vram_address & 0x41F;
865
+ }
866
+ }
867
+
868
+ fn countup_cycle(&mut self) {
869
+ // cycle: 0 - 340
870
+ // scanline: 0 - 261
871
+ self.cycle += 1;
872
+ if self.cycle > 340 {
873
+ self.cycle = 0;
874
+ self.scanline += 1;
875
+
876
+ if self.scanline > 261 {
877
+ self.scanline = 0;
878
+ self.frame += 1;
879
+ }
880
+ }
881
+ }
882
+
883
+ //
884
+
885
+ fn increment_vram_address(&mut self) {
886
+ // Increments vram address based on ppuctrl, 1 or 32
887
+ self.current_vram_address += self.ppuctrl.increment_address_size() as u16;
888
+ self.current_vram_address &= 0x7FFF;
889
+ self.ppuaddr.store(self.current_vram_address as u8 & 0xFF);
890
+ }
891
+
892
+ fn evaluate_sprites(&mut self, rom: &Rom) {
893
+ // oamaddr is set to 0 during cycle 257-320 of the pre-render and visible scanlines.
894
+ // @TODO: Optimize
895
+ if (self.scanline < 240 || self.scanline == 261) &&
896
+ self.cycle >= 257 && self.cycle <= 320 {
897
+ self.oamaddr.store(0);
898
+ }
899
+
900
+ // During all visible scanlines(0-239),
901
+ // the PPU scans through OAM to determine which sprites
902
+ // to render on the next scanline
903
+
904
+ if self.scanline >= 240 {
905
+ return;
906
+ }
907
+
908
+ // Cycles
909
+ // 1-64: Secondary OAM is initialized to 0xff.
910
+ // 65-256: Sprite evaluation
911
+ // 257-320: Sprite fetches
912
+ // 321-340+0: Background render pipeline initialization
913
+ if self.cycle == 1 {
914
+ // Initialize at a time at cycle 1 due to performance
915
+ // and simplicity so far
916
+ self.secondary_oam.reset();
917
+ } else if self.cycle == 257 {
918
+ // Evaluate at a time at cycle 257 due to performance
919
+ // and simplicity so far
920
+ self.process_sprite_pixels(rom);
921
+ }
922
+ }
923
+
924
+ fn process_sprite_pixels(&mut self, rom: &Rom) {
925
+ for i in 0..self.sprite_availables.len() {
926
+ self.sprite_availables[i] = false;
927
+ }
928
+
929
+ let y = self.scanline as u8;
930
+ let height = self.ppuctrl.sprite_height();
931
+ let mut n = 0;
932
+
933
+ // Find up to eight sprite on this scan line from primary OAM and
934
+ // copy them to secondary OAM.
935
+ // And process all bits of a scanline for sprites here now
936
+ // for the performance and simplicity.
937
+ for i in 0..64 {
938
+ let s = self.primary_oam.get(i);
939
+ if s.on(y, height) {
940
+ if n >= 8 {
941
+ // Set sprite overflow flag if
942
+ // more than eight sprites appear on a scanline
943
+ self.ppustatus.set_overflow();
944
+ break;
945
+ }
946
+ let base_x = s.get_x();
947
+ let y_in_sprite = s.get_y_in_sprite(y, height);
948
+ let msb = s.get_palette_num() as u16;
949
+ for j in 0..8 {
950
+ //
951
+ if base_x as u16 + j as u16 >= 256 {
952
+ break;
953
+ }
954
+ let x = base_x + j;
955
+ // No override with later sprites
956
+ if self.sprite_availables[x as usize] {
957
+ continue;
958
+ }
959
+ let x_in_sprite = match s.horizontal_flip() {
960
+ true => 7 - j,
961
+ false => j
962
+ };
963
+ // pattern table holds the lowest two bits of palette memory address
964
+ let lsb = self.get_pattern_table_element_for_sprite(&s, x_in_sprite, y_in_sprite, height, rom) as u16;
965
+ // the lowest two 0 bits means transparent (=no sprite pixel)
966
+ if lsb != 0 {
967
+ self.sprite_availables[x as usize] = true;
968
+ // Sprite palette indices are in 0x3F10-0x3F1F
969
+ self.sprite_palette_addresses[x as usize] = 0x3F10 | (msb << 2) | lsb;
970
+ self.sprite_ids[x as usize] = i;
971
+ self.sprite_priorities[x as usize] = s.get_priority();
972
+ }
973
+ }
974
+ self.secondary_oam.copy(n, s);
975
+ n += 1;
976
+ }
977
+ }
978
+ }
979
+
980
+ fn get_name_table_address_with_mirroring(&self, address: u16, rom: &Rom) -> u16 {
981
+ let name_table_address = address & 0x2C00;
982
+ (address & 0x3FF) | match rom.mirroring_type() {
983
+ Mirrorings::SingleScreen => 0x2000,
984
+ Mirrorings::Horizontal => match name_table_address {
985
+ 0x2000 => 0x2000,
986
+ 0x2400 => 0x2000,
987
+ 0x2800 => 0x2800,
988
+ _ /* 0x2C00 */ => 0x2800
989
+ },
990
+ Mirrorings::Vertical => match name_table_address {
991
+ 0x2000 => 0x2000,
992
+ 0x2400 => 0x2400,
993
+ 0x2800 => 0x2000,
994
+ _ /* 0x2C00 */ => 0x2400
995
+ },
996
+ Mirrorings::FourScreen => name_table_address
997
+ }
998
+ }
999
+
1000
+ fn get_pattern_table_element_for_sprite(&self, s: &Sprite, x_in_sprite: u8, y_in_sprite: u8, height: u8, rom: &Rom) -> u8 {
1001
+ // Get an element from pattern table consisting of the lowest two bits
1002
+ // of palette memory address for sprites
1003
+
1004
+ // 8x8 sprite and 8x16 sprite calculates tile address differently
1005
+ let address = match height == 8 {
1006
+ true => {
1007
+ // 8x8 sprite
1008
+ // ppuctrl selects base address 0x0000 or 0x1000
1009
+ let base_address = self.ppuctrl.sprite_pattern_table_base_address();
1010
+ // Each tile has 16bytes
1011
+ let byte_offset = s.get_tile_index() as u16 * 0x10;
1012
+ // A tile has 8x2 rows
1013
+ let row = y_in_sprite as u16;
1014
+ base_address + byte_offset + row
1015
+ },
1016
+ false => {
1017
+ // 8x16 sprite
1018
+ // Ignore base address selected by ppuctrl but
1019
+ // 0-bit of sprite tile_index selects 0x0000 or 0x1000
1020
+ let tile_index = s.get_tile_index() as u16;
1021
+ let base_address = (tile_index & 1) * 0x1000;
1022
+ // 7-1 bits of tile_index maps to 0-254 tiles
1023
+ let byte_offset = (tile_index & 0xFE) * 0x10;
1024
+ // Eatch 16-byte tile has 8x8 pixels then bottom half pixel needs to see next tile
1025
+ let row = ((y_in_sprite % 8) + ((y_in_sprite & 0x8) << 1)) as u16;
1026
+ base_address + byte_offset + row
1027
+ }
1028
+ };
1029
+
1030
+ // Each tile has 16bytes (8x2 rows)
1031
+ // The first 8bytes in a tile are for 0-bit,
1032
+ // while the second 8bytes are for 1-bit of palette memory address
1033
+ let lower_bits = self.load(address, rom);
1034
+ let higher_bits = self.load(address + 8, rom);
1035
+ let pos = 7 - x_in_sprite; // xxx_bits[7:0] corresponds to x_in_sprite[0:7]
1036
+ (((higher_bits >> pos) & 1) << 1) | ((lower_bits >> pos) & 1)
1037
+ }
1038
+
1039
+ fn load_palette(&self, address: u8) -> u32 {
1040
+ // In greyscale mode, mask the palette index with 0x30 and
1041
+ // read from the grey column 0x00, 0x10, 0x20, or 0x30
1042
+ let mask = match self.ppumask.is_greyscale() {
1043
+ true => 0x30,
1044
+ false => 0xFF
1045
+ };
1046
+ PALETTES[(address & mask) as usize] & 0xFFFFFF
1047
+ }
1048
+
1049
+ fn get_emphasis_color(&self, mut c: u32) -> u32 {
1050
+ // Color emphasis bases on ppumask
1051
+ // @TODO: Implement properly
1052
+ if self.ppumask.is_emphasis_red() {
1053
+ c = c | 0x00FF0000;
1054
+ }
1055
+ if self.ppumask.is_emphasis_green() {
1056
+ c = c | 0x0000FF00;
1057
+ }
1058
+ if self.ppumask.is_emphasis_blue() {
1059
+ c = c | 0x000000FF;
1060
+ }
1061
+ c
1062
+ }
1063
+
1064
+ pub fn get_display(&self) -> &Box<dyn Display> {
1065
+ &self.display
1066
+ }
1067
+ }
1068
+
1069
+ // PPU control 8-bit register.
1070
+ // CPU memory-mapped at 0x2000
1071
+ // Write-only
1072
+
1073
+ pub struct PpuControlRegister {
1074
+ register: Register<u8>
1075
+ }
1076
+
1077
+ impl PpuControlRegister {
1078
+ fn new() -> Self {
1079
+ PpuControlRegister {
1080
+ register: Register::<u8>::new()
1081
+ }
1082
+ }
1083
+
1084
+ fn _load(&self) -> u8 {
1085
+ self.register.load()
1086
+ }
1087
+
1088
+ fn store(&mut self, value: u8) {
1089
+ self.register.store(value);
1090
+ }
1091
+
1092
+ // Bit 7. Flag indicating whether generating NMI
1093
+ // at the start of the vertical blanking interval
1094
+ // -- 0: disabled, 1: enabled
1095
+ fn is_nmi_enabled(&self) -> bool {
1096
+ self.register.is_bit_set(7)
1097
+ }
1098
+
1099
+ // Bit 6. PPU master/slave select
1100
+ // @TODO: Implement
1101
+
1102
+ // Bit 5. Sprite height
1103
+ // -- 0: 8 (8x8 pixels), 1: 16 (8x16 pixels)
1104
+ fn sprite_height(&self) -> u8 {
1105
+ match self.register.is_bit_set(5) {
1106
+ false => 8,
1107
+ true => 16
1108
+ }
1109
+ }
1110
+
1111
+ // Bit 4. Background pattern table address
1112
+ // -- 0: 0x0000, 1: 0x1000
1113
+ fn background_pattern_table_base_address(&self) -> u16 {
1114
+ match self.register.is_bit_set(4) {
1115
+ false => 0,
1116
+ true => 0x1000
1117
+ }
1118
+ }
1119
+
1120
+ // Bit 3. Sprite pattern table address for 8x8 sprites
1121
+ // -- 0: 0x0000, 1: 0x1000
1122
+ fn sprite_pattern_table_base_address(&self) -> u16 {
1123
+ match self.register.is_bit_set(3) {
1124
+ false => 0,
1125
+ true => 0x1000
1126
+ }
1127
+ }
1128
+
1129
+ // Bit 2. VRAM address increment per CPU read/write of PPUDATA
1130
+ // -- 0: 1, 1: 32
1131
+ fn increment_address_size(&self) -> u8 {
1132
+ match self.register.is_bit_set(2) {
1133
+ false => 1,
1134
+ true => 32
1135
+ }
1136
+ }
1137
+
1138
+ // Bit 0-1. Base nametable address
1139
+ // -- 0: 0x2000, 1: 0x2400, 2: 0x2800, 3: 0x2C00
1140
+ fn _base_name_table_address(&self) -> u16 {
1141
+ match self.register.load_bits(0, 2) {
1142
+ 0 => 0x2000,
1143
+ 1 => 0x2400,
1144
+ 2 => 0x2800,
1145
+ _ => 0x2C00
1146
+ }
1147
+ }
1148
+ }
1149
+
1150
+ // PPU mask 8-bit register
1151
+ // CPU memory-mapped at 0x2001
1152
+ // Write-only
1153
+ pub struct PpuMaskRegister {
1154
+ register: Register<u8>
1155
+ }
1156
+
1157
+ impl PpuMaskRegister {
1158
+ fn new() -> Self {
1159
+ PpuMaskRegister {
1160
+ register: Register::<u8>::new()
1161
+ }
1162
+ }
1163
+
1164
+ fn _load(&self) -> u8 {
1165
+ self.register.load()
1166
+ }
1167
+
1168
+ fn store(&mut self, value: u8) {
1169
+ self.register.store(value);
1170
+ }
1171
+
1172
+ // Bit 7. Emphasizes blue
1173
+ fn is_emphasis_blue(&self) -> bool {
1174
+ self.register.is_bit_set(7)
1175
+ }
1176
+
1177
+ // Bit 6. Emphasizes green on the NTSC, while red on the PAL
1178
+ fn is_emphasis_green(&self) -> bool {
1179
+ self.register.is_bit_set(6)
1180
+ }
1181
+
1182
+ // Bit 5. Emphasizes ref on the NTSC, while green on the PAL
1183
+ fn is_emphasis_red(&self) -> bool {
1184
+ self.register.is_bit_set(5)
1185
+ }
1186
+
1187
+ // Bit 4. Show sprites.
1188
+ // -- 0: invisible, 1: visible
1189
+ fn is_sprites_visible(&self) -> bool {
1190
+ self.register.is_bit_set(4)
1191
+ }
1192
+
1193
+ // Bit 3. Show background.
1194
+ // -- 0: invisible, 1: visible
1195
+ fn is_background_visible(&self) -> bool {
1196
+ self.register.is_bit_set(3)
1197
+ }
1198
+
1199
+ // Bit 2. Show sprites in leftmost 8 pixels of screen.
1200
+ // -- 0: invisible, 1: visible
1201
+ fn is_left_most_sprites_visible(&self) -> bool {
1202
+ self.register.is_bit_set(2)
1203
+ }
1204
+
1205
+ // Bit 1. Show background in leftmost 8 pixels of screen.
1206
+ // -- 0: invisible, 1: visible
1207
+ fn is_left_most_background_visible(&self) -> bool {
1208
+ self.register.is_bit_set(1)
1209
+ }
1210
+
1211
+ // Bit 0. Greyscale
1212
+ // -- 0: normal color, 1: produce a greyscale display
1213
+ fn is_greyscale(&self) -> bool {
1214
+ self.register.is_bit_set(0)
1215
+ }
1216
+ }
1217
+
1218
+ // PPU status 8-bit register
1219
+ // CPU memory-mapped at 0x2002
1220
+ // Read-only
1221
+ pub struct PpuStatusRegister {
1222
+ register: Register<u8>
1223
+ }
1224
+
1225
+ impl PpuStatusRegister {
1226
+ fn new() -> Self {
1227
+ PpuStatusRegister {
1228
+ register: Register::<u8>::new()
1229
+ }
1230
+ }
1231
+
1232
+ fn load(&self) -> u8 {
1233
+ self.register.load()
1234
+ }
1235
+
1236
+ fn store(&mut self, value: u8) {
1237
+ // @TOOD: Whether should we update unused 0-5 bits?
1238
+ self.register.store(value);
1239
+ }
1240
+
1241
+ // Bit 7. Vertical blank.
1242
+ // Set at dot 1 of line 241, cleared after reading 0x2002
1243
+ // or dot 1 of the pre-render line 261
1244
+ // -- 0: not in vblank, 1: in vblank
1245
+ fn set_vblank(&mut self) {
1246
+ self.register.set_bit(7);
1247
+ }
1248
+
1249
+ fn clear_vblank(&mut self) {
1250
+ self.register.clear_bit(7);
1251
+ }
1252
+
1253
+ fn is_vblank(&mut self) -> bool {
1254
+ self.register.is_bit_set(7)
1255
+ }
1256
+
1257
+ // Bit 6. Sprite zero hit.
1258
+ // Set when a nonzero pixel of sprite 0 overlaps a nonzero background pixel.
1259
+ // Cleared at dot 1 of the pre-render line. Used for raster timing.
1260
+ fn set_zero_hit(&mut self) {
1261
+ self.register.set_bit(6);
1262
+ }
1263
+
1264
+ fn clear_zero_hit(&mut self) {
1265
+ self.register.clear_bit(6);
1266
+ }
1267
+
1268
+ // Bit 5. Sprite overflow.
1269
+ // Set more than eight sprites appear on a scanline.
1270
+ // Cleared at dot 1 of the pre-render line 261.
1271
+ fn set_overflow(&mut self) {
1272
+ self.register.set_bit(5);
1273
+ }
1274
+
1275
+ fn clear_overflow(&mut self) {
1276
+ self.register.clear_bit(5);
1277
+ }
1278
+ }
1279
+
1280
+ pub struct SpritesManager {
1281
+ memory: Memory
1282
+ }
1283
+
1284
+ impl SpritesManager {
1285
+ fn new(memory: Memory) -> Self {
1286
+ SpritesManager {
1287
+ memory: memory
1288
+ }
1289
+ }
1290
+
1291
+ fn load(&self, address: u8) -> u8 {
1292
+ self.memory.load(address as u32)
1293
+ }
1294
+
1295
+ fn store(&mut self, address: u8, value: u8) {
1296
+ self.memory.store(address as u32, value);
1297
+ }
1298
+
1299
+ fn get_num(&self) -> u8 {
1300
+ (self.memory.capacity() / 4) as u8
1301
+ }
1302
+
1303
+ fn get(&self, index: u8) -> Sprite {
1304
+ Sprite {
1305
+ byte0: self.load(index * 4 + 0),
1306
+ byte1: self.load(index * 4 + 1),
1307
+ byte2: self.load(index * 4 + 2),
1308
+ byte3: self.load(index * 4 + 3)
1309
+ }
1310
+ }
1311
+
1312
+ fn copy(&mut self, index: u8, sprite: Sprite) {
1313
+ self.store(index * 4 + 0, sprite.byte0);
1314
+ self.store(index * 4 + 1, sprite.byte1);
1315
+ self.store(index * 4 + 2, sprite.byte2);
1316
+ self.store(index * 4 + 3, sprite.byte3);
1317
+ }
1318
+
1319
+ fn reset(&mut self) {
1320
+ for i in 0..self.get_num() {
1321
+ self.store(i * 4 + 0, 0xff);
1322
+ self.store(i * 4 + 1, 0xff);
1323
+ self.store(i * 4 + 2, 0xff);
1324
+ self.store(i * 4 + 3, 0xff);
1325
+ }
1326
+ }
1327
+ }
1328
+
1329
+ struct Sprite {
1330
+ byte0: u8,
1331
+ byte1: u8,
1332
+ byte2: u8,
1333
+ byte3: u8
1334
+ }
1335
+
1336
+ impl Sprite {
1337
+ fn get_y(&self) -> u8 {
1338
+ self.byte0
1339
+ }
1340
+
1341
+ fn get_x(&self) -> u8 {
1342
+ self.byte3
1343
+ }
1344
+
1345
+ fn get_tile_index(&self) -> u8 {
1346
+ self.byte1
1347
+ }
1348
+
1349
+ // the lowest two bits of byte2 holds the
1350
+ // 3-2 bits of palette memory address
1351
+ fn get_palette_num(&self)-> u8 {
1352
+ self.byte2 & 0x3
1353
+ }
1354
+
1355
+ fn get_priority(&self) -> u8 {
1356
+ (self.byte2 >> 5) & 1
1357
+ }
1358
+
1359
+ fn horizontal_flip(&self) -> bool {
1360
+ ((self.byte2 >> 6) & 1) == 1
1361
+ }
1362
+
1363
+ fn vertical_flip(&self) -> bool {
1364
+ ((self.byte2 >> 7) & 1) == 1
1365
+ }
1366
+
1367
+ fn on(&self, y: u8, height: u8) -> bool {
1368
+ (y >= self.get_y()) && (y < self.get_y() + height)
1369
+ }
1370
+
1371
+ fn get_y_in_sprite(&self, y: u8, height: u8) -> u8 {
1372
+ // Assumes self.on(y, height) is true
1373
+ match self.vertical_flip() {
1374
+ true => height - 1 - (y - self.get_y()),
1375
+ false => y - self.get_y()
1376
+ }
1377
+ }
1378
+ }