summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--composer.json3
-rw-r--r--composer.lock71
-rw-r--r--src/Canvas/DrawContextInterface.php4
-rw-r--r--src/Canvas/TerminalCanvas.php95
-rw-r--r--src/Core.php193
-rw-r--r--src/LcdController.php164
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;
+ }
+ }
}