Rename to DuckStation

This commit is contained in:
Connor McLaughlin
2019-10-04 13:54:09 +10:00
parent 92ec5a0a14
commit bddbab9d60
61 changed files with 43 additions and 54 deletions

16
src/core/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
add_library(core
cpu_bus.cpp
cpu_bus.h
cpu_bus.inl
cpu_core.cpp
cpu_core.h
cpu_core.inl
system.cpp
system.h
types.h
)
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(core Threads::Threads YBaseLib common)

491
src/core/bus.cpp Normal file
View File

@ -0,0 +1,491 @@
#include "bus.h"
#include "YBaseLib/ByteStream.h"
#include "YBaseLib/Log.h"
#include "YBaseLib/String.h"
#include "cdrom.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
#include "cpu_disasm.h"
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "spu.h"
#include "timers.h"
#include <cstdio>
Log_SetChannel(Bus);
// Offset and value remapping for (w32) registers from nocash docs.
void FixupUnalignedWordAccessW32(u32& offset, u32& value)
{
const u32 byte_offset = offset & u32(3);
offset &= ~u32(3);
value <<= byte_offset * 8;
}
Bus::Bus() = default;
Bus::~Bus() = default;
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom,
Pad* pad, Timers* timers, SPU* spu, MDEC* mdec)
{
if (!LoadBIOS())
return false;
m_cpu = cpu;
m_dma = dma;
m_interrupt_controller = interrupt_controller;
m_gpu = gpu;
m_cdrom = cdrom;
m_pad = pad;
m_timers = timers;
m_spu = spu;
m_mdec = mdec;
return true;
}
void Bus::Reset()
{
m_ram.fill(static_cast<u8>(0));
m_MEMCTRL.exp1_base = 0x1F000000;
m_MEMCTRL.exp2_base = 0x1F802000;
m_MEMCTRL.exp1_delay_size = 0x0013243F;
m_MEMCTRL.exp3_delay_size = 0x00003022;
m_MEMCTRL.bios_delay_size = 0x0013243F;
m_MEMCTRL.spu_delay_size = 0x200931E1;
m_MEMCTRL.cdrom_delay_size = 0x00020843;
m_MEMCTRL.exp2_delay_size = 0x00070777;
m_MEMCTRL.common_delay_size = 0x00031125;
m_ram_size_reg = UINT32_C(0x00000B88);
}
bool Bus::DoState(StateWrapper& sw)
{
sw.DoBytes(m_ram.data(), m_ram.size());
sw.DoBytes(m_bios.data(), m_bios.size());
sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs));
sw.Do(&m_ram_size_reg);
sw.Do(&m_tty_line_buffer);
return !sw.HasError();
}
bool Bus::ReadByte(PhysicalMemoryAddress address, u8* value)
{
u32 temp = 0;
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
*value = Truncate8(temp);
return result;
}
bool Bus::ReadHalfWord(PhysicalMemoryAddress address, u16* value)
{
u32 temp = 0;
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
*value = Truncate16(temp);
return result;
}
bool Bus::ReadWord(PhysicalMemoryAddress address, u32* value)
{
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, *value);
}
bool Bus::WriteByte(PhysicalMemoryAddress address, u8 value)
{
u32 temp = ZeroExtend32(value);
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
}
bool Bus::WriteHalfWord(PhysicalMemoryAddress address, u16 value)
{
u32 temp = ZeroExtend32(value);
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
}
bool Bus::WriteWord(PhysicalMemoryAddress address, u32 value)
{
return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
}
void Bus::PatchBIOS(u32 address, u32 value, u32 mask /*= UINT32_C(0xFFFFFFFF)*/)
{
const u32 phys_address = address & UINT32_C(0x1FFFFFFF);
const u32 offset = phys_address - BIOS_BASE;
Assert(phys_address >= BIOS_BASE && offset < BIOS_SIZE);
u32 existing_value;
std::memcpy(&existing_value, &m_bios[offset], sizeof(existing_value));
u32 new_value = (existing_value & ~mask) | value;
std::memcpy(&m_bios[offset], &new_value, sizeof(new_value));
SmallString old_disasm, new_disasm;
CPU::DisassembleInstruction(&old_disasm, address, existing_value);
CPU::DisassembleInstruction(&new_disasm, address, new_value);
Log_InfoPrintf("BIOS-Patch 0x%08X (+0x%X): 0x%08X %s -> %08X %s", address, offset, existing_value,
old_disasm.GetCharArray(), new_value, new_disasm.GetCharArray());
}
void Bus::SetExpansionROM(std::vector<u8> data)
{
m_exp1_rom = std::move(data);
}
bool Bus::LoadBIOS()
{
std::FILE* fp = std::fopen("SCPH1001.BIN", "rb");
if (!fp)
return false;
std::fseek(fp, 0, SEEK_END);
const u32 size = static_cast<u32>(std::ftell(fp));
std::fseek(fp, 0, SEEK_SET);
if (size != m_bios.size())
{
Log_ErrorPrintf("BIOS image mismatch, expecting %u bytes, got %u bytes", static_cast<u32>(m_bios.size()), size);
std::fclose(fp);
return false;
}
if (std::fread(m_bios.data(), 1, m_bios.size(), fp) != m_bios.size())
{
Log_ErrorPrintf("Failed to read BIOS image");
std::fclose(fp);
return false;
}
std::fclose(fp);
#if 1
// Patch to enable TTY.
PatchBIOS(BIOS_BASE + 0x6F0C, 0x24010001);
PatchBIOS(BIOS_BASE + 0x6F14, 0xAF81A9C0);
#endif
return true;
}
bool Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value)
{
SmallString str;
str.AppendString("Invalid bus ");
if (size == MemoryAccessSize::Byte)
str.AppendString("byte");
if (size == MemoryAccessSize::HalfWord)
str.AppendString("word");
if (size == MemoryAccessSize::Word)
str.AppendString("dword");
str.AppendCharacter(' ');
if (type == MemoryAccessType::Read)
str.AppendString("read");
else
str.AppendString("write");
str.AppendFormattedString(" at address 0x%08X", address);
if (type == MemoryAccessType::Write)
str.AppendFormattedString(" (value 0x%08X)", value);
Log_ErrorPrint(str);
if (type == MemoryAccessType::Read)
value = UINT32_C(0xFFFFFFFF);
return true;
}
bool Bus::DoReadEXP1(MemoryAccessSize size, u32 offset, u32& value)
{
if (m_exp1_rom.empty())
return DoInvalidAccess(MemoryAccessType::Read, size, EXP1_BASE | offset, value);
if (offset == 0x20018)
{
// Bit 0 - Action Replay On/Off
return UINT32_C(1);
}
const u32 transfer_size = u32(1) << static_cast<u32>(size);
if ((offset + transfer_size) > m_exp1_rom.size())
{
value = UINT32_C(0);
return true;
}
if (size == MemoryAccessSize::Byte)
{
value = ZeroExtend32(m_exp1_rom[offset]);
}
else if (size == MemoryAccessSize::HalfWord)
{
u16 halfword;
std::memcpy(&halfword, &m_exp1_rom[offset], sizeof(halfword));
value = ZeroExtend32(halfword);
}
else
{
std::memcpy(&value, &m_exp1_rom[offset], sizeof(value));
}
// Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", EXP1_BASE | offset, value);
return true;
}
bool Bus::DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value)
{
return DoInvalidAccess(MemoryAccessType::Write, size, EXP1_BASE | offset, value);
}
bool Bus::DoReadEXP2(MemoryAccessSize size, u32 offset, u32& value)
{
offset &= EXP2_MASK;
// rx/tx buffer empty
if (offset == 0x21)
{
value = 0x04 | 0x08;
return true;
}
return DoInvalidAccess(MemoryAccessType::Read, size, EXP2_BASE | offset, value);
}
bool Bus::DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value)
{
offset &= EXP2_MASK;
if (offset == 0x23)
{
if (value == '\r')
return true;
if (value == '\n')
{
if (!m_tty_line_buffer.IsEmpty())
Log_InfoPrintf("TTY: %s", m_tty_line_buffer.GetCharArray());
m_tty_line_buffer.Clear();
}
else
{
m_tty_line_buffer.AppendCharacter(Truncate8(value));
}
return true;
}
if (offset == 0x41)
{
Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F));
return true;
}
return DoInvalidAccess(MemoryAccessType::Write, size, EXP2_BASE | offset, value);
}
bool Bus::DoReadMemoryControl(MemoryAccessSize size, u32 offset, u32& value)
{
FixupUnalignedWordAccessW32(offset, value);
value = m_MEMCTRL.regs[offset / 4];
return true;
}
bool Bus::DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value)
{
FixupUnalignedWordAccessW32(offset, value);
m_MEMCTRL.regs[offset / 4] = value;
return true;
}
bool Bus::DoReadMemoryControl2(MemoryAccessSize size, u32 offset, u32& value)
{
if (offset == 0x00)
{
value = m_ram_size_reg;
return true;
}
return DoInvalidAccess(MemoryAccessType::Read, size, MEMCTRL2_BASE | offset, value);
}
bool Bus::DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value)
{
if (offset == 0x00)
{
m_ram_size_reg = value;
return true;
}
return DoInvalidAccess(MemoryAccessType::Write, size, MEMCTRL2_BASE | offset, value);
}
bool Bus::DoReadPad(MemoryAccessSize size, u32 offset, u32& value)
{
value = m_pad->ReadRegister(offset);
return true;
}
bool Bus::DoWritePad(MemoryAccessSize size, u32 offset, u32 value)
{
m_pad->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadSIO(MemoryAccessSize size, u32 offset, u32& value)
{
Log_ErrorPrintf("SIO Read 0x%08X", offset);
value = 0;
if (offset == 0x04)
value = 0x5;
return true;
}
bool Bus::DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value)
{
Log_ErrorPrintf("SIO Write 0x%08X <- 0x%08X", offset, value);
return true;
}
bool Bus::DoReadCDROM(MemoryAccessSize size, u32 offset, u32& value)
{
// TODO: Splitting of half/word reads.
Assert(size == MemoryAccessSize::Byte);
value = ZeroExtend32(m_cdrom->ReadRegister(offset));
return true;
}
bool Bus::DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value)
{
// TODO: Splitting of half/word reads.
Assert(size == MemoryAccessSize::Byte);
m_cdrom->WriteRegister(offset, Truncate8(value));
return true;
}
bool Bus::DoReadGPU(MemoryAccessSize size, u32 offset, u32& value)
{
Assert(size == MemoryAccessSize::Word);
value = m_gpu->ReadRegister(offset);
return true;
}
bool Bus::DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value)
{
Assert(size == MemoryAccessSize::Word);
m_gpu->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadMDEC(MemoryAccessSize size, u32 offset, u32& value)
{
Assert(size == MemoryAccessSize::Word);
value = m_mdec->ReadRegister(offset);
return true;
}
bool Bus::DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value)
{
Assert(size == MemoryAccessSize::Word);
m_mdec->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadInterruptController(MemoryAccessSize size, u32 offset, u32& value)
{
FixupUnalignedWordAccessW32(offset, value);
value = m_interrupt_controller->ReadRegister(offset);
return true;
}
bool Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value)
{
FixupUnalignedWordAccessW32(offset, value);
m_interrupt_controller->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadTimers(MemoryAccessSize size, u32 offset, u32& value)
{
FixupUnalignedWordAccessW32(offset, value);
value = m_timers->ReadRegister(offset);
return true;
}
bool Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value)
{
FixupUnalignedWordAccessW32(offset, value);
m_timers->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadSPU(MemoryAccessSize size, u32 offset, u32& value)
{
// 32-bit reads are read as two 16-bit writes.
if (size == MemoryAccessSize::Word)
{
const u16 lsb = m_spu->ReadRegister(offset);
const u16 msb = m_spu->ReadRegister(offset + 2);
value = ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16);
}
else
{
value = ZeroExtend32(m_spu->ReadRegister(offset));
}
return true;
}
bool Bus::DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value)
{
// 32-bit writes are written as two 16-bit writes.
if (size == MemoryAccessSize::Word)
{
m_spu->WriteRegister(offset, Truncate16(value));
m_spu->WriteRegister(offset + 2, Truncate16(value >> 16));
return true;
}
m_spu->WriteRegister(offset, Truncate16(value));
return true;
}
bool Bus::DoReadDMA(MemoryAccessSize size, u32 offset, u32& value)
{
switch (size)
{
case MemoryAccessSize::Byte:
case MemoryAccessSize::HalfWord:
{
if ((offset & u32(0xF0)) >= 7 || (offset & u32(0x0F)) != 0x4)
FixupUnalignedWordAccessW32(offset, value);
}
default:
break;
}
value = m_dma->ReadRegister(offset);
return true;
}
bool Bus::DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value)
{
switch (size)
{
case MemoryAccessSize::Byte:
case MemoryAccessSize::HalfWord:
{
// zero extend length register
if ((offset & u32(0xF0)) < 7 && (offset & u32(0x0F)) == 0x4)
value = ZeroExtend32(value);
else
FixupUnalignedWordAccessW32(offset, value);
}
default:
break;
}
m_dma->WriteRegister(offset, value);
return true;
}

185
src/core/bus.h Normal file
View File

@ -0,0 +1,185 @@
#pragma once
#include "YBaseLib/String.h"
#include "types.h"
#include <array>
class StateWrapper;
namespace CPU {
class Core;
}
class DMA;
class InterruptController;
class GPU;
class CDROM;
class Pad;
class Timers;
class SPU;
class MDEC;
class System;
class Bus
{
public:
Bus();
~Bus();
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad,
Timers* timers, SPU* spu, MDEC* mdec);
void Reset();
bool DoState(StateWrapper& sw);
bool ReadByte(PhysicalMemoryAddress address, u8* value);
bool ReadHalfWord(PhysicalMemoryAddress address, u16* value);
bool ReadWord(PhysicalMemoryAddress address, u32* value);
bool WriteByte(PhysicalMemoryAddress address, u8 value);
bool WriteHalfWord(PhysicalMemoryAddress address, u16 value);
bool WriteWord(PhysicalMemoryAddress address, u32 value);
template<MemoryAccessType type, MemoryAccessSize size>
bool DispatchAccess(PhysicalMemoryAddress address, u32& value);
void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
void SetExpansionROM(std::vector<u8> data);
private:
enum : u32
{
EXP1_BASE = 0x1F000000,
EXP1_SIZE = 0x800000,
EXP1_MASK = EXP1_SIZE - 1,
MEMCTRL_BASE = 0x1F801000,
MEMCTRL_SIZE = 0x40,
MEMCTRL_MASK = MEMCTRL_SIZE - 1,
PAD_BASE = 0x1F801040,
PAD_SIZE = 0x10,
PAD_MASK = PAD_SIZE - 1,
SIO_BASE = 0x1F801050,
SIO_SIZE = 0x10,
SIO_MASK = SIO_SIZE - 1,
MEMCTRL2_BASE = 0x1F801060,
MEMCTRL2_SIZE = 0x10,
MEMCTRL2_MASK = MEMCTRL_SIZE - 1,
INTERRUPT_CONTROLLER_BASE = 0x1F801070,
INTERRUPT_CONTROLLER_SIZE = 0x10,
INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1,
DMA_BASE = 0x1F801080,
DMA_SIZE = 0x80,
DMA_MASK = DMA_SIZE - 1,
TIMERS_BASE = 0x1F801100,
TIMERS_SIZE = 0x40,
TIMERS_MASK = TIMERS_SIZE - 1,
CDROM_BASE = 0x1F801800,
CDROM_SIZE = 0x10,
CDROM_MASK = CDROM_SIZE - 1,
GPU_BASE = 0x1F801810,
GPU_SIZE = 0x10,
GPU_MASK = GPU_SIZE - 1,
MDEC_BASE = 0x1F801820,
MDEC_SIZE = 0x10,
MDEC_MASK = MDEC_SIZE - 1,
SPU_BASE = 0x1F801C00,
SPU_SIZE = 0x300,
SPU_MASK = 0x3FF,
EXP2_BASE = 0x1F802000,
EXP2_SIZE = 0x2000,
EXP2_MASK = EXP2_SIZE - 1,
BIOS_BASE = 0x1FC00000,
BIOS_SIZE = 0x80000
};
enum : u32
{
MEMCTRL_REG_COUNT = 9
};
union MEMCTRL
{
u32 regs[MEMCTRL_REG_COUNT];
struct
{
u32 exp1_base;
u32 exp2_base;
u32 exp1_delay_size;
u32 exp3_delay_size;
u32 bios_delay_size;
u32 spu_delay_size;
u32 cdrom_delay_size;
u32 exp2_delay_size;
u32 common_delay_size;
};
};
bool LoadBIOS();
template<MemoryAccessType type, MemoryAccessSize size>
bool DoRAMAccess(u32 offset, u32& value);
template<MemoryAccessType type, MemoryAccessSize size>
bool DoBIOSAccess(u32 offset, u32& value);
bool DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value);
bool DoReadEXP1(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadEXP2(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadMemoryControl(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadMemoryControl2(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadPad(MemoryAccessSize size, u32 offset, u32& value);
bool DoWritePad(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadSIO(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadCDROM(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadGPU(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadMDEC(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadInterruptController(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadDMA(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadTimers(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadSPU(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value);
CPU::Core* m_cpu = nullptr;
DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr;
GPU* m_gpu = nullptr;
CDROM* m_cdrom = nullptr;
Pad* m_pad = nullptr;
Timers* m_timers = nullptr;
SPU* m_spu = nullptr;
MDEC* m_mdec = nullptr;
std::array<u8, 2097152> m_ram{}; // 2MB RAM
std::array<u8, 524288> m_bios{}; // 512K BIOS ROM
std::vector<u8> m_exp1_rom;
MEMCTRL m_MEMCTRL = {};
u32 m_ram_size_reg = 0;
String m_tty_line_buffer;
};
#include "bus.inl"

181
src/core/bus.inl Normal file
View File

@ -0,0 +1,181 @@
#pragma once
#include "bus.h"
template<MemoryAccessType type, MemoryAccessSize size>
bool Bus::DoRAMAccess(u32 offset, u32& value)
{
// TODO: Configurable mirroring.
offset &= UINT32_C(0x1FFFFF);
if constexpr (type == MemoryAccessType::Read)
{
if constexpr (size == MemoryAccessSize::Byte)
{
value = ZeroExtend32(m_ram[offset]);
}
else if constexpr (size == MemoryAccessSize::HalfWord)
{
u16 temp;
std::memcpy(&temp, &m_ram[offset], sizeof(u16));
value = ZeroExtend32(temp);
}
else if constexpr (size == MemoryAccessSize::Word)
{
std::memcpy(&value, &m_ram[offset], sizeof(u32));
}
}
else
{
if constexpr (size == MemoryAccessSize::Byte)
{
m_ram[offset] = Truncate8(value);
}
else if constexpr (size == MemoryAccessSize::HalfWord)
{
const u16 temp = Truncate16(value);
std::memcpy(&m_ram[offset], &temp, sizeof(u16));
}
else if constexpr (size == MemoryAccessSize::Word)
{
std::memcpy(&m_ram[offset], &value, sizeof(u32));
}
}
return true;
}
template<MemoryAccessType type, MemoryAccessSize size>
bool Bus::DoBIOSAccess(u32 offset, u32& value)
{
// TODO: Configurable mirroring.
if constexpr (type == MemoryAccessType::Read)
{
offset &= UINT32_C(0x7FFFF);
if constexpr (size == MemoryAccessSize::Byte)
{
value = ZeroExtend32(m_bios[offset]);
}
else if constexpr (size == MemoryAccessSize::HalfWord)
{
u16 temp;
std::memcpy(&temp, &m_bios[offset], sizeof(u16));
value = ZeroExtend32(temp);
}
else
{
std::memcpy(&value, &m_bios[offset], sizeof(u32));
}
}
else
{
// Writes are ignored.
}
return true;
}
template<MemoryAccessType type, MemoryAccessSize size>
bool Bus::DispatchAccess(PhysicalMemoryAddress address, u32& value)
{
if (address < 0x800000)
{
return DoRAMAccess<type, size>(address, value);
}
else if (address < EXP1_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (EXP1_BASE + EXP1_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadEXP1(size, address & EXP1_MASK, value) :
DoWriteEXP1(size, address & EXP1_MASK, value);
}
else if (address < MEMCTRL_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (MEMCTRL_BASE + MEMCTRL_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadMemoryControl(size, address & PAD_MASK, value) :
DoWriteMemoryControl(size, address & PAD_MASK, value);
}
else if (address < (PAD_BASE + PAD_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadPad(size, address & PAD_MASK, value) :
DoWritePad(size, address & PAD_MASK, value);
}
else if (address < (SIO_BASE + SIO_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadSIO(size, address & SIO_MASK, value) :
DoWriteSIO(size, address & SIO_MASK, value);
}
else if (address < (MEMCTRL2_BASE + MEMCTRL2_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadMemoryControl2(size, address & PAD_MASK, value) :
DoWriteMemoryControl2(size, address & PAD_MASK, value);
}
else if (address < (INTERRUPT_CONTROLLER_BASE + INTERRUPT_CONTROLLER_SIZE))
{
return (type == MemoryAccessType::Read) ?
DoReadInterruptController(size, address & INTERRUPT_CONTROLLER_MASK, value) :
DoWriteInterruptController(size, address & INTERRUPT_CONTROLLER_MASK, value);
}
else if (address < (DMA_BASE + DMA_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadDMA(size, address & DMA_MASK, value) :
DoWriteDMA(size, address & DMA_MASK, value);
}
else if (address < (TIMERS_BASE + TIMERS_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadTimers(size, address & TIMERS_MASK, value) :
DoWriteTimers(size, address & TIMERS_MASK, value);
}
else if (address < CDROM_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (CDROM_BASE + GPU_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadCDROM(size, address & CDROM_MASK, value) :
DoWriteCDROM(size, address & CDROM_MASK, value);
}
else if (address < (GPU_BASE + GPU_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadGPU(size, address & GPU_MASK, value) :
DoWriteGPU(size, address & GPU_MASK, value);
}
else if (address < (MDEC_BASE + MDEC_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadMDEC(size, address & MDEC_MASK, value) :
DoWriteMDEC(size, address & MDEC_MASK, value);
}
else if (address < SPU_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (SPU_BASE + SPU_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadSPU(size, address & SPU_MASK, value) :
DoWriteSPU(size, address & SPU_MASK, value);
}
else if (address < EXP2_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (EXP2_BASE + EXP2_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadEXP2(size, address & EXP2_MASK, value) :
DoWriteEXP2(size, address & EXP2_MASK, value);
}
else if (address < BIOS_BASE)
{
return DoInvalidAccess(type, size, address, value);
}
else if (address < (BIOS_BASE + BIOS_SIZE))
{
return DoBIOSAccess<type, size>(static_cast<u32>(address - BIOS_BASE), value);
}
else
{
return DoInvalidAccess(type, size, address, value);
}
}

823
src/core/cdrom.cpp Normal file
View File

@ -0,0 +1,823 @@
#include "cdrom.h"
#include "YBaseLib/Log.h"
#include "common/cd_image.h"
#include "common/state_wrapper.h"
#include "interrupt_controller.h"
#include "system.h"
Log_SetChannel(CDROM);
CDROM::CDROM() : m_sector_buffer(SECTOR_BUFFER_SIZE) {}
CDROM::~CDROM() = default;
bool CDROM::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
{
m_system = system;
m_dma = dma;
m_interrupt_controller = interrupt_controller;
return true;
}
void CDROM::Reset()
{
m_command_state = CommandState::Idle;
m_command = Command::Sync;
m_command_stage = 0;
m_command_remaining_ticks = 0;
m_sector_read_remaining_ticks = 0;
m_reading = false;
m_muted = false;
m_setloc = {};
m_setloc_dirty = false;
m_status.bits = 0;
m_secondary_status.bits = 0;
m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
m_interrupt_flag_register = 0;
m_param_fifo.Clear();
m_response_fifo.Clear();
m_data_fifo.Clear();
UpdateStatusRegister();
}
bool CDROM::DoState(StateWrapper& sw)
{
sw.Do(&m_command);
sw.Do(&m_command_stage);
sw.Do(&m_command_remaining_ticks);
sw.Do(&m_sector_read_remaining_ticks);
sw.Do(&m_reading);
sw.Do(&m_muted);
sw.Do(&m_setloc.minute);
sw.Do(&m_setloc.second);
sw.Do(&m_setloc.frame);
sw.Do(&m_setloc_dirty);
sw.Do(&m_command_state);
sw.Do(&m_status.bits);
sw.Do(&m_secondary_status.bits);
sw.Do(&m_interrupt_enable_register);
sw.Do(&m_interrupt_flag_register);
sw.Do(&m_param_fifo);
sw.Do(&m_response_fifo);
sw.Do(&m_data_fifo);
sw.Do(&m_sector_buffer);
u64 media_lba = m_media ? m_media->GetCurrentLBA() : 0;
sw.Do(&m_media_filename);
sw.Do(&media_lba);
if (sw.IsReading())
{
if (m_command_state == CommandState::WaitForExecute)
m_system->SetDowncount(m_command_remaining_ticks);
if (m_reading)
m_system->SetDowncount(m_sector_read_remaining_ticks);
// load up media if we had something in there before
m_media.reset();
if (!m_media_filename.empty())
{
m_media = std::make_unique<CDImage>();
if (!m_media->Open(m_media_filename.c_str()) || !m_media->Seek(media_lba))
{
Log_ErrorPrintf("Failed to re-insert CD media from save state: '%s'. Ejecting.", m_media_filename.c_str());
RemoveMedia();
}
}
}
return !sw.HasError();
}
bool CDROM::InsertMedia(const char* filename)
{
auto media = std::make_unique<CDImage>();
if (!media->Open(filename))
{
Log_ErrorPrintf("Failed to open media at '%s'", filename);
return false;
}
if (HasMedia())
RemoveMedia();
m_media = std::move(media);
m_media_filename = filename;
// m_secondary_status.shell_open = false;
return true;
}
void CDROM::RemoveMedia()
{
if (!m_media)
return;
// TODO: Error while reading?
Log_InfoPrintf("Removing CD...");
m_media.reset();
m_media_filename.clear();
// m_secondary_status.shell_open = true;
}
u8 CDROM::ReadRegister(u32 offset)
{
switch (offset)
{
case 0: // status register
Log_TracePrintf("CDROM read status register <- 0x%08X", m_status.bits);
return m_status.bits;
case 1: // always response FIFO
{
if (m_response_fifo.IsEmpty())
{
Log_DebugPrintf("Response FIFO empty on read");
return 0xFF;
}
const u8 value = m_response_fifo.Pop();
UpdateStatusRegister();
Log_DebugPrintf("CDROM read response FIFO <- 0x%08X", ZeroExtend32(value));
return value;
}
case 2: // always data FIFO
{
const u8 value = m_data_fifo.Pop();
UpdateStatusRegister();
Log_DebugPrintf("CDROM read data FIFO <- 0x%08X", ZeroExtend32(value));
return value;
}
case 3:
{
switch (m_status.index)
{
case 0:
case 2:
{
const u8 value = m_interrupt_enable_register | ~INTERRUPT_REGISTER_MASK;
Log_DebugPrintf("CDROM read interrupt enable register <- 0x%02X", ZeroExtend32(value));
return value;
}
case 1:
case 3:
{
const u8 value = m_interrupt_flag_register | ~INTERRUPT_REGISTER_MASK;
Log_DebugPrintf("CDROM read interrupt flag register <- 0x%02X", ZeroExtend32(value));
return value;
}
}
}
break;
}
Log_ErrorPrintf("Unknown CDROM register read: offset=0x%02X, index=%d", offset,
ZeroExtend32(m_status.index.GetValue()));
Panic("Unknown CDROM register");
return 0;
}
void CDROM::WriteRegister(u32 offset, u8 value)
{
switch (offset)
{
case 0:
{
Log_TracePrintf("CDROM status register <- 0x%02X", ZeroExtend32(value));
m_status.bits = (m_status.bits & static_cast<u8>(~3)) | (value & u8(3));
return;
}
break;
case 1:
{
switch (m_status.index)
{
case 0:
{
Log_DebugPrintf("CDROM command register <- 0x%02X", ZeroExtend32(value));
if (m_command_state == CommandState::Idle)
BeginCommand(static_cast<Command>(value));
else
Log_ErrorPrintf("Ignoring write (0x%02X) to command register in non-idle state", ZeroExtend32(value));
return;
}
case 1:
{
Log_ErrorPrintf("Sound map data out <- 0x%02X", ZeroExtend32(value));
return;
}
case 2:
{
Log_ErrorPrintf("Sound map coding info <- 0x%02X", ZeroExtend32(value));
return;
}
case 3:
{
Log_ErrorPrintf("Audio volume for right-to-left output <- 0x%02X", ZeroExtend32(value));
return;
}
}
}
break;
case 2:
{
switch (m_status.index)
{
case 0:
{
if (m_param_fifo.IsFull())
{
Log_WarningPrintf("Parameter FIFO overflow");
m_param_fifo.RemoveOne();
}
m_param_fifo.Push(value);
UpdateStatusRegister();
return;
}
case 1:
{
Log_DebugPrintf("Interrupt enable register <- 0x%02X", ZeroExtend32(value));
m_interrupt_enable_register = value & INTERRUPT_REGISTER_MASK;
return;
}
case 2:
{
Log_ErrorPrintf("Audio volume for left-to-left output <- 0x%02X", ZeroExtend32(value));
return;
}
case 3:
{
Log_ErrorPrintf("Audio volume for right-to-left output <- 0x%02X", ZeroExtend32(value));
return;
}
}
}
break;
case 3:
{
switch (m_status.index)
{
case 0:
{
// TODO: sector buffer is not the data fifo
Log_DebugPrintf("Request register <- 0x%02X", value);
const RequestRegister rr{value};
Assert(!rr.SMEN);
if (rr.BFRD)
{
LoadDataFIFO();
}
else
{
Log_DebugPrintf("Clearing data FIFO");
m_data_fifo.Clear();
}
UpdateStatusRegister();
return;
}
case 1:
{
Log_DebugPrintf("Interrupt flag register <- 0x%02X", value);
m_interrupt_flag_register &= ~(value & INTERRUPT_REGISTER_MASK);
if (m_interrupt_flag_register == 0 && m_command_state == CommandState::WaitForIRQClear)
{
m_system->Synchronize();
m_command_state = CommandState::WaitForExecute;
m_system->SetDowncount(m_command_remaining_ticks);
}
// Bit 6 clears the parameter FIFO.
if (value & 0x40)
{
m_param_fifo.Clear();
UpdateStatusRegister();
}
return;
}
case 2:
{
Log_ErrorPrintf("Audio volume for left-to-right output <- 0x%02X", ZeroExtend32(value));
return;
}
case 3:
{
Log_ErrorPrintf("Audio volume apply changes <- 0x%02X", ZeroExtend32(value));
return;
}
}
}
break;
}
Log_ErrorPrintf("Unknown CDROM register write: offset=0x%02X, index=%d, value=0x%02X", offset,
ZeroExtend32(m_status.index.GetValue()), ZeroExtend32(value));
}
u32 CDROM::DMARead()
{
if (m_data_fifo.IsEmpty())
{
Log_ErrorPrintf("DMA read on empty data FIFO");
return UINT32_C(0xFFFFFFFF);
}
u32 data;
if (m_data_fifo.GetSize() >= sizeof(data))
{
std::memcpy(&data, m_data_fifo.GetFrontPointer(), sizeof(data));
m_data_fifo.Remove(sizeof(data));
}
else
{
Log_WarningPrintf("Unaligned DMA read on FIFO(%u)", m_data_fifo.GetSize());
data = 0;
std::memcpy(&data, m_data_fifo.GetFrontPointer(), m_data_fifo.GetSize());
m_data_fifo.Clear();
}
// Log_DebugPrintf("DMA Read -> 0x%08X (%u remaining)", data, m_data_fifo.GetSize());
return data;
}
void CDROM::SetInterrupt(Interrupt interrupt)
{
m_interrupt_flag_register = static_cast<u8>(interrupt);
if (HasPendingInterrupt())
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::CDROM);
}
void CDROM::PushStatResponse(Interrupt interrupt /*= Interrupt::ACK*/)
{
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(interrupt);
}
void CDROM::UpdateStatusRegister()
{
m_status.ADPBUSY = false;
m_status.PRMEMPTY = m_param_fifo.IsEmpty();
m_status.PRMWRDY = !m_param_fifo.IsFull();
m_status.RSLRRDY = !m_response_fifo.IsEmpty();
m_status.DRQSTS = !m_data_fifo.IsEmpty();
m_status.BUSYSTS = m_command_state == CommandState::WaitForExecute;
}
u32 CDROM::GetTicksForCommand() const
{
switch (m_command)
{
case Command::ReadN:
case Command::ReadS:
{
// more if seeking..
return 1000;
}
case Command::Pause:
return 1000;
default:
return 1000;
}
}
u32 CDROM::GetTicksForRead() const
{
return m_mode.double_speed ? (MASTER_CLOCK / 150) : (MASTER_CLOCK / 75);
}
void CDROM::Execute(TickCount ticks)
{
switch (m_command_state)
{
case CommandState::Idle:
case CommandState::WaitForIRQClear:
break;
case CommandState::WaitForExecute:
{
m_command_remaining_ticks -= ticks;
if (m_command_remaining_ticks <= 0)
ExecuteCommand();
else
m_system->SetDowncount(m_command_remaining_ticks);
}
break;
default:
UnreachableCode();
break;
}
if (m_reading)
{
m_sector_read_remaining_ticks -= ticks;
if (m_sector_read_remaining_ticks <= 0)
DoSectorRead();
else
m_system->SetDowncount(m_sector_read_remaining_ticks);
}
}
void CDROM::BeginCommand(Command command)
{
m_response_fifo.Clear();
m_system->Synchronize();
m_command = command;
m_command_stage = 0;
m_command_remaining_ticks = GetTicksForCommand();
if (m_command_remaining_ticks == 0)
{
ExecuteCommand();
}
else
{
m_command_state = CommandState::WaitForExecute;
m_system->SetDowncount(m_command_remaining_ticks);
UpdateStatusRegister();
}
}
void CDROM::NextCommandStage(bool wait_for_irq, u32 time)
{
// prevent re-execution when synchronizing below
m_command_state = CommandState::WaitForIRQClear;
m_command_remaining_ticks = time;
m_command_stage++;
UpdateStatusRegister();
if (wait_for_irq)
return;
m_system->Synchronize();
m_command_state = CommandState::WaitForExecute;
m_system->SetDowncount(m_command_remaining_ticks);
UpdateStatusRegister();
}
void CDROM::EndCommand()
{
m_param_fifo.Clear();
m_command_state = CommandState::Idle;
m_command = Command::Sync;
m_command_stage = 0;
m_command_remaining_ticks = 0;
UpdateStatusRegister();
}
void CDROM::ExecuteCommand()
{
Log_DevPrintf("CDROM executing command 0x%02X stage %u", ZeroExtend32(static_cast<u8>(m_command)), m_command_stage);
switch (m_command)
{
case Command::Getstat:
{
Log_DebugPrintf("CDROM Getstat command");
// if bit 0 or 2 is set, send an additional byte
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
EndCommand();
return;
}
case Command::Test:
{
const u8 subcommand = m_param_fifo.Pop();
ExecuteTestCommand(subcommand);
return;
}
case Command::GetID:
{
Log_DebugPrintf("CDROM GetID command - stage %u", m_command_stage);
if (m_command_stage == 0)
{
if (!HasMedia())
{
static constexpr u8 response[] = {0x11, 0x80};
m_response_fifo.PushRange(response, countof(response));
SetInterrupt(Interrupt::INT5);
EndCommand();
}
else
{
// INT3(stat), ...
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
NextCommandStage(true, GetTicksForCommand());
}
}
else
{
static constexpr u8 response2[] = {0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41}; // last byte is 0x49 for EU
m_response_fifo.PushRange(response2, countof(response2));
SetInterrupt(Interrupt::INT2);
EndCommand();
}
return;
}
case Command::Setloc:
{
// TODO: Verify parameter count
m_setloc.minute = BCDToDecimal(m_param_fifo.Peek(0));
m_setloc.second = BCDToDecimal(m_param_fifo.Peek(1));
m_setloc.frame = BCDToDecimal(m_param_fifo.Peek(2));
m_setloc_dirty = true;
Log_DebugPrintf("CDROM setloc command (%u, %u, %u)", ZeroExtend32(m_setloc.minute), ZeroExtend32(m_setloc.second),
ZeroExtend32(m_setloc.frame));
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
EndCommand();
return;
}
case Command::SeekL:
case Command::SeekP:
{
// TODO: Data vs audio mode
Log_DebugPrintf("CDROM seek command");
if (m_command_stage == 0)
{
Assert(m_setloc_dirty);
StopReading();
if (!m_media || !m_media->Seek(m_setloc.minute, m_setloc.second, m_setloc.frame))
{
Panic("Error in Setloc command");
return;
}
m_setloc_dirty = false;
m_secondary_status.motor_on = true;
m_secondary_status.seeking = true;
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
NextCommandStage(false, 100);
}
else
{
m_secondary_status.seeking = false;
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::INT2);
EndCommand();
}
return;
}
case Command::Setfilter:
{
const u8 file = m_param_fifo.Peek(0);
const u8 channel = m_param_fifo.Peek(1);
Log_WarningPrintf("CDROM setfilter command 0x%02X 0x%02X", ZeroExtend32(file), ZeroExtend32(channel));
PushStatResponse(Interrupt::ACK);
EndCommand();
return;
}
case Command::Setmode:
{
const u8 mode = m_param_fifo.Peek(0);
Log_DebugPrintf("CDROM setmode command 0x%02X", ZeroExtend32(mode));
m_mode.bits = mode;
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
EndCommand();
return;
}
case Command::ReadN:
case Command::ReadS:
{
Log_DebugPrintf("CDROM read command");
// TODO: Seek timing and clean up...
if (m_setloc_dirty)
{
if (!m_media || !m_media->Seek(m_setloc.minute, m_setloc.second, m_setloc.frame))
{
Panic("Seek error");
}
m_setloc_dirty = false;
}
EndCommand();
BeginReading();
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
return;
}
case Command::Pause:
{
if (m_command_stage == 0)
{
const bool was_reading = m_reading;
Log_DebugPrintf("CDROM pause command");
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
StopReading();
NextCommandStage(true, was_reading ? (m_mode.double_speed ? 2000000 : 1000000) : 1000);
}
else
{
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::INT2);
EndCommand();
}
return;
}
case Command::Init:
{
if (m_command_stage == 0)
{
Log_DebugPrintf("CDROM init command");
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
StopReading();
NextCommandStage(true, 1000);
}
else
{
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::INT2);
EndCommand();
}
return;
}
break;
case Command::Demute:
{
Log_DebugPrintf("CDROM demute command");
m_muted = false;
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::ACK);
EndCommand();
}
break;
default:
Panic("Unknown command");
break;
}
}
void CDROM::ExecuteTestCommand(u8 subcommand)
{
switch (subcommand)
{
case 0x20: // Get CDROM BIOS Date/Version
{
Log_DebugPrintf("Get CDROM BIOS Date/Version");
static constexpr u8 response[] = {0x94, 0x09, 0x19, 0xC0};
m_response_fifo.PushRange(response, countof(response));
SetInterrupt(Interrupt::ACK);
EndCommand();
return;
}
case 0x22:
{
Log_DebugPrintf("Get CDROM region ID string");
static constexpr u8 response[] = {'f', 'o', 'r', ' ', 'U', '/', 'C'};
m_response_fifo.PushRange(response, countof(response));
SetInterrupt(Interrupt::ACK);
EndCommand();
return;
}
default:
{
Log_ErrorPrintf("Unknown test command 0x%02X", subcommand);
return;
}
}
}
void CDROM::BeginReading()
{
Log_DebugPrintf("Starting reading");
m_secondary_status.motor_on = true;
m_secondary_status.seeking = false;
m_secondary_status.reading = true;
m_reading = true;
m_sector_read_remaining_ticks = GetTicksForRead();
m_system->SetDowncount(m_sector_read_remaining_ticks);
UpdateStatusRegister();
}
void CDROM::DoSectorRead()
{
if (HasPendingInterrupt())
{
// can't read with a pending interrupt?
Log_WarningPrintf("Missed sector read...");
// m_sector_read_remaining_ticks += 10;
// m_system->SetDowncount(m_sector_read_remaining_ticks);
// return;
}
// TODO: Error handling
// TODO: Sector buffer should be two sectors?
Assert(!m_mode.ignore_bit);
m_sector_buffer.resize(CDImage::RAW_SECTOR_SIZE);
m_media->Read(CDImage::ReadMode::RawSector, 1, m_sector_buffer.data());
Log_DevPrintf("Read sector %llu: mode %u submode 0x%02X", m_media->GetCurrentLBA(), ZeroExtend32(m_sector_buffer[15]),
ZeroExtend32(m_sector_buffer[18]));
bool pass_to_cpu = true;
if (m_mode.xa_enable)
{
const CDSectorHeader* sh =
reinterpret_cast<const CDSectorHeader*>(m_sector_buffer.data() + CDImage::SECTOR_SYNC_SIZE);
if (sh->sector_mode == 2)
{
const XASubHeader* xsh = reinterpret_cast<const XASubHeader*>(m_sector_buffer.data() + CDImage::SECTOR_SYNC_SIZE +
sizeof(CDSectorHeader));
if (xsh->submode.realtime && xsh->submode.audio)
{
// TODO: Decode audio sector. Do we still transfer this to the CPU?
Log_WarningPrintf("Decode CD-XA audio sector");
m_sector_buffer.clear();
pass_to_cpu = false;
}
if (xsh->submode.eof)
{
Log_WarningPrintf("End of CD-XA file");
}
}
}
if (pass_to_cpu)
{
m_response_fifo.Push(m_secondary_status.bits);
SetInterrupt(Interrupt::INT1);
UpdateStatusRegister();
}
m_sector_read_remaining_ticks += GetTicksForRead();
m_system->SetDowncount(m_sector_read_remaining_ticks);
}
void CDROM::StopReading()
{
if (!m_reading)
return;
Log_DebugPrintf("Stopping reading");
m_secondary_status.reading = false;
m_reading = false;
}
void CDROM::LoadDataFIFO()
{
// any data to load?
if (m_sector_buffer.empty())
{
Log_DevPrintf("Attempting to load empty sector buffer");
return;
}
if (m_mode.read_raw_sector)
{
m_data_fifo.PushRange(m_sector_buffer.data() + CDImage::SECTOR_SYNC_SIZE,
CDImage::RAW_SECTOR_SIZE - CDImage::SECTOR_SYNC_SIZE);
}
else
{
m_data_fifo.PushRange(m_sector_buffer.data() + CDImage::SECTOR_SYNC_SIZE + 12, CDImage::DATA_SECTOR_SIZE);
}
Log_DebugPrintf("Loaded %u bytes to data FIFO", m_data_fifo.GetSize());
m_sector_buffer.clear();
}

230
src/core/cdrom.h Normal file
View File

@ -0,0 +1,230 @@
#pragma once
#include "common/bitfield.h"
#include "common/fifo_queue.h"
#include "types.h"
#include <string>
#include <vector>
class CDImage;
class StateWrapper;
class System;
class DMA;
class InterruptController;
class CDROM
{
public:
CDROM();
~CDROM();
bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller);
void Reset();
bool DoState(StateWrapper& sw);
bool HasMedia() const { return static_cast<bool>(m_media); }
bool InsertMedia(const char* filename);
void RemoveMedia();
// I/O
u8 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u8 value);
u32 DMARead();
void Execute(TickCount ticks);
private:
static constexpr u32 PARAM_FIFO_SIZE = 16;
static constexpr u32 RESPONSE_FIFO_SIZE = 16;
static constexpr u32 DATA_FIFO_SIZE = 4096;
static constexpr u32 NUM_INTERRUPTS = 32;
static constexpr u32 SECTOR_BUFFER_SIZE = (2352 - 12);
static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F;
enum class Interrupt : u8
{
INT1 = 0x01,
INT2 = 0x02,
ACK = 0x03,
INT4 = 0x04,
INT5 = 0x05
};
enum class Command : u8
{
Sync = 0x00,
Getstat = 0x01,
Setloc = 0x02,
Play = 0x03,
Forward = 0x04,
Backward = 0x05,
ReadN = 0x06,
MotorOn = 0x07,
Stop = 0x08,
Pause = 0x09,
Init = 0x0A,
Mute = 0x0B,
Demute = 0x0C,
Setfilter = 0x0D,
Setmode = 0x0E,
Getparam = 0x0F,
GetlocL = 0x10,
GetlocP = 0x11,
SetSession = 0x12,
GetTN = 0x13,
GetTD = 0x14,
SeekL = 0x15,
SeekP = 0x16,
SetClock = 0x17,
GetClock = 0x18,
Test = 0x19,
GetID = 0x1A,
ReadS = 0x1B,
Reset = 0x1C,
GetQ = 0x1D,
ReadTOC = 0x1E,
VideoCD = 0x1F
};
enum class CommandState : u32
{
Idle,
WaitForExecute,
WaitForIRQClear
};
struct Loc
{
u8 minute;
u8 second;
u8 frame;
};
union StatusRegister
{
u8 bits;
BitField<u8, u8, 0, 2> index;
BitField<u8, bool, 2, 1> ADPBUSY;
BitField<u8, bool, 3, 1> PRMEMPTY;
BitField<u8, bool, 4, 1> PRMWRDY;
BitField<u8, bool, 5, 1> RSLRRDY;
BitField<u8, bool, 6, 1> DRQSTS;
BitField<u8, bool, 7, 1> BUSYSTS;
};
union SecondaryStatusRegister
{
u8 bits;
BitField<u8, bool, 0, 1> error;
BitField<u8, bool, 1, 1> motor_on;
BitField<u8, bool, 2, 1> seek_error;
BitField<u8, bool, 3, 1> id_error;
BitField<u8, bool, 4, 1> shell_open;
BitField<u8, bool, 5, 1> reading;
BitField<u8, bool, 6, 1> seeking;
BitField<u8, bool, 7, 1> playing_cdda;
};
union ModeRegister
{
u8 bits;
BitField<u8, bool, 0, 1> cdda;
BitField<u8, bool, 1, 1> auto_pause;
BitField<u8, bool, 2, 1> report_audio;
BitField<u8, bool, 3, 1> xa_filter;
BitField<u8, bool, 4, 1> ignore_bit;
BitField<u8, bool, 5, 1> read_raw_sector;
BitField<u8, bool, 6, 1> xa_enable;
BitField<u8, bool, 7, 1> double_speed;
};
union RequestRegister
{
u8 bits;
BitField<u8, bool, 5, 1> SMEN;
BitField<u8, bool, 6, 1> BFWR;
BitField<u8, bool, 7, 1> BFRD;
};
struct CDSectorHeader
{
u8 minute;
u8 second;
u8 frame;
u8 sector_mode;
};
struct XASubHeader
{
u8 file_number;
u8 channel_number;
union Submode
{
u8 bits;
BitField<u8, bool, 0, 1> eor;
BitField<u8, bool, 1, 1> video;
BitField<u8, bool, 2, 1> audio;
BitField<u8, bool, 3, 1> data;
BitField<u8, bool, 4, 1> trigger;
BitField<u8, bool, 5, 1> form2;
BitField<u8, bool, 6, 1> realtime;
BitField<u8, bool, 7, 1> eof;
} submode;
union Codinginfo
{
u8 bits;
BitField<u8, u8, 0, 2> mono_stereo;
BitField<u8, u8, 2, 2> sample_rate;
BitField<u8, u8, 4, 2> bits_per_sample;
BitField<u8, bool, 6, 1> emphasis;
} codinginfo;
};
bool HasPendingInterrupt() const { return m_interrupt_flag_register != 0; }
void SetInterrupt(Interrupt interrupt);
void PushStatResponse(Interrupt interrupt = Interrupt::ACK);
void UpdateStatusRegister();
u32 GetTicksForCommand() const;
u32 GetTicksForRead() const;
void BeginCommand(Command command); // also update status register
void NextCommandStage(bool wait_for_irq, u32 time);
void EndCommand(); // also updates status register
void ExecuteCommand();
void ExecuteTestCommand(u8 subcommand);
void BeginReading();
void DoSectorRead();
void StopReading();
void LoadDataFIFO();
System* m_system = nullptr;
DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr;
std::unique_ptr<CDImage> m_media;
std::string m_media_filename;
CommandState m_command_state = CommandState::Idle;
Command m_command = Command::Sync;
u32 m_command_stage = 0;
TickCount m_command_remaining_ticks = 0;
TickCount m_sector_read_remaining_ticks = 0;
bool m_reading = false;
bool m_muted = false;
Loc m_setloc = {};
bool m_setloc_dirty = false;
StatusRegister m_status = {};
SecondaryStatusRegister m_secondary_status = {};
ModeRegister m_mode = {};
u8 m_interrupt_enable_register = INTERRUPT_REGISTER_MASK;
u8 m_interrupt_flag_register = 0;
InlineFIFOQueue<u8, PARAM_FIFO_SIZE> m_param_fifo;
InlineFIFOQueue<u8, RESPONSE_FIFO_SIZE> m_response_fifo;
HeapFIFOQueue<u8, DATA_FIFO_SIZE> m_data_fifo;
std::vector<u8> m_sector_buffer;
};

477
src/core/core.vcxproj Normal file
View File

@ -0,0 +1,477 @@
<?xml version="1.0" encoding="utf-8"?>
<Project InitialTargets="UNDUPOBJ" DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="DebugFast|Win32">
<Configuration>DebugFast</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="DebugFast|x64">
<Configuration>DebugFast</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseLTCG|Win32">
<Configuration>ReleaseLTCG</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseLTCG|x64">
<Configuration>ReleaseLTCG</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="bus.cpp" />
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="cpu_core.cpp" />
<ClCompile Include="cpu_disasm.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" />
<ClCompile Include="gpu_hw.cpp" />
<ClCompile Include="gpu_hw_opengl.cpp" />
<ClCompile Include="host_interface.cpp" />
<ClCompile Include="interrupt_controller.cpp" />
<ClCompile Include="mdec.cpp" />
<ClCompile Include="memory_card.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="spu.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="timers.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="bus.h" />
<ClInclude Include="cdrom.h" />
<ClInclude Include="cpu_core.h" />
<ClInclude Include="cpu_disasm.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="cpu_types.h" />
<ClInclude Include="dma.h" />
<ClInclude Include="gpu.h" />
<ClInclude Include="gpu_hw.h" />
<ClInclude Include="gpu_hw_opengl.h" />
<ClInclude Include="gte_types.h" />
<ClInclude Include="host_interface.h" />
<ClInclude Include="interrupt_controller.h" />
<ClInclude Include="mdec.h" />
<ClInclude Include="memory_card.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="pad_device.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="spu.h" />
<ClInclude Include="system.h" />
<ClInclude Include="timers.h" />
<ClInclude Include="types.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\dep\libsamplerate\libsamplerate.vcxproj">
<Project>{2f2a2b7b-60b3-478c-921e-3633b3c45c3f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\stb\stb.vcxproj">
<Project>{ed601289-ac1a-46b8-a8ed-17db9eb73423}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\YBaseLib\Source\YBaseLib.vcxproj">
<Project>{b56ce698-7300-4fa5-9609-942f1d05c5a2}</Project>
</ProjectReference>
<ProjectReference Include="..\common\common.vcxproj">
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />
<None Include="bus.inl" />
<None Include="gte.inl" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{868B98C8-65A1-494B-8346-250A73A48C0A}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>pse</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
<OmitFramePointers>true</OmitFramePointers>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
<OmitFramePointers>true</OmitFramePointers>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<!-- ================ UNDUPOBJ ================ -->
<!-- relevant topics -->
<!-- https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
<!-- https://stackoverflow.com/questions/7033855/msvc10-mp-builds-not-multicore-across-folders-in-a-project -->
<!-- https://stackoverflow.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target -->
<!-- other maybe related info -->
<!-- https://stackoverflow.com/questions/841913/modify-msbuild-itemgroup-metadata -->
<UsingTask TaskName="UNDUPOBJ_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputDir ParameterType="System.String" Required="true" />
<ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Code><![CDATA[
//general outline: for each item (in ClCompile) assign it to a subdirectory of $(IntDir) by allocating subdirectories 0,1,2, etc., as needed to prevent duplicate filenames from clobbering each other
//this minimizes the number of batches that need to be run, since each subdirectory will necessarily be in a distinct batch due to /Fo specifying that output subdirectory
var assignmentMap = new Dictionary<string,int>();
HashSet<string> neededDirectories = new HashSet<string>();
foreach( var item in ItemList )
{
//solve bug e.g. Checkbox.cpp vs CheckBox.cpp
var filename = item.GetMetadata("Filename").ToUpperInvariant();
//assign reused filenames to increasing numbers
//assign previously unused filenames to 0
int assignment = 0;
if(assignmentMap.TryGetValue(filename, out assignment))
assignmentMap[filename] = ++assignment;
else
assignmentMap[filename] = 0;
var thisFileOutdir = Path.Combine(OutputDir,assignment.ToString()) + "/"; //take care it ends in / so /Fo knows it's a directory and not a filename
item.SetMetadata( "ObjectFileName", thisFileOutdir );
}
foreach(var needed in neededDirectories)
System.IO.Directory.CreateDirectory(needed);
OutputItemList = ItemList;
ItemList = new Microsoft.Build.Framework.ITaskItem[0];
]]></Code>
</Task>
</UsingTask>
<Target Name="UNDUPOBJ">
<!-- see stackoverflow topics for discussion on why we need to do some loopy copying stuff here -->
<ItemGroup>
<ClCompileCopy Include="@(ClCompile)" />
<ClCompile Remove="@(ClCompile)" />
</ItemGroup>
<UNDUPOBJ_TASK OutputDir="$(IntDir)" ItemList="@(ClCompileCopy)" OutputItemList="@(ClCompile)">
<Output ItemName="ClCompile" TaskParameter="OutputItemList" />
</UNDUPOBJ_TASK>
</Target>
<!-- ================ UNDUPOBJ ================ -->
<PropertyGroup Label="Globals">
<IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename>
<ProjectName>core</ProjectName>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="system.cpp" />
<ClCompile Include="cpu_core.cpp" />
<ClCompile Include="cpu_disasm.cpp" />
<ClCompile Include="bus.cpp" />
<ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" />
<ClCompile Include="gpu_hw_opengl.cpp" />
<ClCompile Include="gpu_hw.cpp" />
<ClCompile Include="host_interface.cpp" />
<ClCompile Include="interrupt_controller.cpp" />
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="timers.cpp" />
<ClCompile Include="spu.cpp" />
<ClCompile Include="mdec.cpp" />
<ClCompile Include="memory_card.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="system.h" />
<ClInclude Include="cpu_core.h" />
<ClInclude Include="cpu_types.h" />
<ClInclude Include="cpu_disasm.h" />
<ClInclude Include="bus.h" />
<ClInclude Include="dma.h" />
<ClInclude Include="gpu.h" />
<ClInclude Include="gpu_hw_opengl.h" />
<ClInclude Include="gpu_hw.h" />
<ClInclude Include="host_interface.h" />
<ClInclude Include="interrupt_controller.h" />
<ClInclude Include="cdrom.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="gte_types.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="pad_device.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="timers.h" />
<ClInclude Include="spu.h" />
<ClInclude Include="mdec.h" />
<ClInclude Include="memory_card.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />
<None Include="bus.inl" />
<None Include="gte.inl" />
</ItemGroup>
</Project>

1269
src/core/cpu_core.cpp Normal file

File diff suppressed because it is too large Load Diff

150
src/core/cpu_core.h Normal file
View File

@ -0,0 +1,150 @@
#pragma once
#include "common/bitfield.h"
#include "cpu_types.h"
#include "gte.h"
#include "types.h"
#include <array>
#include <optional>
class StateWrapper;
class Bus;
namespace CPU {
class Core
{
public:
static constexpr VirtualMemoryAddress RESET_VECTOR = UINT32_C(0xBFC00000);
static constexpr PhysicalMemoryAddress DCACHE_LOCATION = UINT32_C(0x1F800000);
static constexpr PhysicalMemoryAddress DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00);
static constexpr PhysicalMemoryAddress DCACHE_OFFSET_MASK = UINT32_C(0x000003FF);
static constexpr PhysicalMemoryAddress DCACHE_SIZE = UINT32_C(0x00000400);
Core();
~Core();
bool Initialize(Bus* bus);
void Reset();
bool DoState(StateWrapper& sw);
void Execute();
const Registers& GetRegs() const { return m_regs; }
Registers& GetRegs() { return m_regs; }
TickCount GetPendingTicks() const { return m_pending_ticks; }
void ResetPendingTicks() { m_pending_ticks = 0; }
void SetDowncount(TickCount downcount) { m_downcount = (downcount < m_downcount) ? downcount : m_downcount; }
void ResetDowncount() { m_downcount = MAX_SLICE_SIZE; }
// Sets the PC and flushes the pipeline.
void SetPC(u32 new_pc);
// Memory reads variants which do not raise exceptions.
bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value);
bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value);
bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value);
bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
// External IRQs
void SetExternalInterrupt(u8 bit);
void ClearExternalInterrupt(u8 bit);
private:
template<MemoryAccessType type, MemoryAccessSize size>
bool DoMemoryAccess(VirtualMemoryAddress address, u32& value);
template<MemoryAccessType type, MemoryAccessSize size>
bool DoAlignmentCheck(VirtualMemoryAddress address);
template<MemoryAccessType type, MemoryAccessSize size>
void DoScratchpadAccess(PhysicalMemoryAddress address, u32& value);
bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value);
bool WriteMemoryByte(VirtualMemoryAddress addr, u8 value);
bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value);
// state helpers
bool InUserMode() const { return m_cop0_regs.sr.KUc; }
bool InKernelMode() const { return !m_cop0_regs.sr.KUc; }
void DisassembleAndPrint(u32 addr);
void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_after);
// Fetches the instruction at m_regs.npc
bool FetchInstruction();
void ExecuteInstruction();
void ExecuteCop0Instruction();
void ExecuteCop2Instruction();
void Branch(u32 target);
// exceptions
u32 GetExceptionVector(Exception excode) const;
void RaiseException(Exception excode);
void RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE);
bool DispatchInterrupts();
// flushes any load delays if present
void FlushLoadDelay();
// clears pipeline of load/branch delays
void FlushPipeline();
// helper functions for registers which aren't writable
u32 ReadReg(Reg rs);
void WriteReg(Reg rd, u32 value);
// helper for generating a load delay write
void WriteRegDelayed(Reg rd, u32 value);
// write to cache control register
void WriteCacheControl(u32 value);
// read/write cop0 regs
std::optional<u32> ReadCop0Reg(Cop0Reg reg);
void WriteCop0Reg(Cop0Reg reg, u32 value);
Bus* m_bus = nullptr;
// ticks the CPU has executed
TickCount m_pending_ticks = 0;
TickCount m_downcount = MAX_SLICE_SIZE;
Registers m_regs = {};
Cop0Registers m_cop0_regs = {};
Instruction m_next_instruction = {};
// address of the instruction currently being executed
Instruction m_current_instruction = {};
u32 m_current_instruction_pc = 0;
bool m_current_instruction_in_branch_delay_slot = false;
bool m_current_instruction_was_branch_taken = false;
bool m_next_instruction_is_branch_delay_slot = false;
bool m_branch_was_taken = false;
// load delays
Reg m_load_delay_reg = Reg::count;
u32 m_load_delay_old_value = 0;
Reg m_next_load_delay_reg = Reg::count;
u32 m_next_load_delay_old_value = 0;
u32 m_cache_control = 0;
// data cache (used as scratchpad)
std::array<u8, DCACHE_SIZE> m_dcache = {};
GTE::Core m_cop2;
};
extern bool TRACE_EXECUTION;
} // namespace CPU
#include "cpu_core.inl"

164
src/core/cpu_core.inl Normal file
View File

@ -0,0 +1,164 @@
#pragma once
#include "YBaseLib/Assert.h"
#include "bus.h"
#include "cpu_core.h"
namespace CPU {
template<MemoryAccessType type, MemoryAccessSize size>
bool Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
{
switch (address >> 29)
{
case 0x00: // KUSEG 0M-512M
{
if constexpr (type == MemoryAccessType::Write)
{
if (m_cop0_regs.sr.Isc)
return true;
}
const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF);
if ((phys_addr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
{
DoScratchpadAccess<type, size>(phys_addr, value);
return true;
}
if (!m_bus->DispatchAccess<type, size>(phys_addr, value))
{
Panic("Bus error");
return false;
}
return true;
}
case 0x01: // KUSEG 512M-1024M
case 0x02: // KUSEG 1024M-1536M
case 0x03: // KUSEG 1536M-2048M
{
// Above 512mb raises an exception.
return false;
}
case 0x04: // KSEG0 - physical memory cached
{
if constexpr (type == MemoryAccessType::Write)
{
if (m_cop0_regs.sr.Isc)
return true;
}
const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF);
if ((phys_addr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
{
DoScratchpadAccess<type, size>(phys_addr, value);
return true;
}
if (!m_bus->DispatchAccess<type, size>(phys_addr, value))
{
Panic("Bus error");
return false;
}
return true;
}
break;
case 0x05: // KSEG1 - physical memory uncached
{
const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF);
if (!m_bus->DispatchAccess<type, size>(phys_addr, value))
{
Panic("Bus error");
return false;
}
return true;
}
break;
case 0x06: // KSEG2
case 0x07: // KSEG2
{
if (address == 0xFFFE0130)
{
if constexpr (type == MemoryAccessType::Read)
value = m_cache_control;
else
WriteCacheControl(value);
return true;
}
else
{
return false;
}
}
default:
UnreachableCode();
return false;
}
}
template<MemoryAccessType type, MemoryAccessSize size>
bool CPU::Core::DoAlignmentCheck(VirtualMemoryAddress address)
{
if constexpr (size == MemoryAccessSize::HalfWord)
{
if (Common::IsAlignedPow2(address, 2))
return true;
}
else if constexpr (size == MemoryAccessSize::Word)
{
if (Common::IsAlignedPow2(address, 4))
return true;
}
else
{
return true;
}
m_cop0_regs.BadVaddr = address;
RaiseException(type == MemoryAccessType::Read ? Exception::AdEL : Exception::AdES);
return false;
}
template<MemoryAccessType type, MemoryAccessSize size>
void CPU::Core::DoScratchpadAccess(PhysicalMemoryAddress address, u32& value)
{
const PhysicalMemoryAddress cache_offset = address & DCACHE_OFFSET_MASK;
if constexpr (size == MemoryAccessSize::Byte)
{
if constexpr (type == MemoryAccessType::Read)
value = ZeroExtend32(m_dcache[cache_offset]);
else
m_dcache[cache_offset] = Truncate8(value);
}
else if constexpr (size == MemoryAccessSize::HalfWord)
{
if constexpr (type == MemoryAccessType::Read)
{
u16 temp;
std::memcpy(&temp, &m_dcache[cache_offset], sizeof(temp));
value = ZeroExtend32(temp);
}
else
{
u16 temp = Truncate16(value);
std::memcpy(&m_dcache[cache_offset], &temp, sizeof(temp));
}
}
else if constexpr (size == MemoryAccessSize::Word)
{
if constexpr (type == MemoryAccessType::Read)
std::memcpy(&value, &m_dcache[cache_offset], sizeof(value));
else
std::memcpy(&m_dcache[cache_offset], &value, sizeof(value));
}
}
} // namespace CPU

377
src/core/cpu_disasm.cpp Normal file
View File

@ -0,0 +1,377 @@
#include "cpu_disasm.h"
#include "cpu_core.h"
#include <array>
namespace CPU {
enum Operand : u8
{
Operand_None,
i_rs,
i_rt,
i_imm,
j_target,
r_rs,
r_rt,
r_rd,
r_shamt,
r_funct
};
struct TableEntry
{
const char* format;
};
static const std::array<const char*, 32> s_reg_names = {
{"$zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"}};
static const std::array<const char*, 64> s_base_table = {{
"", // 0
"UNKNOWN", // 1
"j $jt", // 2
"jal $jt", // 3
"beq $rs, $rt, $rel", // 4
"bne $rs, $rt, $rel", // 5
"blez $rs, $rel", // 6
"bgtz $rs, $rel", // 7
"addi $rt, $rs, $imm", // 8
"addiu $rt, $rs, $imm", // 9
"slti $rt, $rs, $imm", // 10
"sltiu $rt, $rs, $immu", // 11
"andi $rt, $rs, $immu", // 12
"ori $rt, $rs, $immu", // 13
"UNKNOWN", // 14
"lui $rt, $imm", // 15
"UNKNOWN", // 16
"UNKNOWN", // 17
"UNKNOWN", // 18
"UNKNOWN", // 19
"UNKNOWN", // 20
"UNKNOWN", // 21
"UNKNOWN", // 22
"UNKNOWN", // 23
"UNKNOWN", // 24
"UNKNOWN", // 25
"UNKNOWN", // 26
"UNKNOWN", // 27
"UNKNOWN", // 28
"UNKNOWN", // 29
"UNKNOWN", // 30
"UNKNOWN", // 31
"lb $rt, $offsetrs", // 32
"lh $rt, $offsetrs", // 33
"lwl $rt, $offsetrs", // 34
"lw $rt, $offsetrs", // 35
"lbu $rt, $offsetrs", // 36
"lhu $rt, $offsetrs", // 37
"lwr $rt, $offsetrs", // 38
"UNKNOWN", // 39
"sb $rt, $offsetrs", // 40
"sh $rt, $offsetrs", // 41
"swl $rt, $offsetrs", // 42
"sw $rt, $offsetrs", // 43
"UNKNOWN", // 44
"UNKNOWN", // 45
"swr $rt, $offsetrs", // 46
"UNKNOWN", // 47
"lwc0 $coprt, $offsetrs", // 48
"lwc1 $coprt, $offsetrs", // 49
"lwc2 $coprt, $offsetrs", // 50
"lwc3 $coprt, $offsetrs", // 51
"UNKNOWN", // 52
"UNKNOWN", // 53
"UNKNOWN", // 54
"UNKNOWN", // 55
"swc0 $coprt, $offsetrs", // 56
"swc1 $coprt, $offsetrs", // 57
"swc2 $coprt, $offsetrs", // 58
"swc3 $coprt, $offsetrs", // 59
"UNKNOWN", // 60
"UNKNOWN", // 61
"UNKNOWN", // 62
"UNKNOWN" // 63
}};
static const std::array<const char*, 64> s_special_table = {{
"sll $rd, $rt, $shamt", // 0
"UNKNOWN", // 1
"srl $rd, $rt, $shamt", // 2
"sra $rd, $rt, $shamt", // 3
"sllv $rd, $rt, $rs", // 4
"UNKNOWN", // 5
"srlv $rd, $rt, $rs", // 6
"srav $rd, $rt, $rs", // 7
"jr $rs", // 8
"jalr $rd, $rs", // 9
"UNKNOWN", // 10
"UNKNOWN", // 11
"syscall", // 12
"break", // 13
"UNKNOWN", // 14
"UNKNOWN", // 15
"mfhi $rd", // 16
"mthi $rs", // 17
"mflo $rd", // 18
"mtlo $rs", // 19
"UNKNOWN", // 20
"UNKNOWN", // 21
"UNKNOWN", // 22
"UNKNOWN", // 23
"mult $rs, $rt", // 24
"multu $rs, $rt", // 25
"div $rs, $rt", // 26
"divu $rs, $rt", // 27
"UNKNOWN", // 28
"UNKNOWN", // 29
"UNKNOWN", // 30
"UNKNOWN", // 31
"add $rd, $rs, $rt", // 32
"addu $rd, $rs, $rt", // 33
"sub $rd, $rs, $rt", // 34
"subu $rd, $rs, $rt", // 35
"and $rd, $rs, $rt", // 36
"or $rd, $rs, $rt", // 37
"xor $rd, $rs, $rt", // 38
"nor $rd, $rs, $rt", // 39
"UNKNOWN", // 40
"UNKNOWN", // 41
"slt $rd, $rs, $rt", // 42
"sltu $rd, $rs, $rt", // 43
"UNKNOWN", // 44
"UNKNOWN", // 45
"UNKNOWN", // 46
"UNKNOWN", // 47
"UNKNOWN", // 48
"UNKNOWN", // 49
"UNKNOWN", // 50
"UNKNOWN", // 51
"UNKNOWN", // 52
"UNKNOWN", // 53
"UNKNOWN", // 54
"UNKNOWN", // 55
"UNKNOWN", // 56
"UNKNOWN", // 57
"UNKNOWN", // 58
"UNKNOWN", // 59
"UNKNOWN", // 60
"UNKNOWN", // 61
"UNKNOWN", // 62
"UNKNOWN" // 63
}};
static const std::array<std::pair<CopCommonInstruction, const char*>, 5> s_cop_common_table = {
{{CopCommonInstruction::mfcn, "mfc$cop $rt, $coprd"},
{CopCommonInstruction::cfcn, "cfc$cop $rt, $coprd"},
{CopCommonInstruction::mtcn, "mtc$cop $rt, $coprd"},
{CopCommonInstruction::ctcn, "ctc$cop $rt, $coprd"},
{CopCommonInstruction::bcnc, "bc$cop$copcc $rel"}}};
static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}};
static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core* state, const char* format)
{
dest->Clear();
TinyString comment;
const char* str = format;
while (*str != '\0')
{
const char ch = *(str++);
if (ch != '$')
{
dest->AppendCharacter(ch);
continue;
}
if (std::strncmp(str, "rs", 2) == 0)
{
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rs.GetValue())]);
if (state)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ",
s_reg_names[static_cast<u8>(inst.r.rs.GetValue())],
state->GetRegs().r[static_cast<u8>(inst.r.rs.GetValue())]);
}
str += 2;
}
else if (std::strncmp(str, "rt", 2) == 0)
{
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rt.GetValue())]);
if (state)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ",
s_reg_names[static_cast<u8>(inst.r.rt.GetValue())],
state->GetRegs().r[static_cast<u8>(inst.r.rt.GetValue())]);
}
str += 2;
}
else if (std::strncmp(str, "rd", 2) == 0)
{
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rd.GetValue())]);
if (state)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ",
s_reg_names[static_cast<u8>(inst.r.rd.GetValue())],
state->GetRegs().r[static_cast<u8>(inst.r.rd.GetValue())]);
}
str += 2;
}
else if (std::strncmp(str, "shamt", 5) == 0)
{
dest->AppendFormattedString("%d", ZeroExtend32(inst.r.shamt.GetValue()));
str += 5;
}
else if (std::strncmp(str, "immu", 4) == 0)
{
dest->AppendFormattedString("%u", inst.i.imm_zext32());
str += 4;
}
else if (std::strncmp(str, "imm", 3) == 0)
{
// dest->AppendFormattedString("%d", static_cast<int>(inst.i.imm_sext32()));
dest->AppendFormattedString("%04x", inst.i.imm_zext32());
str += 3;
}
else if (std::strncmp(str, "rel", 3) == 0)
{
const u32 target = (pc + UINT32_C(4)) + (inst.i.imm_sext32() << 2);
dest->AppendFormattedString("%08x", target);
str += 3;
}
else if (std::strncmp(str, "offsetrs", 8) == 0)
{
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
dest->AppendFormattedString("%d(%s)", offset, s_reg_names[static_cast<u8>(inst.i.rs.GetValue())]);
if (state)
{
comment.AppendFormattedString("%saddr=0x%08X", comment.IsEmpty() ? "" : ", ",
state->GetRegs().r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
}
str += 8;
}
else if (std::strncmp(str, "jt", 2) == 0)
{
const u32 target = ((pc + UINT32_C(4)) & UINT32_C(0xF0000000)) | (inst.j.target << 2);
dest->AppendFormattedString("%08x", target);
str += 2;
}
else if (std::strncmp(str, "copcc", 5) == 0)
{
dest->AppendCharacter(((inst.bits & (UINT32_C(1) << 24)) != 0) ? 't' : 'f');
str += 5;
}
else if (std::strncmp(str, "coprd", 5) == 0)
{
dest->AppendFormattedString("%u", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
str += 5;
}
else if (std::strncmp(str, "coprt", 5) == 0)
{
dest->AppendFormattedString("%u", ZeroExtend32(static_cast<u8>(inst.r.rt.GetValue())));
str += 5;
}
else if (std::strncmp(str, "cop", 3) == 0)
{
dest->AppendFormattedString("%u", static_cast<u8>(inst.op.GetValue()) & INSTRUCTION_COP_N_MASK);
str += 3;
}
else
{
Panic("Unknown operand");
}
}
if (!comment.IsEmpty())
{
for (u32 i = dest->GetLength(); i < 30; i++)
dest->AppendCharacter(' ');
dest->AppendString("; ");
dest->AppendString(comment);
}
}
template<typename T>
void FormatCopInstruction(String* dest, u32 pc, Core* state, const Instruction inst,
const std::pair<T, const char*>* table, size_t table_size, T table_key)
{
for (size_t i = 0; i < table_size; i++)
{
if (table[i].first == table_key)
{
FormatInstruction(dest, inst, pc, state, table[i].second);
return;
}
}
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
}
void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state)
{
const Instruction inst{bits};
switch (inst.op)
{
case InstructionOp::funct:
FormatInstruction(dest, inst, pc, state, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
return;
case InstructionOp::cop0:
case InstructionOp::cop1:
case InstructionOp::cop2:
case InstructionOp::cop3:
{
if (inst.cop.IsCommonInstruction())
{
FormatCopInstruction(dest, pc, state, inst, s_cop_common_table.data(), s_cop_common_table.size(),
inst.cop.CommonOp());
}
else
{
switch (inst.op)
{
case InstructionOp::cop0:
{
FormatCopInstruction(dest, pc, state, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
}
break;
case InstructionOp::cop1:
case InstructionOp::cop2:
case InstructionOp::cop3:
default:
{
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
}
break;
}
}
}
break;
// special case for bltz/bgez{al}
case InstructionOp::b:
{
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
if (link)
FormatInstruction(dest, inst, pc, state, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
else
FormatInstruction(dest, inst, pc, state, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
}
break;
default:
FormatInstruction(dest, inst, pc, state, s_base_table[static_cast<u8>(inst.op.GetValue())]);
break;
}
}
} // namespace CPU

9
src/core/cpu_disasm.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "YBaseLib/String.h"
#include "cpu_types.h"
namespace CPU {
class Core;
void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state = nullptr);
} // namespace CPU

349
src/core/cpu_types.h Normal file
View File

@ -0,0 +1,349 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
namespace CPU {
enum class Reg : u8
{
zero,
at,
v0,
v1,
a0,
a1,
a2,
a3,
t0,
t1,
t2,
t3,
t4,
t5,
t6,
t7,
s0,
s1,
s2,
s3,
s4,
s5,
s6,
s7,
t8,
t9,
k0,
k1,
gp,
sp,
fp,
ra,
count
};
enum class InstructionOp : u8
{
funct = 0,
b = 1, // i.rt 0 - bltz, 1 - bgez, 16 - bltzal, 17 - bgezal
j = 2,
jal = 3,
beq = 4,
bne = 5,
blez = 6,
bgtz = 7,
addi = 8,
addiu = 9,
slti = 10,
sltiu = 11,
andi = 12,
ori = 13,
xori = 14,
lui = 15,
cop0 = 16,
cop1 = 17,
cop2 = 18,
cop3 = 19,
lb = 32,
lh = 33,
lwl = 34,
lw = 35,
lbu = 36,
lhu = 37,
lwr = 38,
sb = 40,
sh = 41,
swl = 42,
sw = 43,
swr = 46,
lwc0 = 48,
lwc1 = 49,
lwc2 = 50,
lwc3 = 51,
swc0 = 56,
swc1 = 57,
swc2 = 58,
swc3 = 59,
};
constexpr u8 INSTRUCTION_COP_BITS = 0x10;
constexpr u8 INSTRUCTION_COP_MASK = 0x3C;
constexpr u8 INSTRUCTION_COP_N_MASK = 0x03;
enum class InstructionFunct : u8
{
sll = 0,
srl = 2,
sra = 3,
sllv = 4,
srlv = 6,
srav = 7,
jr = 8,
jalr = 9,
syscall = 12,
break_ = 13,
mfhi = 16,
mthi = 17,
mflo = 18,
mtlo = 19,
mult = 24,
multu = 25,
div = 26,
divu = 27,
add = 32,
addu = 33,
sub = 34,
subu = 35,
and_ = 36,
or_ = 37,
xor_ = 38,
nor = 39,
sh = 41,
slt = 42,
sltu = 43
};
enum class CopCommonInstruction : u32
{
mfcn = 0b0000,
cfcn = 0b0010,
mtcn = 0b0100,
ctcn = 0b0110,
bcnc = 0b1000,
};
enum class Cop0Instruction : u32
{
tlbr = 0x01,
tlbwi = 0x02,
tlbwr = 0x04,
tlbp = 0x08,
rfe = 0x10,
};
union Instruction
{
u32 bits;
BitField<u32, InstructionOp, 26, 6> op; // function/instruction
union
{
BitField<u32, Reg, 21, 5> rs;
BitField<u32, Reg, 16, 5> rt;
BitField<u32, u16, 0, 16> imm;
u32 imm_sext32() const { return SignExtend32(imm.GetValue()); }
u32 imm_zext32() const { return ZeroExtend32(imm.GetValue()); }
} i;
union
{
BitField<u32, u32, 0, 26> target;
} j;
union
{
BitField<u32, Reg, 21, 5> rs;
BitField<u32, Reg, 16, 5> rt;
BitField<u32, Reg, 11, 5> rd;
BitField<u32, u8, 6, 5> shamt;
BitField<u32, InstructionFunct, 0, 6> funct;
} r;
union
{
u32 bits;
BitField<u32, u8, 26, 2> cop_n;
BitField<u32, u16, 0, 16> imm16;
BitField<u32, u32, 0, 25> imm25;
bool IsCommonInstruction() const { return (bits & (UINT32_C(1) << 25)) == 0; }
CopCommonInstruction CommonOp() const { return static_cast<CopCommonInstruction>((bits >> 21) & UINT32_C(0b1111)); }
Cop0Instruction Cop0Op() const { return static_cast<Cop0Instruction>(bits & UINT32_C(0x3F)); }
} cop;
bool IsCop2Instruction() const
{
return (op == InstructionOp::cop2 || op == InstructionOp::lwc2 || op == InstructionOp::swc2);
}
};
struct Registers
{
union
{
u32 r[32];
struct
{
u32 zero; // r0
u32 at; // r1
u32 v0; // r2
u32 v1; // r3
u32 a0; // r4
u32 a1; // r5
u32 a2; // r6
u32 a3; // r7
u32 t0; // r8
u32 t1; // r9
u32 t2; // r10
u32 t3; // r11
u32 t4; // r12
u32 t5; // r13
u32 t6; // r14
u32 t7; // r15
u32 s0; // r16
u32 s1; // r17
u32 s2; // r18
u32 s3; // r19
u32 s4; // r20
u32 s5; // r21
u32 s6; // r22
u32 s7; // r23
u32 t8; // r24
u32 t9; // r25
u32 k0; // r26
u32 k1; // r27
u32 gp; // r28
u32 sp; // r29
u32 fp; // r30
u32 ra; // r31
};
};
u32 hi;
u32 lo;
u32 pc; // at execution time: the address of the next instruction to execute (already fetched)
u32 npc; // at execution time: the address of the next instruction to fetch
};
enum class Cop0Reg : u8
{
BPC = 3,
BDA = 5,
JUMPDEST = 6,
DCIC = 7,
BadVaddr = 8,
BDAM = 9,
BPCM = 11,
SR = 12,
CAUSE = 13,
EPC = 14,
PRID = 15
};
enum class Exception : u8
{
INT = 0x00, // interrupt
MOD = 0x01, // tlb modification
TLBL = 0x02, // tlb load
TLBS = 0x03, // tlb store
AdEL = 0x04, // address error, data load/instruction fetch
AdES = 0x05, // address error, data store
IBE = 0x06, // bus error on instruction fetch
DBE = 0x07, // bus error on data load/store
Syscall = 0x08, // system call instruction
BP = 0x09, // break instruction
RI = 0x0A, // reserved instruction
CpU = 0x0B, // coprocessor unusable
Ov = 0x0C, // arithmetic overflow
};
struct Cop0Registers
{
u32 BPC; // breakpoint on execute
u32 BDA; // breakpoint on data access
u32 TAR; // randomly memorized jump address
u32 BadVaddr; // bad virtual address value
u32 BDAM; // data breakpoint mask
u32 BPCM; // execute breakpoint mask
u32 EPC; // return address from trap
u32 PRID; // processor ID
union SR
{
u32 bits;
BitField<u32, bool, 0, 1> IEc; // current interrupt enable
BitField<u32, bool, 1, 1> KUc; // current kernel/user mode, user = 1
BitField<u32, bool, 2, 1> IEp; // previous interrupt enable
BitField<u32, bool, 3, 1> KUp; // previous kernel/user mode, user = 1
BitField<u32, bool, 4, 1> IEo; // old interrupt enable
BitField<u32, bool, 5, 1> KUo; // old kernel/user mode, user = 1
BitField<u32, u8, 8, 8> Im; // interrupt mask, set to 1 = allowed to trigger
BitField<u32, bool, 16, 1> Isc; // isolate cache, no writes to memory occur
BitField<u32, bool, 17, 1> Swc; // swap data and instruction caches
BitField<u32, bool, 18, 1> PZ; // zero cache parity bits
BitField<u32, bool, 19, 1> CM; // last isolated load contains data from memory (tag matches?)
BitField<u32, bool, 20, 1> PE; // cache parity error
BitField<u32, bool, 21, 1> TS; // tlb shutdown - matched two entries
BitField<u32, bool, 22, 1> BEV; // boot exception vectors, 0 = KSEG0, 1 = KSEG1
BitField<u32, bool, 25, 1> RE; // reverse endianness in user mode
BitField<u32, bool, 28, 1> CU0; // coprocessor 0 enable in user mode
BitField<u32, bool, 29, 1> CU1; // coprocessor 1 enable in user mode
BitField<u32, bool, 30, 1> CU2; // coprocessor 2 enable in user mode
BitField<u32, bool, 31, 1> CU3; // coprocessor 3 enable in user mode
BitField<u32, u8, 0, 6> mode_bits;
BitField<u32, u8, 28, 2> coprocessor_enable_mask;
static constexpr u32 WRITE_MASK = 0b1111'0010'0111'1111'1111'1111'0011'1111;
} sr;
union CAUSE
{
u32 bits;
BitField<u32, Exception, 2, 5> Excode; // which exception occurred
BitField<u32, u8, 8, 8> Ip; // interrupt pending
BitField<u32, u8, 28, 2> CE; // coprocessor number if caused by a coprocessor
BitField<u32, bool, 30, 1> BT; // exception occurred in branch delay slot, and the branch was taken
BitField<u32, bool, 31, 1> BD; // exception occurred in branch delay slot, but pushed IP is for branch
static constexpr u32 WRITE_MASK = 0b0000'0000'0000'0000'0000'0011'0000'0000;
} cause;
union DCIC
{
u32 bits;
BitField<u32, bool, 0, 1> status_any_break;
BitField<u32, bool, 1, 1> status_bpc_code_break;
BitField<u32, bool, 2, 1> status_bda_data_break;
BitField<u32, bool, 3, 1> status_bda_data_read_break;
BitField<u32, bool, 4, 1> status_bda_data_write_break;
BitField<u32, bool, 5, 1> status_any_jump_break;
BitField<u32, u8, 12, 2> jump_redirection;
BitField<u32, bool, 23, 1> super_master_enable_1;
BitField<u32, bool, 24, 1> execution_breakpoint_enable;
BitField<u32, bool, 25, 1> data_access_breakpoint;
BitField<u32, bool, 26, 1> break_on_data_read;
BitField<u32, bool, 27, 1> break_on_data_write;
BitField<u32, bool, 28, 1> break_on_any_jump;
BitField<u32, bool, 29, 1> master_enable_any_jump;
BitField<u32, bool, 30, 1> master_enable_break;
BitField<u32, bool, 31, 1> super_master_enable_2;
static constexpr u32 WRITE_MASK = 0b1111'1111'1000'0000'1111'0000'0011'1111;
} dcic;
};
} // namespace CPU

View File

@ -0,0 +1,83 @@
#include "digital_controller.h"
#include "YBaseLib/Log.h"
Log_SetChannel(DigitalController);
DigitalController::DigitalController() = default;
DigitalController::~DigitalController() = default;
void DigitalController::SetButtonState(Button button, bool pressed)
{
if (pressed)
m_button_state &= ~(u16(1) << static_cast<u8>(button));
else
m_button_state |= u16(1) << static_cast<u8>(button);
}
void DigitalController::ResetTransferState()
{
m_transfer_fifo.Clear();
}
bool DigitalController::Transfer(const u8 data_in, u8* data_out)
{
bool ack;
switch (data_in)
{
case 0x01: // tests if the controller is present
{
Log_DebugPrintf("Access");
// response is hi-z
*data_out = 0xFF;
ack = true;
}
break;
case 0x42: // query state
{
Log_DebugPrintf("Query state");
QueryState();
[[fallthrough]];
}
default: // sending response
{
if (m_transfer_fifo.IsEmpty())
{
Log_WarningPrint("FIFO empty on read");
*data_out = 0xFF;
ack = false;
}
else
{
*data_out = m_transfer_fifo.Pop();
ack = !m_transfer_fifo.IsEmpty();
}
}
break;
}
Log_DebugPrintf("Transfer, data_in=0x%02X, data_out=0x%02X, ack=%s", data_in, *data_out, ack ? "true" : "false");
return ack;
}
void DigitalController::QueryState()
{
constexpr u16 ID = 0x5A41;
m_transfer_fifo.Clear();
m_transfer_fifo.Push(Truncate8(ID));
m_transfer_fifo.Push(Truncate8(ID >> 8));
m_transfer_fifo.Push(Truncate8(m_button_state)); // Digital switches low
m_transfer_fifo.Push(Truncate8(m_button_state >> 8)); // Digital switches high
}
std::shared_ptr<DigitalController> DigitalController::Create()
{
return std::make_shared<DigitalController>();
}

View File

@ -0,0 +1,46 @@
#pragma once
#include "common/fifo_queue.h"
#include "pad_device.h"
#include <memory>
class DigitalController final : public PadDevice
{
public:
enum class Button : u8
{
Select = 0,
L3 = 1,
R3 = 2,
Start = 3,
Up = 4,
Right = 5,
Down = 6,
Left = 7,
L2 = 8,
R2 = 9,
L1 = 10,
R1 = 11,
Triangle = 12,
Circle = 13,
Cross = 14,
Square = 15
};
DigitalController();
~DigitalController() override;
static std::shared_ptr<DigitalController> Create();
void SetButtonState(Button button, bool pressed);
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
private:
void QueryState();
// buttons are active low
u16 m_button_state = UINT16_C(0xFFFF);
InlineFIFOQueue<u8, 8> m_transfer_fifo;
};

432
src/core/dma.cpp Normal file
View File

@ -0,0 +1,432 @@
#include "dma.h"
#include "YBaseLib/Log.h"
#include "bus.h"
#include "cdrom.h"
#include "common/state_wrapper.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "spu.h"
#include "system.h"
Log_SetChannel(DMA);
DMA::DMA() = default;
DMA::~DMA() = default;
bool DMA::Initialize(System* system, Bus* bus, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom,
SPU* spu, MDEC* mdec)
{
m_system = system;
m_bus = bus;
m_interrupt_controller = interrupt_controller;
m_gpu = gpu;
m_cdrom = cdrom;
m_spu = spu;
m_mdec = mdec;
return true;
}
void DMA::Reset()
{
m_transfer_ticks = 0;
m_transfer_pending = false;
m_state = {};
m_DPCR.bits = 0x07654321;
m_DICR.bits = 0;
}
bool DMA::DoState(StateWrapper& sw)
{
for (u32 i = 0; i < NUM_CHANNELS; i++)
{
ChannelState& cs = m_state[i];
sw.Do(&cs.base_address);
sw.Do(&cs.block_control.bits);
sw.Do(&cs.channel_control.bits);
sw.Do(&cs.request);
}
sw.Do(&m_DPCR.bits);
sw.Do(&m_DICR.bits);
return !sw.HasError();
}
u32 DMA::ReadRegister(u32 offset)
{
const u32 channel_index = offset >> 4;
if (channel_index < 7)
{
switch (offset & UINT32_C(0x0F))
{
case 0x00:
return m_state[channel_index].base_address;
case 0x04:
return m_state[channel_index].block_control.bits;
case 0x08:
return m_state[channel_index].channel_control.bits;
default:
break;
}
}
else
{
if (offset == 0x70)
return m_DPCR.bits;
else if (offset == 0x74)
return m_DICR.bits;
}
Log_ErrorPrintf("Unhandled register read: %02X", offset);
return UINT32_C(0xFFFFFFFF);
}
void DMA::WriteRegister(u32 offset, u32 value)
{
const u32 channel_index = offset >> 4;
if (channel_index < 7)
{
ChannelState& state = m_state[channel_index];
switch (offset & UINT32_C(0x0F))
{
case 0x00:
{
state.base_address = value & ADDRESS_MASK;
Log_TracePrintf("DMA channel %u base address <- 0x%08X", channel_index, state.base_address);
return;
}
case 0x04:
{
Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value);
state.block_control.bits = value;
return;
}
case 0x08:
{
state.channel_control.bits = (state.channel_control.bits & ~ChannelState::ChannelControl::WRITE_MASK) |
(value & ChannelState::ChannelControl::WRITE_MASK);
Log_TracePrintf("DMA channel %u channel control <- 0x%08X", channel_index, state.channel_control.bits);
if (CanRunChannel(static_cast<Channel>(channel_index)))
UpdateTransferPending();
return;
}
default:
break;
}
}
else
{
switch (offset)
{
case 0x70:
{
Log_TracePrintf("DPCR <- 0x%08X", value);
m_DPCR.bits = value;
return;
}
case 0x74:
{
Log_TracePrintf("DCIR <- 0x%08X", value);
m_DICR.bits = (m_DICR.bits & ~DICR_WRITE_MASK) | (value & DICR_WRITE_MASK);
m_DICR.bits = (m_DICR.bits & ~DICR_RESET_MASK) & (value ^ DICR_RESET_MASK);
m_DICR.UpdateMasterFlag();
return;
}
default:
break;
}
}
Log_ErrorPrintf("Unhandled register write: %02X <- %08X", offset, value);
}
void DMA::SetRequest(Channel channel, bool request)
{
ChannelState& cs = m_state[static_cast<u32>(channel)];
if (cs.request == request)
return;
cs.request = request;
UpdateTransferPending();
}
void DMA::Execute(TickCount ticks)
{
if (!m_transfer_pending)
return;
m_transfer_ticks -= ticks;
if (m_transfer_ticks <= 0)
{
m_transfer_pending = false;
for (u32 i = 0; i < NUM_CHANNELS; i++)
{
const Channel channel = static_cast<Channel>(i);
if (CanRunChannel(channel))
{
RunDMA(channel);
m_transfer_pending |= CanRunChannel(channel);
}
}
if (m_transfer_pending)
{
m_transfer_ticks += TRANSFER_TICKS;
m_system->SetDowncount(m_transfer_ticks);
}
}
else
{
m_system->SetDowncount(m_transfer_ticks);
}
}
bool DMA::CanRunChannel(Channel channel) const
{
if (!m_DPCR.GetMasterEnable(channel))
return false;
const ChannelState& cs = m_state[static_cast<u32>(channel)];
if (cs.channel_control.start_trigger)
return true;
return (cs.channel_control.enable_busy && cs.request);
}
bool DMA::CanRunAnyChannels() const
{
for (u32 i = 0; i < NUM_CHANNELS; i++)
{
if (CanRunChannel(static_cast<Channel>(i)))
return true;
}
return false;
}
void DMA::RunDMA(Channel channel)
{
ChannelState& cs = m_state[static_cast<u32>(channel)];
const bool copy_to_device = cs.channel_control.copy_to_device;
// start/trigger bit is cleared on beginning of transfer
cs.channel_control.start_trigger = false;
PhysicalMemoryAddress current_address = cs.base_address & ~UINT32_C(3);
const PhysicalMemoryAddress increment = cs.channel_control.address_step_reverse ? static_cast<u32>(-4) : UINT32_C(4);
switch (cs.channel_control.sync_mode)
{
case SyncMode::Manual:
{
const u32 word_count = cs.block_control.manual.GetWordCount();
Log_DebugPrintf("DMA%u: Copying %u words %s 0x%08X", static_cast<u32>(channel), word_count,
copy_to_device ? "from" : "to", current_address);
if (copy_to_device)
{
u32 words_remaining = word_count;
do
{
words_remaining--;
u32 value = 0;
m_bus->DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(current_address, value);
DMAWrite(channel, value, current_address, words_remaining);
current_address = (current_address + increment) & ADDRESS_MASK;
} while (words_remaining > 0);
}
else
{
u32 words_remaining = word_count;
do
{
words_remaining--;
u32 value = DMARead(channel, current_address, words_remaining);
m_bus->DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(current_address, value);
current_address = (current_address + increment) & ADDRESS_MASK;
} while (words_remaining > 0);
}
}
break;
case SyncMode::LinkedList:
{
if (!copy_to_device)
{
Panic("Linked list not implemented for DMA reads");
}
else
{
Log_DebugPrintf("DMA%u: Copying linked list starting at 0x%08X to device", static_cast<u32>(channel),
current_address);
for (;;)
{
u32 header;
m_bus->DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(current_address, header);
const u32 word_count = header >> 24;
const u32 next_address = header & UINT32_C(0xFFFFFF);
Log_TracePrintf(" .. linked list entry at 0x%08X size=%u(%u words) next=0x%08X", current_address,
word_count * UINT32_C(4), word_count, next_address);
current_address += sizeof(header);
if (word_count > 0)
{
u32 words_remaining = word_count;
do
{
words_remaining--;
u32 memory_value = 0;
m_bus->DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(current_address, memory_value);
DMAWrite(channel, memory_value, current_address, words_remaining);
current_address = (current_address + UINT32_C(4)) & ADDRESS_MASK;
} while (words_remaining > 0);
}
if (next_address & UINT32_C(0x800000))
break;
current_address = next_address & ADDRESS_MASK;
}
}
}
break;
case SyncMode::Request:
{
const u32 block_size = cs.block_control.request.GetBlockSize();
const u32 block_count = cs.block_control.request.GetBlockCount();
Log_DebugPrintf("DMA%u: Copying %u blocks of size %u %s 0x%08X", static_cast<u32>(channel), block_count,
block_size, copy_to_device ? "from" : "to", current_address);
if (copy_to_device)
{
u32 words_remaining = block_size * block_count;
do
{
words_remaining--;
u32 value = 0;
m_bus->DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(current_address, value);
DMAWrite(channel, value, current_address, words_remaining);
current_address = (current_address + increment) & ADDRESS_MASK;
} while (words_remaining > 0);
}
else
{
u32 words_remaining = block_size * block_count;
do
{
words_remaining--;
u32 value = DMARead(channel, current_address, words_remaining);
m_bus->DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(current_address, value);
current_address = (current_address + increment) & ADDRESS_MASK;
} while (words_remaining > 0);
}
}
break;
default:
Panic("Unimplemented sync mode");
break;
}
// start/busy bit is cleared on end of transfer
cs.channel_control.enable_busy = false;
if (m_DICR.IsIRQEnabled(channel))
{
Log_TracePrintf("Set DMA interrupt for channel %u", static_cast<u32>(channel));
m_DICR.SetIRQFlag(channel);
m_DICR.UpdateMasterFlag();
if (m_DICR.master_flag)
{
Log_TracePrintf("Firing DMA interrupt");
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::DMA);
}
}
}
u32 DMA::DMARead(Channel channel, PhysicalMemoryAddress dst_address, u32 remaining_words)
{
switch (channel)
{
case Channel::OTC:
// clear ordering table
return (remaining_words == 0) ? UINT32_C(0xFFFFFF) : ((dst_address - UINT32_C(4)) & ADDRESS_MASK);
case Channel::GPU:
return m_gpu->DMARead();
case Channel::CDROM:
return m_cdrom->DMARead();
case Channel::SPU:
return m_spu->DMARead();
case Channel::MDECout:
return m_mdec->DMARead();
case Channel::MDECin:
case Channel::PIO:
default:
Panic("Unhandled DMA channel read");
return UINT32_C(0xFFFFFFFF);
}
}
void DMA::DMAWrite(Channel channel, u32 value, PhysicalMemoryAddress src_address, u32 remaining_words)
{
switch (channel)
{
case Channel::GPU:
m_gpu->DMAWrite(value);
return;
case Channel::SPU:
m_spu->DMAWrite(value);
break;
case Channel::MDECin:
m_mdec->DMAWrite(value);
break;
case Channel::MDECout:
case Channel::CDROM:
case Channel::PIO:
case Channel::OTC:
default:
Panic("Unhandled DMA channel write");
break;
}
}
void DMA::UpdateTransferPending()
{
if (CanRunAnyChannels())
{
if (m_transfer_pending)
return;
m_system->Synchronize();
m_transfer_pending = true;
m_transfer_ticks = TRANSFER_TICKS;
m_system->SetDowncount(m_transfer_ticks);
}
else
{
m_transfer_pending = false;
m_transfer_ticks = 0;
}
}

200
src/core/dma.h Normal file
View File

@ -0,0 +1,200 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
#include <array>
class StateWrapper;
class System;
class Bus;
class InterruptController;
class GPU;
class CDROM;
class SPU;
class MDEC;
class DMA
{
public:
enum : u32
{
NUM_CHANNELS = 7
};
enum class Channel : u32
{
MDECin = 0,
MDECout = 1,
GPU = 2,
CDROM = 3,
SPU = 4,
PIO = 5,
OTC = 6
};
DMA();
~DMA();
bool Initialize(System* system, Bus* bus, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom,
SPU* spu, MDEC* mdec);
void Reset();
bool DoState(StateWrapper& sw);
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
void SetRequest(Channel channel, bool request);
void Execute(TickCount ticks);
private:
static constexpr PhysicalMemoryAddress ADDRESS_MASK = UINT32_C(0x00FFFFFF);
static constexpr u32 TRANSFER_TICKS = 10;
enum class SyncMode : u32
{
Manual = 0,
Request = 1,
LinkedList = 2,
Reserved = 3
};
// is everything enabled for a channel to operate?
bool CanRunChannel(Channel channel) const;
bool CanRunAnyChannels() const;
void RunDMA(Channel channel);
// from device -> memory
u32 DMARead(Channel channel, PhysicalMemoryAddress dst_address, u32 remaining_words);
// from memory -> device
void DMAWrite(Channel channel, u32 value, PhysicalMemoryAddress src_address, u32 remaining_words);
void UpdateTransferPending();
System* m_system = nullptr;
Bus* m_bus = nullptr;
InterruptController* m_interrupt_controller = nullptr;
GPU* m_gpu = nullptr;
CDROM* m_cdrom = nullptr;
SPU* m_spu = nullptr;
MDEC* m_mdec = nullptr;
TickCount m_transfer_ticks = 0;
bool m_transfer_pending = false;
struct ChannelState
{
u32 base_address;
union BlockControl
{
u32 bits;
union
{
BitField<u32, u32, 0, 16> word_count;
u32 GetWordCount() const { return (word_count == 0) ? 0x10000 : word_count; }
} manual;
union
{
BitField<u32, u32, 0, 16> block_size;
BitField<u32, u32, 16, 16> block_count;
u32 GetBlockSize() const { return (block_size == 0) ? 0x10000 : block_size; }
u32 GetBlockCount() const { return (block_count == 0) ? 0x10000 : block_count; }
} request;
} block_control;
union ChannelControl
{
u32 bits;
BitField<u32, bool, 0, 1> copy_to_device;
BitField<u32, bool, 1, 1> address_step_reverse;
BitField<u32, bool, 8, 1> chopping_enable;
BitField<u32, SyncMode, 9, 2> sync_mode;
BitField<u32, u32, 16, 3> chopping_dma_window_size;
BitField<u32, u32, 20, 3> chopping_cpu_window_size;
BitField<u32, bool, 24, 1> enable_busy;
BitField<u32, bool, 28, 1> start_trigger;
static constexpr u32 WRITE_MASK = 0b01110001'01110111'00000111'00000011;
} channel_control;
bool request = false;
};
std::array<ChannelState, NUM_CHANNELS> m_state = {};
union DPCR
{
u32 bits;
BitField<u32, u8, 0, 3> MDECin_priority;
BitField<u32, bool, 3, 1> MDECin_master_enable;
BitField<u32, u8, 4, 3> MDECout_priority;
BitField<u32, bool, 7, 1> MDECout_master_enable;
BitField<u32, u8, 8, 3> GPU_priority;
BitField<u32, bool, 10, 1> GPU_master_enable;
BitField<u32, u8, 12, 3> CDROM_priority;
BitField<u32, bool, 15, 1> CDROM_master_enable;
BitField<u32, u8, 16, 3> SPU_priority;
BitField<u32, bool, 19, 1> SPU_master_enable;
BitField<u32, u8, 20, 3> PIO_priority;
BitField<u32, bool, 23, 1> PIO_master_enable;
BitField<u32, u8, 24, 3> OTC_priority;
BitField<u32, bool, 27, 1> OTC_master_enable;
BitField<u32, u8, 28, 3> priority_offset;
BitField<u32, bool, 31, 1> unused;
u8 GetPriority(Channel channel) const { return ((bits >> (static_cast<u8>(channel) * 4)) & u32(3)); }
bool GetMasterEnable(Channel channel) const
{
return ConvertToBoolUnchecked((bits >> (static_cast<u8>(channel) * 4 + 3)) & u32(1));
}
} m_DPCR;
static constexpr u32 DICR_WRITE_MASK = 0b00000000'11111111'10000000'00111111;
static constexpr u32 DICR_RESET_MASK = 0b01111111'00000000'00000000'00000000;
union DICR
{
u32 bits;
BitField<u32, bool, 15, 1> force_irq;
BitField<u32, bool, 16, 1> MDECin_irq_enable;
BitField<u32, bool, 17, 1> MDECout_irq_enable;
BitField<u32, bool, 18, 1> GPU_irq_enable;
BitField<u32, bool, 19, 1> CDROM_irq_enable;
BitField<u32, bool, 20, 1> SPU_irq_enable;
BitField<u32, bool, 21, 1> PIO_irq_enable;
BitField<u32, bool, 22, 1> OTC_irq_enable;
BitField<u32, bool, 23, 1> master_enable;
BitField<u32, bool, 24, 1> MDECin_irq_flag;
BitField<u32, bool, 25, 1> MDECout_irq_flag;
BitField<u32, bool, 26, 1> GPU_irq_flag;
BitField<u32, bool, 27, 1> CDROM_irq_flag;
BitField<u32, bool, 28, 1> SPU_irq_flag;
BitField<u32, bool, 29, 1> PIO_irq_flag;
BitField<u32, bool, 30, 1> OTC_irq_flag;
BitField<u32, bool, 31, 1> master_flag;
bool IsIRQEnabled(Channel channel) const
{
return ConvertToBoolUnchecked((bits >> (static_cast<u8>(channel) + 16)) & u32(1));
}
bool GetIRQFlag(Channel channel) const
{
return ConvertToBoolUnchecked((bits >> (static_cast<u8>(channel) + 24)) & u32(1));
}
void SetIRQFlag(Channel channel) { bits |= (u32(1) << (static_cast<u8>(channel) + 24)); }
void ClearIRQFlag(Channel channel) { bits &= ~(u32(1) << (static_cast<u8>(channel) + 24)); }
void UpdateMasterFlag()
{
master_flag = master_enable && ((((bits >> 16) & u32(0b1111111)) & ((bits >> 24) & u32(0b1111111))) != 0);
}
} m_DICR = {};
};

840
src/core/gpu.cpp Normal file
View File

@ -0,0 +1,840 @@
#include "gpu.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "dma.h"
#include "interrupt_controller.h"
#include "stb_image_write.h"
#include "system.h"
#include "timers.h"
Log_SetChannel(GPU);
bool GPU::DUMP_CPU_TO_VRAM_COPIES = false;
bool GPU::DUMP_VRAM_TO_CPU_COPIES = false;
static u32 s_cpu_to_vram_dump_id = 1;
static u32 s_vram_to_cpu_dump_id = 1;
GPU::GPU() = default;
GPU::~GPU() = default;
bool GPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers)
{
m_system = system;
m_dma = dma;
m_interrupt_controller = interrupt_controller;
m_timers = timers;
return true;
}
void GPU::Reset()
{
SoftReset();
}
void GPU::SoftReset()
{
m_GPUSTAT.bits = 0x14802000;
m_crtc_state = {};
m_crtc_state.regs.display_address_start = 0;
m_crtc_state.regs.horizontal_display_range = 0xC60260;
m_crtc_state.regs.vertical_display_range = 0x3FC10;
m_render_state = {};
m_render_state.texture_page_changed = true;
m_render_state.texture_color_mode_changed = true;
m_render_state.transparency_mode_changed = true;
UpdateGPUSTAT();
UpdateCRTCConfig();
}
bool GPU::DoState(StateWrapper& sw)
{
if (sw.IsReading())
FlushRender();
sw.Do(&m_GPUSTAT.bits);
sw.Do(&m_render_state.texture_page_x);
sw.Do(&m_render_state.texture_page_y);
sw.Do(&m_render_state.texture_palette_x);
sw.Do(&m_render_state.texture_palette_y);
sw.Do(&m_render_state.texture_color_mode);
sw.Do(&m_render_state.transparency_mode);
sw.Do(&m_render_state.texture_window_mask_x);
sw.Do(&m_render_state.texture_window_mask_y);
sw.Do(&m_render_state.texture_window_offset_x);
sw.Do(&m_render_state.texture_window_offset_y);
sw.Do(&m_render_state.texture_x_flip);
sw.Do(&m_render_state.texture_y_flip);
sw.Do(&m_render_state.texpage_attribute);
sw.Do(&m_render_state.texlut_attribute);
sw.Do(&m_render_state.texture_page_changed);
sw.Do(&m_render_state.texture_color_mode_changed);
sw.Do(&m_render_state.transparency_mode_changed);
sw.Do(&m_drawing_area.left);
sw.Do(&m_drawing_area.top);
sw.Do(&m_drawing_area.right);
sw.Do(&m_drawing_area.bottom);
sw.Do(&m_drawing_offset.x);
sw.Do(&m_drawing_offset.y);
sw.Do(&m_drawing_offset.x);
sw.Do(&m_crtc_state.regs.display_address_start);
sw.Do(&m_crtc_state.regs.horizontal_display_range);
sw.Do(&m_crtc_state.regs.vertical_display_range);
sw.Do(&m_crtc_state.horizontal_resolution);
sw.Do(&m_crtc_state.vertical_resolution);
sw.Do(&m_crtc_state.dot_clock_divider);
sw.Do(&m_crtc_state.visible_horizontal_resolution);
sw.Do(&m_crtc_state.visible_vertical_resolution);
sw.Do(&m_crtc_state.ticks_per_scanline);
sw.Do(&m_crtc_state.visible_ticks_per_scanline);
sw.Do(&m_crtc_state.total_scanlines_per_frame);
sw.Do(&m_crtc_state.fractional_ticks);
sw.Do(&m_crtc_state.current_tick_in_scanline);
sw.Do(&m_crtc_state.current_scanline);
sw.Do(&m_crtc_state.in_hblank);
sw.Do(&m_crtc_state.in_vblank);
if (sw.IsReading())
UpdateSliceTicks();
sw.Do(&m_GP0_command);
sw.Do(&m_GPUREAD_buffer);
if (sw.IsReading())
{
m_render_state.texture_page_changed = true;
m_render_state.texture_color_mode_changed = true;
m_render_state.transparency_mode_changed = true;
UpdateGPUSTAT();
}
if (!sw.DoMarker("GPU-VRAM"))
return false;
if (sw.IsReading())
{
std::vector<u16> vram;
sw.Do(&vram);
UpdateVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT, vram.data());
}
else
{
std::vector<u16> vram(VRAM_WIDTH * VRAM_HEIGHT);
ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT, vram.data());
sw.Do(&vram);
}
return !sw.HasError();
}
void GPU::RenderUI() {}
void GPU::UpdateGPUSTAT()
{
m_GPUSTAT.ready_to_send_vram = !m_GPUREAD_buffer.empty();
m_GPUSTAT.ready_to_recieve_cmd = m_GPUREAD_buffer.empty();
m_GPUSTAT.ready_to_recieve_dma = m_GPUREAD_buffer.empty();
bool dma_request;
switch (m_GPUSTAT.dma_direction)
{
case DMADirection::Off:
dma_request = false;
break;
case DMADirection::FIFO:
dma_request = true; // FIFO not full/full
break;
case DMADirection::CPUtoGP0:
dma_request = m_GPUSTAT.ready_to_recieve_dma;
break;
case DMADirection::GPUREADtoCPU:
dma_request = m_GPUSTAT.ready_to_send_vram;
break;
default:
dma_request = false;
break;
}
m_GPUSTAT.dma_data_request = dma_request;
m_dma->SetRequest(DMA::Channel::GPU, dma_request);
}
u32 GPU::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00:
return ReadGPUREAD();
case 0x04:
{
// Bit 31 of GPUSTAT is always clear during vblank.
u32 bits = m_GPUSTAT.bits;
// bits &= (BoolToUInt32(!m_crtc_state.in_vblank) << 31);
return bits;
}
default:
Log_ErrorPrintf("Unhandled register read: %02X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void GPU::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0x00:
WriteGP0(value);
return;
case 0x04:
WriteGP1(value);
return;
default:
Log_ErrorPrintf("Unhandled register write: %02X <- %08X", offset, value);
return;
}
}
u32 GPU::DMARead()
{
if (m_GPUSTAT.dma_direction != DMADirection::GPUREADtoCPU)
{
Log_ErrorPrintf("Invalid DMA direction from GPU DMA read");
return UINT32_C(0xFFFFFFFF);
}
return ReadGPUREAD();
}
void GPU::DMAWrite(u32 value)
{
switch (m_GPUSTAT.dma_direction)
{
case DMADirection::CPUtoGP0:
WriteGP0(value);
break;
default:
Log_ErrorPrintf("Unhandled GPU DMA write mode %u for value %08X",
static_cast<u32>(m_GPUSTAT.dma_direction.GetValue()), value);
break;
}
}
void GPU::UpdateCRTCConfig()
{
static constexpr std::array<TickCount, 8> dot_clock_dividers = {{8, 4, 10, 5, 7, 7, 7, 7}};
static constexpr std::array<u32, 8> horizontal_resolutions = {{256, 320, 512, 630, 368, 368, 368, 368}};
static constexpr std::array<u32, 2> vertical_resolutions = {{240, 480}};
CRTCState& cs = m_crtc_state;
const u8 horizontal_resolution_index = m_GPUSTAT.horizontal_resolution_1 | (m_GPUSTAT.horizontal_resolution_2 << 2);
cs.dot_clock_divider = dot_clock_dividers[horizontal_resolution_index];
cs.horizontal_resolution = horizontal_resolutions[horizontal_resolution_index];
cs.vertical_resolution = vertical_resolutions[m_GPUSTAT.vertical_resolution];
// check for a change in resolution
const u32 old_horizontal_resolution = cs.visible_horizontal_resolution;
const u32 old_vertical_resolution = cs.visible_vertical_resolution;
cs.visible_horizontal_resolution = std::max((cs.regs.X2 - cs.regs.X1) / cs.dot_clock_divider, u32(1));
cs.visible_vertical_resolution = cs.regs.Y2 - cs.regs.Y1 + 1;
if (cs.visible_horizontal_resolution != old_horizontal_resolution ||
cs.visible_vertical_resolution != old_vertical_resolution)
{
Log_InfoPrintf("Visible resolution is now %ux%u", cs.visible_horizontal_resolution, cs.visible_vertical_resolution);
}
if (m_GPUSTAT.pal_mode)
{
cs.total_scanlines_per_frame = 314;
cs.ticks_per_scanline = 3406;
}
else
{
cs.total_scanlines_per_frame = 263;
cs.ticks_per_scanline = 3413;
}
UpdateSliceTicks();
}
void GPU::UpdateSliceTicks()
{
// the next event is at the end of the next scanline
#if 1
const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline;
#else
// or at vblank. this will depend on the timer config..
const TickCount ticks_until_next_event =
((m_crtc_state.total_scanlines_per_frame - m_crtc_state.current_scanline) * m_crtc_state.ticks_per_scanline) -
m_crtc_state.current_tick_in_scanline;
#endif
// convert to master clock, rounding up as we want to overshoot not undershoot
const TickCount system_ticks = (ticks_until_next_event * 7 + 10) / 11;
m_system->SetDowncount(system_ticks);
}
void GPU::Execute(TickCount ticks)
{
// convert cpu/master clock to GPU ticks, accounting for partial cycles because of the non-integer divider
{
const TickCount temp = (ticks * 11) + m_crtc_state.fractional_ticks;
m_crtc_state.current_tick_in_scanline += temp / 7;
m_crtc_state.fractional_ticks = temp % 7;
}
while (m_crtc_state.current_tick_in_scanline >= m_crtc_state.ticks_per_scanline)
{
m_crtc_state.current_tick_in_scanline -= m_crtc_state.ticks_per_scanline;
m_crtc_state.current_scanline++;
if (m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX))
m_timers->AddTicks(HBLANK_TIMER_INDEX, 1);
// past the end of vblank?
if (m_crtc_state.current_scanline >= m_crtc_state.total_scanlines_per_frame)
{
// flush any pending draws and "scan out" the image
FlushRender();
UpdateDisplay();
// start the new frame
m_system->IncrementFrameNumber();
m_crtc_state.current_scanline = 0;
if (m_GPUSTAT.vertical_resolution)
m_GPUSTAT.drawing_even_line ^= true;
}
const bool old_vblank = m_crtc_state.in_vblank;
const bool new_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution;
if (new_vblank != old_vblank)
{
m_crtc_state.in_vblank = new_vblank;
if (!old_vblank)
{
Log_DebugPrintf("Now in v-blank");
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK);
}
m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank);
}
// alternating even line bit in 240-line mode
if (!m_crtc_state.vertical_resolution)
m_GPUSTAT.drawing_even_line = ConvertToBoolUnchecked(m_crtc_state.current_scanline & u32(1));
}
UpdateSliceTicks();
}
u32 GPU::ReadGPUREAD()
{
if (m_GPUREAD_buffer.empty())
{
Log_ErrorPrintf("GPUREAD read while buffer is empty");
return UINT32_C(0xFFFFFFFF);
}
const u32 value = m_GPUREAD_buffer.front();
m_GPUREAD_buffer.pop_front();
UpdateGPUSTAT();
return value;
}
void GPU::WriteGP0(u32 value)
{
m_GP0_command.push_back(value);
Assert(m_GP0_command.size() <= 1048576);
const u8 command = Truncate8(m_GP0_command[0] >> 24);
const u32 param = m_GP0_command[0] & UINT32_C(0x00FFFFFF);
UpdateGPUSTAT();
if (command >= 0x20 && command <= 0x7F)
{
// Draw polygon
if (!HandleRenderCommand())
return;
}
else
{
switch (command)
{
case 0x00: // NOP
break;
case 0x01: // Clear cache
break;
case 0x02: // Fill Rectangle
{
if (!HandleFillRectangleCommand())
return;
}
break;
case 0xA0: // Copy Rectangle CPU->VRAM
{
if (!HandleCopyRectangleCPUToVRAMCommand())
return;
}
break;
case 0xC0: // Copy Rectangle VRAM->CPU
{
if (!HandleCopyRectangleVRAMToCPUCommand())
return;
}
break;
case 0x80: // Copy Rectangle VRAM->VRAM
{
if (!HandleCopyRectangleVRAMToVRAMCommand())
return;
}
break;
case 0xE1: // Set draw mode
{
// 0..10 bits match GPUSTAT
const u32 MASK = ((UINT32_C(1) << 11) - 1);
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~MASK) | param & MASK;
m_GPUSTAT.texture_disable = (param & (UINT32_C(1) << 11)) != 0;
m_render_state.texture_x_flip = (param & (UINT32_C(1) << 12)) != 0;
m_render_state.texture_y_flip = (param & (UINT32_C(1) << 13)) != 0;
Log_DebugPrintf("Set draw mode %08X", param);
}
break;
case 0xE2: // set texture window
{
m_render_state.texture_window_mask_x = param & UINT32_C(0x1F);
m_render_state.texture_window_mask_y = (param >> 5) & UINT32_C(0x1F);
m_render_state.texture_window_offset_x = (param >> 10) & UINT32_C(0x1F);
m_render_state.texture_window_offset_y = (param >> 15) & UINT32_C(0x1F);
Log_DebugPrintf("Set texture window %02X %02X %02X %02X", m_render_state.texture_window_mask_x,
m_render_state.texture_window_mask_y, m_render_state.texture_window_offset_x,
m_render_state.texture_window_offset_y);
}
break;
case 0xE3: // Set drawing area top left
{
m_drawing_area.left = param & UINT32_C(0x3FF);
m_drawing_area.top = (param >> 10) & UINT32_C(0x1FF);
Log_DebugPrintf("Set drawing area top-left: (%u, %u)", m_drawing_area.left, m_drawing_area.top);
}
break;
case 0xE4: // Set drawing area bottom right
{
m_drawing_area.right = param & UINT32_C(0x3FF);
m_drawing_area.bottom = (param >> 10) & UINT32_C(0x1FF);
Log_DebugPrintf("Set drawing area bottom-right: (%u, %u)", m_drawing_area.right, m_drawing_area.bottom);
}
break;
case 0xE5: // Set drawing offset
{
m_drawing_offset.x = S11ToS32(param & UINT32_C(0x7FF));
m_drawing_offset.y = S11ToS32((param >> 11) & UINT32_C(0x7FF));
Log_DebugPrintf("Set drawing offset (%d, %d)", m_drawing_offset.x, m_drawing_offset.y);
}
break;
case 0xE6: // Mask bit setting
{
m_GPUSTAT.draw_set_mask_bit = (param & UINT32_C(0x01)) != 0;
m_GPUSTAT.draw_to_masked_pixels = (param & UINT32_C(0x01)) != 0;
Log_DebugPrintf("Set mask bit %u %u", BoolToUInt32(m_GPUSTAT.draw_set_mask_bit),
BoolToUInt32(m_GPUSTAT.draw_to_masked_pixels));
}
break;
default:
{
Log_ErrorPrintf("Unimplemented GP0 command 0x%02X", command);
}
break;
}
}
m_GP0_command.clear();
UpdateGPUSTAT();
}
void GPU::WriteGP1(u32 value)
{
const u8 command = Truncate8(value >> 24);
const u32 param = value & UINT32_C(0x00FFFFFF);
switch (command)
{
case 0x01: // Clear FIFO
{
m_GP0_command.clear();
Log_DebugPrintf("GP1 clear FIFO");
UpdateGPUSTAT();
}
break;
case 0x04: // DMA Direction
{
m_GPUSTAT.dma_direction = static_cast<DMADirection>(param);
Log_DebugPrintf("DMA direction <- 0x%02X", static_cast<u32>(m_GPUSTAT.dma_direction.GetValue()));
UpdateGPUSTAT();
}
break;
case 0x05: // Set display start address
{
m_crtc_state.regs.display_address_start = param & CRTCState::Regs::DISPLAY_ADDRESS_START_MASK;
Log_DebugPrintf("Display address start <- 0x%08X", m_crtc_state.regs.display_address_start);
m_system->IncrementInternalFrameNumber();
}
break;
case 0x06: // Set horizontal display range
{
m_crtc_state.regs.horizontal_display_range = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK;
Log_DebugPrintf("Horizontal display range <- 0x%08X", m_crtc_state.regs.horizontal_display_range);
UpdateCRTCConfig();
}
break;
case 0x07: // Set display start address
{
m_crtc_state.regs.vertical_display_range = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK;
Log_DebugPrintf("Vertical display range <- 0x%08X", m_crtc_state.regs.vertical_display_range);
UpdateCRTCConfig();
}
break;
case 0x08: // Set display mode
{
union GP1_08h
{
u32 bits;
BitField<u32, u8, 0, 2> horizontal_resolution_1;
BitField<u32, u8, 2, 1> vertical_resolution;
BitField<u32, bool, 3, 1> pal_mode;
BitField<u32, bool, 4, 1> display_area_color_depth;
BitField<u32, bool, 5, 1> vertical_interlace;
BitField<u32, bool, 6, 1> horizontal_resolution_2;
BitField<u32, bool, 7, 1> reverse_flag;
};
const GP1_08h dm{param};
m_GPUSTAT.horizontal_resolution_1 = dm.horizontal_resolution_1;
m_GPUSTAT.vertical_resolution = dm.vertical_resolution;
m_GPUSTAT.pal_mode = dm.pal_mode;
m_GPUSTAT.display_area_color_depth_24 = dm.display_area_color_depth;
m_GPUSTAT.vertical_interlace = dm.vertical_interlace;
m_GPUSTAT.horizontal_resolution_2 = dm.horizontal_resolution_2;
m_GPUSTAT.reverse_flag = dm.reverse_flag;
Log_DebugPrintf("Set display mode <- 0x%08X", dm.bits);
UpdateCRTCConfig();
}
break;
default:
Log_ErrorPrintf("Unimplemented GP1 command 0x%02X", command);
break;
}
}
bool GPU::HandleRenderCommand()
{
const u8 command = Truncate8(m_GP0_command[0] >> 24);
const RenderCommand rc{m_GP0_command[0]};
u8 words_per_vertex;
u32 num_vertices;
u32 total_words;
switch (rc.primitive)
{
case Primitive::Polygon:
{
// shaded vertices use the colour from the first word for the first vertex
words_per_vertex = 1 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.shading_enable);
num_vertices = rc.quad_polygon ? 4 : 3;
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
}
break;
case Primitive::Line:
{
words_per_vertex = 1 + BoolToUInt8(rc.shading_enable);
if (rc.polyline)
{
// polyline goes until we hit the termination code
num_vertices = 0;
bool found_terminator = false;
for (u32 pos = BoolToUInt32(!rc.shading_enable); pos < static_cast<u32>(m_GP0_command.size());
pos += words_per_vertex)
{
if (m_GP0_command[pos] == 0x55555555)
{
found_terminator = true;
break;
}
num_vertices++;
}
if (!found_terminator)
return false;
}
else
{
num_vertices = 2;
}
total_words = words_per_vertex * num_vertices + BoolToUInt8(!rc.shading_enable);
}
break;
case Primitive::Rectangle:
{
words_per_vertex =
2 + BoolToUInt8(rc.texture_enable) + BoolToUInt8(rc.rectangle_size == DrawRectangleSize::Variable);
num_vertices = 1;
total_words = words_per_vertex;
}
break;
default:
UnreachableCode();
return true;
}
if (m_GP0_command.size() < total_words)
return false;
static constexpr std::array<const char*, 4> primitive_names = {{"", "polygon", "line", "rectangle"}};
Log_DebugPrintf("Render %s %s %s %s %s (%u verts, %u words per vert)", rc.quad_polygon ? "four-point" : "three-point",
rc.transparency_enable ? "semi-transparent" : "opaque",
rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome",
primitive_names[static_cast<u8>(rc.primitive.GetValue())], ZeroExtend32(num_vertices),
ZeroExtend32(words_per_vertex));
DispatchRenderCommand(rc, num_vertices);
return true;
}
bool GPU::HandleFillRectangleCommand()
{
if (m_GP0_command.size() < 3)
return false;
const u32 color = m_GP0_command[0] & UINT32_C(0x00FFFFFF);
const u32 dst_x = m_GP0_command[1] & UINT32_C(0xFFFF);
const u32 dst_y = m_GP0_command[1] >> 16;
const u32 width = m_GP0_command[2] & UINT32_C(0xFFFF);
const u32 height = m_GP0_command[2] >> 16;
Log_DebugPrintf("Fill VRAM rectangle offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, width, height);
// Drop higher precision when filling. Bit15 is set to 0.
// TODO: Force 8-bit color option.
const u16 color16 = RGBA8888ToRGBA5551(color);
FillVRAM(dst_x, dst_y, width, height, color16);
return true;
}
bool GPU::HandleCopyRectangleCPUToVRAMCommand()
{
if (m_GP0_command.size() < 3)
return false;
const u32 copy_width = m_GP0_command[2] & UINT32_C(0xFFFF);
const u32 copy_height = m_GP0_command[2] >> 16;
const u32 num_pixels = copy_width * copy_height;
const u32 num_words = 3 + ((num_pixels + 1) / 2);
if (m_GP0_command.size() < num_words)
return false;
const u32 dst_x = m_GP0_command[1] & UINT32_C(0xFFFF);
const u32 dst_y = m_GP0_command[1] >> 16;
Log_DebugPrintf("Copy rectangle from CPU to VRAM offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, copy_width,
copy_height);
if ((dst_x + copy_width) > VRAM_WIDTH || (dst_y + copy_height) > VRAM_HEIGHT)
{
Panic("Out of bounds VRAM copy");
return true;
}
if (DUMP_CPU_TO_VRAM_COPIES)
{
DumpVRAMToFile(SmallString::FromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++), copy_width, copy_height,
sizeof(u16) * copy_width, &m_GP0_command[3], true);
}
FlushRender();
UpdateVRAM(dst_x, dst_y, copy_width, copy_height, &m_GP0_command[3]);
return true;
}
bool GPU::HandleCopyRectangleVRAMToCPUCommand()
{
if (m_GP0_command.size() < 3)
return false;
const u32 width = m_GP0_command[2] & UINT32_C(0xFFFF);
const u32 height = m_GP0_command[2] >> 16;
const u32 num_pixels = width * height;
const u32 num_words = ((num_pixels + 1) / 2);
const u32 src_x = m_GP0_command[1] & UINT32_C(0xFFFF);
const u32 src_y = m_GP0_command[1] >> 16;
Log_DebugPrintf("Copy rectangle from VRAM to CPU offset=(%u,%u), size=(%u,%u)", src_x, src_y, width, height);
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT)
{
Panic("Out of bounds VRAM copy");
return true;
}
// all rendering should be done first...
FlushRender();
// TODO: A better way of doing this..
std::vector<u32> temp(num_words);
ReadVRAM(src_x, src_y, width, height, temp.data());
for (const u32 bits : temp)
m_GPUREAD_buffer.push_back(bits);
if (DUMP_VRAM_TO_CPU_COPIES)
{
DumpVRAMToFile(SmallString::FromFormat("vram_to_cpu_copy_%u.png", s_cpu_to_vram_dump_id++), width, height,
sizeof(u16) * width, temp.data(), true);
}
// Is this correct?
return true;
}
bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
{
if (m_GP0_command.size() < 4)
return false;
const u32 src_x = m_GP0_command[1] & UINT32_C(0xFFFF);
const u32 src_y = m_GP0_command[1] >> 16;
const u32 dst_x = m_GP0_command[2] & UINT32_C(0xFFFF);
const u32 dst_y = m_GP0_command[2] >> 16;
const u32 width = m_GP0_command[3] & UINT32_C(0xFFFF);
const u32 height = m_GP0_command[3] >> 16;
Log_DebugPrintf("Copy rectangle from VRAM to VRAM src=(%u,%u), dst=(%u,%u), size=(%u,%u)", src_x, src_y, dst_x, dst_y,
width, height);
if ((src_x + width) > VRAM_WIDTH || (src_y + height) > VRAM_HEIGHT || (dst_x + width) > VRAM_WIDTH ||
(dst_y + height) > VRAM_HEIGHT)
{
Panic("Out of bounds VRAM copy");
return true;
}
FlushRender();
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
return true;
}
void GPU::UpdateDisplay() {}
void GPU::ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer) {}
void GPU::FillVRAM(u32 x, u32 y, u32 width, u32 height, u16 color) {}
void GPU::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data) {}
void GPU::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) {}
void GPU::DispatchRenderCommand(RenderCommand rc, u32 num_vertices) {}
void GPU::FlushRender() {}
void GPU::RenderState::SetFromPolygonTexcoord(u32 texcoord0, u32 texcoord1)
{
SetFromPaletteAttribute(Truncate16(texcoord0 >> 16));
SetFromPageAttribute(Truncate16(texcoord1 >> 16));
}
void GPU::RenderState::SetFromRectangleTexcoord(u32 texcoord)
{
SetFromPaletteAttribute(Truncate16(texcoord >> 16));
}
void GPU::RenderState::SetFromPageAttribute(u16 value)
{
const u16 old_page_attribute = texpage_attribute;
value &= PAGE_ATTRIBUTE_MASK;
if (texpage_attribute == value)
return;
texpage_attribute = value;
texture_page_x = static_cast<s32>(ZeroExtend32(value & UINT16_C(0x0F)) * UINT32_C(64));
texture_page_y = static_cast<s32>(ZeroExtend32((value >> 4) & UINT16_C(1)) * UINT32_C(256));
texture_page_changed |=
(old_page_attribute & PAGE_ATTRIBUTE_TEXTURE_PAGE_MASK) != (value & PAGE_ATTRIBUTE_TEXTURE_PAGE_MASK);
const TextureColorMode old_color_mode = texture_color_mode;
texture_color_mode = (static_cast<TextureColorMode>((value >> 7) & UINT16_C(0x03)));
if (texture_color_mode == TextureColorMode::Reserved_Direct16Bit)
texture_color_mode = TextureColorMode::Direct16Bit;
texture_color_mode_changed |= old_color_mode != texture_color_mode;
const TransparencyMode old_transparency_mode = transparency_mode;
transparency_mode = (static_cast<TransparencyMode>((value >> 5) & UINT16_C(0x03)));
transparency_mode_changed = old_transparency_mode != transparency_mode;
}
void GPU::RenderState::SetFromPaletteAttribute(u16 value)
{
value &= PALETTE_ATTRIBUTE_MASK;
if (texlut_attribute == value)
return;
texture_palette_x = static_cast<s32>(ZeroExtend32(value & UINT16_C(0x3F)) * UINT32_C(16));
texture_palette_y = static_cast<s32>(ZeroExtend32((value >> 6) & UINT16_C(0x1FF)));
texlut_attribute = value;
texture_page_changed = true;
}
bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha)
{
std::vector<u32> rgba8_buf(width * height);
const char* ptr_in = static_cast<const char*>(buffer);
u32* ptr_out = rgba8_buf.data();
for (u32 row = 0; row < height; row++)
{
const char* row_ptr_in = ptr_in;
for (u32 col = 0; col < width; col++)
{
u16 src_col;
std::memcpy(&src_col, row_ptr_in, sizeof(u16));
row_ptr_in += sizeof(u16);
*(ptr_out++) = RGBA5551ToRGBA8888(remove_alpha ? (src_col | u16(0x8000)) : src_col);
}
ptr_in += stride;
}
return (stbi_write_png(filename, width, height, 4, rgba8_buf.data(), sizeof(u32) * width) != 0);
}

337
src/core/gpu.h Normal file
View File

@ -0,0 +1,337 @@
#pragma once
#include "common/bitfield.h"
#include "timers.h"
#include "types.h"
#include <array>
#include <deque>
#include <vector>
class StateWrapper;
class System;
class DMA;
class InterruptController;
class Timers;
class GPU
{
public:
GPU();
virtual ~GPU();
virtual bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers);
virtual void Reset();
virtual bool DoState(StateWrapper& sw);
virtual void RenderUI();
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
// DMA access
u32 DMARead();
void DMAWrite(u32 value);
// gpu_hw_opengl.cpp
static std::unique_ptr<GPU> CreateHardwareOpenGLRenderer();
void Execute(TickCount ticks);
protected:
static constexpr float DISPLAY_ASPECT_RATIO = 4.0f / 3.0f;
static constexpr u32 VRAM_WIDTH = 1024;
static constexpr u32 VRAM_HEIGHT = 512;
static constexpr u32 VRAM_SIZE = VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16);
static constexpr u32 TEXTURE_PAGE_WIDTH = 256;
static constexpr u32 TEXTURE_PAGE_HEIGHT = 256;
static constexpr u32 DOT_TIMER_INDEX = 0;
static constexpr u32 HBLANK_TIMER_INDEX = 1;
static constexpr s32 S11ToS32(u32 value)
{
if (value & (UINT16_C(1) << 10))
return static_cast<s32>(UINT32_C(0xFFFFF800) | value);
else
return value;
}
// Helper/format conversion functions.
static constexpr u32 RGBA5551ToRGBA8888(u16 color)
{
u8 r = Truncate8(color & 31);
u8 g = Truncate8((color >> 5) & 31);
u8 b = Truncate8((color >> 10) & 31);
u8 a = Truncate8((color >> 15) & 1);
// 00012345 -> 1234545
b = (b << 3) | (b & 0b111);
g = (g << 3) | (g & 0b111);
r = (r << 3) | (r & 0b111);
a = a ? 255 : 0;
return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24);
}
static constexpr u16 RGBA8888ToRGBA5551(u32 color)
{
const u16 r = Truncate16((color >> 3) & 0x1Fu);
const u16 g = Truncate16((color >> 11) & 0x1Fu);
const u16 b = Truncate16((color >> 19) & 0x1Fu);
const u16 a = Truncate16((color >> 31) & 0x01u);
return r | (g << 5) | (b << 10) | (a << 15);
}
static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer,
bool remove_alpha);
enum class DMADirection : u32
{
Off = 0,
FIFO = 1,
CPUtoGP0 = 2,
GPUREADtoCPU = 3
};
enum class Primitive : u8
{
Reserved = 0,
Polygon = 1,
Line = 2,
Rectangle = 3
};
enum class DrawRectangleSize : u8
{
Variable = 0,
R1x1 = 1,
R8x8 = 2,
R16x16 = 3
};
enum class TextureColorMode : u8
{
Palette4Bit = 0,
Palette8Bit = 1,
Direct16Bit = 2,
Reserved_Direct16Bit = 3
};
enum class TransparencyMode : u8
{
HalfBackgroundPlusHalfForeground = 0,
BackgroundPlusForeground = 1,
BackgroundMinusForeground = 2,
BackgroundPlusQuarterForeground = 3
};
union RenderCommand
{
u32 bits;
BitField<u32, u32, 0, 24> color_for_first_vertex;
BitField<u32, bool, 24, 1> texture_blend_disable; // not valid for lines
BitField<u32, bool, 25, 1> transparency_enable;
BitField<u32, bool, 26, 1> texture_enable;
BitField<u32, DrawRectangleSize, 27, 2> rectangle_size; // only for rectangles
BitField<u32, bool, 27, 1> quad_polygon; // only for polygons
BitField<u32, bool, 27, 1> polyline; // only for lines
BitField<u32, bool, 28, 1> shading_enable; // 0 - flat, 1 = gouroud
BitField<u32, Primitive, 29, 21> primitive;
// Helper functions.
bool IsTextureEnabled() const { return (primitive != Primitive::Line && texture_enable); }
bool IsTextureBlendingEnabled() const { return (IsTextureEnabled() && !texture_blend_disable); }
bool IsTransparencyEnabled() const { return transparency_enable; }
};
// TODO: Use BitField to do sign extending instead
union VertexPosition
{
u32 bits;
BitField<u32, u32, 0, 11> x_s11;
BitField<u32, u32, 16, 11> y_s11;
s32 x() const { return S11ToS32(x_s11); }
s32 y() const { return S11ToS32(y_s11); }
};
void SoftReset();
// Sets dots per scanline
void UpdateCRTCConfig();
// Update ticks for this execution slice
void UpdateSliceTicks();
// Updates dynamic bits in GPUSTAT (ready to send VRAM/ready to receive DMA)
void UpdateGPUSTAT();
u32 ReadGPUREAD();
void WriteGP0(u32 value);
void WriteGP1(u32 value);
// Rendering commands, returns false if not enough data is provided
bool HandleRenderCommand();
bool HandleFillRectangleCommand();
bool HandleCopyRectangleCPUToVRAMCommand();
bool HandleCopyRectangleVRAMToCPUCommand();
bool HandleCopyRectangleVRAMToVRAMCommand();
// Rendering in the backend
virtual void UpdateDisplay();
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer);
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u16 color);
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data);
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height);
virtual void DispatchRenderCommand(RenderCommand rc, u32 num_vertices);
virtual void FlushRender();
System* m_system = nullptr;
DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr;
Timers* m_timers = nullptr;
union GPUSTAT
{
u32 bits;
BitField<u32, u8, 0, 4> texture_page_x_base;
BitField<u32, u8, 4, 1> texture_page_y_base;
BitField<u32, TransparencyMode, 5, 2> semi_transparency_mode;
BitField<u32, TextureColorMode, 7, 2> texture_color_mode;
BitField<u32, bool, 9, 1> dither_enable;
BitField<u32, bool, 10, 1> draw_to_display_area;
BitField<u32, bool, 11, 1> draw_set_mask_bit;
BitField<u32, bool, 12, 1> draw_to_masked_pixels;
BitField<u32, bool, 13, 1> interlaced_field;
BitField<u32, bool, 14, 1> reverse_flag;
BitField<u32, bool, 15, 1> texture_disable;
BitField<u32, u8, 16, 1> horizontal_resolution_2;
BitField<u32, u8, 17, 2> horizontal_resolution_1;
BitField<u32, u8, 19, 1> vertical_resolution;
BitField<u32, bool, 20, 1> pal_mode;
BitField<u32, bool, 21, 1> display_area_color_depth_24;
BitField<u32, bool, 22, 1> vertical_interlace;
BitField<u32, bool, 23, 1> display_enable;
BitField<u32, bool, 24, 1> interrupt_request;
BitField<u32, bool, 25, 1> dma_data_request;
BitField<u32, bool, 26, 1> ready_to_recieve_cmd;
BitField<u32, bool, 27, 1> ready_to_send_vram;
BitField<u32, bool, 28, 1> ready_to_recieve_dma;
BitField<u32, DMADirection, 29, 2> dma_direction;
BitField<u32, bool, 31, 1> drawing_even_line;
} m_GPUSTAT = {};
struct RenderState
{
static constexpr u16 PAGE_ATTRIBUTE_TEXTURE_PAGE_MASK = UINT16_C(0b0000000000011111);
static constexpr u16 PAGE_ATTRIBUTE_MASK = UINT16_C(0b0000000111111111);
static constexpr u16 PALETTE_ATTRIBUTE_MASK = UINT16_C(0b0111111111111111);
// decoded values
u32 texture_page_x;
u32 texture_page_y;
u32 texture_palette_x;
u32 texture_palette_y;
TextureColorMode texture_color_mode;
TransparencyMode transparency_mode;
u8 texture_window_mask_x; // in 8 pixel steps
u8 texture_window_mask_y; // in 8 pixel steps
u8 texture_window_offset_x; // in 8 pixel steps
u8 texture_window_offset_y; // in 8 pixel steps
bool texture_x_flip;
bool texture_y_flip;
// original values
u16 texpage_attribute; // from register in rectangle modes/vertex in polygon modes
u16 texlut_attribute; // from vertex
bool texture_page_changed = false;
bool texture_color_mode_changed = false;
bool transparency_mode_changed = false;
bool IsChanged() const { return texture_page_changed || texture_color_mode_changed || transparency_mode_changed; }
bool IsTexturePageChanged() const { return texture_page_changed; }
void ClearTexturePageChangedFlag() { texture_page_changed = false; }
bool IsTextureColorModeChanged() const { return texture_color_mode_changed; }
void ClearTextureColorModeChangedFlag() { texture_color_mode_changed = false; }
bool IsTransparencyModeChanged() const { return transparency_mode_changed; }
void ClearTransparencyModeChangedFlag() { transparency_mode_changed = false; }
void SetFromPolygonTexcoord(u32 texcoord0, u32 texcoord1);
void SetFromRectangleTexcoord(u32 texcoord);
void SetFromPageAttribute(u16 value);
void SetFromPaletteAttribute(u16 value);
} m_render_state = {};
struct DrawingArea
{
u32 left, top;
u32 right, bottom;
} m_drawing_area = {};
struct DrawingOffset
{
s32 x;
s32 y;
} m_drawing_offset = {};
struct CRTCState
{
struct Regs
{
static constexpr u32 DISPLAY_ADDRESS_START_MASK = 0b111'11111111'11111111;
static constexpr u32 HORIZONTAL_DISPLAY_RANGE_MASK = 0b11111111'11111111'11111111;
static constexpr u32 VERTICAL_DISPLAY_RANGE_MASK = 0b1111'11111111'11111111;
union
{
u32 display_address_start;
BitField<u32, u32, 0, 10> X;
BitField<u32, u32, 10, 9> Y;
};
union
{
u32 horizontal_display_range;
BitField<u32, u32, 0, 12> X1;
BitField<u32, u32, 12, 12> X2;
};
union
{
u32 vertical_display_range;
BitField<u32, u32, 0, 10> Y1;
BitField<u32, u32, 10, 10> Y2;
};
} regs;
u32 horizontal_resolution;
u32 vertical_resolution;
TickCount dot_clock_divider;
u32 visible_horizontal_resolution;
u32 visible_vertical_resolution;
TickCount ticks_per_scanline;
TickCount visible_ticks_per_scanline;
u32 total_scanlines_per_frame;
TickCount fractional_ticks;
TickCount current_tick_in_scanline;
u32 current_scanline;
bool in_hblank;
bool in_vblank;
} m_crtc_state = {};
std::vector<u32> m_GP0_command;
std::deque<u32> m_GPUREAD_buffer;
// debug options
static bool DUMP_CPU_TO_VRAM_COPIES;
static bool DUMP_VRAM_TO_CPU_COPIES;
};

479
src/core/gpu_hw.cpp Normal file
View File

@ -0,0 +1,479 @@
#include "gpu_hw.h"
#include "YBaseLib/Assert.h"
#include "YBaseLib/Log.h"
#include <sstream>
Log_SetChannel(GPU_HW);
GPU_HW::GPU_HW() = default;
GPU_HW::~GPU_HW() = default;
void GPU_HW::LoadVertices(RenderCommand rc, u32 num_vertices)
{
const u32 texpage =
ZeroExtend32(m_render_state.texpage_attribute) | (ZeroExtend32(m_render_state.texlut_attribute) << 16);
// TODO: Move this to the GPU..
switch (rc.primitive)
{
case Primitive::Polygon:
{
// if we're drawing quads, we need to create a degenerate triangle to restart the triangle strip
bool restart_strip = (rc.quad_polygon && !m_batch.vertices.empty());
if (restart_strip)
m_batch.vertices.push_back(m_batch.vertices.back());
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
u32 buffer_pos = 1;
for (u32 i = 0; i < num_vertices; i++)
{
HWVertex hw_vert;
hw_vert.color = (shaded && i > 0) ? (m_GP0_command[buffer_pos++] & UINT32_C(0x00FFFFFF)) : first_color;
const VertexPosition vp{m_GP0_command[buffer_pos++]};
hw_vert.x = vp.x();
hw_vert.y = vp.y();
hw_vert.texpage = texpage;
if (textured)
hw_vert.texcoord = Truncate16(m_GP0_command[buffer_pos++]);
else
hw_vert.texcoord = 0;
hw_vert.padding = 0;
m_batch.vertices.push_back(hw_vert);
if (restart_strip)
{
m_batch.vertices.push_back(m_batch.vertices.back());
restart_strip = false;
}
}
}
break;
case Primitive::Rectangle:
{
// if we're drawing quads, we need to create a degenerate triangle to restart the triangle strip
const bool restart_strip = !m_batch.vertices.empty();
if (restart_strip)
m_batch.vertices.push_back(m_batch.vertices.back());
u32 buffer_pos = 1;
const bool textured = rc.texture_enable;
const u32 color = rc.color_for_first_vertex;
const VertexPosition vp{m_GP0_command[buffer_pos++]};
const s32 pos_left = vp.x();
const s32 pos_top = vp.y();
const auto [tex_left, tex_top] =
HWVertex::DecodeTexcoord(rc.texture_enable ? Truncate16(m_GP0_command[buffer_pos++]) : 0);
s32 rectangle_width;
s32 rectangle_height;
switch (rc.rectangle_size)
{
case DrawRectangleSize::R1x1:
rectangle_width = 1;
rectangle_height = 1;
break;
case DrawRectangleSize::R8x8:
rectangle_width = 8;
rectangle_height = 8;
break;
case DrawRectangleSize::R16x16:
rectangle_width = 16;
rectangle_height = 16;
break;
default:
rectangle_width = static_cast<s32>(m_GP0_command[buffer_pos] & UINT32_C(0xFFFF));
rectangle_height = static_cast<s32>(m_GP0_command[buffer_pos] >> 16);
break;
}
// TODO: This should repeat the texcoords instead of stretching
const s32 pos_right = pos_left + rectangle_width;
const s32 pos_bottom = pos_top + rectangle_height;
const u8 tex_right = static_cast<u8>(tex_left + (rectangle_width - 1));
const u8 tex_bottom = static_cast<u8>(tex_top + (rectangle_height - 1));
m_batch.vertices.push_back(
HWVertex{pos_left, pos_top, color, texpage, HWVertex::EncodeTexcoord(tex_left, tex_top)});
if (restart_strip)
m_batch.vertices.push_back(m_batch.vertices.back());
m_batch.vertices.push_back(
HWVertex{pos_right, pos_top, color, texpage, HWVertex::EncodeTexcoord(tex_right, tex_top)});
m_batch.vertices.push_back(
HWVertex{pos_left, pos_bottom, color, texpage, HWVertex::EncodeTexcoord(tex_left, tex_bottom)});
m_batch.vertices.push_back(
HWVertex{pos_right, pos_bottom, color, texpage, HWVertex::EncodeTexcoord(tex_right, tex_bottom)});
}
break;
case Primitive::Line:
{
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
u32 buffer_pos = 1;
for (u32 i = 0; i < num_vertices; i++)
{
const u32 color = (shaded && i > 0) ? (m_GP0_command[buffer_pos++] & UINT32_C(0x00FFFFFF)) : first_color;
const VertexPosition vp{m_GP0_command[buffer_pos++]};
m_batch.vertices.push_back(HWVertex{vp.x(), vp.y(), color});
}
}
break;
default:
UnreachableCode();
break;
}
}
void GPU_HW::CalcScissorRect(int* left, int* top, int* right, int* bottom)
{
*left = m_drawing_area.left * s32(m_resolution_scale);
*right = (m_drawing_area.right + 1) * s32(m_resolution_scale);
*top = m_drawing_area.top * s32(m_resolution_scale);
*bottom = (m_drawing_area.bottom + 1) * s32(m_resolution_scale);
}
static void DefineMacro(std::stringstream& ss, const char* name, bool enabled)
{
if (enabled)
ss << "#define " << name << " 1\n";
else
ss << "/* #define " << name << " 0 */\n";
}
void GPU_HW::GenerateShaderHeader(std::stringstream& ss)
{
ss << "#version 330 core\n\n";
ss << "const int RESOLUTION_SCALE = " << m_resolution_scale << ";\n";
ss << "const ivec2 VRAM_SIZE = ivec2(" << VRAM_WIDTH << ", " << VRAM_HEIGHT << ") * RESOLUTION_SCALE;\n";
ss << "const vec2 RCP_VRAM_SIZE = vec2(1.0, 1.0) / vec2(VRAM_SIZE);\n";
ss << R"(
float fixYCoord(float y)
{
return 1.0 - RCP_VRAM_SIZE.y - y;
}
int fixYCoord(int y)
{
return VRAM_SIZE.y - y - 1;
}
uint RGBA8ToRGBA5551(vec4 v)
{
uint r = uint(v.r * 255.0) >> 3;
uint g = uint(v.g * 255.0) >> 3;
uint b = uint(v.b * 255.0) >> 3;
uint a = (v.a != 0.0) ? 1u : 0u;
return (r) | (g << 5) | (b << 10) | (a << 15);
}
vec4 RGBA5551ToRGBA8(uint v)
{
uint r = (v & 0x1Fu);
uint g = ((v >> 5) & 0x1Fu);
uint b = ((v >> 10) & 0x1Fu);
uint a = ((v >> 15) & 0x01u);
return vec4(float(r) * 255.0, float(g) * 255.0, float(b) * 255.0, float(a) * 255.0);
}
)";
}
std::string GPU_HW::GenerateVertexShader(bool textured)
{
std::stringstream ss;
GenerateShaderHeader(ss);
DefineMacro(ss, "TEXTURED", textured);
ss << R"(
in ivec2 a_pos;
in vec4 a_col0;
in vec2 a_tex0;
in int a_texpage;
out vec3 v_col0;
#if TEXTURED
out vec2 v_tex0;
flat out ivec4 v_texpage;
#endif
uniform ivec2 u_pos_offset;
void main()
{
// 0..+1023 -> -1..1
float pos_x = (float(a_pos.x + u_pos_offset.x) / 512.0) - 1.0;
float pos_y = (float(a_pos.y + u_pos_offset.y) / -256.0) + 1.0;
gl_Position = vec4(pos_x, pos_y, 0.0, 1.0);
v_col0 = a_col0.rgb;
#if TEXTURED
v_tex0 = a_tex0;
// base_x,base_y,palette_x,palette_y
v_texpage.x = (a_texpage & 15) * 64;
v_texpage.y = ((a_texpage >> 4) & 1) * 256;
v_texpage.z = ((a_texpage >> 16) & 63) * 16;
v_texpage.w = ((a_texpage >> 22) & 511);
#endif
}
)";
return ss.str();
}
std::string GPU_HW::GenerateFragmentShader(bool textured, bool blending, bool transparent,
TextureColorMode texture_color_mode)
{
std::stringstream ss;
GenerateShaderHeader(ss);
DefineMacro(ss, "TEXTURED", textured);
DefineMacro(ss, "BLENDING", blending);
DefineMacro(ss, "PALETTE",
textured && (texture_color_mode == GPU::TextureColorMode::Palette4Bit ||
texture_color_mode == GPU::TextureColorMode::Palette8Bit));
DefineMacro(ss, "PALETTE_4_BIT", textured && texture_color_mode == GPU::TextureColorMode::Palette4Bit);
DefineMacro(ss, "PALETTE_8_BIT", textured && texture_color_mode == GPU::TextureColorMode::Palette8Bit);
ss << R"(
in vec3 v_col0;
uniform vec2 u_transparent_alpha;
#if TEXTURED
in vec2 v_tex0;
flat in ivec4 v_texpage;
uniform sampler2D samp0;
#endif
out vec4 o_col0;
#if TEXTURED
vec4 SampleFromVRAM(vec2 coord)
{
// from 0..1 to 0..255
ivec2 icoord = ivec2(coord * vec2(255.0));
// adjust for tightly packed palette formats
ivec2 index_coord = icoord;
#if PALETTE_4_BIT
index_coord.x /= 4;
#elif PALETTE_8_BIT
index_coord.x /= 2;
#endif
// fixup coords
ivec2 vicoord = ivec2((v_texpage.x + index_coord.x) * RESOLUTION_SCALE,
fixYCoord((v_texpage.y + index_coord.y) * RESOLUTION_SCALE));
// load colour/palette
vec4 color = texelFetch(samp0, vicoord, 0);
// apply palette
#if PALETTE
#if PALETTE_4_BIT
int subpixel = int(icoord.x) & 3;
uint vram_value = RGBA8ToRGBA5551(color);
int palette_index = int((vram_value >> (subpixel * 4)) & 0x0Fu);
#elif PALETTE_8_BIT
int subpixel = int(icoord.x) & 1;
uint vram_value = RGBA8ToRGBA5551(color);
int palette_index = int((vram_value >> (subpixel * 8)) & 0xFFu);
#endif
ivec2 palette_icoord = ivec2((v_texpage.z + palette_index) * RESOLUTION_SCALE,
fixYCoord(v_texpage.w * RESOLUTION_SCALE));
color = texelFetch(samp0, palette_icoord, 0);
#endif
return color;
}
#endif
void main()
{
#if TEXTURED
vec4 texcol = SampleFromVRAM(v_tex0);
if (texcol == vec4(0.0, 0.0, 0.0, 0.0))
discard;
vec3 color;
#if BLENDING
color = vec3((ivec3(v_col0 * 255.0) * ivec3(texcol.rgb * 255.0)) >> 7) / 255.0;
#else
color = texcol.rgb;
#endif
#if TRANSPARENT
// Apply semitransparency. If not a semitransparent texel, destination alpha is ignored.
if (texcol.a != 0)
o_col0 = vec4(color * u_transparent_alpha.x, u_transparent_alpha.y);
else
o_col0 = vec4(color, 0.0);
#else
// Mask bit from texture.
o_col0 = vec4(color, texcol.a);
#endif
#else
#if TRANSPARENT
o_col0 = vec4(v_col0 * u_transparent_alpha.x, u_transparent_alpha.y);
#else
// Mask bit is cleared for untextured polygons.
o_col0 = vec4(v_col0, 0.0);
#endif
#endif
}
)";
return ss.str();
}
std::string GPU_HW::GenerateScreenQuadVertexShader()
{
std::stringstream ss;
GenerateShaderHeader(ss);
ss << R"(
out vec2 v_tex0;
void main()
{
v_tex0 = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
gl_Position = vec4(v_tex0 * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f);
gl_Position.y = -gl_Position.y;
}
)";
return ss.str();
}
std::string GPU_HW::GenerateFillFragmentShader()
{
std::stringstream ss;
GenerateShaderHeader(ss);
ss << R"(
uniform vec4 fill_color;
out vec4 o_col0;
void main()
{
o_col0 = fill_color;
}
)";
return ss.str();
}
GPU_HW::HWRenderBatch::Primitive GPU_HW::GetPrimitiveForCommand(RenderCommand rc)
{
if (rc.primitive == Primitive::Line)
return rc.polyline ? HWRenderBatch::Primitive::LineStrip : HWRenderBatch::Primitive::Lines;
else if ((rc.primitive == Primitive::Polygon && rc.quad_polygon) || rc.primitive == Primitive::Rectangle)
return HWRenderBatch::Primitive::TriangleStrip;
else
return HWRenderBatch::Primitive::Triangles;
}
void GPU_HW::InvalidateVRAMReadCache() {}
void GPU_HW::DispatchRenderCommand(RenderCommand rc, u32 num_vertices)
{
if (rc.texture_enable)
{
// extract texture lut/page
switch (rc.primitive)
{
case Primitive::Polygon:
{
if (rc.shading_enable)
m_render_state.SetFromPolygonTexcoord(m_GP0_command[2], m_GP0_command[5]);
else
m_render_state.SetFromPolygonTexcoord(m_GP0_command[2], m_GP0_command[4]);
}
break;
case Primitive::Rectangle:
{
m_render_state.SetFromRectangleTexcoord(m_GP0_command[2]);
m_render_state.SetFromPageAttribute(Truncate16(m_GPUSTAT.bits));
}
break;
default:
break;
}
}
// has any state changed which requires a new batch?
const bool rc_transparency_enable = rc.IsTransparencyEnabled();
const bool rc_texture_enable = rc.IsTextureEnabled();
const bool rc_texture_blend_enable = rc.IsTextureBlendingEnabled();
const HWRenderBatch::Primitive rc_primitive = GetPrimitiveForCommand(rc);
const u32 max_added_vertices = num_vertices + 2;
const bool buffer_overflow = (m_batch.vertices.size() + max_added_vertices) >= MAX_BATCH_VERTEX_COUNT;
const bool rc_changed =
m_batch.render_command_bits != rc.bits && m_batch.transparency_enable != rc_transparency_enable ||
m_batch.texture_enable != rc_texture_enable || m_batch.texture_blending_enable != rc_texture_blend_enable ||
m_batch.primitive != rc_primitive;
const bool restart_line_strip = (rc_primitive == HWRenderBatch::Primitive::LineStrip);
const bool needs_flush =
!IsFlushed() && (m_render_state.IsTextureColorModeChanged() || m_render_state.IsTransparencyModeChanged() ||
buffer_overflow || rc_changed || restart_line_strip);
if (needs_flush)
FlushRender();
// update state
if (rc_changed)
{
m_batch.render_command_bits = rc.bits;
m_batch.primitive = rc_primitive;
m_batch.transparency_enable = rc_transparency_enable;
m_batch.texture_enable = rc_texture_enable;
m_batch.texture_blending_enable = rc_texture_blend_enable;
}
if (m_render_state.IsTexturePageChanged())
{
// we only need to update the copy texture if the render area intersects with the texture page
const u32 texture_page_left = m_render_state.texture_page_x;
const u32 texture_page_right = m_render_state.texture_page_y + TEXTURE_PAGE_WIDTH;
const u32 texture_page_top = m_render_state.texture_page_y;
const u32 texture_page_bottom = texture_page_top + TEXTURE_PAGE_HEIGHT;
const bool texture_page_overlaps =
(texture_page_left < m_drawing_area.right && texture_page_right > m_drawing_area.left &&
texture_page_top > m_drawing_area.bottom && texture_page_bottom < m_drawing_area.top);
// TODO: Check palette too.
if (texture_page_overlaps)
{
Log_DebugPrintf("Invalidating VRAM read cache due to drawing area overlap");
InvalidateVRAMReadCache();
}
m_batch.texture_page_x = m_render_state.texture_page_x;
m_batch.texture_page_y = m_render_state.texture_page_y;
m_batch.texture_palette_x = m_render_state.texture_palette_x;
m_batch.texture_palette_y = m_render_state.texture_palette_y;
m_render_state.ClearTexturePageChangedFlag();
}
if (m_render_state.IsTextureColorModeChanged())
{
m_batch.texture_color_mode = m_render_state.texture_color_mode;
m_render_state.ClearTextureColorModeChangedFlag();
}
if (m_render_state.IsTransparencyModeChanged())
{
m_batch.transparency_mode = m_render_state.transparency_mode;
m_render_state.ClearTransparencyModeChangedFlag();
}
LoadVertices(rc, num_vertices);
}

99
src/core/gpu_hw.h Normal file
View File

@ -0,0 +1,99 @@
#pragma once
#include "gpu.h"
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
class GPU_HW : public GPU
{
public:
GPU_HW();
virtual ~GPU_HW();
protected:
struct HWVertex
{
s32 x;
s32 y;
u32 color;
u32 texpage;
u16 texcoord;
u16 padding;
static constexpr std::tuple<u8, u8> DecodeTexcoord(u16 texcoord)
{
return std::make_tuple(static_cast<u8>(texcoord), static_cast<u8>(texcoord >> 8));
}
static constexpr u16 EncodeTexcoord(u8 x, u8 y) { return ZeroExtend16(x) | (ZeroExtend16(y) << 8); }
};
struct HWRenderBatch
{
enum class Primitive : u8
{
Lines = 0,
LineStrip = 1,
Triangles = 2,
TriangleStrip = 3
};
u32 render_command_bits;
Primitive primitive;
bool transparency_enable;
bool texture_enable;
bool texture_blending_enable;
TextureColorMode texture_color_mode;
u32 texture_page_x;
u32 texture_page_y;
u32 texture_palette_x;
u32 texture_palette_y;
TransparencyMode transparency_mode;
std::vector<HWVertex> vertices;
};
static constexpr u32 VERTEX_BUFFER_SIZE = 1 * 1024 * 1024;
static constexpr u32 MAX_BATCH_VERTEX_COUNT = VERTEX_BUFFER_SIZE / sizeof(HWVertex);
static constexpr u32 TEXTURE_TILE_SIZE = 256;
static constexpr u32 TEXTURE_TILE_X_COUNT = VRAM_WIDTH / TEXTURE_TILE_SIZE;
static constexpr u32 TEXTURE_TILE_Y_COUNT = VRAM_HEIGHT / TEXTURE_TILE_SIZE;
static constexpr u32 TEXTURE_TILE_COUNT = TEXTURE_TILE_X_COUNT * TEXTURE_TILE_Y_COUNT;
static constexpr std::tuple<float, float, float, float> RGBA8ToFloat(u32 rgba)
{
return std::make_tuple(static_cast<float>(rgba & UINT32_C(0xFF)) * (1.0f / 255.0f),
static_cast<float>((rgba >> 16) & UINT32_C(0xFF)) * (1.0f / 255.0f),
static_cast<float>((rgba >> 8) & UINT32_C(0xFF)) * (1.0f / 255.0f),
static_cast<float>(rgba >> 24) * (1.0f / 255.0f));
}
virtual void InvalidateVRAMReadCache();
bool IsFlushed() const { return m_batch.vertices.empty(); }
void DispatchRenderCommand(RenderCommand rc, u32 num_vertices) override;
void CalcScissorRect(int* left, int* top, int* right, int* bottom);
std::tuple<s32, s32> ScaleVRAMCoordinates(s32 x, s32 y) const
{
return std::make_tuple(x * s32(m_resolution_scale), y * s32(m_resolution_scale));
}
std::string GenerateVertexShader(bool textured);
std::string GenerateFragmentShader(bool textured, bool blending, bool transparent,
TextureColorMode texture_color_mode);
std::string GenerateScreenQuadVertexShader();
std::string GenerateFillFragmentShader();
u32 m_resolution_scale = 1;
HWRenderBatch m_batch = {};
private:
static HWRenderBatch::Primitive GetPrimitiveForCommand(RenderCommand rc);
void GenerateShaderHeader(std::stringstream& ss);
void LoadVertices(RenderCommand rc, u32 num_vertices);
};

580
src/core/gpu_hw_opengl.cpp Normal file
View File

@ -0,0 +1,580 @@
#include "gpu_hw_opengl.h"
#include "YBaseLib/Assert.h"
#include "YBaseLib/Log.h"
#include "YBaseLib/String.h"
#include "host_interface.h"
#include "imgui.h"
#include "system.h"
Log_SetChannel(GPU_HW_OpenGL);
GPU_HW_OpenGL::GPU_HW_OpenGL() : GPU_HW() {}
GPU_HW_OpenGL::~GPU_HW_OpenGL()
{
DestroyFramebuffer();
}
bool GPU_HW_OpenGL::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers)
{
if (!GPU_HW::Initialize(system, dma, interrupt_controller, timers))
return false;
CreateFramebuffer();
CreateVertexBuffer();
if (!CompilePrograms())
return false;
m_system->GetHostInterface()->SetDisplayTexture(m_display_texture.get(), 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 1.0f);
return true;
}
void GPU_HW_OpenGL::Reset()
{
GPU_HW::Reset();
ClearFramebuffer();
}
void GPU_HW_OpenGL::RenderUI()
{
GPU_HW::RenderUI();
ImGui::SetNextWindowSize(ImVec2(300.0f, 130.0f), ImGuiCond_Once);
const bool is_null_frame = m_stats.num_batches == 0;
if (!is_null_frame)
{
m_last_stats = m_stats;
m_stats = {};
}
if (ImGui::Begin("GL Render Statistics"))
{
ImGui::Columns(2);
ImGui::SetColumnWidth(0, 200.0f);
ImGui::TextUnformatted("VRAM Read Texture Updates:");
ImGui::NextColumn();
ImGui::Text("%u", m_last_stats.num_vram_read_texture_updates);
ImGui::NextColumn();
ImGui::TextUnformatted("Batches Drawn:");
ImGui::NextColumn();
ImGui::Text("%u", m_last_stats.num_batches);
ImGui::NextColumn();
ImGui::TextUnformatted("Vertices Drawn: ");
ImGui::NextColumn();
ImGui::Text("%u", m_last_stats.num_vertices);
ImGui::NextColumn();
ImGui::TextUnformatted("GPU Active In This Frame: ");
ImGui::NextColumn();
ImGui::Text("%s", is_null_frame ? "Yes" : "No");
ImGui::NextColumn();
ImGui::Columns(1);
ImGui::Checkbox("Show VRAM##gpu_gl_show_vram", &m_show_vram);
static constexpr std::array<const char*, 16> internal_resolution_items = {
{"1x Internal Resolution", "2x Internal Resolution", "3x Internal Resolution", "4x Internal Resolution",
"5x Internal Resolution", "6x Internal Resolution", "7x Internal Resolution", "8x Internal Resolution",
"9x Internal Resolution", "10x Internal Resolution", "11x Internal Resolution", "12x Internal Resolution",
"13x Internal Resolution", "14x Internal Resolution", "15x Internal Resolution", "16x Internal Resolution"}};
int internal_resolution_item =
std::clamp(static_cast<int>(m_resolution_scale) - 1, 0, static_cast<int>(internal_resolution_items.size()));
if (ImGui::Combo("##gpu_internal_resolution", &internal_resolution_item, internal_resolution_items.data(),
static_cast<int>(internal_resolution_items.size())))
{
m_resolution_scale = static_cast<u32>(internal_resolution_item + 1);
m_system->GetHostInterface()->AddOSDMessage(
TinyString::FromFormat("Internal resolution changed to %ux, recompiling programs", m_resolution_scale));
CreateFramebuffer();
CompilePrograms();
}
}
ImGui::End();
}
void GPU_HW_OpenGL::InvalidateVRAMReadCache()
{
m_vram_read_texture_dirty = true;
}
std::tuple<s32, s32> GPU_HW_OpenGL::ConvertToFramebufferCoordinates(s32 x, s32 y)
{
return std::make_tuple(x, static_cast<s32>(static_cast<s32>(VRAM_HEIGHT) - y));
}
void GPU_HW_OpenGL::CreateFramebuffer()
{
// save old vram texture/fbo, in case we're changing scale
auto old_vram_texture = std::move(m_vram_texture);
const GLuint old_vram_fbo = m_vram_fbo;
m_vram_fbo = 0;
DestroyFramebuffer();
// scale vram size to internal resolution
const u32 texture_width = VRAM_WIDTH * m_resolution_scale;
const u32 texture_height = VRAM_HEIGHT * m_resolution_scale;
m_vram_texture =
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
glGenFramebuffers(1, &m_vram_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
// do we need to restore the framebuffer after a size change?
if (old_vram_texture)
{
const bool linear_filter = old_vram_texture->GetWidth() > m_vram_texture->GetWidth();
Log_DevPrintf("Scaling %ux%u VRAM texture to %ux%u using %s filter", old_vram_texture->GetWidth(),
old_vram_texture->GetHeight(), m_vram_texture->GetWidth(), m_vram_texture->GetHeight(),
linear_filter ? "linear" : "nearest");
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, old_vram_fbo);
glBlitFramebuffer(0, 0, old_vram_texture->GetWidth(), old_vram_texture->GetHeight(), 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), GL_COLOR_BUFFER_BIT,
linear_filter ? GL_LINEAR : GL_NEAREST);
glDeleteFramebuffers(1, &old_vram_fbo);
old_vram_texture.reset();
}
m_vram_read_texture =
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
glGenFramebuffers(1, &m_vram_read_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_read_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_read_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
if (m_resolution_scale > 1)
{
m_vram_downsample_texture =
std::make_unique<GL::Texture>(VRAM_WIDTH, VRAM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
m_vram_downsample_texture->Bind();
glGenFramebuffers(1, &m_vram_downsample_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_downsample_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_downsample_texture->GetGLId(),
0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
m_display_texture =
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
m_display_texture->Bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenFramebuffers(1, &m_display_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_display_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_display_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
void GPU_HW_OpenGL::ClearFramebuffer()
{
// TODO: get rid of the FBO switches
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void GPU_HW_OpenGL::DestroyFramebuffer()
{
glDeleteFramebuffers(1, &m_vram_read_fbo);
m_vram_read_fbo = 0;
m_vram_read_texture.reset();
glDeleteFramebuffers(1, &m_vram_fbo);
m_vram_fbo = 0;
m_vram_texture.reset();
if (m_vram_downsample_texture)
{
glDeleteFramebuffers(1, &m_vram_downsample_fbo);
m_vram_downsample_fbo = 0;
m_vram_downsample_texture.reset();
}
glDeleteFramebuffers(1, &m_display_fbo);
m_display_fbo = 0;
m_display_texture.reset();
}
void GPU_HW_OpenGL::CreateVertexBuffer()
{
glGenBuffers(1, &m_vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE, nullptr, GL_STREAM_DRAW);
glGenVertexArrays(1, &m_vao_id);
glBindVertexArray(m_vao_id);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glVertexAttribIPointer(0, 2, GL_INT, sizeof(HWVertex), reinterpret_cast<void*>(offsetof(HWVertex, x)));
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true, sizeof(HWVertex),
reinterpret_cast<void*>(offsetof(HWVertex, color)));
glVertexAttribPointer(2, 2, GL_UNSIGNED_BYTE, true, sizeof(HWVertex),
reinterpret_cast<void*>(offsetof(HWVertex, texcoord)));
glVertexAttribIPointer(3, 1, GL_INT, sizeof(HWVertex), reinterpret_cast<void*>(offsetof(HWVertex, texpage)));
glBindVertexArray(0);
glGenVertexArrays(1, &m_attributeless_vao_id);
}
bool GPU_HW_OpenGL::CompilePrograms()
{
for (u32 textured = 0; textured < 2; textured++)
{
for (u32 blending = 0; blending < 2; blending++)
{
for (u32 transparent = 0; transparent < 2; transparent++)
{
for (u32 format = 0; format < 3; format++)
{
// TODO: eliminate duplicate shaders here
if (!CompileProgram(m_render_programs[textured][blending][transparent][format],
ConvertToBoolUnchecked(textured), ConvertToBoolUnchecked(blending),
ConvertToBoolUnchecked(transparent), static_cast<TextureColorMode>(format)))
{
return false;
}
}
}
}
}
return true;
}
bool GPU_HW_OpenGL::CompileProgram(GL::Program& prog, bool textured, bool blending, bool transparent,
TextureColorMode texture_color_mode)
{
const std::string vs = GenerateVertexShader(textured);
const std::string fs = GenerateFragmentShader(textured, blending, transparent, texture_color_mode);
if (!prog.Compile(vs.c_str(), fs.c_str()))
return false;
prog.BindAttribute(0, "a_pos");
prog.BindAttribute(1, "a_col0");
if (textured)
{
prog.BindAttribute(2, "a_tex0");
prog.BindAttribute(3, "a_texpage");
}
prog.BindFragData(0, "o_col0");
if (!prog.Link())
return false;
prog.Bind();
prog.RegisterUniform("u_pos_offset");
prog.RegisterUniform("u_transparent_alpha");
prog.Uniform2i(0, 0, 0);
prog.Uniform2f(1, 1.0f, 0.0f);
if (textured)
{
prog.RegisterUniform("samp0");
prog.Uniform1i(2, 0);
}
return true;
}
void GPU_HW_OpenGL::SetProgram()
{
const GL::Program& prog =
m_render_programs[BoolToUInt32(m_batch.texture_enable)][BoolToUInt32(m_batch.texture_blending_enable)]
[BoolToUInt32(m_batch.transparency_enable)][static_cast<u32>(m_batch.texture_color_mode)];
prog.Bind();
prog.Uniform2i(0, m_drawing_offset.x, m_drawing_offset.y);
if (m_batch.transparency_enable)
{
static constexpr float transparent_alpha[4][2] = {{0.5f, 0.5f}, {1.0f, 1.0f}, {1.0f, 1.0f}, {0.25f, 1.0f}};
prog.Uniform2fv(1, transparent_alpha[static_cast<u32>(m_batch.transparency_mode)]);
}
else
{
static constexpr float disabled_alpha[2] = {1.0f, 0.0f};
prog.Uniform2fv(1, disabled_alpha);
}
if (m_batch.texture_enable)
m_vram_read_texture->Bind();
}
void GPU_HW_OpenGL::SetViewport()
{
glViewport(0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
}
void GPU_HW_OpenGL::SetScissor()
{
int left, top, right, bottom;
CalcScissorRect(&left, &top, &right, &bottom);
const int width = right - left;
const int height = bottom - top;
const int x = left;
const int y = m_vram_texture->GetHeight() - bottom;
Log_DebugPrintf("SetScissor: (%d-%d, %d-%d)", x, x + width, y, y + height);
glScissor(x, y, width, height);
}
void GPU_HW_OpenGL::SetBlendState()
{
if (!m_batch.transparency_enable)
{
glDisable(GL_BLEND);
return;
}
glEnable(GL_BLEND);
glBlendEquationSeparate(m_batch.transparency_mode == GPU::TransparencyMode::BackgroundMinusForeground ?
GL_FUNC_REVERSE_SUBTRACT :
GL_FUNC_ADD,
GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_ONE, GL_ZERO);
}
void GPU_HW_OpenGL::UpdateDisplay()
{
GPU_HW::UpdateDisplay();
const u32 texture_width = m_vram_texture->GetWidth();
const u32 texture_height = m_vram_texture->GetHeight();
// TODO: 24-bit support.
if (m_show_vram)
{
m_system->GetHostInterface()->SetDisplayTexture(m_vram_texture.get(), 0, 0, texture_width, texture_height, 1.0f);
}
else
{
const u32 display_width = m_crtc_state.horizontal_resolution * m_resolution_scale;
const u32 display_height = m_crtc_state.vertical_resolution * m_resolution_scale;
const u32 vram_offset_x = m_crtc_state.regs.X * m_resolution_scale;
const u32 vram_offset_y = m_crtc_state.regs.Y * m_resolution_scale;
const u32 copy_width =
((vram_offset_x + display_width) > texture_width) ? (texture_width - vram_offset_x) : display_width;
const u32 copy_height =
((vram_offset_y + display_height) > texture_height) ? (texture_height - vram_offset_y) : display_height;
glCopyImageSubData(m_vram_texture->GetGLId(), GL_TEXTURE_2D, 0, vram_offset_x,
texture_height - vram_offset_y - copy_height, 0, m_display_texture->GetGLId(), GL_TEXTURE_2D, 0,
0, 0, 0, copy_width, copy_height, 1);
m_system->GetHostInterface()->SetDisplayTexture(m_display_texture.get(), 0, 0, copy_width, copy_height,
DISPLAY_ASPECT_RATIO);
}
}
void GPU_HW_OpenGL::ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer)
{
// we need to convert RGBA8 -> RGBA5551
std::vector<u32> temp_buffer(width * height);
// downscaling to 1xIR.
if (m_resolution_scale > 1)
{
const u32 texture_width = m_vram_texture->GetWidth();
const u32 texture_height = m_vram_texture->GetHeight();
const u32 scaled_x = x * m_resolution_scale;
const u32 scaled_y = y * m_resolution_scale;
const u32 scaled_width = width * m_resolution_scale;
const u32 scaled_height = height * m_resolution_scale;
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_downsample_fbo);
glBlitFramebuffer(scaled_x, texture_height - scaled_y - height, scaled_x + scaled_width, scaled_y + scaled_height,
0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_downsample_fbo);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_buffer.data());
}
else
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_fbo);
glReadPixels(x, VRAM_HEIGHT - y - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_buffer.data());
}
// reverse copy because of lower-left origin
const u32 source_stride = width * sizeof(u32);
const u8* source_ptr = reinterpret_cast<const u8*>(temp_buffer.data()) + (source_stride * (height - 1));
const u32 dst_stride = width * sizeof(u16);
u8* dst_ptr = static_cast<u8*>(buffer);
for (u32 row = 0; row < height; row++)
{
const u8* source_row_ptr = source_ptr;
u8* dst_row_ptr = dst_ptr;
for (u32 col = 0; col < width; col++)
{
u32 src_col;
std::memcpy(&src_col, source_row_ptr, sizeof(src_col));
source_row_ptr += sizeof(src_col);
const u16 dst_col = RGBA8888ToRGBA5551(src_col);
std::memcpy(dst_row_ptr, &dst_col, sizeof(dst_col));
dst_row_ptr += sizeof(dst_col);
}
source_ptr -= source_stride;
dst_ptr += dst_stride;
}
}
void GPU_HW_OpenGL::FillVRAM(u32 x, u32 y, u32 width, u32 height, u16 color)
{
// scale coordiantes
x *= m_resolution_scale;
y *= m_resolution_scale;
width *= m_resolution_scale;
height *= m_resolution_scale;
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glEnable(GL_SCISSOR_TEST);
glScissor(x, m_vram_texture->GetHeight() - y - height, width, height);
const auto [r, g, b, a] = RGBA8ToFloat(RGBA5551ToRGBA8888(color));
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
InvalidateVRAMReadCache();
}
void GPU_HW_OpenGL::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data)
{
std::vector<u32> rgba_data;
rgba_data.reserve(width * height);
// reverse copy the rows so it matches opengl's lower-left origin
const u32 source_stride = width * sizeof(u16);
const u8* source_ptr = static_cast<const u8*>(data) + (source_stride * (height - 1));
for (u32 row = 0; row < height; row++)
{
const u8* source_row_ptr = source_ptr;
for (u32 col = 0; col < width; col++)
{
u16 src_col;
std::memcpy(&src_col, source_row_ptr, sizeof(src_col));
source_row_ptr += sizeof(src_col);
const u32 dst_col = RGBA5551ToRGBA8888(src_col);
rgba_data.push_back(dst_col);
}
source_ptr -= source_stride;
}
// have to write to the 1x texture first
if (m_resolution_scale > 1)
m_vram_downsample_texture->Bind();
else
m_vram_texture->Bind();
// lower-left origin flip happens here
const u32 flipped_y = VRAM_HEIGHT - y - height;
// update texture data
glTexSubImage2D(GL_TEXTURE_2D, 0, x, flipped_y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgba_data.data());
InvalidateVRAMReadCache();
if (m_resolution_scale > 1)
{
// scale to internal resolution
const u32 scaled_width = width * m_resolution_scale;
const u32 scaled_height = height * m_resolution_scale;
const u32 scaled_x = x * m_resolution_scale;
const u32 scaled_y = y * m_resolution_scale;
const u32 scaled_flipped_y = m_vram_texture->GetHeight() - scaled_y - scaled_height;
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_downsample_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo);
glBlitFramebuffer(x, flipped_y, x + width, flipped_y + height, scaled_x, scaled_flipped_y, scaled_x + scaled_width,
scaled_flipped_y + scaled_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
void GPU_HW_OpenGL::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height)
{
src_x *= m_resolution_scale;
src_y *= m_resolution_scale;
dst_x *= m_resolution_scale;
dst_y *= m_resolution_scale;
width *= m_resolution_scale;
height *= m_resolution_scale;
// lower-left origin flip
src_y = m_vram_texture->GetHeight() - src_y - height;
dst_y = m_vram_texture->GetHeight() - dst_y - height;
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glBlitFramebuffer(src_x, src_y, src_x + width, src_y + height, dst_x, dst_y, dst_x + width, dst_y + height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
InvalidateVRAMReadCache();
}
void GPU_HW_OpenGL::UpdateVRAMReadTexture()
{
m_stats.num_vram_read_texture_updates++;
m_vram_read_texture_dirty = false;
// TODO: Fallback blit path, and partial updates.
glCopyImageSubData(m_vram_texture->GetGLId(), GL_TEXTURE_2D, 0, 0, 0, 0, m_vram_read_texture->GetGLId(),
GL_TEXTURE_2D, 0, 0, 0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), 1);
}
void GPU_HW_OpenGL::FlushRender()
{
if (m_batch.vertices.empty())
return;
if (m_vram_read_texture_dirty)
UpdateVRAMReadTexture();
m_stats.num_batches++;
m_stats.num_vertices += static_cast<u32>(m_batch.vertices.size());
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glDepthMask(GL_FALSE);
SetProgram();
SetViewport();
SetScissor();
SetBlendState();
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glBindVertexArray(m_vao_id);
Assert((m_batch.vertices.size() * sizeof(HWVertex)) <= VERTEX_BUFFER_SIZE);
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizei>(sizeof(HWVertex) * m_batch.vertices.size()),
m_batch.vertices.data());
static constexpr std::array<GLenum, 4> gl_primitives = {{GL_LINES, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP}};
glDrawArrays(gl_primitives[static_cast<u8>(m_batch.primitive)], 0, static_cast<GLsizei>(m_batch.vertices.size()));
m_batch.vertices.clear();
}
std::unique_ptr<GPU> GPU::CreateHardwareOpenGLRenderer()
{
return std::make_unique<GPU_HW_OpenGL>();
}

77
src/core/gpu_hw_opengl.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#include "common/gl_program.h"
#include "common/gl_texture.h"
#include "glad.h"
#include "gpu_hw.h"
#include <array>
#include <memory>
#include <tuple>
class GPU_HW_OpenGL : public GPU_HW
{
public:
GPU_HW_OpenGL();
~GPU_HW_OpenGL() override;
bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers) override;
void Reset() override;
void RenderUI() override;
protected:
void UpdateDisplay() override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer) override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u16 color) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void FlushRender() override;
void InvalidateVRAMReadCache() override;
private:
struct GLStats
{
u32 num_vram_read_texture_updates;
u32 num_batches;
u32 num_vertices;
};
std::tuple<s32, s32> ConvertToFramebufferCoordinates(s32 x, s32 y);
void CreateFramebuffer();
void ClearFramebuffer();
void DestroyFramebuffer();
void UpdateVRAMReadTexture();
void CreateVertexBuffer();
bool CompilePrograms();
bool CompileProgram(GL::Program& prog, bool textured, bool blending, bool transparent, TextureColorMode texture_color_mode);
void SetProgram();
void SetViewport();
void SetScissor();
void SetBlendState();
// downsample texture - used for readbacks at >1xIR.
std::unique_ptr<GL::Texture> m_vram_texture;
std::unique_ptr<GL::Texture> m_vram_read_texture;
std::unique_ptr<GL::Texture> m_vram_downsample_texture;
std::unique_ptr<GL::Texture> m_display_texture;
GLuint m_vram_fbo = 0;
GLuint m_vram_read_fbo = 0;
GLuint m_vram_downsample_fbo = 0;
GLuint m_display_fbo = 0;
GLuint m_vertex_buffer = 0;
GLuint m_vao_id = 0;
GLuint m_attributeless_vao_id = 0;
bool m_vram_read_texture_dirty = true;
std::array<std::array<std::array<std::array<GL::Program, 3>, 2>, 2>, 2> m_render_programs;
std::array<GL::Program, 3> m_texture_page_programs;
GLStats m_stats = {};
GLStats m_last_stats = {};
bool m_show_vram = false;
};

874
src/core/gte.cpp Normal file
View File

@ -0,0 +1,874 @@
#include "gte.h"
#include "YBaseLib/Log.h"
#include <algorithm>
Log_SetChannel(GTE);
static inline constexpr u32 CountLeadingBits(u32 value)
{
u32 count = 0;
if ((value & UINT32_C(0x80000000)) != 0)
{
for (u32 i = 0; i < 32 && ((value & UINT32_C(0x80000000)) != 0); i++)
{
count++;
value <<= 1;
}
}
else
{
for (u32 i = 0; i < 32 && (value & UINT32_C(0x80000000)) == 0; i++)
{
count++;
value <<= 1;
}
}
return count;
}
namespace GTE {
Core::Core() = default;
Core::~Core() = default;
void Core::Initialize() {}
void Core::Reset()
{
m_regs = {};
}
bool Core::DoState(StateWrapper& sw)
{
sw.DoPOD(&m_regs);
return !sw.HasError();
}
u32 Core::ReadDataRegister(u32 index) const
{
switch (index)
{
case 15: // SXY3
{
// mirror of SXY2
return m_regs.dr32[14];
}
case 28: // IRGB
case 29: // ORGB
{
// ORGB register, convert 16-bit to 555
const u8 r = static_cast<u8>(std::clamp(m_regs.IR1 / 0x80, 0x00, 0x1F));
const u8 g = static_cast<u8>(std::clamp(m_regs.IR2 / 0x80, 0x00, 0x1F));
const u8 b = static_cast<u8>(std::clamp(m_regs.IR3 / 0x80, 0x00, 0x1F));
return ZeroExtend32(r) | (ZeroExtend32(g) << 5) | (ZeroExtend32(b) << 10);
}
case 0: // V0-1 [x,y]
case 1: // V0[z]
case 2: // V1-2 [x,y]
case 3: // V1[z]
case 4: // V2-3 [x,y]
case 5: // V2[z]
case 6: // RGBC
case 7: // OTZ
case 8: // IR0
case 9: // IR1
case 10: // IR2
case 11: // IR3
case 12: // SXY0
case 13: // SXY1
case 14: // SXY2
case 16: // SZ0
case 17: // SZ1
case 18: // SZ2
case 19: // SZ3
case 20: // RGB0
case 21: // RGB1
case 22: // RGB2
case 23: // RES1
case 24: // MAC0
case 25: // MAC1
case 26: // MAC2
case 27: // MAC3
case 30: // LZCS
case 31: // LZCR
return m_regs.dr32[index];
default:
Panic("Unknown register");
return 0;
}
}
void Core::WriteDataRegister(u32 index, u32 value)
{
// Log_DebugPrintf("DataReg(%u) <- 0x%08X", index, value);
switch (index)
{
case 1: // V0[z]
case 3: // V1[z]
case 5: // V2[z]
case 8: // IR0
case 9: // IR1
case 10: // IR2
case 11: // IR3
{
// sign-extend z component of vector registers
m_regs.dr32[index] = SignExtend32(Truncate16(value));
}
break;
case 7: // OTZ
case 16: // SZ0
case 17: // SZ1
case 18: // SZ2
case 19: // SZ3
{
// zero-extend unsigned values
m_regs.dr32[index] = ZeroExtend32(Truncate16(value));
}
break;
case 15: // SXY3
{
// writing to SXYP pushes to the FIFO
m_regs.dr32[12] = m_regs.dr32[13]; // SXY0 <- SXY1
m_regs.dr32[13] = m_regs.dr32[14]; // SXY1 <- SXY2
m_regs.dr32[14] = value; // SXY2 <- SXYP
}
break;
case 28: // IRGB
{
// IRGB register, convert 555 to 16-bit
m_regs.IRGB = value & UINT32_C(0x7FFF);
m_regs.dr32[9] = SignExtend32(static_cast<u16>(Truncate16((value & UINT32_C(0x1F)) * UINT32_C(0x80))));
m_regs.dr32[10] = SignExtend32(static_cast<u16>(Truncate16(((value >> 5) & UINT32_C(0x1F)) * UINT32_C(0x80))));
m_regs.dr32[11] = SignExtend32(static_cast<u16>(Truncate16(((value >> 10) & UINT32_C(0x1F)) * UINT32_C(0x80))));
}
break;
case 30: // LZCS
{
m_regs.LZCS = static_cast<s32>(value);
m_regs.LZCR = CountLeadingBits(value);
}
break;
case 29: // ORGB
case 31: // LZCR
{
// read-only registers
}
break;
case 0: // V0-1 [x,y]
case 2: // V1-2 [x,y]
case 4: // V2-3 [x,y]
case 6: // RGBC
case 12: // SXY0
case 13: // SXY1
case 14: // SXY2
case 20: // RGB0
case 21: // RGB1
case 22: // RGB2
case 23: // RES1
case 24: // MAC0
case 25: // MAC1
case 26: // MAC2
case 27: // MAC3
m_regs.dr32[index] = value;
break;
default:
Panic("Unknown register");
break;
}
}
u32 Core::ReadControlRegister(u32 index) const
{
return m_regs.cr32[index];
}
void Core::WriteControlRegister(u32 index, u32 value)
{
// Log_DebugPrintf("ControlReg(%u,%u) <- 0x%08X", index, index + 32, value);
switch (index)
{
case 36 - 32: // RT33
case 44 - 32: // L33
case 52 - 32: // LR33
case 58 - 32: // H - sign-extended on read but zext on use
case 59 - 32: // DQA
case 61 - 32: // ZSF3
case 62 - 32: // ZSF4
{
// MSB of the last matrix element is the last element sign-extended
m_regs.cr32[index] = SignExtend32(Truncate16(value));
}
break;
case 63 - 32: // FLAG
{
m_regs.FLAG.bits = value & UINT32_C(0x7FFFF000);
m_regs.FLAG.UpdateError();
}
break;
case 32 - 32: // RT11,RT12
case 33 - 32: // RT13,RT21
case 34 - 32: // RT22,RT23
case 35 - 32: // RT31,RT32
case 37 - 32: // TRX
case 38 - 32: // TRY
case 39 - 32: // TRZ
case 40 - 32: // L11,L12
case 41 - 32: // L13,L21
case 42 - 32: // L22,L23
case 43 - 32: // L31,L32
case 45 - 32: // RBK
case 46 - 32: // GBK
case 47 - 32: // BBK
case 48 - 32: // LR11,LR12
case 49 - 32: // LR13,LR21
case 50 - 32: // LR22,LR23
case 51 - 32: // LR31,LR32
case 53 - 32: // RFC
case 54 - 32: // GFC
case 55 - 32: // BFC
case 56 - 32: // OFX
case 57 - 32: // OFY
case 60 - 32: // DQB
{
// written as-is, 2x16 or 1x32 bits
m_regs.cr32[index] = value;
}
break;
default:
Panic("Unknown register");
break;
}
}
void Core::ExecuteInstruction(Instruction inst)
{
// Panic("GTE instruction");
switch (inst.command)
{
case 0x01:
Execute_RTPS(inst);
break;
case 0x06:
Execute_NCLIP(inst);
break;
case 0x10:
Execute_DPCS(inst);
break;
case 0x12:
Execute_MVMVA(inst);
break;
case 0x13:
Execute_NCDS(inst);
break;
case 0x16:
Execute_NCCT(inst);
break;
case 0x1B:
Execute_NCCS(inst);
break;
case 0x28:
Execute_SQR(inst);
break;
case 0x29:
Execute_DPCL(inst);
break;
case 0x2A:
Execute_DPCT(inst);
break;
case 0x2D:
Execute_AVSZ3(inst);
break;
case 0x2E:
Execute_AVSZ4(inst);
break;
case 0x30:
Execute_RTPT(inst);
break;
case 0x3D:
Execute_GPF(inst);
break;
case 0x3E:
Execute_GPL(inst);
break;
case 0x3F:
Execute_NCCT(inst);
break;
default:
Panic("Missing handler");
break;
}
}
void Core::SetOTZ(s32 value)
{
if (value < 0)
{
m_regs.FLAG.sz1_otz_saturated = true;
value = 0;
}
else if (value > 0xFFFF)
{
m_regs.FLAG.sz1_otz_saturated = true;
value = 0xFFFF;
}
m_regs.dr32[7] = static_cast<u32>(value);
}
void Core::PushSXY(s32 x, s32 y)
{
if (x < -1024)
{
m_regs.FLAG.sx2_saturated = true;
x = -1024;
}
else if (x > 32767)
{
m_regs.FLAG.sx2_saturated = true;
x = 32767;
}
if (y < -1024)
{
m_regs.FLAG.sy2_saturated = true;
y = -1024;
}
else if (x > 32767)
{
m_regs.FLAG.sy2_saturated = true;
y = 32767;
}
m_regs.dr32[12] = m_regs.dr32[13]; // SXY0 <- SXY1
m_regs.dr32[13] = m_regs.dr32[14]; // SXY1 <- SXY2
m_regs.SXY2[0] = static_cast<s16>(x);
m_regs.SXY2[1] = static_cast<s16>(y);
}
void Core::PushSZ(s32 value)
{
if (value < 0)
{
m_regs.FLAG.sz1_otz_saturated = true;
value = 0;
}
else if (value > 0xFFFF)
{
m_regs.FLAG.sz1_otz_saturated = true;
value = 0xFFFF;
}
m_regs.dr32[16] = m_regs.dr32[17]; // SZ0 <- SZ1
m_regs.dr32[17] = m_regs.dr32[18]; // SZ1 <- SZ2
m_regs.dr32[18] = m_regs.dr32[19]; // SZ2 <- SZ3
m_regs.dr32[19] = static_cast<u32>(value); // SZ3 <- value
}
void Core::PushRGB(u8 r, u8 g, u8 b, u8 c)
{
m_regs.dr32[20] = m_regs.dr32[21]; // RGB0 <- RGB1
m_regs.dr32[21] = m_regs.dr32[22]; // RGB1 <- RGB2
m_regs.dr32[22] =
ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(c) << 24); // RGB2 <- Value
}
void Core::PushRGBFromMAC()
{
// Note: SHR 4 used instead of /16 as the results are different.
PushRGB(TruncateRGB<0>(m_regs.MAC1 >> 4), TruncateRGB<1>(m_regs.MAC2 >> 4), TruncateRGB<2>(m_regs.MAC3 >> 4),
m_regs.RGBC[3]);
}
void Core::RTPS(const s16 V[3], bool sf, bool lm, bool last)
{
const u8 shift = sf ? 12 : 0;
#define dot3(i) \
SignExtendMACResult<i + 1>( \
(s64(m_regs.TR[i]) << 12) + \
SignExtendMACResult<i + 1>( \
SignExtendMACResult<i + 1>(SignExtendMACResult<i + 1>(s64(s32(m_regs.RT[i][0]) * s32(V[0]))) + \
s64(s32(m_regs.RT[i][1]) * s32(V[1]))) + \
s64(s32(m_regs.RT[i][2]) * s32(V[2]))))
// IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12)
// IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12)
// IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12)
const s64 x = dot3(0);
const s64 y = dot3(1);
const s64 z = dot3(2);
TruncateAndSetMAC<1>(x, shift);
TruncateAndSetMAC<2>(y, shift);
TruncateAndSetMAC<3>(z, shift);
TruncateAndSetIR<1>(m_regs.MAC1, lm);
TruncateAndSetIR<2>(m_regs.MAC2, lm);
// The command does saturate IR1,IR2,IR3 to -8000h..+7FFFh (regardless of lm bit). When using RTP with sf=0, then the
// IR3 saturation flag (FLAG.22) gets set <only> if "MAC3 SAR 12" exceeds -8000h..+7FFFh (although IR3 is saturated
// when "MAC3" exceeds -8000h..+7FFFh).
TruncateAndSetIR<3>(m_regs.MAC3, false);
m_regs.dr32[11] = std::clamp(m_regs.MAC3, lm ? 0 : IR123_MIN_VALUE, IR123_MAX_VALUE);
#undef dot3
// SZ3 = MAC3 SAR ((1-sf)*12) ;ScreenZ FIFO 0..+FFFFh
PushSZ(s32(z >> 12));
s32 result;
if (m_regs.SZ3 == 0)
{
// divide by zero
result = 0x1FFFF;
}
else
{
result = s32(((s64(ZeroExtend64(m_regs.H) * 0x20000) / s64(ZeroExtend64(m_regs.SZ3))) + 1) / 2);
if (result > 0x1FFFF)
{
m_regs.FLAG.divide_overflow = true;
result = 0x1FFFF;
}
}
// MAC0=(((H*20000h/SZ3)+1)/2)*IR1+OFX, SX2=MAC0/10000h ;ScrX FIFO -400h..+3FFh
// MAC0=(((H*20000h/SZ3)+1)/2)*IR2+OFY, SY2=MAC0/10000h ;ScrY FIFO -400h..+3FFh
const s64 Sx = s64(result) * s64(m_regs.IR1) + s64(m_regs.OFX);
const s64 Sy = s64(result) * s64(m_regs.IR2) + s64(m_regs.OFY);
TruncateAndSetMAC<0>(Sx, 0);
TruncateAndSetMAC<1>(Sy, 0);
PushSXY(s32(Sx >> 16), s32(Sy >> 16));
if (last)
{
// MAC0=(((H*20000h/SZ3)+1)/2)*DQA+DQB, IR0=MAC0/1000h ;Depth cueing 0..+1000h
const s64 Sz = s64(result) * s64(m_regs.DQA) + s64(m_regs.DQB);
TruncateAndSetMAC<0>(Sz, 0);
TruncateAndSetIR<0>(s32(Sz >> 12), true);
}
}
void Core::Execute_RTPS(Instruction inst)
{
m_regs.FLAG.Clear();
RTPS(m_regs.V0, inst.sf, inst.lm, true);
m_regs.FLAG.UpdateError();
}
void Core::Execute_RTPT(Instruction inst)
{
m_regs.FLAG.Clear();
const bool sf = inst.sf;
RTPS(m_regs.V0, sf, inst.lm, false);
RTPS(m_regs.V1, sf, inst.lm, false);
RTPS(m_regs.V2, sf, inst.lm, true);
m_regs.FLAG.UpdateError();
}
void Core::Execute_NCLIP(Instruction inst)
{
// MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1
m_regs.FLAG.Clear();
TruncateAndSetMAC<0>(s64(m_regs.SXY0[0]) * s64(m_regs.SXY1[1]) + s64(m_regs.SXY1[0]) * s64(m_regs.SXY2[1]) +
s64(m_regs.SXY2[0]) * s64(m_regs.SXY0[1]) - s64(m_regs.SXY0[0]) * s64(m_regs.SXY2[1]) -
s64(m_regs.SXY1[0]) * s64(m_regs.SXY0[1]) - s64(m_regs.SXY2[0]) * s64(m_regs.SXY1[1]),
0);
m_regs.FLAG.UpdateError();
}
void Core::Execute_SQR(Instruction inst)
{
m_regs.FLAG.Clear();
// 32-bit multiply for speed - 16x16 isn't >32bit, and we know it won't overflow/underflow.
const u8 shift = inst.GetShift();
m_regs.MAC1 = (s32(m_regs.IR1) * s32(m_regs.IR1)) >> shift;
m_regs.MAC2 = (s32(m_regs.IR2) * s32(m_regs.IR2)) >> shift;
m_regs.MAC3 = (s32(m_regs.IR3) * s32(m_regs.IR3)) >> shift;
const bool lm = inst.lm;
TruncateAndSetIR<1>(m_regs.MAC1, lm);
TruncateAndSetIR<2>(m_regs.MAC2, lm);
TruncateAndSetIR<3>(m_regs.MAC3, lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_AVSZ3(Instruction inst)
{
m_regs.FLAG.Clear();
const s64 result = s64(m_regs.ZSF3) * s32(u32(m_regs.SZ1) + u32(m_regs.SZ2) + u32(m_regs.SZ3));
TruncateAndSetMAC<0>(result, 0);
SetOTZ(s32(result >> 12));
m_regs.FLAG.UpdateError();
}
void Core::Execute_AVSZ4(Instruction inst)
{
m_regs.FLAG.Clear();
const s64 result = s64(m_regs.ZSF4) * s32(u32(m_regs.SZ0) + u32(m_regs.SZ1) + u32(m_regs.SZ2) + u32(m_regs.SZ3));
TruncateAndSetMAC<0>(result, 0);
SetOTZ(s32(result >> 12));
m_regs.FLAG.UpdateError();
}
void Core::MulMatVec(const s16 M[3][3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm)
{
#define dot3(i) \
TruncateAndSetMACAndIR<i + 1>(SignExtendMACResult<i + 1>((s64(M[i][0]) * s64(Vx)) + (s64(M[i][1]) * s64(Vy))) + \
(s64(M[i][2]) * s64(Vz)), \
shift, lm)
dot3(0);
dot3(1);
dot3(2);
#undef dot3
}
void Core::MulMatVec(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm)
{
#define dot3(i) \
TruncateAndSetMACAndIR<i + 1>( \
SignExtendMACResult<i + 1>(SignExtendMACResult<i + 1>((s64(T[i]) << 12) + (s64(M[i][0]) * s64(Vx))) + \
(s64(M[i][1]) * s64(Vy))) + \
(s64(M[i][2]) * s64(Vz)), \
shift, lm)
dot3(0);
dot3(1);
dot3(2);
#undef dot3
}
void Core::NCCS(const s16 V[3], u8 shift, bool lm)
{
// [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12)
MulMatVec(m_regs.LLM, V[0], V[1], V[2], shift, lm);
// [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12)
MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm);
// [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx
// [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) ;<--- for NCDx/NCCx
TruncateAndSetMACAndIR<1>(s64(s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4, shift, lm);
TruncateAndSetMACAndIR<2>(s64(s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4, shift, lm);
TruncateAndSetMACAndIR<3>(s64(s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4, shift, lm);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGBFromMAC();
}
void Core::Execute_NCCS(Instruction inst)
{
m_regs.FLAG.Clear();
NCCS(m_regs.V0, inst.GetShift(), inst.lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_NCCT(Instruction inst)
{
m_regs.FLAG.Clear();
const u8 shift = inst.GetShift();
const bool lm = inst.lm;
NCCS(m_regs.V0, shift, lm);
NCCS(m_regs.V1, shift, lm);
NCCS(m_regs.V2, shift, lm);
m_regs.FLAG.UpdateError();
}
void Core::NCDS(const s16 V[3], bool sf, bool lm)
{
const u8 shift = sf ? 12 : 0;
// [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12)
MulMatVec(m_regs.LLM, V[0], V[1], V[2], shift, lm);
// [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12)
MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm);
// [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx
TruncateAndSetMAC<1>((s64(ZeroExtend64(m_regs.RGBC[0])) << 4) * s64(m_regs.MAC1), 0);
TruncateAndSetMAC<2>((s64(ZeroExtend64(m_regs.RGBC[1])) << 4) * s64(m_regs.MAC2), 0);
TruncateAndSetMAC<3>((s64(ZeroExtend64(m_regs.RGBC[2])) << 4) * s64(m_regs.MAC3), 0);
// [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 ;<--- for NCDx only
// [IR1,IR2,IR3] = (([RFC,GFC,BFC] SHL 12) - [MAC1,MAC2,MAC3]) SAR (sf*12)
TruncateAndSetIR<1>(s32((s64(m_regs.FC[0]) << 12) - s64(m_regs.MAC1)) >> shift, false);
TruncateAndSetIR<2>(s32((s64(m_regs.FC[1]) << 12) - s64(m_regs.MAC2)) >> shift, false);
TruncateAndSetIR<3>(s32((s64(m_regs.FC[2]) << 12) - s64(m_regs.MAC3)) >> shift, false);
// [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3])
// [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) ;<--- for NCDx/NCCx
TruncateAndSetMAC<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + s64(m_regs.MAC1), shift);
TruncateAndSetMAC<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + s64(m_regs.MAC2), shift);
TruncateAndSetMAC<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + s64(m_regs.MAC3), shift);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGB(TruncateRGB<0>(m_regs.MAC1 / 16), TruncateRGB<1>(m_regs.MAC2 / 16), TruncateRGB<2>(m_regs.MAC3 / 16),
m_regs.RGBC[3]);
TruncateAndSetIR<1>(m_regs.MAC1, lm);
TruncateAndSetIR<2>(m_regs.MAC2, lm);
TruncateAndSetIR<3>(m_regs.MAC3, lm);
}
void Core::Execute_NCDS(Instruction inst)
{
m_regs.FLAG.Clear();
NCDS(m_regs.V0, inst.sf, inst.lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_NCDT(Instruction inst)
{
m_regs.FLAG.Clear();
NCDS(m_regs.V0, inst.sf, inst.lm);
NCDS(m_regs.V1, inst.sf, inst.lm);
NCDS(m_regs.V2, inst.sf, inst.lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_MVMVA(Instruction inst)
{
m_regs.FLAG.Clear();
// TODO: Remove memcpy..
s16 M[3][3];
switch (inst.mvmva_multiply_matrix)
{
case 0:
std::memcpy(M, m_regs.RT, sizeof(s16) * 3 * 3);
break;
case 1:
std::memcpy(M, m_regs.LLM, sizeof(s16) * 3 * 3);
break;
case 2:
std::memcpy(M, m_regs.LCM, sizeof(s16) * 3 * 3);
break;
default:
// buggy
Panic("Missing implementation");
return;
}
s16 Vx, Vy, Vz;
switch (inst.mvmva_multiply_vector)
{
case 0:
Vx = m_regs.V0[0];
Vy = m_regs.V0[1];
Vz = m_regs.V0[2];
break;
case 1:
Vx = m_regs.V1[0];
Vy = m_regs.V1[1];
Vz = m_regs.V1[2];
break;
case 2:
Vx = m_regs.V2[0];
Vy = m_regs.V2[1];
Vz = m_regs.V2[2];
break;
default:
Vx = m_regs.IR1;
Vy = m_regs.IR2;
Vz = m_regs.IR3;
break;
}
s32 T[3];
switch (inst.mvmva_translation_vector)
{
case 0:
std::memcpy(T, m_regs.TR, sizeof(T));
break;
case 1:
std::memcpy(T, m_regs.BK, sizeof(T));
break;
case 2:
// buggy
std::memcpy(T, m_regs.FC, sizeof(T));
break;
case 3:
std::fill_n(T, countof(T), s32(0));
break;
default:
Panic("Missing implementation");
return;
}
MulMatVec(M, T, Vx, Vy, Vz, inst.GetShift(), inst.lm);
m_regs.FLAG.UpdateError();
}
void Core::DPCS(const u8 color[3], bool sf, bool lm)
{
const u8 shift = sf ? 12 : 0;
// In: [IR1,IR2,IR3]=Vector, FC=Far Color, IR0=Interpolation value, CODE=MSB of RGBC
// [MAC1,MAC2,MAC3] = [R,G,B] SHL 16 ;<--- for DPCS/DPCT
TruncateAndSetMAC<1>((s64(ZeroExtend64(color[0])) << 16), 0);
TruncateAndSetMAC<2>((s64(ZeroExtend64(color[1])) << 16), 0);
TruncateAndSetMAC<3>((s64(ZeroExtend64(color[2])) << 16), 0);
// [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0
// [IR1,IR2,IR3] = (([RFC,GFC,BFC] SHL 12) - [MAC1,MAC2,MAC3]) SAR (sf*12)
TruncateAndSetIR<1>(s32((s64(m_regs.FC[0]) << 12) - s64(m_regs.MAC1)) >> shift, false);
TruncateAndSetIR<2>(s32((s64(m_regs.FC[1]) << 12) - s64(m_regs.MAC2)) >> shift, false);
TruncateAndSetIR<3>(s32((s64(m_regs.FC[2]) << 12) - s64(m_regs.MAC3)) >> shift, false);
// [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3])
// [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12)
TruncateAndSetMAC<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + s64(m_regs.MAC1), shift);
TruncateAndSetMAC<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + s64(m_regs.MAC2), shift);
TruncateAndSetMAC<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + s64(m_regs.MAC3), shift);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGB(TruncateRGB<0>(m_regs.MAC1 / 16), TruncateRGB<1>(m_regs.MAC2 / 16), TruncateRGB<2>(m_regs.MAC3 / 16),
m_regs.RGBC[3]);
TruncateAndSetIR<1>(m_regs.MAC1, lm);
TruncateAndSetIR<2>(m_regs.MAC2, lm);
TruncateAndSetIR<3>(m_regs.MAC3, lm);
}
void Core::Execute_DPCS(Instruction inst)
{
m_regs.FLAG.Clear();
DPCS(m_regs.RGBC, inst.sf, inst.lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_DPCT(Instruction inst)
{
m_regs.FLAG.Clear();
const bool sf = inst.sf;
const bool lm = inst.lm;
for (u32 i = 0; i < 3; i++)
DPCS(m_regs.RGB0, sf, lm);
m_regs.FLAG.UpdateError();
}
void Core::Execute_DPCL(Instruction inst)
{
m_regs.FLAG.Clear();
const u8 shift = inst.GetShift();
const bool lm = inst.lm;
// In: [IR1,IR2,IR3]=Vector, FC=Far Color, IR0=Interpolation value, CODE=MSB of RGBC
// [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for DCPL only
TruncateAndSetMAC<1>(((s64(ZeroExtend64(m_regs.RGBC[0])) + s64(m_regs.IR1)) << 4), 0);
TruncateAndSetMAC<2>(((s64(ZeroExtend64(m_regs.RGBC[1])) + s64(m_regs.IR2)) << 4), 0);
TruncateAndSetMAC<3>(((s64(ZeroExtend64(m_regs.RGBC[2])) + s64(m_regs.IR3)) << 4), 0);
// [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0
// [IR1,IR2,IR3] = (([RFC,GFC,BFC] SHL 12) - [MAC1,MAC2,MAC3]) SAR (sf*12)
TruncateAndSetIR<1>(s32((s64(m_regs.FC[0]) << 12) - s64(m_regs.MAC1)) >> shift, false);
TruncateAndSetIR<2>(s32((s64(m_regs.FC[1]) << 12) - s64(m_regs.MAC2)) >> shift, false);
TruncateAndSetIR<3>(s32((s64(m_regs.FC[2]) << 12) - s64(m_regs.MAC3)) >> shift, false);
// [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3])
// [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12)
TruncateAndSetMACAndIR<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + s64(m_regs.MAC1), shift, lm);
TruncateAndSetMACAndIR<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + s64(m_regs.MAC2), shift, lm);
TruncateAndSetMACAndIR<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + s64(m_regs.MAC3), shift, lm);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGBFromMAC();
m_regs.FLAG.UpdateError();
}
void Core::Execute_GPL(Instruction inst)
{
m_regs.FLAG.Clear();
const u8 shift = inst.GetShift();
const bool lm = inst.lm;
// [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SHL (sf*12) ;<--- for GPL only
// [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12)
TruncateAndSetMACAndIR<1>((s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + (s64(m_regs.MAC1) << shift)), shift, lm);
TruncateAndSetMACAndIR<2>((s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + (s64(m_regs.MAC2) << shift)), shift, lm);
TruncateAndSetMACAndIR<3>((s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + (s64(m_regs.MAC3) << shift)), shift, lm);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGBFromMAC();
m_regs.FLAG.UpdateError();
}
void Core::Execute_GPF(Instruction inst)
{
m_regs.FLAG.Clear();
const u8 shift = inst.GetShift();
const bool lm = inst.lm;
// [MAC1,MAC2,MAC3] = [0,0,0] ;<--- for GPF only
// [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12)
TruncateAndSetMACAndIR<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)), shift, lm);
TruncateAndSetMACAndIR<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)), shift, lm);
TruncateAndSetMACAndIR<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)), shift, lm);
// Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
PushRGBFromMAC();
m_regs.FLAG.UpdateError();
}
} // namespace GTE

97
src/core/gte.h Normal file
View File

@ -0,0 +1,97 @@
#pragma once
#include "common/state_wrapper.h"
#include "gte_types.h"
namespace GTE {
class Core
{
public:
Core();
~Core();
void Initialize();
void Reset();
bool DoState(StateWrapper& sw);
u32 ReadRegister(u32 index) const { return m_regs.dr32[index]; }
void WriteRegister(u32 index, u32 value) { m_regs.dr32[index] = value; }
u32 ReadDataRegister(u32 index) const;
void WriteDataRegister(u32 index, u32 value);
u32 ReadControlRegister(u32 index) const;
void WriteControlRegister(u32 index, u32 value);
void ExecuteInstruction(Instruction inst);
private:
static constexpr s64 MAC0_MIN_VALUE = -(INT64_C(1) << 31);
static constexpr s64 MAC0_MAX_VALUE = (INT64_C(1) << 31) - 1;
static constexpr s64 MAC123_MIN_VALUE = -(INT64_C(1) << 43);
static constexpr s64 MAC123_MAX_VALUE = (INT64_C(1) << 43) - 1;
static constexpr s32 IR0_MIN_VALUE = 0x0000;
static constexpr s32 IR0_MAX_VALUE = 0x1000;
static constexpr s32 IR123_MIN_VALUE = -(INT64_C(1) << 15);
static constexpr s32 IR123_MAX_VALUE = (INT64_C(1) << 15) - 1;
// Checks for underflow/overflow.
template<u32 index>
void CheckMACOverflow(s64 value);
// Checks for underflow/overflow, sign-extending to 31/43 bits.
template<u32 index>
s64 SignExtendMACResult(s64 value);
template<u32 index>
void TruncateAndSetMAC(s64 value, u8 shift);
template<u32 index>
void TruncateAndSetMACAndIR(s64 value, u8 shift, bool lm);
template<u32 index>
void TruncateAndSetIR(s32 value, bool lm);
template<u32 index>
u8 TruncateRGB(s32 value);
void SetOTZ(s32 value);
void PushSXY(s32 x, s32 y);
void PushSZ(s32 value);
void PushRGB(u8 r, u8 g, u8 b, u8 c);
void PushRGBFromMAC();
// 3x3 matrix * 3x1 vector, updates MAC[1-3] and IR[1-3]
void MulMatVec(const s16 M[3][3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm);
// 3x3 matrix * 3x1 vector with translation, updates MAC[1-3] and IR[1-3]
void MulMatVec(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm);
void RTPS(const s16 V[3], bool sf, bool lm, bool last);
void NCCS(const s16 V[3], u8 shift, bool lm);
void NCDS(const s16 V[3], bool sf, bool lm);
void DPCS(const u8 color[3], bool sf, bool lm);
void Execute_RTPS(Instruction inst);
void Execute_RTPT(Instruction inst);
void Execute_NCLIP(Instruction inst);
void Execute_SQR(Instruction inst);
void Execute_AVSZ3(Instruction inst);
void Execute_AVSZ4(Instruction inst);
void Execute_NCCS(Instruction inst);
void Execute_NCCT(Instruction inst);
void Execute_NCDS(Instruction inst);
void Execute_NCDT(Instruction inst);
void Execute_MVMVA(Instruction inst);
void Execute_DPCS(Instruction inst);
void Execute_DPCT(Instruction inst);
void Execute_DPCL(Instruction inst);
void Execute_GPL(Instruction inst);
void Execute_GPF(Instruction inst);
Regs m_regs = {};
};
#include "gte.inl"
} // namespace GTE

117
src/core/gte.inl Normal file
View File

@ -0,0 +1,117 @@
#include "gte.h"
template<u32 index>
void GTE::Core::CheckMACOverflow(s64 value)
{
constexpr s64 MIN_VALUE = (index == 0) ? MAC0_MIN_VALUE : MAC123_MIN_VALUE;
constexpr s64 MAX_VALUE = (index == 0) ? MAC0_MAX_VALUE : MAC123_MAX_VALUE;
if (value < MIN_VALUE)
{
if constexpr (index == 0)
m_regs.FLAG.mac0_underflow = true;
else if constexpr (index == 1)
m_regs.FLAG.mac1_underflow = true;
else if constexpr (index == 2)
m_regs.FLAG.mac2_underflow = true;
else if constexpr (index == 3)
m_regs.FLAG.mac3_underflow = true;
}
else if (value > MAX_VALUE)
{
if constexpr (index == 0)
m_regs.FLAG.mac0_overflow = true;
else if constexpr (index == 1)
m_regs.FLAG.mac1_overflow = true;
else if constexpr (index == 2)
m_regs.FLAG.mac2_overflow = true;
else if constexpr (index == 3)
m_regs.FLAG.mac3_overflow = true;
}
}
template<u32 index>
s64 GTE::Core::SignExtendMACResult(s64 value)
{
CheckMACOverflow<index>(value);
return SignExtendN < index == 0 ? 31 : 44 > (value);
}
template<u32 index>
void GTE::Core::TruncateAndSetMAC(s64 value, u8 shift)
{
CheckMACOverflow<index>(value);
// shift should be done before storing to avoid losing precision
value >>= shift;
m_regs.dr32[24 + index] = Truncate32(static_cast<u64>(value));
}
template<u32 index>
void GTE::Core::TruncateAndSetIR(s32 value, bool lm)
{
constexpr s32 MIN_VALUE = (index == 0) ? IR0_MIN_VALUE : IR123_MIN_VALUE;
constexpr s32 MAX_VALUE = (index == 0) ? IR0_MAX_VALUE : IR123_MAX_VALUE;
const s32 actual_min_value = lm ? 0 : MIN_VALUE;
if (value < actual_min_value)
{
value = actual_min_value;
if constexpr (index == 0)
m_regs.FLAG.ir0_saturated = true;
else if constexpr (index == 1)
m_regs.FLAG.ir1_saturated = true;
else if constexpr (index == 2)
m_regs.FLAG.ir2_saturated = true;
else if constexpr (index == 3)
m_regs.FLAG.ir3_saturated = true;
}
else if (value > MAX_VALUE)
{
value = MAX_VALUE;
if constexpr (index == 0)
m_regs.FLAG.ir0_saturated = true;
else if constexpr (index == 1)
m_regs.FLAG.ir1_saturated = true;
else if constexpr (index == 2)
m_regs.FLAG.ir2_saturated = true;
else if constexpr (index == 3)
m_regs.FLAG.ir3_saturated = true;
}
// store sign-extended 16-bit value as 32-bit
m_regs.dr32[8 + index] = value;
}
template<u32 index>
void GTE::Core::TruncateAndSetMACAndIR(s64 value, u8 shift, bool lm)
{
CheckMACOverflow<index>(value);
// shift should be done before storing to avoid losing precision
value >>= shift;
// set MAC
const s32 value32 = static_cast<s32>(value);
m_regs.dr32[24 + index] = value32;
// set IR
TruncateAndSetIR<index>(value32, lm);
}
template<u32 index>
u8 GTE::Core::TruncateRGB(s32 value)
{
if (value < 0 || value > 0xFF)
{
if constexpr (index == 0)
m_regs.FLAG.color_r_saturated = true;
else if constexpr (index == 1)
m_regs.FLAG.color_g_saturated = true;
else
m_regs.FLAG.color_b_saturated = true;
value = (value < 0) ? 0 : 0xFF;
}
return static_cast<u8>(value);
}

142
src/core/gte_types.h Normal file
View File

@ -0,0 +1,142 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
namespace GTE {
static constexpr u32 NUM_DATA_REGS = 32;
static constexpr u32 NUM_CONTROL_REGS = 32;
static constexpr u32 NUM_REGS = NUM_DATA_REGS + NUM_CONTROL_REGS;
union FLAGS
{
u32 bits;
BitField<u32, bool, 31, 1> error;
BitField<u32, bool, 30, 1> mac1_overflow;
BitField<u32, bool, 29, 1> mac2_overflow;
BitField<u32, bool, 28, 1> mac3_overflow;
BitField<u32, bool, 27, 1> mac1_underflow;
BitField<u32, bool, 26, 1> mac2_underflow;
BitField<u32, bool, 25, 1> mac3_underflow;
BitField<u32, bool, 24, 1> ir1_saturated;
BitField<u32, bool, 23, 1> ir2_saturated;
BitField<u32, bool, 22, 1> ir3_saturated;
BitField<u32, bool, 21, 1> color_r_saturated;
BitField<u32, bool, 20, 1> color_g_saturated;
BitField<u32, bool, 19, 1> color_b_saturated;
BitField<u32, bool, 18, 1> sz1_otz_saturated;
BitField<u32, bool, 17, 1> divide_overflow;
BitField<u32, bool, 16, 1> mac0_overflow;
BitField<u32, bool, 15, 1> mac0_underflow;
BitField<u32, bool, 14, 1> sx2_saturated;
BitField<u32, bool, 13, 1> sy2_saturated;
BitField<u32, bool, 12, 1> ir0_saturated;
static constexpr u32 WRITE_MASK = UINT32_C(0xFFFFF000);
void SetMACOverflow(u32 index) { bits |= (index == 0) ? (UINT32_C(1) << 16) : (UINT32_C(1) << (31 - index)); }
void SetMACUnderflow(u32 index) { bits |= (index == 0) ? (UINT32_C(1) << 15) : (UINT32_C(1) << (27 - index)); }
void SetIRSaturated(u32 index) { bits |= (index == 0) ? (UINT32_C(1) << 12) : (UINT32_C(1) << (25 - index)); }
void Clear() { bits = 0; }
// Bits 30..23, 18..13 OR'ed
void UpdateError() { error = (bits & UINT32_C(0x7F87E000)) != UINT32_C(0); }
};
union Regs
{
struct
{
u32 dr32[NUM_DATA_REGS];
u32 cr32[NUM_CONTROL_REGS];
};
#pragma pack(push, 1)
struct
{
s16 V0[3]; // 0-1
u16 pad1; // 1
s16 V1[3]; // 2-3
u16 pad2; // 3
s16 V2[3]; // 4-5
u16 pad3; // 5
u8 RGBC[4]; // 6
u16 OTZ; // 7
u16 pad4; // 7
s16 IR0; // 8
u16 pad5; // 8
s16 IR1; // 9
u16 pad6; // 9
s16 IR2; // 10
u16 pad7; // 10
s16 IR3; // 11
u16 pad8; // 11
s16 SXY0[2]; // 12
s16 SXY1[2]; // 13
s16 SXY2[2]; // 14
s16 SXYP[2]; // 15
u16 SZ0; // 16
u16 pad13; // 16
u16 SZ1; // 17
u16 pad14; // 17
u16 SZ2; // 18
u16 pad15; // 18
u16 SZ3; // 19
u16 pad16; // 19
u8 RGB0[4]; // 20
u8 RGB1[4]; // 21
u8 RGB2[4]; // 22
u32 RES1; // 23
s32 MAC0; // 24
s32 MAC1; // 25
s32 MAC2; // 26
s32 MAC3; // 27
u32 IRGB; // 28
u32 ORGB; // 29
s32 LZCS; // 30
u32 LZCR; // 31
s16 RT[3][3]; // 32-36
u16 pad17; // 36
s32 TR[3]; // 37-39
s16 LLM[3][3]; // 40-44
u16 pad18; // 44
s32 BK[3]; // 45-47
s16 LCM[3][3]; // 48-52
u16 pad19; // 52
s32 FC[3]; // 53-55
s32 OFX; // 56
s32 OFY; // 57
u16 H; // 58
u16 pad20; // 58
s16 DQA; // 59
u16 pad21; // 59
s32 DQB; // 60
s16 ZSF3; // 61
u16 pad22; // 61
s16 ZSF4; // 62
u16 pad23; // 62
FLAGS FLAG; // 63
};
#pragma pack(pop)
};
static_assert(sizeof(Regs) == (sizeof(u32) * NUM_REGS));
union Instruction
{
u32 bits;
BitField<u32, u8, 20, 5> fake_command;
BitField<u32, u8, 19, 1> sf; // shift fraction in IR registers, 0=no fraction, 1=12bit fraction
BitField<u32, u8, 17, 2> mvmva_multiply_matrix;
BitField<u32, u8, 15, 2> mvmva_multiply_vector;
BitField<u32, u8, 13, 2> mvmva_translation_vector;
BitField<u32, bool, 10, 1> lm; // saturate IR1, IR2, IR3 result
BitField<u32, u8, 0, 6> command;
u8 GetShift() const { return sf ? 12 : 0; }
};
} // namespace GTE

View File

@ -0,0 +1,97 @@
#include "host_interface.h"
#include "YBaseLib/ByteStream.h"
#include "YBaseLib/Log.h"
#include "system.h"
Log_SetChannel(HostInterface);
HostInterface::HostInterface() = default;
HostInterface::~HostInterface() = default;
bool HostInterface::InitializeSystem(const char* filename, const char* exp1_filename)
{
m_system = std::make_unique<System>(this);
if (!m_system->Initialize())
{
m_system.reset();
return false;
}
m_system->Reset();
if (filename)
{
const StaticString filename_str(filename);
if (filename_str.EndsWith(".psexe", false) || filename_str.EndsWith(".exe", false))
{
Log_InfoPrintf("Sideloading EXE file '%s'", filename);
if (!m_system->LoadEXE(filename))
{
Log_ErrorPrintf("Failed to load EXE file '%s'", filename);
return false;
}
}
else
{
Log_InfoPrintf("Inserting CDROM from image file '%s'", filename);
if (!m_system->InsertMedia(filename))
{
Log_ErrorPrintf("Failed to insert media '%s'", filename);
return false;
}
}
}
if (exp1_filename)
m_system->SetExpansionROM(exp1_filename);
// Resume execution.
m_running = true;
return true;
}
bool HostInterface::LoadState(const char* filename)
{
ByteStream* stream;
if (!ByteStream_OpenFileStream(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED, &stream))
return false;
ReportMessage(SmallString::FromFormat("Loading state from %s...", filename));
const bool result = m_system->LoadState(stream);
if (!result)
{
ReportMessage(SmallString::FromFormat("Loading state from %s failed. Resetting.", filename));
m_system->Reset();
}
stream->Release();
return result;
}
bool HostInterface::SaveState(const char* filename)
{
ByteStream* stream;
if (!ByteStream_OpenFileStream(filename,
BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED,
&stream))
{
return false;
}
const bool result = m_system->SaveState(stream);
if (!result)
{
ReportMessage(SmallString::FromFormat("Saving state to %s failed.", filename));
stream->Discard();
}
else
{
ReportMessage(SmallString::FromFormat("State saved to %s.", filename));
stream->Commit();
}
stream->Release();
return result;
}

31
src/core/host_interface.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "types.h"
#include <memory>
namespace GL {
class Texture;
}
class System;
class HostInterface
{
public:
HostInterface();
virtual ~HostInterface();
bool InitializeSystem(const char* filename, const char* exp1_filename);
virtual void SetDisplayTexture(GL::Texture* texture, u32 offset_x, u32 offset_y, u32 width, u32 height, float aspect_ratio) = 0;
virtual void ReportMessage(const char* message) = 0;
// Adds OSD messages, duration is in seconds.
virtual void AddOSDMessage(const char* message, float duration = 2.0f) = 0;
bool LoadState(const char* filename);
bool SaveState(const char* filename);
protected:
std::unique_ptr<System> m_system;
bool m_running = false;
};

View File

@ -0,0 +1,88 @@
#include "interrupt_controller.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
Log_SetChannel(InterruptController);
InterruptController::InterruptController() = default;
InterruptController::~InterruptController() = default;
bool InterruptController::Initialize(CPU::Core* cpu)
{
m_cpu = cpu;
return true;
}
void InterruptController::Reset()
{
m_interrupt_status_register = 0;
m_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
}
bool InterruptController::DoState(StateWrapper& sw)
{
sw.Do(&m_interrupt_status_register);
sw.Do(&m_interrupt_mask_register);
return !sw.HasError();
}
void InterruptController::InterruptRequest(IRQ irq)
{
const u32 bit = (u32(1) << static_cast<u32>(irq));
m_interrupt_status_register |= bit;
UpdateCPUInterruptRequest();
}
u32 InterruptController::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00: // I_STATUS
return m_interrupt_status_register;
case 0x04: // I_MASK
return m_interrupt_mask_register;
default:
Log_ErrorPrintf("Invalid read at offset 0x%08X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void InterruptController::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0x00: // I_STATUS
{
if ((m_interrupt_status_register & ~value) != 0)
Log_DebugPrintf("Clearing bits 0x%08X", (m_interrupt_status_register & ~value));
m_interrupt_status_register = m_interrupt_status_register & (value & REGISTER_WRITE_MASK);
UpdateCPUInterruptRequest();
}
break;
case 0x04: // I_MASK
{
Log_DebugPrintf("Interrupt mask <- 0x%08X", value);
m_interrupt_mask_register = value & REGISTER_WRITE_MASK;
}
break;
default:
Log_ErrorPrintf("Invalid write at offset 0x%08X", offset);
break;
}
}
void InterruptController::UpdateCPUInterruptRequest()
{
// external interrupts set bit 10 only?
if ((m_interrupt_status_register & m_interrupt_mask_register) != 0)
m_cpu->SetExternalInterrupt(2);
else
m_cpu->ClearExternalInterrupt(2);
}

View File

@ -0,0 +1,60 @@
#pragma once
#include "types.h"
class StateWrapper;
namespace CPU
{
class Core;
}
class InterruptController
{
public:
static constexpr u32 NUM_IRQS = 11;
enum class IRQ : u32
{
VBLANK = 0, // IRQ0 - VBLANK
GPU = 1, // IRQ1 - GPU via GP0(1Fh)
CDROM = 2, // IRQ2 - CDROM
DMA = 3, // IRQ3 - DMA
TMR0 = 4, // IRQ4 - TMR0 - Sysclk or Dotclk
TMR1 = 5, // IRQ5 - TMR1 - Sysclk Hblank
TMR2 = 6, // IRQ6 - TMR2 - Sysclk or Sysclk / 8
IRQ7 = 7, // IRQ7 - Controller and Memory Card Byte Received
SIO = 8, // IRQ8 - SIO
SPU = 9, // IRQ9 - SPU
IRQ10 = 10 // IRQ10 - Lightpen interrupt, PIO
};
InterruptController();
~InterruptController();
bool Initialize(CPU::Core* cpu);
void Reset();
bool DoState(StateWrapper& sw);
// Should mirror CPU state.
bool GetIRQLineState() const { return (m_interrupt_status_register != 0); }
// Interupts are edge-triggered, so if it is masked when TriggerInterrupt() is called, it will be lost.
void InterruptRequest(IRQ irq);
// I/O
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
private:
static constexpr u32 REGISTER_WRITE_MASK = (u32(1) << NUM_IRQS) - 1;
static constexpr u32 DEFAULT_INTERRUPT_MASK = 0; //(u32(1) << NUM_IRQS) - 1;
void UpdateCPUInterruptRequest();
CPU::Core* m_cpu;
u32 m_interrupt_status_register = 0;
u32 m_interrupt_mask_register = DEFAULT_INTERRUPT_MASK;
};

536
src/core/mdec.cpp Normal file
View File

@ -0,0 +1,536 @@
#include "mdec.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "dma.h"
#include "interrupt_controller.h"
#include "system.h"
Log_SetChannel(MDEC);
MDEC::MDEC() = default;
MDEC::~MDEC() = default;
bool MDEC::Initialize(System* system, DMA* dma)
{
m_system = system;
m_dma = dma;
return true;
}
void MDEC::Reset()
{
SoftReset();
}
bool MDEC::DoState(StateWrapper& sw)
{
sw.Do(&m_status.bits);
sw.Do(&m_data_in_fifo);
sw.Do(&m_data_out_fifo);
sw.Do(&m_command);
sw.Do(&m_command_parameter_count);
sw.Do(&m_iq_uv);
sw.Do(&m_iq_y);
sw.Do(&m_scale_table);
return !sw.HasError();
}
u32 MDEC::ReadRegister(u32 offset)
{
switch (offset)
{
case 0:
return ReadDataRegister();
case 4:
{
Log_DebugPrintf("MDEC status register -> 0x%08X", m_status.bits);
return m_status.bits;
}
default:
{
Log_ErrorPrintf("Unknown MDEC register read: 0x%08X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
}
void MDEC::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0:
{
WriteCommandRegister(value);
return;
}
case 4:
{
Log_DebugPrintf("MDEC control register <- 0x%08X", value);
const ControlRegister cr{value};
if (cr.reset)
SoftReset();
m_status.data_in_request = cr.enable_dma_in;
m_status.data_out_request = cr.enable_dma_out;
m_dma->SetRequest(DMA::Channel::MDECin, cr.enable_dma_in);
m_dma->SetRequest(DMA::Channel::MDECout, cr.enable_dma_out);
return;
}
default:
{
Log_ErrorPrintf("Unknown MDEC register write: 0x%08X <- 0x%08X", offset, value);
return;
}
}
}
u32 MDEC::DMARead()
{
return ReadDataRegister();
}
void MDEC::DMAWrite(u32 value)
{
WriteCommandRegister(value);
}
void MDEC::SoftReset()
{
m_status = {};
m_data_in_fifo.Clear();
m_data_out_fifo.Clear();
UpdateStatusRegister();
}
void MDEC::UpdateStatusRegister()
{
m_status.data_out_fifo_empty = m_data_out_fifo.IsEmpty();
m_status.data_in_fifo_full = m_data_in_fifo.IsFull();
m_status.command_busy = !m_data_in_fifo.IsEmpty();
if (!m_data_in_fifo.IsEmpty())
{
const CommandWord cw{m_data_in_fifo.Peek(0)};
m_status.parameter_words_remaining = Truncate16(m_command_parameter_count - m_data_in_fifo.GetSize());
}
else
{
m_status.parameter_words_remaining = 0;
}
}
u32 MDEC::ReadDataRegister()
{
if (m_data_out_fifo.IsEmpty())
{
// Log_WarningPrintf("MDEC data out FIFO empty on read");
return UINT32_C(0xFFFFFFFF);
}
const u32 value = m_data_out_fifo.Pop();
UpdateStatusRegister();
return value;
}
void MDEC::WriteCommandRegister(u32 value)
{
Log_DebugPrintf("MDEC command/data register <- 0x%08X", value);
if (m_data_in_fifo.IsEmpty())
{
// first word
const CommandWord cw{value};
m_command = cw.command;
m_status.data_output_depth = cw.data_output_depth;
m_status.data_output_signed = cw.data_output_signed;
m_status.data_output_bit15 = cw.data_output_bit15;
switch (cw.command)
{
case Command::DecodeMacroblock:
m_command_parameter_count = ZeroExtend32(cw.parameter_word_count.GetValue());
break;
case Command::SetIqTab:
m_command_parameter_count = 16 + (((value & 1) != 0) ? 16 : 0);
break;
case Command::SetScale:
m_command_parameter_count = 32;
break;
default:
Panic("Unknown command");
break;
}
Log_DebugPrintf("MDEC command: 0x%08X (%u, %u words in parameter, %u expected)", cw.bits,
ZeroExtend32(static_cast<u8>(cw.command.GetValue())),
ZeroExtend32(cw.parameter_word_count.GetValue()), m_command_parameter_count);
}
m_data_in_fifo.Push(value);
if (m_data_in_fifo.GetSize() <= m_command_parameter_count)
{
UpdateStatusRegister();
return;
}
// pop command
m_data_in_fifo.RemoveOne();
switch (m_command)
{
case Command::DecodeMacroblock:
HandleDecodeMacroblockCommand();
break;
case Command::SetIqTab:
HandleSetQuantTableCommand();
break;
case Command::SetScale:
HandleSetScaleCommand();
break;
}
m_data_in_fifo.Clear();
m_command = Command::None;
m_command_parameter_count = 0;
UpdateStatusRegister();
}
void MDEC::HandleDecodeMacroblockCommand()
{
// TODO: Remove this copy and strict aliasing violation..
std::vector<u16> temp(m_data_in_fifo.GetSize() * 2);
m_data_in_fifo.PopRange(reinterpret_cast<u32*>(temp.data()), m_data_in_fifo.GetSize());
const u16* src = temp.data();
const u16* src_end = src + temp.size();
if (m_status.data_output_depth <= DataOutputDepth_8Bit)
{
while (src != src_end)
{
src = DecodeMonoMacroblock(src, src_end);
Log_DevPrintf("Decoded mono macroblock");
}
}
else
{
while (src != src_end)
{
u32 old_offs = static_cast<u32>(src - temp.data());
src = DecodeColoredMacroblock(src, src_end);
Log_DevPrintf("Decoded colour macroblock, ptr was %u, now %u", old_offs, static_cast<u32>(src - temp.data()));
}
}
}
const u16* MDEC::DecodeMonoMacroblock(const u16* src, const u16* src_end)
{
std::array<s16, 64> Yblk;
if (!rl_decode_block(Yblk.data(), src, src_end, m_iq_y.data()))
return src_end;
std::array<u8, 64> out_r;
y_to_mono(Yblk, out_r);
switch (m_status.data_output_depth)
{
case DataOutputDepth_4Bit:
{
const u8* in_ptr = out_r.data();
for (u32 i = 0; i < (64 / 8); i++)
{
u32 value = ZeroExtend32(*(in_ptr++) >> 4);
value |= ZeroExtend32(*(in_ptr++) >> 4) << 4;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 8;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 12;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 16;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 20;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 24;
value |= ZeroExtend32(*(in_ptr++) >> 4) << 28;
m_data_out_fifo.Push(value);
}
}
break;
case DataOutputDepth_8Bit:
{
const u8* in_ptr = out_r.data();
for (u32 i = 0; i < (64 / 4); i++)
{
u32 value = ZeroExtend32(*in_ptr++);
value |= ZeroExtend32(*in_ptr++) << 8;
value |= ZeroExtend32(*in_ptr++) << 16;
value |= ZeroExtend32(*in_ptr++) << 24;
m_data_out_fifo.Push(value);
}
}
break;
default:
break;
}
return src;
}
const u16* MDEC::DecodeColoredMacroblock(const u16* src, const u16* src_end)
{
std::array<s16, 64> Crblk;
std::array<s16, 64> Cbblk;
std::array<std::array<s16, 64>, 4> Yblk;
std::array<u32, 256> out_rgb;
if (!rl_decode_block(Crblk.data(), src, src_end, m_iq_uv.data()) ||
!rl_decode_block(Cbblk.data(), src, src_end, m_iq_uv.data()) ||
!rl_decode_block(Yblk[0].data(), src, src_end, m_iq_y.data()) ||
!rl_decode_block(Yblk[1].data(), src, src_end, m_iq_y.data()) ||
!rl_decode_block(Yblk[2].data(), src, src_end, m_iq_y.data()) ||
!rl_decode_block(Yblk[3].data(), src, src_end, m_iq_y.data()))
{
return src_end;
}
yuv_to_rgb(0, 0, Crblk, Cbblk, Yblk[0], out_rgb);
yuv_to_rgb(0, 8, Crblk, Cbblk, Yblk[1], out_rgb);
yuv_to_rgb(8, 0, Crblk, Cbblk, Yblk[2], out_rgb);
yuv_to_rgb(8, 8, Crblk, Cbblk, Yblk[3], out_rgb);
switch (m_status.data_output_depth)
{
case DataOutputDepth_24Bit:
{
// pack tightly
u32 index = 0;
u32 state = 0;
u32 rgb = 0;
while (index < out_rgb.size())
{
switch (state)
{
case 0:
rgb = out_rgb[index++]; // RGB-
state = 1;
break;
case 1:
rgb |= (out_rgb[index] & 0xFF) << 24; // RGBR
m_data_out_fifo.Push(rgb);
rgb = out_rgb[index] >> 8; // GB--
index++;
state = 2;
break;
case 2:
rgb |= out_rgb[index] << 16; // GBRG
m_data_out_fifo.Push(rgb);
rgb = out_rgb[index] >> 16; // B---
index++;
state = 3;
break;
case 3:
rgb |= out_rgb[index] << 8; // BRGB
m_data_out_fifo.Push(rgb);
index++;
state = 0;
break;
}
}
break;
}
case DataOutputDepth_15Bit:
{
const u16 a = ZeroExtend16(m_status.data_output_bit15.GetValue());
for (u32 i = 0; i < static_cast<u32>(out_rgb.size());)
{
u32 color = out_rgb[i++];
u16 r = Truncate16((color >> 3) & 0x1Fu);
u16 g = Truncate16((color >> 11) & 0x1Fu);
u16 b = Truncate16((color >> 19) & 0x1Fu);
const u16 color15a = r | (g << 5) | (b << 10) | (a << 15);
color = out_rgb[i++];
r = Truncate16((color >> 3) & 0x1Fu);
g = Truncate16((color >> 11) & 0x1Fu);
b = Truncate16((color >> 19) & 0x1Fu);
const u16 color15b = r | (g << 5) | (b << 10) | (a << 15);
m_data_out_fifo.Push(ZeroExtend32(color15a) | (ZeroExtend32(color15b) << 16));
}
}
break;
default:
break;
}
return src;
}
static constexpr std::array<u8, 64> zigzag = {{0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63}};
bool MDEC::rl_decode_block(s16* blk, const u16*& src, const u16* src_end, const u8* qt)
{
std::fill_n(blk, 64, s16(0));
// skip padding
u16 n;
for (;;)
{
if (src == src_end)
return false;
n = *(src++);
if (n == 0xFE00)
continue;
else
break;
}
u32 k = 0;
u16 q_scale = (n >> 10) & 0x3F;
s32 val = SignExtendN<10, s32>(static_cast<s32>(n & 0x3FF)) * static_cast<s32>(ZeroExtend32(qt[k]));
for (;;)
{
if (q_scale == 0)
val = SignExtendN<10, s32>(static_cast<s32>(n & 0x3FF)) * 2;
val = std::clamp(val, -0x400, 0x3FF);
// val = val * static_cast<s32>(ZeroExtend32(scalezag[i]));
if (q_scale > 0)
blk[zigzag[k]] = static_cast<s16>(val);
else if (q_scale == 0)
blk[k] = static_cast<s16>(val);
if (src == src_end)
break;
n = *(src++);
k += ((n >> 10) & 0x3F) + 1;
if (k >= 64)
break;
val = (SignExtendN<10, s32>(static_cast<s32>(n & 0x3FF)) * static_cast<s32>(ZeroExtend32(qt[k])) *
static_cast<s32>(q_scale) +
4) /
8;
}
#undef READ_SRC
// insufficient coefficients
if (k < 64)
{
Log_DebugPrintf("Only %u of 64 coefficients in block, skipping", k);
return false;
}
IDCT(blk);
return true;
}
void MDEC::IDCT(s16* blk)
{
std::array<s64, 64> temp_buffer;
for (u32 x = 0; x < 8; x++)
{
for (u32 y = 0; y < 8; y++)
{
s64 sum = 0;
for (u32 u = 0; u < 8; u++)
sum += s32(blk[u * 8 + x]) * s32(m_scale_table[u * 8 + y]);
temp_buffer[x + y * 8] = sum;
}
}
for (u32 x = 0; x < 8; x++)
{
for (u32 y = 0; y < 8; y++)
{
s64 sum = 0;
for (u32 u = 0; u < 8; u++)
sum += s64(temp_buffer[u + y * 8]) * s32(m_scale_table[u * 8 + x]);
blk[x + y * 8] =
static_cast<s16>(std::clamp<s32>(SignExtendN<9, s32>((sum >> 32) + ((sum >> 31) & 1)), -128, 127));
}
}
}
void MDEC::yuv_to_rgb(u32 xx, u32 yy, const std::array<s16, 64>& Crblk, const std::array<s16, 64>& Cbblk,
const std::array<s16, 64>& Yblk, std::array<u32, 256>& rgb_out)
{
for (u32 y = 0; y < 8; y++)
{
for (u32 x = 0; x < 8; x++)
{
s16 R = Crblk[((x + xx) / 2) + ((y + yy) / 2) * 8];
s16 B = Cbblk[((x + xx) / 2) + ((y + yy) / 2) * 8];
s16 G = static_cast<s16>((-0.3437f * static_cast<float>(B)) + (-0.7143f * static_cast<float>(R)));
R = static_cast<s16>(1.402f * static_cast<float>(R));
B = static_cast<s16>(1.772f * static_cast<float>(B));
s16 Y = Yblk[x + y * 8];
R = static_cast<s16>(std::clamp(static_cast<int>(Y) + R, -128, 127));
G = static_cast<s16>(std::clamp(static_cast<int>(Y) + G, -128, 127));
B = static_cast<s16>(std::clamp(static_cast<int>(Y) + B, -128, 127));
R += 128;
G += 128;
B += 128;
rgb_out[(x + xx) + ((y + yy) * 16)] = ZeroExtend32(static_cast<u16>(R)) |
(ZeroExtend32(static_cast<u16>(G)) << 8) |
(ZeroExtend32(static_cast<u16>(B)) << 16) | UINT32_C(0xFF000000);
}
}
}
void MDEC::y_to_mono(const std::array<s16, 64>& Yblk, std::array<u8, 64>& r_out)
{
for (u32 i = 0; i < 64; i++)
{
s16 Y = Yblk[i];
Y = SignExtendN<10, s16>(Y);
Y = std::clamp<s16>(Y, -128, 127);
Y += 128;
r_out[i] = static_cast<u8>(Y);
}
}
void MDEC::HandleSetQuantTableCommand()
{
// TODO: Remove extra copies..
std::array<u32, 16> packed_data;
m_data_in_fifo.PopRange(packed_data.data(), static_cast<u32>(packed_data.size()));
std::memcpy(m_iq_y.data(), packed_data.data(), m_iq_y.size());
if (!m_data_in_fifo.IsEmpty())
{
m_data_in_fifo.PopRange(packed_data.data(), static_cast<u32>(packed_data.size()));
std::memcpy(m_iq_uv.data(), packed_data.data(), m_iq_uv.size());
}
}
void MDEC::HandleSetScaleCommand()
{
// TODO: Remove extra copies..
std::array<u32, 32> packed_data;
m_data_in_fifo.PopRange(packed_data.data(), static_cast<u32>(packed_data.size()));
std::memcpy(m_scale_table.data(), packed_data.data(), m_scale_table.size() * sizeof(s16));
}

118
src/core/mdec.h Normal file
View File

@ -0,0 +1,118 @@
#pragma once
#include "common/bitfield.h"
#include "common/fifo_queue.h"
#include "types.h"
#include <array>
class StateWrapper;
class System;
class DMA;
class MDEC
{
public:
MDEC();
~MDEC();
bool Initialize(System* system, DMA* dma);
void Reset();
bool DoState(StateWrapper& sw);
// I/O
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
u32 DMARead();
void DMAWrite(u32 value);
private:
static constexpr u32 DATA_IN_FIFO_SIZE = 1048576;
static constexpr u32 DATA_OUT_FIFO_SIZE = 1048576;
enum DataOutputDepth : u8
{
DataOutputDepth_4Bit = 0,
DataOutputDepth_8Bit = 1,
DataOutputDepth_24Bit = 2,
DataOutputDepth_15Bit = 3
};
enum class Command : u8
{
None = 0,
DecodeMacroblock = 1,
SetIqTab = 2,
SetScale = 3
};
union StatusRegister
{
u32 bits;
BitField<u32, bool, 31, 1> data_out_fifo_empty;
BitField<u32, bool, 30, 1> data_in_fifo_full;
BitField<u32, bool, 29, 1> command_busy;
BitField<u32, bool, 28, 1> data_in_request;
BitField<u32, bool, 27, 1> data_out_request;
BitField<u32, DataOutputDepth, 25, 2> data_output_depth;
BitField<u32, bool, 24, 1> data_output_signed;
BitField<u32, u8, 23, 1> data_output_bit15;
BitField<u32, u8, 16, 3> current_block;
BitField<u32, u16, 0, 16> parameter_words_remaining;
};
union ControlRegister
{
u32 bits;
BitField<u32, bool, 31, 1> reset;
BitField<u32, bool, 30, 1> enable_dma_in;
BitField<u32, bool, 29, 1> enable_dma_out;
};
union CommandWord
{
u32 bits;
BitField<u32, Command, 29, 3> command;
BitField<u32, DataOutputDepth, 27, 2> data_output_depth;
BitField<u32, bool, 26, 1> data_output_signed;
BitField<u32, u8, 25, 1> data_output_bit15;
BitField<u32, u16, 0, 16> parameter_word_count;
};
void SoftReset();
void UpdateStatusRegister();
u32 ReadDataRegister();
void WriteCommandRegister(u32 value);
void HandleDecodeMacroblockCommand();
void HandleSetQuantTableCommand();
void HandleSetScaleCommand();
const u16* DecodeColoredMacroblock(const u16* src, const u16* src_end);
const u16* DecodeMonoMacroblock(const u16* src, const u16* src_end);
// from nocash spec
bool rl_decode_block(s16* blk, const u16*& src, const u16* src_end, const u8* qt);
void IDCT(s16* blk);
void yuv_to_rgb(u32 xx, u32 yy, const std::array<s16, 64>& Crblk, const std::array<s16, 64>& Cbblk,
const std::array<s16, 64>& Yblk, std::array<u32, 256>& rgb_out);
void y_to_mono(const std::array<s16, 64>& Yblk, std::array<u8, 64>& r_out);
System* m_system = nullptr;
DMA* m_dma = nullptr;
StatusRegister m_status = {};
InlineFIFOQueue<u32, DATA_IN_FIFO_SIZE> m_data_in_fifo;
InlineFIFOQueue<u32, DATA_OUT_FIFO_SIZE> m_data_out_fifo;
Command m_command = Command::None;
u32 m_command_parameter_count = 0;
std::array<u8, 64> m_iq_uv{};
std::array<u8, 64> m_iq_y{};
std::array<s16, 64> m_scale_table{};
};

287
src/core/memory_card.cpp Normal file
View File

@ -0,0 +1,287 @@
#include "memory_card.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
Log_SetChannel(MemoryCard);
MemoryCard::MemoryCard()
{
m_FLAG.no_write_yet = true;
Format();
}
MemoryCard::~MemoryCard() = default;
void MemoryCard::Reset()
{
ResetTransferState();
}
bool MemoryCard::DoState(StateWrapper& sw)
{
sw.Do(&m_state);
sw.Do(&m_address);
sw.Do(&m_sector_offset);
sw.Do(&m_checksum);
sw.Do(&m_last_byte);
sw.Do(&m_data);
return !sw.HasError();
}
void MemoryCard::ResetTransferState()
{
m_state = State::Idle;
m_address = 0;
m_sector_offset = 0;
m_checksum = 0;
m_last_byte = 0;
}
bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
{
bool ack = false;
const State old_state = m_state;
switch (m_state)
{
#define FIXED_REPLY_STATE(state, reply, ack_value, next_state) \
case state: \
{ \
*data_out = reply; \
ack = ack_value; \
m_state = next_state; \
} \
break;
#define ADDRESS_STATE_MSB(state, next_state) \
case state: \
{ \
*data_out = 0x00; \
ack = true; \
m_address = ((m_address & u16(0x00FF)) | (ZeroExtend16(data_in) << 8)) & 0x3FF; \
m_state = next_state; \
} \
break;
#define ADDRESS_STATE_LSB(state, next_state) \
case state: \
{ \
*data_out = m_last_byte; \
ack = true; \
m_address = ((m_address & u16(0xFF00)) | ZeroExtend16(data_in)) & 0x3FF; \
m_sector_offset = 0; \
m_state = next_state; \
} \
break;
// read state
FIXED_REPLY_STATE(State::ReadCardID1, 0x5A, true, State::ReadCardID2);
FIXED_REPLY_STATE(State::ReadCardID2, 0x5D, true, State::ReadAddressMSB);
ADDRESS_STATE_MSB(State::ReadAddressMSB, State::ReadAddressLSB);
ADDRESS_STATE_LSB(State::ReadAddressLSB, State::ReadACK1);
FIXED_REPLY_STATE(State::ReadACK1, 0x5C, true, State::ReadACK2);
FIXED_REPLY_STATE(State::ReadACK2, 0x5D, true, State::ReadConfirmAddressMSB);
FIXED_REPLY_STATE(State::ReadConfirmAddressMSB, Truncate8(m_address >> 8), true, State::ReadConfirmAddressLSB);
FIXED_REPLY_STATE(State::ReadConfirmAddressLSB, Truncate8(m_address), true, State::ReadData);
case State::ReadData:
{
const u8 bits = m_data[ZeroExtend32(m_address) * SECTOR_SIZE + m_sector_offset];
if (m_sector_offset == 0)
{
Log_DevPrintf("Reading memory card sector %u", ZeroExtend32(m_address));
m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ bits;
}
else
{
m_checksum ^= bits;
}
*data_out = bits;
ack = true;
m_sector_offset++;
if (m_sector_offset == SECTOR_SIZE)
{
m_state = State::ReadChecksum;
m_sector_offset = 0;
}
}
break;
FIXED_REPLY_STATE(State::ReadChecksum, m_checksum, true, State::ReadEnd);
FIXED_REPLY_STATE(State::ReadEnd, 0x47, true, State::Idle);
// write state
FIXED_REPLY_STATE(State::WriteCardID1, 0x5A, true, State::WriteCardID2);
FIXED_REPLY_STATE(State::WriteCardID2, 0x5D, true, State::WriteAddressMSB);
ADDRESS_STATE_MSB(State::WriteAddressMSB, State::WriteAddressLSB);
ADDRESS_STATE_LSB(State::WriteAddressLSB, State::WriteData);
case State::WriteData:
{
if (m_sector_offset == 0)
{
Log_DevPrintf("Writing memory card sector %u", ZeroExtend32(m_address));
m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ data_in;
m_FLAG.no_write_yet = false;
}
else
{
m_checksum ^= data_in;
}
m_data[ZeroExtend32(m_address) * SECTOR_SIZE + m_sector_offset] = data_in;
*data_out = m_last_byte;
ack = true;
m_sector_offset++;
if (m_sector_offset == SECTOR_SIZE)
{
m_state = State::WriteChecksum;
m_sector_offset = 0;
}
}
break;
FIXED_REPLY_STATE(State::WriteChecksum, m_checksum, true, State::WriteACK1);
FIXED_REPLY_STATE(State::WriteACK1, 0x5C, true, State::WriteACK2);
FIXED_REPLY_STATE(State::WriteACK2, 0x5D, true, State::WriteEnd);
FIXED_REPLY_STATE(State::WriteEnd, 0x47, true, State::Idle);
// new command
case State::Idle:
{
// select device
if (data_in == 0x81)
{
*data_out = 0xFF;
ack = true;
m_state = State::Command;
}
}
break;
case State::Command:
{
switch (data_in)
{
case 0x52: // read data
{
*data_out = m_FLAG.bits;
ack = true;
m_state = State::ReadCardID1;
}
break;
case 0x57: // write data
{
*data_out = m_FLAG.bits;
ack = true;
m_state = State::WriteCardID1;
}
break;
case 0x53: // get id
{
Panic("implement me");
}
break;
default:
{
Log_ErrorPrintf("Invalid command 0x%02X", ZeroExtend32(data_in));
*data_out = m_FLAG.bits;
ack = false;
m_state = State::Idle;
}
}
}
break;
default:
UnreachableCode();
break;
}
Log_DebugPrintf("Transfer, old_state=%u, new_state=%u, data_in=0x%02X, data_out=0x%02X, ack=%s",
static_cast<u32>(old_state), static_cast<u32>(m_state), data_in, *data_out, ack ? "true" : "false");
m_last_byte = data_in;
return ack;
}
std::shared_ptr<MemoryCard> MemoryCard::Create()
{
return std::make_shared<MemoryCard>();
}
u8 MemoryCard::ChecksumFrame(const u8* fptr)
{
u8 value = fptr[0];
for (u32 i = 0; i < SECTOR_SIZE; i++)
value ^= fptr[i];
return value;
}
void MemoryCard::Format()
{
// fill everything with FF
m_data.fill(u8(0xFF));
// header
{
u8* fptr = GetSectorPtr(0);
std::fill_n(fptr, SECTOR_SIZE, u8(0));
fptr[0] = 'M';
fptr[1] = 'C';
fptr[0x7F] = ChecksumFrame(&m_data[0]);
}
// directory
for (u32 frame = 1; frame < 16; frame++)
{
u8* fptr = GetSectorPtr(frame);
std::fill_n(fptr, SECTOR_SIZE, u8(0));
fptr[0] = 0xA0; // free
fptr[0x7F] = ChecksumFrame(fptr); // checksum
}
// broken sector list
for (u32 frame = 16; frame < 36; frame++)
{
u8* fptr = GetSectorPtr(frame);
std::fill_n(fptr, SECTOR_SIZE, u8(0));
fptr[0] = 0xFF;
fptr[1] = 0xFF;
fptr[2] = 0xFF;
fptr[3] = 0xFF;
fptr[0x7F] = ChecksumFrame(fptr); // checksum
}
// broken sector replacement data
for (u32 frame = 36; frame < 56; frame++)
{
u8* fptr = GetSectorPtr(frame);
std::fill_n(fptr, SECTOR_SIZE, u8(0xFF));
}
// unused frames
for (u32 frame = 56; frame < 63; frame++)
{
u8* fptr = GetSectorPtr(frame);
std::fill_n(fptr, SECTOR_SIZE, u8(0xFF));
}
// write test frame
std::memcpy(GetSectorPtr(63), GetSectorPtr(0), SECTOR_SIZE);
}
u8* MemoryCard::GetSectorPtr(u32 sector)
{
Assert(sector < NUM_SECTORS);
return &m_data[sector * SECTOR_SIZE];
}

80
src/core/memory_card.h Normal file
View File

@ -0,0 +1,80 @@
#pragma once
#include "common/bitfield.h"
#include "pad_device.h"
#include <memory>
#include <array>
class MemoryCard final : public PadDevice
{
public:
enum : u32
{
DATA_SIZE = 128 * 1024, // 1mbit
SECTOR_SIZE = 128,
NUM_SECTORS = DATA_SIZE / SECTOR_SIZE
};
MemoryCard();
~MemoryCard() override;
static std::shared_ptr<MemoryCard> Create();
void Reset() override;
bool DoState(StateWrapper& sw) override;
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void Format();
private:
union FLAG
{
u8 bits;
BitField<u8, bool, 3, 1> no_write_yet;
BitField<u8, bool, 2, 1> write_error;
};
FLAG m_FLAG = {};
enum class State : u8
{
Idle,
Command,
ReadCardID1,
ReadCardID2,
ReadAddressMSB,
ReadAddressLSB,
ReadACK1,
ReadACK2,
ReadConfirmAddressMSB,
ReadConfirmAddressLSB,
ReadData,
ReadChecksum,
ReadEnd,
WriteCardID1,
WriteCardID2,
WriteAddressMSB,
WriteAddressLSB,
WriteData,
WriteChecksum,
WriteACK1,
WriteACK2,
WriteEnd,
};
static u8 ChecksumFrame(const u8* fptr);
u8* GetSectorPtr(u32 sector);
State m_state = State::Idle;
u16 m_address = 0;
u8 m_sector_offset = 0;
u8 m_checksum = 0;
u8 m_last_byte = 0;
std::array<u8, DATA_SIZE> m_data{};
};

350
src/core/pad.cpp Normal file
View File

@ -0,0 +1,350 @@
#include "pad.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "interrupt_controller.h"
#include "pad_device.h"
#include "system.h"
Log_SetChannel(Pad);
Pad::Pad() = default;
Pad::~Pad() = default;
bool Pad::Initialize(System* system, InterruptController* interrupt_controller)
{
m_system = system;
m_interrupt_controller = interrupt_controller;
return true;
}
void Pad::Reset()
{
SoftReset();
for (u32 i = 0; i < NUM_SLOTS; i++)
{
if (m_controllers[i])
m_controllers[i]->Reset();
if (m_memory_cards[i])
m_memory_cards[i]->Reset();
}
}
bool Pad::DoState(StateWrapper& sw)
{
for (u32 i = 0; i < NUM_SLOTS; i++)
{
if (m_controllers[i])
{
if (!sw.DoMarker("Controller") || !m_controllers[i]->DoState(sw))
return false;
}
else
{
if (!sw.DoMarker("NoController"))
return false;
}
if (m_memory_cards[i])
{
if (!sw.DoMarker("MemoryCard") || !m_memory_cards[i]->DoState(sw))
return false;
}
else
{
if (!sw.DoMarker("NoController"))
return false;
}
}
sw.Do(&m_state);
sw.Do(&m_ticks_remaining);
sw.Do(&m_JOY_CTRL.bits);
sw.Do(&m_JOY_STAT.bits);
sw.Do(&m_JOY_MODE.bits);
sw.Do(&m_RX_FIFO);
sw.Do(&m_TX_FIFO);
return !sw.HasError();
}
u32 Pad::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00: // JOY_DATA
{
if (m_RX_FIFO.IsEmpty())
{
Log_WarningPrint("Read from RX fifo when empty");
return 0;
}
const u8 value = m_RX_FIFO.Pop();
UpdateJoyStat();
Log_DebugPrintf("JOY_DATA (R) -> 0x%02X", ZeroExtend32(value));
return ZeroExtend32(value);
}
case 0x04: // JOY_STAT
{
const u32 bits = m_JOY_STAT.bits;
m_JOY_STAT.ACKINPUT = false;
return bits;
}
case 0x08: // JOY_MODE
return ZeroExtend32(m_JOY_MODE.bits);
case 0x0A: // JOY_CTRL
return ZeroExtend32(m_JOY_CTRL.bits);
default:
Log_ErrorPrintf("Unknown register read: 0x%X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void Pad::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0x00: // JOY_DATA
{
Log_DebugPrintf("JOY_DATA (W) <- 0x%02X", value);
if (m_TX_FIFO.IsFull())
{
Log_WarningPrint("TX FIFO overrun");
m_TX_FIFO.RemoveOne();
}
m_TX_FIFO.Push(Truncate8(value));
if (!IsTransmitting() && CanTransfer())
BeginTransfer();
return;
}
case 0x0A: // JOY_CTRL
{
Log_DebugPrintf("JOY_CTRL <- 0x%04X", value);
const bool old_select = m_JOY_CTRL.SELECT;
m_JOY_CTRL.bits = Truncate16(value);
if (m_JOY_CTRL.RESET)
SoftReset();
if (m_JOY_CTRL.ACK)
{
// reset stat bits
m_JOY_STAT.INTR = false;
}
if (!m_JOY_CTRL.SELECT)
ResetDeviceTransferState();
if (!m_JOY_CTRL.SELECT || !m_JOY_CTRL.TXEN)
{
if (IsTransmitting())
EndTransfer();
}
else
{
if (!IsTransmitting() && CanTransfer())
BeginTransfer();
}
return;
}
case 0x08: // JOY_MODE
{
Log_DebugPrintf("JOY_MODE <- 0x%08X", value);
m_JOY_MODE.bits = Truncate16(value);
return;
}
case 0x0E:
{
Log_WarningPrintf("JOY_BAUD <- 0x%08X", value);
return;
}
default:
Log_ErrorPrintf("Unknown register write: 0x%X <- 0x%08X", offset, value);
return;
}
}
void Pad::Execute(TickCount ticks)
{
switch (m_state)
{
case State::Idle:
break;
case State::Transmitting:
{
m_ticks_remaining -= ticks;
if (m_ticks_remaining <= 0)
DoTransfer();
else
m_system->SetDowncount(m_ticks_remaining);
}
break;
}
}
void Pad::SoftReset()
{
if (IsTransmitting())
EndTransfer();
m_JOY_CTRL.bits = 0;
m_JOY_STAT.bits = 0;
m_JOY_MODE.bits = 0;
m_RX_FIFO.Clear();
m_TX_FIFO.Clear();
ResetDeviceTransferState();
UpdateJoyStat();
}
void Pad::UpdateJoyStat()
{
m_JOY_STAT.RXFIFONEMPTY = !m_RX_FIFO.IsEmpty();
m_JOY_STAT.TXDONE = m_TX_FIFO.IsEmpty();
m_JOY_STAT.TXRDY = !m_TX_FIFO.IsFull();
}
void Pad::BeginTransfer()
{
DebugAssert(m_state == State::Idle && CanTransfer());
Log_DebugPrintf("Starting transfer");
m_JOY_CTRL.RXEN = true;
// The transfer or the interrupt must be delayed, otherwise the BIOS thinks there's no device detected.
// It seems to do something resembling the following:
// 1) Sets the control register up for transmitting, interrupt on ACK.
// 2) Writes 0x01 to the TX FIFO.
// 3) Delays for a bit.
// 4) Writes ACK to the control register, clearing the interrupt flag.
// 5) Clears IRQ7 in the interrupt controller.
// 6) Waits until the RX FIFO is not empty, reads the first byte to $zero.
// 7) Checks if the interrupt status register had IRQ7 set. If not, no device connected.
//
// Performing the transfer immediately will result in both the INTR bit and the bit in the interrupt
// controller being discarded in (4)/(5), but this bit was set by the *new* transfer. Therefore, the
// test in (7) will fail, and it won't send any more data. So, the transfer/interrupt must be delayed
// until after (4) and (5) have been completed.
m_system->Synchronize();
m_state = State::Transmitting;
m_ticks_remaining = TRANSFER_TICKS;
m_system->SetDowncount(m_ticks_remaining);
}
void Pad::DoTransfer()
{
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
const std::shared_ptr<PadDevice>& controller = m_controllers[m_JOY_CTRL.SLOT];
const std::shared_ptr<PadDevice>& memory_card = m_memory_cards[m_JOY_CTRL.SLOT];
// set rx?
m_JOY_CTRL.RXEN = true;
const u8 data_out = m_TX_FIFO.Pop();
u8 data_in = 0xFF;
bool ack = false;
switch (m_active_device)
{
case ActiveDevice::None:
{
if (!controller || !(ack = controller->Transfer(data_out, &data_in)))
{
if (!memory_card || !(ack = memory_card->Transfer(data_out, &data_in)))
{
// nothing connected to this port
Log_DebugPrintf("Nothing connected or ACK'ed");
}
else
{
// memory card responded, make it the active device until non-ack
m_active_device = ActiveDevice::MemoryCard;
}
}
else
{
// controller responded, make it the active device until non-ack
m_active_device = ActiveDevice::Controller;
}
}
break;
case ActiveDevice::Controller:
{
if (controller)
ack = controller->Transfer(data_out, &data_in);
}
break;
case ActiveDevice::MemoryCard:
{
if (memory_card)
ack = memory_card->Transfer(data_out, &data_in);
}
break;
}
m_RX_FIFO.Push(data_in);
m_JOY_STAT.ACKINPUT |= ack;
// device no longer active?
if (!ack)
m_active_device = ActiveDevice::None;
if (m_JOY_STAT.ACKINPUT && m_JOY_CTRL.ACKINTEN)
{
Log_DebugPrintf("Triggering interrupt");
m_JOY_STAT.INTR = true;
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::IRQ7);
}
if (m_TX_FIFO.IsEmpty())
{
EndTransfer();
}
else
{
// queue the next byte
m_ticks_remaining += TRANSFER_TICKS;
m_system->SetDowncount(m_ticks_remaining);
}
UpdateJoyStat();
}
void Pad::EndTransfer()
{
DebugAssert(m_state == State::Transmitting);
Log_DebugPrintf("Ending transfer");
m_state = State::Idle;
m_ticks_remaining = 0;
}
void Pad::ResetDeviceTransferState()
{
for (u32 i = 0; i < NUM_SLOTS; i++)
{
if (m_controllers[i])
m_controllers[i]->ResetTransferState();
if (m_memory_cards[i])
m_memory_cards[i]->ResetTransferState();
m_active_device = ActiveDevice::None;
}
}

120
src/core/pad.h Normal file
View File

@ -0,0 +1,120 @@
#pragma once
#include "common/bitfield.h"
#include "common/fifo_queue.h"
#include "types.h"
#include <array>
#include <memory>
class StateWrapper;
class System;
class InterruptController;
class PadDevice;
class Pad
{
public:
Pad();
~Pad();
bool Initialize(System* system, InterruptController* interrupt_controller);
void Reset();
bool DoState(StateWrapper& sw);
PadDevice* GetController(u32 slot) { return m_controllers[slot].get(); }
void SetController(u32 slot, std::shared_ptr<PadDevice> dev) { m_controllers[slot] = dev; }
PadDevice* GetMemoryCard(u32 slot) { return m_memory_cards[slot].get(); }
void SetMemoryCard(u32 slot, std::shared_ptr<PadDevice> dev) { m_memory_cards[slot] = dev; }
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
void Execute(TickCount ticks);
private:
static constexpr u32 NUM_SLOTS = 2;
static constexpr u32 TRANSFER_TICKS = 750;
enum class State : u32
{
Idle,
Transmitting
};
enum class ActiveDevice : u8
{
None,
Controller,
MemoryCard
};
union JOY_CTRL
{
u16 bits;
BitField<u16, bool, 0, 1> TXEN;
BitField<u16, bool, 1, 1> SELECT;
BitField<u16, bool, 2, 1> RXEN;
BitField<u16, bool, 4, 1> ACK;
BitField<u16, bool, 6, 1> RESET;
BitField<u16, u8, 8, 2> RXIMODE;
BitField<u16, bool, 10, 1> TXINTEN;
BitField<u16, bool, 11, 1> RXINTEN;
BitField<u16, bool, 12, 1> ACKINTEN;
BitField<u16, u8, 13, 1> SLOT;
};
union JOY_STAT
{
u32 bits;
BitField<u32, bool, 0, 1> TXRDY;
BitField<u32, bool, 1, 1> RXFIFONEMPTY;
BitField<u32, bool, 2, 1> TXDONE;
BitField<u32, bool, 7, 1> ACKINPUT;
BitField<u32, bool, 9, 1> INTR;
BitField<u32, u32, 11, 21> TMR;
};
union JOY_MODE
{
u16 bits;
BitField<u16, u8, 0, 2> reload_factor;
BitField<u16, u8, 2, 2> character_length;
BitField<u16, bool, 4, 1> parity_enable;
BitField<u16, u8, 5, 1> parity_type;
BitField<u16, u8, 8, 1> clk_polarity;
};
bool IsTransmitting() const { return m_state == State::Transmitting; }
bool CanTransfer() const
{
return !m_TX_FIFO.IsEmpty() && !m_RX_FIFO.IsFull() && m_JOY_CTRL.SELECT && m_JOY_CTRL.TXEN;
}
void SoftReset();
void UpdateJoyStat();
void BeginTransfer();
void DoTransfer();
void EndTransfer();
void ResetDeviceTransferState();
System* m_system = nullptr;
InterruptController* m_interrupt_controller = nullptr;
State m_state = State::Idle;
TickCount m_ticks_remaining = 0;
JOY_CTRL m_JOY_CTRL = {};
JOY_STAT m_JOY_STAT = {};
JOY_MODE m_JOY_MODE = {};
ActiveDevice m_active_device = ActiveDevice::None;
InlineFIFOQueue<u8, 8> m_RX_FIFO;
InlineFIFOQueue<u8, 2> m_TX_FIFO;
std::array<std::shared_ptr<PadDevice>, NUM_SLOTS> m_controllers;
std::array<std::shared_ptr<PadDevice>, NUM_SLOTS> m_memory_cards;
};

21
src/core/pad_device.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "pad_device.h"
#include "common/state_wrapper.h"
PadDevice::PadDevice() = default;
PadDevice::~PadDevice() = default;
void PadDevice::Reset() {}
bool PadDevice::DoState(StateWrapper& sw)
{
return !sw.HasError();
}
void PadDevice::ResetTransferState() {}
bool PadDevice::Transfer(const u8 data_in, u8* data_out)
{
*data_out = 0xFF;
return false;
}

21
src/core/pad_device.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "types.h"
class StateWrapper;
class PadDevice
{
public:
PadDevice();
virtual ~PadDevice();
virtual void Reset();
virtual bool DoState(StateWrapper& sw);
// Resets all state for the transferring to/from the device.
virtual void ResetTransferState();
// Returns the value of ACK, as well as filling out_data.
virtual bool Transfer(const u8 data_in, u8* data_out);
};

View File

@ -0,0 +1,4 @@
#pragma once
#include "pse/types.h"
constexpr u32 SAVE_STATE_VERSION = 1;

140
src/core/spu.cpp Normal file
View File

@ -0,0 +1,140 @@
#include "spu.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "dma.h"
#include "interrupt_controller.h"
#include "system.h"
Log_SetChannel(SPU);
SPU::SPU() = default;
SPU::~SPU() = default;
bool SPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
{
m_system = system;
m_dma = dma;
m_interrupt_controller = interrupt_controller;
return true;
}
void SPU::Reset()
{
m_SPUCNT.bits = 0;
m_SPUSTAT.bits = 0;
m_transfer_address = 0;
m_transfer_address_reg = 0;
}
bool SPU::DoState(StateWrapper& sw)
{
sw.Do(&m_SPUCNT.bits);
sw.Do(&m_SPUSTAT.bits);
sw.Do(&m_transfer_address);
sw.Do(&m_transfer_address_reg);
return !sw.HasError();
}
u16 SPU::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x1F801DA6 - SPU_BASE:
Log_DebugPrintf("SPU transfer address register -> 0x%04X", ZeroExtend32(m_transfer_address_reg));
return m_transfer_address_reg;
case 0x1F801DA8 - SPU_BASE:
Log_ErrorPrintf("SPU transfer data register read");
return UINT16_C(0xFFFF);
case 0x1F801DAA - SPU_BASE:
Log_DebugPrintf("SPU control register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits));
return m_SPUCNT.bits;
case 0x1F801DAE - SPU_BASE:
Log_DebugPrintf("SPU status register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits));
return m_SPUSTAT.bits;
default:
Log_ErrorPrintf("Unknown SPU register read: offset 0x%X (address 0x%08X)", offset, offset | SPU_BASE);
return UINT16_C(0xFFFF);
}
}
void SPU::WriteRegister(u32 offset, u16 value)
{
switch (offset)
{
case 0x1F801DA6 - SPU_BASE:
{
Log_DebugPrintf("SPU transfer address register <- 0x%04X", ZeroExtend32(value));
m_transfer_address_reg = value;
m_transfer_address = ZeroExtend32(value) * 8;
return;
}
case 0x1F801DA8 - SPU_BASE:
{
Log_TracePrintf("SPU transfer data register <- 0x%04X (RAM offset 0x%08X)", ZeroExtend32(value),
m_transfer_address);
RAMTransferWrite(value);
return;
}
case 0x1F801DAA - SPU_BASE:
{
Log_DebugPrintf("SPU control register <- 0x%04X", ZeroExtend32(value));
m_SPUCNT.bits = value;
UpdateDMARequest();
return;
}
// read-only registers
case 0x1F801DAE - SPU_BASE:
{
return;
}
default:
{
Log_ErrorPrintf("Unknown SPU register write: offset 0x%X (address 0x%08X) value 0x%04X", offset,
offset | SPU_BASE, ZeroExtend32(value));
return;
}
}
}
u32 SPU::DMARead()
{
const u16 lsb = RAMTransferRead();
const u16 msb = RAMTransferRead();
return ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16);
}
void SPU::DMAWrite(u32 value)
{
// two 16-bit writes to prevent out-of-bounds
RAMTransferWrite(Truncate16(value));
RAMTransferWrite(Truncate16(value >> 16));
}
void SPU::UpdateDMARequest()
{
const RAMTransferMode mode = m_SPUCNT.ram_transfer_mode;
const bool request = (mode == RAMTransferMode::DMAWrite || mode == RAMTransferMode::DMARead);
m_dma->SetRequest(DMA::Channel::SPU, request);
}
u16 SPU::RAMTransferRead()
{
u16 value;
std::memcpy(&value, &m_ram[m_transfer_address], sizeof(value));
m_transfer_address = (m_transfer_address + sizeof(value)) & RAM_MASK;
return value;
}
void SPU::RAMTransferWrite(u16 value)
{
std::memcpy(&m_ram[m_transfer_address], &value, sizeof(value));
m_transfer_address = (m_transfer_address + sizeof(value)) & RAM_MASK;
}

99
src/core/spu.h Normal file
View File

@ -0,0 +1,99 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
#include <array>
class StateWrapper;
class System;
class DMA;
class InterruptController;
class SPU
{
public:
SPU();
~SPU();
bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller);
void Reset();
bool DoState(StateWrapper& sw);
u16 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u16 value);
u32 DMARead();
void DMAWrite(u32 value);
private:
static constexpr u32 RAM_SIZE = 512 * 1024;
static constexpr u32 RAM_MASK = RAM_SIZE - 1;
static constexpr u32 SPU_BASE = 0x1F801C00;
enum class RAMTransferMode : u8
{
Stopped =0,
ManualWrite = 1,
DMAWrite = 2,
DMARead = 3
};
union SPUCNT
{
u16 bits;
BitField<u16, bool, 15, 1> enable;
BitField<u16, bool, 14, 1> mute;
BitField<u16, u8, 10, 4> noise_frequency_shift;
BitField<u16, u8, 8, 2> noise_frequency_step;
BitField<u16, bool, 7, 1> reverb_master_enable;
BitField<u16, bool, 6, 1> irq9_enable;
BitField<u16, RAMTransferMode, 4, 2> ram_transfer_mode;
BitField<u16, bool, 3, 1> external_audio_reverb;
BitField<u16, bool, 2, 1> cd_audio_reverb;
BitField<u16, bool, 1, 1> external_audio_enable;
BitField<u16, bool, 0, 1> cd_audio_enable;
BitField<u16, u8, 0, 6> mode;
};
union SPUSTAT
{
u16 bits;
BitField<u16, bool, 11, 1> second_half_capture_buffer;
BitField<u16, bool, 10, 1> transfer_busy;
BitField<u16, bool, 9, 1> dma_read_request;
BitField<u16, bool, 8, 1> dma_write_request;
BitField<u16, bool, 7, 1> dma_read_write_request;
BitField<u16, bool, 6, 1> irq9_flag;
BitField<u16, u8, 0, 6> mode;
};
#if 0
struct Voice
{
static constexpr u32 NUM_REGS = 8;
static constexpr u32 NUM_FLAGS = 6;
std::array<u16, NUM_REGS> regs;
};
#endif
void UpdateDMARequest();
u16 RAMTransferRead();
void RAMTransferWrite(u16 value);
System* m_system = nullptr;
DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr;
SPUCNT m_SPUCNT = {};
SPUSTAT m_SPUSTAT = {};
u16 m_transfer_address_reg = 0;
u32 m_transfer_address = 0;
std::array<u8, RAM_SIZE> m_ram{};
};

310
src/core/system.cpp Normal file
View File

@ -0,0 +1,310 @@
#include "system.h"
#include "YBaseLib/Log.h"
#include "bus.h"
#include "cdrom.h"
#include "common/state_wrapper.h"
#include "cpu_core.h"
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "pad_device.h"
#include "spu.h"
#include "timers.h"
#include <cstdio>
Log_SetChannel(System);
System::System(HostInterface* host_interface) : m_host_interface(host_interface)
{
m_cpu = std::make_unique<CPU::Core>();
m_bus = std::make_unique<Bus>();
m_dma = std::make_unique<DMA>();
m_interrupt_controller = std::make_unique<InterruptController>();
// m_gpu = std::make_unique<GPU>();
m_gpu = GPU::CreateHardwareOpenGLRenderer();
m_cdrom = std::make_unique<CDROM>();
m_pad = std::make_unique<Pad>();
m_timers = std::make_unique<Timers>();
m_spu = std::make_unique<SPU>();
m_mdec = std::make_unique<MDEC>();
}
System::~System() = default;
bool System::Initialize()
{
if (!m_cpu->Initialize(m_bus.get()))
return false;
if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(),
m_pad.get(), m_timers.get(), m_spu.get(), m_mdec.get()))
{
return false;
}
if (!m_dma->Initialize(this, m_bus.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(), m_spu.get(),
m_mdec.get()))
{
return false;
}
if (!m_interrupt_controller->Initialize(m_cpu.get()))
return false;
if (!m_gpu->Initialize(this, m_dma.get(), m_interrupt_controller.get(), m_timers.get()))
return false;
if (!m_cdrom->Initialize(this, m_dma.get(), m_interrupt_controller.get()))
return false;
if (!m_pad->Initialize(this, m_interrupt_controller.get()))
return false;
if (!m_timers->Initialize(this, m_interrupt_controller.get()))
return false;
if (!m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get()))
return false;
if (!m_mdec->Initialize(this, m_dma.get()))
return false;
return true;
}
bool System::DoState(StateWrapper& sw)
{
if (!sw.DoMarker("CPU") || !m_cpu->DoState(sw))
return false;
if (!sw.DoMarker("Bus") || !m_bus->DoState(sw))
return false;
if (!sw.DoMarker("DMA") || !m_dma->DoState(sw))
return false;
if (!sw.DoMarker("InterruptController") || !m_interrupt_controller->DoState(sw))
return false;
if (!sw.DoMarker("GPU") || !m_gpu->DoState(sw))
return false;
if (!sw.DoMarker("CDROM") || !m_cdrom->DoState(sw))
return false;
if (!sw.DoMarker("Pad") || !m_pad->DoState(sw))
return false;
if (!sw.DoMarker("Timers") || !m_timers->DoState(sw))
return false;
if (!sw.DoMarker("SPU") || !m_timers->DoState(sw))
return false;
if (!sw.DoMarker("MDEC") || !m_mdec->DoState(sw))
return false;
return !sw.HasError();
}
void System::Reset()
{
m_cpu->Reset();
m_bus->Reset();
m_dma->Reset();
m_interrupt_controller->Reset();
m_gpu->Reset();
m_cdrom->Reset();
m_pad->Reset();
m_timers->Reset();
m_spu->Reset();
m_mdec->Reset();
m_frame_number = 1;
}
bool System::LoadState(ByteStream* state)
{
StateWrapper sw(state, StateWrapper::Mode::Read);
return DoState(sw);
}
bool System::SaveState(ByteStream* state)
{
StateWrapper sw(state, StateWrapper::Mode::Write);
return DoState(sw);
}
void System::RunFrame()
{
u32 current_frame_number = m_frame_number;
while (current_frame_number == m_frame_number)
{
m_cpu->Execute();
Synchronize();
}
}
void System::RenderUI()
{
m_gpu->RenderUI();
}
bool System::LoadEXE(const char* filename)
{
#pragma pack(push, 1)
struct EXEHeader
{
char id[8]; // 0x000-0x007 PS-X EXE
char pad1[8]; // 0x008-0x00F
u32 initial_pc; // 0x010
u32 initial_gp; // 0x014
u32 load_address; // 0x018
u32 file_size; // 0x01C excluding 0x800-byte header
u32 unk0; // 0x020
u32 unk1; // 0x024
u32 memfill_start; // 0x028
u32 memfill_size; // 0x02C
u32 initial_sp_base; // 0x030
u32 initial_sp_offset; // 0x034
u32 reserved[5]; // 0x038-0x04B
char marker[0x7B4]; // 0x04C-0x7FF
};
static_assert(sizeof(EXEHeader) == 0x800);
#pragma pack(pop)
std::FILE* fp = std::fopen(filename, "rb");
if (!fp)
return false;
EXEHeader header;
if (std::fread(&header, sizeof(header), 1, fp) != 1)
{
std::fclose(fp);
return false;
}
if (header.memfill_size > 0)
{
const u32 words_to_write = header.memfill_size / 4;
u32 address = header.memfill_start & ~UINT32_C(3);
for (u32 i = 0; i < words_to_write; i++)
{
m_cpu->SafeWriteMemoryWord(address, 0);
address += sizeof(u32);
}
}
if (header.file_size >= 4)
{
std::vector<u32> data_words(header.file_size / 4);
if (std::fread(data_words.data(), header.file_size, 1, fp) != 1)
{
std::fclose(fp);
return false;
}
const u32 num_words = header.file_size / 4;
u32 address = header.load_address;
for (u32 i = 0; i < num_words; i++)
{
m_cpu->SafeWriteMemoryWord(address, data_words[i]);
address += sizeof(u32);
}
}
std::fclose(fp);
// patch the BIOS to jump to the executable directly
{
const u32 r_pc = header.load_address;
const u32 r_gp = header.initial_gp;
const u32 r_sp = header.initial_sp_base;
const u32 r_fp = header.initial_sp_base + header.initial_sp_offset;
// pc has to be done first because we can't load it in the delay slot
m_bus->PatchBIOS(0xBFC06FF0, UINT32_C(0x3C080000) | r_pc >> 16); // lui $t0, (r_pc >> 16)
m_bus->PatchBIOS(0xBFC06FF4, UINT32_C(0x35080000) | (r_pc & UINT32_C(0xFFFF))); // ori $t0, $t0, (r_pc & 0xFFFF)
m_bus->PatchBIOS(0xBFC06FF8, UINT32_C(0x3C1C0000) | r_gp >> 16); // lui $gp, (r_gp >> 16)
m_bus->PatchBIOS(0xBFC06FFC, UINT32_C(0x379C0000) | (r_gp & UINT32_C(0xFFFF))); // ori $gp, $gp, (r_gp & 0xFFFF)
m_bus->PatchBIOS(0xBFC07000, UINT32_C(0x3C1D0000) | r_sp >> 16); // lui $sp, (r_sp >> 16)
m_bus->PatchBIOS(0xBFC07004, UINT32_C(0x37BD0000) | (r_sp & UINT32_C(0xFFFF))); // ori $sp, $sp, (r_sp & 0xFFFF)
m_bus->PatchBIOS(0xBFC07008, UINT32_C(0x3C1E0000) | r_fp >> 16); // lui $fp, (r_fp >> 16)
m_bus->PatchBIOS(0xBFC0700C, UINT32_C(0x01000008)); // jr $t0
m_bus->PatchBIOS(0xBFC07010, UINT32_C(0x37DE0000) | (r_fp & UINT32_C(0xFFFF))); // ori $fp, $fp, (r_fp & 0xFFFF)
}
return true;
}
bool System::SetExpansionROM(const char* filename)
{
std::FILE* fp = std::fopen(filename, "rb");
if (!fp)
{
Log_ErrorPrintf("Failed to open '%s'", filename);
return false;
}
std::fseek(fp, 0, SEEK_END);
const u32 size = static_cast<u32>(std::ftell(fp));
std::fseek(fp, 0, SEEK_SET);
std::vector<u8> data(size);
if (std::fread(data.data(), size, 1, fp) != 1)
{
Log_ErrorPrintf("Failed to read ROM data from '%s'", filename);
std::fclose(fp);
return false;
}
std::fclose(fp);
Log_InfoPrintf("Loaded expansion ROM from '%s': %u bytes", filename, size);
m_bus->SetExpansionROM(std::move(data));
return true;
}
void System::Synchronize()
{
m_cpu->ResetDowncount();
const TickCount pending_ticks = m_cpu->GetPendingTicks();
m_cpu->ResetPendingTicks();
m_gpu->Execute(pending_ticks);
m_timers->AddSystemTicks(pending_ticks);
m_cdrom->Execute(pending_ticks);
m_pad->Execute(pending_ticks);
m_dma->Execute(pending_ticks);
}
void System::SetDowncount(TickCount downcount)
{
m_cpu->SetDowncount(downcount);
}
void System::SetController(u32 slot, std::shared_ptr<PadDevice> dev)
{
m_pad->SetController(slot, std::move(dev));
}
void System::SetMemoryCard(u32 slot, std::shared_ptr<PadDevice> dev)
{
m_pad->SetMemoryCard(slot, std::move(dev));
}
bool System::HasMedia() const
{
return m_cdrom->HasMedia();
}
bool System::InsertMedia(const char* path)
{
return m_cdrom->InsertMedia(path);
}
void System::RemoveMedia()
{
m_cdrom->RemoveMedia();
}

77
src/core/system.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#include "types.h"
#include <memory>
class ByteStream;
class StateWrapper;
class HostInterface;
namespace CPU
{
class Core;
}
class Bus;
class DMA;
class InterruptController;
class GPU;
class CDROM;
class Pad;
class PadDevice;
class Timers;
class SPU;
class MDEC;
class System
{
public:
System(HostInterface* host_interface);
~System();
HostInterface* GetHostInterface() const { return m_host_interface; }
u32 GetFrameNumber() const { return m_frame_number; }
u32 GetInternalFrameNumber() const { return m_internal_frame_number; }
void IncrementFrameNumber() { m_frame_number++; }
void IncrementInternalFrameNumber() { m_internal_frame_number++; }
bool Initialize();
void Reset();
bool LoadState(ByteStream* state);
bool SaveState(ByteStream* state);
void RunFrame();
void RenderUI();
bool LoadEXE(const char* filename);
bool SetExpansionROM(const char* filename);
void SetDowncount(TickCount downcount);
void Synchronize();
void SetController(u32 slot, std::shared_ptr<PadDevice> dev);
void SetMemoryCard(u32 slot, std::shared_ptr<PadDevice> dev);
bool HasMedia() const;
bool InsertMedia(const char* path);
void RemoveMedia();
private:
bool DoState(StateWrapper& sw);
HostInterface* m_host_interface;
std::unique_ptr<CPU::Core> m_cpu;
std::unique_ptr<Bus> m_bus;
std::unique_ptr<DMA> m_dma;
std::unique_ptr<InterruptController> m_interrupt_controller;
std::unique_ptr<GPU> m_gpu;
std::unique_ptr<CDROM> m_cdrom;
std::unique_ptr<Pad> m_pad;
std::unique_ptr<Timers> m_timers;
std::unique_ptr<SPU> m_spu;
std::unique_ptr<MDEC> m_mdec;
u32 m_frame_number = 1;
u32 m_internal_frame_number = 1;
};

228
src/core/timers.cpp Normal file
View File

@ -0,0 +1,228 @@
#include "timers.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "interrupt_controller.h"
#include "system.h"
Log_SetChannel(Timers);
Timers::Timers() = default;
Timers::~Timers() = default;
bool Timers::Initialize(System* system, InterruptController* interrupt_controller)
{
m_system = system;
m_interrupt_controller = interrupt_controller;
return true;
}
void Timers::Reset()
{
for (CounterState& cs : m_states)
{
cs.mode.bits = 0;
cs.counter = 0;
cs.target = 0;
cs.gate = false;
cs.external_counting_enabled = false;
cs.counting_enabled = true;
}
}
bool Timers::DoState(StateWrapper& sw)
{
for (CounterState& cs : m_states)
{
sw.Do(&cs.mode.bits);
sw.Do(&cs.counter);
sw.Do(&cs.target);
sw.Do(&cs.gate);
sw.Do(&cs.use_external_clock);
sw.Do(&cs.external_counting_enabled);
sw.Do(&cs.counting_enabled);
}
return !sw.HasError();
}
void Timers::SetGate(u32 timer, bool state)
{
CounterState& cs = m_states[timer];
if (cs.gate == state)
return;
cs.gate = state;
if (cs.mode.sync_enable)
{
if (state)
{
switch (cs.mode.sync_mode)
{
case SyncMode::ResetOnGate:
case SyncMode::ResetAndRunOnGate:
cs.counter = 0;
break;
case SyncMode::FreeRunOnGate:
cs.mode.sync_enable = false;
break;
}
}
UpdateCountingEnabled(cs);
}
}
void Timers::AddTicks(u32 timer, u32 count)
{
CounterState& cs = m_states[timer];
cs.counter += count;
const u32 reset_value = cs.mode.reset_at_target ? cs.target : u32(0xFFFF);
if (cs.counter < reset_value)
return;
const bool old_intr = cs.mode.interrupt_request;
if (cs.counter >= cs.target)
cs.mode.reached_target = true;
if (cs.counter >= u32(0xFFFF))
cs.mode.reached_overflow = true;
// TODO: Non-repeat mode.
const bool target_intr = cs.mode.reached_target & cs.mode.irq_at_target;
const bool overflow_intr = cs.mode.reached_overflow & cs.mode.irq_on_overflow;
const bool new_intr = target_intr | overflow_intr;
if (!old_intr && new_intr)
{
m_interrupt_controller->InterruptRequest(
static_cast<InterruptController::IRQ>(static_cast<u32>(InterruptController::IRQ::TMR0) + timer));
}
if (reset_value > 0)
cs.counter = cs.counter % reset_value;
else
cs.counter = 0;
}
void Timers::AddSystemTicks(u32 ticks)
{
if (!m_states[0].external_counting_enabled && m_states[0].counting_enabled)
AddTicks(0, ticks);
if (!m_states[1].external_counting_enabled && m_states[1].counting_enabled)
AddTicks(1, ticks);
if (m_states[2].counting_enabled)
AddTicks(2, m_states[2].external_counting_enabled ? (ticks / 8) : (ticks));
}
u32 Timers::ReadRegister(u32 offset)
{
const u32 timer_index = (offset >> 4) & u32(0x03);
const u32 port_offset = offset & u32(0x0F);
CounterState& cs = m_states[timer_index];
switch (port_offset)
{
case 0x00:
{
m_system->Synchronize();
return cs.counter;
}
case 0x04:
{
m_system->Synchronize();
const u32 bits = cs.mode.bits;
cs.mode.reached_overflow = false;
cs.mode.reached_target = false;
return bits;
}
case 0x08:
return cs.target;
default:
Log_ErrorPrintf("Read unknown register in timer %u (offset 0x%02X)", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void Timers::WriteRegister(u32 offset, u32 value)
{
const u32 timer_index = (offset >> 4) & u32(0x03);
const u32 port_offset = offset & u32(0x0F);
CounterState& cs = m_states[timer_index];
switch (port_offset)
{
case 0x00:
{
Log_DebugPrintf("Timer %u write counter %u", timer_index, value);
m_system->Synchronize();
cs.counter = value & u32(0xFFFF);
}
break;
case 0x04:
{
Log_DebugPrintf("Timer %u write mode register 0x%04X", timer_index, value);
m_system->Synchronize();
cs.mode.bits = value & u32(0x1FFF);
cs.use_external_clock = (cs.mode.clock_source & (timer_index == 2 ? 2 : 1)) != 0;
cs.counter = 0;
UpdateCountingEnabled(cs);
}
break;
case 0x08:
{
Log_DebugPrintf("Timer %u write target 0x%04X", timer_index, ZeroExtend32(Truncate16(value)));
m_system->Synchronize();
cs.target = value & u32(0xFFFF);
}
break;
default:
Log_ErrorPrintf("Write unknown register in timer %u (offset 0x%02X, value 0x%X)", offset, value);
break;
}
}
void Timers::UpdateCountingEnabled(CounterState& cs)
{
if (cs.mode.sync_enable)
{
switch (cs.mode.sync_mode)
{
case SyncMode::PauseOnGate:
case SyncMode::FreeRunOnGate:
cs.counting_enabled = !cs.gate;
break;
case SyncMode::ResetOnGate:
cs.counting_enabled = true;
break;
case SyncMode::ResetAndRunOnGate:
cs.counting_enabled = cs.gate;
break;
}
}
else
{
cs.counting_enabled = true;
}
cs.external_counting_enabled = cs.use_external_clock && cs.counting_enabled;
}
void Timers::UpdateDowncount() {}
u32 Timers::GetSystemTicksForTimerTicks(u32 timer) const
{
return 1;
}

79
src/core/timers.h Normal file
View File

@ -0,0 +1,79 @@
#pragma once
#include "common/bitfield.h"
#include "types.h"
#include <array>
class StateWrapper;
class System;
class InterruptController;
class Timers
{
public:
Timers();
~Timers();
bool Initialize(System* system, InterruptController* interrupt_controller);
void Reset();
bool DoState(StateWrapper& sw);
void SetGate(u32 timer, bool state);
// dot clock/hblank/sysclk div 8
bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; }
void AddTicks(u32 timer, u32 ticks);
void AddSystemTicks(u32 ticks);
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
private:
static constexpr u32 NUM_TIMERS = 3;
enum class SyncMode : u8
{
PauseOnGate = 0,
ResetOnGate = 1,
ResetAndRunOnGate = 2,
FreeRunOnGate = 3
};
union CounterMode
{
u32 bits;
BitField<u32, bool, 0, 1> sync_enable;
BitField<u32, SyncMode, 1, 2> sync_mode;
BitField<u32, bool, 3, 1> reset_at_target;
BitField<u32, bool, 4, 1> irq_at_target;
BitField<u32, bool, 5, 1> irq_on_overflow;
BitField<u32, bool, 6, 1> irq_repeat;
BitField<u32, bool, 7, 1> irq_pulse;
BitField<u32, u8, 8, 2> clock_source;
BitField<u32, bool, 10, 1> interrupt_request;
BitField<u32, bool, 11, 1> reached_target;
BitField<u32, bool, 12, 1> reached_overflow;
};
struct CounterState
{
CounterMode mode;
u32 counter;
u32 target;
bool gate;
bool use_external_clock;
bool external_counting_enabled;
bool counting_enabled;
};
void UpdateCountingEnabled(CounterState& cs);
void UpdateDowncount();
u32 GetSystemTicksForTimerTicks(u32 timer) const;
System* m_system = nullptr;
InterruptController* m_interrupt_controller = nullptr;
std::array<CounterState, NUM_TIMERS> m_states{};
};

24
src/core/types.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "common/types.h"
// Physical memory addresses are 32-bits wide
using PhysicalMemoryAddress = u32;
using VirtualMemoryAddress = u32;
enum class MemoryAccessType : u32
{
Read,
Write
};
enum class MemoryAccessSize : u32
{
Byte,
HalfWord,
Word
};
using TickCount = s32;
static constexpr TickCount MASTER_CLOCK = 44100 * 0x300; // 33868800Hz or 33.8688MHz, also used as CPU clock
static constexpr TickCount MAX_SLICE_SIZE = MASTER_CLOCK / 10;