Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/quicknes/nes_emu/Nes_Ppu_Rendering.cpp
2 views
1
2
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
3
4
#include "Nes_Ppu_Rendering.h"
5
6
#include <string.h>
7
#include <stddef.h>
8
9
/* Copyright (C) 2004-2006 Shay Green. This module is free software; you
10
can redistribute it and/or modify it under the terms of the GNU Lesser
11
General Public License as published by the Free Software Foundation; either
12
version 2.1 of the License, or (at your option) any later version. This
13
module is distributed in the hope that it will be useful, but WITHOUT ANY
14
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
16
more details. You should have received a copy of the GNU Lesser General
17
Public License along with this module; if not, write to the Free Software
18
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
19
20
#include "blargg_source.h"
21
22
#ifdef BLARGG_ENABLE_OPTIMIZER
23
#include BLARGG_ENABLE_OPTIMIZER
24
#endif
25
26
#ifdef __MWERKS__
27
static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants
28
#else
29
const unsigned zero = 0; // compile-time constant on other compilers
30
#endif
31
32
// Nes_Ppu_Impl
33
34
inline Nes_Ppu_Impl::cached_tile_t const&
35
Nes_Ppu_Impl::get_sprite_tile( byte const* sprite ) /*const*/
36
{
37
cached_tile_t* tiles = tile_cache;
38
if ( sprite [2] & 0x40 )
39
tiles = flipped_tiles;
40
int index = sprite_tile_index( sprite );
41
42
// use index directly, since cached tile is same size as native tile
43
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
44
return *(Nes_Ppu_Impl::cached_tile_t*)
45
((byte*) tiles + map_chr_addr( index * bytes_per_tile ));
46
}
47
48
inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) /*const*/
49
{
50
// use index directly, since cached tile is same size as native tile
51
BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile );
52
return *(Nes_Ppu_Impl::cached_tile_t*)
53
((byte*) tile_cache + map_chr_addr( index * bytes_per_tile ));
54
}
55
56
// Fill
57
58
void Nes_Ppu_Rendering::fill_background( int count )
59
{
60
ptrdiff_t const next_line = scanline_row_bytes - image_width;
61
uint32_t* pixels = (uint32_t*) scanline_pixels;
62
63
unsigned long fill = palette_offset;
64
if ( (vram_addr & 0x3f00) == 0x3f00 )
65
{
66
// PPU uses current palette entry if addr is within palette ram
67
int color = vram_addr & 0x1f;
68
if ( !(color & 3) )
69
color &= 0x0f;
70
fill += color * 0x01010101;
71
}
72
73
for ( int n = count; n--; )
74
{
75
for ( int n = image_width / 16; n--; )
76
{
77
pixels [0] = fill;
78
pixels [1] = fill;
79
pixels [2] = fill;
80
pixels [3] = fill;
81
pixels += 4;
82
}
83
pixels = (uint32_t*) ((byte*) pixels + next_line);
84
}
85
}
86
87
void Nes_Ppu_Rendering::clip_left( int count )
88
{
89
ptrdiff_t next_line = scanline_row_bytes;
90
byte* p = scanline_pixels;
91
unsigned long fill = palette_offset;
92
93
for ( int n = count; n--; )
94
{
95
((uint32_t*) p) [0] = fill;
96
((uint32_t*) p) [1] = fill;
97
p += next_line;
98
}
99
}
100
101
void Nes_Ppu_Rendering::save_left( int count )
102
{
103
ptrdiff_t next_line = scanline_row_bytes;
104
byte* in = scanline_pixels;
105
uint32_t* out = impl->clip_buf;
106
107
for ( int n = count; n--; )
108
{
109
unsigned long in0 = ((uint32_t*) in) [0];
110
unsigned long in1 = ((uint32_t*) in) [1];
111
in += next_line;
112
out [0] = in0;
113
out [1] = in1;
114
out += 2;
115
}
116
}
117
118
void Nes_Ppu_Rendering::restore_left( int count )
119
{
120
ptrdiff_t next_line = scanline_row_bytes;
121
byte* out = scanline_pixels;
122
uint32_t* in = impl->clip_buf;
123
124
for ( int n = count; n--; )
125
{
126
unsigned long in0 = in [0];
127
unsigned long in1 = in [1];
128
in += 2;
129
((uint32_t*) out) [0] = in0;
130
((uint32_t*) out) [1] = in1;
131
out += next_line;
132
}
133
}
134
135
// Background
136
137
void Nes_Ppu_Rendering::draw_background_( int remain )
138
{
139
// Draws 'remain' background scanlines. Does not modify vram_addr.
140
141
int vram_addr = this->vram_addr & 0x7fff;
142
byte* row_pixels = scanline_pixels - pixel_x;
143
int left_clip = (w2001 >> 1 & 1) ^ 1;
144
row_pixels += left_clip * 8;
145
do
146
{
147
// scanlines until next row
148
int height = 8 - (vram_addr >> 12);
149
if ( height > remain )
150
height = remain;
151
152
// handle hscroll change before next scanline
153
int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f;
154
int addr = vram_addr;
155
if ( hscroll_changed )
156
{
157
vram_addr ^= hscroll_changed;
158
height = 1; // hscroll will change after first line
159
}
160
remain -= height;
161
162
// increment address for next row
163
vram_addr += height << 12;
164
assert( vram_addr < 0x10000 );
165
if ( vram_addr & 0x8000 )
166
{
167
int y = (vram_addr + 0x20) & 0x3e0;
168
vram_addr &= 0x7fff & ~0x3e0;
169
if ( y == 30 * 0x20 )
170
y = 0x800; // toggle vertical nametable
171
vram_addr ^= y;
172
}
173
174
// nametable change usually occurs in middle of row
175
byte const* nametable = get_nametable( addr );
176
byte const* nametable2 = get_nametable( addr ^ 0x400 );
177
int count2 = addr & 31;
178
int count = 32 - count2 - left_clip;
179
180
// this conditional is commented out because of mmc2\4
181
// normally, the extra row of pixels is only fetched when pixel_ x is not 0, which makes sense
182
// but here, we need a correct fetch pattern to pick up 0xfd\0xfe tiles off the edge of the display
183
184
// this doesn't cause any problems with buffer overflow because the framebuffer we're rendering to is
185
// already guarded (width = 272)
186
// this doesn't give us a fully correct ppu fetch pattern, but it's close enough for punch out
187
188
//if ( pixel_x )
189
count2++;
190
191
byte const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)];
192
int bg_bank = (w2000 << 4) & 0x100;
193
addr += left_clip;
194
195
// output pixels
196
ptrdiff_t const row_bytes = scanline_row_bytes;
197
byte* pixels = row_pixels;
198
row_pixels += height * row_bytes;
199
200
unsigned long const mask = 0x03030303 + zero;
201
unsigned long const attrib_factor = 0x04040404 + zero;
202
203
if ( height == 8 )
204
{
205
// unclipped
206
assert( (addr >> 12) == 0 );
207
addr &= 0x03ff;
208
int const fine_y = 0;
209
int const clipped = false;
210
#include "Nes_Ppu_Bg.h"
211
}
212
else
213
{
214
// clipped
215
int const fine_y = addr >> 12;
216
addr &= 0x03ff;
217
height -= fine_y & 1;
218
int const clipped = true;
219
#include "Nes_Ppu_Bg.h"
220
}
221
}
222
while ( remain );
223
}
224
225
// Sprites
226
227
void Nes_Ppu_Rendering::draw_sprites_( int begin, int end )
228
{
229
// Draws sprites on scanlines begin through end - 1. Handles clipping.
230
231
int const sprite_height = this->sprite_height();
232
int end_minus_one = end - 1;
233
int begin_minus_one = begin - 1;
234
int index = 0;
235
do
236
{
237
byte const* sprite = &spr_ram [index];
238
index += 4;
239
240
// find if sprite is visible
241
int top_minus_one = sprite [0];
242
int visible = end_minus_one - top_minus_one;
243
if ( visible <= 0 )
244
continue; // off bottom
245
246
// quickly determine whether sprite is unclipped
247
int neg_vis = visible - sprite_height;
248
int neg_skip = top_minus_one - begin_minus_one;
249
if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0
250
{
251
// unclipped
252
#ifndef NDEBUG
253
int top = sprite [0] + 1;
254
assert( (top + sprite_height) > begin && top < end );
255
assert( begin <= top && top + sprite_height <= end );
256
#endif
257
258
int const skip = 0;
259
int visible = sprite_height;
260
261
#define CLIPPED 0
262
#include "Nes_Ppu_Sprites.h"
263
}
264
else
265
{
266
// clipped
267
if ( neg_vis > 0 )
268
visible -= neg_vis;
269
270
if ( neg_skip > 0 )
271
neg_skip = 0;
272
visible += neg_skip;
273
274
if ( visible <= 0 )
275
continue; // off top
276
277
// visible and clipped
278
#ifndef NDEBUG
279
int top = sprite [0] + 1;
280
assert( (top + sprite_height) > begin && top < end );
281
assert( top < begin || top + sprite_height > end );
282
#endif
283
284
int skip = -neg_skip;
285
286
//dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n",
287
// begin, end, top_minus_one + 1, skip, visible );
288
289
#define CLIPPED 1
290
#include "Nes_Ppu_Sprites.h"
291
}
292
}
293
while ( index < 0x100 );
294
}
295
296
void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end )
297
{
298
// Checks for sprite 0 hit on scanlines begin through end - 1.
299
// Updates sprite_hit_found. Background (but not sprites) must have
300
// already been rendered for the scanlines.
301
302
// clip
303
int top = spr_ram [0] + 1;
304
int skip = begin - top;
305
if ( skip < 0 )
306
skip = 0;
307
308
top += skip;
309
int visible = end - top;
310
if ( visible <= 0 )
311
return; // not visible
312
313
int height = sprite_height();
314
if ( visible >= height )
315
{
316
visible = height;
317
sprite_hit_found = -1; // signal that no more hit checking will take place
318
}
319
320
// pixels
321
ptrdiff_t next_row = this->scanline_row_bytes;
322
byte const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row;
323
cache_t const* lines = get_sprite_tile( spr_ram );
324
325
// left edge clipping
326
int start_x = 0;
327
if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e )
328
{
329
if ( spr_ram [3] == 0 )
330
return; // won't hit
331
start_x = 8 - spr_ram [3];
332
}
333
334
// vertical flip
335
int final = skip + visible;
336
if ( spr_ram [2] & 0x80 )
337
{
338
skip += height - 1;
339
final = skip - visible;
340
}
341
342
// check each line
343
unsigned long const mask = 0x01010101 + zero;
344
do
345
{
346
// get pixels for line
347
unsigned long line = lines [skip >> 1];
348
unsigned long hit0 = ((uint32_t*) bg) [0];
349
unsigned long hit1 = ((uint32_t*) bg) [1];
350
bg += next_row;
351
line >>= skip << 1 & 2;
352
line |= line >> 1;
353
354
// check for hits
355
hit0 = ((hit0 >> 1) | hit0) & (line >> 4);
356
hit1 = ((hit1 >> 1) | hit1) & line;
357
if ( (hit0 | hit1) & mask )
358
{
359
// write to memory to avoid endian issues
360
uint32_t quads [3];
361
quads [0] = hit0;
362
quads [1] = hit1;
363
364
// find which pixel hit
365
int x = start_x;
366
do
367
{
368
if ( ((byte*) quads) [x] & 1 )
369
{
370
x += spr_ram [3];
371
if ( x >= 255 )
372
break; // ignore right edge
373
374
if ( spr_ram [2] & 0x80 )
375
skip = height - 1 - skip; // vertical flip
376
int y = spr_ram [0] + 1 + skip;
377
sprite_hit_found = y * scanline_len + x;
378
379
return;
380
}
381
}
382
while ( x++ < 7 );
383
}
384
if ( skip > final )
385
skip -= 2;
386
skip++;
387
}
388
while ( skip != final );
389
}
390
391
// Draw scanlines
392
393
inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const
394
{
395
return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18;
396
}
397
398
void Nes_Ppu_Rendering::draw_scanlines( int start, int count,
399
byte* pixels, long pitch, int mode )
400
{
401
assert( start + count <= image_height );
402
assert( pixels );
403
404
scanline_pixels = pixels + image_left;
405
scanline_row_bytes = pitch;
406
407
int const obj_mask = 2;
408
int const bg_mask = 1;
409
int draw_mode = (w2001 >> 3) & 3;
410
int clip_mode = (~w2001 >> 1) & draw_mode;
411
412
if ( !(draw_mode & bg_mask) )
413
{
414
// no background
415
clip_mode |= bg_mask; // avoid unnecessary save/restore
416
if ( mode & bg_mask )
417
fill_background( count );
418
}
419
420
if ( start == 0 && mode & 1 )
421
memset( sprite_scanlines, max_sprites - sprite_limit, 240 );
422
423
if ( (draw_mode &= mode) )
424
{
425
// sprites and/or background are being rendered
426
427
if ( any_tiles_modified && chr_is_writable )
428
{
429
any_tiles_modified = false;
430
update_tiles( 0 );
431
}
432
433
if ( draw_mode & bg_mask )
434
{
435
//dprintf( "bg %3d-%3d\n", start, start + count - 1 );
436
draw_background_( count );
437
438
if ( clip_mode == bg_mask )
439
clip_left( count );
440
441
if ( sprite_hit_possible( start + count ) )
442
check_sprite_hit( start, start + count );
443
}
444
445
if ( draw_mode & obj_mask )
446
{
447
// when clipping just sprites, save left strip then restore after drawing them
448
if ( clip_mode == obj_mask )
449
save_left( count );
450
451
//dprintf( "obj %3d-%3d\n", start, start + count - 1 );
452
453
draw_sprites_( start, start + count );
454
455
if ( clip_mode == obj_mask )
456
restore_left( count );
457
458
if ( clip_mode == (obj_mask | bg_mask) )
459
clip_left( count );
460
}
461
}
462
463
scanline_pixels = NULL;
464
}
465
466
void Nes_Ppu_Rendering::draw_background( int start, int count )
467
{
468
// always capture palette at least once per frame
469
if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) )
470
{
471
palette_changed = false;
472
capture_palette();
473
}
474
475
if ( host_pixels )
476
{
477
draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 );
478
}
479
else if ( sprite_hit_possible( start + count ) )
480
{
481
// not rendering, but still handle sprite hit using mini graphics buffer
482
int y = spr_ram [0] + 1;
483
int skip = min( count, max( y - start, 0 ) );
484
int visible = min( count - skip, sprite_height() );
485
486
assert( skip + visible <= count );
487
assert( visible <= mini_offscreen_height );
488
489
if ( visible > 0 )
490
{
491
run_hblank( skip );
492
draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 );
493
}
494
}
495
}
496
497
498