cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

Video.cpp (30892B)


      1/*
      2 * Gearboy - Nintendo Game Boy Emulator
      3 * Copyright (C) 2012  Ignacio Sanchez
      4
      5 * This program is free software: you can redistribute it and/or modify
      6 * it under the terms of the GNU General Public License as published by
      7 * the Free Software Foundation, either version 3 of the License, or
      8 * any later version.
      9
     10 * This program is distributed in the hope that it will be useful,
     11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13 * GNU General Public License for more details.
     14
     15 * You should have received a copy of the GNU General Public License
     16 * along with this program.  If not, see http://www.gnu.org/licenses/
     17 *
     18 */
     19
     20#include "Video.h"
     21#include "Memory.h"
     22#include "Processor.h"
     23
     24Video::Video(Memory* pMemory, Processor* pProcessor)
     25{
     26    m_pMemory = pMemory;
     27    m_pMemory->SetVideo(this);
     28    m_pProcessor = pProcessor;
     29    InitPointer(m_pFrameBuffer);
     30    InitPointer(m_pColorFrameBuffer);
     31    InitPointer(m_pSpriteXCacheBuffer);
     32    InitPointer(m_pColorCacheBuffer);
     33    m_iStatusMode = 0;
     34    m_iStatusModeCounter = 0;
     35    m_iStatusModeCounterAux = 0;
     36    m_iStatusModeLYCounter = 0;
     37    m_iScreenEnableDelayCycles = 0;
     38    m_iStatusVBlankLine = 0;
     39    m_iWindowLine = 0;
     40    m_iPixelCounter = 0;
     41    m_iTileCycleCounter = 0;
     42    m_bScreenEnabled = true;
     43    m_bCGB = false;
     44    m_bScanLineTransfered = false;
     45    m_iHideFrames = 0;
     46    m_IRQ48Signal = 0;
     47    m_pixelFormat = GB_PIXEL_RGB565;
     48}
     49
     50Video::~Video()
     51{
     52    SafeDeleteArray(m_pSpriteXCacheBuffer);
     53    SafeDeleteArray(m_pColorCacheBuffer);
     54    SafeDeleteArray(m_pFrameBuffer);
     55}
     56
     57void Video::Init()
     58{
     59    m_pFrameBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT];
     60    m_pSpriteXCacheBuffer = new int[GAMEBOY_WIDTH * GAMEBOY_HEIGHT];
     61    m_pColorCacheBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT];
     62    Reset(false);
     63}
     64
     65void Video::Reset(bool bCGB)
     66{
     67    for (int i = 0; i < (GAMEBOY_WIDTH * GAMEBOY_HEIGHT); i++)
     68        m_pSpriteXCacheBuffer[i] = m_pFrameBuffer[i] = m_pColorCacheBuffer[i] = 0;
     69
     70    for (int p = 0; p < 8; p++)
     71        for (int c = 0; c < 4; c++)
     72        {
     73            m_CGBBackgroundPalettes[p][c][0] = m_CGBSpritePalettes[p][c][0] = 0x0000;
     74            m_CGBBackgroundPalettes[p][c][1] = m_CGBSpritePalettes[p][c][1] = 0x0000;
     75        }
     76
     77    m_iStatusMode = 1;
     78    m_iStatusModeCounter = 0;
     79    m_iStatusModeCounterAux = 0;
     80    m_iStatusModeLYCounter = 144;
     81    m_iScreenEnableDelayCycles = 0;
     82    m_iStatusVBlankLine = 0;
     83    m_iWindowLine = 0;
     84    m_iPixelCounter = 0;
     85    m_iTileCycleCounter = 0;
     86    m_bScreenEnabled = true;
     87    m_bScanLineTransfered = false;
     88    m_bCGB = bCGB;
     89    m_iHideFrames = 0;
     90    m_IRQ48Signal = 0;
     91}
     92
     93bool Video::Tick(unsigned int &clockCycles, u16* pColorFrameBuffer, GB_Color_Format pixelFormat)
     94{
     95    m_pColorFrameBuffer = pColorFrameBuffer;
     96    m_pixelFormat = pixelFormat;
     97
     98    bool vblank = false;
     99    m_iStatusModeCounter += clockCycles;
    100
    101    if (m_bScreenEnabled)
    102    {
    103        switch (m_iStatusMode)
    104        {
    105            // During H-BLANK
    106            case 0:
    107            {
    108                if (m_iStatusModeCounter >= 204)
    109                {
    110                    m_iStatusModeCounter -= 204;
    111                    m_iStatusMode = 2;
    112
    113                    m_iStatusModeLYCounter++;
    114                    m_pMemory->Load(0xFF44, m_iStatusModeLYCounter);
    115                    CompareLYToLYC();
    116
    117                    if (m_bCGB && m_pMemory->IsHDMAEnabled() && (!m_pProcessor->Halted() || m_pProcessor->InterruptIsAboutToRaise()))
    118                    {
    119                        unsigned int cycles = m_pMemory->PerformHDMA();
    120                        m_iStatusModeCounter += cycles;
    121                        clockCycles += cycles;
    122                    }
    123
    124                    if (m_iStatusModeLYCounter == 144)
    125                    {
    126                        m_iStatusMode = 1;
    127                        m_iStatusVBlankLine = 0;
    128                        m_iStatusModeCounterAux = m_iStatusModeCounter;
    129
    130                        m_pProcessor->RequestInterrupt(Processor::VBlank_Interrupt);
    131
    132                        m_IRQ48Signal &= 0x09;
    133                        u8 stat = m_pMemory->Retrieve(0xFF41);
    134                        if (IsSetBit(stat, 4))
    135                        {
    136                            if (!IsSetBit(m_IRQ48Signal, 0) && !IsSetBit(m_IRQ48Signal, 3))
    137                            {
    138                                m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    139                            }
    140                            m_IRQ48Signal = SetBit(m_IRQ48Signal, 1);
    141                        }
    142                        m_IRQ48Signal &= 0x0E;
    143
    144                        if (m_iHideFrames > 0)
    145                            m_iHideFrames--;
    146                        else
    147                            vblank = true;
    148
    149                        m_iWindowLine = 0;
    150                    }
    151                    else
    152                    {
    153                        m_IRQ48Signal &= 0x09;
    154                        u8 stat = m_pMemory->Retrieve(0xFF41);
    155                        if (IsSetBit(stat, 5))
    156                        {
    157                            if (m_IRQ48Signal == 0)
    158                            {
    159                                m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    160                            }
    161                            m_IRQ48Signal = SetBit(m_IRQ48Signal, 2);
    162                        }
    163                        m_IRQ48Signal &= 0x0E;
    164                    }
    165
    166                    UpdateStatRegister();
    167                }
    168                break;
    169            }
    170            // During V-BLANK
    171            case 1:
    172            {
    173                m_iStatusModeCounterAux += clockCycles;
    174
    175                if (m_iStatusModeCounterAux >= 456)
    176                {
    177                    m_iStatusModeCounterAux -= 456;
    178                    m_iStatusVBlankLine++;
    179
    180                    if (m_iStatusVBlankLine <= 9)
    181                    {
    182                        m_iStatusModeLYCounter++;
    183                        m_pMemory->Load(0xFF44, m_iStatusModeLYCounter);
    184                        CompareLYToLYC();
    185                    }
    186                }
    187
    188                if ((m_iStatusModeCounter >= 4104) && (m_iStatusModeCounterAux >= 4) && (m_iStatusModeLYCounter == 153))
    189                {
    190                    m_iStatusModeLYCounter = 0;
    191                    m_pMemory->Load(0xFF44, m_iStatusModeLYCounter);
    192                    CompareLYToLYC();
    193                }
    194
    195                if (m_iStatusModeCounter >= 4560)
    196                {
    197                    m_iStatusModeCounter -= 4560;
    198                    m_iStatusMode = 2;
    199                    UpdateStatRegister();
    200                    m_IRQ48Signal &= 0x07;
    201
    202
    203                    m_IRQ48Signal &= 0x0A;
    204                    u8 stat = m_pMemory->Retrieve(0xFF41);
    205                    if (IsSetBit(stat, 5))
    206                    {
    207                        if (m_IRQ48Signal == 0)
    208                        {
    209                            m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    210                        }
    211                        m_IRQ48Signal = SetBit(m_IRQ48Signal, 2);
    212                    }
    213                    m_IRQ48Signal &= 0x0D;
    214                }
    215                break;
    216            }
    217            // During searching OAM RAM
    218            case 2:
    219            {
    220                if (m_iStatusModeCounter >= 80)
    221                {
    222                    m_iStatusModeCounter -= 80;
    223                    m_iStatusMode = 3;
    224                    m_bScanLineTransfered = false;
    225                    m_IRQ48Signal &= 0x08;
    226                    UpdateStatRegister();
    227                }
    228                break;
    229            }
    230            // During transfering data to LCD driver
    231            case 3:
    232            {
    233#ifndef PERFORMANCE
    234                if (m_iPixelCounter < 160)
    235                {
    236                    m_iTileCycleCounter += clockCycles;
    237                    u8 lcdc = m_pMemory->Retrieve(0xFF40);
    238
    239                    if (m_bScreenEnabled && IsSetBit(lcdc, 7))
    240                    {
    241                        while (m_iTileCycleCounter >= 3)
    242                        {
    243                            if (IsValidPointer(m_pColorFrameBuffer))
    244                            {
    245                                RenderBG(m_iStatusModeLYCounter, m_iPixelCounter);
    246                            }
    247                            m_iPixelCounter += 4;
    248                            m_iTileCycleCounter -= 3;
    249
    250                            if (m_iPixelCounter >= 160)
    251                            {
    252                                break;
    253                            }
    254                        }
    255                    }
    256                }
    257#endif
    258
    259                if (m_iStatusModeCounter >= 160 && !m_bScanLineTransfered)
    260                {
    261                    ScanLine(m_iStatusModeLYCounter);
    262                    m_bScanLineTransfered = true;
    263                }
    264
    265                if (m_iStatusModeCounter >= 172)
    266                {
    267                    m_iPixelCounter = 0;
    268                    m_iStatusModeCounter -= 172;
    269                    m_iStatusMode = 0;
    270                    m_iTileCycleCounter = 0;
    271                    UpdateStatRegister();
    272
    273                    m_IRQ48Signal &= 0x08;
    274                    u8 stat = m_pMemory->Retrieve(0xFF41);
    275                    if (IsSetBit(stat, 3))
    276                    {
    277                        if (!IsSetBit(m_IRQ48Signal, 3))
    278                        {
    279                            m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    280                        }
    281                        m_IRQ48Signal = SetBit(m_IRQ48Signal, 0);
    282                    }
    283                }
    284                break;
    285            }
    286        }
    287    }
    288    // Screen disabled
    289    else
    290    {
    291        if (m_iScreenEnableDelayCycles > 0)
    292        {
    293            m_iScreenEnableDelayCycles -= clockCycles;
    294
    295            if (m_iScreenEnableDelayCycles <= 0)
    296            {
    297                m_iScreenEnableDelayCycles = 0;
    298                m_bScreenEnabled = true;
    299                m_iHideFrames = 3;
    300                m_iStatusMode = 0;
    301                m_iStatusModeCounter = 0;
    302                m_iStatusModeCounterAux = 0;
    303                m_iStatusModeLYCounter = 0;
    304                m_iWindowLine = 0;
    305                m_iStatusVBlankLine = 0;
    306                m_iPixelCounter = 0;
    307                m_iTileCycleCounter = 0;
    308                m_pMemory->Load(0xFF44, m_iStatusModeLYCounter);
    309                m_IRQ48Signal = 0;
    310
    311                u8 stat = m_pMemory->Retrieve(0xFF41);
    312                if (IsSetBit(stat, 5))
    313                {
    314                    m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    315                    m_IRQ48Signal = SetBit(m_IRQ48Signal, 2);
    316                }
    317
    318                CompareLYToLYC();
    319            }
    320        }
    321        else if (m_iStatusModeCounter >= 70224)
    322        {
    323            m_iStatusModeCounter -= 70224;
    324            vblank = true;
    325        }
    326    }
    327    return vblank;
    328}
    329
    330void Video::EnableScreen()
    331{
    332    if (!m_bScreenEnabled)
    333    {
    334        m_iScreenEnableDelayCycles = 244;
    335    }
    336}
    337
    338void Video::DisableScreen()
    339{
    340    m_bScreenEnabled = false;
    341    m_pMemory->Load(0xFF44, 0x00);
    342    u8 stat = m_pMemory->Retrieve(0xFF41);
    343    stat &= 0x7C;
    344    m_pMemory->Load(0xFF41, stat);
    345    m_iStatusMode = 0;
    346    m_iStatusModeCounter = 0;
    347    m_iStatusModeCounterAux = 0;
    348    m_iStatusModeLYCounter = 0;
    349    m_IRQ48Signal = 0;
    350}
    351
    352bool Video::IsScreenEnabled() const
    353{
    354    return m_bScreenEnabled;
    355}
    356
    357const u8* Video::GetFrameBuffer() const
    358{
    359    return m_pFrameBuffer;
    360}
    361
    362void Video::UpdatePaletteToSpecification(bool background, u8 value)
    363{
    364    bool hl = IsSetBit(value, 0);
    365    int index = (value >> 1) & 0x03;
    366    int pal = (value >> 3) & 0x07;
    367
    368    u16 color = (background ? m_CGBBackgroundPalettes[pal][index][0] : m_CGBSpritePalettes[pal][index][0]);
    369
    370    m_pMemory->Load(background ? 0xFF69 : 0xFF6B, hl ? (color >> 8) & 0xFF : color & 0xFF);
    371}
    372
    373void Video::SetColorPalette(bool background, u8 value)
    374{
    375    u8 ps = background ? m_pMemory->Retrieve(0xFF68) : m_pMemory->Retrieve(0xFF6A);
    376    bool hl = IsSetBit(ps, 0);
    377    int index = (ps >> 1) & 0x03;
    378    int pal = (ps >> 3) & 0x07;
    379    bool increment = IsSetBit(ps, 7);
    380
    381    if (increment)
    382    {
    383        u8 address = ps & 0x3F;
    384        address++;
    385        address &= 0x3F;
    386        ps = (ps & 0x80) | address;
    387        m_pMemory->Load(background ? 0xFF68 : 0xFF6A, ps);
    388        UpdatePaletteToSpecification(background, ps);
    389    }
    390
    391    u16* palette_color_gbc = background ? &m_CGBBackgroundPalettes[pal][index][0] : &m_CGBSpritePalettes[pal][index][0];
    392    u16* palette_color_final = background ? &m_CGBBackgroundPalettes[pal][index][1] : &m_CGBSpritePalettes[pal][index][1];
    393
    394    *palette_color_gbc = hl ? (*palette_color_gbc & 0x00FF) | (value << 8) : (*palette_color_gbc & 0xFF00) | value;
    395    
    396    u8 red_5bit = *palette_color_gbc & 0x1F;
    397    u8 blue_5bit = (*palette_color_gbc >> 10) & 0x1F;
    398
    399    switch (m_pixelFormat)
    400    {
    401        case GB_PIXEL_RGB565:
    402        {
    403            u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E;
    404            *palette_color_final = (red_5bit << 11) | (green_6bit << 5) | blue_5bit;
    405            break;
    406        }
    407        case GB_PIXEL_BGR565:
    408        {
    409            u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E;
    410            *palette_color_final = (blue_5bit << 11) | (green_6bit << 5) | red_5bit;
    411            break;
    412        }
    413        case GB_PIXEL_RGB555:
    414        {
    415            u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F;
    416            *palette_color_final = 0x8000 | (red_5bit << 10) | (green_5bit << 5) | blue_5bit;
    417            break;
    418        }
    419        case GB_PIXEL_BGR555:
    420        {
    421            u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F;
    422            *palette_color_final = 0x8000 | (blue_5bit << 10) | (green_5bit << 5) | red_5bit;
    423            break;
    424        }
    425    }
    426
    427}
    428
    429int Video::GetCurrentStatusMode() const
    430{
    431    return m_iStatusMode;
    432}
    433
    434void Video::ResetWindowLine()
    435{
    436    u8 wy = m_pMemory->Retrieve(0xFF4A);
    437
    438    if ((m_iWindowLine == 0) && (m_iStatusModeLYCounter < 144) && (m_iStatusModeLYCounter > wy))
    439        m_iWindowLine = 144;
    440}
    441
    442void Video::ScanLine(int line)
    443{
    444    if (IsValidPointer(m_pColorFrameBuffer))
    445    {
    446        u8 lcdc = m_pMemory->Retrieve(0xFF40);
    447
    448        if (m_bScreenEnabled && IsSetBit(lcdc, 7))
    449        {
    450#ifdef PERFORMANCE
    451            RenderBG(line, 0);
    452#endif
    453            RenderWindow(line);
    454            RenderSprites(line);
    455        }
    456        else
    457        {
    458            int line_width = (line * GAMEBOY_WIDTH);
    459            if (m_bCGB)
    460            {
    461                for (int x = 0; x < GAMEBOY_WIDTH; x++)
    462                    m_pColorFrameBuffer[line_width + x] = 0x8000;
    463            }
    464            else
    465            {
    466                for (int x = 0; x < GAMEBOY_WIDTH; x++)
    467                    m_pFrameBuffer[line_width + x] = 0;
    468            }
    469        }
    470    }
    471}
    472
    473void Video::RenderBG(int line, int pixel)
    474{
    475    u8 lcdc = m_pMemory->Retrieve(0xFF40);
    476    int line_width = (line * GAMEBOY_WIDTH);
    477    
    478    if (m_bCGB || IsSetBit(lcdc, 0))
    479    {
    480#ifdef PERFORMANCE
    481        int pixels_to_render = 160;
    482#else
    483        int pixels_to_render = 4;
    484#endif
    485        int offset_x_init = pixel & 0x7;
    486        int offset_x_end = offset_x_init + pixels_to_render;
    487        int screen_tile = pixel >> 3;
    488        int tile_start_addr = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800;
    489        int map_start_addr = IsSetBit(lcdc, 3) ? 0x9C00 : 0x9800;
    490        u8 scroll_x = m_pMemory->Retrieve(0xFF43);
    491        u8 scroll_y = m_pMemory->Retrieve(0xFF42);
    492        u8 line_scrolled = line + scroll_y;
    493        int line_scrolled_32 = (line_scrolled >> 3) << 5;
    494        int tile_pixel_y = line_scrolled & 0x7;
    495        int tile_pixel_y_2 = tile_pixel_y << 1;
    496        int tile_pixel_y_flip_2 = (7 - tile_pixel_y) << 1;
    497        u8 palette = m_pMemory->Retrieve(0xFF47);
    498
    499        for (int offset_x = offset_x_init; offset_x < offset_x_end; offset_x++)
    500        {
    501            int screen_pixel_x = (screen_tile << 3) + offset_x;
    502            u8 map_pixel_x = screen_pixel_x + scroll_x;
    503            int map_tile_x = map_pixel_x >> 3;
    504            int map_tile_offset_x = map_pixel_x & 0x7;
    505            u16 map_tile_addr = map_start_addr + line_scrolled_32 + map_tile_x;
    506            int map_tile = 0;
    507
    508            if (tile_start_addr == 0x8800)
    509            {
    510                map_tile = static_cast<s8> (m_pMemory->Retrieve(map_tile_addr));
    511                map_tile += 128;
    512            }
    513            else
    514            {
    515                map_tile = m_pMemory->Retrieve(map_tile_addr);
    516            }
    517
    518            u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map_tile_addr, true) : 0;
    519            u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0;
    520            bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false;
    521            bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false;
    522            bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false;
    523            int map_tile_16 = map_tile << 4;
    524            u8 byte1 = 0;
    525            u8 byte2 = 0;
    526            int final_pixely_2 = cgb_tile_yflip ? tile_pixel_y_flip_2 : tile_pixel_y_2;
    527            int tile_address = tile_start_addr + map_tile_16 + final_pixely_2;
    528
    529            if (cgb_tile_bank)
    530            {
    531                byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true);
    532                byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true);
    533            }
    534            else
    535            {
    536                byte1 = m_pMemory->Retrieve(tile_address);
    537                byte2 = m_pMemory->Retrieve(tile_address + 1);
    538            }
    539
    540            int pixel_x_in_tile = map_tile_offset_x;
    541
    542            if (cgb_tile_xflip)
    543            {
    544                pixel_x_in_tile = 7 - pixel_x_in_tile;
    545            }
    546            int pixel_x_in_tile_bit = 0x1 << (7 - pixel_x_in_tile);
    547            int pixel_data = (byte1 & pixel_x_in_tile_bit) ? 1 : 0;
    548            pixel_data |= (byte2 & pixel_x_in_tile_bit) ? 2 : 0;
    549
    550            int index = line_width + screen_pixel_x;
    551            m_pColorCacheBuffer[index] = pixel_data & 0x03;
    552
    553            if (m_bCGB)
    554            {
    555                bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0);
    556                if (cgb_tile_priority && (pixel_data != 0))
    557                    m_pColorCacheBuffer[index] = SetBit(m_pColorCacheBuffer[index], 2);
    558                m_pColorFrameBuffer[index] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel_data][1];
    559            }
    560            else
    561            {
    562                u8 color = (palette >> (pixel_data << 1)) & 0x03;
    563                m_pColorFrameBuffer[index] = m_pFrameBuffer[index] = color;
    564            }
    565        }
    566    }
    567    else
    568    {
    569        for (int x = 0; x < 4; x++)
    570        {
    571            int position = line_width + pixel + x;
    572            m_pFrameBuffer[position] = 0;
    573            m_pColorCacheBuffer[position] = 0;
    574        }
    575    }
    576}
    577
    578void Video::RenderWindow(int line)
    579{
    580    if (m_iWindowLine > 143)
    581        return;
    582
    583    u8 lcdc = m_pMemory->Retrieve(0xFF40);
    584    if (!IsSetBit(lcdc, 5))
    585        return;
    586
    587    int wx = m_pMemory->Retrieve(0xFF4B) - 7;
    588    if (wx > 159)
    589        return;
    590
    591    u8 wy = m_pMemory->Retrieve(0xFF4A);
    592    if ((wy > 143) || (wy > line))
    593        return;
    594
    595    int tiles = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800;
    596    int map = IsSetBit(lcdc, 6) ? 0x9C00 : 0x9800;
    597    int lineAdjusted = m_iWindowLine;
    598    int y_32 = (lineAdjusted >> 3) << 5;
    599    int pixely = lineAdjusted & 0x7;
    600    int pixely_2 = pixely << 1;
    601    int pixely_2_flip = (7 - pixely) << 1;
    602    int line_width = (line * GAMEBOY_WIDTH);
    603    u8 palette = m_pMemory->Retrieve(0xFF47);
    604
    605    for (int x = 0; x < 32; x++)
    606    {
    607        int tile = 0;
    608
    609        if (tiles == 0x8800)
    610        {
    611            tile = static_cast<s8> (m_pMemory->Retrieve(map + y_32 + x));
    612            tile += 128;
    613        }
    614        else
    615        {
    616            tile = m_pMemory->Retrieve(map + y_32 + x);
    617        }
    618
    619        u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map + y_32 + x, true) : 0;
    620        u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0;
    621        bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false;
    622        bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false;
    623        bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false;
    624        int mapOffsetX = x << 3;
    625        int tile_16 = tile << 4;
    626        u8 byte1 = 0;
    627        u8 byte2 = 0;
    628        int final_pixely_2 = (m_bCGB && cgb_tile_yflip) ? pixely_2_flip : pixely_2;
    629        int tile_address = tiles + tile_16 + final_pixely_2;
    630
    631        if (m_bCGB && cgb_tile_bank)
    632        {
    633            byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true);
    634            byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true);
    635        }
    636        else
    637        {
    638            byte1 = m_pMemory->Retrieve(tile_address);
    639            byte2 = m_pMemory->Retrieve(tile_address + 1);
    640        }
    641
    642        for (int pixelx = 0; pixelx < 8; pixelx++)
    643        {
    644            int bufferX = (mapOffsetX + pixelx + wx);
    645
    646            if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH)
    647                continue;
    648
    649            int pixelx_pos = pixelx;
    650
    651            if (m_bCGB && cgb_tile_xflip)
    652            {
    653                pixelx_pos = 7 - pixelx_pos;
    654            }
    655
    656            int pixel = (byte1 & (0x1 << (7 - pixelx_pos))) ? 1 : 0;
    657            pixel |= (byte2 & (0x1 << (7 - pixelx_pos))) ? 2 : 0;
    658
    659            int position = line_width + bufferX;
    660            m_pColorCacheBuffer[position] = pixel & 0x03;
    661
    662            if (m_bCGB)
    663            {
    664                bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0);
    665                if (cgb_tile_priority && (pixel != 0))
    666                    m_pColorCacheBuffer[position] = SetBit(m_pColorCacheBuffer[position], 2);
    667                 m_pColorFrameBuffer[position] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel][1];
    668            }
    669            else
    670            {
    671                u8 color = (palette >> (pixel << 1)) & 0x03;
    672                m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color;
    673            }
    674        }
    675    }
    676    m_iWindowLine++;
    677}
    678
    679void Video::RenderSprites(int line)
    680{
    681    u8 lcdc = m_pMemory->Retrieve(0xFF40);
    682
    683    if (!IsSetBit(lcdc, 1))
    684        return;
    685
    686    int sprite_height = IsSetBit(lcdc, 2) ? 16 : 8;
    687    int line_width = (line * GAMEBOY_WIDTH);
    688
    689    bool visible_sprites[40];
    690    int sprite_limit = 0;
    691
    692    for (int sprite = 0; sprite < 40; sprite++)
    693    {
    694        int sprite_4 = sprite << 2;
    695        int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16;
    696
    697        if ((sprite_y > line) || ((sprite_y + sprite_height) <= line))
    698        {
    699            visible_sprites[sprite] = false;
    700            continue;
    701        }
    702
    703        sprite_limit++;
    704        
    705        visible_sprites[sprite] = sprite_limit <= 10;
    706    }
    707
    708    for (int sprite = 39; sprite >= 0; sprite--)
    709    {
    710        if (!visible_sprites[sprite])
    711            continue;
    712
    713        int sprite_4 = sprite << 2;
    714        int sprite_x = m_pMemory->Retrieve(0xFE00 + sprite_4 + 1) - 8;
    715
    716        if ((sprite_x < -7) || (sprite_x >= GAMEBOY_WIDTH))
    717            continue;
    718
    719        int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16;
    720        int sprite_tile_16 = (m_pMemory->Retrieve(0xFE00 + sprite_4 + 2)
    721                & ((sprite_height == 16) ? 0xFE : 0xFF)) << 4;
    722        u8 sprite_flags = m_pMemory->Retrieve(0xFE00 + sprite_4 + 3);
    723        int sprite_pallette = IsSetBit(sprite_flags, 4) ? 1 : 0;
    724        u8 palette = m_pMemory->Retrieve(sprite_pallette ? 0xFF49 : 0xFF48);
    725        bool xflip = IsSetBit(sprite_flags, 5);
    726        bool yflip = IsSetBit(sprite_flags, 6);
    727        bool aboveBG = (!IsSetBit(sprite_flags, 7));
    728        bool cgb_tile_bank = IsSetBit(sprite_flags, 3);
    729        int cgb_tile_pal = sprite_flags & 0x07;
    730        int tiles = 0x8000;
    731        int pixel_y = yflip ? ((sprite_height == 16) ? 15 : 7) - (line - sprite_y) : line - sprite_y;
    732        u8 byte1 = 0;
    733        u8 byte2 = 0;
    734        int pixel_y_2 = 0;
    735        int offset = 0;
    736
    737        if (sprite_height == 16 && (pixel_y >= 8))
    738        {
    739            pixel_y_2 = (pixel_y - 8) << 1;
    740            offset = 16;
    741        }
    742        else
    743            pixel_y_2 = pixel_y << 1;
    744
    745        int tile_address = tiles + sprite_tile_16 + pixel_y_2 + offset;
    746
    747        if (m_bCGB && cgb_tile_bank)
    748        {
    749            byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true);
    750            byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true);
    751        }
    752        else
    753        {
    754            byte1 = m_pMemory->Retrieve(tile_address);
    755            byte2 = m_pMemory->Retrieve(tile_address + 1);
    756        }
    757
    758        for (int pixelx = 0; pixelx < 8; pixelx++)
    759        {
    760            int pixel = (byte1 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 1 : 0;
    761            pixel |= (byte2 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 2 : 0;
    762
    763            if (pixel == 0)
    764                continue;
    765
    766            int bufferX = (sprite_x + pixelx);
    767
    768            if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH)
    769                continue;
    770
    771            int position = line_width + bufferX;
    772            u8 color_cache = m_pColorCacheBuffer[position];
    773
    774            if (m_bCGB)
    775            {
    776                if (IsSetBit(color_cache, 2))
    777                    continue;
    778            }
    779            else
    780            {
    781                int sprite_x_cache = m_pSpriteXCacheBuffer[position];
    782                if (IsSetBit(color_cache, 3) && (sprite_x_cache < sprite_x))
    783                    continue;
    784            }
    785
    786            if (!aboveBG && (color_cache & 0x03))
    787                continue;
    788
    789            m_pColorCacheBuffer[position] = SetBit(color_cache, 3);
    790            m_pSpriteXCacheBuffer[position] = sprite_x;
    791            if (m_bCGB)
    792            {
    793                m_pColorFrameBuffer[position] = m_CGBSpritePalettes[cgb_tile_pal][pixel][1];
    794            }
    795            else
    796            {
    797                u8 color = (palette >> (pixel << 1)) & 0x03;
    798                m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color;
    799            }
    800        }
    801    }
    802}
    803
    804void Video::UpdateStatRegister()
    805{
    806    // Updates the STAT register with current mode
    807    u8 stat = m_pMemory->Retrieve(0xFF41);
    808    m_pMemory->Load(0xFF41, (stat & 0xFC) | (m_iStatusMode & 0x3));
    809}
    810
    811void Video::CompareLYToLYC()
    812{
    813    if (m_bScreenEnabled)
    814    {
    815        u8 lyc = m_pMemory->Retrieve(0xFF45);
    816        u8 stat = m_pMemory->Retrieve(0xFF41);
    817
    818        if (lyc == m_iStatusModeLYCounter)
    819        {
    820            stat = SetBit(stat, 2);
    821            if (IsSetBit(stat, 6))
    822            {
    823                if (m_IRQ48Signal == 0)
    824                {
    825                    m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt);
    826                }
    827                m_IRQ48Signal = SetBit(m_IRQ48Signal, 3);
    828            }
    829        }
    830        else
    831        {
    832            stat = UnsetBit(stat, 2);
    833            m_IRQ48Signal = UnsetBit(m_IRQ48Signal, 3);
    834        }
    835
    836        m_pMemory->Load(0xFF41, stat);
    837    }
    838}
    839
    840u8 Video::GetIRQ48Signal() const
    841{
    842    return m_IRQ48Signal;
    843}
    844
    845void Video::SetIRQ48Signal(u8 signal)
    846{
    847    m_IRQ48Signal = signal;
    848}
    849
    850void Video::SaveState(std::ostream& stream)
    851{
    852    using namespace std;
    853
    854    stream.write(reinterpret_cast<const char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    855    stream.write(reinterpret_cast<const char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    856    stream.write(reinterpret_cast<const char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    857    stream.write(reinterpret_cast<const char*> (&m_iStatusMode), sizeof(m_iStatusMode));
    858    stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter));
    859    stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux));
    860    stream.write(reinterpret_cast<const char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter));
    861    stream.write(reinterpret_cast<const char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles));
    862    stream.write(reinterpret_cast<const char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine));
    863    stream.write(reinterpret_cast<const char*> (&m_iPixelCounter), sizeof(m_iPixelCounter));
    864    stream.write(reinterpret_cast<const char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter));
    865    stream.write(reinterpret_cast<const char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled));
    866    stream.write(reinterpret_cast<const char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes));
    867    stream.write(reinterpret_cast<const char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes));
    868    stream.write(reinterpret_cast<const char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered));
    869    stream.write(reinterpret_cast<const char*> (&m_iWindowLine), sizeof(m_iWindowLine));
    870    stream.write(reinterpret_cast<const char*> (&m_iHideFrames), sizeof(m_iHideFrames));
    871    stream.write(reinterpret_cast<const char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal));
    872}
    873
    874void Video::LoadState(std::istream& stream)
    875{
    876    using namespace std;
    877
    878    stream.read(reinterpret_cast<char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    879    stream.read(reinterpret_cast<char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    880    stream.read(reinterpret_cast<char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT);
    881    stream.read(reinterpret_cast<char*> (&m_iStatusMode), sizeof(m_iStatusMode));
    882    stream.read(reinterpret_cast<char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter));
    883    stream.read(reinterpret_cast<char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux));
    884    stream.read(reinterpret_cast<char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter));
    885    stream.read(reinterpret_cast<char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles));
    886    stream.read(reinterpret_cast<char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine));
    887    stream.read(reinterpret_cast<char*> (&m_iPixelCounter), sizeof(m_iPixelCounter));
    888    stream.read(reinterpret_cast<char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter));
    889    stream.read(reinterpret_cast<char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled));
    890    stream.read(reinterpret_cast<char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes));
    891    stream.read(reinterpret_cast<char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes));
    892    stream.read(reinterpret_cast<char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered));
    893    stream.read(reinterpret_cast<char*> (&m_iWindowLine), sizeof(m_iWindowLine));
    894    stream.read(reinterpret_cast<char*> (&m_iHideFrames), sizeof(m_iHideFrames));
    895    stream.read(reinterpret_cast<char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal));
    896}
    897
    898PaletteMatrix Video::GetCGBBackgroundPalettes()
    899{
    900    return &m_CGBBackgroundPalettes;
    901}
    902
    903PaletteMatrix Video::GetCGBSpritePalettes()
    904{
    905    return &m_CGBSpritePalettes;
    906}