private static void PPUClock() { board.OnPPUClock(); if ((VClock < 240) || (VClock == vbl_vclock_End)) { if (bkg_enabled || spr_enabled) { if (HClock < 256) { #region BKG FETCHES 0 - 255 // UNUSED AT 248-255 switch (HClock & 7) { case 0: { // Fetch address of nametable bkg_fetch_address = 0x2000 | (vram_address & 0x0FFF); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 1: bkg_fetch_nametable = board.ReadNMT(ref bkg_fetch_address); break; case 2: { // Fetch address for attr byte bkg_fetch_address = 0x23C0 | (vram_address & 0xC00) | (vram_address >> 4 & 0x38) | (vram_address >> 2 & 0x7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 3: bkg_fetch_attr = (byte)(board.ReadNMT(ref bkg_fetch_address) >> ((vram_address >> 4 & 0x04) | (vram_address & 0x02))); break; case 4: { // Fetch bit 0 address bkg_fetch_address = bkg_patternAddress | (bkg_fetch_nametable << 4) | 0 | (vram_address >> 12 & 7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 5: bkg_fetch_bit0 = board.ReadCHR(ref bkg_fetch_address, false); break; case 6: { // Fetch bit 1 address bkg_fetch_address = bkg_patternAddress | (bkg_fetch_nametable << 4) | 8 | (vram_address >> 12 & 7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 7: { bkg_fetch_bit1 = board.ReadCHR(ref bkg_fetch_address, false); if (HClock == 255) { // Increment Y if ((vram_address & 0x7000) != 0x7000) { vram_address += 0x1000; } else { vram_address ^= 0x7000; switch (vram_address & 0x3E0) { case 0x3A0: vram_address ^= 0xBA0; break; case 0x3E0: vram_address ^= 0x3E0; break; default: vram_address += 0x20; break; } } } else { // Increment X if ((vram_address & 0x001F) == 0x001F) { vram_address ^= 0x041F; } else { vram_address++; } } // Render BKG tile bkg_pos = (HClock + 9) % 336; for (bkg_render_i = 0; bkg_render_i < 8 && bkg_pos < 272; bkg_render_i++, bkg_pos++, bkg_fetch_bit0 <<= 1, bkg_fetch_bit1 <<= 1) { bkg_pixels[bkg_pos] = (bkg_fetch_attr << 2 & 12) | (bkg_fetch_bit0 >> 7 & 1) | (bkg_fetch_bit1 >> 6 & 2); } break; } } #endregion #region Render Pixel if (VClock < 240) { if (!bkg_enabled || (bkg_clipped && HClock < 8)) { bkgPixel = 0x3F00; } else { bkgPixel = 0x3F00 | bkg_pixels[HClock + vram_fine]; } if (!spr_enabled || (spr_clipped && HClock < 8)) { sprPixel = 0x3F10; } else { sprPixel = 0x3F10 | spr_pixels[HClock]; } current_pixel = 0; //Priority ******************************* if ((sprPixel & 0x8000) != 0) { current_pixel = sprPixel; } else { current_pixel = bkgPixel; } //**************************************** // Transparency ************************** if ((bkgPixel & 0x03) == 0) { current_pixel = sprPixel; goto render; } if ((sprPixel & 0x03) == 0) { current_pixel = bkgPixel; goto render; } //**************************************** //Sprite 0 Hit if ((sprPixel & 0x4000) != 0 & HClock < 255) { spr_0Hit = true; } render: if (!screenPointerMode) { screen[(VClock * 256) + HClock] = palette[paletteIndexes[palettes_bank[current_pixel & ((current_pixel & 0x03) == 0 ? 0x0C : 0x1F)] & (grayscale | emphasis)]]; } else { screenPointerPos = (VClock * 256) + HClock; if (screenPointerPos >= screenPointerStart && screenPointerPos < screenPointerSize) { screenPointer[screenPointerPos - screenPointerStart] = palette[paletteIndexes[palettes_bank[current_pixel & ((current_pixel & 0x03) == 0 ? 0x0C : 0x1F)] & (grayscale | emphasis)]]; } } } #endregion #region OAM EVALUATION switch (HClock & 1) { case 0: { if (!oam_fetch_mode) { oam_fetch_data = 0xFF; } else { oam_fetch_data = oam_ram[oam_address]; } break; } case 1: { switch (oam_phase_index) { case 0: { if (HClock <= 64) { switch (HClock >> 1 & 0x03) { case 0: oam_secondary[((HClock >> 3) * 4) + 0] = 0xFF; break; case 1: oam_secondary[((HClock >> 3) * 4) + 1] = 0xFF; break; case 2: oam_secondary[((HClock >> 3) * 4) + 2] = 0xFF; break; case 3: { oam_secondary[((HClock >> 3) * 4) + 3] = 0xFF; spr_zero_buffer[HClock >> 3 & 7] = false; break; } } } break; } case 1: { if (VClock == vbl_vclock_End) { break; } oam_evaluate_count++; temp_comparator = (VClock - oam_fetch_data) & int.MaxValue; if (temp_comparator >= spr_size16) { if (oam_evaluate_count != 64) { oam_address = (byte)(oam_evaluate_count != 2 ? oam_address + 4 : 8); } else { oam_address = 0; oam_phase_index = 9; } } else { oam_address++; oam_phase_index = 2; oam_secondary[oam_evaluate_slot * 4] = oam_fetch_data; spr_zero_buffer[oam_evaluate_slot] = (oam_evaluate_count == 1); } break; } case 2: { oam_address++; oam_phase_index = 3; oam_secondary[(oam_evaluate_slot * 4) + 1] = oam_fetch_data; break; } case 3: { oam_address++; oam_phase_index = 4; oam_secondary[(oam_evaluate_slot * 4) + 2] = oam_fetch_data; break; } case 4: { oam_secondary[(oam_evaluate_slot * 4) + 3] = oam_fetch_data; oam_evaluate_slot++; if (oam_evaluate_count != 64) { oam_phase_index = (byte)((oam_evaluate_slot != 8) ? 1 : 5); if (oam_evaluate_count != 2) { oam_address++; } else { oam_address = 8; } } else { oam_address = 0; oam_phase_index = 9; } break; } case 5: { if (VClock == vbl_vclock_End) { break; } temp_comparator = (VClock - oam_fetch_data) & int.MaxValue; if (temp_comparator >= spr_size16) { oam_address = (byte)(((oam_address + 4) & 0xFC) + ((oam_address + 1) & 0x03)); if (oam_address <= 5) { oam_phase_index = 9; oam_address &= 0xFC; } } else { oam_phase_index = 6; oam_address += (0x01) & 0xFF; spr_overflow = true; } break; } case 6: { oam_phase_index = 7; oam_address += (0x01); break; } case 7: { oam_phase_index = 8; oam_address += (0x01); break; } case 8: { oam_phase_index = 9; oam_address += (0x01); if ((oam_address & 0x03) == 0x03) { oam_address += (0x01); } oam_address &= 0xFC; break; } case 9: { oam_address += 0x4; break; } } break; } } if (HClock == 63) {//Evaluation Begin oam_fetch_mode = true; oam_phase_index = 1; oam_evaluate_slot = 0; oam_evaluate_count = 0; } if (HClock == 255) { // Evaluation Reset oam_fetch_mode = false; oam_phase_index = 0; oam_evaluate_slot = 0; oam_address = 0; oam_evaluate_count = 0; //spr_pixels = new int[256]; for (spr_evaluation_i = 0; spr_evaluation_i < 256; spr_evaluation_i++) { spr_pixels[spr_evaluation_i] = 0; } } #endregion } else if (HClock < 320) { #region SPRITE FETCHES + GARBAGE BKG FETCHES 256 - 319 switch (HClock & 7) { case 0: { // Fetch address of nametable bkg_fetch_address = 0x2000 | (vram_address & 0x0FFF); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 1: bkg_fetch_nametable = board.ReadNMT(ref bkg_fetch_address); break; case 2: { // Fetch address for attr byte bkg_fetch_address = 0x23C0 | (vram_address & 0xC00) | (vram_address >> 4 & 0x38) | (vram_address >> 2 & 0x7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 3: bkg_fetch_attr = (byte)(board.ReadNMT(ref bkg_fetch_address) >> ((vram_address >> 4 & 0x04) | (vram_address & 0x02))); break; case 4: { temp = HClock >> 3 & 7; temp_comparator = (VClock - oam_secondary[temp * 4]) ^ ((oam_secondary[(temp * 4) + 2] & 0x80) != 0 ? 0x0F : 0x00); if (spr_size16 == 0x10) { spr_fetch_address = (oam_secondary[(temp * 4) + 1] << 0x0C & 0x1000) | (oam_secondary[(temp * 4) + 1] << 0x04 & 0x0FE0) | (temp_comparator << 0x01 & 0x0010) | (temp_comparator & 0x0007); } else { spr_fetch_address = spr_patternAddress | (oam_secondary[(temp * 4) + 1] << 0x04) | (temp_comparator & 0x0007); } board.OnPPUAddressUpdate(ref spr_fetch_address); break; } case 5: { spr_fetch_bit0 = board.ReadCHR(ref spr_fetch_address, true); if ((oam_secondary[((HClock >> 3 & 7) * 4) + 2] & 0x40) != 0) { spr_fetch_bit0 = reverseLookup[spr_fetch_bit0]; } break; } case 6: { spr_fetch_address = spr_fetch_address | 0x08; board.OnPPUAddressUpdate(ref spr_fetch_address); break; } case 7: { spr_fetch_bit1 = board.ReadCHR(ref spr_fetch_address, true); if ((oam_secondary[((HClock >> 3 & 7) * 4) + 2] & 0x40) != 0) { spr_fetch_bit1 = reverseLookup[spr_fetch_bit1]; } spr_fetch_attr = oam_secondary[((HClock >> 3 & 7) * 4) + 2]; // Render SPR tile temp = HClock >> 3 & 7; if (oam_secondary[(temp * 4) + 3] == 255) { break; } spr_pos = oam_secondary[(temp * 4) + 3]; object0 = spr_zero_buffer[temp] ? 0x4000 : 0x0000; infront = ((oam_secondary[(temp * 4) + 2] & 0x20) == 0) ? 0x8000 : 0x0000; for (spr_render_i = 0; spr_render_i < 8 && spr_pos < 256; spr_render_i++, spr_pos++, spr_fetch_bit0 <<= 1, spr_fetch_bit1 <<= 1) { if (spr_pos > 255) { break; } spr_render_temp_pixel = (spr_fetch_attr << 2 & 12) | (spr_fetch_bit0 >> 7 & 1) | (spr_fetch_bit1 >> 6 & 2) | object0 | infront; if ((spr_pixels[spr_pos] & 0x03) == 0 && (spr_render_temp_pixel & 0x03) != 0) { spr_pixels[spr_pos] = spr_render_temp_pixel; } } break; } } #endregion if (HClock == 256)// 257 in the Ntsc_timing diagram { vram_address = (vram_address & 0x7BE0) | (vram_temp & 0x041F); } // 280-304 in the Ntsc_timing diagram if (VClock == vbl_vclock_End && HClock >= 279 && HClock <= 303) { vram_address = (vram_address & 0x041F) | (vram_temp & 0x7BE0); } } else if (HClock < 336) { #region FIRST 2 BKG TILES FOR 1ST SCANLINE 320 - 335 switch (HClock & 7) { case 0: { // Fetch address of nametable bkg_fetch_address = 0x2000 | (vram_address & 0x0FFF); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 1: bkg_fetch_nametable = board.ReadNMT(ref bkg_fetch_address); break; case 2: { // Fetch address for attr byte bkg_fetch_address = 0x23C0 | (vram_address & 0xC00) | (vram_address >> 4 & 0x38) | (vram_address >> 2 & 0x7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 3: bkg_fetch_attr = (byte)(board.ReadNMT(ref bkg_fetch_address) >> ((vram_address >> 4 & 0x04) | (vram_address & 0x02))); break; case 4: { // Fetch bit 0 address bkg_fetch_address = bkg_patternAddress | (bkg_fetch_nametable << 4) | 0 | (vram_address >> 12 & 7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 5: bkg_fetch_bit0 = board.ReadCHR(ref bkg_fetch_address, false); break; case 6: { // Fetch bit 1 address bkg_fetch_address = bkg_patternAddress | (bkg_fetch_nametable << 4) | 8 | (vram_address >> 12 & 7); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 7: { bkg_fetch_bit1 = board.ReadCHR(ref bkg_fetch_address, false); // Increment X if ((vram_address & 0x001F) == 0x001F) { vram_address ^= 0x041F; } else { vram_address++; } // Render BKG tile bkg_pos = (HClock + 9) % 336; for (bkg_render_i = 0; bkg_render_i < 8 && bkg_pos < 272; bkg_render_i++, bkg_pos++, bkg_fetch_bit0 <<= 1, bkg_fetch_bit1 <<= 1) { bkg_pixels[bkg_pos] = (bkg_fetch_attr << 2 & 12) | (bkg_fetch_bit0 >> 7 & 1) | (bkg_fetch_bit1 >> 6 & 2); } break; } } #endregion } else if (HClock < 340) { #region DUMMY FETCHES 336 - 339 switch (HClock & 7) { case 0: { // Fetch address of nametable bkg_fetch_address = 0x2000 | (vram_address & 0x0FFF); board.OnPPUAddressUpdate(ref bkg_fetch_address); break; } case 1: { bkg_fetch_nametable = board.ReadNMT(ref bkg_fetch_address); break; } } #endregion } else { // Idle cycle } } else { #region Rendering is off, draw color at vram address if it in range 0x3F00 - 0x3FFF if (HClock < 255 & VClock < 240) { if ((vram_address & 0x3F00) == 0x3F00) { if (!screenPointerMode) { screen[(VClock * 256) + HClock] = palette[paletteIndexes[palettes_bank[vram_address & ((vram_address & 0x03) == 0 ? 0x0C : 0x1F)] & (grayscale | emphasis)]]; } else { screenPointerPos = (VClock * 256) + HClock; if (screenPointerPos >= screenPointerStart && screenPointerPos < screenPointerSize) { screenPointer[screenPointerPos - screenPointerStart] = palette[paletteIndexes[palettes_bank[vram_address & ((vram_address & 0x03) == 0 ? 0x0C : 0x1F)] & (grayscale | emphasis)]]; } } } else { if (!screenPointerMode) { screen[(VClock * 256) + HClock] = palette[paletteIndexes[palettes_bank[0] & (grayscale | emphasis)]]; } else { screenPointerPos = (VClock * 256) + HClock; if (screenPointerPos >= screenPointerStart && screenPointerPos < screenPointerSize) { screenPointer[screenPointerPos - screenPointerStart] = palette[paletteIndexes[palettes_bank[0] & (grayscale | emphasis)]]; } } } } #endregion } } // Clock Horz HClock++; // Update vbl flag from latch vbl_flag = vbl_flag_temp; // Check for nmi if ((VClock == vbl_vclock_Start) && (HClock <= 3)) { NMI_Current = (vbl_flag_temp & nmi_enabled); } #region odd frame in the idle cycle if (UseOddFrame) { if (HClock == 338 && VClock == vbl_vclock_End) { oddSwap = !oddSwap; if (!oddSwap & bkg_enabled) { HClock++; } } } #endregion #region VBLANK, NMI and frame end if (HClock == 341) { board.OnPPUScanlineTick(); HClock = 0; VClock++; //set vbl if (VClock == vbl_vclock_Start) { vbl_flag_temp = true; } //clear vbl else if (VClock == vbl_vclock_End) { spr_0Hit = false; vbl_flag_temp = false; spr_overflow = false; } else if (VClock == frameEnd) { VClock = 0; #region Render if (FrameSkipEnabled) { if (FrameSkipTimer == 0) { if (!screenPointerMode) { videoOut.SubmitBuffer(ref screen); } else { videoOut.OnFrameFinished(); } } if (FrameSkipTimer > 0) { FrameSkipTimer--; } else { FrameSkipTimer = FrameSkipReload; } } else { if (!screenPointerMode) { videoOut.SubmitBuffer(ref screen); } else { videoOut.OnFrameFinished(); } } OnFinishFrame(); #endregion } } #endregion }