mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-14 20:55:46 -04:00
Rename to DuckStation
This commit is contained in:
16
src/core/CMakeLists.txt
Normal file
16
src/core/CMakeLists.txt
Normal 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
491
src/core/bus.cpp
Normal 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
185
src/core/bus.h
Normal 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
181
src/core/bus.inl
Normal 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
823
src/core/cdrom.cpp
Normal 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
230
src/core/cdrom.h
Normal 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
477
src/core/core.vcxproj
Normal 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>
|
54
src/core/core.vcxproj.filters
Normal file
54
src/core/core.vcxproj.filters
Normal 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
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
150
src/core/cpu_core.h
Normal 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
164
src/core/cpu_core.inl
Normal 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
377
src/core/cpu_disasm.cpp
Normal 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
9
src/core/cpu_disasm.h
Normal 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
349
src/core/cpu_types.h
Normal 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
|
83
src/core/digital_controller.cpp
Normal file
83
src/core/digital_controller.cpp
Normal 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>();
|
||||
}
|
||||
|
46
src/core/digital_controller.h
Normal file
46
src/core/digital_controller.h
Normal 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
432
src/core/dma.cpp
Normal 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
200
src/core/dma.h
Normal 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
840
src/core/gpu.cpp
Normal 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
337
src/core/gpu.h
Normal 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
479
src/core/gpu_hw.cpp
Normal 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
99
src/core/gpu_hw.h
Normal 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
580
src/core/gpu_hw_opengl.cpp
Normal 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
77
src/core/gpu_hw_opengl.h
Normal 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
874
src/core/gte.cpp
Normal 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
97
src/core/gte.h
Normal 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
117
src/core/gte.inl
Normal 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
142
src/core/gte_types.h
Normal 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
|
97
src/core/host_interface.cpp
Normal file
97
src/core/host_interface.cpp
Normal 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
31
src/core/host_interface.h
Normal 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;
|
||||
};
|
88
src/core/interrupt_controller.cpp
Normal file
88
src/core/interrupt_controller.cpp
Normal 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);
|
||||
}
|
60
src/core/interrupt_controller.h
Normal file
60
src/core/interrupt_controller.h
Normal 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
536
src/core/mdec.cpp
Normal 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
118
src/core/mdec.h
Normal 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
287
src/core/memory_card.cpp
Normal 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
80
src/core/memory_card.h
Normal 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
350
src/core/pad.cpp
Normal 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
120
src/core/pad.h
Normal 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
21
src/core/pad_device.cpp
Normal 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
21
src/core/pad_device.h
Normal 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);
|
||||
};
|
||||
|
4
src/core/save_state_version.h
Normal file
4
src/core/save_state_version.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "pse/types.h"
|
||||
|
||||
constexpr u32 SAVE_STATE_VERSION = 1;
|
140
src/core/spu.cpp
Normal file
140
src/core/spu.cpp
Normal 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
99
src/core/spu.h
Normal 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
310
src/core/system.cpp
Normal 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
77
src/core/system.h
Normal 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
228
src/core/timers.cpp
Normal 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
79
src/core/timers.h
Normal 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
24
src/core/types.h
Normal 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;
|
||||
|
Reference in New Issue
Block a user