コード例 #1
0
        public unsafe void FrameAdvance()
        {
            BGDataRecord *bgdata = stackalloc BGDataRecord[34];             //one at the end is junk, it can never be rendered

            //262 scanlines
            if (ppudead != 0)
            {
                FrameAdvance_ppudead();
                return;
            }

            Reg2002_vblank_active_pending = true;
            ppuphase       = PPUPHASE.VBL;
            ppur.status.sl = 241;

            //Not sure if this is correct.  According to Matt Conte and my own tests, it is. Timing is probably off, though.
            //NOTE:  Not having this here breaks a Super Donkey Kong game.
            if (reg_2001.show_obj || reg_2001.show_bg)
            {
                reg_2003 = 0;
            }

            //this was repeatedly finetuned from the fceux days thrugh the old cpu core and into the new one to pass 05-nmi_timing.nes
            //note that there is still some leniency. for instance, 4,2 will pass in addition to 3,3
            const int delay = 6;

            runppu(3);
            bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active;

            runppu(3);
            if (nmi_destiny)
            {
                TriggerNMI();
            }
            nes.Board.AtVsyncNMI();
            runppu(postNMIlines * kLineTime - delay);

            //this seems to run just before the dummy scanline begins
            clear_2002();

            idleSynch ^= true;

            //render 241 scanlines (including 1 dummy at beginning)
            for (int sl = 0; sl < 241; sl++)
            {
                ppur.status.cycle = 0;

                ppur.status.sl = sl;

                soam_index        = 0;
                soam_m_index      = 0;
                soam_m_index_aux  = 0;
                oam_index_aux     = 0;
                oam_index         = 0;
                o_bug             = 0;
                is_even_cycle     = true;
                sprite_eval_write = true;
                sprite_zero_go    = false;
                if (sprite_zero_in_range)
                {
                    sprite_zero_go = true;
                }

                sprite_zero_in_range = false;

                yp       = sl - 1;
                ppuphase = PPUPHASE.BG;

                if (NTViewCallback != null && yp == NTViewCallback.Scanline)
                {
                    NTViewCallback.Callback();
                }
                if (PPUViewCallback != null && yp == PPUViewCallback.Scanline)
                {
                    PPUViewCallback.Callback();
                }

                //ok, we're also going to draw here.
                //unless we're on the first dummy scanline
                if (sl != 0)
                {
                    //the main scanline rendering loop:
                    //32 times, we will fetch a tile and then render 8 pixels.
                    //two of those tiles were read in the last scanline.
                    int yp_shift = yp << 8;
                    for (int xt = 0; xt < 32; xt++)
                    {
                        int xstart = xt << 3;
                        target = yp_shift + xstart;
                        int rasterpos = xstart;

                        spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                        //check all the conditions that can cause things to render in these 8px
                        bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost);
                        bool renderbgnow;

                        for (int xp = 0; xp < 8; xp++, rasterpos++)
                        {
                            //////////////////////////////////////////////////
                            //Sprite Evaluation Start
                            //////////////////////////////////////////////////
                            if (ppur.status.cycle <= 63 && !is_even_cycle)
                            {
                                // the first 64 cycles of each scanline are used to initialize sceondary OAM
                                // the actual effect setting a flag that always returns 0xFF from a OAM read
                                // this is a bit of a shortcut to save some instructions
                                // data is read from OAM as normal but never used
                                soam[soam_index] = 0xFF;
                                soam_index++;
                            }
                            if (ppur.status.cycle == 64)
                            {
                                soam_index = 0;
                            }

                            // otherwise, scan through OAM and test if sprites are in range
                            // if they are, they get copied to the secondary OAM
                            if (ppur.status.cycle >= 64)
                            {
                                if (oam_index == 64)
                                {
                                    oam_index_aux     = 0;
                                    oam_index         = 0;
                                    sprite_eval_write = false;
                                }

                                if (is_even_cycle)
                                {
                                    read_value = OAM[oam_index * 4 + soam_m_index];

                                    if (oam_index_aux > 63)
                                    {
                                        oam_index_aux = 63;
                                    }

                                    read_value_aux = OAM[oam_index_aux * 4 + soam_m_index_aux];
                                }
                                else if (sprite_eval_write)
                                {
                                    if (soam_index >= 8)
                                    {
                                        // this code mirrors sprite overflow bug behaviour
                                        // see http://wiki.nesdev.com/w/index.php/PPU_sprite_evaluation
                                        if (yp >= read_value && yp < read_value + spriteHeight && reg_2001.PPUON)
                                        {
                                            Reg2002_objoverflow = true;
                                        }
                                        else
                                        {
                                            soam_m_index++;
                                            oam_index++;
                                            if (soam_m_index == 4)
                                            {
                                                soam_m_index = 0;
                                            }
                                        }
                                    }

                                    //look for sprites
                                    soam[soam_index * 4] = OAM[oam_index_aux * 4];
                                    if (yp >= read_value_aux && yp < read_value_aux + spriteHeight && soam_m_index_aux == 0)
                                    {
                                        //a flag gets set if sprite zero is in range
                                        if (oam_index_aux == 0)
                                        {
                                            sprite_zero_in_range = true;
                                        }

                                        soam_m_index_aux++;
                                    }
                                    else if (soam_m_index_aux > 0 && soam_m_index_aux < 4)
                                    {
                                        soam[soam_index * 4 + soam_m_index_aux] = OAM[oam_index_aux * 4 + soam_m_index_aux];
                                        soam_m_index_aux++;
                                        if (soam_m_index_aux == 4)
                                        {
                                            oam_index_aux++;
                                            soam_index++;
                                            soam_m_index_aux = 0;
                                        }
                                    }
                                    else
                                    {
                                        oam_index_aux++;
                                    }

                                    if (soam_index < 8)
                                    {
                                        soam_m_index = soam_m_index_aux;
                                        oam_index    = oam_index_aux;
                                    }
                                }
                            }
                            //////////////////////////////////////////////////
                            //Sprite Evaluation End
                            //////////////////////////////////////////////////

                            //process the current clock's worth of bg data fetching
                            //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below
                            if (reg_2001.show_obj || reg_2001.show_bg)
                            {
                                Read_bgdata(xp, ref bgdata[xt + 2]);
                            }
                            else
                            {
                                runppu(1);
                            }

                            renderbgnow = reg_2001.show_bg && (xt > 0 || reg_2001.show_bg_leftmost);
                            //bg pos is different from raster pos due to its offsetability.
                            //so adjust for that here
                            int bgpos  = rasterpos + ppur.fh;
                            int bgpx   = bgpos & 7;
                            int bgtile = bgpos >> 3;

                            int pixel = 0, pixelcolor = PALRAM[pixel];

                            //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx
                            //at one point I commented this out to fix bottom-left garbage in DW4. but it's needed for full_nes_palette.
                            //solution is to only run when PPU is actually OFF (left-suppression doesnt count)
                            if (!reg_2001.show_bg && !reg_2001.show_obj)
                            {
                                // if there's anything wrong with how we're doing this, someone please chime in
                                int addr = ppur.get_2007access();
                                if ((addr & 0x3F00) == 0x3F00)
                                {
                                    pixel = addr & 0x1F;
                                }
                                pixelcolor  = PALRAM[pixel];
                                pixelcolor |= 0x8000;                                 //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                            }

                            //generate the BG data
                            if (renderbgnow)
                            {
                                byte pt_0 = bgdata[bgtile].pt_0;
                                byte pt_1 = bgdata[bgtile].pt_1;
                                int  sel  = 7 - bgpx;
                                pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1);
                                if (pixel != 0)
                                {
                                    pixel |= bgdata[bgtile].at;
                                }
                                pixelcolor = PALRAM[pixel];
                            }

                            if (!nes.Settings.DispBackground)
                            {
                                pixelcolor = 0x8000;                                 //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                            }
                            //look for a sprite to be drawn
                            bool havepixel = false;
                            for (int s = 0; s < soam_index_prev; s++)
                            {
                                int x = t_oam[s].oam_x;
                                if (rasterpos >= x && rasterpos < x + 8)
                                {
                                    //build the pixel.
                                    //fetch the LSB of the patterns
                                    int spixel = t_oam[s].patterns_0 & 1;
                                    spixel |= (t_oam[s].patterns_1 & 1) << 1;

                                    //shift down the patterns so the next pixel is in the LSB
                                    t_oam[s].patterns_0 >>= 1;
                                    t_oam[s].patterns_1 >>= 1;

                                    //bail out if we already have a pixel from a higher priority sprite.
                                    //notice that we continue looping anyway, so that we can shift down the patterns
                                    //transparent pixel bailout
                                    if (!renderspritenow || havepixel || spixel == 0)
                                    {
                                        continue;
                                    }

                                    havepixel = true;

                                    //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ
                                    //spritehit:
                                    //1. is it sprite#0?
                                    //2. is the bg pixel nonzero?
                                    //then, it is spritehit.
                                    Reg2002_objhit |= (sprite_zero_go && s == 0 && pixel != 0 && rasterpos < 255 && reg_2001.show_bg && reg_2001.show_obj);

                                    //priority handling, if in front of BG:
                                    bool drawsprite = !(((t_oam[s].oam_attr & 0x20) != 0) && ((pixel & 3) != 0));
                                    if (drawsprite && nes.Settings.DispSprites)
                                    {
                                        //bring in the palette bits and palettize
                                        spixel |= (t_oam[s].oam_attr & 3) << 2;
                                        //save it for use in the framebuffer
                                        pixelcolor = PALRAM[0x10 + spixel];
                                    }
                                }                         //rasterpos in sprite range
                            }                             //oamcount loop

                            /*
                             * if (reg_2001.color_disable)
                             *      pixelcolor &= 0x30;
                             * xbuf[target] = PaletteAdjustPixel(pixelcolor);
                             */
                            pipeline(pixelcolor, target, xt * 32 + xp);
                            target++;
                        }                 //loop across 8 pixels
                    }                     //loop across 32 tiles
                }
                else
                {
                    for (int xt = 0; xt < 32; xt++)
                    {
                        Read_bgdata(ref bgdata[xt + 2]);
                    }
                }

                // normally only 8 sprites are allowed, but with a particular setting we can have more then that
                soam_index_prev = soam_index;
                if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites)
                {
                    soam_index_prev = 8;
                }

                ppuphase = PPUPHASE.OBJ;

                spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                // if there are less then 8 evaluated sprites, we still process 8 sprites
                int bound;

                if (soam_index_prev > 8)
                {
                    bound = soam_index_prev;
                }
                else
                {
                    bound = 8;
                }

                for (int s = 0; s < bound; s++)
                {
                    //if this is a real sprite sprite, then it is not above the 8 sprite limit.
                    //this is how we support the no 8 sprite limit feature.
                    //not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters
                    //this could be handy for the debugging tools also
                    bool realSprite   = (s < 8);
                    bool junksprite   = (!reg_2001.PPUON);
                    bool extra_sprite = (s >= 8);

                    t_oam[s].oam_y    = soam[s * 4];
                    t_oam[s].oam_ind  = soam[s * 4 + 1];
                    t_oam[s].oam_attr = soam[s * 4 + 2];
                    t_oam[s].oam_x    = soam[s * 4 + 3];

                    int line = yp - t_oam[s].oam_y;
                    if ((t_oam[s].oam_attr & 0x80) != 0)                     //vflip
                    {
                        line = spriteHeight - line - 1;
                    }

                    int patternNumber = t_oam[s].oam_ind;
                    int patternAddress;

                    //8x16 sprite handling:
                    if (reg_2000.obj_size_16)
                    {
                        int bank = (patternNumber & 1) << 12;
                        patternNumber  = patternNumber & ~1;
                        patternNumber |= (line >> 3) & 1;
                        patternAddress = (patternNumber << 4) | bank;
                    }
                    else
                    {
                        patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12);
                    }

                    //offset into the pattern for the current line.
                    //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
                    //so we just need the line offset for the second pattern
                    patternAddress += line & 7;

                    //garbage nametable fetches + scroll resets
                    int garbage_todo = 2;
                    ppubus_read(ppur.get_ntread(), true, true);
                    if (reg_2001.PPUON)
                    {
                        if (sl == 0 && ppur.status.cycle == 304)
                        {
                            runppu(1);
                            read_value = t_oam[s].oam_y;
                            if (reg_2001.PPUON)
                            {
                                ppur.install_latches();
                            }
                            runppu(1);
                            read_value   = t_oam[s].oam_ind;
                            garbage_todo = 0;
                        }
                        if ((sl != 0) && ppur.status.cycle == 256)
                        {
                            runppu(1);
                            if (target <= 61441 && target > 0 && s == 0)
                            {
                                pipeline(0, target, 256);
                                target++;
                            }
                            read_value = t_oam[s].oam_y;
                            //at 257: 3d world runner is ugly if we do this at 256
                            if (reg_2001.PPUON)
                            {
                                ppur.install_h_latches();
                            }
                            runppu(1);
                            if (target <= 61441 && target > 0 && s == 0)
                            {
                                pipeline(0, target, 257);                                  //  last pipeline call option 1 of 2
                            }

                            read_value   = t_oam[s].oam_ind;
                            garbage_todo = 0;
                        }
                    }
                    if (realSprite)
                    {
                        for (int i = 0; i < garbage_todo; i++)
                        {
                            runppu(1);

                            if (i == 0)
                            {
                                if (target <= 61441 && target > 0 && s == 0)
                                {
                                    pipeline(0, target, 256);
                                    target++;
                                }
                                read_value = t_oam[s].oam_y;
                            }
                            else
                            {
                                if (target <= 61441 && target > 0 && s == 0)
                                {
                                    pipeline(0, target, 257);                                      //  last pipeline call option 2 of 2
                                }
                                read_value = t_oam[s].oam_ind;
                            }
                        }
                    }


                    ppubus_read(ppur.get_atread(), true, true);                     //at or nt?
                    if (realSprite)
                    {
                        runppu(1);
                        read_value = t_oam[s].oam_attr;
                        runppu(1);
                        read_value = t_oam[s].oam_x;
                    }

                    // TODO - fake sprites should not come through ppubus_read but rather peek it
                    // (at least, they should not probe it with AddressPPU. maybe the difference between peek and read is not necessary)
                    if (junksprite)
                    {
                        if (realSprite)
                        {
                            ppubus_read(patternAddress, true, false);
                            ppubus_read(patternAddress, true, false);
                            runppu(kFetchTime * 2);
                        }
                    }
                    else
                    {
                        int addr = patternAddress;
                        t_oam[s].patterns_0 = ppubus_read(addr, true, true);
                        if (realSprite)
                        {
                            runppu(kFetchTime);
                            read_value = t_oam[s].oam_x;
                        }
                        addr += 8;
                        t_oam[s].patterns_1 = ppubus_read(addr, true, true);
                        if (realSprite)
                        {
                            runppu(kFetchTime);
                            read_value = t_oam[s].oam_x;
                        }


                        // hflip
                        if ((t_oam[s].oam_attr & 0x40) == 0)
                        {
                            t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0];
                            t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1];
                        }
                    }
                }                 // sprite pattern fetch loop

                ppuphase = PPUPHASE.BG;

                // fetch BG: two tiles for next line
                for (int xt = 0; xt < 2; xt++)
                {
                    Read_bgdata(ref bgdata[xt]);
                }


                // this sequence is tuned to pass 10-even_odd_timing.nes
                runppu(kFetchTime);
                bool evenOddDestiny = (reg_2001.show_bg || reg_2001.show_obj);
                runppu(kFetchTime);

                // After memory access 170, the PPU simply rests for 4 cycles (or the
                // equivelant of half a memory access cycle) before repeating the whole
                // pixel/scanline rendering process. If the scanline being rendered is the very
                // first one on every second frame, then this delay simply doesn't exist.
                if (sl == 0 && idleSynch && evenOddDestiny && chopdot)
                {
                }
                else
                {
                    runppu(1);
                }
            }             // scanline loop

            ppur.status.sl = 241;

            //idle for pre NMI lines
            runppu(preNMIlines * kLineTime);
        }         //FrameAdvance
コード例 #2
0
ファイル: PPU.run.cs プロジェクト: tutty427/BizHawk
        public void TickPPU_active()
        {
            if (ppur.status.cycle == 0)
            {
                ppur.status.cycle = 0;

                spr_true_count    = 0;
                soam_index        = 0;
                soam_m_index      = 0;
                soam_m_index_aux  = 0;
                oam_index_aux     = 0;
                oam_index         = 0;
                is_even_cycle     = true;
                sprite_eval_write = true;
                sprite_zero_go    = sprite_zero_in_range;

                sprite_zero_in_range = false;

                yp       = ppur.status.sl - 1;
                ppuphase = PPUPHASE.BG;

                // "If PPUADDR is not less then 8 when rendering starts, the first 8 bytes in OAM are written to from
                // the current location of PPUADDR"
                if (ppur.status.sl == 0 && PPUON && reg_2003 >= 8 && region == Region.NTSC)
                {
                    for (int i = 0; i < 8; i++)
                    {
                        OAM[i] = OAM[(reg_2003 & 0xF8) + i];
                    }
                }

                if (NTViewCallback != null && yp == NTViewCallback.Scanline)
                {
                    NTViewCallback.Callback();
                }
                if (PPUViewCallback != null && yp == PPUViewCallback.Scanline)
                {
                    PPUViewCallback.Callback();
                }

                // set up intial values to use later
                yp_shift = yp << 8;
                xt       = 0;
                xp       = 0;

                sprite_eval_cycle = 0;

                xstart    = xt << 3;
                target    = yp_shift + xstart;
                rasterpos = xstart;

                spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                //check all the conditions that can cause things to render in these 8px
                renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost);
                hit_pending     = false;
            }

            if (ppur.status.cycle < 256)
            {
                if (ppur.status.sl != 0)
                {
                    /////////////////////////////////////////////
                    // Sprite Evaluation End
                    /////////////////////////////////////////////

                    if (sprite_eval_cycle <= 63 && !is_even_cycle)
                    {
                        // the first 64 cycles of each scanline are used to initialize sceondary OAM
                        // the actual effect setting a flag that always returns 0xFF from a OAM read
                        // this is a bit of a shortcut to save some instructions
                        // data is read from OAM as normal but never used
                        soam[soam_index] = 0xFF;
                        soam_index++;
                    }
                    if (sprite_eval_cycle == 64)
                    {
                        soam_index = 0;
                        oam_index  = reg_2003;
                    }

                    // otherwise, scan through OAM and test if sprites are in range
                    // if they are, they get copied to the secondary OAM
                    if (sprite_eval_cycle >= 64)
                    {
                        if (oam_index >= 256)
                        {
                            oam_index         = 0;
                            sprite_eval_write = false;
                        }

                        if (is_even_cycle && oam_index < 256)
                        {
                            if ((oam_index + soam_m_index) < 256)
                            {
                                read_value = OAM[oam_index + soam_m_index];
                            }
                            else
                            {
                                read_value = OAM[oam_index + soam_m_index - 256];
                            }
                        }
                        else if (!sprite_eval_write)
                        {
                            // if we don't write sprites anymore, just scan through the oam
                            read_value = soam[0];
                            oam_index += 4;
                        }
                        else if (sprite_eval_write)
                        {
                            //look for sprites
                            if (spr_true_count == 0 && soam_index < 8)
                            {
                                soam[soam_index * 4] = read_value;
                            }

                            if (soam_index < 8)
                            {
                                if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0)
                                {
                                    //a flag gets set if sprite zero is in range
                                    if (oam_index == reg_2003)
                                    {
                                        sprite_zero_in_range = true;
                                    }

                                    spr_true_count++;
                                    soam_m_index++;
                                }
                                else if (spr_true_count > 0 && spr_true_count < 4)
                                {
                                    soam[soam_index * 4 + soam_m_index] = read_value;

                                    soam_m_index++;

                                    spr_true_count++;
                                    if (spr_true_count == 4)
                                    {
                                        oam_index += 4;
                                        soam_index++;
                                        if (soam_index == 8)
                                        {
                                            // oam_index could be pathologically misaligned at this point, so we have to find the next
                                            // nearest actual sprite to work on >8 sprites per scanline option
                                            oam_index_aux = (oam_index % 4) * 4;
                                        }

                                        soam_m_index   = 0;
                                        spr_true_count = 0;
                                    }
                                }
                                else
                                {
                                    oam_index += 4;
                                }
                            }
                            else if (soam_index >= 8)
                            {
                                if (yp >= read_value && yp < read_value + spriteHeight && PPUON)
                                {
                                    hit_pending = true;
                                    //Reg2002_objoverflow = true;
                                }

                                if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0)
                                {
                                    spr_true_count++;
                                    soam_m_index++;
                                }
                                else if (spr_true_count > 0 && spr_true_count < 4)
                                {
                                    soam_m_index++;

                                    spr_true_count++;
                                    if (spr_true_count == 4)
                                    {
                                        oam_index += 4;
                                        soam_index++;
                                        soam_m_index   = 0;
                                        spr_true_count = 0;
                                    }
                                }
                                else
                                {
                                    oam_index += 4;
                                    if (soam_index == 8)
                                    {
                                        soam_m_index++;                                         // glitchy increment
                                        soam_m_index &= 3;
                                    }
                                }

                                read_value = soam[0];                                 //writes change to reads
                            }
                        }
                    }

                    /////////////////////////////////////////////
                    // Sprite Evaluation End
                    /////////////////////////////////////////////

                    //process the current clock's worth of bg data fetching
                    //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work
                    // due to the cpu not running while the sprite renders below
                    if (PPUON)
                    {
                        Read_bgdata(xp, ref bgdata[xt + 2]);
                    }

                    runppu();

                    if (PPUON && xp == 6)
                    {
                        ppu_was_on = true;
                    }

                    if (PPUON && xp == 7)
                    {
                        if (!race_2006)
                        {
                            ppur.increment_hsc();
                        }

                        if (ppur.status.cycle == 256 && !race_2006)
                        {
                            ppur.increment_vs();
                        }

                        ppu_was_on = false;
                    }

                    if (hit_pending)
                    {
                        hit_pending         = false;
                        Reg2002_objoverflow = true;
                    }

                    renderbgnow = show_bg_new && (xt > 0 || reg_2001.show_bg_leftmost);
                    //bg pos is different from raster pos due to its offsetability.
                    //so adjust for that here
                    int bgpos  = rasterpos + ppur.fh;
                    int bgpx   = bgpos & 7;
                    int bgtile = bgpos >> 3;

                    int pixel = 0, pixelcolor = PALRAM[pixel];

                    //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx
                    //at one point I commented this out to fix bottom-left garbage in DW4. but it's needed for full_nes_palette.
                    //solution is to only run when PPU is actually OFF (left-suppression doesnt count)
                    if (!PPUON)
                    {
                        // if there's anything wrong with how we're doing this, someone please chime in
                        int addr = ppur.get_2007access();
                        if ((addr & 0x3F00) == 0x3F00)
                        {
                            pixel = addr & 0x1F;
                        }
                        pixelcolor  = PALRAM[pixel];
                        pixelcolor |= 0x8000;                         //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                    }

                    //generate the BG data
                    if (renderbgnow)
                    {
                        byte pt_0 = bgdata[bgtile].pt_0;
                        byte pt_1 = bgdata[bgtile].pt_1;
                        int  sel  = 7 - bgpx;
                        pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1);
                        if (pixel != 0)
                        {
                            pixel |= bgdata[bgtile].at;
                        }
                        pixelcolor = PALRAM[pixel];
                    }

                    if (!nes.Settings.DispBackground)
                    {
                        pixelcolor = 0x8000;                         //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                    }
                    //check if the pixel has a sprite in it
                    if (sl_sprites[256 + xt * 8 + xp] != 0 && renderspritenow)
                    {
                        int s         = sl_sprites[xt * 8 + xp];
                        int spixel    = sl_sprites[256 + xt * 8 + xp];
                        int temp_attr = sl_sprites[512 + xt * 8 + xp];

                        //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ
                        //spritehit:
                        //1. is it sprite#0?
                        //2. is the bg pixel nonzero?
                        //then, it is spritehit.
                        Reg2002_objhit |= (sprite_zero_go && s == 0 && pixel != 0 && rasterpos < 255 && show_bg_new && show_obj_new);

                        //priority handling, if in front of BG:
                        bool drawsprite = !(((temp_attr & 0x20) != 0) && ((pixel & 3) != 0));
                        if (drawsprite && nes.Settings.DispSprites)
                        {
                            //bring in the palette bits and palettize
                            spixel |= (temp_attr & 3) << 2;
                            //save it for use in the framebuffer
                            pixelcolor = PALRAM[0x10 + spixel];
                        }
                    }                     //oamcount loop


                    pipeline(pixelcolor, target, xt * 8 + xp);
                    target++;

                    // clear out previous sprites from scanline buffer
                    //sl_sprites[xt * 8 + xp] = 0;
                    sl_sprites[256 + xt * 8 + xp] = 0;
                    //sl_sprites[512 + xt * 8 + xp] = 0;

                    // end of visible part of the scanline
                    sprite_eval_cycle++;
                    xp++;
                    rasterpos++;

                    if (xp == 8)
                    {
                        xp = 0;
                        xt++;

                        xstart    = xt << 3;
                        target    = yp_shift + xstart;
                        rasterpos = xstart;

                        spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                        //check all the conditions that can cause things to render in these 8px
                        renderspritenow = show_obj_new && (xt > 0 || reg_2001.show_obj_leftmost);
                        hit_pending     = false;
                    }
                }
                else
                {
                    // if scanline is the pre-render line, we just read BG data
                    Read_bgdata(xp, ref bgdata[xt + 2]);

                    runppu();

                    if (PPUON && xp == 6)
                    {
                        ppu_was_on = true;
                    }

                    if (PPUON && xp == 7)
                    {
                        if (!race_2006)
                        {
                            ppur.increment_hsc();
                        }

                        if (ppur.status.cycle == 256 && !race_2006)
                        {
                            ppur.increment_vs();
                        }

                        ppu_was_on = false;
                    }

                    xp++;

                    if (xp == 8)
                    {
                        xp = 0;
                        xt++;
                    }
                }
            }
            else if (ppur.status.cycle < 320)
            {
                // after we are done with the visible part of the frame, we reach sprite transfer to temp OAM tables and such
                if (ppur.status.cycle == 256)
                {
                    // do the more then 8 sprites stuff here where it is convenient
                    // normally only 8 sprites are allowed, but with a particular setting we can have more then that
                    // this extra bit takes care of it quickly
                    soam_index_aux = 8;

                    if (nes.Settings.AllowMoreThanEightSprites)
                    {
                        while (oam_index_aux < 64 && soam_index_aux < 64)
                        {
                            //look for sprites
                            soam[soam_index_aux * 4] = OAM[oam_index_aux * 4];
                            read_value_aux           = OAM[oam_index_aux * 4];
                            if (yp >= read_value_aux && yp < read_value_aux + spriteHeight)
                            {
                                soam[soam_index_aux * 4 + 1] = OAM[oam_index_aux * 4 + 1];
                                soam[soam_index_aux * 4 + 2] = OAM[oam_index_aux * 4 + 2];
                                soam[soam_index_aux * 4 + 3] = OAM[oam_index_aux * 4 + 3];
                                soam_index_aux++;
                                oam_index_aux++;
                            }
                            else
                            {
                                oam_index_aux++;
                            }
                        }
                    }

                    soam_index_prev = soam_index_aux;

                    if (soam_index_prev > 8 && !nes.Settings.AllowMoreThanEightSprites)
                    {
                        soam_index_prev = 8;
                    }

                    ppuphase = PPUPHASE.OBJ;

                    spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                    s             = 0;
                    ppu_aux_index = 0;

                    junksprite = (!PPUON);

                    t_oam[s].oam_y    = soam[s * 4];
                    t_oam[s].oam_ind  = soam[s * 4 + 1];
                    t_oam[s].oam_attr = soam[s * 4 + 2];
                    t_oam[s].oam_x    = soam[s * 4 + 3];

                    line = yp - t_oam[s].oam_y;
                    if ((t_oam[s].oam_attr & 0x80) != 0)                     //vflip
                    {
                        line = spriteHeight - line - 1;
                    }

                    patternNumber = t_oam[s].oam_ind;
                }

                switch (ppu_aux_index)
                {
                case 0:
                    //8x16 sprite handling:
                    if (reg_2000.obj_size_16)
                    {
                        int bank = (patternNumber & 1) << 12;
                        patternNumber  = patternNumber & ~1;
                        patternNumber |= (line >> 3) & 1;
                        patternAddress = (patternNumber << 4) | bank;
                    }
                    else
                    {
                        patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12);
                    }

                    //offset into the pattern for the current line.
                    //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
                    //so we just need the line offset for the second pattern
                    patternAddress += line & 7;

                    ppubus_read(ppur.get_ntread(), true, true);

                    read_value = t_oam[s].oam_y;
                    runppu();
                    break;

                case 1:
                    if (PPUON && ppur.status.sl == 0 && ppur.status.cycle == 305)
                    {
                        ppur.install_latches();

                        read_value = t_oam[s].oam_ind;
                        runppu();
                    }
                    else if (PPUON && (ppur.status.sl != 0) && ppur.status.cycle == 257)
                    {
                        if (target <= 61441 && target > 0 && s == 0)
                        {
                            pipeline(0, target, 256);
                            target++;
                        }

                        //at 257: 3d world runner is ugly if we do this at 256
                        if (PPUON)
                        {
                            ppur.install_h_latches();
                        }
                        read_value = t_oam[s].oam_ind;
                        runppu();

                        if (target <= 61441 && target > 0 && s == 0)
                        {
                            pipeline(0, target, 257);                                      //  last pipeline call option 1 of 2
                        }
                    }
                    else
                    {
                        if (target <= 61441 && target > 0 && s == 0)
                        {
                            pipeline(0, target, 256);
                            target++;
                        }

                        read_value = t_oam[s].oam_ind;
                        runppu();

                        if (target <= 61441 && target > 0 && s == 0)
                        {
                            pipeline(0, target, 257);                                      //  last pipeline call option 2 of 2
                        }
                    }
                    break;

                case 2:
                    ppubus_read(ppur.get_atread(), true, true);                             //at or nt?
                    read_value = t_oam[s].oam_attr;
                    runppu();
                    break;

                case 3:
                    read_value = t_oam[s].oam_x;
                    runppu();
                    break;

                case 4:
                    // if the PPU is off, we don't put anything on the bus
                    if (junksprite)
                    {
                        ppubus_read(patternAddress, true, false);
                        runppu();
                    }
                    else
                    {
                        temp_addr           = patternAddress;
                        t_oam[s].patterns_0 = ppubus_read(temp_addr, true, true);
                        read_value          = t_oam[s].oam_x;
                        runppu();
                    }
                    break;

                case 5:
                    // if the PPU is off, we don't put anything on the bus
                    if (junksprite)
                    {
                        runppu();
                    }
                    else
                    {
                        runppu();
                    }
                    break;

                case 6:
                    // if the PPU is off, we don't put anything on the bus
                    if (junksprite)
                    {
                        ppubus_read(patternAddress, true, false);
                        runppu();
                    }
                    else
                    {
                        temp_addr          += 8;
                        t_oam[s].patterns_1 = ppubus_read(temp_addr, true, true);
                        read_value          = t_oam[s].oam_x;
                        runppu();
                    }
                    break;

                case 7:
                    // if the PPU is off, we don't put anything on the bus
                    if (junksprite)
                    {
                        runppu();
                    }
                    else
                    {
                        runppu();

                        // hflip
                        if ((t_oam[s].oam_attr & 0x40) == 0)
                        {
                            t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0];
                            t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1];
                        }

                        // if the sprites attribute is 0xFF, then this indicates a non-existent sprite
                        // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF
                        // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline
                        if (t_oam[s].oam_attr == 0xFF)
                        {
                            t_oam[s].patterns_0 = 0;
                            t_oam[s].patterns_1 = 0;
                        }
                    }
                    break;
                }

                ppu_aux_index++;
                if (ppu_aux_index == 8)
                {
                    // now that we have a sprite, we can fill in the next scnaline's sprite pixels with it
                    // this saves quite a bit of processing compared to checking each pixel

                    if (s < soam_index_prev)
                    {
                        int temp_x = t_oam[s].oam_x;
                        for (int i = 0; (temp_x + i) < 256 && i < 8; i++)
                        {
                            if (sl_sprites[256 + temp_x + i] == 0)
                            {
                                if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i))
                                {
                                    int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0;
                                    spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);

                                    sl_sprites[temp_x + i]       = (byte)s;
                                    sl_sprites[256 + temp_x + i] = (byte)spixel;
                                    sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr;
                                }
                            }
                        }
                    }

                    ppu_aux_index = 0;
                    s++;

                    if (s < 8)
                    {
                        junksprite = (!PPUON);

                        t_oam[s].oam_y    = soam[s * 4];
                        t_oam[s].oam_ind  = soam[s * 4 + 1];
                        t_oam[s].oam_attr = soam[s * 4 + 2];
                        t_oam[s].oam_x    = soam[s * 4 + 3];

                        line = yp - t_oam[s].oam_y;
                        if ((t_oam[s].oam_attr & 0x80) != 0)                         //vflip
                        {
                            line = spriteHeight - line - 1;
                        }

                        patternNumber = t_oam[s].oam_ind;
                    }
                    else
                    {
                        // repeat all the above steps for more then 8 sprites but don't run any cycles
                        if (soam_index_aux > 8)
                        {
                            for (int s = 8; s < soam_index_aux; s++)
                            {
                                t_oam[s].oam_y    = soam[s * 4];
                                t_oam[s].oam_ind  = soam[s * 4 + 1];
                                t_oam[s].oam_attr = soam[s * 4 + 2];
                                t_oam[s].oam_x    = soam[s * 4 + 3];

                                int line = yp - t_oam[s].oam_y;
                                if ((t_oam[s].oam_attr & 0x80) != 0)                                 //vflip
                                {
                                    line = spriteHeight - line - 1;
                                }

                                int patternNumber = t_oam[s].oam_ind;
                                int patternAddress;

                                //8x16 sprite handling:
                                if (reg_2000.obj_size_16)
                                {
                                    int bank = (patternNumber & 1) << 12;
                                    patternNumber  = patternNumber & ~1;
                                    patternNumber |= (line >> 3) & 1;
                                    patternAddress = (patternNumber << 4) | bank;
                                }
                                else
                                {
                                    patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12);
                                }

                                //offset into the pattern for the current line.
                                //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
                                //so we just need the line offset for the second pattern
                                patternAddress += line & 7;

                                ppubus_read(ppur.get_ntread(), true, false);

                                ppubus_read(ppur.get_atread(), true, false);                                 //at or nt?

                                int addr = patternAddress;
                                t_oam[s].patterns_0 = ppubus_read(addr, true, false);

                                addr += 8;
                                t_oam[s].patterns_1 = ppubus_read(addr, true, false);

                                // hflip
                                if ((t_oam[s].oam_attr & 0x40) == 0)
                                {
                                    t_oam[s].patterns_0 = BitReverse.Byte8[t_oam[s].patterns_0];
                                    t_oam[s].patterns_1 = BitReverse.Byte8[t_oam[s].patterns_1];
                                }

                                // if the sprites attribute is 0xFF, then this indicates a non-existent sprite
                                // I think the logic here is that bits 2-4 in OAM are disabled, but soam is initialized with 0xFF
                                // so the only way a sprite could have an 0xFF attribute is if it is not in the scope of the scanline
                                if (t_oam[s].oam_attr == 0xFF)
                                {
                                    t_oam[s].patterns_0 = 0;
                                    t_oam[s].patterns_1 = 0;
                                }

                                int temp_x = t_oam[s].oam_x;
                                for (int i = 0; (temp_x + i) < 256 && i < 8; i++)
                                {
                                    if (sl_sprites[256 + temp_x + i] == 0)
                                    {
                                        if (t_oam[s].patterns_0.Bit(i) || t_oam[s].patterns_1.Bit(i))
                                        {
                                            int spixel = t_oam[s].patterns_0.Bit(i) ? 1 : 0;
                                            spixel |= (t_oam[s].patterns_1.Bit(i) ? 2 : 0);

                                            sl_sprites[temp_x + i]       = (byte)s;
                                            sl_sprites[256 + temp_x + i] = (byte)spixel;
                                            sl_sprites[512 + temp_x + i] = t_oam[s].oam_attr;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                if (ppur.status.cycle == 320)
                {
                    ppuphase = PPUPHASE.BG;
                    xt       = 0;
                    xp       = 0;
                }

                if (ppur.status.cycle < 336)
                {
                    // if scanline is the pre-render line, we just read BG data
                    Read_bgdata(xp, ref bgdata[xt]);

                    runppu();

                    if (PPUON && xp == 6)
                    {
                        ppu_was_on = true;
                    }

                    if (PPUON && xp == 7)
                    {
                        if (!race_2006)
                        {
                            ppur.increment_hsc();
                        }

                        if (ppur.status.cycle == 256 && !race_2006)
                        {
                            ppur.increment_vs();
                        }

                        ppu_was_on = false;
                    }

                    xp++;

                    if (xp == 8)
                    {
                        xp = 0;
                        xt++;
                    }
                }
                else if (ppur.status.cycle < 340)
                {
                    runppu();
                }
                else
                {
                    bool evenOddDestiny = PPUON;

                    // After memory access 170, the PPU simply rests for 4 cycles (or the
                    // equivelant of half a memory access cycle) before repeating the whole
                    // pixel/scanline rendering process. If the scanline being rendered is the very
                    // first one on every second frame, then this delay simply doesn't exist.
                    if (ppur.status.sl == 0 && idleSynch && evenOddDestiny && chopdot)
                    {
                        ppur.status.cycle++;
                    }                                            // increment cycle without running ppu
                    else
                    {
                        runppu();
                    }
                }
            }

            if (ppur.status.cycle == 341)
            {
                ppur.status.cycle = 0;
                ppur.status.sl++;

                if (ppur.status.sl == 241)
                {
                    do_active_sl = false;
                }
            }
        }
コード例 #3
0
ファイル: PPU.run.cs プロジェクト: stuff2600/RAEmus
            public unsafe void FrameAdvance()
            {
                BGDataRecord *bgdata = stackalloc BGDataRecord[34];                 //one at the end is junk, it can never be rendered

                //262 scanlines
                if (ppudead != 0)
                {
                    FrameAdvance_ppudead();
                    return;
                }

                Reg2002_vblank_active_pending = true;
                ppuphase       = PPUPHASE.VBL;
                ppur.status.sl = 241;

                //Not sure if this is correct.  According to Matt Conte and my own tests, it is. Timing is probably off, though.
                //NOTE:  Not having this here breaks a Super Donkey Kong game.
                reg_2003 = 0;

                //this was repeatedly finetuned from the fceux days thrugh the old cpu core and into the new one to pass 05-nmi_timing.nes
                //note that there is still some leniency. for instance, 4,2 will pass in addition to 3,3
                const int delay = 6;

                runppu(3);
                bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active;

                runppu(3);
                if (nmi_destiny)
                {
                    TriggerNMI();
                }
                runppu(postNMIlines * kLineTime - delay);

                //this seems to run just before the dummy scanline begins
                clear_2002();

                TempOAM *oams      = stackalloc TempOAM[128];
                int *    oamcounts = stackalloc int[2];
                int      oamslot   = 0;
                int      oamcount  = 0;

                idleSynch ^= true;

                //render 241 scanlines (including 1 dummy at beginning)
                for (int sl = 0; sl < 241; sl++)
                {
                    ppur.status.cycle = 0;

                    ppur.status.sl = sl;

                    int yp = sl - 1;
                    ppuphase = PPUPHASE.BG;

                    if (NTViewCallback != null && yp == NTViewCallback.Scanline)
                    {
                        NTViewCallback.Callback();
                    }
                    if (PPUViewCallback != null && yp == PPUViewCallback.Scanline)
                    {
                        PPUViewCallback.Callback();
                    }

                    //twiddle the oam buffers
                    int scanslot   = oamslot ^ 1;
                    int renderslot = oamslot;
                    oamslot ^= 1;

                    oamcount = oamcounts[renderslot];

                    //ok, we're also going to draw here.
                    //unless we're on the first dummy scanline
                    if (sl != 0)
                    {
                        //the main scanline rendering loop:
                        //32 times, we will fetch a tile and then render 8 pixels.
                        //two of those tiles were read in the last scanline.
                        int yp_shift = yp << 8;
                        for (int xt = 0; xt < 32; xt++)
                        {
                            int xstart = xt << 3;
                            oamcount = oamcounts[renderslot];
                            int target    = yp_shift + xstart;
                            int rasterpos = xstart;

                            //check all the conditions that can cause things to render in these 8px
                            bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost);
                            bool renderbgnow     = reg_2001.show_bg && (xt > 0 || reg_2001.show_bg_leftmost);

                            for (int xp = 0; xp < 8; xp++, rasterpos++)
                            {
                                //process the current clock's worth of bg data fetching
                                //this needs to be split into 8 pieces or else exact sprite 0 hitting wont work due to the cpu not running while the sprite renders below
                                Read_bgdata(xp, ref bgdata[xt + 2]);

                                //bg pos is different from raster pos due to its offsetability.
                                //so adjust for that here
                                int bgpos  = rasterpos + ppur.fh;
                                int bgpx   = bgpos & 7;
                                int bgtile = bgpos >> 3;

                                int pixel = 0, pixelcolor;

                                //generate the BG data
                                if (renderbgnow)
                                {
                                    byte pt_0 = bgdata[bgtile].pt_0;
                                    byte pt_1 = bgdata[bgtile].pt_1;
                                    int  sel  = 7 - bgpx;
                                    pixel = ((pt_0 >> sel) & 1) | (((pt_1 >> sel) & 1) << 1);
                                    if (pixel != 0)
                                    {
                                        pixel |= bgdata[bgtile].at;
                                    }
                                    pixelcolor = PALRAM[pixel];
                                }
                                else
                                {
                                    if (!renderspritenow)
                                    {
                                        //according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx
                                        //EDIT - this requires corect emulation of PPU OFF state, and seems only to apply when the PPU is OFF
                                        // not sure why this was off, but having it on fixes full_nes_palette, and it's a behavior that's been
                                        // verified on the decapped PPU

                                        // if there's anything wrong with how we're doing this, someone please chime in
                                        int addr = ppur.get_2007access();
                                        if ((addr & 0x3F00) == 0x3F00)
                                        {
                                            //  System.Console.WriteLine("{0:X4}", addr);
                                            pixel = addr & 0x1F;
                                        }
                                    }
                                    pixelcolor  = PALRAM[pixel];
                                    pixelcolor |= 0x8000;                                     //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                                }

                                if (!nes.Settings.DispBackground)
                                {
                                    pixelcolor = 0x8000;                                     //whats this? i think its a flag to indicate a hidden background to be used by the canvas filling logic later
                                }
                                //look for a sprite to be drawn
                                bool havepixel        = false;
                                int  renderslot_shift = renderslot << 6;
                                for (int s = 0; s < oamcount; s++)
                                {
                                    TempOAM *oam = &oams[renderslot_shift + s];
                                    int      x   = oam->oam[3];
                                    if (rasterpos >= x && rasterpos < x + 8)
                                    {
                                        //build the pixel.
                                        //fetch the LSB of the patterns
                                        int spixel = oam->patterns[0] & 1;
                                        spixel |= (oam->patterns[1] & 1) << 1;

                                        //shift down the patterns so the next pixel is in the LSB
                                        oam->patterns[0] >>= 1;
                                        oam->patterns[1] >>= 1;

                                        //bail out if we already have a pixel from a higher priority sprite.
                                        //notice that we continue looping anyway, so that we can shift down the patterns
                                        //transparent pixel bailout
                                        if (!renderspritenow || havepixel || spixel == 0)
                                        {
                                            continue;
                                        }

                                        havepixel = true;

                                        //TODO - make sure we dont trigger spritehit if the edges are masked for either BG or OBJ
                                        //spritehit:
                                        //1. is it sprite#0?
                                        //2. is the bg pixel nonzero?
                                        //then, it is spritehit.
                                        Reg2002_objhit |= (oam->index == 0 && pixel != 0 && rasterpos < 255);
                                        //priority handling, if in front of BG:
                                        bool drawsprite = !(((oam->oam[2] & 0x20) != 0) && ((pixel & 3) != 0));
                                        if (drawsprite && nes.Settings.DispSprites)
                                        {
                                            //bring in the palette bits and palettize
                                            spixel |= (oam->oam[2] & 3) << 2;
                                            //save it for use in the framebuffer
                                            pixelcolor = PALRAM[0x10 + spixel];
                                        }
                                    }                             //rasterpos in sprite range
                                }                                 //oamcount loop
                                if (reg_2001.color_disable)
                                {
                                    pixelcolor &= 0x30;
                                }

                                xbuf[target] = PaletteAdjustPixel(pixelcolor);

                                target++;
                            }                     //loop across 8 pixels
                        }                         //loop across 32 tiles
                    }
                    else
                    {
                        for (int xt = 0; xt < 32; xt++)
                        {
                            Read_bgdata(ref bgdata[xt + 2]);
                        }
                    }

                    //look for sprites (was supposed to run concurrent with bg rendering)
                    oamcounts[scanslot] = 0;
                    oamcount            = 0;
                    int spriteHeight = reg_2000.obj_size_16 ? 16 : 8;

                    int scanslot_lshift = scanslot << 6;

                    for (int i = 0; i < 64; i++)
                    {
                        oams[scanslot_lshift + i].present = 0;
                        int spr = i * 4;
                        if (yp >= OAM[spr] && yp < OAM[spr] + spriteHeight)
                        {
                            //if we already have maxsprites, then this new one causes an overflow,
                            //set the flag and bail out.
                            //should we set this flag anyway??
                            if (oamcount >= 8 && reg_2001.PPUON)
                            {
                                Reg2002_objoverflow = true;
                                if (!nes.Settings.AllowMoreThanEightSprites)
                                {
                                    break;
                                }
                            }
                            //just copy some bytes into the internal sprite buffer
                            TempOAM *oam = &oams[scanslot_lshift + oamcount];
                            for (int j = 0; j < 4; j++)
                            {
                                oam->oam[j] = OAM[spr + j];
                            }
                            oam->present = 1;
                            //note that we stuff the oam index into [6].
                            //i need to turn this into a struct so we can have fewer magic numbers
                            oams[scanslot_lshift + oamcount].index = (byte)i;
                            oamcount++;
                        }
                    }
                    oamcounts[scanslot] = oamcount;

                    ppuphase = PPUPHASE.OBJ;

                    //fetch sprite patterns
                    int oam_todo = oamcount;
                    if (oam_todo < 8)
                    {
                        oam_todo = 8;
                    }
                    for (int s = 0; s < oam_todo; s++)
                    {
                        //if this is a real sprite sprite, then it is not above the 8 sprite limit.
                        //this is how we support the no 8 sprite limit feature.
                        //not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters
                        //this could be handy for the debugging tools also
                        bool realSprite = (s < 8);
                        bool junksprite = (s >= oamcount || !reg_2001.PPUON);

                        TempOAM *oam  = &oams[scanslot_lshift + s];
                        int      line = yp - oam->oam[0];
                        if ((oam->oam[2] & 0x80) != 0)                         //vflip
                        {
                            line = spriteHeight - line - 1;
                        }

                        int patternNumber = oam->oam[1];
                        int patternAddress;

                        //create deterministic dummy fetch pattern.
                        if (oam->present == 0)
                        {
                            //according to nintendulator:
                            //* On the first empty sprite slot, read the Y-coordinate of sprite #63 followed by $FF for the remaining 7 cycles
                            //* On all subsequent empty sprite slots, read $FF for all 8 reads
                            //well, we shall just read $FF and that is good enough for now to make mmc3 work
                            patternNumber = 0xFF;
                            line          = 0;
                        }

                        //8x16 sprite handling:
                        if (reg_2000.obj_size_16)
                        {
                            int bank = (patternNumber & 1) << 12;
                            patternNumber  = patternNumber & ~1;
                            patternNumber |= (line >> 3) & 1;
                            patternAddress = (patternNumber << 4) | bank;
                        }
                        else
                        {
                            patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 12);
                        }

                        //offset into the pattern for the current line.
                        //tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
                        //so we just need the line offset for the second pattern
                        patternAddress += line & 7;

                        //garbage nametable fetches + scroll resets
                        int garbage_todo = 2;
                        ppubus_read(ppur.get_ntread(), true);
                        if (reg_2001.PPUON)
                        {
                            if (sl == 0 && ppur.status.cycle == 304)
                            {
                                runppu(1);
                                if (reg_2001.PPUON)
                                {
                                    ppur.install_latches();
                                }
                                runppu(1);
                                garbage_todo = 0;
                            }
                            if ((sl != 0) && ppur.status.cycle == 256)
                            {
                                runppu(1);
                                //at 257: 3d world runner is ugly if we do this at 256
                                if (reg_2001.PPUON)
                                {
                                    ppur.install_h_latches();
                                }
                                runppu(1);
                                garbage_todo = 0;
                            }
                        }
                        if (realSprite)
                        {
                            runppu(garbage_todo);
                        }

                        ppubus_read(ppur.get_atread(), true);                         //at or nt?
                        if (realSprite)
                        {
                            runppu(kFetchTime);
                        }

                        // TODO - fake sprites should not come through ppubus_read but rather peek it
                        // (at least, they should not probe it with AddressPPU. maybe the difference between peek and read is not necessary)

                        if (junksprite)
                        {
                            if (realSprite)
                            {
                                ppubus_read(patternAddress, true);
                                ppubus_read(patternAddress, true);
                                runppu(kFetchTime * 2);
                            }
                        }
                        else
                        {
                            int addr = patternAddress;
                            oam->patterns[0] = ppubus_read(addr, true);
                            if (realSprite)
                            {
                                runppu(kFetchTime);
                            }

                            addr            += 8;
                            oam->patterns[1] = ppubus_read(addr, true);
                            if (realSprite)
                            {
                                runppu(kFetchTime);
                            }

                            // hflip
                            if ((oam->oam[2] & 0x40) == 0)
                            {
                                oam->patterns[0] = BitReverse.Byte8[oam->patterns[0]];
                                oam->patterns[1] = BitReverse.Byte8[oam->patterns[1]];
                            }
                        }
                    }                     // sprite pattern fetch loop

                    ppuphase = PPUPHASE.BG;

                    // fetch BG: two tiles for next line
                    for (int xt = 0; xt < 2; xt++)
                    {
                        Read_bgdata(ref bgdata[xt]);
                    }

                    // this sequence is tuned to pass 10-even_odd_timing.nes
                    runppu(kFetchTime);
                    bool evenOddDestiny = reg_2001.show_bg;
                    runppu(kFetchTime);

                    // After memory access 170, the PPU simply rests for 4 cycles (or the
                    // equivelant of half a memory access cycle) before repeating the whole
                    // pixel/scanline rendering process. If the scanline being rendered is the very
                    // first one on every second frame, then this delay simply doesn't exist.
                    if (sl == 0 && idleSynch && evenOddDestiny && chopdot)
                    {
                    }
                    else
                    {
                        runppu(1);
                    }
                }                 // scanline loop

                ppur.status.sl = 241;

                //idle for pre NMI lines
                runppu(preNMIlines * kLineTime);
            }             //FrameAdvance