diff options
author | Gabriel Rodrigues Couto <gabrielrcouto@gmail.com> | 2016-07-02 23:18:46 -0300 |
---|---|---|
committer | Gabriel Rodrigues Couto <gabrielrcouto@gmail.com> | 2016-07-02 23:18:46 -0300 |
commit | 89e12a4415c38e04631747b4bc6103e0a64b1e8b (patch) | |
tree | 55f847a5cd1af60a86850968affb95607611bb7d | |
parent | adffbf649de03cbb83b2cdc09559f45c567e42c2 (diff) | |
download | php-terminal-gameboy-emulator-origin/HEAD.zip php-terminal-gameboy-emulator-origin/HEAD.tar.gz php-terminal-gameboy-emulator-origin/HEAD.tar.bz2 |
Some functions moved to LcdControllerHEADorigin/masterorigin/HEADmaster
TerminalCanvas refactored, removed Drawille dependency, implemented a faster braille render. Thank you @whatthejeff for the inspiration :-)
Little FPS gain #46
-rw-r--r-- | composer.json | 3 | ||||
-rw-r--r-- | composer.lock | 71 | ||||
-rw-r--r-- | src/Canvas/DrawContextInterface.php | 4 | ||||
-rw-r--r-- | src/Canvas/TerminalCanvas.php | 95 | ||||
-rw-r--r-- | src/Core.php | 193 | ||||
-rw-r--r-- | src/LcdController.php | 164 |
6 files changed, 249 insertions, 281 deletions
diff --git a/composer.json b/composer.json index 679dbb7..0bd0084 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,7 @@ } ], "require": { - "php": ">=5.6.0", - "whatthejeff/drawille": "^1.0" + "php": ">=5.6.0" }, "bin": ["bin/php-gameboy"], "require-dev": { diff --git a/composer.lock b/composer.lock index 01397d1..fdeaa0d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,62 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "6d7bf9f4d4a59581c0bdf16ecfa1e8b9", - "content-hash": "9d0ca641c2c0029c4ebdde83b2a8a6db", - "packages": [ - { - "name": "whatthejeff/drawille", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/whatthejeff/php-drawille.git", - "reference": "bafea427f5bf2445413f6807000a95f70cc83bfd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/whatthejeff/php-drawille/zipball/bafea427f5bf2445413f6807000a95f70cc83bfd", - "reference": "bafea427f5bf2445413f6807000a95f70cc83bfd", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "4.1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Drawille": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - } - ], - "description": "Terminal drawing with braille", - "homepage": "http://github.com/whatthejeff/php-drawille", - "keywords": [ - "Turtle", - "braille", - "console", - "drawing", - "terminal" - ], - "time": "2014-05-26 13:45:31" - } - ], + "hash": "1d43205f748238a612e736313fb85ead", + "content-hash": "650da37ee06349860cc3ce6988504476", + "packages": [], "packages-dev": [ { "name": "squizlabs/php_codesniffer", @@ -136,16 +83,16 @@ }, { "name": "symfony/finder", - "version": "v3.0.2", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" + "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", - "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", + "url": "https://api.github.com/repos/symfony/finder/zipball/8201978de88a9fa0923e18601bb17f1df9c721e7", + "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7", "shasum": "" }, "require": { @@ -154,7 +101,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -181,7 +128,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-01-27 05:14:46" + "time": "2016-06-29 05:41:56" } ], "aliases": [], diff --git a/src/Canvas/DrawContextInterface.php b/src/Canvas/DrawContextInterface.php index 5b79f8d..6b4aaa5 100644 --- a/src/Canvas/DrawContextInterface.php +++ b/src/Canvas/DrawContextInterface.php @@ -12,8 +12,6 @@ interface DrawContextInterface * Draw image on canvas. * * @param array $canvasBuffer If colored, each pixel => 4 items on array (RGBA) - * @param int $left - * @param int $top */ - public function draw($canvasBuffer, $left, $top); + public function draw($canvasBuffer); } diff --git a/src/Canvas/TerminalCanvas.php b/src/Canvas/TerminalCanvas.php index 5760377..cfc39cf 100644 --- a/src/Canvas/TerminalCanvas.php +++ b/src/Canvas/TerminalCanvas.php @@ -2,11 +2,15 @@ namespace GameBoy\Canvas; -use Drawille\Canvas; use GameBoy\Settings; class TerminalCanvas implements DrawContextInterface { + /** + * The blank brailler char + * @var String + */ + protected $brailleCharOffset; protected $canvas; /** * If is a color enabled canvas, set to true @@ -16,25 +20,39 @@ class TerminalCanvas implements DrawContextInterface protected $currentSecond = 0; protected $framesInSecond = 0; protected $fps = 0; + protected $height = 0; protected $lastFrame; protected $lastFrameCanvasBuffer; - - private $width = 0; - private $height = 0; + /** + * Braille Pixel Matrix + * ,___, + * |1 4| + * |2 5| + * |3 6| + * |7 8| + * ````` + * @var Array + */ + protected $pixelMap; + protected $width = 0; public function __construct() { - $this->canvas = new Canvas(); + $this->brailleCharOffset = html_entity_decode('&#' . (0x2800) . ';', ENT_NOQUOTES, 'UTF-8'); + $this->pixelMap = [ + [html_entity_decode('&#' . (0x2801) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2808) . ';', ENT_NOQUOTES, 'UTF-8')], + [html_entity_decode('&#' . (0x2802) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2810) . ';', ENT_NOQUOTES, 'UTF-8')], + [html_entity_decode('&#' . (0x2804) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2820) . ';', ENT_NOQUOTES, 'UTF-8')], + [html_entity_decode('&#' . (0x2840) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2880) . ';', ENT_NOQUOTES, 'UTF-8')], + ]; } /** * Draw image on canvas using braille font. * * @param object $canvasBuffer $data = Each pixel (true/false) - * @param int $left - * @param int $top */ - public function draw($canvasBuffer, $left, $top) + public function draw($canvasBuffer) { //Calculate current FPS if ($this->currentSecond != time()) { @@ -49,33 +67,38 @@ class TerminalCanvas implements DrawContextInterface // @TODO - The FPS will be wrong, need to find a way to update // without redraw if ($canvasBuffer != $this->lastFrameCanvasBuffer) { - //Clear the pixels from the canvas - $this->canvas->clear(); - - //Corner pixel, to draw same size each time - $this->canvas->set(0, 0); - $this->canvas->set(159, 143); - - $y = 0; - $count = count($canvasBuffer); - - for ($i = 0; $i < $count; $i++) { - $x = $i % 160; - - if ($canvasBuffer[$i]) { - $this->canvas->set($x, $y); - } - - if ($x == 159) { - ++$y; + // Array with all braille chars of the frame, filled with the blank char + // 2880 = total braille chars per frame + $chars = array_fill(0, 2880, $this->brailleCharOffset); + + // Turn on the first and last pixels + $chars[0] |= $this->pixelMap[0][0]; + $chars[2879] |= $this->pixelMap[0][0]; + + // Frame string - It's a big braille chars string + $frame = ''; + + for ($y = 0; $y < 144; $y++) { + for ($x = 0; $x < 160; $x++) { + $pixelCanvasNumber = $x + (160 * $y); + $charPosition = floor($x / 2) + (floor($y / 4) * 80); + + if (isset($canvasBuffer[$pixelCanvasNumber]) && $canvasBuffer[$pixelCanvasNumber]) { + $chars[$charPosition] |= $this->pixelMap[$y % 4][$x % 2]; + } + + // Each braille frame has 8 pixels, when we reach the last pixel, + // we can append to the frame string + if ($x % 2 === 1 && $y % 4 === 3) { + $frame .= $chars[$charPosition]; + + if ($x % 159 === 0) { + $frame .= PHP_EOL; + } + } } } - $frame = $this->canvas->frame([ - 'min_x' => 0, - 'max_x' => 79 - ]); - $this->lastFrame = $frame; $this->lastFrameCanvasBuffer = $canvasBuffer; @@ -85,13 +108,11 @@ class TerminalCanvas implements DrawContextInterface $content = "\e[{$this->height}A\e[{$this->width}D"; } - $content .= sprintf('FPS: %d - Frame Skip: %s'.PHP_EOL, $this->fps, Settings::$frameskipAmout); - $content .= $frame; - + $content .= sprintf('FPS: %3d - Frame Skip: %3d' . PHP_EOL, $this->fps, Settings::$frameskipAmout) . $frame; echo $content; - $this->height = substr_count($frame, PHP_EOL) + 1; - $this->width = strpos($frame, PHP_EOL); + $this->height = 37; + $this->width = 80; } } } diff --git a/src/Core.php b/src/Core.php index 3e5091a..3423d2f 100644 --- a/src/Core.php +++ b/src/Core.php @@ -140,27 +140,6 @@ class Core //When loaded in as a save state, this will not be empty. public $savedStateFileName = ''; - //Tracker for STAT triggering. - public $STATTracker = 0; - - //The scan line mode (for lines 1-144 it's 2-3-0, for 145-154 it's 1) - public $modeSTAT = 0; - - //Should we trigger an interrupt if LY==LYC? - public $LYCMatchTriggerSTAT = false; - - //Should we trigger an interrupt if in mode 2? - public $mode2TriggerSTAT = false; - - //Should we trigger an interrupt if in mode 1? - public $mode1TriggerSTAT = false; - - //Should we trigger an interrupt if in mode 0? - public $mode0TriggerSTAT = false; - - //Is the emulated LCD controller on? - public $LCDisOn = false; - //lcdControllerler object public $lcdController = null; @@ -236,9 +215,6 @@ class Core //The last time we iterated the main loop. public $lastIteration = 0; - //Actual scan line... - public $actualScanLine = 0; - // //ROM Cartridge Components: // @@ -420,12 +396,12 @@ class Core $this->cartridgeType, $this->name, $this->gameCode, - $this->modeSTAT, - $this->LYCMatchTriggerSTAT, - $this->mode2TriggerSTAT, - $this->mode1TriggerSTAT, - $this->mode0TriggerSTAT, - $this->LCDisOn, + $this->lcdController->modeSTAT, + $this->lcdController->LYCMatchTriggerSTAT, + $this->lcdController->mode2TriggerSTAT, + $this->lcdController->mode1TriggerSTAT, + $this->lcdController->mode0TriggerSTAT, + $this->lcdController->LCDisOn, $this->gfxWindowY, $this->gfxWindowDisplay, $this->gfxSpriteShow, @@ -463,7 +439,7 @@ class Core $this->spritePriorityEnabled, $this->fromTypedArray($this->tileReadState), $this->windowSourceLine, - $this->actualScanLine, + $this->lcdController->actualScanLine, $this->RTCisLatched, $this->latchedSeconds, $this->latchedMinutes, @@ -478,7 +454,7 @@ class Core $this->RTCHALT, $this->gbColorizedPalette, $this->skipPCIncrement, - $this->STATTracker, + $this->lcdController->STATTracker, $this->gbcRamBankPositionECHO, $this->numRAMBanks, ]; @@ -525,12 +501,12 @@ class Core $this->cartridgeType = $state[$address++]; $this->name = $state[$address++]; $this->gameCode = $state[$address++]; - $this->modeSTAT = $state[$address++]; - $this->LYCMatchTriggerSTAT = $state[$address++]; - $this->mode2TriggerSTAT = $state[$address++]; - $this->mode1TriggerSTAT = $state[$address++]; - $this->mode0TriggerSTAT = $state[$address++]; - $this->LCDisOn = $state[$address++]; + $this->lcdController->modeSTAT = $state[$address++]; + $this->lcdController->LYCMatchTriggerSTAT = $state[$address++]; + $this->lcdController->mode2TriggerSTAT = $state[$address++]; + $this->lcdController->mode1TriggerSTAT = $state[$address++]; + $this->lcdController->mode0TriggerSTAT = $state[$address++]; + $this->lcdController->LCDisOn = $state[$address++]; $this->gfxWindowY = $state[$address++]; $this->gfxWindowDisplay = $state[$address++]; $this->gfxSpriteShow = $state[$address++]; @@ -568,7 +544,7 @@ class Core $this->spritePriorityEnabled = $state[$address++]; $this->tileReadState = $this->toTypedArray($state[$address++], false, false); $this->windowSourceLine = $state[$address++]; - $this->actualScanLine = $state[$address++]; + $this->lcdController->actualScanLine = $state[$address++]; $this->RTCisLatched = $state[$address++]; $this->latchedSeconds = $state[$address++]; $this->latchedMinutes = $state[$address++]; @@ -583,7 +559,7 @@ class Core $this->RTCHALT = $state[$address++]; $this->gbColorizedPalette = $state[$address++]; $this->skipPCIncrement = $state[$address++]; - $this->STATTracker = $state[$address++]; + $this->lcdController->STATTracker = $state[$address++]; $this->gbcRamBankPositionECHO = $state[$address++]; $this->numRAMBanks = $state[$address]; $this->tileCountInvalidator = $this->tileCount * 4; @@ -1024,7 +1000,7 @@ class Core $this->frameBuffer = array_fill(0, $this->pixelCount, 0x00FFFFFF); } - $this->drawContext->draw($this->canvasBuffer, 0, 0); + $this->drawContext->draw($this->canvasBuffer); } public function joyPadEvent($key, $down) @@ -1134,56 +1110,6 @@ class Core } } - public function scanLineMode2() - { // OAM in use - if ($this->modeSTAT != 2) { - if ($this->mode2TriggerSTAT) { - $this->memory[0xFF0F] |= 0x2; // set IF bit 1 - } - $this->STATTracker = 1; - $this->modeSTAT = 2; - } - } - - public function scanLineMode3() - { // OAM in use - if ($this->modeSTAT != 3) { - if ($this->mode2TriggerSTAT && $this->STATTracker == 0) { - $this->memory[0xFF0F] |= 0x2; // set IF bit 1 - } - $this->STATTracker = 1; - $this->modeSTAT = 3; - } - } - - public function scanLineMode0() - { // H-Blank - if ($this->modeSTAT != 0) { - if ($this->hdmaRunning && !$this->halt && $this->LCDisOn) { - $this->performHdma(); //H-Blank DMA - } - if ($this->mode0TriggerSTAT || ($this->mode2TriggerSTAT && $this->STATTracker == 0)) { - $this->memory[0xFF0F] |= 0x2; // if STAT bit 3 -> set IF bit1 - } - $this->notifyScanline(); - $this->STATTracker = 2; - $this->modeSTAT = 0; - } - } - - public function matchLYC() - { // LY - LYC Compare - // If LY==LCY - if ($this->memory[0xFF44] == $this->memory[0xFF45]) { - $this->memory[0xFF41] |= 0x04; // set STAT bit 2: LY-LYC coincidence flag - if ($this->LYCMatchTriggerSTAT) { - $this->memory[0xFF0F] |= 0x2; // set IF bit 1 - } - } else { - $this->memory[0xFF41] &= 0xFB; // reset STAT bit 2 (LY!=LYC) - } - } - public function updateCore() { // DIV control @@ -1196,7 +1122,7 @@ class Core $timedTicks = $this->CPUTicks / $this->multiplier; // LCD Timing $this->LCDTicks += $timedTicks; //LCD timing - $this->lcdController->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control + $this->lcdController->scanLine($this->lcdController->actualScanLine); //Scan Line and STAT Mode Control //Audio Timing $this->audioTicks += $timedTicks; //Not the same as the LCD timing (Cannot be altered by display on/off changes!!!). @@ -1243,7 +1169,7 @@ class Core $this->canvasBuffer = array_fill(0, 4 * $this->width * $this->height, false); } - $this->drawContext->draw($this->canvasBuffer, 0, 0); + $this->drawContext->draw($this->canvasBuffer); $this->drewBlank = 2; } } @@ -1382,7 +1308,7 @@ class Core } //Draw out the CanvasPixelArray data: - $this->drawContext->draw($this->canvasBuffer, 0, 0); + $this->drawContext->draw($this->canvasBuffer); if (Settings::$frameskipAmout > 0) { //Decrement the frameskip counter: @@ -1438,29 +1364,6 @@ class Core } } - public function notifyScanline() - { - if ($this->actualScanLine == 0) { - $this->windowSourceLine = 0; - } - // determine the left edge of the window (160 if window is inactive) - $windowLeft = ($this->gfxWindowDisplay && $this->memory[0xFF4A] <= $this->actualScanLine) ? min(160, $this->memory[0xFF4B] - 7) : 160; - // step 1: background+window - $skippedAnything = $this->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0); - // At this point, the high (alpha) byte in the frameBuffer is 0xff for colors 1,2,3 and - // 0x00 for color 0. Foreground sprites draw on all colors, background sprites draw on - // top of color 0 only. - // step 2: sprites - $this->drawSpritesForLine($this->actualScanLine); - // step 3: prio tiles+window - if ($skippedAnything) { - $this->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0x80); - } - if ($windowLeft < 160) { - ++$this->windowSourceLine; - } - } - public function drawBackgroundForLine($line, $windowLeft, $priority) { $skippedTile = false; @@ -1694,11 +1597,11 @@ class Core } elseif ($address >= 0x8000 && $address < 0xA000) { if ($this->cGBC) { //CPU Side Reading The VRAM (Optimized for GameBoy Color) - return ($this->modeSTAT > 2) ? 0xFF : (($this->currVRAMBank == 0) ? $this->memory[$address] : $this->VRAM[$address - 0x8000]); + return ($this->lcdController->modeSTAT > 2) ? 0xFF : (($this->currVRAMBank == 0) ? $this->memory[$address] : $this->VRAM[$address - 0x8000]); } //CPU Side Reading The VRAM (Optimized for classic GameBoy) - return ($this->modeSTAT > 2) ? 0xFF : $this->memory[$address]; + return ($this->lcdController->modeSTAT > 2) ? 0xFF : $this->memory[$address]; } elseif ($address >= 0xA000 && $address < 0xC000) { if (($this->numRAMBanks == 1 / 16 && $address < 0xA200) || $this->numRAMBanks >= 1) { if (!$this->cMBC3) { @@ -1760,7 +1663,7 @@ class Core } } elseif ($address < 0xFEA0) { //memoryReadOAM - return ($this->modeSTAT > 1) ? 0xFF : $this->memory[$address]; + return ($this->lcdController->modeSTAT > 1) ? 0xFF : $this->memory[$address]; } elseif ($this->cGBC && $address >= 0xFEA0 && $address < 0xFF00) { //memoryReadNormal return $this->memory[$address]; @@ -1840,10 +1743,10 @@ class Core return (($this->memory[0xFF26] & 0x4) == 0x4) ? 0xFF : $this->memory[$address]; break; case 0xFF41: - return 0x80 | $this->memory[0xFF41] | $this->modeSTAT; + return 0x80 | $this->memory[0xFF41] | $this->lcdController->modeSTAT; break; case 0xFF44: - return ($this->LCDisOn) ? $this->memory[0xFF44] : 0; + return ($this->lcdController->LCDisOn) ? $this->memory[0xFF44] : 0; break; case 0xFF4F: return $this->currVRAMBank; @@ -2022,7 +1925,7 @@ class Core } elseif ($address < 0xA000) { // VRAMWrite //VRAM cannot be written to during mode 3 - if ($this->modeSTAT < 3) { + if ($this->lcdController->modeSTAT < 3) { // Bkg Tile data area if ($address < 0x9800) { $tileIndex = (($address - 0x8000) >> 4) + (384 * $this->currVRAMBank); @@ -2115,7 +2018,7 @@ class Core } elseif ($address <= 0xFEA0) { //memoryWriteOAMRAM //OAM RAM cannot be written to in mode 2 & 3 - if ($this->modeSTAT < 2) { + if ($this->lcdController->modeSTAT < 2) { $this->memory[$address] = $data; } } elseif ($address < 0xFF00) { @@ -2148,13 +2051,13 @@ class Core } elseif ($address == 0xFF40) { if ($this->cGBC) { $temp_var = ($data & 0x80) == 0x80; - if ($temp_var != $this->LCDisOn) { + if ($temp_var != $this->lcdController->LCDisOn) { //When the display mode changes... - $this->LCDisOn = $temp_var; + $this->lcdController->LCDisOn = $temp_var; $this->memory[0xFF41] &= 0xF8; - $this->STATTracker = $this->modeSTAT = $this->LCDTicks = $this->actualScanLine = $this->memory[0xFF44] = 0; - if ($this->LCDisOn) { - $this->matchLYC(); //Get the compare of the first scan line. + $this->lcdController->STATTracker = $this->lcdController->modeSTAT = $this->LCDTicks = $this->lcdController->actualScanLine = $this->memory[0xFF44] = 0; + if ($this->lcdController->LCDisOn) { + $this->lcdController->matchLYC(); //Get the compare of the first scan line. } else { $this->displayShowOff(); } @@ -2170,13 +2073,13 @@ class Core $this->memory[0xFF40] = $data; } else { $temp_var = ($data & 0x80) == 0x80; - if ($temp_var != $this->LCDisOn) { + if ($temp_var != $this->lcdController->LCDisOn) { //When the display mode changes... - $this->LCDisOn = $temp_var; + $this->lcdController->LCDisOn = $temp_var; $this->memory[0xFF41] &= 0xF8; - $this->STATTracker = $this->modeSTAT = $this->LCDTicks = $this->actualScanLine = $this->memory[0xFF44] = 0; - if ($this->LCDisOn) { - $this->matchLYC(); //Get the compare of the first scan line. + $this->lcdController->STATTracker = $this->lcdController->modeSTAT = $this->LCDTicks = $this->lcdController->actualScanLine = $this->memory[0xFF44] = 0; + if ($this->lcdController->LCDisOn) { + $this->lcdController->matchLYC(); //Get the compare of the first scan line. } else { $this->displayShowOff(); } @@ -2199,25 +2102,25 @@ class Core } } elseif ($address == 0xFF41) { if ($this->cGBC) { - $this->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); - $this->mode2TriggerSTAT = (($data & 0x20) == 0x20); - $this->mode1TriggerSTAT = (($data & 0x10) == 0x10); - $this->mode0TriggerSTAT = (($data & 0x08) == 0x08); + $this->lcdController->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); + $this->lcdController->mode2TriggerSTAT = (($data & 0x20) == 0x20); + $this->lcdController->mode1TriggerSTAT = (($data & 0x10) == 0x10); + $this->lcdController->mode0TriggerSTAT = (($data & 0x08) == 0x08); $this->memory[0xFF41] = ($data & 0xF8); } else { - $this->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); - $this->mode2TriggerSTAT = (($data & 0x20) == 0x20); - $this->mode1TriggerSTAT = (($data & 0x10) == 0x10); - $this->mode0TriggerSTAT = (($data & 0x08) == 0x08); + $this->lcdController->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); + $this->lcdController->mode2TriggerSTAT = (($data & 0x20) == 0x20); + $this->lcdController->mode1TriggerSTAT = (($data & 0x10) == 0x10); + $this->lcdController->mode0TriggerSTAT = (($data & 0x08) == 0x08); $this->memory[0xFF41] = ($data & 0xF8); - if ($this->LCDisOn && $this->modeSTAT < 2) { + if ($this->lcdController->LCDisOn && $this->lcdController->modeSTAT < 2) { $this->memory[0xFF0F] |= 0x2; } } } elseif ($address == 0xFF45) { $this->memory[0xFF45] = $data; - if ($this->LCDisOn) { - $this->matchLYC(); //Get the compare of the first scan line. + if ($this->lcdController->LCDisOn) { + $this->lcdController->matchLYC(); //Get the compare of the first scan line. } } elseif ($address == 0xFF46) { $this->memory[0xFF46] = $data; diff --git a/src/LcdController.php b/src/LcdController.php index 5501465..9e2015d 100644 --- a/src/LcdController.php +++ b/src/LcdController.php @@ -4,13 +4,74 @@ namespace GameBoy; class LcdController { + //Actual scan line... + public $actualScanLine = 0; + protected $core; + //Is the emulated LCD controller on? + public $LCDisOn = false; + + //Should we trigger an interrupt if LY==LYC? + public $LYCMatchTriggerSTAT = false; + + //The scan line mode (for lines 1-144 it's 2-3-0, for 145-154 it's 1) + public $modeSTAT = 0; + + //Should we trigger an interrupt if in mode 0? + public $mode0TriggerSTAT = false; + + //Should we trigger an interrupt if in mode 1? + public $mode1TriggerSTAT = false; + + //Should we trigger an interrupt if in mode 2? + public $mode2TriggerSTAT = false; + + //Tracker for STAT triggering. + public $STATTracker = 0; + public function __construct($core) { $this->core = $core; } + public function matchLYC() + { + // LY - LYC Compare + // If LY==LCY + if ($this->core->memory[0xFF44] == $this->core->memory[0xFF45]) { + $this->core->memory[0xFF41] |= 0x04; // set STAT bit 2: LY-LYC coincidence flag + if ($this->LYCMatchTriggerSTAT) { + $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 + } + } else { + $this->core->memory[0xFF41] &= 0xFB; // reset STAT bit 2 (LY!=LYC) + } + } + + public function notifyScanline() + { + if ($this->actualScanLine == 0) { + $this->core->windowSourceLine = 0; + } + // determine the left edge of the window (160 if window is inactive) + $windowLeft = ($this->core->gfxWindowDisplay && $this->core->memory[0xFF4A] <= $this->actualScanLine) ? min(160, $this->core->memory[0xFF4B] - 7) : 160; + // step 1: background+window + $skippedAnything = $this->core->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0); + // At this point, the high (alpha) byte in the frameBuffer is 0xff for colors 1,2,3 and + // 0x00 for color 0. Foreground sprites draw on all colors, background sprites draw on + // top of color 0 only. + // step 2: sprites + $this->core->drawSpritesForLine($this->actualScanLine); + // step 3: prio tiles+window + if ($skippedAnything) { + $this->core->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0x80); + } + if ($windowLeft < 160) { + ++$this->core->windowSourceLine; + } + } + /** * Scan Line and STAT Mode Control * @param int $line Memory Scanline @@ -18,64 +79,63 @@ class LcdController public function scanLine($line) { //When turned off = Do nothing! - //@TODO - Move LCDisOn to this class - if ($this->core->LCDisOn) { + if ($this->LCDisOn) { if ($line < 143) { //We're on a normal scan line: if ($this->core->LCDTicks < 20) { - $this->core->scanLineMode2(); // mode2: 80 cycles + $this->scanLineMode2(); // mode2: 80 cycles } elseif ($this->core->LCDTicks < 63) { - $this->core->scanLineMode3(); // mode3: 172 cycles + $this->scanLineMode3(); // mode3: 172 cycles } elseif ($this->core->LCDTicks < 114) { - $this->core->scanLineMode0(); // mode0: 204 cycles + $this->scanLineMode0(); // mode0: 204 cycles } else { //We're on a new scan line: $this->core->LCDTicks -= 114; - $this->core->actualScanLine = ++$this->core->memory[0xFF44]; - $this->core->matchLYC(); - if ($this->core->STATTracker != 2) { - if ($this->core->hdmaRunning && !$this->core->halt && $this->core->LCDisOn) { + $this->actualScanLine = ++$this->core->memory[0xFF44]; + $this->matchLYC(); + if ($this->STATTracker != 2) { + if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) { $this->core->performHdma(); //H-Blank DMA } - if ($this->core->mode0TriggerSTAT) { + if ($this->mode0TriggerSTAT) { $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 } } - $this->core->STATTracker = 0; - $this->core->scanLineMode2(); // mode2: 80 cycles + $this->STATTracker = 0; + $this->scanLineMode2(); // mode2: 80 cycles if ($this->core->LCDTicks >= 114) { //We need to skip 1 or more scan lines: $this->core->notifyScanline(); - $this->scanLine($this->core->actualScanLine); //Scan Line and STAT Mode Control + $this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control } } } elseif ($line == 143) { //We're on the last visible scan line of the LCD screen: if ($this->core->LCDTicks < 20) { - $this->core->scanLineMode2(); // mode2: 80 cycles + $this->scanLineMode2(); // mode2: 80 cycles } elseif ($this->core->LCDTicks < 63) { - $this->core->scanLineMode3(); // mode3: 172 cycles + $this->scanLineMode3(); // mode3: 172 cycles } elseif ($this->core->LCDTicks < 114) { - $this->core->scanLineMode0(); // mode0: 204 cycles + $this->scanLineMode0(); // mode0: 204 cycles } else { //Starting V-Blank: //Just finished the last visible scan line: $this->core->LCDTicks -= 114; - $this->core->actualScanLine = ++$this->core->memory[0xFF44]; - $this->core->matchLYC(); - if ($this->core->mode1TriggerSTAT) { + $this->actualScanLine = ++$this->core->memory[0xFF44]; + $this->matchLYC(); + if ($this->mode1TriggerSTAT) { $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 } - if ($this->core->STATTracker != 2) { - if ($this->core->hdmaRunning && !$this->core->halt && $this->core->LCDisOn) { + if ($this->STATTracker != 2) { + if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) { $this->core->performHdma(); //H-Blank DMA } - if ($this->core->mode0TriggerSTAT) { + if ($this->mode0TriggerSTAT) { $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 } } - $this->core->STATTracker = 0; - $this->core->modeSTAT = 1; + $this->STATTracker = 0; + $this->modeSTAT = 1; $this->core->memory[0xFF0F] |= 0x1; // set IF flag 0 //LCD off takes at least 2 frames. if ($this->core->drewBlank > 0) { @@ -83,7 +143,7 @@ class LcdController } if ($this->core->LCDTicks >= 114) { //We need to skip 1 or more scan lines: - $this->scanLine($this->core->actualScanLine); //Scan Line and STAT Mode Control + $this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control } } } elseif ($line < 153) { @@ -91,30 +151,70 @@ class LcdController if ($this->core->LCDTicks >= 114) { //We're on a new scan line: $this->core->LCDTicks -= 114; - $this->core->actualScanLine = ++$this->core->memory[0xFF44]; - $this->core->matchLYC(); + $this->actualScanLine = ++$this->core->memory[0xFF44]; + $this->matchLYC(); if ($this->core->LCDTicks >= 114) { //We need to skip 1 or more scan lines: - $this->scanLine($this->core->actualScanLine); //Scan Line and STAT Mode Control + $this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control } } } else { //VBlank Ending (We're on the last actual scan line) if ($this->core->memory[0xFF44] == 153) { $this->core->memory[0xFF44] = 0; //LY register resets to 0 early. - $this->core->matchLYC(); //LY==LYC Test is early here (Fixes specific one-line glitches (example: Kirby2 intro)). + $this->matchLYC(); //LY==LYC Test is early here (Fixes specific one-line glitches (example: Kirby2 intro)). } if ($this->core->LCDTicks >= 114) { //We reset back to the beginning: $this->core->LCDTicks -= 114; - $this->core->actualScanLine = 0; - $this->core->scanLineMode2(); // mode2: 80 cycles + $this->actualScanLine = 0; + $this->scanLineMode2(); // mode2: 80 cycles if ($this->core->LCDTicks >= 114) { //We need to skip 1 or more scan lines: - $this->scanLine($this->core->actualScanLine); //Scan Line and STAT Mode Control + $this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control } } } } } + + public function scanLineMode0() + { + // H-Blank + if ($this->modeSTAT != 0) { + if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) { + $this->performHdma(); //H-Blank DMA + } + if ($this->mode0TriggerSTAT || ($this->mode2TriggerSTAT && $this->STATTracker == 0)) { + $this->core->memory[0xFF0F] |= 0x2; // if STAT bit 3 -> set IF bit1 + } + $this->notifyScanline(); + $this->STATTracker = 2; + $this->modeSTAT = 0; + } + } + + public function scanLineMode2() + { + // OAM in use + if ($this->modeSTAT != 2) { + if ($this->mode2TriggerSTAT) { + $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 + } + $this->STATTracker = 1; + $this->modeSTAT = 2; + } + } + + public function scanLineMode3() + { + // OAM in use + if ($this->modeSTAT != 3) { + if ($this->mode2TriggerSTAT && $this->STATTracker == 0) { + $this->core->memory[0xFF0F] |= 0x2; // set IF bit 1 + } + $this->STATTracker = 1; + $this->modeSTAT = 3; + } + } } |