PPU - Grayscale/Emphasis bit support & fixed behavior when rendering disabled and vram addr in palette range

This commit is contained in:
Souryo 2015-07-22 22:08:28 -04:00
parent 41ae3cdcd2
commit fb17e7b154
7 changed files with 158 additions and 64 deletions

View File

@ -325,6 +325,7 @@
<ClInclude Include="stdafx.h" /> <ClInclude Include="stdafx.h" />
<ClInclude Include="TriangleChannel.h" /> <ClInclude Include="TriangleChannel.h" />
<ClInclude Include="UNROM.h" /> <ClInclude Include="UNROM.h" />
<ClInclude Include="VideoDecoder.h" />
<ClInclude Include="VirtualController.h" /> <ClInclude Include="VirtualController.h" />
<ClInclude Include="VRC2_4.h" /> <ClInclude Include="VRC2_4.h" />
</ItemGroup> </ItemGroup>

View File

@ -221,6 +221,9 @@
<ClInclude Include="EmulationSettings.h"> <ClInclude Include="EmulationSettings.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="VideoDecoder.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="CPU.cpp"> <ClCompile Include="CPU.cpp">

View File

@ -6,28 +6,12 @@
PPU* PPU::Instance = nullptr; PPU* PPU::Instance = nullptr;
IVideoDevice *PPU::VideoDevice = nullptr; IVideoDevice *PPU::VideoDevice = nullptr;
uint32_t PPU_PALETTE_RGB[] = {
0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E,
0xFF6E0040, 0xFF6C0600, 0xFF561D00, 0xFF333500, 0xFF0B4800,
0xFF005200, 0xFF004F08, 0xFF00404D, 0xFF000000, 0xFF000000,
0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE,
0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00,
0xFF388700, 0xFF0C9300, 0xFF008F32, 0xFF007C8D, 0xFF000000,
0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFF64B0FF, 0xFF9290FF,
0xFFC676FF, 0xFFF36AFF, 0xFFFE6ECC, 0xFFFE8170, 0xFFEA9E22,
0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE,
0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFFC0DFFF,
0xFFD3D2FF, 0xFFE8C8FF, 0xFFFBC2FF, 0xFFFEC4EA, 0xFFFECCC5,
0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC,
0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000,
};
PPU::PPU(MemoryManager *memoryManager) PPU::PPU(MemoryManager *memoryManager)
{ {
PPU::Instance = this; PPU::Instance = this;
_memoryManager = memoryManager; _memoryManager = memoryManager;
_outputBuffer = new uint32_t[256 * 240]; _outputBuffer = new uint16_t[256 * 240];
Reset(); Reset();
} }
@ -170,7 +154,7 @@ uint8_t PPU::ReadPaletteRAM(uint16_t addr)
if(addr == 0x10 || addr == 0x14 || addr == 0x18 || addr == 0x1C) { if(addr == 0x10 || addr == 0x14 || addr == 0x18 || addr == 0x1C) {
addr &= ~0x10; addr &= ~0x10;
} }
return _paletteRAM[addr]; return (_paletteRAM[addr] & _paletteRamMask);
} }
void PPU::WritePaletteRAM(uint16_t addr, uint8_t value) void PPU::WritePaletteRAM(uint16_t addr, uint8_t value)
@ -185,18 +169,18 @@ void PPU::WritePaletteRAM(uint16_t addr, uint8_t value)
uint32_t PPU::GetBGPaletteEntry(uint32_t paletteOffset, uint32_t pixel) uint32_t PPU::GetBGPaletteEntry(uint32_t paletteOffset, uint32_t pixel)
{ {
if(pixel == 0) { if(pixel == 0) {
return ReadPaletteRAM(0x3F00) & 0x3F; return ReadPaletteRAM(0x3F00) | _intensifyColorBits;
} else { } else {
return ReadPaletteRAM(0x3F00 + paletteOffset + pixel) & 0x3F; return ReadPaletteRAM(0x3F00 + paletteOffset + pixel) | _intensifyColorBits;
} }
} }
uint32_t PPU::GetSpritePaletteEntry(uint32_t paletteOffset, uint32_t pixel) uint32_t PPU::GetSpritePaletteEntry(uint32_t paletteOffset, uint32_t pixel)
{ {
if(pixel == 0) { if(pixel == 0) {
return ReadPaletteRAM(0x3F00) & 0x3F; return ReadPaletteRAM(0x3F00) | _intensifyColorBits;
} else { } else {
return ReadPaletteRAM(0x3F10 + paletteOffset + pixel) & 0x3F; return ReadPaletteRAM(0x3F10 + paletteOffset + pixel) | _intensifyColorBits;
} }
} }
@ -236,9 +220,21 @@ void PPU::SetMaskRegister(uint8_t value)
_flags.SpriteMask = (_state.Mask & 0x04) == 0x04; _flags.SpriteMask = (_state.Mask & 0x04) == 0x04;
_flags.BackgroundEnabled = (_state.Mask & 0x08) == 0x08; _flags.BackgroundEnabled = (_state.Mask & 0x08) == 0x08;
_flags.SpritesEnabled = (_state.Mask & 0x10) == 0x10; _flags.SpritesEnabled = (_state.Mask & 0x10) == 0x10;
_flags.IntensifyRed = (_state.Mask & 0x20) == 0x20;
_flags.IntensifyGreen = (_state.Mask & 0x40) == 0x40;
_flags.IntensifyBlue = (_state.Mask & 0x80) == 0x80; _flags.IntensifyBlue = (_state.Mask & 0x80) == 0x80;
//"Bit 0 controls a greyscale mode, which causes the palette to use only the colors from the grey column: $00, $10, $20, $30. This is implemented as a bitwise AND with $30 on any value read from PPU $3F00-$3FFF"
_paletteRamMask = _flags.Grayscale ? 0x30 : 0x3F;
if(_nesModel == NesModel::NTSC) {
_flags.IntensifyRed = (_state.Mask & 0x20) == 0x20;
_flags.IntensifyGreen = (_state.Mask & 0x40) == 0x40;
_intensifyColorBits = (value & 0xE0) << 1;
} else {
//"Note that on the Dendy and PAL NES, the green and red bits swap meaning."
_flags.IntensifyRed = (_state.Mask & 0x40) == 0x40;
_flags.IntensifyGreen = (_state.Mask & 0x20) == 0x20;
_intensifyColorBits = (_flags.IntensifyRed ? 0x40 : 0x00) | (_flags.IntensifyGreen ? 0x80 : 0x00) | (_flags.IntensifyBlue ? 0x100 : 0x00);
}
} }
void PPU::UpdateStatusFlag() void PPU::UpdateStatusFlag()
@ -396,50 +392,55 @@ void PPU::ShiftTileRegisters()
void PPU::DrawPixel() void PPU::DrawPixel()
{ {
//This is called 3.7 million times per second - needs to be as fast as possible. //This is called 3.7 million times per second - needs to be as fast as possible.
uint8_t offset = _state.XScroll; uint16_t &pixel = _outputBuffer[(_scanline << 8) + _cycle - 1];
uint32_t backgroundColor = 0; if(IsRenderingEnabled() || ((_state.VideoRamAddr & 0x3F00) != 0x3F00)) {
uint32_t &pixel = _outputBuffer[(_scanline << 8) + _cycle - 1]; uint8_t offset = _state.XScroll;
uint32_t backgroundColor = 0;
if((_cycle > 8 || _flags.BackgroundMask) && _flags.BackgroundEnabled) { if((_cycle > 8 || _flags.BackgroundMask) && _flags.BackgroundEnabled) {
//BackgroundMask = false: Hide background in leftmost 8 pixels of screen //BackgroundMask = false: Hide background in leftmost 8 pixels of screen
backgroundColor = (((_state.LowBitShift << offset) & 0x8000) >> 15) | (((_state.HighBitShift << offset) & 0x8000) >> 14); backgroundColor = (((_state.LowBitShift << offset) & 0x8000) >> 15) | (((_state.HighBitShift << offset) & 0x8000) >> 14);
} }
if((_cycle > 8 || _flags.SpriteMask) && _flags.SpritesEnabled) { if((_cycle > 8 || _flags.SpriteMask) && _flags.SpritesEnabled) {
//SpriteMask = true: Hide sprites in leftmost 8 pixels of screen //SpriteMask = true: Hide sprites in leftmost 8 pixels of screen
for(uint8_t i = 0; i < _spriteCount; i++) { for(uint8_t i = 0; i < _spriteCount; i++) {
int32_t shift = -((int32_t)_spriteX[i] - (int32_t)_cycle + 1); int32_t shift = -((int32_t)_spriteX[i] - (int32_t)_cycle + 1);
if(shift >= 0 && shift < 8) { if(shift >= 0 && shift < 8) {
uint32_t spriteColor; uint32_t spriteColor;
if(_spriteTiles[i].HorizontalMirror) { if(_spriteTiles[i].HorizontalMirror) {
spriteColor = ((_spriteTiles[i].LowByte >> shift) & 0x01) | ((_spriteTiles[i].HighByte >> shift) & 0x01) << 1; spriteColor = ((_spriteTiles[i].LowByte >> shift) & 0x01) | ((_spriteTiles[i].HighByte >> shift) & 0x01) << 1;
} else { } else {
spriteColor = ((_spriteTiles[i].LowByte << shift) & 0x80) >> 7 | ((_spriteTiles[i].HighByte << shift) & 0x80) >> 6; spriteColor = ((_spriteTiles[i].LowByte << shift) & 0x80) >> 7 | ((_spriteTiles[i].HighByte << shift) & 0x80) >> 6;
}
if(spriteColor != 0) {
//First sprite without a 00 color, use it.
if(i == 0 && backgroundColor != 0 && _sprite0Visible && _cycle != 256 && _flags.BackgroundEnabled) {
//"The hit condition is basically sprite zero is in range AND the first sprite output unit is outputting a non-zero pixel AND the background drawing unit is outputting a non-zero pixel."
//"Sprite zero hits do not register at x=255" (cycle 256)
//"... provided that background and sprite rendering are both enabled"
//"Should always miss when Y >= 239"
_statusFlags.Sprite0Hit = true;
} }
if(backgroundColor == 0 || !_spriteTiles[i].BackgroundPriority) { if(spriteColor != 0) {
//Check sprite priority //First sprite without a 00 color, use it.
pixel = PPU_PALETTE_RGB[GetSpritePaletteEntry(_spriteTiles[i].PaletteOffset, spriteColor)]; if(i == 0 && backgroundColor != 0 && _sprite0Visible && _cycle != 256 && _flags.BackgroundEnabled) {
return; //"The hit condition is basically sprite zero is in range AND the first sprite output unit is outputting a non-zero pixel AND the background drawing unit is outputting a non-zero pixel."
} //"Sprite zero hits do not register at x=255" (cycle 256)
//"... provided that background and sprite rendering are both enabled"
//"Should always miss when Y >= 239"
_statusFlags.Sprite0Hit = true;
}
break; if(backgroundColor == 0 || !_spriteTiles[i].BackgroundPriority) {
//Check sprite priority
pixel = GetSpritePaletteEntry(_spriteTiles[i].PaletteOffset, spriteColor);
return;
}
break;
}
} }
} }
} }
pixel = GetBGPaletteEntry(offset + ((_cycle - 1) & 0x07) < 8 ? _previousTile.PaletteOffset : _currentTile.PaletteOffset, backgroundColor);
} else {
//"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color."
pixel = ReadPaletteRAM(_state.VideoRamAddr) | _intensifyColorBits;
} }
pixel = PPU_PALETTE_RGB[GetBGPaletteEntry(offset + ((_cycle - 1) & 0x07) < 8 ? _previousTile.PaletteOffset : _currentTile.PaletteOffset, backgroundColor)];
} }
void PPU::ProcessPreVBlankScanline() void PPU::ProcessPreVBlankScanline()
@ -686,6 +687,8 @@ void PPU::StreamState(bool saving)
Stream<bool>(_flags.IntensifyRed); Stream<bool>(_flags.IntensifyRed);
Stream<bool>(_flags.IntensifyGreen); Stream<bool>(_flags.IntensifyGreen);
Stream<bool>(_flags.IntensifyBlue); Stream<bool>(_flags.IntensifyBlue);
Stream<uint8_t>(_paletteRamMask);
Stream<uint16_t>(_intensifyColorBits);
Stream<bool>(_statusFlags.SpriteOverflow); Stream<bool>(_statusFlags.SpriteOverflow);
Stream<bool>(_statusFlags.Sprite0Hit); Stream<bool>(_statusFlags.Sprite0Hit);

View File

@ -104,7 +104,7 @@ class PPU : public IMemoryHandler, public Snapshotable
uint8_t _spriteRAM[0x100]; uint8_t _spriteRAM[0x100];
uint8_t _secondarySpriteRAM[0x20]; uint8_t _secondarySpriteRAM[0x20];
uint32_t *_outputBuffer; uint16_t *_outputBuffer;
NesModel _nesModel; NesModel _nesModel;
uint16_t _vblankEnd; uint16_t _vblankEnd;
@ -112,6 +112,9 @@ class PPU : public IMemoryHandler, public Snapshotable
PPUControlFlags _flags; PPUControlFlags _flags;
PPUStatusFlags _statusFlags; PPUStatusFlags _statusFlags;
uint16_t _intensifyColorBits;
uint8_t _paletteRamMask;
bool _doNotSetVBFlag = false; bool _doNotSetVBFlag = false;
TileInfo _currentTile; TileInfo _currentTile;

67
Core/VideoDecoder.h Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#include "stdafx.h"
#include <algorithm>
const uint32_t PPU_PALETTE_ARGB[] = {
0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E,
0xFF6E0040, 0xFF6C0600, 0xFF561D00, 0xFF333500, 0xFF0B4800,
0xFF005200, 0xFF004F08, 0xFF00404D, 0xFF000000, 0xFF000000,
0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE,
0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00,
0xFF388700, 0xFF0C9300, 0xFF008F32, 0xFF007C8D, 0xFF000000,
0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFF64B0FF, 0xFF9290FF,
0xFFC676FF, 0xFFF36AFF, 0xFFFE6ECC, 0xFFFE8170, 0xFFEA9E22,
0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE,
0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFFC0DFFF,
0xFFD3D2FF, 0xFFE8C8FF, 0xFFFBC2FF, 0xFFFEC4EA, 0xFFFECCC5,
0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC,
0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000,
};
class VideoDecoder
{
private:
static uint32_t ProcessIntensifyBits(uint16_t ppuPixel)
{
uint32_t pixelOutput = PPU_PALETTE_ARGB[ppuPixel & 0x3F];
//Incorrect emphasis bit implementation, but will do for now.
float redChannel = (float)((pixelOutput & 0xFF0000) >> 16);
float greenChannel = (float)((pixelOutput & 0xFF00) >> 8);
float blueChannel = (float)(pixelOutput & 0xFF);
if(ppuPixel & 0x40) {
//Intensify red
redChannel *= 1.1f;
greenChannel *= 0.9f;
blueChannel *= 0.9f;
}
if(ppuPixel & 0x80) {
//Intensify green
greenChannel *= 1.1f;
redChannel *= 0.9f;
blueChannel *= 0.9f;
}
if(ppuPixel & 0x100) {
//Intensify blue
blueChannel *= 1.1f;
redChannel *= 0.9f;
greenChannel *= 0.9f;
}
uint8_t r, g, b;
r = (uint8_t)fmin(redChannel, 255);
g = (uint8_t)fmin(greenChannel, 255);
b = (uint8_t)fmin(blueChannel, 255);
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
public:
static void DecodeFrame(uint16_t* inputBuffer, uint32_t* outputBuffer)
{
for(uint32_t i = 0; i < 256*240; i++) {
((uint32_t*)outputBuffer)[i] = ProcessIntensifyBits(inputBuffer[i]);
}
}
};

View File

@ -5,6 +5,7 @@
#include "DirectXTK/DDSTextureLoader.h" #include "DirectXTK/DDSTextureLoader.h"
#include "DirectXTK/WICTextureLoader.h" #include "DirectXTK/WICTextureLoader.h"
#include "../Core/PPU.h" #include "../Core/PPU.h"
#include "../Core/VideoDecoder.h"
#include "../Core/EmulationSettings.h" #include "../Core/EmulationSettings.h"
#include "../Core/MessageManager.h" #include "../Core/MessageManager.h"
#include "../Utilities/UTF8Util.h" #include "../Utilities/UTF8Util.h"
@ -76,6 +77,13 @@ namespace NES
_overlayBuffer = nullptr; _overlayBuffer = nullptr;
} }
if(_ppuOutputBuffer) {
delete[] _ppuOutputBuffer;
}
if(_ppuOutputSecondaryBuffer) {
delete[] _ppuOutputSecondaryBuffer;
}
} }
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
@ -222,8 +230,12 @@ namespace NES
_videoRAM = new uint8_t[_screenBufferSize]; _videoRAM = new uint8_t[_screenBufferSize];
_nextFrameBuffer = new uint8_t[_screenBufferSize]; _nextFrameBuffer = new uint8_t[_screenBufferSize];
_ppuOutputBuffer = new uint16_t[_screenWidth * _screenHeight];
_ppuOutputSecondaryBuffer = new uint16_t[_screenWidth * _screenHeight];
memset(_videoRAM, 0x00, _screenBufferSize); memset(_videoRAM, 0x00, _screenBufferSize);
memset(_nextFrameBuffer, 0x00, _screenBufferSize); memset(_nextFrameBuffer, 0x00, _screenBufferSize);
memset(_ppuOutputBuffer, 0x00, _screenWidth * _screenHeight * sizeof(uint16_t));
memset(_ppuOutputSecondaryBuffer, 0x00, _screenWidth * _screenHeight * sizeof(uint16_t));
_pTexture = CreateTexture(_screenWidth, _screenHeight); _pTexture = CreateTexture(_screenWidth, _screenHeight);
if(!_pTexture) { if(!_pTexture) {
@ -364,6 +376,12 @@ namespace NES
void Renderer::DrawNESScreen() void Renderer::DrawNESScreen()
{ {
_frameLock.Acquire();
memcpy(_ppuOutputSecondaryBuffer, _ppuOutputBuffer, 256 * 240 * sizeof(uint16_t));
_frameLock.Release();
VideoDecoder::DecodeFrame(_ppuOutputSecondaryBuffer, (uint32_t*)_nextFrameBuffer);
RECT sourceRect; RECT sourceRect;
sourceRect.left = 0; sourceRect.left = 0;
sourceRect.right = _screenWidth; sourceRect.right = _screenWidth;
@ -383,9 +401,7 @@ namespace NES
dd.DepthPitch = _screenBufferSize; dd.DepthPitch = _screenBufferSize;
_pDeviceContext->Map(_pTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &dd); _pDeviceContext->Map(_pTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &dd);
_frameLock.Acquire();
memcpy(dd.pData, _nextFrameBuffer, _screenBufferSize); memcpy(dd.pData, _nextFrameBuffer, _screenBufferSize);
_frameLock.Release();
_pDeviceContext->Unmap(_pTexture, 0); _pDeviceContext->Unmap(_pTexture, 0);
ID3D11ShaderResourceView *nesOutputBuffer = GetShaderResourceView(_pTexture); ID3D11ShaderResourceView *nesOutputBuffer = GetShaderResourceView(_pTexture);
@ -568,12 +584,11 @@ namespace NES
void Renderer::UpdateFrame(void* frameBuffer) void Renderer::UpdateFrame(void* frameBuffer)
{ {
_frameChanged = true;
_frameLock.Acquire(); _frameLock.Acquire();
memcpy(_nextFrameBuffer, frameBuffer, 256 * 240 * 4); memcpy(_ppuOutputBuffer, frameBuffer, 256 * 240 * 2);
_frameLock.Release(); _frameLock.Release();
_frameChanged = true;
_frameCount++; _frameCount++;
} }

View File

@ -40,6 +40,8 @@ namespace NES {
bool _frameChanged = true; bool _frameChanged = true;
uint8_t* _nextFrameBuffer = nullptr; uint8_t* _nextFrameBuffer = nullptr;
uint16_t* _ppuOutputBuffer = nullptr;
uint16_t* _ppuOutputSecondaryBuffer = nullptr;
SimpleLock _frameLock; SimpleLock _frameLock;
Timer _fpsTimer; Timer _fpsTimer;