diff options
| author | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
|---|---|---|
| committer | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
| commit | 5bc16063c29aa4d3d287ebd163ccdbcbf54c4f9f (patch) | |
| tree | c131f947a37b3af2d14d41e9eda098bdec2d061c /gearboy/src/Video.cpp | |
| parent | 78a5f810b22f0d8cafa05f638b0cb2e889824859 (diff) | |
| download | cscg2022-gearboy-master.tar.gz cscg2022-gearboy-master.zip | |
Diffstat (limited to 'gearboy/src/Video.cpp')
| -rw-r--r-- | gearboy/src/Video.cpp | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/gearboy/src/Video.cpp b/gearboy/src/Video.cpp new file mode 100644 index 00000000..6ddb466f --- /dev/null +++ b/gearboy/src/Video.cpp @@ -0,0 +1,906 @@ +/* + * Gearboy - Nintendo Game Boy Emulator + * Copyright (C) 2012 Ignacio Sanchez + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/ + * + */ + +#include "Video.h" +#include "Memory.h" +#include "Processor.h" + +Video::Video(Memory* pMemory, Processor* pProcessor) +{ + m_pMemory = pMemory; + m_pMemory->SetVideo(this); + m_pProcessor = pProcessor; + InitPointer(m_pFrameBuffer); + InitPointer(m_pColorFrameBuffer); + InitPointer(m_pSpriteXCacheBuffer); + InitPointer(m_pColorCacheBuffer); + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_iScreenEnableDelayCycles = 0; + m_iStatusVBlankLine = 0; + m_iWindowLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_bScreenEnabled = true; + m_bCGB = false; + m_bScanLineTransfered = false; + m_iHideFrames = 0; + m_IRQ48Signal = 0; + m_pixelFormat = GB_PIXEL_RGB565; +} + +Video::~Video() +{ + SafeDeleteArray(m_pSpriteXCacheBuffer); + SafeDeleteArray(m_pColorCacheBuffer); + SafeDeleteArray(m_pFrameBuffer); +} + +void Video::Init() +{ + m_pFrameBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + m_pSpriteXCacheBuffer = new int[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + m_pColorCacheBuffer = new u8[GAMEBOY_WIDTH * GAMEBOY_HEIGHT]; + Reset(false); +} + +void Video::Reset(bool bCGB) +{ + for (int i = 0; i < (GAMEBOY_WIDTH * GAMEBOY_HEIGHT); i++) + m_pSpriteXCacheBuffer[i] = m_pFrameBuffer[i] = m_pColorCacheBuffer[i] = 0; + + for (int p = 0; p < 8; p++) + for (int c = 0; c < 4; c++) + { + m_CGBBackgroundPalettes[p][c][0] = m_CGBSpritePalettes[p][c][0] = 0x0000; + m_CGBBackgroundPalettes[p][c][1] = m_CGBSpritePalettes[p][c][1] = 0x0000; + } + + m_iStatusMode = 1; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 144; + m_iScreenEnableDelayCycles = 0; + m_iStatusVBlankLine = 0; + m_iWindowLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_bScreenEnabled = true; + m_bScanLineTransfered = false; + m_bCGB = bCGB; + m_iHideFrames = 0; + m_IRQ48Signal = 0; +} + +bool Video::Tick(unsigned int &clockCycles, u16* pColorFrameBuffer, GB_Color_Format pixelFormat) +{ + m_pColorFrameBuffer = pColorFrameBuffer; + m_pixelFormat = pixelFormat; + + bool vblank = false; + m_iStatusModeCounter += clockCycles; + + if (m_bScreenEnabled) + { + switch (m_iStatusMode) + { + // During H-BLANK + case 0: + { + if (m_iStatusModeCounter >= 204) + { + m_iStatusModeCounter -= 204; + m_iStatusMode = 2; + + m_iStatusModeLYCounter++; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + + if (m_bCGB && m_pMemory->IsHDMAEnabled() && (!m_pProcessor->Halted() || m_pProcessor->InterruptIsAboutToRaise())) + { + unsigned int cycles = m_pMemory->PerformHDMA(); + m_iStatusModeCounter += cycles; + clockCycles += cycles; + } + + if (m_iStatusModeLYCounter == 144) + { + m_iStatusMode = 1; + m_iStatusVBlankLine = 0; + m_iStatusModeCounterAux = m_iStatusModeCounter; + + m_pProcessor->RequestInterrupt(Processor::VBlank_Interrupt); + + m_IRQ48Signal &= 0x09; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 4)) + { + if (!IsSetBit(m_IRQ48Signal, 0) && !IsSetBit(m_IRQ48Signal, 3)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 1); + } + m_IRQ48Signal &= 0x0E; + + if (m_iHideFrames > 0) + m_iHideFrames--; + else + vblank = true; + + m_iWindowLine = 0; + } + else + { + m_IRQ48Signal &= 0x09; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + m_IRQ48Signal &= 0x0E; + } + + UpdateStatRegister(); + } + break; + } + // During V-BLANK + case 1: + { + m_iStatusModeCounterAux += clockCycles; + + if (m_iStatusModeCounterAux >= 456) + { + m_iStatusModeCounterAux -= 456; + m_iStatusVBlankLine++; + + if (m_iStatusVBlankLine <= 9) + { + m_iStatusModeLYCounter++; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + } + } + + if ((m_iStatusModeCounter >= 4104) && (m_iStatusModeCounterAux >= 4) && (m_iStatusModeLYCounter == 153)) + { + m_iStatusModeLYCounter = 0; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + CompareLYToLYC(); + } + + if (m_iStatusModeCounter >= 4560) + { + m_iStatusModeCounter -= 4560; + m_iStatusMode = 2; + UpdateStatRegister(); + m_IRQ48Signal &= 0x07; + + + m_IRQ48Signal &= 0x0A; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + m_IRQ48Signal &= 0x0D; + } + break; + } + // During searching OAM RAM + case 2: + { + if (m_iStatusModeCounter >= 80) + { + m_iStatusModeCounter -= 80; + m_iStatusMode = 3; + m_bScanLineTransfered = false; + m_IRQ48Signal &= 0x08; + UpdateStatRegister(); + } + break; + } + // During transfering data to LCD driver + case 3: + { +#ifndef PERFORMANCE + if (m_iPixelCounter < 160) + { + m_iTileCycleCounter += clockCycles; + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (m_bScreenEnabled && IsSetBit(lcdc, 7)) + { + while (m_iTileCycleCounter >= 3) + { + if (IsValidPointer(m_pColorFrameBuffer)) + { + RenderBG(m_iStatusModeLYCounter, m_iPixelCounter); + } + m_iPixelCounter += 4; + m_iTileCycleCounter -= 3; + + if (m_iPixelCounter >= 160) + { + break; + } + } + } + } +#endif + + if (m_iStatusModeCounter >= 160 && !m_bScanLineTransfered) + { + ScanLine(m_iStatusModeLYCounter); + m_bScanLineTransfered = true; + } + + if (m_iStatusModeCounter >= 172) + { + m_iPixelCounter = 0; + m_iStatusModeCounter -= 172; + m_iStatusMode = 0; + m_iTileCycleCounter = 0; + UpdateStatRegister(); + + m_IRQ48Signal &= 0x08; + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 3)) + { + if (!IsSetBit(m_IRQ48Signal, 3)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 0); + } + } + break; + } + } + } + // Screen disabled + else + { + if (m_iScreenEnableDelayCycles > 0) + { + m_iScreenEnableDelayCycles -= clockCycles; + + if (m_iScreenEnableDelayCycles <= 0) + { + m_iScreenEnableDelayCycles = 0; + m_bScreenEnabled = true; + m_iHideFrames = 3; + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_iWindowLine = 0; + m_iStatusVBlankLine = 0; + m_iPixelCounter = 0; + m_iTileCycleCounter = 0; + m_pMemory->Load(0xFF44, m_iStatusModeLYCounter); + m_IRQ48Signal = 0; + + u8 stat = m_pMemory->Retrieve(0xFF41); + if (IsSetBit(stat, 5)) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + m_IRQ48Signal = SetBit(m_IRQ48Signal, 2); + } + + CompareLYToLYC(); + } + } + else if (m_iStatusModeCounter >= 70224) + { + m_iStatusModeCounter -= 70224; + vblank = true; + } + } + return vblank; +} + +void Video::EnableScreen() +{ + if (!m_bScreenEnabled) + { + m_iScreenEnableDelayCycles = 244; + } +} + +void Video::DisableScreen() +{ + m_bScreenEnabled = false; + m_pMemory->Load(0xFF44, 0x00); + u8 stat = m_pMemory->Retrieve(0xFF41); + stat &= 0x7C; + m_pMemory->Load(0xFF41, stat); + m_iStatusMode = 0; + m_iStatusModeCounter = 0; + m_iStatusModeCounterAux = 0; + m_iStatusModeLYCounter = 0; + m_IRQ48Signal = 0; +} + +bool Video::IsScreenEnabled() const +{ + return m_bScreenEnabled; +} + +const u8* Video::GetFrameBuffer() const +{ + return m_pFrameBuffer; +} + +void Video::UpdatePaletteToSpecification(bool background, u8 value) +{ + bool hl = IsSetBit(value, 0); + int index = (value >> 1) & 0x03; + int pal = (value >> 3) & 0x07; + + u16 color = (background ? m_CGBBackgroundPalettes[pal][index][0] : m_CGBSpritePalettes[pal][index][0]); + + m_pMemory->Load(background ? 0xFF69 : 0xFF6B, hl ? (color >> 8) & 0xFF : color & 0xFF); +} + +void Video::SetColorPalette(bool background, u8 value) +{ + u8 ps = background ? m_pMemory->Retrieve(0xFF68) : m_pMemory->Retrieve(0xFF6A); + bool hl = IsSetBit(ps, 0); + int index = (ps >> 1) & 0x03; + int pal = (ps >> 3) & 0x07; + bool increment = IsSetBit(ps, 7); + + if (increment) + { + u8 address = ps & 0x3F; + address++; + address &= 0x3F; + ps = (ps & 0x80) | address; + m_pMemory->Load(background ? 0xFF68 : 0xFF6A, ps); + UpdatePaletteToSpecification(background, ps); + } + + u16* palette_color_gbc = background ? &m_CGBBackgroundPalettes[pal][index][0] : &m_CGBSpritePalettes[pal][index][0]; + u16* palette_color_final = background ? &m_CGBBackgroundPalettes[pal][index][1] : &m_CGBSpritePalettes[pal][index][1]; + + *palette_color_gbc = hl ? (*palette_color_gbc & 0x00FF) | (value << 8) : (*palette_color_gbc & 0xFF00) | value; + + u8 red_5bit = *palette_color_gbc & 0x1F; + u8 blue_5bit = (*palette_color_gbc >> 10) & 0x1F; + + switch (m_pixelFormat) + { + case GB_PIXEL_RGB565: + { + u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E; + *palette_color_final = (red_5bit << 11) | (green_6bit << 5) | blue_5bit; + break; + } + case GB_PIXEL_BGR565: + { + u8 green_6bit = (*palette_color_gbc >> 4) & 0x3E; + *palette_color_final = (blue_5bit << 11) | (green_6bit << 5) | red_5bit; + break; + } + case GB_PIXEL_RGB555: + { + u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F; + *palette_color_final = 0x8000 | (red_5bit << 10) | (green_5bit << 5) | blue_5bit; + break; + } + case GB_PIXEL_BGR555: + { + u8 green_5bit = (*palette_color_gbc >> 5) & 0x1F; + *palette_color_final = 0x8000 | (blue_5bit << 10) | (green_5bit << 5) | red_5bit; + break; + } + } + +} + +int Video::GetCurrentStatusMode() const +{ + return m_iStatusMode; +} + +void Video::ResetWindowLine() +{ + u8 wy = m_pMemory->Retrieve(0xFF4A); + + if ((m_iWindowLine == 0) && (m_iStatusModeLYCounter < 144) && (m_iStatusModeLYCounter > wy)) + m_iWindowLine = 144; +} + +void Video::ScanLine(int line) +{ + if (IsValidPointer(m_pColorFrameBuffer)) + { + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (m_bScreenEnabled && IsSetBit(lcdc, 7)) + { +#ifdef PERFORMANCE + RenderBG(line, 0); +#endif + RenderWindow(line); + RenderSprites(line); + } + else + { + int line_width = (line * GAMEBOY_WIDTH); + if (m_bCGB) + { + for (int x = 0; x < GAMEBOY_WIDTH; x++) + m_pColorFrameBuffer[line_width + x] = 0x8000; + } + else + { + for (int x = 0; x < GAMEBOY_WIDTH; x++) + m_pFrameBuffer[line_width + x] = 0; + } + } + } +} + +void Video::RenderBG(int line, int pixel) +{ + u8 lcdc = m_pMemory->Retrieve(0xFF40); + int line_width = (line * GAMEBOY_WIDTH); + + if (m_bCGB || IsSetBit(lcdc, 0)) + { +#ifdef PERFORMANCE + int pixels_to_render = 160; +#else + int pixels_to_render = 4; +#endif + int offset_x_init = pixel & 0x7; + int offset_x_end = offset_x_init + pixels_to_render; + int screen_tile = pixel >> 3; + int tile_start_addr = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800; + int map_start_addr = IsSetBit(lcdc, 3) ? 0x9C00 : 0x9800; + u8 scroll_x = m_pMemory->Retrieve(0xFF43); + u8 scroll_y = m_pMemory->Retrieve(0xFF42); + u8 line_scrolled = line + scroll_y; + int line_scrolled_32 = (line_scrolled >> 3) << 5; + int tile_pixel_y = line_scrolled & 0x7; + int tile_pixel_y_2 = tile_pixel_y << 1; + int tile_pixel_y_flip_2 = (7 - tile_pixel_y) << 1; + u8 palette = m_pMemory->Retrieve(0xFF47); + + for (int offset_x = offset_x_init; offset_x < offset_x_end; offset_x++) + { + int screen_pixel_x = (screen_tile << 3) + offset_x; + u8 map_pixel_x = screen_pixel_x + scroll_x; + int map_tile_x = map_pixel_x >> 3; + int map_tile_offset_x = map_pixel_x & 0x7; + u16 map_tile_addr = map_start_addr + line_scrolled_32 + map_tile_x; + int map_tile = 0; + + if (tile_start_addr == 0x8800) + { + map_tile = static_cast<s8> (m_pMemory->Retrieve(map_tile_addr)); + map_tile += 128; + } + else + { + map_tile = m_pMemory->Retrieve(map_tile_addr); + } + + u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map_tile_addr, true) : 0; + u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0; + bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false; + bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false; + bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false; + int map_tile_16 = map_tile << 4; + u8 byte1 = 0; + u8 byte2 = 0; + int final_pixely_2 = cgb_tile_yflip ? tile_pixel_y_flip_2 : tile_pixel_y_2; + int tile_address = tile_start_addr + map_tile_16 + final_pixely_2; + + if (cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + int pixel_x_in_tile = map_tile_offset_x; + + if (cgb_tile_xflip) + { + pixel_x_in_tile = 7 - pixel_x_in_tile; + } + int pixel_x_in_tile_bit = 0x1 << (7 - pixel_x_in_tile); + int pixel_data = (byte1 & pixel_x_in_tile_bit) ? 1 : 0; + pixel_data |= (byte2 & pixel_x_in_tile_bit) ? 2 : 0; + + int index = line_width + screen_pixel_x; + m_pColorCacheBuffer[index] = pixel_data & 0x03; + + if (m_bCGB) + { + bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0); + if (cgb_tile_priority && (pixel_data != 0)) + m_pColorCacheBuffer[index] = SetBit(m_pColorCacheBuffer[index], 2); + m_pColorFrameBuffer[index] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel_data][1]; + } + else + { + u8 color = (palette >> (pixel_data << 1)) & 0x03; + m_pColorFrameBuffer[index] = m_pFrameBuffer[index] = color; + } + } + } + else + { + for (int x = 0; x < 4; x++) + { + int position = line_width + pixel + x; + m_pFrameBuffer[position] = 0; + m_pColorCacheBuffer[position] = 0; + } + } +} + +void Video::RenderWindow(int line) +{ + if (m_iWindowLine > 143) + return; + + u8 lcdc = m_pMemory->Retrieve(0xFF40); + if (!IsSetBit(lcdc, 5)) + return; + + int wx = m_pMemory->Retrieve(0xFF4B) - 7; + if (wx > 159) + return; + + u8 wy = m_pMemory->Retrieve(0xFF4A); + if ((wy > 143) || (wy > line)) + return; + + int tiles = IsSetBit(lcdc, 4) ? 0x8000 : 0x8800; + int map = IsSetBit(lcdc, 6) ? 0x9C00 : 0x9800; + int lineAdjusted = m_iWindowLine; + int y_32 = (lineAdjusted >> 3) << 5; + int pixely = lineAdjusted & 0x7; + int pixely_2 = pixely << 1; + int pixely_2_flip = (7 - pixely) << 1; + int line_width = (line * GAMEBOY_WIDTH); + u8 palette = m_pMemory->Retrieve(0xFF47); + + for (int x = 0; x < 32; x++) + { + int tile = 0; + + if (tiles == 0x8800) + { + tile = static_cast<s8> (m_pMemory->Retrieve(map + y_32 + x)); + tile += 128; + } + else + { + tile = m_pMemory->Retrieve(map + y_32 + x); + } + + u8 cgb_tile_attr = m_bCGB ? m_pMemory->ReadCGBLCDRAM(map + y_32 + x, true) : 0; + u8 cgb_tile_pal = m_bCGB ? (cgb_tile_attr & 0x07) : 0; + bool cgb_tile_bank = m_bCGB ? IsSetBit(cgb_tile_attr, 3) : false; + bool cgb_tile_xflip = m_bCGB ? IsSetBit(cgb_tile_attr, 5) : false; + bool cgb_tile_yflip = m_bCGB ? IsSetBit(cgb_tile_attr, 6) : false; + int mapOffsetX = x << 3; + int tile_16 = tile << 4; + u8 byte1 = 0; + u8 byte2 = 0; + int final_pixely_2 = (m_bCGB && cgb_tile_yflip) ? pixely_2_flip : pixely_2; + int tile_address = tiles + tile_16 + final_pixely_2; + + if (m_bCGB && cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + for (int pixelx = 0; pixelx < 8; pixelx++) + { + int bufferX = (mapOffsetX + pixelx + wx); + + if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH) + continue; + + int pixelx_pos = pixelx; + + if (m_bCGB && cgb_tile_xflip) + { + pixelx_pos = 7 - pixelx_pos; + } + + int pixel = (byte1 & (0x1 << (7 - pixelx_pos))) ? 1 : 0; + pixel |= (byte2 & (0x1 << (7 - pixelx_pos))) ? 2 : 0; + + int position = line_width + bufferX; + m_pColorCacheBuffer[position] = pixel & 0x03; + + if (m_bCGB) + { + bool cgb_tile_priority = IsSetBit(cgb_tile_attr, 7) && IsSetBit(lcdc, 0); + if (cgb_tile_priority && (pixel != 0)) + m_pColorCacheBuffer[position] = SetBit(m_pColorCacheBuffer[position], 2); + m_pColorFrameBuffer[position] = m_CGBBackgroundPalettes[cgb_tile_pal][pixel][1]; + } + else + { + u8 color = (palette >> (pixel << 1)) & 0x03; + m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color; + } + } + } + m_iWindowLine++; +} + +void Video::RenderSprites(int line) +{ + u8 lcdc = m_pMemory->Retrieve(0xFF40); + + if (!IsSetBit(lcdc, 1)) + return; + + int sprite_height = IsSetBit(lcdc, 2) ? 16 : 8; + int line_width = (line * GAMEBOY_WIDTH); + + bool visible_sprites[40]; + int sprite_limit = 0; + + for (int sprite = 0; sprite < 40; sprite++) + { + int sprite_4 = sprite << 2; + int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16; + + if ((sprite_y > line) || ((sprite_y + sprite_height) <= line)) + { + visible_sprites[sprite] = false; + continue; + } + + sprite_limit++; + + visible_sprites[sprite] = sprite_limit <= 10; + } + + for (int sprite = 39; sprite >= 0; sprite--) + { + if (!visible_sprites[sprite]) + continue; + + int sprite_4 = sprite << 2; + int sprite_x = m_pMemory->Retrieve(0xFE00 + sprite_4 + 1) - 8; + + if ((sprite_x < -7) || (sprite_x >= GAMEBOY_WIDTH)) + continue; + + int sprite_y = m_pMemory->Retrieve(0xFE00 + sprite_4) - 16; + int sprite_tile_16 = (m_pMemory->Retrieve(0xFE00 + sprite_4 + 2) + & ((sprite_height == 16) ? 0xFE : 0xFF)) << 4; + u8 sprite_flags = m_pMemory->Retrieve(0xFE00 + sprite_4 + 3); + int sprite_pallette = IsSetBit(sprite_flags, 4) ? 1 : 0; + u8 palette = m_pMemory->Retrieve(sprite_pallette ? 0xFF49 : 0xFF48); + bool xflip = IsSetBit(sprite_flags, 5); + bool yflip = IsSetBit(sprite_flags, 6); + bool aboveBG = (!IsSetBit(sprite_flags, 7)); + bool cgb_tile_bank = IsSetBit(sprite_flags, 3); + int cgb_tile_pal = sprite_flags & 0x07; + int tiles = 0x8000; + int pixel_y = yflip ? ((sprite_height == 16) ? 15 : 7) - (line - sprite_y) : line - sprite_y; + u8 byte1 = 0; + u8 byte2 = 0; + int pixel_y_2 = 0; + int offset = 0; + + if (sprite_height == 16 && (pixel_y >= 8)) + { + pixel_y_2 = (pixel_y - 8) << 1; + offset = 16; + } + else + pixel_y_2 = pixel_y << 1; + + int tile_address = tiles + sprite_tile_16 + pixel_y_2 + offset; + + if (m_bCGB && cgb_tile_bank) + { + byte1 = m_pMemory->ReadCGBLCDRAM(tile_address, true); + byte2 = m_pMemory->ReadCGBLCDRAM(tile_address + 1, true); + } + else + { + byte1 = m_pMemory->Retrieve(tile_address); + byte2 = m_pMemory->Retrieve(tile_address + 1); + } + + for (int pixelx = 0; pixelx < 8; pixelx++) + { + int pixel = (byte1 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 1 : 0; + pixel |= (byte2 & (0x01 << (xflip ? pixelx : 7 - pixelx))) ? 2 : 0; + + if (pixel == 0) + continue; + + int bufferX = (sprite_x + pixelx); + + if (bufferX < 0 || bufferX >= GAMEBOY_WIDTH) + continue; + + int position = line_width + bufferX; + u8 color_cache = m_pColorCacheBuffer[position]; + + if (m_bCGB) + { + if (IsSetBit(color_cache, 2)) + continue; + } + else + { + int sprite_x_cache = m_pSpriteXCacheBuffer[position]; + if (IsSetBit(color_cache, 3) && (sprite_x_cache < sprite_x)) + continue; + } + + if (!aboveBG && (color_cache & 0x03)) + continue; + + m_pColorCacheBuffer[position] = SetBit(color_cache, 3); + m_pSpriteXCacheBuffer[position] = sprite_x; + if (m_bCGB) + { + m_pColorFrameBuffer[position] = m_CGBSpritePalettes[cgb_tile_pal][pixel][1]; + } + else + { + u8 color = (palette >> (pixel << 1)) & 0x03; + m_pColorFrameBuffer[position] = m_pFrameBuffer[position] = color; + } + } + } +} + +void Video::UpdateStatRegister() +{ + // Updates the STAT register with current mode + u8 stat = m_pMemory->Retrieve(0xFF41); + m_pMemory->Load(0xFF41, (stat & 0xFC) | (m_iStatusMode & 0x3)); +} + +void Video::CompareLYToLYC() +{ + if (m_bScreenEnabled) + { + u8 lyc = m_pMemory->Retrieve(0xFF45); + u8 stat = m_pMemory->Retrieve(0xFF41); + + if (lyc == m_iStatusModeLYCounter) + { + stat = SetBit(stat, 2); + if (IsSetBit(stat, 6)) + { + if (m_IRQ48Signal == 0) + { + m_pProcessor->RequestInterrupt(Processor::LCDSTAT_Interrupt); + } + m_IRQ48Signal = SetBit(m_IRQ48Signal, 3); + } + } + else + { + stat = UnsetBit(stat, 2); + m_IRQ48Signal = UnsetBit(m_IRQ48Signal, 3); + } + + m_pMemory->Load(0xFF41, stat); + } +} + +u8 Video::GetIRQ48Signal() const +{ + return m_IRQ48Signal; +} + +void Video::SetIRQ48Signal(u8 signal) +{ + m_IRQ48Signal = signal; +} + +void Video::SaveState(std::ostream& stream) +{ + using namespace std; + + stream.write(reinterpret_cast<const char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.write(reinterpret_cast<const char*> (&m_iStatusMode), sizeof(m_iStatusMode)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux)); + stream.write(reinterpret_cast<const char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter)); + stream.write(reinterpret_cast<const char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles)); + stream.write(reinterpret_cast<const char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine)); + stream.write(reinterpret_cast<const char*> (&m_iPixelCounter), sizeof(m_iPixelCounter)); + stream.write(reinterpret_cast<const char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter)); + stream.write(reinterpret_cast<const char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled)); + stream.write(reinterpret_cast<const char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes)); + stream.write(reinterpret_cast<const char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes)); + stream.write(reinterpret_cast<const char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered)); + stream.write(reinterpret_cast<const char*> (&m_iWindowLine), sizeof(m_iWindowLine)); + stream.write(reinterpret_cast<const char*> (&m_iHideFrames), sizeof(m_iHideFrames)); + stream.write(reinterpret_cast<const char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal)); +} + +void Video::LoadState(std::istream& stream) +{ + using namespace std; + + stream.read(reinterpret_cast<char*> (m_pFrameBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (m_pSpriteXCacheBuffer), sizeof(int) * GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (m_pColorCacheBuffer), GAMEBOY_WIDTH * GAMEBOY_HEIGHT); + stream.read(reinterpret_cast<char*> (&m_iStatusMode), sizeof(m_iStatusMode)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeCounter), sizeof(m_iStatusModeCounter)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeCounterAux), sizeof(m_iStatusModeCounterAux)); + stream.read(reinterpret_cast<char*> (&m_iStatusModeLYCounter), sizeof(m_iStatusModeLYCounter)); + stream.read(reinterpret_cast<char*> (&m_iScreenEnableDelayCycles), sizeof(m_iScreenEnableDelayCycles)); + stream.read(reinterpret_cast<char*> (&m_iStatusVBlankLine), sizeof(m_iStatusVBlankLine)); + stream.read(reinterpret_cast<char*> (&m_iPixelCounter), sizeof(m_iPixelCounter)); + stream.read(reinterpret_cast<char*> (&m_iTileCycleCounter), sizeof(m_iTileCycleCounter)); + stream.read(reinterpret_cast<char*> (&m_bScreenEnabled), sizeof(m_bScreenEnabled)); + stream.read(reinterpret_cast<char*> (m_CGBSpritePalettes), sizeof(m_CGBSpritePalettes)); + stream.read(reinterpret_cast<char*> (m_CGBBackgroundPalettes), sizeof(m_CGBBackgroundPalettes)); + stream.read(reinterpret_cast<char*> (&m_bScanLineTransfered), sizeof(m_bScanLineTransfered)); + stream.read(reinterpret_cast<char*> (&m_iWindowLine), sizeof(m_iWindowLine)); + stream.read(reinterpret_cast<char*> (&m_iHideFrames), sizeof(m_iHideFrames)); + stream.read(reinterpret_cast<char*> (&m_IRQ48Signal), sizeof(m_IRQ48Signal)); +} + +PaletteMatrix Video::GetCGBBackgroundPalettes() +{ + return &m_CGBBackgroundPalettes; +} + +PaletteMatrix Video::GetCGBSpritePalettes() +{ + return &m_CGBSpritePalettes; +} |
