Files
Adrian "asie" Siekierka 5bb64aacf0 WS: Initial Pocket Challenge V2 support (#95)
The Pocket Challenge V2 is an education handheld based on the ASWAN SoC, as used in the WonderSwan. It uses the same boot ROM (with an early exit), but has some implementation changes:

    The LCD icon segment area is not present (on the display panel only).
    There is no onboard internal EEPROM.
    The input keypad layout differs.
    The headphone output is always used with a custom onboard DAC, as opposed to relying on the SoC's own PWM DAC for the internal speaker.

This PR implements support for all of this. Support for the PCv2-exclusive cartridge mapper (KARNAK) will be added by a separate PR.
2026-05-17 01:11:02 +09:00

756 lines
24 KiB
C++

#include "pch.h"
#include "WS/WsConsole.h"
#include "WS/WsTimer.h"
#include "WS/WsControlManager.h"
#include "WS/WsMemoryManager.h"
#include "WS/APU/WsApu.h"
#include "Shared/EmuSettings.h"
#include "Shared/NotificationManager.h"
#include "Shared/RewindManager.h"
#include "Shared/Video/VideoDecoder.h"
#include "Shared/RenderedFrame.h"
#include "Shared/EventType.h"
#include "Shared/MessageManager.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/Serializer.h"
WsPpu::WsPpu(Emulator* emu, WsConsole* console, WsMemoryManager* memoryManager, WsTimer* timer, uint8_t* vram)
{
_emu = emu;
_console = console;
_memoryManager = memoryManager;
_timer = timer;
_vram = vram;
_state.LastScanline = 158;
if(_console->GetModel() != WsModel::SwanCrystal) {
_state.BackPorchScanline = 155;
}
_state.ShowVolumeIconFrame = UINT32_MAX;
_screenWidth = WsConstants::ScreenWidth;
_screenHeight = WsConstants::ScreenHeight;
if(console->GetModel() == WsModel::Monochrome) {
_screenHeight += 13;
} else if(console->IsColorModel()) {
_screenWidth += 13;
}
_outputBuffers[0] = new uint16_t[WsConstants::MaxPixelCount];
_outputBuffers[1] = new uint16_t[WsConstants::MaxPixelCount];
memset(_outputBuffers[0], 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
memset(_outputBuffers[1], 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
_currentBuffer = _outputBuffers[0];
_showIcons = _emu->GetSettings()->GetWsConfig().LcdShowIcons;
}
WsPpu::~WsPpu()
{
delete[] _outputBuffers[0];
delete[] _outputBuffers[1];
}
void WsPpu::SetVideoMode(WsVideoMode mode)
{
_state.NextMode = mode;
}
void WsPpu::ProcessHblank()
{
_timer->TickHorizontalTimer();
if(_state.Scanline < WsConstants::ScreenHeight) {
switch(_state.Mode) {
case WsVideoMode::Monochrome: DrawScanline<WsVideoMode::Monochrome>(); break;
case WsVideoMode::Color2bpp: DrawScanline<WsVideoMode::Color2bpp>(); break;
case WsVideoMode::Color4bpp: DrawScanline<WsVideoMode::Color4bpp>(); break;
case WsVideoMode::Color4bppPacked: DrawScanline<WsVideoMode::Color4bppPacked>(); break;
}
}
}
void WsPpu::ProcessEndOfScanline()
{
_state.Cycle = 0;
_state.Scanline++;
_state.BgLayers[0].Latch();
_state.BgLayers[1].Latch();
_state.BgWindow.Latch();
_state.SpriteWindow.Latch();
_state.SpritesEnabledLatch = _state.SpritesEnabled;
_state.DrawOutsideBgWindowLatch = _state.DrawOutsideBgWindow;
if(_state.Scanline > _state.LastScanline) {
if(_state.Scanline <= 145) {
//Support sending frame to LCD even when number of scanlines is less than the 144px resolution
SendFrame();
}
_state.Mode = _state.NextMode;
_state.Scanline = 0;
_emu->ProcessEvent(EventType::StartFrame, CpuType::Ws);
_currentBuffer = _currentBuffer == _outputBuffers[0] ? _outputBuffers[1] : _outputBuffers[0];
_showIcons = _emu->GetSettings()->GetWsConfig().LcdShowIcons;
} else if(_state.Scanline == 145) {
SendFrame();
} else if(_state.Scanline == 144) {
_timer->TickVerticalTimer();
((WsControlManager*)_console->GetControlManager())->TriggerKeyIrq();
_console->GetMemoryManager()->SetIrqSource(WsIrqSource::VerticalBlank);
}
if(_state.Scanline == _state.IrqScanline) {
_console->GetMemoryManager()->SetIrqSource(WsIrqSource::Scanline);
}
}
template<WsVideoMode mode>
void WsPpu::DrawScanline()
{
uint8_t rowIndex = _state.Scanline & 0x01;
std::fill(_rowData[rowIndex], _rowData[rowIndex] + WsConstants::ScreenWidth, PixelData {});
DrawSprites<mode>();
DrawBackground<mode, 0>();
DrawBackground<mode, 1>();
}
template<WsVideoMode mode>
void WsPpu::DrawSprites()
{
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
if(!_state.SpritesEnabled || cfg.DisableSprites) {
return;
}
uint16_t scanline = _state.Scanline;
uint8_t rowIndex = _state.Scanline & 0x01;
constexpr int tileSize = mode >= WsVideoMode::Color4bpp ? 32 : 16;
constexpr int tileBytesPerRow = mode >= WsVideoMode::Color4bpp ? 4 : 2;
constexpr int bank0Addr = mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
int spriteCount = 0;
for(int i = 0; i < _state.SpriteCountLatch; i++) {
uint16_t addr = i * 4;
uint8_t attributes = _spriteRam[addr + 1];
bool highPriority = attributes & 0x20;
uint8_t y = _spriteRam[addr + 2];
uint8_t tileRow = scanline - y;
if(tileRow < 8 && scanline < y + 8) {
spriteCount++;
bool showOutsideWindow = attributes & 0x10;
bool vMirror = attributes & 0x80;
bool hMirror = attributes & 0x40;
uint16_t tileIndex = _spriteRam[addr] | ((attributes & 0x01) << 8);
uint8_t palette = ((attributes >> 1) & 0x07) + 8;
uint8_t sprX = _spriteRam[addr + 3];
if(vMirror) {
tileRow = 7 - tileRow;
}
uint16_t tileDataAddr = (bank0Addr + tileIndex * tileSize + tileRow * tileBytesPerRow);
for(int j = 0; j < 8; j++) {
uint8_t x = sprX + j;
if(x >= 224) {
continue;
} else if(_rowData[rowIndex][x].Priority > 0) {
continue;
} else if(_state.SpriteWindow.EnabledLatch && showOutsideWindow == _state.SpriteWindow.IsInsideWindow(x, scanline)) {
//Don't draw this pixel, it's outside/inside the window and should only be drawn on the other side
continue;
}
int tileColumn = j;
if(hMirror) {
tileColumn = 7 - tileColumn;
}
uint8_t color = GetPixelColor<mode>(tileDataAddr, tileColumn);
if(color != 0 || (!(palette & 0x04) && mode <= WsVideoMode::Color2bpp)) {
_rowData[rowIndex][x] = { palette, color, highPriority ? (uint8_t)2 : (uint8_t)1 };
}
}
}
if(spriteCount >= 32) {
break;
}
}
}
template<WsVideoMode mode, int layerIndex>
void WsPpu::DrawBackground()
{
constexpr int tileSize = mode >= WsVideoMode::Color4bpp ? 32 : 16;
constexpr int tileBytesPerRow = mode >= WsVideoMode::Color4bpp ? 4 : 2;
constexpr int bank0Addr = mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
constexpr int bank1Addr = mode >= WsVideoMode::Color4bpp ? 0x8000 : 0x4000;
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
uint8_t rowIndex = _state.Scanline & 0x01;
WsBgLayer& layer = _state.BgLayers[layerIndex];
if(cfg.HideBgLayers[layerIndex] || !layer.EnabledLatch) {
return;
}
uint16_t scanline = _state.Scanline;
uint16_t layerAddr = (uint16_t)(layer.MapAddressLatch & (_console->IsColorModel() ? 0x7FFF : 0x3FFF));
for(int cycle = 0; cycle < WsConstants::ScreenWidth; cycle++) {
int y = (scanline + layer.ScrollYLatch) & 0xFF;
int x = (cycle + layer.ScrollXLatch) & 0xFF;
int row = y / 8;
int column = x / 8;
uint16_t tilemapAddr = layerAddr + row * 32 * 2 + column * 2;
uint16_t tilemapData = _vram[tilemapAddr] | (_vram[tilemapAddr + 1] << 8);
int tileRow = y & 0x07;
int tileColumn = x & 0x07;
int counter = 8 - tileColumn;
uint16_t tileIndex = tilemapData & 0x1FF;
uint8_t palette = (tilemapData >> 9) & 0x0F;
bool vMirror = tilemapData & 0x8000;
bool hMirror = tilemapData & 0x4000;
if(hMirror) {
tileColumn = 7 - tileColumn;
}
if(vMirror) {
tileRow = 7 - tileRow;
}
uint16_t tilesetAddr = mode >= WsVideoMode::Color2bpp && (tilemapData & 0x2000) ? bank1Addr : bank0Addr;
uint16_t tileDataAddr = (tilesetAddr + tileIndex * tileSize + tileRow * tileBytesPerRow);
for(int i = cycle, end = std::min<int>(cycle + counter, WsConstants::ScreenWidth); i < end; i++) {
uint8_t color = GetPixelColor<mode>(tileDataAddr, tileColumn);
tileColumn += (hMirror ? -1 : 1);
if(_rowData[rowIndex][i].Priority >= layerIndex + 1) {
continue;
}
if constexpr(layerIndex == 1) {
if(_state.BgWindow.EnabledLatch && _state.DrawOutsideBgWindowLatch == _state.BgWindow.IsInsideWindow(i, scanline)) {
//Pixel is hidden by window
continue;
}
}
//"In two bit per pixel modes: Palettes 0-3 and 8-11 are opaque. For these, index zero is treated as opaque."
if(color != 0 || (!(palette & 0x04) && mode <= WsVideoMode::Color2bpp)) {
_rowData[rowIndex][i] = { palette, color, (uint8_t)(layerIndex + 1) };
}
}
cycle += counter - 1;
}
}
template<WsVideoMode mode>
uint16_t WsPpu::GetPixelColor(uint16_t tileAddr, uint8_t column)
{
switch(mode) {
case WsVideoMode::Monochrome: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6);
}
case WsVideoMode::Color2bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6);
}
case WsVideoMode::Color4bpp: {
uint8_t tileData = _vram[tileAddr];
uint8_t tileData2 = _vram[tileAddr + 1];
uint8_t tileData3 = _vram[tileAddr + 2];
uint8_t tileData4 = _vram[tileAddr + 3];
return (
((tileData << column) & 0x80) >> 7 |
((tileData2 << column) & 0x80) >> 6 |
((tileData3 << column) & 0x80) >> 5 |
((tileData4 << column) & 0x80) >> 4);
}
case WsVideoMode::Color4bppPacked:
return (_vram[tileAddr + column / 2] >> (column & 0x01 ? 0 : 4)) & 0x0F;
}
return 0;
}
void WsPpu::ProcessSpriteCopy()
{
if(_state.Cycle == 0) {
_state.SpriteCountLatch = _state.SpriteCount;
}
uint16_t baseAddr = _state.SpriteTableAddress & (_console->IsColorModel() ? 0x7FFF : 0x3FFF);
int i = _state.Cycle << 1;
_spriteRam[i] = _vram[baseAddr + (((_state.FirstSpriteIndex * 4) + i) & 0x1FF)];
_spriteRam[i + 1] = _vram[baseAddr + (((_state.FirstSpriteIndex * 4) + i + 1) & 0x1FF)];
}
void WsPpu::DrawIcons()
{
//11x11 1bpp icons (same shapes as ares)
static constexpr uint16_t power[11] = { 0x70, 0xD8, 0x18C, 0x18C, 0x3DE, 0x3FE, 0x3FE, 0x1FC, 0x1FC, 0xF8, 0x70 };
//static constexpr uint16_t initialized[11] = { 0, 0x3FE, 0x7FF, 0x603, 0x603, 0x603, 0x7FF, 0x7FF, 0x7FF, 0x3FE, 0 };
static constexpr uint16_t sleep[11] = { 0x80, 0x250, 0x11B, 0x1F, 0x3E, 0xFC, 0x3C, 0x1E, 0x3E, 0x33, 0 };
//static constexpr uint16_t lowBattery[11] = { 0x70, 0xD8, 0x88, 0x88, 0x88, 0x88, 0x88, 0x98, 0xB8, 0x88, 0xF8 };
static constexpr uint16_t volumeWsOff[11] = { 0, 0x10, 0x18, 0x1C, 0x1E, 0x1E, 0x1E, 0x1C, 0x18, 0x10, 0 };
static constexpr uint16_t volumeWsLow[11] = { 0, 0x10, 0x18, 0x5C, 0x9E, 0x9E, 0x9E, 0x5C, 0x18, 0x10, 0 };
static constexpr uint16_t volumeWsHigh[11] = { 0, 0x90, 0x118, 0x25C, 0x29E, 0x29E, 0x29E, 0x25C, 0x118, 0x90, 0 };
static constexpr uint16_t volumeWscOff[11] = { 0, 0, 0, 0, 0, 0, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
static constexpr uint16_t volumeWscLow[11] = { 0, 0, 0, 0xF8, 0x104, 0, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
static constexpr uint16_t volumeWscMed[11] = { 0x1FC, 0x202, 0x401, 0, 0x70, 0x88, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
static constexpr uint16_t volumeWscHigh[11] = { 0x1FC, 0x202, 0x4F9, 0x104, 0x272, 0x88, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
static constexpr uint16_t headphones[11] = { 0x70, 0x18C, 0x306, 0x20B, 0x41F, 0x40E, 0x447, 0x2E2, 0x370, 0x1F8, 0xD0 };
static constexpr uint16_t verticalIcon[11] = { 0x6, 0x1E, 0x38, 0x3BF, 0x7FF, 0x7FF, 0x7FF, 0x3BF, 0x38, 0x1E, 0x6 };
static constexpr uint16_t horizontalIcon[11] = { 0x70, 0xF8, 0xF8, 0xF8, 0x70, 0x1FC, 0x3FE, 0x3FE, 0x6FB, 0x6FB, 0xF8 };
static constexpr uint16_t aux3[11] = { 0, 0x70, 0x1FC, 0x1FC, 0x3FE, 0x3FE, 0x3FE, 0x1FC, 0x1FC, 0x70, 0 };
static constexpr uint16_t aux2[11] = { 0, 0, 0x70, 0xF8, 0x1FC, 0x1FC, 0x1FC, 0xF8, 0x70, 0, 0 };
static constexpr uint16_t aux1[11] = { 0, 0, 0, 0, 0x70, 0x70, 0x70, 0, 0, 0, 0 };
if(!_console->IsColorModel()) {
uint16_t* start = _currentBuffer + (WsConstants::ScreenWidth * WsConstants::ScreenHeight);
std::fill(start, start + WsConstants::ScreenWidth * 13, 0xFFF);
} else {
for(int i = 0; i < WsConstants::ScreenHeight; i++) {
uint16_t* start = _currentBuffer + (i * _screenWidth) + WsConstants::ScreenWidth;
std::fill(start, start + 13, 0);
}
}
DrawIcon(true, power, 0);
//DrawIcon(true, initialized, 13);
DrawIcon(_state.Icons.Sleep, sleep, 26);
//DrawIcon(true, lowBattery, 39, 144);
if(_state.ShowVolumeIconFrame <= _state.FrameCount && _state.FrameCount - _state.ShowVolumeIconFrame < 128) {
//Show speaker/headphone icons if sound button was pressed within the last 128 frames
if(_console->GetAudioMode() == WsAudioMode::Headphones) {
DrawIcon(true, headphones, 65);
} else {
if(!_console->IsColorModel()) {
switch(_console->GetApu()->GetMasterVolume()) {
default:
case 0: DrawIcon(true, volumeWsOff, 52); break;
case 1: DrawIcon(true, volumeWsLow, 52); break;
case 2: DrawIcon(true, volumeWsHigh, 52); break;
}
} else {
switch(_console->GetApu()->GetMasterVolume()) {
default:
case 0: DrawIcon(true, volumeWscOff, 52); break;
case 1: DrawIcon(true, volumeWscLow, 52); break;
case 2: DrawIcon(true, volumeWscMed, 52); break;
case 3: DrawIcon(true, volumeWscHigh, 52); break;
}
}
}
}
DrawIcon(_state.Icons.Vertical, verticalIcon, 78);
DrawIcon(_state.Icons.Horizontal, horizontalIcon, 91);
DrawIcon(_state.Icons.Aux3, aux3, 104);
DrawIcon(_state.Icons.Aux2, aux2, 117);
DrawIcon(_state.Icons.Aux1, aux1, 130);
}
void WsPpu::DrawIcon(bool visible, const uint16_t icon[11], uint8_t position)
{
if(!visible || _console->GetModel() == WsModel::PocketChallenge) {
return;
}
uint8_t xPos;
uint8_t yPos;
uint16_t color;
if(_console->GetModel() == WsModel::Monochrome) {
xPos = position + 1;
yPos = 145;
color = 0;
} else {
xPos = 225;
yPos = WsConstants::ScreenHeight - 13 - position;
color = 0xFFF;
}
for(int y = 0; y < 11; y++) {
uint16_t basePos = (yPos + y) * _screenWidth + xPos;
for(int x = 0; x < 11; x++) {
if(icon[y] & (1 << x)) {
_currentBuffer[basePos + x] = color;
}
}
}
}
uint16_t WsPpu::GetVisibleScanlineCount()
{
uint16_t scanlineCount = GetScanlineCount();
return scanlineCount <= WsConstants::ScreenHeight ? scanlineCount - 1 : WsConstants::ScreenHeight;
}
uint16_t* WsPpu::GetScreenBuffer(bool prevFrame)
{
return prevFrame ? ((_currentBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0]) : _currentBuffer;
}
void WsPpu::DebugSendFrame()
{
int offset = std::max(0, (int)(_state.Cycle + (_state.Scanline - 1) * _screenWidth));
int pixelsToClear = WsConstants::MaxPixelCount - offset;
if(pixelsToClear > 0) {
memset(_currentBuffer + offset, 0, pixelsToClear * sizeof(uint16_t));
}
uint16_t width = _showIcons ? _screenWidth : WsConstants::ScreenWidth;
uint16_t height = _showIcons ? _screenHeight : WsConstants::ScreenHeight;
RenderedFrame frame(_currentBuffer, width, height, 1.0, _state.FrameCount);
_emu->GetVideoDecoder()->UpdateFrame(frame, false, false);
}
void WsPpu::SetOutputToBgColor()
{
std::fill(_currentBuffer, _currentBuffer + WsConstants::MaxPixelCount, GetBgColor());
}
void WsPpu::ShowVolumeIcon()
{
_state.ShowVolumeIconFrame = _state.FrameCount;
}
uint8_t WsPpu::GetLcdStatus()
{
uint8_t masterVolume = _console->GetApu()->GetMasterVolume();
uint8_t volumeLevel;
if(!_console->IsColorModel()) {
switch(masterVolume) {
default:
case 0: volumeLevel = 0; break;
case 1: volumeLevel = 2; break;
case 2: volumeLevel = 3; break;
}
} else {
switch(masterVolume) {
default:
case 0: volumeLevel = 0; break;
case 1: volumeLevel = 2; break;
case 2: volumeLevel = 1; break;
case 3: volumeLevel = 3; break;
}
}
bool headphone = false;
bool speaker = false;
if(_state.ShowVolumeIconFrame <= _state.FrameCount && _state.FrameCount - _state.ShowVolumeIconFrame < 128) {
//Show speaker/headphone icons if sound button was pressed within the last 128 frames
if(_console->GetAudioMode() == WsAudioMode::Headphones) {
headphone = true;
} else {
speaker = true;
}
}
return (
(_state.SleepEnabled ? 0x01 : 0) |
(headphone ? 0x02 : 0) |
(volumeLevel << 2) |
(speaker ? 0x10 : 0));
}
void WsPpu::SendFrame()
{
if(_state.SleepEnabled || !_state.LcdEnabled || _state.LastScanline == 255 || _console->IsPowerOff()) {
//Screen should be white when in sleep mode, or if the last scanline is set to 255
std::fill(_currentBuffer, _currentBuffer + WsConstants::MaxPixelCount, 0xFFF);
} else if(_state.LastScanline < 144) {
//Clear everything after the last scanline (results in less than 144 visible scanlines)
std::fill(_currentBuffer + _state.LastScanline * _screenWidth, _currentBuffer + WsConstants::MaxPixelCount, 0xFFF);
}
if(_showIcons) {
DrawIcons();
}
_emu->ProcessEvent(EventType::EndFrame, CpuType::Ws);
_state.FrameCount++;
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone);
uint16_t width = _showIcons ? _screenWidth : WsConstants::ScreenWidth;
uint16_t height = _showIcons ? _screenHeight : WsConstants::ScreenHeight;
RenderedFrame frame(_currentBuffer, width, height, 1.0, _state.FrameCount, _console->GetControlManager()->GetPortStates());
bool rewinding = _emu->GetRewindManager()->IsRewinding();
_emu->GetVideoDecoder()->UpdateFrame(frame, rewinding, rewinding);
_emu->ProcessEndOfFrame();
_console->ProcessEndOfFrame();
}
uint8_t WsPpu::ReadPort(uint16_t port)
{
switch(port) {
case 0x00: return _state.Control;
case 0x01: return _state.BgColor & (_memoryManager->IsColorEnabled() ? 0xFF : 0x07);
case 0x02: return _state.Scanline;
case 0x03: return _state.IrqScanline;
case 0x04: return (_state.SpriteTableAddress >> 9) & (_memoryManager->IsColorEnabled() ? 0x3F : 0x1F);
case 0x05: return _state.FirstSpriteIndex;
case 0x06: return _state.SpriteCount;
case 0x07: return _state.ScreenAddress & (_memoryManager->IsColorEnabled() ? 0xFF : 0x77);
case 0x08: return _state.BgWindow.Left;
case 0x09: return _state.BgWindow.Top;
case 0x0A: return _state.BgWindow.Right;
case 0x0B: return _state.BgWindow.Bottom;
case 0x0C: return _state.SpriteWindow.Left;
case 0x0D: return _state.SpriteWindow.Top;
case 0x0E: return _state.SpriteWindow.Right;
case 0x0F: return _state.SpriteWindow.Bottom;
case 0x10: return _state.BgLayers[0].ScrollX;
case 0x11: return _state.BgLayers[0].ScrollY;
case 0x12: return _state.BgLayers[1].ScrollX;
case 0x13: return _state.BgLayers[1].ScrollY;
case 0x14: return _state.LcdControl;
case 0x15: return _state.Icons.Value;
case 0x16: return _state.LastScanline;
case 0x17: return _state.BackPorchScanline;
case 0x1A: return GetLcdStatus();
case 0x1B: return 0;
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
return (
_state.BwShades[(port - 0x1C) * 2] |
(_state.BwShades[(port - 0x1C) * 2 + 1] << 4));
default:
if(port >= 0x20 && port <= 0x3F) {
return (
_state.BwPalettes[(port - 0x20) * 2] |
(_state.BwPalettes[(port - 0x20) * 2 + 1] << 4));
} else {
LogDebug("[Debug] PPU Read - missing handler: $" + HexUtilities::ToHex(port));
return _memoryManager->GetUnmappedPort();
}
}
}
void WsPpu::WritePort(uint16_t port, uint8_t value)
{
switch(port) {
case 0x00:
_state.Control = value & 0x3F;
_state.BgLayers[0].Enabled = value & 0x01;
_state.BgLayers[1].Enabled = value & 0x02;
_state.SpritesEnabled = value & 0x04;
_state.SpriteWindow.Enabled = value & 0x08;
_state.DrawOutsideBgWindow = value & 0x10;
_state.BgWindow.Enabled = value & 0x20;
break;
case 0x01: _state.BgColor = value & (_console->IsColorModel() ? 0xFF : 0x07); break;
case 0x03: _state.IrqScanline = value; break;
case 0x04: _state.SpriteTableAddress = (value & (_console->IsColorModel() ? 0x3F : 0x1F)) << 9; break;
case 0x05: _state.FirstSpriteIndex = value & 0x7F; break;
case 0x06: _state.SpriteCount = value; break;
case 0x07:
_state.ScreenAddress = value & (_console->IsColorModel() ? 0xFF : 0x77);
_state.BgLayers[0].MapAddress = (_state.ScreenAddress & 0x0F) << 11;
_state.BgLayers[1].MapAddress = (_state.ScreenAddress & 0xF0) << 7;
break;
case 0x08: _state.BgWindow.Left = value; break;
case 0x09: _state.BgWindow.Top = value; break;
case 0x0A: _state.BgWindow.Right = value; break;
case 0x0B: _state.BgWindow.Bottom = value; break;
case 0x0C: _state.SpriteWindow.Left = value; break;
case 0x0D: _state.SpriteWindow.Top = value; break;
case 0x0E: _state.SpriteWindow.Right = value; break;
case 0x0F: _state.SpriteWindow.Bottom = value; break;
case 0x10: _state.BgLayers[0].ScrollX = value; break;
case 0x11: _state.BgLayers[0].ScrollY = value; break;
case 0x12: _state.BgLayers[1].ScrollX = value; break;
case 0x13: _state.BgLayers[1].ScrollY = value; break;
case 0x14:
_state.LcdEnabled = value & 0x01;
switch(_console->GetModel()) {
case WsModel::SwanCrystal:
_state.LcdControl = value & 0x01;
break;
case WsModel::Color:
_state.HighContrast = value & 0x02;
_state.LcdControl = value & 0xF3;
break;
default:
_state.LcdControl = value & 0xF1;
break;
}
break;
case 0x15:
_state.Icons.Sleep = value & 0x01;
_state.Icons.Vertical = value & 0x02;
_state.Icons.Horizontal = value & 0x04;
_state.Icons.Aux1 = value & 0x08;
_state.Icons.Aux2 = value & 0x10;
_state.Icons.Aux3 = value & 0x20;
_state.Icons.Value = value & 0x3F;
break;
case 0x16: _state.LastScanline = value; break;
case 0x17:
if(_console->GetModel() != WsModel::SwanCrystal) {
_state.BackPorchScanline = value;
}
break;
case 0x1A:
_state.SleepEnabled = value & 0x01;
break;
case 0x1C:
case 0x1D:
case 0x1E:
case 0x1F:
_state.BwShades[(port - 0x1C) * 2] = value & 0x0F;
_state.BwShades[(port - 0x1C) * 2 + 1] = (value >> 4) & 0x0F;
break;
default:
if(port >= 0x20 && port <= 0x3F) {
int index = port - 0x20;
//Color 0 only exists on palettes 0-3 and 8-11
//For other palettes, this is always read as 0 (writes have no effect)
if((index & 0x09) != 0x08) {
_state.BwPalettes[index * 2] = value & 0x07;
}
_state.BwPalettes[index * 2 + 1] = (value >> 4) & 0x07;
} else {
LogDebug("[Debug] PPU Write - missing handler: $" + HexUtilities::ToHex(port) + " = " + HexUtilities::ToHex(value));
}
break;
}
}
uint8_t WsPpu::ReadLcdConfigPort(uint16_t port)
{
return _state.LcdTftConfig[port - 0x70];
}
void WsPpu::WriteLcdConfigPort(uint16_t port, uint8_t value)
{
_state.LcdTftConfig[port - 0x70] = value;
}
void WsPpu::Serialize(Serializer& s)
{
SV(_state.FrameCount);
SV(_state.Cycle);
SV(_state.Scanline);
SV(_state.LastScanline);
for(int i = 0; i < 2; i++) {
SVI(_state.BgLayers[i].Enabled);
SVI(_state.BgLayers[i].EnabledLatch);
SVI(_state.BgLayers[i].MapAddress);
SVI(_state.BgLayers[i].MapAddressLatch);
SVI(_state.BgLayers[i].ScrollX);
SVI(_state.BgLayers[i].ScrollXLatch);
SVI(_state.BgLayers[i].ScrollY);
SVI(_state.BgLayers[i].ScrollYLatch);
}
SV(_state.BgWindow.Enabled);
SV(_state.BgWindow.EnabledLatch);
SV(_state.BgWindow.Bottom);
SV(_state.BgWindow.BottomLatch);
SV(_state.BgWindow.Top);
SV(_state.BgWindow.TopLatch);
SV(_state.BgWindow.Left);
SV(_state.BgWindow.LeftLatch);
SV(_state.BgWindow.Right);
SV(_state.BgWindow.RightLatch);
SV(_state.SpriteWindow.Enabled);
SV(_state.SpriteWindow.EnabledLatch);
SV(_state.SpriteWindow.Bottom);
SV(_state.SpriteWindow.BottomLatch);
SV(_state.SpriteWindow.Top);
SV(_state.SpriteWindow.TopLatch);
SV(_state.SpriteWindow.Left);
SV(_state.SpriteWindow.LeftLatch);
SV(_state.SpriteWindow.Right);
SV(_state.SpriteWindow.RightLatch);
SV(_state.DrawOutsideBgWindow);
SV(_state.DrawOutsideBgWindowLatch);
SVArray(_state.BwPalettes, 0x40);
SVArray(_state.BwShades, 8);
SVArray(_spriteRam, 512);
SVArray(_state.LcdTftConfig, 8);
SV(_state.SpriteTableAddress);
SV(_state.FirstSpriteIndex);
SV(_state.SpriteCount);
SV(_state.SpriteCountLatch);
SV(_state.SpritesEnabled);
SV(_state.SpritesEnabledLatch);
SV(_state.Mode);
SV(_state.NextMode);
SV(_state.BgColor);
SV(_state.IrqScanline);
SV(_state.Icons.Sleep);
SV(_state.Icons.Horizontal);
SV(_state.Icons.Vertical);
SV(_state.Icons.Aux1);
SV(_state.Icons.Aux2);
SV(_state.Icons.Aux3);
SV(_state.Icons.Value);
SV(_state.Control);
SV(_state.ScreenAddress);
SV(_state.LcdEnabled);
SV(_state.HighContrast);
SV(_state.SleepEnabled);
SV(_state.LcdControl);
SV(_state.ShowVolumeIconFrame);
SV(_state.BackPorchScanline);
}