예제 #1
0
파일: GPU.cs 프로젝트: Strimy/GBEmulator
        internal void Step(uint instructionTime)
        {
            if (!LCDC.HasFlag(LCDC.LCDEnable))
            {
                return;
            }

            _modeClock += instructionTime;

            GPUMode mode = Mode;

            switch (mode)
            {
            case GPUMode.Mode0:     // HBlank
                Mode0();
                break;

            case GPUMode.Mode1:     // VBlank
                Mode1();
                break;

            case GPUMode.Mode2:     // OAM
                Mode2();
                break;

            case GPUMode.Mode3:     // LCD Transfer
                Mode3();
                break;

            default:
                break;
            }
        }
예제 #2
0
 public override string ToString()
 {
     return(String.Format("{0}{1}Scanline: {2}{3}Window X,Y: {4} , {5}{6}BG Scroll X, Y: {7} , {8}{9}",
                          LCDC.ToString(),
                          STAT.ToString(),
                          ppu.CurrentScanline, Environment.NewLine,
                          WindowX, WindowY, Environment.NewLine,
                          BgScrollX, BgScrollY, Environment.NewLine));
 }
예제 #3
0
        public override void process_sprite()
        {
            int y;

            if (SL_sprites[sl_use_index * 4 + 3].Bit(6))
            {
                if (LCDC.Bit(2))
                {
                    y             = LY - (SL_sprites[sl_use_index * 4] - 16);
                    y             = 15 - y;
                    sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
                    sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
                }
                else
                {
                    y             = LY - (SL_sprites[sl_use_index * 4] - 16);
                    y             = 7 - y;
                    sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
                    sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
                }
            }
            else
            {
                if (LCDC.Bit(2))
                {
                    y             = LY - (SL_sprites[sl_use_index * 4] - 16);
                    sprite_sel[0] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
                    sprite_sel[1] = Core.VRAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
                }
                else
                {
                    y             = LY - (SL_sprites[sl_use_index * 4] - 16);
                    sprite_sel[0] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
                    sprite_sel[1] = Core.VRAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
                }
            }

            if (SL_sprites[sl_use_index * 4 + 3].Bit(5))
            {
                int b0, b1, b2, b3, b4, b5, b6, b7 = 0;
                for (int i = 0; i < 2; i++)
                {
                    b0 = (sprite_sel[i] & 0x01) << 7;
                    b1 = (sprite_sel[i] & 0x02) << 5;
                    b2 = (sprite_sel[i] & 0x04) << 3;
                    b3 = (sprite_sel[i] & 0x08) << 1;
                    b4 = (sprite_sel[i] & 0x10) >> 1;
                    b5 = (sprite_sel[i] & 0x20) >> 3;
                    b6 = (sprite_sel[i] & 0x40) >> 5;
                    b7 = (sprite_sel[i] & 0x80) >> 7;

                    sprite_sel[i] = (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
                }
            }
        }
예제 #4
0
파일: GPU.cs 프로젝트: Strimy/GBEmulator
 private ReadOnlySpan <byte> GetSelectedBackgroundTileMap()
 {
     if (LCDC.HasFlag(LCDC.BGTileMapSelect))
     {
         return(BackgroundMap1);
     }
     else
     {
         return(BackgroundMap0);
     }
 }
예제 #5
0
파일: GPU.cs 프로젝트: Strimy/GBEmulator
        private void Scanline()
        {
            int line      = LY;
            var frameLine = _frame.AsSpan(line * 256, 256);

            ReadOnlySpan <byte>   windowTileMap = GetSelectedWindowTileMap();
            ReadOnlySpan <byte>   bgTileMap     = GetSelectedBackgroundTileMap();
            ReadOnlySpan <BGTile> bgTileData    = SelectedBackgroundTileData;

            bool isWindowDisplayEnabled = LCDC.HasFlag(LCDC.WindowDisplayEnabled);

            var tileMapSliced = bgTileMap.Slice(line / 8 * 32, 32);


            if (isWindowDisplayEnabled)
            {
                // TODO : Draw the window over
            }
        }
예제 #6
0
        public override void render(int render_cycle)
        {
            // we are now in STAT mode 3
            // NOTE: presumably the first necessary sprite is fetched at sprite evaulation
            // i.e. just keeping track of the lowest x-value sprite
            if (render_cycle == 0)
            {
                OAM_access_read  = false;
                OAM_access_write = true;
                VRAM_access_read = false;

                // window X is latched for the scanline, mid-line changes have no effect
                window_x_latch = window_x;

                OAM_scan_index  = 0;
                read_case       = 0;
                internal_cycle  = 0;
                pre_render      = true;
                tile_inc        = 0;
                pixel_counter   = -8;
                sl_use_index    = 0;
                fetch_sprite    = false;
                fetch_sprite_01 = false;
                fetch_sprite_4  = false;
                going_to_fetch  = false;
                first_fetch     = true;
                no_sprites      = false;
                evaled_sprites  = 0;

                window_pre_render = false;
                window_latch      = LCDC.Bit(5);

                // TODO: If Window is turned on midscanline what happens? When is this check done exactly?
                if ((window_started && window_latch) || (window_is_reset && !window_latch && (LY > window_y)))
                {
                    window_y_tile_inc++;
                    if (window_y_tile_inc == 8)
                    {
                        window_y_tile_inc = 0;
                        window_y_tile++;
                        window_y_tile %= 32;
                    }
                }
                window_started = false;

                if (SL_sprites_index == 0)
                {
                    no_sprites = true;
                }
                // it is much easier to process sprites if we order them according to the rules of sprite priority first
                if (!no_sprites)
                {
                    reorder_and_assemble_sprites();
                }
            }

            // before anything else, we have to check if windowing is in effect
            if (window_latch && !window_started && (LY >= window_y) && (pixel_counter >= (window_x_latch - 7)) && (window_x_latch < 167))
            {
                /*
                 * Console.Write(LY);
                 * Console.Write(" ");
                 * Console.Write(cycle);
                 * Console.Write(" ");
                 * Console.Write(window_y_tile_inc);
                 * Console.Write(" ");
                 * Console.Write(window_x_latch);
                 * Console.Write(" ");
                 * Console.WriteLine(pixel_counter);
                 */
                if (window_x_latch <= 7)
                {
                    // if the window starts at zero, we still do the first access to the BG
                    // but then restart all over again at the window
                    read_case = 9;
                }
                else
                {
                    // otherwise, just restart the whole process as if starting BG again
                    read_case = 4;
                }
                window_pre_render = true;

                window_counter = 0;
                render_counter = 0;

                window_x_tile = (int)Math.Floor((float)(pixel_counter - (window_x_latch - 7)) / 8);

                window_tile_inc = 0;
                window_started  = true;
                window_is_reset = false;
            }

            if (!pre_render && !fetch_sprite)
            {
                // start shifting data into the LCD
                if (render_counter >= (render_offset + 8))
                {
                    pixel  = tile_data_latch[0].Bit(7 - (render_counter % 8)) ? 1 : 0;
                    pixel |= tile_data_latch[1].Bit(7 - (render_counter % 8)) ? 2 : 0;

                    int ref_pixel = pixel;
                    if (LCDC.Bit(0))
                    {
                        pixel = (BGP >> (pixel * 2)) & 3;
                    }
                    else
                    {
                        pixel = 0;
                    }

                    // now we have the BG pixel, we next need the sprite pixel
                    if (!no_sprites)
                    {
                        bool have_sprite = false;
                        int  s_pixel     = 0;
                        int  sprite_attr = 0;

                        if (sprite_present_list[pixel_counter] == 1)
                        {
                            have_sprite = true;
                            s_pixel     = sprite_pixel_list[pixel_counter];
                            sprite_attr = sprite_attr_list[pixel_counter];
                        }

                        if (have_sprite)
                        {
                            bool use_sprite = false;
                            if (LCDC.Bit(1))
                            {
                                if (!sprite_attr.Bit(7))
                                {
                                    use_sprite = true;
                                }
                                else if (ref_pixel == 0)
                                {
                                    use_sprite = true;
                                }

                                if (!LCDC.Bit(0))
                                {
                                    use_sprite = true;
                                }
                            }

                            if (use_sprite)
                            {
                                if (sprite_attr.Bit(4))
                                {
                                    pixel = (obj_pal_1 >> (s_pixel * 2)) & 3;
                                }
                                else
                                {
                                    pixel = (obj_pal_0 >> (s_pixel * 2)) & 3;
                                }
                            }
                        }
                    }

                    // based on sprite priority and pixel values, pick a final pixel color
                    Core._vidbuffer[LY * 160 + pixel_counter] = (int)Core.color_palette[pixel];
                    pixel_counter++;

                    if (pixel_counter == 160)
                    {
                        read_case     = 8;
                        hbl_countdown = 5;
                    }
                }
                else if (pixel_counter < 0)
                {
                    pixel_counter++;
                }
                render_counter++;
            }

            if (!fetch_sprite)
            {
                if (!pre_render)
                {
                    // before we go on to read case 3, we need to know if we stall there or not
                    // Gekkio's tests show that if sprites are at position 0 or 1 (mod 8)
                    // then it takes an extra cycle (1 or 2 more t-states) to process them

                    if (!no_sprites && (pixel_counter < 160))
                    {
                        for (int i = 0; i < SL_sprites_index; i++)
                        {
                            if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) &&
                                (pixel_counter < (SL_sprites[i * 4 + 1])) &&
                                !evaled_sprites.Bit(i))
                            {
                                going_to_fetch = true;
                                fetch_sprite   = true;

                                if ((SL_sprites[i * 4 + 1] % 8) < 2)
                                {
                                    fetch_sprite_01 = true;
                                }
                                if ((SL_sprites[i * 4 + 1] % 8) > 3)
                                {
                                    fetch_sprite_4 = true;
                                }
                            }
                        }
                    }
                }

                switch (read_case)
                {
                case 0:                         // read a background tile
                    if ((internal_cycle % 2) == 0)
                    {
                        // calculate the row number of the tiles to be fetched
                        y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32;

                        temp_fetch = y_tile * 32 + (x_tile + tile_inc) % 32;
                        tile_byte  = Core.VRAM[0x1800 + (LCDC.Bit(3) ? 1 : 0) * 0x400 + temp_fetch];
                    }
                    else
                    {
                        read_case = 1;
                        if (!pre_render)
                        {
                            tile_inc++;
                        }
                    }
                    break;

                case 1:                         // read from tile graphics (0)
                    if ((internal_cycle % 2) == 0)
                    {
                        y_scroll_offset = (scroll_y + LY) % 8;

                        if (LCDC.Bit(4))
                        {
                            tile_data[0] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2];
                        }
                        else
                        {
                            // same as before except now tile byte represents a signed byte
                            if (tile_byte.Bit(7))
                            {
                                tile_byte -= 256;
                            }
                            tile_data[0] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
                        }
                    }
                    else
                    {
                        read_case = 2;
                    }
                    break;

                case 2:                         // read from tile graphics (1)
                    if ((internal_cycle % 2) == 0)
                    {
                        y_scroll_offset = (scroll_y + LY) % 8;

                        if (LCDC.Bit(4))
                        {
                            // if LCDC somehow changed between the two reads, make sure we have a positive number
                            if (tile_byte < 0)
                            {
                                tile_byte += 256;
                            }

                            tile_data[1] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
                        }
                        else
                        {
                            // same as before except now tile byte represents a signed byte
                            if (tile_byte.Bit(7) && tile_byte > 0)
                            {
                                tile_byte -= 256;
                            }

                            tile_data[1] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
                        }
                    }
                    else
                    {
                        if (pre_render)
                        {
                            // here we set up rendering
                            pre_render     = false;
                            render_offset  = scroll_x % 8;
                            render_counter = 0;
                            latch_counter  = 0;
                            read_case      = 0;
                        }
                        else
                        {
                            read_case = 3;
                        }
                    }
                    break;

                case 3:                         // read from sprite data
                    if ((internal_cycle % 2) == 0)
                    {
                        // nothing to do if not fetching
                    }
                    else
                    {
                        read_case      = 0;
                        latch_new_data = true;
                    }
                    break;

                case 4:                         // read from window data
                    if ((window_counter % 2) == 0)
                    {
                        temp_fetch = window_y_tile * 32 + (window_x_tile + window_tile_inc) % 32;
                        tile_byte  = Core.VRAM[0x1800 + (LCDC.Bit(6) ? 1 : 0) * 0x400 + temp_fetch];;
                    }
                    else
                    {
                        window_tile_inc++;
                        read_case = 5;
                    }
                    window_counter++;
                    break;

                case 5:                         // read from tile graphics (for the window)
                    if ((window_counter % 2) == 0)
                    {
                        y_scroll_offset = (window_y_tile_inc) % 8;

                        if (LCDC.Bit(4))
                        {
                            tile_data[0] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2];
                        }
                        else
                        {
                            // same as before except now tile byte represents a signed byte
                            if (tile_byte.Bit(7))
                            {
                                tile_byte -= 256;
                            }

                            tile_data[0] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
                        }
                    }
                    else
                    {
                        read_case = 6;
                    }
                    window_counter++;
                    break;

                case 6:                         // read from tile graphics (for the window)
                    if ((window_counter % 2) == 0)
                    {
                        y_scroll_offset = (window_y_tile_inc) % 8;
                        if (LCDC.Bit(4))
                        {
                            // if LCDC somehow changed between the two reads, make sure we have a positive number
                            if (tile_byte < 0)
                            {
                                tile_byte += 256;
                            }

                            tile_data[1] = Core.VRAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
                        }
                        else
                        {
                            // same as before except now tile byte represents a signed byte
                            if (tile_byte.Bit(7) && tile_byte > 0)
                            {
                                tile_byte -= 256;
                            }

                            tile_data[1] = Core.VRAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
                        }
                    }
                    else
                    {
                        if (window_pre_render)
                        {
                            // here we set up rendering
                            // unlike for the normal background case, there is no pre-render period for the window
                            // so start shifting in data to the screen right away
                            render_offset  = 0;
                            render_counter = 8;
                            latch_counter  = 0;
                            latch_new_data = true;

                            window_pre_render = false;
                            read_case         = 4;
                        }
                        else
                        {
                            read_case = 7;
                        }
                    }
                    window_counter++;
                    break;

                case 7:                         // read from sprite data
                    if ((window_counter % 2) == 0)
                    {
                        // nothing to do if not fetching
                    }
                    else
                    {
                        read_case      = 4;
                        latch_new_data = true;
                    }
                    window_counter++;
                    break;

                case 8:                         // done reading, we are now in phase 0
                    pre_render = true;

                    // the other interrupts appear to be delayed by 1 CPU cycle, so do the same here
                    if (hbl_countdown > 0)
                    {
                        hbl_countdown--;
                        if (hbl_countdown == 0)
                        {
                            STAT &= 0xFC;
                            STAT |= 0x00;

                            if (STAT.Bit(3))
                            {
                                HBL_INT = true;
                            }

                            OAM_access_read   = true;
                            OAM_access_write  = true;
                            VRAM_access_read  = true;
                            VRAM_access_write = true;
                        }
                    }
                    break;

                case 9:
                    // this is a degenerate case for starting the window at 0
                    // kevtris' timing doc indicates an additional normal BG access
                    // but this information is thrown away, so it's faster to do this then constantly check
                    // for it in read case 0
                    read_case = 4;
                    break;
                }
                internal_cycle++;

                if (latch_new_data)
                {
                    latch_new_data     = false;
                    tile_data_latch[0] = tile_data[0];
                    tile_data_latch[1] = tile_data[1];
                }
            }

            // every in range sprite takes 6 cycles to process
            // sprites located at x=0 still take 6 cycles to process even though they don't appear on screen
            // sprites above x=168 do not take any cycles to process however
            if (fetch_sprite)
            {
                if (going_to_fetch)
                {
                    going_to_fetch       = false;
                    sprite_fetch_counter = first_fetch ? 2 : 0;
                    first_fetch          = false;

                    if (fetch_sprite_01)
                    {
                        sprite_fetch_counter += 2;
                        fetch_sprite_01       = false;
                    }

                    if (fetch_sprite_4)
                    {
                        sprite_fetch_counter -= 2;
                        fetch_sprite_4        = false;
                    }

                    int last_eval = 0;

                    // at this time it is unknown what each cycle does, but we only need to accurately keep track of cycles
                    for (int i = 0; i < SL_sprites_index; i++)
                    {
                        if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) &&
                            (pixel_counter < (SL_sprites[i * 4 + 1])) &&
                            !evaled_sprites.Bit(i))
                        {
                            sprite_fetch_counter += 6;
                            evaled_sprites       |= (1 << i);
                            last_eval             = SL_sprites[i * 4 + 1];
                        }
                    }

                    // if we didn't evaluate all the sprites immediately, 2 more cycles are added to restart it
                    if (evaled_sprites != (Math.Pow(2, SL_sprites_index) - 1))
                    {
                        if ((last_eval % 8) == 0)
                        {
                            sprite_fetch_counter += 3;
                        }
                        else if ((last_eval % 8) == 1)
                        {
                            sprite_fetch_counter += 2;
                        }
                        else if ((last_eval % 8) == 2)
                        {
                            sprite_fetch_counter += 3;
                        }
                        else if ((last_eval % 8) == 3)
                        {
                            sprite_fetch_counter += 2;
                        }
                        else if ((last_eval % 8) == 4)
                        {
                            sprite_fetch_counter += 3;
                        }
                        else
                        {
                            sprite_fetch_counter += 2;
                        }
                    }
                }
                else
                {
                    sprite_fetch_counter--;
                    if (sprite_fetch_counter == 0)
                    {
                        fetch_sprite = false;
                    }
                }
            }
        }
예제 #7
0
        public override void WriteReg(int addr, byte value)
        {
            switch (addr)
            {
            case 0xFF40:                     // LCDC
                if (LCDC.Bit(7) && !value.Bit(7))
                {
                    VRAM_access_read  = true;
                    VRAM_access_write = true;
                    OAM_access_read   = true;
                    OAM_access_write  = true;
                }

                if (!LCDC.Bit(7) && value.Bit(7))
                {
                    // don't draw for one frame after turning on
                    blank_frame = true;
                }

                LCDC = value;
                break;

            case 0xFF41:                     // STAT
                // writing to STAT during mode 0 or 2 causes a STAT IRQ
                if (LCDC.Bit(7))
                {
                    if (((STAT & 3) == 0) || ((STAT & 3) == 1))
                    {
                        LYC_INT = true;
                    }
                }
                STAT = (byte)((value & 0xF8) | (STAT & 7) | 0x80);
                break;

            case 0xFF42:                     // SCY
                scroll_y = value;
                break;

            case 0xFF43:                     // SCX
                scroll_x = value;
                // calculate the column number of the tile to start with
                x_tile = (int)Math.Floor((float)(scroll_x) / 8);
                break;

            case 0xFF44:                     // LY
                LY = 0;                      /*reset*/
                break;

            case 0xFF45:                      // LYC
                LYC = value;
                if (LCDC.Bit(7))
                {
                    if (LY != LYC)
                    {
                        STAT &= 0xFB;
                    }
                    else
                    {
                        STAT |= 0x4;
                    }
                }
                break;

            case 0xFF46:                     // DMA
                DMA_addr       = value;
                DMA_start      = true;
                DMA_OAM_access = true;
                DMA_clock      = 0;
                DMA_inc        = 0;
                break;

            case 0xFF47:                     // BGP
                BGP = value;
                break;

            case 0xFF48:                     // OBP0
                obj_pal_0 = value;
                break;

            case 0xFF49:                     // OBP1
                obj_pal_1 = value;
                break;

            case 0xFF4A:                     // WY
                window_y = value;
                break;

            case 0xFF4B:                     // WX
                window_x = value;
                break;
            }
        }
예제 #8
0
        public override void tick()
        {
            // the ppu only does anything if it is turned on via bit 7 of LCDC
            if (LCDC.Bit(7))
            {
                // start the next scanline
                if (cycle == 456)
                {
                    // scanline callback
                    if ((LY + LY_inc) == Core._scanlineCallbackLine)
                    {
                        if (Core._scanlineCallback != null)
                        {
                            Core.GetGPU();
                            Core._scanlineCallback(LCDC);
                        }
                    }

                    cycle = 0;

                    LY         += LY_inc;
                    Core.cpu.LY = LY;

                    no_scan = false;

                    if (LY == 0 && LY_inc == 0)
                    {
                        LY_inc         = 1;
                        Core.in_vblank = false;

                        VBL_INT = false;
                        if (STAT.Bit(3))
                        {
                            HBL_INT = true;
                        }

                        STAT &= 0xFC;

                        // special note here, the y coordiate of the window is kept if the window is deactivated
                        // meaning it will pick up where it left off if re-enabled later
                        // so we don't reset it in the scanline loop
                        window_y_tile     = 0;
                        window_y_tile_inc = 0;
                        window_started    = false;
                        if (!LCDC.Bit(5))
                        {
                            window_is_reset = true;
                        }
                    }

                    // Automatically restore access to VRAM at this time (force end drawing)
                    // Who Framed Roger Rabbit seems to run into this.
                    VRAM_access_write = true;
                    VRAM_access_read  = true;

                    if (LY == 144)
                    {
                        Core.in_vblank = true;
                    }
                }

                // exit vblank if LCD went from off to on
                if (LCD_was_off)
                {
                    //VBL_INT = false;
                    Core.in_vblank = false;
                    LCD_was_off    = false;

                    // we exit vblank into mode 0 for 4 cycles
                    // but no hblank interrupt, presumably this only happens transitioning from mode 3 to 0
                    STAT &= 0xFC;

                    // also the LCD doesn't turn on right away

                    // also, the LCD does not enter mode 2 on scanline 0 when first turned on
                    no_scan = true;
                    cycle   = 8;
                }

                // the VBL stat is continuously asserted
                if ((LY >= 144))
                {
                    if (STAT.Bit(4))
                    {
                        if ((cycle >= 4) && (LY == 144))
                        {
                            VBL_INT = true;
                        }
                        else if (LY > 144)
                        {
                            VBL_INT = true;
                        }
                    }

                    if ((cycle == 4) && (LY == 144))
                    {
                        HBL_INT = false;

                        // set STAT mode to 1 (VBlank) and interrupt flag if it is enabled
                        STAT &= 0xFC;
                        STAT |= 0x01;

                        if (Core.REG_FFFF.Bit(0))
                        {
                            Core.cpu.FlagI = true;
                        }
                        Core.REG_FF0F |= 0x01;
                    }

                    if ((LY >= 144) && (cycle == 4))
                    {
                        // a special case of OAM mode 2 IRQ assertion, even though PPU Mode still is 1
                        if (STAT.Bit(5))
                        {
                            OAM_INT = true;
                        }
                    }

                    if ((LY == 153) && (cycle == 6))
                    {
                        LY          = 0;
                        LY_inc      = 0;
                        Core.cpu.LY = LY;
                    }
                }

                if (!Core.in_vblank)
                {
                    if (no_scan)
                    {
                        // timings are slightly different if we just turned on the LCD
                        // there is no mode 2  (presumably it missed the trigger)
                        // mode 3 is very short, probably in some self test mode before turning on?

                        if (cycle == 8)
                        {
                            if (LY != LYC)
                            {
                                LYC_INT = false;
                                STAT   &= 0xFB;
                            }

                            if ((LY == LYC) && !STAT.Bit(2))
                            {
                                // set STAT coincidence FLAG and interrupt flag if it is enabled
                                STAT |= 0x04;
                                if (STAT.Bit(6))
                                {
                                    LYC_INT = true;
                                }
                            }
                        }

                        if (cycle == 84)
                        {
                            STAT   &= 0xFC;
                            STAT   |= 0x03;
                            OAM_INT = false;

                            OAM_access_read   = false;
                            OAM_access_write  = false;
                            VRAM_access_read  = false;
                            VRAM_access_write = false;
                        }

                        if (cycle == 256)
                        {
                            STAT   &= 0xFC;
                            OAM_INT = false;

                            if (STAT.Bit(3))
                            {
                                HBL_INT = true;
                            }

                            OAM_access_read   = true;
                            OAM_access_write  = true;
                            VRAM_access_read  = true;
                            VRAM_access_write = true;
                        }
                    }
                    else
                    {
                        if (cycle < 80)
                        {
                            if (cycle == 2)
                            {
                                if (LY != 0)
                                {
                                    if (STAT.Bit(5))
                                    {
                                        OAM_INT = true;
                                    }
                                }
                            }
                            else if (cycle == 4)
                            {
                                // apparently, writes can make it to OAM one cycle longer then reads
                                OAM_access_write = false;

                                // here mode 2 will be set to true and interrupts fired if enabled
                                STAT &= 0xFC;
                                STAT |= 0x2;

                                if (LY == 0)
                                {
                                    if (STAT.Bit(5))
                                    {
                                        OAM_INT = true;
                                    }
                                }

                                HBL_INT = false;
                            }

                            // here OAM scanning is performed
                            OAM_scan(cycle);
                        }
                        else if ((cycle >= 80) && (LY < 144))
                        {
                            if (cycle == 84)
                            {
                                STAT             &= 0xFC;
                                STAT             |= 0x03;
                                OAM_INT           = false;
                                OAM_access_write  = false;
                                VRAM_access_write = false;
                            }

                            // render the screen and handle hblank
                            render(cycle - 80);
                        }
                    }
                }

                if ((LY_inc == 0))
                {
                    if (cycle == 12)
                    {
                        LYC_INT = false;
                        STAT   &= 0xFB;

                        // Special case of LY = LYC
                        if ((LY == LYC) && !STAT.Bit(2))
                        {
                            // set STAT coincidence FLAG and interrupt flag if it is enabled
                            STAT |= 0x04;
                            if (STAT.Bit(6))
                            {
                                LYC_INT = true;
                            }
                        }

                        // also a special case of OAM mode 2 IRQ assertion, even though PPU Mode still is 1
                        if (STAT.Bit(5))
                        {
                            OAM_INT = true;
                        }
                    }

                    if (cycle == 92)
                    {
                        OAM_INT = false;
                    }
                }

                // here LY=LYC will be asserted or cleared (but only if LY isnt 0 as that's a special case)
                if ((cycle == 2) && (LY != 0))
                {
                    if (LY_inc == 1)
                    {
                        LYC_INT = false;
                        STAT   &= 0xFB;
                    }
                }
                else if ((cycle == 4) && (LY != 0))
                {
                    if ((LY == LYC) && !STAT.Bit(2))
                    {
                        // set STAT coincidence FLAG and interrupt flag if it is enabled
                        STAT |= 0x04;
                        if (STAT.Bit(6))
                        {
                            LYC_INT = true;
                        }
                    }
                }

                cycle++;
            }
            else
            {
                STAT &= 0xFC;

                VBL_INT = LYC_INT = HBL_INT = OAM_INT = false;

                Core.in_vblank = true;

                LCD_was_off = true;

                LY          = 0;
                Core.cpu.LY = LY;

                cycle = 0;
            }

            // assert the STAT IRQ line if the line went from zero to 1
            stat_line = VBL_INT | LYC_INT | HBL_INT | OAM_INT;

            if (stat_line && !stat_line_old)
            {
                if (Core.REG_FFFF.Bit(1))
                {
                    Core.cpu.FlagI = true;
                }
                Core.REG_FF0F |= 0x02;
            }

            stat_line_old = stat_line;

            // process latch delays
            //latch_delay();
        }
예제 #9
0
        public override void OAM_scan(int OAM_cycle)
        {
            // we are now in STAT mode 2
            // TODO: maybe stat mode 2 flags are set at cycle 0 on visible scanlines?
            if (OAM_cycle == 0)
            {
                OAM_access_read = false;

                OAM_scan_index   = 0;
                SL_sprites_index = 0;
                write_sprite     = 0;
            }

            // the gameboy has 80 cycles to scan through 40 sprites, picking out the first 10 it finds to draw
            // the following is a guessed at implmenentation based on how NES does it, it's probably pretty close
            if (OAM_cycle < 10)
            {
                // start by clearing the sprite table (probably just clears X on hardware, but let's be safe here.)
                SL_sprites[OAM_cycle * 4]     = 0;
                SL_sprites[OAM_cycle * 4 + 1] = 0;
                SL_sprites[OAM_cycle * 4 + 2] = 0;
                SL_sprites[OAM_cycle * 4 + 3] = 0;
            }
            else
            {
                if (write_sprite == 0)
                {
                    if (OAM_scan_index < 40)
                    {
                        ushort temp = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4] : (ushort)0xFF;
                        // (sprite Y - 16) equals LY, we have a sprite
                        if ((temp - 16) <= LY &&
                            ((temp - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY)
                        {
                            // always pick the first 10 in range sprites
                            if (SL_sprites_index < 10)
                            {
                                SL_sprites[SL_sprites_index * 4] = temp;

                                write_sprite = 1;
                            }
                            else
                            {
                                // if we already have 10 sprites, there's nothing to do, increment the index
                                OAM_scan_index++;
                            }
                        }
                        else
                        {
                            OAM_scan_index++;
                        }
                    }
                }
                else
                {
                    ushort temp2 = DMA_OAM_access ? Core.OAM[OAM_scan_index * 4 + write_sprite] : (ushort)0xFF;
                    SL_sprites[SL_sprites_index * 4 + write_sprite] = temp2;
                    write_sprite++;

                    if (write_sprite == 4)
                    {
                        write_sprite = 0;
                        SL_sprites_index++;
                        OAM_scan_index++;
                    }
                }
            }
        }
예제 #10
0
        public override void WriteReg(int addr, byte value)
        {
            switch (addr)
            {
            case 0xFF40:                     // LCDC
                if (LCDC.Bit(7) && !value.Bit(7))
                {
                    VRAM_access_read  = true;
                    VRAM_access_write = true;
                    OAM_access_read   = true;
                    OAM_access_write  = true;

                    clear_screen = true;
                }

                if (!LCDC.Bit(7) && value.Bit(7))
                {
                    // don't draw for one frame after turning on
                    blank_frame = true;
                }

                LCDC = value;
                break;

            case 0xFF41:                     // STAT
                // writing to STAT during mode 0 or 1 causes a STAT IRQ
                // this appears to be a glitchy LYC compare
                if (LCDC.Bit(7))
                {
                    if (((STAT & 3) == 0) || ((STAT & 3) == 1))
                    {
                        LYC_INT = true;
                        //if (Core.REG_FFFF.Bit(1)) { Core.cpu.FlagI = true; }
                        //Core.REG_FF0F |= 0x02;
                    }
                    else
                    {
                        if (value.Bit(6))
                        {
                            if (LY == LYC)
                            {
                                LYC_INT = true;
                            }
                            else
                            {
                                LYC_INT = false;
                            }
                        }
                    }
                }
                STAT = (byte)((value & 0xF8) | (STAT & 7) | 0x80);

                //if (!STAT.Bit(6)) { LYC_INT = false; }
                if (!STAT.Bit(4))
                {
                    VBL_INT = false;
                }
                break;

            case 0xFF42:                     // SCY
                scroll_y = value;
                break;

            case 0xFF43:                     // SCX
                scroll_x = value;
                break;

            case 0xFF44:                     // LY
                LY = 0;                      /*reset*/
                break;

            case 0xFF45:                      // LYC
                LYC = value;
                if (LCDC.Bit(7))
                {
                    if (LY != LYC)
                    {
                        STAT &= 0xFB; LYC_INT = false;
                    }
                    else
                    {
                        STAT |= 0x4; LYC_INT = true;
                    }

                    // special case: the transition from 153 -> 0 acts strange
                    // the comparison to 153 expects to be true for longer then the value of LY expects to be 153
                    // this appears to be fixed in CGB
                    if ((LY_inc == 0) && cycle == 8)
                    {
                        if (153 != LYC)
                        {
                            STAT &= 0xFB; LYC_INT = false;
                        }
                        else
                        {
                            STAT |= 0x4; LYC_INT = true;
                        }
                    }
                }
                break;

            case 0xFF46:                     // DMA
                DMA_addr       = value;
                DMA_start      = true;
                DMA_OAM_access = true;
                DMA_clock      = 0;
                DMA_inc        = 0;
                break;

            case 0xFF47:                     // BGP
                BGP = value;
                break;

            case 0xFF48:                     // OBP0
                obj_pal_0 = value;
                break;

            case 0xFF49:                     // OBP1
                obj_pal_1 = value;
                break;

            case 0xFF4A:                     // WY
                window_y = value;
                break;

            case 0xFF4B:                     // WX
                window_x = value;
                break;
            }
        }