@tmustier/pi-nes 0.2.4 → 0.2.6
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.
- package/AGENTS.md +89 -1
- package/README.md +77 -48
- package/extensions/nes/config.ts +32 -12
- package/extensions/nes/native/nes-core/Cargo.lock +0 -2
- package/extensions/nes/native/nes-core/Cargo.toml +1 -1
- package/extensions/nes/native/nes-core/index.d.ts +5 -0
- package/extensions/nes/native/nes-core/index.node +0 -0
- package/extensions/nes/native/nes-core/native.d.ts +5 -0
- package/extensions/nes/native/nes-core/src/lib.rs +25 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo-ok +1 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo_vcs_info.json +5 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.travis.yml +3 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/Cargo.toml +23 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/LICENSE +21 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/README.md +59 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/apu.rs +1114 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/audio.rs +6 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/button.rs +23 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/cpu.rs +2364 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_audio.rs +49 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_display.rs +47 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_input.rs +33 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/display.rs +10 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/input.rs +7 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/joypad.rs +86 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/lib.rs +168 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/mapper.rs +502 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/memory.rs +73 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/ppu.rs +1378 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/register.rs +560 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/rom.rs +231 -0
- package/extensions/nes/nes-core.ts +27 -4
- package/package.json +1 -1
- package/spec.md +2 -4
|
@@ -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
|
+
}
|