1 // license:BSD-3-Clause
2 // copyright-holders:Ernesto Corvi, Brad Oliver, Fabio Priuli
3 /******************************************************************************
4
5 Nintendo 2C0x PPU emulation.
6
7 Written by Ernesto Corvi.
8 This code is heavily based on Brad Oliver's MESS implementation.
9
10 2009-04: Changed NES PPU to be a device (Nathan Woods)
11 2009-07: Changed NES PPU to use a device memory map (Robert Bohms)
12
13 Current known bugs
14
15 General:
16
17 * PPU timing is imprecise for updates that happen mid-scanline. Some games
18 may demand more precision.
19
20 NES-specific:
21
22 * Micro Machines has minor rendering glitches (needs better timing).
23 * Mach Rider has minor road rendering glitches (needs better timing).
24 * Rad Racer demonstrates road glitches: it changes horizontal scrolling mid-line.
25
26 ******************************************************************************/
27
28 #include "emu.h"
29 #include "video/ppu2c0x.h"
30
31 #include "screen.h"
32
33 //**************************************************************************
34 // GLOBAL VARIABLES
35 //**************************************************************************
36
37 // devices
38 DEFINE_DEVICE_TYPE(PPU_2C02, ppu2c02_device, "ppu2c02", "2C02 PPU")
39 DEFINE_DEVICE_TYPE(PPU_2C03B, ppu2c03b_device, "ppu2c03b", "2C03B PPC")
40 DEFINE_DEVICE_TYPE(PPU_2C04, ppu2c04_device, "ppu2c04", "2C04 PPU")
41 DEFINE_DEVICE_TYPE(PPU_2C07, ppu2c07_device, "ppu2c07", "2C07 PPU")
42 DEFINE_DEVICE_TYPE(PPU_PALC, ppupalc_device, "ppupalc", "Generic PAL Clone PPU")
43 DEFINE_DEVICE_TYPE(PPU_2C05_01, ppu2c05_01_device, "ppu2c05_01", "2C05_01 PPU")
44 DEFINE_DEVICE_TYPE(PPU_2C05_02, ppu2c05_02_device, "ppu2c05_02", "2C05_02 PPU")
45 DEFINE_DEVICE_TYPE(PPU_2C05_03, ppu2c05_03_device, "ppu2c05_03", "2C05_03 PPU")
46 DEFINE_DEVICE_TYPE(PPU_2C05_04, ppu2c05_04_device, "ppu2c05_04", "2C05_04 PPU")
47 DEFINE_DEVICE_TYPE(PPU_2C04C, ppu2c04_clone_device, "ppu2c04c", "2C04 Clone PPU")
48
49
50 // default address map
ppu2c0x(address_map & map)51 void ppu2c0x_device::ppu2c0x(address_map& map)
52 {
53 if (!has_configured_map(0))
54 {
55 map(0x0000, 0x3eff).ram();
56 map(0x3f00, 0x3fff).rw(FUNC(ppu2c0x_device::palette_read), FUNC(ppu2c0x_device::palette_write));
57 //map(0x0000, 0x3fff).ram();
58 }
59 }
60
61 //-------------------------------------------------
62 // memory_space_config - return a description of
63 // any address spaces owned by this device
64 //-------------------------------------------------
65
memory_space_config() const66 device_memory_interface::space_config_vector ppu2c0x_device::memory_space_config() const
67 {
68 return space_config_vector
69 {
70 std::make_pair(0, &m_space_config)
71 };
72 }
73
74
75 //-------------------------------------------------
76 // ppu2c0x_device - constructor
77 //-------------------------------------------------
78
device_config_complete()79 void ppu2c0x_device::device_config_complete()
80 {
81 /* reset the callbacks */
82 m_scanline_callback_proc.set(nullptr);
83 m_hblank_callback_proc.set(nullptr);
84 m_vidaccess_callback_proc.set(nullptr);
85 m_latch.set(nullptr);
86 }
87
ppu2c0x_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock,address_map_constructor internal_map)88 ppu2c0x_device::ppu2c0x_device(const machine_config& mconfig, device_type type, const char* tag, device_t* owner, uint32_t clock, address_map_constructor internal_map) :
89 device_t(mconfig, type, tag, owner, clock),
90 device_memory_interface(mconfig, *this),
91 device_video_interface(mconfig, *this),
92 m_space_config("videoram", ENDIANNESS_LITTLE, 8, 17, 0, internal_map),
93 m_cpu(*this, finder_base::DUMMY_TAG),
94 m_scanline(0), // reset the scanline count
95 m_videoram_addr_mask(0x3fff),
96 m_global_refresh_mask(0x7fff),
97 m_line_write_increment_large(32),
98 m_paletteram_in_ppuspace(false),
99 m_tile_page(0),
100 m_back_color(0),
101 m_refresh_data(0),
102 m_x_fine(0),
103 m_toggle(0),
104 m_tilecount(0),
105 m_latch(*this),
106 m_scanline_callback_proc(*this),
107 m_hblank_callback_proc(*this),
108 m_vidaccess_callback_proc(*this),
109 m_int_callback(*this),
110 m_refresh_latch(0),
111 m_add(1),
112 m_videomem_addr(0),
113 m_data_latch(0),
114 m_buffered_data(0),
115 m_sprite_page(0),
116 m_scan_scale(1), // set the scan scale (this is for dual monitor vertical setups)
117 m_draw_phase(0),
118 m_use_sprite_write_limitation(true)
119 {
120 for (auto& elem : m_regs)
121 elem = 0;
122
123 m_scanlines_per_frame = NTSC_SCANLINES_PER_FRAME;
124 m_vblank_first_scanline = VBLANK_FIRST_SCANLINE;
125
126 /* usually, no security value... */
127 m_security_value = 0;
128 }
129
ppu2c0x_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)130 ppu2c0x_device::ppu2c0x_device(const machine_config& mconfig, device_type type, const char* tag, device_t* owner, uint32_t clock) :
131 ppu2c0x_device(mconfig, type, tag, owner, clock, address_map_constructor(FUNC(ppu2c0x_device::ppu2c0x), this))
132 {
133 m_paletteram_in_ppuspace = true;
134 }
135
ppu2c0x_rgb_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)136 ppu2c0x_rgb_device::ppu2c0x_rgb_device(const machine_config& mconfig, device_type type, const char* tag, device_t* owner, uint32_t clock) :
137 ppu2c0x_device(mconfig, type, tag, owner, clock),
138 m_palette_data(*this, "palette", 0xc0)
139 {
140 }
141
142 // NTSC NES
ppu2c02_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)143 ppu2c02_device::ppu2c02_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
144 ppu2c0x_device(mconfig, PPU_2C02, tag, owner, clock)
145 {
146 }
147
148 // Playchoice 10
ppu2c03b_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)149 ppu2c03b_device::ppu2c03b_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
150 ppu2c0x_rgb_device(mconfig, PPU_2C03B, tag, owner, clock)
151 {
152 }
153
154 // Vs. Unisystem
ppu2c04_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)155 ppu2c04_device::ppu2c04_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
156 ppu2c0x_rgb_device(mconfig, PPU_2C04, tag, owner, clock)
157 {
158 }
159
160 // PAL NES
ppu2c07_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)161 ppu2c07_device::ppu2c07_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
162 ppu2c0x_device(mconfig, PPU_2C07, tag, owner, clock)
163 {
164 m_scanlines_per_frame = PAL_SCANLINES_PER_FRAME;
165 }
166
167 // PAL clones
ppupalc_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)168 ppupalc_device::ppupalc_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
169 ppu2c0x_device(mconfig, PPU_PALC, tag, owner, clock)
170 {
171 m_scanlines_per_frame = PAL_SCANLINES_PER_FRAME;
172 m_vblank_first_scanline = VBLANK_FIRST_SCANLINE_PALC;
173 }
174
175 // The PPU_2C05 variants have different protection value, set at device start, but otherwise are all the same...
176 // Vs. Unisystem (Ninja Jajamaru Kun)
ppu2c05_01_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)177 ppu2c05_01_device::ppu2c05_01_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
178 ppu2c0x_rgb_device(mconfig, PPU_2C05_01, tag, owner, clock)
179 {
180 m_security_value = 0x1b; // game (jajamaru) doesn't seem to ever actually check it
181 }
182 // Vs. Unisystem (Mighty Bomb Jack)
ppu2c05_02_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)183 ppu2c05_02_device::ppu2c05_02_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
184 ppu2c0x_rgb_device(mconfig, PPU_2C05_02, tag, owner, clock)
185 {
186 m_security_value = 0x3d;
187 }
188 // Vs. Unisystem (Gumshoe)
ppu2c05_03_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)189 ppu2c05_03_device::ppu2c05_03_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
190 ppu2c0x_rgb_device(mconfig, PPU_2C05_03, tag, owner, clock)
191 {
192 m_security_value = 0x1c;
193 }
194 // Vs. Unisystem (Top Gun)
ppu2c05_04_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)195 ppu2c05_04_device::ppu2c05_04_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
196 ppu2c0x_rgb_device(mconfig, PPU_2C05_04, tag, owner, clock)
197 {
198 m_security_value = 0x1b;
199 }
200
201 // Vs. Unisystem (Super Mario Bros. bootlegs)
ppu2c04_clone_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)202 ppu2c04_clone_device::ppu2c04_clone_device(const machine_config& mconfig, const char* tag, device_t* owner, uint32_t clock) :
203 ppu2c0x_device(mconfig, PPU_2C04C, tag, owner, clock),
204 m_palette_data(*this, "palette", 0x100)
205 {
206 m_scanlines_per_frame = VS_CLONE_SCANLINES_PER_FRAME;
207 m_vblank_first_scanline = VBLANK_FIRST_SCANLINE_VS_CLONE;
208
209 // background and sprites are always enabled; monochrome and color emphasis aren't supported
210 m_regs[PPU_CONTROL1] = ~(PPU_CONTROL1_COLOR_EMPHASIS | PPU_CONTROL1_DISPLAY_MONO);
211 }
212
213 //-------------------------------------------------
214 // device_start - device-specific startup
215 //-------------------------------------------------
216
start_nopalram()217 void ppu2c0x_device::start_nopalram()
218 {
219 // bind our handler
220 m_int_callback.resolve_safe();
221
222 // allocate timers
223 m_hblank_timer = timer_alloc(TIMER_HBLANK);
224 m_nmi_timer = timer_alloc(TIMER_NMI);
225 m_scanline_timer = timer_alloc(TIMER_SCANLINE);
226
227 /* initialize the scanline handling portion */
228 m_scanline_timer->adjust(screen().time_until_pos(1));
229 m_hblank_timer->adjust(m_cpu->cycles_to_attotime(260) / 3); // ??? FIXME - hardcoding NTSC, need better calculation
230 m_nmi_timer->adjust(attotime::never);
231
232 /* allocate a screen bitmap, videomem and spriteram, a dirtychar array and the monochromatic colortable */
233 m_bitmap = std::make_unique<bitmap_rgb32>(VISIBLE_SCREEN_WIDTH, VISIBLE_SCREEN_HEIGHT);
234 m_spriteram = make_unique_clear<uint8_t[]>(SPRITERAM_SIZE);
235
236 init_palette_tables();
237
238 // register for state saving
239 save_item(NAME(m_scanline));
240 save_item(NAME(m_refresh_data));
241 save_item(NAME(m_refresh_latch));
242 save_item(NAME(m_x_fine));
243 save_item(NAME(m_toggle));
244 save_item(NAME(m_add));
245 save_item(NAME(m_videomem_addr));
246 save_item(NAME(m_data_latch));
247 save_item(NAME(m_buffered_data));
248 save_item(NAME(m_tile_page));
249 save_item(NAME(m_sprite_page));
250 save_item(NAME(m_back_color));
251 save_item(NAME(m_scan_scale));
252 save_item(NAME(m_scanlines_per_frame));
253 save_item(NAME(m_vblank_first_scanline));
254 save_item(NAME(m_regs));
255 save_item(NAME(m_draw_phase));
256 save_item(NAME(m_tilecount));
257 save_pointer(NAME(m_spriteram), SPRITERAM_SIZE);
258
259 save_item(NAME(*m_bitmap));
260 }
261
device_start()262 void ppu2c0x_device::device_start()
263 {
264 start_nopalram();
265
266 m_palette_ram.resize(0x20);
267
268 for (int i = 0; i < 0x20; i++)
269 m_palette_ram[i] = 0x00;
270
271 save_item(NAME(m_palette_ram));
272 }
273
device_start()274 void ppu2c04_clone_device::device_start()
275 {
276 ppu2c0x_device::device_start();
277
278 /* this PPU clone draws sprites into a frame buffer before displaying them,
279 causing sprite rendering to be one frame behind tile/background rendering
280 (mainly noticeable during scrolling)
281 to simulate that, we can just have a secondary OAM buffer and swap them
282 at the end of each frame.
283
284 (theoretically this can cause the wrong sprite tiles to be drawn for
285 one frame after changing CHR banks, but the Vs. SMB bootlegs that use
286 this clone hardware don't actually have CHR bank switching anyway.
287 also generally affects PPU-side read timings involving the OAM, but
288 this still doesn't seem to matter for Vs. SMB specifically)
289 */
290 m_spritebuf = make_unique_clear<uint8_t[]>(SPRITERAM_SIZE);
291 save_pointer(NAME(m_spritebuf), SPRITERAM_SIZE);
292 }
293
294 //**************************************************************************
295 // INLINE HELPERS
296 //**************************************************************************
297
298 //-------------------------------------------------
299 // readbyte - read a byte at the given address
300 //-------------------------------------------------
301
readbyte(offs_t address)302 uint8_t ppu2c0x_device::readbyte(offs_t address)
303 {
304 return space().read_byte(address);
305 }
306
307
308 //-------------------------------------------------
309 // writebyte - write a byte at the given address
310 //-------------------------------------------------
311
writebyte(offs_t address,uint8_t data)312 inline void ppu2c0x_device::writebyte(offs_t address, uint8_t data)
313 {
314 space().write_byte(address, data);
315 }
316
317
318 /***************************************************************************
319 IMPLEMENTATION
320 ***************************************************************************/
321
322 /*************************************
323 *
324 * PPU Palette Initialization
325 *
326 *************************************/
327
apply_color_emphasis_and_clamp(bool is_pal_or_dendy,int color_emphasis,double & R,double & G,double & B)328 void ppu2c0x_device::apply_color_emphasis_and_clamp(bool is_pal_or_dendy, int color_emphasis, double& R, double& G, double& B)
329 {
330 if (is_pal_or_dendy) // PAL machines swap the colour emphasis bits, this means the red/blue highlighting on rampart tally bar doesn't look as good
331 {
332 color_emphasis = bitswap<3>(color_emphasis, 2, 0, 1);
333 }
334
335 double r_mod = 0.0;
336 double g_mod = 0.0;
337 double b_mod = 0.0;
338
339 switch (color_emphasis)
340 {
341 case 0: r_mod = 1.0; g_mod = 1.0; b_mod = 1.0; break;
342 case 1: r_mod = 1.24; g_mod = .915; b_mod = .743; break;
343 case 2: r_mod = .794; g_mod = 1.09; b_mod = .882; break;
344 case 3: r_mod = .905; g_mod = 1.03; b_mod = 1.28; break;
345 case 4: r_mod = .741; g_mod = .987; b_mod = 1.0; break;
346 case 5: r_mod = 1.02; g_mod = .908; b_mod = .979; break;
347 case 6: r_mod = 1.02; g_mod = .98; b_mod = .653; break;
348 case 7: r_mod = .75; g_mod = .75; b_mod = .75; break;
349 }
350
351 R = R * r_mod;
352 G = G * g_mod;
353 B = B * b_mod;
354
355 /* Clipping, in case of saturation */
356 if (R < 0)
357 R = 0;
358 if (R > 255)
359 R = 255;
360 if (G < 0)
361 G = 0;
362 if (G > 255)
363 G = 255;
364 if (B < 0)
365 B = 0;
366 if (B > 255)
367 B = 255;
368 }
369
nespal_to_RGB(int color_intensity,int color_num,int color_emphasis,bool is_pal_or_dendy)370 rgb_t ppu2c0x_device::nespal_to_RGB(int color_intensity, int color_num, int color_emphasis, bool is_pal_or_dendy)
371 {
372 const double tint = 0.22; /* adjust to taste */
373 const double hue = 287.0;
374
375 const double Kr = 0.2989;
376 const double Kb = 0.1145;
377 const double Ku = 2.029;
378 const double Kv = 1.140;
379
380 static const double brightness[3][4] =
381 {
382 { 0.50, 0.75, 1.0, 1.0 },
383 { 0.29, 0.45, 0.73, 0.9 },
384 { 0, 0.24, 0.47, 0.77 }
385 };
386
387 double sat;
388 double y, u, v;
389 double rad;
390
391 switch (color_num)
392 {
393 case 0:
394 sat = 0; rad = 0;
395 y = brightness[0][color_intensity];
396 break;
397
398 case 13:
399 sat = 0; rad = 0;
400 y = brightness[2][color_intensity];
401 break;
402
403 case 14:
404 case 15:
405 sat = 0; rad = 0; y = 0;
406 break;
407
408 default:
409 sat = tint;
410 rad = M_PI * ((color_num * 30 + hue) / 180.0);
411 y = brightness[1][color_intensity];
412 break;
413 }
414
415 u = sat * cos(rad);
416 v = sat * sin(rad);
417
418 /* Transform to RGB */
419 double R = (y + Kv * v) * 255.0;
420 double G = (y - (Kb * Ku * u + Kr * Kv * v) / (1 - Kb - Kr)) * 255.0;
421 double B = (y + Ku * u) * 255.0;
422
423 apply_color_emphasis_and_clamp(is_pal_or_dendy, color_emphasis, R, G, B);
424
425 return rgb_t(floor(R + .5), floor(G + .5), floor(B + .5));
426 }
427
init_palette_tables()428 void ppu2c0x_device::init_palette_tables()
429 {
430 bool is_pal = m_scanlines_per_frame != NTSC_SCANLINES_PER_FRAME;
431
432 /* This routine builds a palette using a transformation from */
433 /* the YUV (Y, B-Y, R-Y) to the RGB color space */
434
435 /* The NES has a 64 color palette */
436 /* 16 colors, with 4 luminance levels for each color */
437 /* The 16 colors circle around the YUV color space, */
438
439 int entry = 0;
440
441 /* Loop through the emphasis modes (8 total) */
442 for (int color_emphasis = 0; color_emphasis < 8; color_emphasis++)
443 {
444 /* loop through the 4 intensities */
445 for (int color_intensity = 0; color_intensity < 4; color_intensity++)
446 {
447 /* loop through the 16 colors */
448 for (int color_num = 0; color_num < 16; color_num++)
449 {
450 rgb_t col = nespal_to_RGB(color_intensity, color_num, color_emphasis, is_pal);
451
452 m_nespens[entry] = (uint32_t)col;
453 entry++;
454
455 }
456 }
457 }
458 }
459
init_palette_tables()460 void ppu2c0x_rgb_device::init_palette_tables()
461 {
462 /* Loop through the emphasis modes (8 total) */
463 int entry = 0;
464 for (int color_emphasis = 0; color_emphasis < 8; color_emphasis++)
465 {
466 for (int color_num = 0; color_num < 64; color_num++)
467 {
468 int R = ((color_emphasis & 1) ? 7 : m_palette_data[color_num * 3]);
469 int G = ((color_emphasis & 2) ? 7 : m_palette_data[color_num * 3 + 1]);
470 int B = ((color_emphasis & 4) ? 7 : m_palette_data[color_num * 3 + 2]);
471
472 m_nespens[entry] = (pal3bit(R)<<16) | (pal3bit(G)<<8) | pal3bit(B);
473
474 //set_pen_color(entry++, pal3bit(R), pal3bit(G), pal3bit(B));
475 entry++;
476 }
477 }
478 }
479
init_palette_tables()480 void ppu2c04_clone_device::init_palette_tables()
481 {
482 /* clone HW doesn't use color emphasis bits.
483 however, it does have two separate palettes: colors 0-63 for background, and 64-127 for sprites
484 (although the tile and sprite colors are identical in the Vs. SMB bootleg ROMs)
485 */
486 for (int color_num = 0; color_num < 64*2; color_num++)
487 {
488 /* A7 line on palette ROMs is always high, color bits are in reverse order */
489 u8 color = m_palette_data[color_num | 0x80];
490 int R = bitswap<3>(color, 0, 1, 2);
491 int G = bitswap<3>(color, 3, 4, 5);
492 int B = bitswap<2>(color, 6, 7);
493
494 m_nespens[color_num] = (pal3bit(R) << 16) | (pal3bit(G) << 8) | pal2bit(B);
495 }
496 }
497
498 /*************************************
499 *
500 * PPU Initialization and Disposal
501 *
502 *************************************/
503
504 //-------------------------------------------------
505 // device_timer - handle timer events
506 //-------------------------------------------------
507
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)508 void ppu2c0x_device::device_timer(emu_timer& timer, device_timer_id id, int param, void* ptr)
509 {
510 int blanked, vblank;
511
512 switch (id)
513 {
514 case TIMER_HBLANK:
515 blanked = (m_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES)) == 0;
516 vblank = ((m_scanline >= m_vblank_first_scanline - 1) && (m_scanline < m_scanlines_per_frame - 1)) ? 1 : 0;
517
518 //update_scanline();
519
520 if (!m_hblank_callback_proc.isnull())
521 m_hblank_callback_proc(m_scanline, vblank, blanked);
522
523 m_hblank_timer->adjust(attotime::never);
524 break;
525
526 case TIMER_NMI:
527 // Actually fire the VMI
528 m_int_callback(ASSERT_LINE);
529 m_int_callback(CLEAR_LINE);
530
531 m_nmi_timer->adjust(attotime::never);
532 break;
533
534 case TIMER_SCANLINE:
535 blanked = (m_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES)) == 0;
536 vblank = ((m_scanline >= m_vblank_first_scanline - 1) && (m_scanline < m_scanlines_per_frame - 1)) ? 1 : 0;
537 int next_scanline;
538
539 /* if a callback is available, call it */
540 if (!m_scanline_callback_proc.isnull())
541 m_scanline_callback_proc(m_scanline, vblank, blanked);
542
543 /* update the scanline that just went by */
544 update_scanline();
545
546 /* increment our scanline count */
547 m_scanline++;
548
549 //logerror("starting scanline %d (MAME %d, beam %d)\n", m_scanline, device->screen().vpos(), device->screen().hpos());
550
551 /* Note: this is called at the _end_ of each scanline */
552 if (m_scanline == m_vblank_first_scanline)
553 {
554 // logerror("vblank starting\n");
555 /* We just entered VBLANK */
556 m_regs[PPU_STATUS] |= PPU_STATUS_VBLANK;
557
558 /* If NMI's are set to be triggered, go for it */
559 if (m_regs[PPU_CONTROL0] & PPU_CONTROL0_NMI)
560 {
561 // We need an ever-so-slight delay between entering vblank and firing an NMI - enough so that
562 // a game can read the high bit of $2002 before the NMI is called (potentially resetting the bit
563 // via a read from $2002 in the NMI handler).
564 // B-Wings is an example game that needs this.
565 m_nmi_timer->adjust(m_cpu->cycles_to_attotime(4));
566 }
567 }
568
569 if (m_scanline == m_scanlines_per_frame - 1)
570 {
571 //logerror("vblank ending\n");
572 /* clear the vblank & sprite hit flag */
573 m_regs[PPU_STATUS] &= ~(PPU_STATUS_VBLANK | PPU_STATUS_SPRITE0_HIT | PPU_STATUS_8SPRITES);
574 }
575
576 /* see if we rolled */
577 else if (m_scanline == m_scanlines_per_frame)
578 {
579 /* if background or sprites are enabled, copy the ppu address latch */
580 if (!blanked)
581 m_refresh_data = m_refresh_latch;
582
583 /* reset the scanline count */
584 m_scanline = 0;
585 //logerror("sprite 0 x: %d y: %d num: %d\n", m_spriteram[3], m_spriteram[0] + 1, m_spriteram[1]);
586 }
587
588 next_scanline = m_scanline + 1;
589 if (next_scanline == m_scanlines_per_frame)
590 next_scanline = 0;
591
592 // Call us back when the hblank starts for this scanline
593 m_hblank_timer->adjust(m_cpu->cycles_to_attotime(260) / 3); // ??? FIXME - hardcoding NTSC, need better calculation
594
595 // trigger again at the start of the next scanline
596 m_scanline_timer->adjust(screen().time_until_pos(next_scanline * m_scan_scale));
597 break;
598 }
599 }
600
601
read_tile_plane_data(int address,int color)602 void ppu2c0x_device::read_tile_plane_data(int address, int color)
603 {
604 m_planebuf[0] = readbyte((address & 0x1fff));
605 m_planebuf[1] = readbyte((address + 8) & 0x1fff);
606 }
607
shift_tile_plane_data(uint8_t & pix)608 void ppu2c0x_device::shift_tile_plane_data(uint8_t& pix)
609 {
610 pix = ((m_planebuf[0] >> 7) & 1) | (((m_planebuf[1] >> 7) & 1) << 1);
611 m_planebuf[0] = m_planebuf[0] << 1;
612 m_planebuf[1] = m_planebuf[1] << 1;
613 }
614
draw_tile_pixel(uint8_t pix,int color,uint32_t back_pen,uint32_t * & dest)615 void ppu2c0x_device::draw_tile_pixel(uint8_t pix, int color, uint32_t back_pen, uint32_t*& dest)
616 {
617 uint32_t usepen;
618
619 if (pix)
620 {
621 uint8_t pen = ((4 * color) + pix) & 0x1f;
622 uint16_t palval = m_palette_ram[pen];
623
624 if (m_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
625 palval &= 0x30;
626
627 // apply colour emphasis
628 palval |= ((m_regs[PPU_CONTROL1] & PPU_CONTROL1_COLOR_EMPHASIS) << 1);
629
630 usepen = m_nespens[palval];
631 }
632 else
633 {
634 usepen = m_nespens[back_pen];
635 }
636
637
638
639 *dest = usepen;
640 }
641
draw_tile(uint8_t * line_priority,int color_byte,int color_bits,int address,int start_x,uint32_t back_pen,uint32_t * & dest)642 void ppu2c0x_device::draw_tile(uint8_t* line_priority, int color_byte, int color_bits, int address, int start_x, uint32_t back_pen, uint32_t*& dest)
643 {
644 int color = (((color_byte >> color_bits) & 0x03));
645
646 read_tile_plane_data(address, color);
647
648 /* render the pixel */
649 for (int i = 0; i < 8; i++)
650 {
651 uint8_t pix;
652 shift_tile_plane_data(pix);
653
654 if ((start_x + i) >= 0 && (start_x + i) < VISIBLE_SCREEN_WIDTH)
655 {
656 draw_tile_pixel(pix, color, back_pen, dest);
657
658 // priority marking
659 if (pix)
660 line_priority[start_x + i] |= 0x02;
661 }
662 dest++;
663 }
664 }
665
666
667 // m_refresh_data is important as it is updated during rendering, and overwritten when you write new scroll values
668 // making raster effects more complex than on other systems
669 // https://retrocomputing.stackexchange.com/questions/1898/how-can-i-create-a-split-scroll-effect-in-an-nes-game
670
draw_background(uint8_t * line_priority)671 void ppu2c0x_device::draw_background(uint8_t* line_priority)
672 {
673 bitmap_rgb32& bitmap = *m_bitmap;
674
675 /* determine where in the nametable to start drawing from */
676 /* based on the current scanline and scroll regs */
677 uint8_t scroll_x_coarse = m_refresh_data & 0x001f;
678 uint8_t scroll_y_coarse = (m_refresh_data & 0x03e0) >> 5;
679 uint16_t nametable = (m_refresh_data & 0x0c00);
680 uint8_t scroll_y_fine = (m_refresh_data & 0x7000) >> 12;
681
682 int x = scroll_x_coarse;
683
684 /* get the tile index */
685 int tile_index = (nametable | 0x2000) + scroll_y_coarse * 32;
686
687 /* set up dest */
688 int start_x = (m_x_fine ^ 0x07) - 7;
689 uint32_t* dest = &bitmap.pix(m_scanline, start_x);
690
691 m_tilecount = 0;
692
693 /* draw the 32 or 33 tiles that make up a line */
694 while (m_tilecount < 34)
695 {
696 int color_byte;
697 int color_bits;
698 int pos;
699 int index1;
700 int page, page2, address;
701
702 index1 = tile_index + x;
703
704 // page2 is the output of the nametable read (this section is the FIRST read per tile!)
705 page2 = readbyte(index1);
706
707 // this is attribute table stuff! (actually read 2 in PPUspeak)!
708 /* Figure out which byte in the color table to use */
709 pos = ((index1 & 0x380) >> 4) | ((index1 & 0x1f) >> 2);
710 page = (index1 & 0x0c00) >> 10;
711 address = 0x3c0 + pos;
712 color_byte = readbyte((((page * 0x400) + address) & 0xfff) + 0x2000);
713
714 /* figure out which bits in the color table to use */
715 color_bits = ((index1 & 0x40) >> 4) + (index1 & 0x02);
716
717 // 27/12/2002
718 if (!m_latch.isnull())
719 m_latch((m_tile_page << 10) | (page2 << 4));
720
721 if (start_x < VISIBLE_SCREEN_WIDTH)
722 {
723 // need to read 0x0000 or 0x1000 + 16*nametable data
724 address = ((m_tile_page) ? 0x1000 : 0) + (page2 * 16);
725 // plus something that accounts for y
726 address += scroll_y_fine;
727
728 draw_tile(line_priority, color_byte, color_bits, address, start_x, m_back_color, dest);
729
730 start_x += 8;
731
732 /* move to next tile over and toggle the horizontal name table if necessary */
733 x++;
734 if (x > 31)
735 {
736 x = 0;
737 tile_index ^= 0x400;
738 }
739 }
740 m_tilecount++;
741 }
742
743 /* if the left 8 pixels for the background are off, blank 'em */
744 if (!(m_regs[PPU_CONTROL1] & PPU_CONTROL1_BACKGROUND_L8))
745 {
746 dest = &bitmap.pix(m_scanline);
747 for (int i = 0; i < 8; i++)
748 {
749 draw_back_pen(dest, m_back_color);
750 dest++;
751
752 line_priority[i] ^= 0x02;
753 }
754 }
755 }
756
draw_background(uint8_t * line_priority)757 void ppu2c04_clone_device::draw_background(uint8_t* line_priority)
758 {
759 // nametable selection is ignored below the hardwired scroll split position
760 if (m_scanline < 31)
761 m_refresh_data &= ~0x0c00;
762
763 ppu2c0x_device::draw_background(line_priority);
764 }
765
draw_back_pen(uint32_t * dst,int back_pen)766 void ppu2c0x_device::draw_back_pen(uint32_t* dst, int back_pen)
767 {
768 *dst = m_nespens[back_pen];
769 }
770
draw_background_pen()771 void ppu2c0x_device::draw_background_pen()
772 {
773 bitmap_rgb32& bitmap = *m_bitmap;
774
775 /* setup the color mask and colortable to use */
776 uint8_t color_mask = (m_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO) ? 0x30 : 0x3f;
777
778 // Fill this scanline with the background pen.
779 for (int i = 0; i < bitmap.width(); i++)
780 draw_back_pen(&bitmap.pix(m_scanline, i), m_back_color & color_mask);
781 }
782
read_sprite_plane_data(int address)783 void ppu2c0x_device::read_sprite_plane_data(int address)
784 {
785 m_planebuf[0] = readbyte((address + 0) & 0x1fff);
786 m_planebuf[1] = readbyte((address + 8) & 0x1fff);
787 }
788
make_sprite_pixel_data(uint8_t & pixel_data,int flipx)789 void ppu2c0x_device::make_sprite_pixel_data(uint8_t& pixel_data, int flipx)
790 {
791 if (flipx)
792 {
793 pixel_data = (m_planebuf[0] & 1) + ((m_planebuf[1] & 1) << 1);
794 m_planebuf[0] = m_planebuf[0] >> 1;
795 m_planebuf[1] = m_planebuf[1] >> 1;
796 }
797 else
798 {
799 pixel_data = ((m_planebuf[0] >> 7) & 1) | (((m_planebuf[1] >> 7) & 1) << 1);
800 m_planebuf[0] = m_planebuf[0] << 1;
801 m_planebuf[1] = m_planebuf[1] << 1;
802 }
803 }
804
draw_sprite_pixel(int sprite_xpos,int color,int pixel,uint8_t pixel_data,bitmap_rgb32 & bitmap)805 void ppu2c0x_device::draw_sprite_pixel(int sprite_xpos, int color, int pixel, uint8_t pixel_data, bitmap_rgb32& bitmap)
806 {
807 uint16_t palval = m_palette_ram[((4 * color) | pixel_data) & 0x1f];
808
809 if (m_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
810 palval &= 0x30;
811
812 // apply colour emphasis
813 palval |= ((m_regs[PPU_CONTROL1] & PPU_CONTROL1_COLOR_EMPHASIS) << 1);
814
815 uint32_t pix = m_nespens[palval];
816 bitmap.pix(m_scanline, sprite_xpos + pixel) = pix;
817 }
818
draw_sprite_pixel(int sprite_xpos,int color,int pixel,uint8_t pixel_data,bitmap_rgb32 & bitmap)819 void ppu2c04_clone_device::draw_sprite_pixel(int sprite_xpos, int color, int pixel, uint8_t pixel_data, bitmap_rgb32 &bitmap)
820 {
821 /* clone PPU clips sprites at the screen edges */
822 if ((sprite_xpos + pixel < 8) || (sprite_xpos + pixel) >= (VISIBLE_SCREEN_WIDTH - 6))
823 return;
824
825 uint16_t palval = m_palette_ram[((4 * color) | pixel_data) & 0x1f];
826 uint32_t pix = m_nespens[palval | 0x40];
827 bitmap.pix(m_scanline, sprite_xpos + pixel) = pix;
828 }
829
read_extra_sprite_bits(int sprite_index)830 void ppu2c0x_device::read_extra_sprite_bits(int sprite_index)
831 {
832 // needed for some clones
833 }
834
is_spritepixel_opaque(int pixel_data,int color)835 bool ppu2c0x_device::is_spritepixel_opaque(int pixel_data, int color)
836 {
837 if (pixel_data)
838 return true;
839 else
840 return false;
841 }
842
draw_sprite_pixel_low(bitmap_rgb32 & bitmap,int pixel_data,int pixel,int sprite_xpos,int color,int sprite_index,uint8_t * line_priority)843 void ppu2c0x_device::draw_sprite_pixel_low(bitmap_rgb32& bitmap, int pixel_data, int pixel, int sprite_xpos, int color, int sprite_index, uint8_t* line_priority)
844 {
845 if (is_spritepixel_opaque(pixel_data, color))
846 {
847 /* has the background (or another sprite) already been drawn here? */
848 if ((sprite_xpos + pixel) < VISIBLE_SCREEN_WIDTH)
849 {
850 if (!line_priority[sprite_xpos + pixel])
851 {
852 /* no, draw */
853 draw_sprite_pixel(sprite_xpos, color, pixel, pixel_data, bitmap);
854 }
855 /* indicate that a sprite was drawn at this location, even if it's not seen */
856 line_priority[sprite_xpos + pixel] |= 0x01;
857 }
858 }
859
860 /* set the "sprite 0 hit" flag if appropriate */
861 if (sprite_index == 0 && (pixel_data & 0x03) && ((sprite_xpos + pixel) < 255) && (line_priority[sprite_xpos + pixel] & 0x02))
862 m_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
863 }
864
draw_sprite_pixel_high(bitmap_rgb32 & bitmap,int pixel_data,int pixel,int sprite_xpos,int color,int sprite_index,uint8_t * line_priority)865 void ppu2c0x_device::draw_sprite_pixel_high(bitmap_rgb32& bitmap, int pixel_data, int pixel, int sprite_xpos, int color, int sprite_index, uint8_t* line_priority)
866 {
867 if (is_spritepixel_opaque(pixel_data, color))
868 {
869 if ((sprite_xpos + pixel) < VISIBLE_SCREEN_WIDTH)
870 {
871 /* has another sprite been drawn here? */
872 if (!(line_priority[sprite_xpos + pixel] & 0x01))
873 {
874 /* no, draw */
875 draw_sprite_pixel(sprite_xpos, color, pixel, pixel_data, bitmap);
876 line_priority[sprite_xpos + pixel] |= 0x01;
877 }
878 }
879 }
880
881 /* set the "sprite 0 hit" flag if appropriate */
882 if (sprite_index == 0 && (pixel_data & 0x03) && ((sprite_xpos + pixel) < 255) && (line_priority[sprite_xpos + pixel] & 0x02))
883 m_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
884 }
885
apply_sprite_pattern_page(int index1,int size)886 int ppu2c0x_device::apply_sprite_pattern_page(int index1, int size)
887 {
888 if (size == 8)
889 index1 += ((m_sprite_page == 0) ? 0 : 0x1000);
890
891 return index1;
892 }
893
draw_sprites(uint8_t * line_priority)894 void ppu2c0x_device::draw_sprites(uint8_t* line_priority)
895 {
896 bitmap_rgb32& bitmap = *m_bitmap;
897
898 int sprite_xpos, sprite_ypos, sprite_index;
899 int tile, index1;
900 int pri;
901
902 int flipx, flipy, color;
903 int size;
904 int sprite_count = 0;
905 int sprite_line;
906
907 int first_pixel;
908 int pixel;
909
910 /* determine if the sprites are 8x8 or 8x16 */
911 size = (m_regs[PPU_CONTROL0] & PPU_CONTROL0_SPRITE_SIZE) ? 16 : 8;
912
913 first_pixel = (m_regs[PPU_CONTROL1] & PPU_CONTROL1_SPRITES_L8) ? 0 : 8;
914
915 for (sprite_index = 0; sprite_index < SPRITERAM_SIZE; sprite_index += 4)
916 {
917 sprite_ypos = m_spriteram[sprite_index] + 1;
918 sprite_xpos = m_spriteram[sprite_index + 3];
919
920 // The sprite collision acts funny on the last pixel of a scanline.
921 // The various scanline latches update while the last few pixels
922 // are being drawn. Since we don't do cycle-by-cycle PPU emulation,
923 // we fudge it a bit here so that sprite 0 collisions are detected
924 // when, e.g., sprite x is 254, sprite y is 29 and we're rendering
925 // at the end of scanline 28.
926 // Battletoads needs this level of precision to be playable.
927 if ((sprite_index == 0) && (sprite_xpos == 254))
928 {
929 sprite_ypos--;
930 /* set the "sprite 0 hit" flag if appropriate */
931 if (line_priority[sprite_xpos] & 0x02)
932 m_regs[PPU_STATUS] |= PPU_STATUS_SPRITE0_HIT;
933 }
934
935 /* if the sprite isn't visible, skip it */
936 if ((sprite_ypos + size <= m_scanline) || (sprite_ypos > m_scanline))
937 continue;
938
939 tile = m_spriteram[sprite_index + 1];
940 color = (m_spriteram[sprite_index + 2] & 0x03) + 4;
941 pri = m_spriteram[sprite_index + 2] & 0x20;
942 flipx = m_spriteram[sprite_index + 2] & 0x40;
943 flipy = m_spriteram[sprite_index + 2] & 0x80;
944 read_extra_sprite_bits(sprite_index);
945
946 if (size == 16)
947 {
948 /* if it's 8x16 and odd-numbered, draw the other half instead */
949 if (tile & 0x01)
950 {
951 tile &= ~0x01;
952 tile |= 0x100;
953 }
954 }
955
956 if (!m_latch.isnull())
957 m_latch((m_sprite_page << 10) | ((tile & 0xff) << 4));
958
959 /* compute the character's line to draw */
960 sprite_line = m_scanline - sprite_ypos;
961
962 if (flipy)
963 sprite_line = (size - 1) - sprite_line;
964
965 if (size == 16 && sprite_line > 7)
966 {
967 tile++;
968 sprite_line -= 8;
969 }
970
971 index1 = tile * 16;
972
973 index1 = apply_sprite_pattern_page(index1, size);
974
975 read_sprite_plane_data(index1 + sprite_line);
976
977 /* if there are more than 8 sprites on this line, set the flag */
978 if (sprite_count == 8)
979 {
980 m_regs[PPU_STATUS] |= PPU_STATUS_8SPRITES;
981 //logerror ("> 8 sprites, scanline: %d\n", m_scanline);
982
983 /* the real NES only draws up to 8 sprites - the rest should be invisible */
984 break;
985 }
986
987 sprite_count++;
988
989 /* abort drawing if sprites aren't rendered */
990 if (!(m_regs[PPU_CONTROL1] & PPU_CONTROL1_SPRITES))
991 continue;
992
993 if (pri)
994 {
995 /* draw the low-priority sprites */
996 for (pixel = 0; pixel < 8; pixel++)
997 {
998 uint8_t pixel_data;
999 make_sprite_pixel_data(pixel_data, flipx);
1000
1001 /* is this pixel non-transparent? */
1002 if (sprite_xpos + pixel >= first_pixel)
1003 {
1004 draw_sprite_pixel_low(bitmap, pixel_data, pixel, sprite_xpos, color, sprite_index, line_priority);
1005 }
1006 }
1007 }
1008 else
1009 {
1010 /* draw the high-priority sprites */
1011 for (pixel = 0; pixel < 8; pixel++)
1012 {
1013 uint8_t pixel_data;
1014 make_sprite_pixel_data(pixel_data, flipx);
1015
1016 /* is this pixel non-transparent? */
1017 if (sprite_xpos + pixel >= first_pixel)
1018 {
1019 draw_sprite_pixel_high(bitmap, pixel_data, pixel, sprite_xpos, color, sprite_index, line_priority);
1020 }
1021 }
1022 }
1023 }
1024 }
1025
draw_sprites(uint8_t * line_priority)1026 void ppu2c04_clone_device::draw_sprites(uint8_t *line_priority)
1027 {
1028 ppu2c0x_device::draw_sprites(line_priority);
1029
1030 if (m_scanline == BOTTOM_VISIBLE_SCANLINE)
1031 {
1032 /* this frame's sprite buffer is cleared after being displayed
1033 and the other one that was filled this frame will be displayed next frame */
1034 m_spriteram.swap(m_spritebuf);
1035 memset(m_spritebuf.get(), 0, SPRITERAM_SIZE);
1036 }
1037 }
1038
1039 /*************************************
1040 *
1041 * Scanline Rendering and Update
1042 *
1043 *************************************/
1044
render_scanline()1045 void ppu2c0x_device::render_scanline()
1046 {
1047 uint8_t line_priority[VISIBLE_SCREEN_WIDTH];
1048
1049 /* lets see how long it takes */
1050 g_profiler.start(PROFILER_USER1);
1051
1052 /* clear the line priority for this scanline */
1053 memset(line_priority, 0, VISIBLE_SCREEN_WIDTH);
1054
1055 m_draw_phase = PPU_DRAW_BG;
1056
1057 /* see if we need to render the background */
1058 if (m_regs[PPU_CONTROL1] & PPU_CONTROL1_BACKGROUND)
1059 {
1060 draw_background(line_priority);
1061 }
1062 else
1063 {
1064 draw_background_pen();
1065 }
1066
1067 m_draw_phase = PPU_DRAW_OAM;
1068
1069 /* if sprites are on, draw them, but we call always to process them */
1070 draw_sprites(line_priority);
1071
1072 m_draw_phase = PPU_DRAW_BG;
1073
1074 /* done updating, whew */
1075 g_profiler.stop();
1076 }
1077
scanline_increment_fine_ycounter()1078 void ppu2c0x_device::scanline_increment_fine_ycounter()
1079 {
1080 /* increment the fine y-scroll */
1081 m_refresh_data += 0x1000;
1082
1083 /* if it's rolled, increment the coarse y-scroll */
1084 if (m_refresh_data & 0x8000)
1085 {
1086 uint16_t tmp;
1087 tmp = (m_refresh_data & 0x03e0) + 0x20;
1088 m_refresh_data &= 0x7c1f;
1089
1090 /* handle bizarro scrolling rollover at the 30th (not 32nd) vertical tile */
1091 if (tmp == 0x03c0)
1092 m_refresh_data ^= 0x0800;
1093 else
1094 m_refresh_data |= (tmp & 0x03e0);
1095
1096 //logerror("updating refresh_data: %04x\n", m_refresh_data);
1097 }
1098 }
1099
update_visible_enabled_scanline()1100 void ppu2c0x_device::update_visible_enabled_scanline()
1101 {
1102 if (m_scanline_timer->remaining() == attotime::zero)
1103 {
1104 /* If background or sprites are enabled, copy the ppu address latch */
1105 /* Copy only the scroll x-coarse and the x-overflow bit */
1106 m_refresh_data &= ~0x041f;
1107 m_refresh_data |= (m_refresh_latch & 0x041f);
1108 }
1109
1110 //logerror("updating refresh_data: %04x (scanline: %d)\n", m_refresh_data, m_scanline);
1111 render_scanline();
1112 }
1113
update_visible_disabled_scanline()1114 void ppu2c0x_device::update_visible_disabled_scanline()
1115 {
1116 bitmap_rgb32& bitmap = *m_bitmap;
1117 uint32_t back_pen;
1118
1119 /* setup the color mask and colortable to use */
1120 uint8_t color_mask = (m_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO) ? 0x30 : 0x3f;
1121
1122 uint16_t palval = m_back_color & color_mask;
1123
1124 back_pen = palval;
1125
1126 if (m_paletteram_in_ppuspace)
1127 {
1128 /* cache the background pen */
1129 if (m_videomem_addr >= 0x3f00)
1130 {
1131 // If the PPU's VRAM address happens to point into palette ram space while
1132 // both the sprites and background are disabled, the PPU paints the scanline
1133 // with the palette entry at the VRAM address instead of the usual background
1134 // pen. Micro Machines makes use of this feature.
1135 int pen_num = m_palette_ram[(m_videomem_addr & 0x03) ? (m_videomem_addr & 0x1f) : 0];
1136
1137 back_pen = pen_num;
1138 }
1139 }
1140
1141 // Fill this scanline with the background pen.
1142 for (int i = 0; i < bitmap.width(); i++)
1143 draw_back_pen(&bitmap.pix(m_scanline, i), back_pen);
1144 }
1145
update_visible_scanline()1146 void ppu2c0x_device::update_visible_scanline()
1147 {
1148 /* Render this scanline if appropriate */
1149 if (m_regs[PPU_CONTROL1] & (PPU_CONTROL1_BACKGROUND | PPU_CONTROL1_SPRITES))
1150 {
1151 update_visible_enabled_scanline();
1152 }
1153 else
1154 {
1155 update_visible_disabled_scanline();
1156 }
1157
1158 if (m_scanline_timer->remaining() == attotime::zero)
1159 {
1160 scanline_increment_fine_ycounter();
1161 }
1162 }
1163
update_scanline()1164 void ppu2c0x_device::update_scanline()
1165 {
1166 if (m_scanline <= BOTTOM_VISIBLE_SCANLINE)
1167 {
1168 update_visible_scanline();
1169 }
1170 }
1171
1172 /*************************************
1173 *
1174 * PPU Memory functions
1175 *
1176 *************************************/
1177
palette_write(offs_t offset,uint8_t data)1178 void ppu2c0x_device::palette_write(offs_t offset, uint8_t data)
1179 {
1180 // palette RAM is only 6 bits wide
1181 data &= 0x3f;
1182
1183 if (offset & 0x3)
1184 {
1185 // regular pens, no mirroring
1186 m_palette_ram[offset & 0x1f] = data;
1187 }
1188 else
1189 {
1190 // transparent pens are mirrored!
1191 if (0 == (offset & 0xf))
1192 {
1193 m_back_color = data;
1194 }
1195 m_palette_ram[offset & 0xf] = m_palette_ram[(offset & 0xf) + 0x10] = data;
1196 }
1197 }
1198
palette_read(offs_t offset)1199 uint8_t ppu2c0x_device::palette_read(offs_t offset)
1200 {
1201 if (m_regs[PPU_CONTROL1] & PPU_CONTROL1_DISPLAY_MONO)
1202 return (m_palette_ram[offset & 0x1f] & 0x30);
1203
1204 else
1205 return (m_palette_ram[offset & 0x1f]);
1206 }
1207
1208 /*************************************
1209 *
1210 * PPU Registers Read
1211 *
1212 *************************************/
1213
read(offs_t offset)1214 uint8_t ppu2c0x_device::read(offs_t offset)
1215 {
1216 if (offset >= PPU_MAX_REG)
1217 {
1218 logerror("PPU %s: Attempting to read past the chip: offset %x\n", this->tag(), offset);
1219 offset &= PPU_MAX_REG - 1;
1220 }
1221
1222 // see which register to read
1223 switch (offset & 7)
1224 {
1225 case PPU_STATUS: /* 2 */
1226 // The top 3 bits of the status register are the only ones that report data. The
1227 // remainder contain whatever was last in the PPU data latch, except on the RC2C05 (protection)
1228 if (m_security_value)
1229 m_data_latch = (m_regs[PPU_STATUS] & 0xc0) | m_security_value;
1230 else
1231 m_data_latch = m_regs[PPU_STATUS] | (m_data_latch & 0x1f);
1232
1233 // Reset hi/lo scroll toggle
1234 m_toggle = 0;
1235
1236 // If the vblank bit is set, clear all status bits but the 2 sprite flags
1237 if (m_data_latch & PPU_STATUS_VBLANK)
1238 m_regs[PPU_STATUS] &= 0x60;
1239 break;
1240
1241 case PPU_SPRITE_DATA: /* 4 */
1242 m_data_latch = m_spriteram[m_regs[PPU_SPRITE_ADDRESS]];
1243 break;
1244
1245 case PPU_DATA: /* 7 */
1246 if (!m_latch.isnull())
1247 m_latch(m_videomem_addr & 0x3fff);
1248
1249 if ((m_videomem_addr >= 0x3f00) && m_paletteram_in_ppuspace)
1250 {
1251 m_data_latch = readbyte(m_videomem_addr);
1252 // buffer the mirrored NT data
1253 m_buffered_data = readbyte(m_videomem_addr & 0x2fff);
1254 }
1255 else
1256 {
1257 m_data_latch = m_buffered_data;
1258 m_buffered_data = readbyte(m_videomem_addr);
1259 }
1260
1261 m_videomem_addr += m_add;
1262 break;
1263
1264 default:
1265 break;
1266 }
1267
1268 return m_data_latch;
1269 }
1270
read(offs_t offset)1271 uint8_t ppu2c04_clone_device::read(offs_t offset)
1272 {
1273 switch (offset & 7)
1274 {
1275 case PPU_STATUS: /* 2 */
1276 // $2002 on this clone only contains the sprite 0 hit flag,
1277 // and it's hardwired to trigger after a specific scanline, not based on actual sprite positions
1278 if (m_scanline < 31 || m_scanline >= (m_vblank_first_scanline - 1))
1279 return ~PPU_STATUS_SPRITE0_HIT;
1280 return 0xff;
1281
1282 case PPU_SPRITE_DATA: /* 4 */
1283 return m_spritebuf[m_regs[PPU_SPRITE_ADDRESS]];
1284 }
1285
1286 return ppu2c0x_device::read(offset);
1287 }
1288
1289 /*************************************
1290 *
1291 * PPU Registers Write
1292 *
1293 *************************************/
1294
write(offs_t offset,uint8_t data)1295 void ppu2c0x_device::write(offs_t offset, uint8_t data)
1296 {
1297 if (offset >= PPU_MAX_REG)
1298 {
1299 logerror("PPU %s: Attempting to write past the chip: offset %x, data %x\n", this->tag(), offset, data);
1300 offset &= PPU_MAX_REG - 1;
1301 }
1302
1303 #ifdef MAME_DEBUG
1304 if (m_scanline <= BOTTOM_VISIBLE_SCANLINE)
1305 {
1306 logerror("PPU register %d write %02x during non-vblank scanline %d (MAME %d, beam pos: %d)\n", offset, data, m_scanline, screen().vpos(), screen().hpos());
1307 }
1308 #endif
1309
1310 /* on the RC2C05, PPU_CONTROL0 and PPU_CONTROL1 are swapped (protection) */
1311 if ((m_security_value) && !(offset & 6))
1312 offset ^= 1;
1313
1314 switch (offset & 7)
1315 {
1316 case PPU_CONTROL0: /* 0 */
1317 m_regs[PPU_CONTROL0] = data;
1318
1319 /* update the name table number on our refresh latches */
1320 m_refresh_latch &= 0x73ff;
1321 m_refresh_latch |= (data & 3) << 10;
1322
1323 /* the char ram bank points either 0x0000 or 0x1000 (page 0 or page 4) */
1324 m_tile_page = (data & PPU_CONTROL0_CHR_SELECT) >> 2;
1325 m_sprite_page = (data & PPU_CONTROL0_SPR_SELECT) >> 1;
1326
1327 m_add = (data & PPU_CONTROL0_INC) ? m_line_write_increment_large : 1;
1328 //logerror("control0 write: %02x (scanline: %d)\n", data, m_scanline);
1329 break;
1330
1331 case PPU_CONTROL1: /* 1 */
1332 //logerror("control1 write: %02x (scanline: %d)\n", data, m_scanline);
1333 m_regs[PPU_CONTROL1] = data;
1334 break;
1335
1336 case PPU_SPRITE_ADDRESS: /* 3 */
1337 m_regs[PPU_SPRITE_ADDRESS] = data;
1338 break;
1339
1340 case PPU_SPRITE_DATA: /* 4 */
1341 // If the PPU is currently rendering the screen, 0xff is written instead of the desired data.
1342 if (m_use_sprite_write_limitation)
1343 if (m_scanline <= BOTTOM_VISIBLE_SCANLINE)
1344 data = 0xff;
1345 m_spriteram[m_regs[PPU_SPRITE_ADDRESS]] = data;
1346 m_regs[PPU_SPRITE_ADDRESS] = (m_regs[PPU_SPRITE_ADDRESS] + 1) & 0xff;
1347 break;
1348
1349 case PPU_SCROLL: /* 5 */
1350 if (m_toggle)
1351 {
1352 /* second write */
1353 m_refresh_latch &= 0x0c1f;
1354 m_refresh_latch |= (data & 0xf8) << 2;
1355 m_refresh_latch |= (data & 0x07) << 12;
1356 //logerror("scroll write 2: %d, %04x (scanline: %d)\n", data, m_refresh_latch, m_scanline);
1357 }
1358 else
1359 {
1360 /* first write */
1361 m_refresh_latch &= (0xffe0 & m_global_refresh_mask);
1362 m_refresh_latch |= (data & 0xf8) >> 3;
1363
1364 m_x_fine = data & 7;
1365 //logerror("scroll write 1: %d, %04x (scanline: %d)\n", data, m_refresh_latch, m_scanline);
1366 }
1367
1368 m_toggle ^= 1;
1369 break;
1370
1371 case PPU_ADDRESS: /* 6 */
1372 if (m_toggle)
1373 {
1374 /* second write */
1375 m_refresh_latch &= (0xff00 & m_global_refresh_mask);
1376 m_refresh_latch |= data;
1377 m_refresh_data = m_refresh_latch;
1378
1379 m_videomem_addr = m_refresh_latch;
1380 //logerror("vram addr write 2: %02x, %04x (scanline: %d)\n", data, m_refresh_latch, m_scanline);
1381 }
1382 else
1383 {
1384 /* first write */
1385 m_refresh_latch &= 0x00ff;
1386 m_refresh_latch |= (data & (m_videoram_addr_mask >>8) ) << 8;
1387 //logerror("vram addr write 1: %02x, %04x (scanline: %d)\n", data, m_refresh_latch, m_scanline);
1388 }
1389
1390 m_toggle ^= 1;
1391 break;
1392
1393 case PPU_DATA: /* 7 */
1394 {
1395 int tempAddr = m_videomem_addr & m_videoram_addr_mask;
1396
1397 if (!m_latch.isnull())
1398 m_latch(tempAddr);
1399
1400 /* if there's a callback, call it now */
1401 if (!m_vidaccess_callback_proc.isnull())
1402 data = m_vidaccess_callback_proc(tempAddr, data);
1403
1404 /* see if it's on the chargen portion */
1405 if (tempAddr < 0x2000)
1406 {
1407 /* store the data */
1408 writebyte(tempAddr, data);
1409 }
1410 else // this codepath is identical?
1411 {
1412 writebyte(tempAddr, data);
1413 }
1414 /* increment the address */
1415 m_videomem_addr += m_add;
1416 }
1417 break;
1418
1419 default:
1420 /* ignore other registers writes */
1421 break;
1422 }
1423
1424 m_data_latch = data;
1425 }
1426
write(offs_t offset,uint8_t data)1427 void ppu2c04_clone_device::write(offs_t offset, uint8_t data)
1428 {
1429 switch (offset & 7)
1430 {
1431 case PPU_CONTROL0: /* 0 */
1432 data &= (0x01 | PPU_CONTROL0_INC | PPU_CONTROL0_NMI); /* other bits of $2000 are ignored by this clone */
1433 data |= PPU_CONTROL0_CHR_SELECT;
1434 break;
1435
1436 case PPU_CONTROL1: /* 1 */
1437 case PPU_SPRITE_ADDRESS: /* 3 */
1438 return; /* $2001 and $2003 do nothing on this clone */
1439
1440 case PPU_SPRITE_DATA: /* 4 */
1441 m_spritebuf[m_regs[PPU_SPRITE_ADDRESS]] = data;
1442 m_regs[PPU_SPRITE_ADDRESS] = (m_regs[PPU_SPRITE_ADDRESS] + 1) & 0xff;
1443 return;
1444
1445 case PPU_SCROLL: /* 5 */
1446 if (m_toggle)
1447 data = 0; /* no vertical scroll */
1448 break;
1449
1450 case PPU_ADDRESS: /* 6 */
1451 /* $2006 doesn't affect scroll latching */
1452 if (m_toggle)
1453 {
1454 /* second write */
1455 set_vram_dest((get_vram_dest() & 0xff00) | data);
1456 }
1457 else
1458 {
1459 /* first write */
1460 set_vram_dest((get_vram_dest() & 0x00ff) | (data << 8));
1461 }
1462
1463 m_toggle ^= 1;
1464 return;
1465 }
1466
1467 ppu2c0x_device::write(offset, data);
1468 }
1469
get_vram_dest()1470 uint16_t ppu2c0x_device::get_vram_dest()
1471 {
1472 return m_videomem_addr;
1473 }
1474
set_vram_dest(uint16_t dest)1475 void ppu2c0x_device::set_vram_dest(uint16_t dest)
1476 {
1477 m_videomem_addr = dest;
1478 }
1479
1480 /*************************************
1481 *
1482 * Sprite DMA
1483 *
1484 *************************************/
1485
spriteram_dma(address_space & space,const uint8_t page)1486 void ppu2c0x_device::spriteram_dma(address_space& space, const uint8_t page)
1487 {
1488 int i;
1489 int address = page << 8;
1490
1491 for (i = 0; i < SPRITERAM_SIZE; i++)
1492 {
1493 uint8_t spriteData = space.read_byte(address + i);
1494 space.write_byte(0x2004, spriteData);
1495 }
1496
1497 // should last 513 CPU cycles.
1498 space.device().execute().adjust_icount(-513);
1499 }
1500
1501 /*************************************
1502 *
1503 * PPU Rendering
1504 *
1505 *************************************/
1506
render(bitmap_rgb32 & bitmap,int flipx,int flipy,int sx,int sy,const rectangle & cliprect)1507 void ppu2c0x_device::render(bitmap_rgb32& bitmap, int flipx, int flipy, int sx, int sy, const rectangle& cliprect)
1508 {
1509 if (m_scanline_timer->remaining() != attotime::zero)
1510 {
1511 // Partial line update, need to render first (especially for light gun emulation).
1512 update_scanline();
1513 }
1514 copybitmap(bitmap, *m_bitmap, flipx, flipy, sx, sy, cliprect);
1515 }
1516
screen_update(screen_device & screen,bitmap_rgb32 & bitmap,const rectangle & cliprect)1517 uint32_t ppu2c0x_device::screen_update(screen_device& screen, bitmap_rgb32& bitmap, const rectangle& cliprect)
1518 {
1519 render(bitmap, 0, 0, 0, 0, cliprect);
1520 return 0;
1521 }
1522