spencercw avatar spencercw committed 80619e0

Video emulation accuracy improvements.

- The display is no longer blanked when turned off; it simply doesn't update.
- Reset the LCD_STAT and LCDC_Y registers when the display is disabled.
- Don't send VBLANK or stat interrupts when the display is disabled.
- The undocumented high bit in the LCD_STAT register is now always 1.

Comments (0)

Files changed (2)

gb_emulator/src/gb_memory.cpp

 	romBank      = 1;
 	extRamBank   = 0;
 
-	ioPorts[JOYP]  = 0x0f;
-	ioPorts[LCDC]  = 0x91;
-	ioPorts[BGP]   = 0xfc;
-	ioPorts[OBP0]  = 0xff;
-	ioPorts[OBP1]  = 0xff;
-	ioPorts[VBK]   = 0x01;
-	ioPorts[HDMA5] = 0xff;
-	ioPorts[SVBK]  = 0x01;
+	ioPorts[JOYP]     = 0x0f;
+	ioPorts[LCDC]     = 0x91;
+	ioPorts[LCD_STAT] = 0x80;
+	ioPorts[BGP]      = 0xfc;
+	ioPorts[OBP0]     = 0xff;
+	ioPorts[OBP1]     = 0xff;
+	ioPorts[VBK]      = 0x01;
+	ioPorts[HDMA5]    = 0xff;
+	ioPorts[SVBK]     = 0x01;
 
 	// Undocumented GBC registers
 	ioPorts[0x6c] = 0xfe;
 	// LCD status
 	case LCD_STAT:
 		ioPorts[ptr] &= 0x07;
-		ioPorts[ptr] |= val & 0x78;
+		ioPorts[ptr] |= 0x80 | (val & 0x78);
 		break;
 
 	// Current draw line

gb_emulator/src/gb_video.cpp

 
 int GbVideo::poll()
 {
+	if (!(gb_.mem_.ioPorts[LCDC] & LCDC_OPERATIONAL))
+	{
+		gb_.mem_.ioPorts[LCD_STAT] &= 0xfc;
+		gb_.mem_.ioPorts[LCDC_Y] = 0;
+		return lineCycles;
+	}
+
 	// Draw a line
 	if (gb_.mem_.ioPorts[LCDC_Y] < VBLANK_START)
 	{
 		{
 		// HBLANK -> OAM read
 		case 0x00:
-			gb_.mem_.ioPorts[LCD_STAT] &= 0x7c;
+			gb_.mem_.ioPorts[LCD_STAT] &= 0xfc;
 			gb_.mem_.ioPorts[LCD_STAT] |= 0x02;
 			if (gb_.mem_.ioPorts[LCD_STAT] & LCD_STAT_OAM_READ_INTR)
 				gb_.mem_.ioPorts[IF] |= LCD_STAT_INTR;
 
 		// OAM read -> OAM + VRAM read
 		case 0x02:
-			gb_.mem_.ioPorts[LCD_STAT] &= 0x7c;
+			gb_.mem_.ioPorts[LCD_STAT] &= 0xfc;
 			gb_.mem_.ioPorts[LCD_STAT] |= 0x03;
 			return oamVramReadCycles;
 
 			assert(!"invalid video mode");
 		}
 
-		if (!(gb_.mem_.ioPorts[LCDC] & LCDC_OPERATIONAL))
+		// Draw the background
+		#ifdef DEBUG
+		if (drawBackground)
 		{
-			// Colour the line white
+		#endif
+
+		drawBgWndw(DRAW_BACKGROUND);
+
+		#ifdef DEBUG
+		}
+		else
+		{
+			// Fill the line with white
 			static const uint32_t white = 0xffffff;
 			for (int i = 0; i != 160; ++i)
 			{
 				pixelBuffer[gb_.mem_.ioPorts[LCDC_Y] * 160 + i] = white;
 			}
 		}
-		else
+		#endif
+
+		// Draw the window
+		#ifdef DEBUG
+		if (drawWindow)
 		{
-			// Draw the background
-			#ifdef DEBUG
-			if (drawBackground)
+		#endif
+
+		if ((gb_.mem_.ioPorts[LCDC] & LCDC_WNDW_DISPLAY) &&
+			gb_.mem_.ioPorts[LCDC_Y] >= gb_.mem_.ioPorts[WY])
+		{
+			drawBgWndw(DRAW_WINDOW);
+		}
+
+		#ifdef DEBUG
+		}
+		#endif
+
+		// Draw the sprites
+		#ifdef DEBUG
+		if (drawSprites)
+		{
+		#endif
+
+		if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_DISPLAY)
+		{
+			// Select the sprites to draw (10 max)
+			// FIXME: This algorithm is not valid for non-GBC mode; the priority needs to be
+			// first determined by the x position
+			uint8_t *sprites[10];
+			int spritesCount = 0;
+			for (int i = 0; spritesCount != 10 && i != 40; ++i)
 			{
-			#endif
+				uint8_t *sprite = &gb_.mem_.oam[i * 4];
 
-			drawBgWndw(DRAW_BACKGROUND);
-
-			#ifdef DEBUG
-			}
-			else
-			{
-				// Fill the line with white
-				static const uint32_t white = 0xffffff;
-				for (int i = 0; i != 160; ++i)
+				// Check sprite covers this line
+				if (gb_.mem_.ioPorts[LCDC_Y] >= sprite[0] - 16 &&
+				    gb_.mem_.ioPorts[LCDC_Y] <  sprite[0] -
+				   (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE ? 0 : 8))
 				{
-					pixelBuffer[gb_.mem_.ioPorts[LCDC_Y] * 160 + i] = white;
-				}
-			}
-			#endif
-
-			// Draw the window
-			#ifdef DEBUG
-			if (drawWindow)
-			{
-			#endif
-
-			if ((gb_.mem_.ioPorts[LCDC] & LCDC_WNDW_DISPLAY) &&
-				gb_.mem_.ioPorts[LCDC_Y] >= gb_.mem_.ioPorts[WY])
-			{
-				drawBgWndw(DRAW_WINDOW);
-			}
-
-			#ifdef DEBUG
-			}
-			#endif
-
-			// Draw the sprites
-			#ifdef DEBUG
-			if (drawSprites)
-			{
-			#endif
-
-			if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_DISPLAY)
-			{
-				// Select the sprites to draw (10 max)
-				// FIXME: This algorithm is not valid for non-GBC mode; the priority needs to be
-				// first determined by the x position
-				uint8_t *sprites[10];
-				int spritesCount = 0;
-				for (int i = 0; spritesCount != 10 && i != 40; ++i)
-				{
-					uint8_t *sprite = &gb_.mem_.oam[i * 4];
-
-					// Check sprite covers this line
-					if (gb_.mem_.ioPorts[LCDC_Y] >= sprite[0] - 16 &&
-						gb_.mem_.ioPorts[LCDC_Y] <  sprite[0] -
-							(gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE ? 0 : 8))
-					{
-						sprites[spritesCount++] = sprite;
-					}
-				}
-
-				// Draw the selected sprites; iterate backwards so the higher priority sprites
-				// overlay the lower priority ones
-				for (int i = spritesCount - 1; i >= 0; --i)
-				{
-					// Get the tile
-					uint16_t spriteIndex = sprites[i][2];
-					if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE)
-						spriteIndex &= 0xfe;
-					spriteIndex *= 16;
-					if (gb_.gbc_ && (sprites[i][3] & 0x08))
-						spriteIndex += VRAM_BANK_SIZE;
-					uint8_t *tile = &gb_.mem_.vram[spriteIndex];
-
-					unsigned tileLineIndex = gb_.mem_.ioPorts[LCDC_Y] - (sprites[i][0] - 16);
-					if (sprites[i][3] & 0x40)
-					{
-						if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE)
-							tileLineIndex = 15 - tileLineIndex;
-						else
-							tileLineIndex = 7  - tileLineIndex;
-					}
-					uint16_t tileLine = *reinterpret_cast<uint16_t *>(&tile[tileLineIndex * 2]);
-
-					// Select the palette
-					uint8_t *palette = &gbcSpritePalette[(sprites[i][3] & 0x07) * 8];
-
-					// Iterate over each pixel in the line
-					for (int j = 0; j != 8; ++j)
-					{
-						unsigned value = ((tileLine & (1 << (15 - j))) >> (14 - j)) |
-								         ((tileLine & (1 << (7  - j))) >> (7  - j));
-
-						// Nothing is drawn if the colour is zero
-						if (!value)
-							continue;
-
-						// Don't do anything if the pixel is off-screen
-						int x = sprites[i][1] - 8 + (sprites[i][3] & 0x20 ? 7 - j : j);
-						if (x < 0 || x >= 160)
-							continue;
-
-						// If the sprite is to be behind the background then the pixel is only drawn
-						// if the background colour is zero.
-						if ((gb_.mem_.ioPorts[LCDC] & LCDC_BG_DISPLAY) &&
-							(priorityMap[x] == 2 || (priorityMap[x] == 1 && (sprites[i][3] & 0x80))))
-							continue;
-
-						uint8_t r = 0,
-						        g = 0,
-						        b = 0;
-						if (gb_.gbc_)
-						{
-							// Select the colour from the palette
-							uint16_t colour = (palette[value * 2 + 1] << 8) | palette[value * 2];
-
-							r = static_cast<uint8_t>(round( (colour & 0x001f)        * COLOUR_RATIO));
-							g = static_cast<uint8_t>(round(((colour & 0x03e0) >> 5)  * COLOUR_RATIO));
-							b = static_cast<uint8_t>(round(((colour & 0x7c00) >> 10) * COLOUR_RATIO));
-						}
-						else
-						{
-							uint8_t palette = gb_.mem_.ioPorts[sprites[i][3] & 0x10 ? OBP1 : OBP0];
-
-							// Select the brightness from the palette
-							switch (value)
-							{
-							case 0: value =  palette & 0x03;        break;
-							case 1: value = (palette & 0x0c) >> 2;  break;
-							case 2: value = (palette & 0x30) >> 4;  break;
-							case 3: value = (palette & 0xc0) >> 6;  break;
-							}
-
-							// Set the actual pixel value
-							switch (value)
-							{
-							case 0: r = g = b = 255; break;
-							case 1: r = g = b = 170; break;
-							case 2: r = g = b = 85;  break;
-							case 3: r = g = b = 0;   break;
-							}
-						}
-
-						pixelBuffer[gb_.mem_.ioPorts[LCDC_Y] * 160 + x] = (r << 16) | (g << 8) | b;
-					}
+					sprites[spritesCount++] = sprite;
 				}
 			}
 
-			#ifdef DEBUG
+			// Draw the selected sprites; iterate backwards so the higher priority sprites
+			// overlay the lower priority ones
+			for (int i = spritesCount - 1; i >= 0; --i)
+			{
+				// Get the tile
+				uint16_t spriteIndex = sprites[i][2];
+				if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE)
+					spriteIndex &= 0xfe;
+				spriteIndex *= 16;
+				if (gb_.gbc_ && (sprites[i][3] & 0x08))
+					spriteIndex += VRAM_BANK_SIZE;
+				uint8_t *tile = &gb_.mem_.vram[spriteIndex];
+
+				unsigned tileLineIndex = gb_.mem_.ioPorts[LCDC_Y] - (sprites[i][0] - 16);
+				if (sprites[i][3] & 0x40)
+				{
+					if (gb_.mem_.ioPorts[LCDC] & LCDC_SPRITE_SIZE)
+						tileLineIndex = 15 - tileLineIndex;
+					else
+						tileLineIndex = 7  - tileLineIndex;
+				}
+				uint16_t tileLine = *reinterpret_cast<uint16_t *>(&tile[tileLineIndex * 2]);
+
+				// Select the palette
+				uint8_t *palette = &gbcSpritePalette[(sprites[i][3] & 0x07) * 8];
+
+				// Iterate over each pixel in the line
+				for (int j = 0; j != 8; ++j)
+				{
+					unsigned value = ((tileLine & (1 << (15 - j))) >> (14 - j)) |
+					                 ((tileLine & (1 << (7  - j))) >> (7  - j));
+
+					// Nothing is drawn if the colour is zero
+					if (!value)
+						continue;
+
+					// Don't do anything if the pixel is off-screen
+					int x = sprites[i][1] - 8 + (sprites[i][3] & 0x20 ? 7 - j : j);
+					if (x < 0 || x >= 160)
+						continue;
+
+					// If the sprite is to be behind the background then the pixel is only drawn
+					// if the background colour is zero.
+					if ((gb_.mem_.ioPorts[LCDC] & LCDC_BG_DISPLAY) &&
+						(priorityMap[x] == 2 || (priorityMap[x] == 1 && (sprites[i][3] & 0x80))))
+						continue;
+
+					uint8_t r = 0,
+					        g = 0,
+					        b = 0;
+					if (gb_.gbc_)
+					{
+						// Select the colour from the palette
+						uint16_t colour = (palette[value * 2 + 1] << 8) | palette[value * 2];
+
+						r = static_cast<uint8_t>(round( (colour & 0x001f)        * COLOUR_RATIO));
+						g = static_cast<uint8_t>(round(((colour & 0x03e0) >> 5)  * COLOUR_RATIO));
+						b = static_cast<uint8_t>(round(((colour & 0x7c00) >> 10) * COLOUR_RATIO));
+					}
+					else
+					{
+						uint8_t palette = gb_.mem_.ioPorts[sprites[i][3] & 0x10 ? OBP1 : OBP0];
+
+						// Select the brightness from the palette
+						switch (value)
+						{
+						case 0: value =  palette & 0x03;        break;
+						case 1: value = (palette & 0x0c) >> 2;  break;
+						case 2: value = (palette & 0x30) >> 4;  break;
+						case 3: value = (palette & 0xc0) >> 6;  break;
+						}
+
+						// Set the actual pixel value
+						switch (value)
+						{
+						case 0: r = g = b = 255; break;
+						case 1: r = g = b = 170; break;
+						case 2: r = g = b = 85;  break;
+						case 3: r = g = b = 0;   break;
+						}
+					}
+
+					pixelBuffer[gb_.mem_.ioPorts[LCDC_Y] * 160 + x] = (r << 16) | (g << 8) | b;
+				}
 			}
-			#endif
 		}
+
+		#ifdef DEBUG
+		}
+		#endif
 	}
 
 	// Reset the priority map
 	int cycles;
 	if (gb_.mem_.ioPorts[LCDC_Y] < VBLANK_START)
 	{
-		gb_.mem_.ioPorts[LCD_STAT] &= 0x7c;
+		gb_.mem_.ioPorts[LCD_STAT] &= 0xfc;
 		if (gb_.mem_.ioPorts[LCD_STAT] & LCD_STAT_HBLANK_INTR)
 			gb_.mem_.ioPorts[IF] |= LCD_STAT_INTR;
 		cycles = hblankCycles;
 		gb_.mem_.ioPorts[IF] |= VBLANK_INTR;
 
 		// Update the status register
-		gb_.mem_.ioPorts[LCD_STAT] &= 0x7c;
+		gb_.mem_.ioPorts[LCD_STAT] &= 0xfc;
 		gb_.mem_.ioPorts[LCD_STAT] |= 0x01;
 		if (gb_.mem_.ioPorts[LCD_STAT] & LCD_STAT_VBLANK_INTR)
 			gb_.mem_.ioPorts[IF] |= LCD_STAT_INTR;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.