Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/quicknes/nes_emu/Nes_Ppu.cpp
2 views
1
2
// Timing and behavior of PPU
3
4
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
5
6
#include "Nes_Ppu.h"
7
8
#include <string.h>
9
#include "Nes_State.h"
10
#include "Nes_Mapper.h"
11
#include "Nes_Core.h"
12
13
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
14
can redistribute it and/or modify it under the terms of the GNU Lesser
15
General Public License as published by the Free Software Foundation; either
16
version 2.1 of the License, or (at your option) any later version. This
17
module is distributed in the hope that it will be useful, but WITHOUT ANY
18
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
20
more details. You should have received a copy of the GNU Lesser General
21
Public License along with this module; if not, write to the Free Software
22
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
23
24
// to do: remove unnecessary run_until() calls
25
26
#include "blargg_source.h"
27
28
// Timing
29
30
ppu_time_t const scanline_len = Nes_Ppu::scanline_len;
31
32
// if non-zero, report sprite max at fixed time rather than calculating it
33
nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock;
34
int const sprite_max_cpu_offset = 2420 + 3;
35
36
ppu_time_t const t_to_v_time = 20 * scanline_len + 302;
37
ppu_time_t const even_odd_time = 20 * scanline_len + 328;
38
39
ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied
40
ppu_time_t const first_hblank_time = 21 * scanline_len + 252;
41
42
ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock;
43
ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set
44
45
nes_time_t const vbl_end_time = 2272;
46
ppu_time_t const max_frame_length = 262 * scanline_len;
47
//ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync
48
nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10;
49
50
// Scanline rendering
51
52
void Nes_Ppu::render_bg_until_( nes_time_t cpu_time )
53
{
54
ppu_time_t time = ppu_time( cpu_time );
55
ppu_time_t const frame_duration = scanline_len * 261;
56
if ( time > frame_duration )
57
time = frame_duration;
58
59
// one-time events
60
if ( frame_phase <= 1 )
61
{
62
if ( frame_phase < 1 )
63
{
64
// vtemp->vaddr
65
frame_phase = 1;
66
if ( w2001 & 0x08 )
67
vram_addr = vram_temp;
68
}
69
70
// variable-length scanline
71
if ( time <= even_odd_time )
72
{
73
next_bg_time = nes_time( even_odd_time );
74
return;
75
}
76
frame_phase = 2;
77
if ( !(w2001 & 0x08) || emu.nes.frame_count & 1 )
78
{
79
if ( --frame_length_extra < 0 )
80
{
81
frame_length_extra = 2;
82
frame_length_++;
83
}
84
burst_phase--;
85
}
86
burst_phase = (burst_phase + 2) % 3;
87
}
88
89
// scanlines
90
if ( scanline_time < time )
91
{
92
int count = (time - scanline_time + scanline_len) / scanline_len;
93
94
// hblank before next scanline
95
if ( hblank_time < scanline_time )
96
{
97
hblank_time += scanline_len;
98
run_hblank( 1 );
99
}
100
101
scanline_time += count * scanline_len;
102
103
hblank_time += scanline_len * (count - 1);
104
int saved_vaddr = vram_addr;
105
106
int start = scanline_count;
107
scanline_count += count;
108
draw_background( start, count );
109
110
vram_addr = saved_vaddr; // to do: this is cheap
111
run_hblank( count - 1 );
112
}
113
114
// hblank after current scanline
115
ppu_time_t next_ppu_time = hblank_time;
116
if ( hblank_time < time )
117
{
118
hblank_time += scanline_len;
119
run_hblank( 1 );
120
next_ppu_time = scanline_time; // scanline will run next
121
}
122
assert( time <= hblank_time );
123
124
// either hblank or scanline comes next
125
next_bg_time = nes_time( next_ppu_time );
126
}
127
128
void Nes_Ppu::render_until_( nes_time_t time )
129
{
130
// render bg scanlines then render sprite scanlines up to wherever bg was rendered to
131
132
render_bg_until( time );
133
next_sprites_time = nes_time( scanline_time );
134
if ( host_pixels )
135
{
136
int start = next_sprites_scanline;
137
int count = scanline_count - start;
138
if ( count > 0 )
139
{
140
next_sprites_scanline += count;
141
draw_sprites( start, count );
142
}
143
}
144
}
145
146
// Frame events
147
148
inline void Nes_Ppu::end_vblank()
149
{
150
// clear VBL, sprite hit, and max sprites flags first time after 20 scanlines
151
r2002 &= end_vbl_mask;
152
end_vbl_mask = ~0;
153
}
154
155
inline void Nes_Ppu::run_end_frame( nes_time_t time )
156
{
157
if ( !frame_ended )
158
{
159
// update frame_length
160
render_bg_until( time );
161
162
// set VBL when end of frame is reached
163
nes_time_t len = frame_length();
164
if ( time >= len )
165
{
166
r2002 |= 0x80;
167
frame_ended = true;
168
if ( w2000 & 0x80 )
169
nmi_time_ = len + 2 - (frame_length_extra >> 1);
170
}
171
}
172
}
173
174
// Sprite max
175
176
inline void Nes_Ppu::invalidate_sprite_max_()
177
{
178
next_sprite_max_run = earliest_sprite_max / ppu_overclock;
179
sprite_max_set_time = 0;
180
}
181
182
void Nes_Ppu::run_sprite_max_( nes_time_t cpu_time )
183
{
184
end_vblank(); // might get run outside $2002 handler
185
186
// 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is
187
int start_scanline = next_sprite_max_scanline;
188
next_sprite_max_scanline = unsigned ((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u;
189
assert( next_sprite_max_scanline >= 0 && next_sprite_max_scanline <= last_sprite_max_scanline );
190
191
if ( !sprite_max_set_time )
192
{
193
if ( !(w2001 & 0x18) )
194
return;
195
196
long t = recalc_sprite_max( start_scanline );
197
sprite_max_set_time = indefinite_time;
198
if ( t > 0 )
199
sprite_max_set_time = t / 3 + sprite_max_cpu_offset;
200
next_sprite_max_run = sprite_max_set_time;
201
//dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time );
202
}
203
204
if ( cpu_time > sprite_max_set_time )
205
{
206
r2002 |= 0x20;
207
//dprintf( "Sprite max flag set: %d\n", sprite_max_set_time );
208
next_sprite_max_run = indefinite_time;
209
}
210
}
211
212
inline void Nes_Ppu::run_sprite_max( nes_time_t t )
213
{
214
if ( !fixed_sprite_max_time && t > next_sprite_max_run )
215
run_sprite_max_( t );
216
}
217
218
inline void Nes_Ppu::invalidate_sprite_max( nes_time_t t )
219
{
220
if ( !fixed_sprite_max_time && !(r2002 & 0x20) )
221
{
222
run_sprite_max( t );
223
invalidate_sprite_max_();
224
}
225
}
226
227
// Sprite 0 hit
228
229
inline int Nes_Ppu_Impl::first_opaque_sprite_line() /*const*/
230
{
231
// advance earliest time if sprite has blank lines at beginning
232
byte const* p = map_chr( sprite_tile_index( spr_ram ) * 16 );
233
int twice = w2000 >> 5 & 1; // loop twice if double height is set
234
int line = 0;
235
do
236
{
237
for ( int n = 8; n--; p++ )
238
{
239
if ( p [0] | p [8] )
240
return line;
241
line++;
242
}
243
244
p += 8;
245
}
246
while ( !--twice );
247
return line;
248
}
249
250
void Nes_Ppu::update_sprite_hit( nes_time_t cpu_time )
251
{
252
ppu_time_t earliest = earliest_sprite_hit + spr_ram [0] * scanline_len + spr_ram [3];
253
//ppu_time_t latest = earliest + sprite_height() * scanline_len;
254
255
earliest += first_opaque_sprite_line() * scanline_len;
256
257
ppu_time_t time = ppu_time( cpu_time );
258
next_sprite_hit_check = indefinite_time;
259
260
if ( false )
261
if ( earliest < time )
262
{
263
r2002 |= 0x40;
264
return;
265
}
266
267
if ( time < earliest )
268
{
269
next_sprite_hit_check = nes_time( earliest );
270
return;
271
}
272
273
// within possible range; render scanline and compare pixels
274
int count_needed = 2 + (time - earliest_sprite_hit - spr_ram [3]) / scanline_len;
275
if ( count_needed > 240 )
276
count_needed = 240;
277
while ( scanline_count < count_needed )
278
render_bg_until( max( cpu_time, next_bg_time + 1 ) );
279
280
if ( sprite_hit_found < 0 )
281
return; // sprite won't hit
282
283
if ( !sprite_hit_found )
284
{
285
// check again next scanline
286
next_sprite_hit_check = nes_time( earliest_sprite_hit + spr_ram [3] +
287
(scanline_count - 1) * scanline_len );
288
}
289
else
290
{
291
// hit found
292
ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len;
293
294
if ( time < hit_time )
295
{
296
next_sprite_hit_check = nes_time( hit_time );
297
return;
298
}
299
300
//dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n",
301
// sprite_hit_found % 341, sprite_hit_found / 341, scanline_count );
302
303
r2002 |= 0x40;
304
}
305
}
306
307
// $2002
308
309
inline void Nes_Ppu::query_until( nes_time_t time )
310
{
311
end_vblank();
312
313
// sprite hit
314
if ( time > next_sprite_hit_check )
315
update_sprite_hit( time );
316
317
// sprite max
318
if ( !fixed_sprite_max_time )
319
run_sprite_max( time );
320
else if ( time >= fixed_sprite_max_time )
321
r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20);
322
}
323
324
int Nes_Ppu::read_2002( nes_time_t time )
325
{
326
nes_time_t next = next_status_event;
327
next_status_event = vbl_end_time;
328
int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0;
329
if ( time > next && time > vbl_end_time + extra_clock )
330
{
331
query_until( time );
332
333
next_status_event = next_sprite_hit_check;
334
nes_time_t const next_max = fixed_sprite_max_time ?
335
fixed_sprite_max_time : next_sprite_max_run;
336
if ( next_status_event > next_max )
337
next_status_event = next_max;
338
339
if ( time > earliest_open_bus_decay() )
340
{
341
next_status_event = earliest_open_bus_decay();
342
update_open_bus( time );
343
}
344
345
if ( time > earliest_vbl_end_time )
346
{
347
if ( next_status_event > earliest_vbl_end_time )
348
next_status_event = earliest_vbl_end_time;
349
run_end_frame( time );
350
351
// special vbl behavior when read is just before or at clock when it's set
352
if ( extra_clocks != 1 )
353
{
354
if ( time == frame_length() )
355
{
356
nmi_time_ = indefinite_time;
357
//dprintf( "Suppressed NMI\n" );
358
}
359
}
360
else if ( time == frame_length() - 1 )
361
{
362
r2002 &= ~0x80;
363
frame_ended = true;
364
nmi_time_ = indefinite_time;
365
//dprintf( "Suppressed NMI\n" );
366
}
367
}
368
}
369
emu.set_ppu_2002_time( next_status_event );
370
371
int result = r2002;
372
second_write = false;
373
r2002 = result & ~0x80;
374
poke_open_bus( time, result, 0xE0 );
375
update_open_bus( time );
376
return ( result & 0xE0 ) | ( open_bus & 0x1F );
377
}
378
379
void Nes_Ppu::dma_sprites( nes_time_t time, void const* in )
380
{
381
//dprintf( "%d sprites written\n", time );
382
render_until( time );
383
384
invalidate_sprite_max( time );
385
// catch anything trying to dma while rendering is enabled
386
check( time + 513 <= vbl_end_time || !(w2001 & 0x18) );
387
388
memcpy( spr_ram + w2003, in, 0x100 - w2003 );
389
memcpy( spr_ram, (char*) in + 0x100 - w2003, w2003 );
390
}
391
392
// Read
393
394
inline int Nes_Ppu_Impl::read_2007( int addr )
395
{
396
int result = r2007;
397
if ( addr < 0x2000 )
398
{
399
r2007 = *map_chr( addr );
400
}
401
else
402
{
403
r2007 = get_nametable( addr ) [addr & 0x3ff];
404
if ( addr >= 0x3f00 )
405
{
406
return palette [map_palette( addr )] | ( open_bus & 0xC0 );
407
}
408
}
409
return result;
410
}
411
412
int Nes_Ppu::read( unsigned addr, nes_time_t time )
413
{
414
if ( addr & ~0x2007 )
415
dprintf( "Read from mirrored PPU register 0x%04X\n", addr );
416
417
switch ( addr & 7 )
418
{
419
// status
420
case 2: // handled inline
421
return read_2002( time );
422
423
// sprite ram
424
case 4: {
425
int result = spr_ram [w2003];
426
if ( (w2003 & 3) == 2 )
427
result &= 0xe3;
428
poke_open_bus( time, result, ~0 );
429
return result;
430
}
431
432
// video ram
433
case 7: {
434
render_bg_until( time );
435
int addr = vram_addr;
436
int new_addr = addr + addr_inc;
437
vram_addr = new_addr;
438
if ( ~addr & new_addr & vaddr_clock_mask )
439
{
440
emu.mapper->a12_clocked();
441
addr = vram_addr - addr_inc; // avoid having to save across func call
442
}
443
int result = read_2007( addr & 0x3fff );
444
poke_open_bus( time, result, ( ( addr & 0x3fff ) >= 0x3f00 ) ? 0x3F : ~0 );
445
return result;
446
}
447
448
default:
449
dprintf( "Read from unimplemented PPU register 0x%04X\n", addr );
450
break;
451
}
452
453
update_open_bus( time );
454
455
return open_bus;
456
}
457
458
// Write
459
460
void Nes_Ppu::write( nes_time_t time, unsigned addr, int data )
461
{
462
if ( addr & ~0x2007 )
463
dprintf( "Wrote to mirrored PPU register 0x%04X\n", addr );
464
465
switch ( addr & 7 )
466
{
467
case 0:{// control
468
int changed = w2000 ^ data;
469
470
if ( changed & 0x28 )
471
render_until( time ); // obj height or pattern addr changed
472
else if ( changed & 0x10 )
473
render_bg_until( time ); // bg pattern addr changed
474
else if ( ((data << 10) ^ vram_temp) & 0x0C00 )
475
render_bg_until( time ); // nametable changed
476
477
if ( changed & 0x80 )
478
{
479
if ( time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1) )
480
end_vblank(); // to do: clean this up
481
482
if ( data & 0x80 & r2002 )
483
{
484
nmi_time_ = time + 2;
485
emu.event_changed();
486
}
487
if ( time >= earliest_vbl_end_time )
488
run_end_frame( time - 1 + (extra_clocks & 1) );
489
}
490
491
// nametable select
492
vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400);
493
494
if ( changed & 0x20 ) // sprite height changed
495
invalidate_sprite_max( time );
496
w2000 = data;
497
addr_inc = data & 4 ? 32 : 1;
498
499
break;
500
}
501
502
case 1:{// sprites, bg enable
503
int changed = w2001 ^ data;
504
505
if ( changed & 0xE1 )
506
{
507
render_until( time + 1 ); // emphasis/monochrome bits changed
508
palette_changed = 0x18;
509
}
510
511
if ( changed & 0x14 )
512
render_until( time + 1 ); // sprite enable/clipping changed
513
else if ( changed & 0x0A )
514
render_bg_until( time + 1 ); // bg enable/clipping changed
515
516
if ( changed & 0x08 ) // bg enabled changed
517
emu.mapper->run_until( time );
518
519
if ( !(w2001 & 0x18) != !(data & 0x18) )
520
invalidate_sprite_max( time ); // all rendering just turned on or off
521
522
w2001 = data;
523
524
if ( changed & 0x08 )
525
emu.irq_changed();
526
527
break;
528
}
529
530
case 3: // spr addr
531
w2003 = data;
532
poke_open_bus( time, w2003, ~0 );
533
break;
534
535
case 4:
536
//dprintf( "%d sprites written\n", time );
537
if ( time > first_scanline_time / ppu_overclock )
538
{
539
render_until( time );
540
invalidate_sprite_max( time );
541
}
542
spr_ram [w2003] = data;
543
w2003 = (w2003 + 1) & 0xff;
544
break;
545
546
case 5:
547
render_bg_until( time );
548
if ( (second_write ^= 1) )
549
{
550
pixel_x = data & 7;
551
vram_temp = (vram_temp & ~0x1f) | (data >> 3);
552
}
553
else
554
{
555
vram_temp = (vram_temp & ~0x73e0) |
556
(data << 12 & 0x7000) | (data << 2 & 0x03e0);
557
}
558
break;
559
560
case 6:
561
render_bg_until( time );
562
if ( (second_write ^= 1) )
563
{
564
vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00);
565
}
566
else
567
{
568
int changed = ~vram_addr & vram_temp;
569
vram_addr = vram_temp = (vram_temp & 0xff00) | data;
570
if ( changed & vaddr_clock_mask )
571
emu.mapper->a12_clocked();
572
}
573
break;
574
575
default:
576
dprintf( "Wrote to unimplemented PPU register 0x%04X\n", addr );
577
break;
578
}
579
580
poke_open_bus( time, data, ~0 );
581
}
582
583
// Frame begin/end
584
585
nes_time_t Nes_Ppu::begin_frame( ppu_time_t timestamp )
586
{
587
// current time
588
int cpu_timestamp = timestamp / ppu_overclock;
589
extra_clocks = timestamp - cpu_timestamp * ppu_overclock;
590
591
// frame end
592
ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks;
593
frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock;
594
frame_length_extra = frame_length_ * ppu_overclock - frame_end;
595
assert( (unsigned) frame_length_extra < 3 );
596
597
// nmi
598
nmi_time_ = indefinite_time;
599
if ( w2000 & 0x80 & r2002 )
600
nmi_time_ = 2 - (extra_clocks >> 1);
601
602
// bg rendering
603
frame_phase = 0;
604
scanline_count = 0;
605
hblank_time = first_hblank_time;
606
scanline_time = first_scanline_time;
607
next_bg_time = nes_time( t_to_v_time );
608
609
// sprite rendering
610
next_sprites_scanline = 0;
611
next_sprites_time = 0;
612
613
// status register
614
frame_ended = false;
615
end_vbl_mask = ~0xE0;
616
next_status_event = 0;
617
sprite_hit_found = 0;
618
next_sprite_hit_check = 0;
619
next_sprite_max_scanline = 0;
620
invalidate_sprite_max_();
621
622
decay_low += cpu_timestamp;
623
decay_high += cpu_timestamp;
624
625
base::begin_frame();
626
627
//dprintf( "cpu_timestamp: %d\n", cpu_timestamp );
628
return cpu_timestamp;
629
}
630
631
ppu_time_t Nes_Ppu::end_frame( nes_time_t end_time )
632
{
633
render_bg_until( end_time );
634
render_until( end_time );
635
query_until( end_time );
636
run_end_frame( end_time );
637
638
update_open_bus( end_time );
639
decay_low -= end_time;
640
decay_high -= end_time;
641
642
// to do: do more PPU RE to get exact behavior
643
if ( w2001 & 0x08 )
644
{
645
unsigned a = vram_addr + 2;
646
if ( (vram_addr & 0xff) >= 0xfe )
647
a = (vram_addr ^ 0x400) - 0x1e;
648
vram_addr = a;
649
}
650
651
if ( w2001 & 0x10 )
652
w2003 = 0;
653
654
suspend_rendering();
655
656
return (end_time - frame_length_) * ppu_overclock + frame_length_extra;
657
}
658
659
void Nes_Ppu::poke_open_bus( nes_time_t time, int data, int mask )
660
{
661
open_bus = ( open_bus & ~mask ) | ( data & mask );
662
if ( mask & 0x1F ) decay_low = time + scanline_len * 100 / ppu_overclock;
663
if ( mask & 0xE0 ) decay_high = time + scanline_len * 100 / ppu_overclock;
664
}
665
666
const nes_time_t Nes_Ppu::earliest_open_bus_decay() const
667
{
668
return ( decay_low < decay_high ) ? decay_low : decay_high;
669
}
670
671