Merge remote-tracking branch 'upstream/main' into rewind-defer

This commit is contained in:
Rix
2026-10-10 15:31:34 -07:00
90 changed files with 7413 additions and 5678 deletions
+2
View File
@@ -32,6 +32,8 @@ jobs:
make -C src/mips/openbios clean
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
cp ./openbios.bin src/mips/openbios/
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
DBUS_SESSION_BUS_ADDRESS="disabled:" ASAN_OPTIONS=detect_leaks=0 xvfb-run ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
- name: Test
env:
# No dbus-launch in the image, so libdbus autolaunches a session bus, fails to
+8
View File
@@ -86,6 +86,14 @@ jobs:
cp version.json AppDir/usr/share/pcsx-redux/resources
linuxdeploy -v 3 --appdir=AppDir -e AppDir/usr/bin/pcsx-redux -d AppDir/usr/share/applications/pcsx-redux.desktop -i AppDir/usr/share/icons/hicolor/256x256/apps/pcsx-redux.png -o appimage
mv PCSX-Redux-${{ matrix.arch }}.AppImage PCSX-Redux-HEAD-${{ matrix.arch }}.AppImage
- run: |
cp src/mips/openbios/openbios.bin .
make -C src/mips/openbios clean
make -C src/mips/tests -j 2 PCSX_TESTS=true
cp ./openbios.bin src/mips/openbios/
- run: |
make -C src/mips/monitor/hosts/retail -j 2
xvfb-run catchsegv ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
- name: Test
run: |
export GTEST_OUTPUT=xml:${TEST_RESULTS}/
+4
View File
@@ -38,9 +38,13 @@ jobs:
make -j 2 all pcsx-redux-tests
make -C src/mips/openbios -j 2 BUILD=Release
cp src/mips/openbios/openbios.bin .
- run: |
make -C src/mips/openbios clean
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
cp ./openbios.bin src/mips/openbios/
- run: |
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
xvfb-run catchsegv ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
- name: Test
run: |
xvfb-run catchsegv ./pcsx-redux-tests
+2
View File
@@ -30,6 +30,8 @@ jobs:
make -C src/mips/openbios clean
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
cp ./openbios.bin src/mips/openbios/
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
xvfb-run ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
- name: Test
run: |
xvfb-run ./pcsx-redux-tests
+7 -1
View File
@@ -119,9 +119,15 @@ sstate.proto
# Sentry Native files
.sentry-native
# NOPS leftovers
# nops' configuration file
comport.txt
# Cue sheets.
*.cue
# Various editors' backup files
*.bak
# NVIDIA Nsight
GPUCache
+2 -1
View File
@@ -9,7 +9,8 @@
"compilerPath": "/usr/bin/clang-9",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
"intelliSenseMode": "clang-x64",
"configurationProvider": "ms-vscode.makefile-tools"
}
],
"version": 4
+408 -404
View File
File diff suppressed because it is too large Load Diff
+413 -405
View File
File diff suppressed because it is too large Load Diff
+533 -501
View File
File diff suppressed because it is too large Load Diff
+408 -404
View File
File diff suppressed because it is too large Load Diff
+414 -408
View File
File diff suppressed because it is too large Load Diff
+413 -405
View File
File diff suppressed because it is too large Load Diff
+414 -408
View File
File diff suppressed because it is too large Load Diff
+414 -408
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -126,8 +126,8 @@ let
({
owner = "pcsx-redux";
repo = "nugget";
rev = "e9f70ebc993d2a4323368ca88d5eabfe1233e0a0";
hash = "sha256-qwzQXhj/OlnekJPawrWidV5xGGjbB46OcIgR0RidZio=";
rev = "7ffc2b14b3ec4b912e7e79f6e31101ff3f24cc30";
hash = "sha256-cg+zmfd7SjZ9RmpJCLEO4xCCgdJLWCA1f0Ug2pKWSh8=";
dest = "src/mips";
})
({
+16 -1
View File
@@ -30,7 +30,7 @@ bool PCSX::CDRIso::parsemds(const char *isofileString) {
// copy name of the iso and change extension from .mdf to .mds
mdsname = isofile;
isofile.replace_extension("mds");
mdsname.replace_extension("mds");
IO<File> fi(new UvFile(mdsname));
if (fi->failed()) return false;
@@ -49,6 +49,21 @@ bool PCSX::CDRIso::parsemds(const char *isofileString) {
return false;
}
// if we were given the .mds itself, the sectors are in the .mdf
if (isofile == mdsname) {
std::filesystem::path mdfname = isofile;
mdfname.replace_extension("mdf");
IO<File> mdf(new UvFile(mdfname));
if (mdf->failed()) return false;
if (g_emulator->settings.get<Emulator::SettingFullCaching>()) {
mdf.asA<UvFile>()->startCaching();
}
m_cdHandle = mdf;
}
// readCDDA reads tracks through m_ti[1].handle, and CDRIso::open only fills
// it in for multi-track images, so set it here for single-track ones too.
m_ti[1].handle = m_cdHandle;
// get offset to session block
fi->rSeek(0x50, SEEK_SET);
offset = fi->read<uint32_t>();
+51 -1
View File
@@ -331,7 +331,7 @@ bool PCSX::CDRIso::open(IO<File> isoFile) {
m_ti[1].start = IEC60908b::MSF(0, 2, 0);
m_ti[1].pregap = IEC60908b::MSF(0, 0, 0);
m_ti[1].handle = m_cdHandle;
m_ti[1].length = IEC60908b::MSF(m_ti[1].handle->size() / 2352);
m_ti[1].length = IEC60908b::MSF(m_ti[1].handle->size() / (m_isMode1ISO ? 2048 : 2352));
}
if (m_ppf.load(m_isoPath)) {
@@ -422,6 +422,56 @@ PCSX::IEC60908b::MSF PCSX::CDRIso::getPregap(uint8_t track) {
return IEC60908b::MSF(0, 2, 0);
}
bool PCSX::CDRIso::getLocP(const PCSX::IEC60908b::MSF msf, uint8_t locP[8]) {
// TODO: actually check subchannels from iso.
auto length = getTD(0) + IEC60908b::MSF{0, 2, 0};
if (msf >= length) return false;
unsigned track;
for (track = m_numtracks; track != 1; track--) {
if (m_ti[track].start <= msf) break;
}
bool inPregap = false;
IEC60908b::MSF nextPregapStart;
if (msf < m_ti[track].start) {
// Before track 1 starts: its pregap, index 00, counting down.
inPregap = true;
} else if (track != m_numtracks) {
if (msf >= (m_ti[track + 1].start - m_ti[track + 1].pregap)) {
track++;
inPregap = true;
}
}
locP[0] = IEC60908b::itob(track);
locP[1] = inPregap ? 0 : 1;
IEC60908b::MSF relative;
if (inPregap) {
relative = m_ti[track].start - msf;
} else {
relative = msf - m_ti[track].start;
}
relative.toBCD(locP + 2);
msf.toBCD(locP + 5);
return true;
}
unsigned PCSX::CDRIso::getTrack(const PCSX::IEC60908b::MSF msf) {
auto length = getTD(0) + IEC60908b::MSF{0, 2, 0};
if (msf >= length) return 0;
unsigned track;
for (track = m_numtracks; track != 1; track--) {
if (m_ti[track].start <= msf) break;
}
IEC60908b::MSF nextPregapStart;
if (track != m_numtracks) {
if (msf >= (m_ti[track + 1].start - m_ti[track + 1].pregap)) {
track++;
}
}
return track;
}
// Decode 'raw' subchannel data from being packed bitwise.
// Essentially is a bitwise matrix transposition.
void PCSX::CDRIso::decodeRawSubData() {
+8 -5
View File
@@ -48,10 +48,16 @@ class CDRIso {
enum class TrackType { CLOSED = 0, DATA = 1, CDDA = 2 };
TrackType getTrackType(unsigned track) { return m_ti[track].type; }
const std::filesystem::path& getIsoPath() { return m_isoPath; }
uint8_t getTN() { return std::max(m_numtracks, 1); }
uint8_t getTN() { return std::max(m_numtracks, 1U); }
IEC60908b::MSF getTD(uint8_t track);
IEC60908b::MSF getLength(uint8_t track);
IEC60908b::MSF getPregap(uint8_t track);
bool getLocP(const IEC60908b::MSF msf, uint8_t locP[8]);
unsigned getTrack(const IEC60908b::MSF msf);
bool failed();
/* Probably have to change those */
bool readTrack(const IEC60908b::MSF time);
unsigned readSectors(uint32_t lba, void* buffer, unsigned count);
uint8_t* getBuffer();
@@ -59,8 +65,6 @@ class CDRIso {
bool readCDDA(const IEC60908b::MSF msf, unsigned char* buffer);
PPF* getPPF() { return &m_ppf; }
bool failed();
unsigned m_cdrIsoMultidiskCount;
unsigned m_cdrIsoMultidiskSelect;
@@ -132,7 +136,6 @@ class CDRIso {
ECMFILELUT* m_ecm_savetable = nullptr;
static inline const size_t ECM_SECTOR_SIZE[4] = {1, 2352, 2336, 2336};
static inline const uint8_t ZEROADDRESS[4] = {0, 0, 0, 0};
struct trackinfo {
TrackType type = TrackType::CLOSED;
@@ -146,7 +149,7 @@ class CDRIso {
static constexpr unsigned MAXTRACKS = 100; /* How many tracks can a CD hold? */
int m_numtracks = 0;
unsigned m_numtracks = 0;
struct trackinfo m_ti[MAXTRACKS];
// redump.org SBI files
+41 -3
View File
@@ -291,10 +291,47 @@ void DynaRecCPU::recCOP1or3(uint32_t code) {
}
// BCzF / BCzT. None of the coprocessors drives the condition input, so BCzF
// always branches and BCzT never does. Bit 1 of rt is ignored. Like the rest
// of the dynarec, this does not check SR.CUz.
// always branches and BCzT never does. Bit 1 of rt is ignored. With SR.CUz
// clear, the branch raises a coprocessor unusable exception, COP0 included,
// and leaves the block without running the delay slot.
void DynaRecCPU::recBCz(uint32_t code) {
const unsigned z = (code >> 26) & 3;
const auto target = _Imm_ * 4 + m_pc;
Label usable;
flushRegs();
gen.Ldr(w4, MemOperand(contextPointer, COP0_OFFSET(12)));
gen.Tbnz(w4, 28 + z, &usable);
// The load started by the previous instruction still lands
if (m_firstInstruction && m_fullLoadDelayEmulation) {
Label noDelayedLoad;
const auto isActiveOffset = (uintptr_t)&m_runtimeLoadDelay.active - (uintptr_t)this;
gen.Ldrb(w4, MemOperand(contextPointer, isActiveOffset));
gen.Cbz(w4, &noDelayedLoad);
call(m_loadDelayHandler);
gen.L(noDelayedLoad);
}
const auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
if (delayedLoad.active && delayedLoad.index != 0) {
const auto delayedValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
gen.Ldr(w4, MemOperand(contextPointer, delayedValueOffset));
gen.Str(w4, MemOperand(contextPointer, GPR_OFFSET(delayedLoad.index)));
}
loadThisPointer(arg1.X());
gen.Mov(arg2, (static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28));
gen.Mov(arg3, (int32_t)m_inDelaySlot);
gen.Mov(w3, m_pc - 4);
gen.Str(w3, MemOperand(contextPointer, PC_OFFSET));
call(exceptionWrapper);
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET));
gen.Add(x0, x0,
(m_instructionCount + m_romInstructionCount * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET));
jmp((void*)m_returnFromBlock);
gen.L(usable);
m_nextIsDelaySlot = true;
if ((_Rt_ & 1) || target == m_pc + 4) {
@@ -834,7 +871,8 @@ void DynaRecCPU::recMTC0(uint32_t code) {
else {
allocateReg(_Rt_);
if (_Rd_ == 13) {
gen.And(m_gprs[_Rt_].allocatedReg, m_gprs[_Rt_].allocatedReg, ~0xFC00);
gen.And(w0, m_gprs[_Rt_].allocatedReg, ~0xFC00);
gen.Str(w0, MemOperand(contextPointer, COP0_OFFSET(_Rd_)));
} else if (_Rd_ != 6 && _Rd_ != 14 && _Rd_ != 15) { // Don't write to JUMPDEST, EPC or PRID
gen.Str(m_gprs[_Rt_].allocatedReg,
MemOperand(contextPointer, COP0_OFFSET(_Rd_))); // Write rt to the cop0 reg
+88 -2
View File
@@ -367,7 +367,8 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
m_fullLoadDelayEmulation = fullLoadDelayEmulation;
const auto startingPC = m_pc;
int count = 0; // How many instructions have we compiled?
unsigned count = 0; // How many instructions have we compiled?
unsigned extra = 0; // How many instructions from rom?
#if defined(__APPLE__)
gen.setRW(); // Mark code cache as readable/writeable before emitting code
@@ -412,6 +413,9 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
return true;
};
// Target of the branch whose delay slot is compiled next, when it is known at compile time.
std::optional<uint32_t> slotBranchTarget;
// Compile the instruction at m_pc. Returns false if it could not be fetched.
auto compileInstruction = [&]() {
m_inDelaySlot = m_nextIsDelaySlot;
@@ -425,6 +429,10 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
uint32_t code = m_regs.code = *p; // Actually read the instruction
m_pc += 4; // Increment recompiler PC
count++; // Increment instruction count
if ((m_pc & 0xffc00000) == 0xbfc00000) extra++;
m_instructionCount = count;
m_romInstructionCount = extra;
slotBranchTarget = staticBranchTarget(code, m_pc);
const auto func = m_recBSC[code >> 26]; // Look up the opcode in our decoding LUT
(*this.*func)(code); // Jump into the handler to recompile it
@@ -470,7 +478,85 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
processDelayedLoad();
m_firstInstruction = false;
// Ends the block: writes back the registers and the PC, adds the cycles, returns to the dispatcher.
const auto endBlock = [&]() {
flushRegs();
if (!m_pcWrittenBack) {
gen.Mov(w0, m_pc);
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
}
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET));
gen.Add(x0, x0, (count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET));
jmp((void*)m_returnFromBlock);
};
while (shouldContinue()) {
// A branch in the delay slot of a branch that may be taken. When the first branch is taken, the
// second one is relative to the first one's target, and its own delay slot is the instruction at
// that target. Compile that path separately, then fall through to the not-taken path, which is
// an ordinary branch with its delay slot after it.
auto* memory = PCSX::g_emulator->m_mem.get();
uint32_t* slotPtr =
m_nextIsDelaySlot && m_pcWrittenBack && slotBranchTarget ? memory->getPointer<uint32_t>(m_pc) : nullptr;
uint32_t* targetPtr = slotPtr ? memory->getPointer<uint32_t>(*slotBranchTarget) : nullptr;
if (slotPtr && targetPtr && isBranch(*slotPtr)) {
const uint32_t firstTarget = *slotBranchTarget;
const uint32_t slotPC = m_pc;
const auto savedLoads0 = m_delayedLoadInfo[0];
const auto savedLoads1 = m_delayedLoadInfo[1];
const auto savedCurrentLoad = m_currentDelayedLoad;
const auto savedCount = count;
const auto savedExtra = extra;
Label notTaken;
flushRegs();
gen.Ldr(w0, MemOperand(contextPointer, PC_OFFSET));
gen.Mov(w1, firstTarget);
gen.Cmp(w0, w1);
gen.B(&notTaken, ne);
// Taken: the second branch, based on the first branch's target.
m_inDelaySlot = true;
m_nextIsDelaySlot = false;
m_pcWrittenBack = false;
const uint32_t code = m_regs.code = *slotPtr;
m_pc = firstTarget;
count++;
if (((slotPC + 4) & 0xffc00000) == 0xbfc00000) extra++;
m_instructionCount = count;
m_romInstructionCount = extra;
(*this.*m_recBSC[code >> 26])(code);
fixDelaySlotBranchLink(code, slotPC + 8);
if (!m_pcWrittenBack) {
gen.Mov(w0, firstTarget + 4);
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
m_pcWrittenBack = true;
}
processDelayedLoad();
// Its delay slot is the instruction at the first branch's target.
m_nextIsDelaySlot = true;
if (!compileInstruction()) {
return m_invalidBlock;
}
processDelayedLoad();
m_pcWrittenBack = true;
endBlock();
// Not taken: back to the state before the taken path and compile it the ordinary way.
gen.L(notTaken);
m_delayedLoadInfo[0] = savedLoads0;
m_delayedLoadInfo[1] = savedLoads1;
m_currentDelayedLoad = savedCurrentLoad;
count = savedCount;
extra = savedExtra;
m_pc = slotPC;
m_inDelaySlot = false;
m_nextIsDelaySlot = true;
m_pcWrittenBack = true;
m_stopCompiling = true;
m_linkedPC = std::nullopt;
}
if (!compileInstruction()) {
return m_invalidBlock;
}
@@ -489,7 +575,7 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
}
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET)); // Fetch cycle count from memory
gen.Add(x0, x0, count * PCSX::Emulator::BIAS); // Add block cycles
gen.Add(x0, x0, (count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS); // Add block cycles
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET)); // Store cycles back to memory
// Link block else return to dispatcher
+33
View File
@@ -131,6 +131,8 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
DynarecCallback m_needFullLoadDelays;
uint32_t m_pc;
unsigned m_instructionCount; // Instructions compiled so far in the current block
unsigned m_romInstructionCount; // How many of those were fetched from ROM
Emitter gen;
bool m_stopCompiling; // Should we stop compiling code?
@@ -221,6 +223,37 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
// Check if we're executing from valid memory
inline bool isPcValid(uint32_t addr) { return m_recompilerLUT[addr >> 16] != m_dummyBlocks; }
// True for every branch and jump: REGIMM, J, JAL, BEQ, BNE, BLEZ, BGTZ, JR and JALR.
static bool isBranch(uint32_t code) {
const uint32_t op = code >> 26;
if (op == 0) return (code & 0x3e) == 0x08; // JR, JALR
return op >= 1 && op <= 7;
}
// Target of the branch or jump in `code` if it is taken, when it is known at compile time.
// `pc` is the address after the branch, which is the base of a relative branch.
std::optional<uint32_t> staticBranchTarget(uint32_t code, uint32_t pc) {
const uint32_t op = code >> 26;
if (op == 1 || (op >= 4 && op <= 7)) return (uint32_t)((int16_t)code * 4) + pc;
if (op == 2 || op == 3) return (pc & 0xf0000000) | ((code & 0x03ffffff) << 2);
if (op == 0 && (code & 0x3e) == 0x08) {
const unsigned rs = (code >> 21) & 0x1f;
if (m_gprs[rs].isConst()) return m_gprs[rs].val & ~3;
}
return std::nullopt;
}
// A linking branch compiled with a moved base still links to its own address + 8.
void fixDelaySlotBranchLink(uint32_t code, uint32_t link) {
const uint32_t op = code >> 26;
if (op == 3 || (op == 1 && ((code >> 17) & 0xf) == 8)) {
markConst(31, link);
} else if (op == 0 && (code & 0x3f) == 0x09) {
const unsigned rd = (code >> 11) & 0x1f;
if (rd) markConst(rd, link);
}
}
DynarecCallback* getBlockPointer(uint32_t pc);
DynarecCallback recompile(DynarecCallback* callback, uint32_t pc, bool align = true,
bool fullLoadDelayEmulation = false);
+43 -4
View File
@@ -792,7 +792,7 @@ void DynaRecCPU::recompileLoadWithDelay(uint32_t code, LoadDelayDependencyType t
gen.mov(Xbyak::util::byte[contextPointer + isActiveOffset], 1);
gen.mov(dword[contextPointer + indexOffset], _Rt_);
} else {
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
const auto delayedLoadValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
delayedLoad.index = _Rt_;
gen.mov(dword[contextPointer + delayedLoadValueOffset], eax);
@@ -1430,10 +1430,47 @@ void DynaRecCPU::recCOP1or3(uint32_t code) {
}
// BCzF / BCzT. None of the coprocessors drives the condition input, so BCzF
// always branches and BCzT never does. Bit 1 of rt is ignored. Like the rest
// of the dynarec, this does not check SR.CUz.
// always branches and BCzT never does. Bit 1 of rt is ignored. With SR.CUz
// clear, the branch raises a coprocessor unusable exception, COP0 included,
// and leaves the block without running the delay slot.
void DynaRecCPU::recBCz(uint32_t code) {
const unsigned z = (code >> 26) & 3;
const auto target = _Imm_ * 4 + m_pc;
Label usable;
flushRegs();
gen.test(dword[contextPointer + COP0_OFFSET(12)], 0x10000000 << z);
gen.jnz(usable, Xbyak::CodeGenerator::T_NEAR);
// The load started by the previous instruction still lands
if (m_firstInstruction && m_fullLoadDelayEmulation) {
Label noDelayedLoad;
const auto isActiveOffset = (uintptr_t)&m_runtimeLoadDelay.active - (uintptr_t)this;
gen.cmp(Xbyak::util::byte[contextPointer + isActiveOffset], 0);
gen.je(noDelayedLoad);
gen.call((void*)m_loadDelayHandler);
gen.L(noDelayedLoad);
}
const auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
if (delayedLoad.active && delayedLoad.index != 0) {
const auto delayedValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
gen.mov(eax, dword[contextPointer + delayedValueOffset]);
gen.mov(dword[contextPointer + GPR_OFFSET(delayedLoad.index)], eax);
}
loadThisPointer(arg1.cvt64());
gen.moveImm(arg2, (static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28));
gen.moveImm(arg3, (int32_t)m_inDelaySlot);
gen.mov(dword[contextPointer + PC_OFFSET], m_pc - 4);
call(exceptionWrapper);
if constexpr (ENABLE_PROFILER) {
endProfiling();
}
gen.add(qword[contextPointer + CYCLE_OFFSET],
(m_instructionCount + m_romInstructionCount * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
gen.jmp((void*)m_returnFromBlock);
gen.L(usable);
m_nextIsDelaySlot = true;
if ((_Rt_ & 1) || target == m_pc + 4) {
@@ -1468,7 +1505,9 @@ void DynaRecCPU::recMTC0(uint32_t code) {
else {
allocateReg(_Rt_);
if (_Rd_ == 13) {
gen.and_(m_gprs[_Rt_].allocatedReg, ~0xFC00);
gen.mov(eax, m_gprs[_Rt_].allocatedReg);
gen.and_(eax, ~0xFC00);
gen.mov(dword[contextPointer + COP0_OFFSET(_Rd_)], eax);
} else if (_Rd_ != 6 && _Rd_ != 14 && _Rd_ != 15) { // Don't write to JUMPDEST, EPC or PRID
gen.mov(dword[contextPointer + COP0_OFFSET(_Rd_)], m_gprs[_Rt_].allocatedReg); // Write rt to the cop0 reg
}
+85 -2
View File
@@ -334,6 +334,7 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
const auto startingPC = m_pc;
unsigned count = 0; // How many instructions have we compiled?
unsigned extra = 0; // How many instructions in rom?
DynarecCallback* callback = getBlockPointer(m_pc); // Pointer to where we'll store the addr of the emitted code
if (align) {
@@ -395,7 +396,10 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
}
};
const auto compileInstruction = [this, &count, &memory]() -> bool {
// Target of the branch whose delay slot is compiled next, when it is known at compile time.
std::optional<uint32_t> slotBranchTarget;
const auto compileInstruction = [this, &count, &memory, &extra, &slotBranchTarget]() -> bool {
m_inDelaySlot = m_nextIsDelaySlot;
m_nextIsDelaySlot = false;
@@ -405,7 +409,10 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
uint32_t code = m_regs.code = *ptr;
m_pc += 4; // Increment recompiler PC
count++; // Increment instruction count
if ((m_pc & 0xffc00000) == 0xbfc00000) extra++;
m_instructionCount = count;
m_romInstructionCount = extra;
slotBranchTarget = staticBranchTarget(code, m_pc);
const auto func = m_recBSC[code >> 26]; // Look up the opcode in our decoding LUT
(*this.*func)(code); // Jump into the handler to recompile it
@@ -434,7 +441,82 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
processDelayedLoad();
m_firstInstruction = false;
// Ends the block: writes back the registers and the PC, adds the cycles, returns to the dispatcher.
const auto endBlock = [this, &count, &extra]() {
flushRegs();
if (!m_pcWrittenBack) {
gen.mov(dword[contextPointer + PC_OFFSET], m_pc);
}
if constexpr (ENABLE_PROFILER) {
endProfiling();
}
gen.add(qword[contextPointer + CYCLE_OFFSET],
(count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
gen.jmp((void*)m_returnFromBlock);
};
while (shouldContinue()) {
// A branch in the delay slot of a branch that may be taken. When the first branch is taken, the
// second one is relative to the first one's target, and its own delay slot is the instruction at
// that target. Compile that path separately, then fall through to the not-taken path, which is
// an ordinary branch with its delay slot after it.
uint32_t* slotPtr =
m_nextIsDelaySlot && m_pcWrittenBack && slotBranchTarget ? memory->getPointer<uint32_t>(m_pc) : nullptr;
uint32_t* targetPtr = slotPtr ? memory->getPointer<uint32_t>(*slotBranchTarget) : nullptr;
if (slotPtr && targetPtr && isBranch(*slotPtr)) {
const uint32_t firstTarget = *slotBranchTarget;
const uint32_t slotPC = m_pc;
const auto savedLoads0 = m_delayedLoadInfo[0];
const auto savedLoads1 = m_delayedLoadInfo[1];
const auto savedCurrentLoad = m_currentDelayedLoad;
const auto savedCount = count;
const auto savedExtra = extra;
Label notTaken;
flushRegs();
gen.cmp(dword[contextPointer + PC_OFFSET], firstTarget);
gen.jne(notTaken, Xbyak::CodeGenerator::T_NEAR);
// Taken: the second branch, based on the first branch's target.
m_inDelaySlot = true;
m_nextIsDelaySlot = false;
m_pcWrittenBack = false;
const uint32_t code = m_regs.code = *slotPtr;
m_pc = firstTarget;
count++;
if (((slotPC + 4) & 0xffc00000) == 0xbfc00000) extra++;
m_instructionCount = count;
m_romInstructionCount = extra;
(*this.*m_recBSC[code >> 26])(code);
fixDelaySlotBranchLink(code, slotPC + 8);
if (!m_pcWrittenBack) {
gen.mov(dword[contextPointer + PC_OFFSET], firstTarget + 4);
m_pcWrittenBack = true;
}
processDelayedLoad();
// Its delay slot is the instruction at the first branch's target.
m_nextIsDelaySlot = true;
if (!compileInstruction()) {
return m_invalidBlock;
}
processDelayedLoad();
m_pcWrittenBack = true;
endBlock();
// Not taken: back to the state before the taken path and compile it the ordinary way.
gen.L(notTaken);
m_delayedLoadInfo[0] = savedLoads0;
m_delayedLoadInfo[1] = savedLoads1;
m_currentDelayedLoad = savedCurrentLoad;
count = savedCount;
extra = savedExtra;
m_pc = slotPC;
m_inDelaySlot = false;
m_nextIsDelaySlot = true;
m_pcWrittenBack = true;
m_stopCompiling = true;
m_linkedPC = std::nullopt;
}
if (!compileInstruction()) {
return m_invalidBlock;
}
@@ -454,7 +536,8 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
endProfiling();
}
gen.add(qword[contextPointer + CYCLE_OFFSET], count * PCSX::Emulator::BIAS); // Add block cycles;
gen.add(qword[contextPointer + CYCLE_OFFSET],
(count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS); // Add block cycles;
if (m_linkedPC && ENABLE_BLOCK_LINKING && m_linkedPC.value() != startingPC) {
handleLinking();
} else {
+34 -2
View File
@@ -133,8 +133,9 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
DynarecCallback m_needFullLoadDelays;
Emitter gen;
uint32_t m_pc; // Recompiler PC
unsigned m_instructionCount; // Instructions compiled so far in the current block
uint32_t m_pc; // Recompiler PC
unsigned m_instructionCount; // Instructions compiled so far in the current block
unsigned m_romInstructionCount; // How many of those were fetched from ROM
bool m_stopCompiling; // Should we stop compiling code?
bool m_pcWrittenBack; // Has the PC been written back already by a jump?
@@ -397,6 +398,37 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
// Check if we're executing from valid memory
inline bool isPcValid(uint32_t addr) { return m_recompilerLUT[addr >> 16] != m_dummyBlocks; }
// True for every branch and jump: REGIMM, J, JAL, BEQ, BNE, BLEZ, BGTZ, JR and JALR.
static bool isBranch(uint32_t code) {
const uint32_t op = code >> 26;
if (op == 0) return (code & 0x3e) == 0x08; // JR, JALR
return op >= 1 && op <= 7;
}
// Target of the branch or jump in `code` if it is taken, when it is known at compile time.
// `pc` is the address after the branch, which is the base of a relative branch.
std::optional<uint32_t> staticBranchTarget(uint32_t code, uint32_t pc) {
const uint32_t op = code >> 26;
if (op == 1 || (op >= 4 && op <= 7)) return (uint32_t)((int16_t)code * 4) + pc;
if (op == 2 || op == 3) return (pc & 0xf0000000) | ((code & 0x03ffffff) << 2);
if (op == 0 && (code & 0x3e) == 0x08) {
const unsigned rs = (code >> 21) & 0x1f;
if (m_gprs[rs].isConst()) return m_gprs[rs].val & ~3;
}
return std::nullopt;
}
// A linking branch compiled with a moved base still links to its own address + 8.
void fixDelaySlotBranchLink(uint32_t code, uint32_t link) {
const uint32_t op = code >> 26;
if (op == 3 || (op == 1 && ((code >> 17) & 0xf) == 8)) {
markConst(31, link);
} else if (op == 0 && (code & 0x3f) == 0x09) {
const unsigned rd = (code >> 11) & 0x1f;
if (rd) markConst(rd, link);
}
}
DynarecCallback* getBlockPointer(uint32_t pc);
DynarecCallback recompile(uint32_t pc, bool fullLoadDelayEmulation, bool align = true);
void error();
+1582 -1537
View File
File diff suppressed because it is too large Load Diff
+151 -89
View File
@@ -21,6 +21,7 @@
#include <memory>
#include <string>
#include <string_view>
#include "cdrom/cdriso.h"
#include "core/decode_xa.h"
@@ -38,21 +39,30 @@ namespace Widgets {
class IsoBrowser;
}
struct CdrStat {
uint32_t Type;
uint32_t Status;
IEC60908b::MSF Time;
};
class CDRom {
public:
using MSF = PCSX::IEC60908b::MSF;
using MSF = IEC60908b::MSF;
CDRom() : m_iso(new CDRIso(new FailedFile)) {}
virtual ~CDRom() {}
static CDRom* factory();
bool isLidOpened() { return m_lidOpenTime < 0 || m_lidOpenTime > (int64_t)time(nullptr); }
void setLidOpenTime(int64_t time) { m_lidOpenTime = time; }
void check();
bool isLidOpen();
void closeLid() {
m_lidOpen = false;
m_lidCloseScheduled = false;
}
void openLid() {
m_lidOpen = true;
m_wasLidOpened = true;
m_lidCloseScheduled = false;
}
void scheduleCloseLid() {
m_lidOpen = true;
m_wasLidOpened = true;
m_lidCloseScheduled = true;
using namespace std::chrono_literals;
m_lidCloseAtCycles = g_emulator->m_cpu->m_regs.getFutureCycle(1s);
}
void parseIso();
std::shared_ptr<CDRIso> getIso() { return m_iso; }
void clearIso() {
@@ -68,24 +78,19 @@ class CDRom {
const std::string& getCDRomLabel() { return m_cdromLabel; }
virtual void reset() = 0;
virtual void attenuate(int16_t* buf, int samples, int stereo) = 0;
virtual void interrupt() = 0;
virtual void readInterrupt() = 0;
virtual void decodedBufferInterrupt() = 0;
virtual void lidSeekInterrupt() = 0;
virtual void playInterrupt() = 0;
virtual void dmaInterrupt() = 0;
virtual void lidInterrupt() = 0;
virtual uint8_t read0(void) = 0;
virtual uint8_t read1(void) = 0;
virtual uint8_t read2(void) = 0;
virtual uint8_t read3(void) = 0;
virtual void fifoScheduledCallback() = 0;
virtual void commandsScheduledCallback() = 0;
virtual void readScheduledCallback() = 0;
virtual void scheduledDmaCallback() = 0;
virtual uint8_t read0() = 0;
virtual uint8_t read1() = 0;
virtual uint8_t read2() = 0;
virtual uint8_t read3() = 0;
virtual void write0(uint8_t rt) = 0;
virtual void write1(uint8_t rt) = 0;
virtual void write2(uint8_t rt) = 0;
virtual void write3(uint8_t rt) = 0;
virtual void load() = 0;
virtual void dma(uint32_t madr, uint32_t bcr, uint32_t chcr) = 0;
@@ -93,74 +98,131 @@ class CDRom {
protected:
std::shared_ptr<CDRIso> m_iso;
// savestate stuff starts here
uint8_t m_reg1Mode;
uint8_t m_reg2;
uint8_t m_cmdProcess;
uint8_t m_ctrl;
uint8_t m_stat;
uint8_t m_statP;
uint8_t m_transfer[PCSX::IEC60908b::FRAMESIZE_RAW];
unsigned int m_transferIndex;
MSF m_prev;
uint8_t m_param[8];
uint8_t m_result[16];
uint8_t m_paramC;
uint8_t m_resultC;
uint8_t m_resultP;
uint8_t m_resultReady;
uint8_t m_cmd;
uint8_t m_read;
uint8_t m_setlocPending;
bool m_locationChanged;
uint32_t m_reading;
MSF m_setSectorPlay;
MSF m_setSectorEnd;
MSF m_setSector;
uint8_t m_track;
bool m_play, m_muted;
int m_curTrack;
int m_mode, m_file, m_channel;
bool m_suceeded;
int m_firstSector;
public:
// this belongs in the SPU, not here.
xa_decode_t m_xa;
protected:
int64_t m_lidOpenTime = 0;
uint16_t m_irq;
uint8_t m_irqRepeated;
uint32_t m_eCycle;
uint8_t m_seeked;
uint8_t m_readRescheduled;
uint8_t m_driveState;
uint8_t m_fastForward;
uint8_t m_fastBackward;
uint8_t m_attenuatorLeftToLeft, m_attenuatorLeftToRight;
uint8_t m_attenuatorRightToRight, m_attenuatorRightToLeft;
uint8_t m_attenuatorLeftToLeftT, m_attenuatorLeftToRightT;
uint8_t m_attenuatorRightToRightT, m_attenuatorRightToLeftT;
struct {
uint8_t track;
uint8_t index;
uint8_t relative[3];
uint8_t absolute[3];
} m_subq;
bool m_trackChanged;
// end savestate
friend SaveStates::SaveState SaveStates::constructSaveState();
bool dataFIFOHasData() { return m_dataFIFOIndex != m_dataFIFOSize; }
bool m_lidOpen = false;
bool m_wasLidOpened = false;
bool m_lidCloseScheduled = false;
uint32_t m_lidCloseAtCycles = 0;
// to save/init
uint64_t m_seed = 9223521712174600777ull;
uint8_t m_dataFIFO[2352] = {0};
uint32_t m_dataFIFOIndex = 0;
uint32_t m_dataFIFOSize = 0;
uint32_t m_dataFIFOPending = 0;
uint8_t m_registerAddress = 0;
bool m_motorOn = false;
bool m_speedChanged = false;
bool m_invalidLocL = false;
bool m_dataRequested = false;
bool m_subheaderFilter = false;
bool m_realtime = false;
enum class ReadingState : uint8_t {
None,
Seeking,
Reading,
} m_readingState = ReadingState::None;
bool m_startPlaying = false;
enum class ReadingType : uint8_t {
None,
Normal,
Streaming,
Play,
} m_readingType = ReadingType::None;
bool m_autoPause = false;
bool m_report = false;
bool m_setLocPending = false;
bool m_muted = false;
bool m_peakFlag = false;
uint8_t m_playTrack = 0;
uint32_t m_playStartCycle = 0;
enum class Status : uint8_t {
Idle,
ReadingData,
Seeking,
PlayingCDDA,
} m_status = Status::Idle;
enum class Speed : uint8_t { Simple, Double } m_speed;
enum class ReadSpan : uint8_t { S2048, S2328, S2340 } m_readSpan;
uint8_t m_interruptCauseMask = 0x1f;
uint8_t m_atv[4] = {0x80, 0, 0x80, 0};
uint8_t m_atvPending[4] = {0x80, 0, 0x80, 0};
bool m_adpcmMuted = false;
uint8_t m_mode = 0;
uint8_t m_filterFile = 0;
uint8_t m_filterChannel = 0;
bool m_xaEnded = false;
bool m_xaFirstSector = true;
xa_decode_t m_xa;
xa_decode_t m_xaOut;
bool m_soundMapEnabled = false;
enum class Cause : uint8_t {
None = 0,
DataReady = 1,
Complete = 2,
Acknowledge = 3,
End = 4,
Error = 5,
};
MSF m_currentPosition;
MSF m_seekPosition;
uint8_t m_lastLocP[8] = {0};
uint8_t m_lastLocL[8] = {0};
struct QueueElement {
uint8_t payload[16];
uint8_t value;
bool valueRead = false;
bool hasValue = false;
bool hitMax = false;
uint8_t payloadSize = 0;
uint8_t payloadIndex = 0;
bool isPayloadAtEnd() const { return payloadIndex == payloadSize; }
bool isPayloadEmpty() const { return (payloadSize == 0) || hitMax; }
bool isPayloadFull() const { return payloadSize == sizeof(payload); }
bool empty() const { return valueEmpty() && isPayloadEmpty(); }
bool valueEmpty() const { return !hasValue || valueRead; }
void clear() {
hasValue = false;
valueRead = false;
payloadSize = 0;
payloadIndex = 0;
}
void setValue(uint8_t newValue) {
value = newValue;
hasValue = true;
}
void setValue(Cause cause) { setValue(static_cast<uint8_t>(cause)); }
void pushPayloadData(uint8_t value) {
if (payloadSize < sizeof(payload)) payload[payloadSize++] = value;
}
void pushPayloadData(std::string_view values) {
for (auto value : values) {
pushPayloadData(value);
}
}
uint8_t getValue() const { return valueRead ? 0 : value; }
uint8_t readPayloadByte() {
while (payloadIndex >= 16) payloadIndex -= 16;
uint8_t r = 0;
if (payloadIndex < payloadSize) {
r = payload[payloadIndex];
}
if (++payloadIndex == payloadSize) hitMax = true;
return r;
}
};
QueueElement m_commandFifo;
QueueElement m_commandExecuting;
QueueElement m_responseFifo[2];
bool responseFifoFull() { return !m_responseFifo[0].valueEmpty() && !m_responseFifo[1].empty(); }
private:
friend class Widgets::IsoBrowser;
std::string m_cdromId;
+1 -1
View File
@@ -206,7 +206,7 @@ void PCSX::LuaBindings::open_events(Lua L) {
} else if (name == "Memory::SetLuts") {
createListener<Events::Memory::SetLuts>(L);
} else {
return L.error("createListener: unknown event name");
return L.error("createEventListener: unknown event name '%s'", name.c_str());
}
return 1;
},
+1 -1
View File
@@ -767,7 +767,7 @@ void PCSX::GdbClient::processMonitorCommand(const std::string& cmd) {
auto pathCmd = cmd.substr(8);
auto pathView = StringsHelpers::trim(pathCmd);
g_emulator->m_cdrom->setIso(new CDRIso(pathView));
g_emulator->m_cdrom->check();
g_emulator->m_cdrom->parseIso();
}
} else if (words[0] == "sharedmem") {
if (words.size() != 2) {
+9
View File
@@ -35,6 +35,7 @@
#define GPUSTATUS_READYFORVRAM 0x08000000
#define GPUSTATUS_IDLE 0x04000000 // CMD ready
#define GPUSTATUS_MODE 0x02000000 // Data request mode
#define GPUSTATUS_IRQ1 0x01000000 // GP0(1Fh) / GP1(02h)
namespace PCSX {
@@ -377,6 +378,7 @@ uint32_t PCSX::GPU::readStatus() {
if ((ret & GPUSTATUS_IDLE) == 0) ret &= ~GPUSTATUS_READYFORVRAM;
#endif
if (m_readFifo->size() != 0) ret |= GPUSTATUS_READYFORVRAM;
if (m_irq1Flag) ret |= GPUSTATUS_IRQ1;
// Let's pretend our input fifo is always ready for more data.
if ((ret & 0x60000000) == 0x20000000) ret |= 0x02000000;
return ret;
@@ -671,6 +673,9 @@ void PCSX::GPU::Command::processWrite(Buffer &buf, Logged::Origin origin, uint32
m_gpu->m_fastFill.setActive();
m_gpu->m_fastFill.processWrite(buf, origin, originValue, length);
} break;
case 0x1f: { // interrupt request (IRQ1)
m_gpu->requestIRQ1();
} break;
default: {
gotUnknown = true;
} break;
@@ -779,6 +784,10 @@ void PCSX::GPU::FastFill::processWrite(Buffer &buf, Logged::Origin origin, uint3
raw.y = y;
raw.w = w;
raw.h = h;
// GP0(02h) parameter masking (psx-spx): Xpos ignores its low 4 bits,
// Xsiz rounds up to the next multiple of 16 within 0..400h.
x &= 0x3f0;
w = ((w & 0x3ff) + 0xf) & ~0xf;
clipped = GPU::clip(x, y, w, h);
m_state = READ_COLOR;
m_gpu->m_defaultProcessor.setActive();
+5 -2
View File
@@ -98,12 +98,13 @@ class GPU {
void dma(uint32_t madr, uint32_t bcr, uint32_t chcr);
static void gpuInterrupt();
// These functions do not touch GPUSTAT. GPU backends should mirror the IRQ status into GPUSTAT
// when readStatus is called
// These set and clear the IRQ1 flag, which readStatus mirrors into GPUSTAT.24.
void requestIRQ1() {
m_irq1Flag = true;
if (!m_detached) g_emulator->m_mem->setIRQ(2);
}
void acknowledgeIRQ1() {
m_irq1Flag = false;
if (!m_detached) g_emulator->m_mem->clearIRQ(2);
}
@@ -166,6 +167,7 @@ class GPU {
virtual void setDither(int setting) = 0;
void reset() {
resetBackend();
m_irq1Flag = false;
m_dataRet = 0;
m_readFifo->reset();
m_processor->reset();
@@ -251,6 +253,7 @@ class GPU {
private:
uint32_t m_statusControl[256];
bool m_irq1Flag = false;
uint32_t m_envRaw[7];
bool m_detached = false;
GPULogger *m_logger = nullptr;
+5
View File
@@ -28,6 +28,7 @@ enum SectorMode {
typedef struct { char opaque[?]; } LuaIso;
typedef struct { char opaque[?]; } IsoReader;
typedef struct { uint8_t m, s, f; } MSF;
void deleteIso(LuaIso* wrapper);
bool isIsoFailed(LuaIso* wrapper);
@@ -42,6 +43,8 @@ void deleteIsoReader(IsoReader* isoReader);
bool isReaderFailed(IsoReader* reader);
LuaFile* readerOpen(IsoReader* reader, const char* path);
LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size, enum SectorMode mode);
uint32_t getIsoTN(LuaIso* wrapper);
MSF getIsoTD(LuaIso* wrapper, uint32_t tn);
typedef struct { char opaque[?]; } DirEntries;
DirEntries* readerListDir(IsoReader* reader, const char* path);
@@ -189,6 +192,8 @@ local function createIsoWrapper(wrapper)
if mode == nil then mode = 'GUESS' end
return Support.File._createFileWrapper(C.fileisoOpen(self._wrapper, lba, size, mode))
end,
getTD = function(self, tn) return C.getIsoTD(self._wrapper, tn) end,
getTN = function(self) return C.getIsoTN(self._wrapper) end,
}
return iso
end
+12
View File
@@ -30,6 +30,7 @@
#include "lua/luafile.h"
#include "lua/luawrapper.h"
#include "support/strings-helpers.h"
#include "supportpsx/iec-60908b.h"
#include "supportpsx/iso9660-builder.h"
namespace {
@@ -39,6 +40,10 @@ struct LuaIso {
std::shared_ptr<PCSX::CDRIso> iso;
};
struct MSF {
uint8_t m, s, f;
};
void deleteIso(LuaIso* wrapper) { delete wrapper; }
bool isIsoFailed(LuaIso* wrapper) { return wrapper->iso->failed(); }
void isoClearPPF(LuaIso* wrapper) { wrapper->iso->getPPF()->clear(); }
@@ -59,6 +64,11 @@ PCSX::LuaFFI::LuaFile* readerOpen(PCSX::ISO9660Reader* reader, const char* path)
PCSX::LuaFFI::LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size, PCSX::IEC60908b::SectorMode mode) {
return new PCSX::LuaFFI::LuaFile(new PCSX::CDRIsoFile(wrapper->iso, lba, size, mode));
}
uint32_t getIsoTN(LuaIso* wrapper) { return wrapper->iso->getTN(); }
MSF getIsoTD(LuaIso* wrapper, uint32_t tn) {
auto ret = wrapper->iso->getTD(tn);
return {ret.m, ret.s, ret.f};
}
struct DirEntries {
std::vector<PCSX::ISO9660Reader::FullDirEntry> entries;
@@ -433,6 +443,8 @@ static void registerAllSymbols(PCSX::Lua L) {
REGISTER(L, getCurrentIso);
REGISTER(L, openIso);
REGISTER(L, openIsoFromFile);
REGISTER(L, getIsoTD);
REGISTER(L, getIsoTN);
REGISTER(L, createIsoReader);
REGISTER(L, deleteIsoReader);
REGISTER(L, isReaderFailed);
+5 -5
View File
@@ -733,10 +733,10 @@ void PCSX::MDEC::dma0(uint32_t adr, uint32_t bcr, uint32_t chcr) {
break;
}
mdec0Interrupt();
scheduledCallback0();
}
void PCSX::MDEC::mdec0Interrupt() {
void PCSX::MDEC::scheduledCallback0() {
auto &mem = g_emulator->m_mem;
mem->clearDMABusy<0>();
mem->dmaInterrupt<0>();
@@ -836,7 +836,7 @@ void PCSX::MDEC::dma1(uint32_t adr, uint32_t bcr, uint32_t chcr) {
}
}
void PCSX::MDEC::mdec1Interrupt() {
void PCSX::MDEC::scheduledCallback1() {
/* Author : gschwind
*
* in that case we have done all decoding stuff
@@ -864,11 +864,11 @@ void PCSX::MDEC::mdec1Interrupt() {
/* this else if avoid to read outside memory */
if (mdec.rl >= mdec.rl_end) {
mdec.reg1 &= ~MDEC1_STP;
mdec0Interrupt();
scheduledCallback0();
mdec.reg1 &= ~MDEC1_BUSY;
} else if (SWAP_LE16(*(mdec.rl)) == MDEC_END_OF_DATA) {
mdec.reg1 &= ~MDEC1_STP;
mdec0Interrupt();
scheduledCallback0();
mdec.reg1 &= ~MDEC1_BUSY;
}
+2 -2
View File
@@ -37,8 +37,8 @@ class MDEC {
uint32_t read1();
void dma0(uint32_t madr, uint32_t bcr, uint32_t chcr);
void dma1(uint32_t madr, uint32_t bcr, uint32_t chcr);
void mdec0Interrupt();
void mdec1Interrupt();
void scheduledCallback0();
void scheduledCallback1();
static const unsigned DSIZE = 8;
static const unsigned DSIZE2 = DSIZE * DSIZE;
+3
View File
@@ -668,6 +668,9 @@ void PadsImpl::Pad::getButtons() {
auto getKeyboardButtons = [this]() -> uint16_t {
if (!ImGui::GetCurrentContext()) return 0xffff;
// Don't feed host keys into the emulated pad while ImGui text fields
// own the keyboard (typing in the UI would otherwise hit in-game).
if (ImGui::GetIO().WantTextInput) return 0xffff;
uint16_t result = 0;
for (unsigned i = 0; i < 16; i++) {
auto key = SdlScancodeToImGuiKey(m_scancodes[i]);
+5 -5
View File
@@ -23,23 +23,23 @@
#include "core/r3000a.h"
static inline void scheduleGPUDMAIRQ(uint32_t eCycle) {
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_GPUDMA, eCycle);
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::GPUDMA, eCycle);
}
static inline void scheduleSPUDMAIRQ(uint32_t eCycle) {
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_SPUDMA, eCycle);
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::SPUDMA, eCycle);
}
static inline void scheduleMDECOUTDMAIRQ(uint32_t eCycle) {
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_MDECOUTDMA, eCycle);
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::MDECOUTDMA, eCycle);
}
static inline void scheduleMDECINDMAIRQ(uint32_t eCycle) {
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_MDECINDMA, eCycle);
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::MDECINDMA, eCycle);
}
static inline void scheduleGPUOTCDMAIRQ(uint32_t eCycle) {
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_GPUOTCDMA, eCycle);
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::GPUOTCDMA, eCycle);
}
void dma4(uint32_t madr, uint32_t bcr, uint32_t chcr);
+10 -8
View File
@@ -115,6 +115,7 @@ class Emulator {
typedef Setting<uint32_t, TYPESTRING("FirstChanceException"), 0x00001cf0> FirstChanceException;
typedef Setting<bool, TYPESTRING("SkipISR")> SkipISR;
typedef Setting<bool, TYPESTRING("LoggingCDROM"), false> LoggingCDROM;
typedef Setting<bool, TYPESTRING("LoggingHWCDROM"), false> LoggingHWCDROM;
typedef Setting<bool, TYPESTRING("GdbServer"), false> GdbServer;
typedef Setting<bool, TYPESTRING("GdbManifest"), true> GdbManifest;
enum class GdbLog {
@@ -150,12 +151,12 @@ class Emulator {
Raw,
};
typedef Setting<SIO1Mode, TYPESTRING("SIO1Mode"), SIO1Mode::Protobuf> SIO1ModeSetting;
typedef Settings<Debug, Trace, KernelLog, FirstChanceException, SkipISR, LoggingCDROM, GdbServer, GdbManifest,
GdbLogSetting, GdbServerPort, GdbServerTrace, WebServer, WebServerPort, KernelCallA0_00_1f,
KernelCallA0_20_3f, KernelCallA0_40_5f, KernelCallA0_60_7f, KernelCallA0_80_9f,
KernelCallA0_a0_bf, KernelCallB0_00_1f, KernelCallB0_20_3f, KernelCallB0_40_5f,
KernelCallC0_00_1f, DemangledSymbols, PCdrv, PCdrvBase, SIO1Server, SIO1ServerPort, SIO1Client,
SIO1ClientHost, SIO1ClientPort, SIO1ModeSetting>
typedef Settings<Debug, Trace, KernelLog, FirstChanceException, SkipISR, LoggingCDROM, LoggingHWCDROM,
GdbServer, GdbManifest, GdbLogSetting, GdbServerPort, GdbServerTrace, WebServer, WebServerPort,
KernelCallA0_00_1f, KernelCallA0_20_3f, KernelCallA0_40_5f, KernelCallA0_60_7f,
KernelCallA0_80_9f, KernelCallA0_a0_bf, KernelCallB0_00_1f, KernelCallB0_20_3f,
KernelCallB0_40_5f, KernelCallC0_00_1f, DemangledSymbols, PCdrv, PCdrvBase, SIO1Server,
SIO1ServerPort, SIO1Client, SIO1ClientHost, SIO1ClientPort, SIO1ModeSetting>
type;
};
typedef SettingNested<TYPESTRING("Debug"), DebugSettings::type> SettingDebugSettings;
@@ -242,8 +243,9 @@ class Emulator {
// Make the timing events trigger faster as we are currently assuming everything
// takes one cycle, which is not the case on real hardware.
// FIXME: Count the proper cycle and get rid of this
uint32_t m_psxClockSpeed = 33868800 /* 33.8688 MHz */;
enum { BIAS = 2 };
static constexpr uint32_t m_psxClockSpeed = 33868800 /* 33.8688 MHz */;
static constexpr uint32_t BIAS = 2;
static constexpr uint32_t ROM_EXTRA_BIAS = 10;
template <unsigned alignment = 1>
requires((alignment == 1) || (alignment == 4))
+56 -50
View File
@@ -79,13 +79,27 @@
#define _rLo_ m_regs.GPR.n.lo // The LO register
#define _JumpTarget_ ((_Target_ * 4) + (_PC_ & 0xf0000000)) // Calculates the target during a jump instruction
#define _BranchTarget_ ((int16_t)_Im_ * 4 + _PC_) // Calculates the target during a branch instruction
// A relative branch adds its offset to the PC register. In the delay slot of a taken branch, the PC
// register already holds the first branch's target, so that is the base.
#define _BranchTarget_ ((int16_t)_Im_ * 4 + branchBase())
class InterpretedCPU final : public PCSX::R3000Acpu {
public:
InterpretedCPU() : R3000Acpu("Interpreted") {}
private:
// An exception taken in a branch delay slot cancels the pending branch. While the delay slot
// executes, that branch sits in the other delayed load slot.
// A BREAK that PCdrv handles takes no exception, so the branch stays pending.
bool exception(uint32_t code, bool bd, bool cop0 = false) {
bool taken = R3000Acpu::exception(code, bd, cop0);
if (taken && bd) m_delayedLoadInfo[m_currentDelayedLoad ^ 1].pcActive = false;
return taken;
}
bool exception(Exception e, bool bd, bool cop0 = false) {
return exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
}
virtual bool Implemented() final { return true; }
virtual bool Init() override;
virtual void Reset() override;
@@ -117,6 +131,7 @@ class InterpretedCPU final : public PCSX::R3000Acpu {
template <bool debug, bool trace>
void execBlock();
void doBranch(uint32_t target, bool fromLink);
uint32_t branchBase();
void MTC0(int reg, uint32_t val);
@@ -333,6 +348,11 @@ class InterpretedCPU final : public PCSX::R3000Acpu {
static const intFunc_t s_pgxpPsxBSCMem[64];
};
inline uint32_t InterpretedCPU::branchBase() {
auto &pending = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
return pending.pcActive ? pending.pcValue : m_regs.pc;
}
inline void InterpretedCPU::doBranch(uint32_t target, bool fromLink) {
m_nextIsDelaySlot = true;
delayedPCLoad(target, fromLink);
@@ -376,7 +396,7 @@ void InterpretedCPU::psxADDI(uint32_t code) {
void InterpretedCPU::psxADDIU(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _u32(_rRs_) + _Imm_;
uint32_t newValue = _rRs_ + _Imm_;
if (_Rt_ == 29) {
if (_Rs_ == 29) {
PCSX::g_emulator->m_callStacks->offsetSP(_rRt_, _Imm_);
@@ -390,7 +410,7 @@ void InterpretedCPU::psxADDIU(uint32_t code) {
void InterpretedCPU::psxANDI(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _u32(_rRs_) & _ImmU_;
uint32_t newValue = _rRs_ & _ImmU_;
if (_Rt_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
}
@@ -399,7 +419,7 @@ void InterpretedCPU::psxANDI(uint32_t code) {
void InterpretedCPU::psxORI(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _u32(_rRs_) | _ImmU_;
uint32_t newValue = _rRs_ | _ImmU_;
if (_Rt_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
}
@@ -408,7 +428,7 @@ void InterpretedCPU::psxORI(uint32_t code) {
void InterpretedCPU::psxXORI(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _u32(_rRs_) ^ _ImmU_;
uint32_t newValue = _rRs_ ^ _ImmU_;
if (_Rt_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
}
@@ -417,7 +437,7 @@ void InterpretedCPU::psxXORI(uint32_t code) {
void InterpretedCPU::psxSLTI(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _i32(_rRs_) < _Imm_;
uint32_t newValue = int32_t(_rRs_) < _Imm_;
if (_Rt_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
}
@@ -426,7 +446,7 @@ void InterpretedCPU::psxSLTI(uint32_t code) {
void InterpretedCPU::psxSLTIU(uint32_t code) {
if (!_Rt_) return;
maybeCancelDelayedLoad(_Rt_);
uint32_t newValue = _u32(_rRs_) < ((uint32_t)_Imm_);
uint32_t newValue = _rRs_ < ((uint32_t)_Imm_);
if (_Rt_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
}
@@ -523,7 +543,7 @@ void InterpretedCPU::psxSUBU(uint32_t code) {
void InterpretedCPU::psxAND(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRs_) & _u32(_rRt_);
uint32_t newValue = _rRs_ & _rRt_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -532,7 +552,7 @@ void InterpretedCPU::psxAND(uint32_t code) {
void InterpretedCPU::psxOR(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRs_) | _u32(_rRt_);
uint32_t newValue = _rRs_ | _rRt_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -541,7 +561,7 @@ void InterpretedCPU::psxOR(uint32_t code) {
void InterpretedCPU::psxXOR(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRs_) ^ _u32(_rRt_);
uint32_t newValue = _rRs_ ^ _rRt_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -550,7 +570,7 @@ void InterpretedCPU::psxXOR(uint32_t code) {
void InterpretedCPU::psxNOR(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = ~(_u32(_rRs_) | _u32(_rRt_));
uint32_t newValue = ~(_rRs_ | _rRt_);
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -559,7 +579,7 @@ void InterpretedCPU::psxNOR(uint32_t code) {
void InterpretedCPU::psxSLT(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _i32(_rRs_) < _i32(_rRt_);
uint32_t newValue = int32_t(_rRs_) < int32_t(_rRt_);
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -568,7 +588,7 @@ void InterpretedCPU::psxSLT(uint32_t code) {
void InterpretedCPU::psxSLTU(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRs_) < _u32(_rRt_);
uint32_t newValue = _rRs_ < _rRt_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -629,7 +649,7 @@ void InterpretedCPU::psxMULTU(uint32_t code) {
* Format: OP rs, offset *
*********************************************************/
#define RepZBranchi32(op) \
if (_i32(_rRs_) op 0) { \
if (int32_t(_rRs_) op 0) { \
doBranch(_BranchTarget_, false); \
}
#define RepZBranchLinki32(op) \
@@ -657,7 +677,7 @@ void InterpretedCPU::psxBLTZAL(uint32_t code) { RepZBranchLinki32(<); } // Bra
void InterpretedCPU::psxSLL(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRt_) << _Sa_;
uint32_t newValue = _rRt_ << _Sa_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -666,7 +686,7 @@ void InterpretedCPU::psxSLL(uint32_t code) {
void InterpretedCPU::psxSRA(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _i32(_rRt_) >> _Sa_;
uint32_t newValue = int32_t(_rRt_) >> _Sa_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -675,7 +695,7 @@ void InterpretedCPU::psxSRA(uint32_t code) {
void InterpretedCPU::psxSRL(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRt_) >> _Sa_;
uint32_t newValue = _rRt_ >> _Sa_;
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -689,7 +709,7 @@ void InterpretedCPU::psxSRL(uint32_t code) {
void InterpretedCPU::psxSLLV(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRt_) << (_u32(_rRs_) & 0x1f);
uint32_t newValue = _rRt_ << (_rRs_ & 0x1f);
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -698,7 +718,7 @@ void InterpretedCPU::psxSLLV(uint32_t code) {
void InterpretedCPU::psxSRAV(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _i32(_rRt_) >> (_u32(_rRs_) & 0x1f);
uint32_t newValue = int32_t(_rRt_) >> (_rRs_ & 0x1f);
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -707,7 +727,7 @@ void InterpretedCPU::psxSRAV(uint32_t code) {
void InterpretedCPU::psxSRLV(uint32_t code) {
if (!_Rd_) return;
maybeCancelDelayedLoad(_Rd_);
uint32_t newValue = _u32(_rRt_) >> (_u32(_rRs_) & 0x1f);
uint32_t newValue = _rRt_ >> (_rRs_ & 0x1f);
if (_Rd_ == 29) {
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
}
@@ -773,21 +793,11 @@ void InterpretedCPU::psxMTLO(uint32_t code) {
void InterpretedCPU::psxBREAK(uint32_t code) {
m_regs.pc -= 4;
exception(Exception::Break, m_inDelaySlot);
if (m_inDelaySlot) {
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
if (!delayedLoad.pcActive) abort();
delayedLoad.pcActive = false;
}
}
void InterpretedCPU::psxSYSCALL(uint32_t code) {
m_regs.pc -= 4;
exception(Exception::Syscall, m_inDelaySlot);
if (m_inDelaySlot) {
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
if (!delayedLoad.pcActive) abort();
delayedLoad.pcActive = false;
}
}
void InterpretedCPU::psxRFE(uint32_t code) {
@@ -839,7 +849,7 @@ void InterpretedCPU::psxJR(uint32_t code) {
}
void InterpretedCPU::psxJALR(uint32_t code) {
uint32_t temp = _u32(_rRs_);
uint32_t temp = _rRs_;
if (_Rd_) {
maybeCancelDelayedLoad(_Rd_);
uint32_t ra = m_regs.pc + 4;
@@ -868,12 +878,12 @@ void InterpretedCPU::psxJALR(uint32_t code) {
* Format: OP rt, offset(base) *
*********************************************************/
#define _oB_ (_u32(_rRs_) + _Imm_)
#define _oB_ (_rRs_ + _Imm_)
void InterpretedCPU::psxLB(uint32_t code) {
// load delay = 1 latency
if (_Rt_) {
_i32(delayedLoadRef(_Rt_)) = (int8_t)PCSX::g_emulator->m_mem->read8(_oB_);
delayedLoadRef<int32_t>(_Rt_) = (int8_t)PCSX::g_emulator->m_mem->read8(_oB_);
} else {
PCSX::g_emulator->m_mem->read8(_oB_);
}
@@ -882,7 +892,7 @@ void InterpretedCPU::psxLB(uint32_t code) {
void InterpretedCPU::psxLBU(uint32_t code) {
// load delay = 1 latency
if (_Rt_) {
_u32(delayedLoadRef(_Rt_)) = PCSX::g_emulator->m_mem->read8(_oB_);
delayedLoadRef(_Rt_) = PCSX::g_emulator->m_mem->read8(_oB_);
} else {
PCSX::g_emulator->m_mem->read8(_oB_);
}
@@ -899,7 +909,7 @@ void InterpretedCPU::psxLH(uint32_t code) {
}
if (_Rt_) {
_i32(delayedLoadRef(_Rt_)) = (short)PCSX::g_emulator->m_mem->read16(_oB_);
delayedLoadRef<int32_t>(_Rt_) = (short)PCSX::g_emulator->m_mem->read16(_oB_);
} else {
PCSX::g_emulator->m_mem->read16(_oB_);
}
@@ -916,7 +926,7 @@ void InterpretedCPU::psxLHU(uint32_t code) {
}
if (_Rt_) {
_u32(delayedLoadRef(_Rt_)) = PCSX::g_emulator->m_mem->read16(_oB_);
delayedLoadRef(_Rt_) = PCSX::g_emulator->m_mem->read16(_oB_);
} else {
PCSX::g_emulator->m_mem->read16(_oB_);
}
@@ -944,7 +954,7 @@ void InterpretedCPU::psxLW(uint32_t code) {
}
break;
}
_u32(delayedLoadRef(_Rt_)) = val;
delayedLoadRef(_Rt_) = val;
}
}
@@ -955,7 +965,7 @@ void InterpretedCPU::psxLWL(uint32_t code) {
// load delay = 1 latency
if (!_Rt_) return;
_u32(delayedLoadRef(_Rt_, LWL_MASK[shift])) = mem << LWL_SHIFT[shift];
delayedLoadRef(_Rt_, LWL_MASK[shift]) = mem << LWL_SHIFT[shift];
/*
Mem = 1234. Reg = abcd
@@ -973,7 +983,7 @@ void InterpretedCPU::psxLWR(uint32_t code) {
// load delay = 1 latency
if (!_Rt_) return;
_u32(delayedLoadRef(_Rt_, LWR_MASK[shift])) = mem >> LWR_SHIFT[shift];
delayedLoadRef(_Rt_, LWR_MASK[shift]) = mem >> LWR_SHIFT[shift];
/*
Mem = 1234. Reg = abcd
@@ -1017,7 +1027,7 @@ void InterpretedCPU::psxSWL(uint32_t code) {
// the preserved bytes aren't consumed, so msan doesn't check any of them
uint32_t mem = PCSX::g_emulator->m_mem->read32Masked(addr, 0);
PCSX::g_emulator->m_mem->write32Masked(addr, (_u32(_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift]),
PCSX::g_emulator->m_mem->write32Masked(addr, ((_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift]),
PCSX::Memory::leftByteMask(shift));
/*
Mem = 1234. Reg = abcd
@@ -1035,7 +1045,7 @@ void InterpretedCPU::psxSWR(uint32_t code) {
// the preserved bytes aren't consumed, so msan doesn't check any of them
uint32_t mem = PCSX::g_emulator->m_mem->read32Masked(addr, 0);
PCSX::g_emulator->m_mem->write32Masked(addr, (_u32(_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift]),
PCSX::g_emulator->m_mem->write32Masked(addr, ((_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift]),
PCSX::Memory::rightByteMask(shift));
/*
@@ -1054,13 +1064,13 @@ void InterpretedCPU::psxSWR(uint32_t code) {
void InterpretedCPU::psxMFC0(uint32_t code) {
// load delay = 1 latency
if (!_Rt_) return;
_i32(delayedLoadRef(_Rt_)) = (int)m_regs.CP0.r[_Rd_];
delayedLoadRef<int32_t>(_Rt_) = (int)m_regs.CP0.r[_Rd_];
}
void InterpretedCPU::psxCFC0(uint32_t code) {
// load delay = 1 latency
if (!_Rt_) return;
_i32(delayedLoadRef(_Rt_)) = (int)m_regs.CP0.r[_Rd_];
delayedLoadRef<int32_t>(_Rt_) = (int)m_regs.CP0.r[_Rd_];
}
void InterpretedCPU::psxTestSWInts() {
@@ -1092,8 +1102,8 @@ inline void InterpretedCPU::MTC0(int reg, uint32_t val) {
}
}
void InterpretedCPU::psxMTC0(uint32_t code) { MTC0(_Rd_, _u32(_rRt_)); }
void InterpretedCPU::psxCTC0(uint32_t code) { MTC0(_Rd_, _u32(_rRt_)); }
void InterpretedCPU::psxMTC0(uint32_t code) { MTC0(_Rd_, _rRt_); }
void InterpretedCPU::psxCTC0(uint32_t code) { MTC0(_Rd_, _rRt_); }
void InterpretedCPU::psxMFC2(uint32_t code) {
gteStall();
@@ -1131,11 +1141,6 @@ void InterpretedCPU::psxBCz(uint32_t code, unsigned z) {
if ((m_regs.CP0.n.Status & (0x10000000 << z)) == 0) {
m_regs.pc -= 4;
exception((static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28), m_inDelaySlot);
if (m_inDelaySlot) {
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
if (!delayedLoad.pcActive) abort();
delayedLoad.pcActive = false;
}
return;
}
if ((_Rt_ & 1) == 0) doBranch(_BranchTarget_, false);
@@ -1676,6 +1681,7 @@ inline void InterpretedCPU::execBlock() {
m_regs.pc += 4;
m_regs.cycle += PCSX::Emulator::BIAS;
if ((m_regs.pc & 0xffc00000) == 0xbfc00000) m_regs.cycle += PCSX::Emulator::BIAS * 10;
cIntFunc_t func = s_pPsxBSC[code >> 26];
(*this.*func)(code);
+61 -72
View File
@@ -81,7 +81,7 @@ void PCSX::R3000Acpu::psxReset() {
void PCSX::R3000Acpu::psxShutdown() { Shutdown(); }
void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
bool PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
auto& emuSettings = g_emulator->settings;
auto& debugSettings = emuSettings.get<Emulator::SettingDebugSettings>();
unsigned ec = (code >> 2) & 0x1f;
@@ -102,14 +102,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = 0;
regs.v1 = 0;
m_regs.pc += 4;
return;
return false;
}
case 0x102: { // PCcreat
if (m_availableFDs.empty()) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
std::filesystem::path basepath = debugSettings.get<Emulator::DebugSettings::PCdrvBase>();
memFile->rSeek(m_regs.GPR.n.a0);
@@ -127,14 +127,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v1 = fd;
}
m_regs.pc += 4;
return;
return false;
}
case 0x103: { // PCopen
if (m_availableFDs.empty()) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
std::filesystem::path basepath = debugSettings.get<Emulator::DebugSettings::PCdrvBase>();
memFile->rSeek(m_regs.GPR.n.a0);
@@ -158,7 +158,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v1 = fd;
}
m_regs.pc += 4;
return;
return false;
}
case 0x104: { // PCclose
fd = m_regs.GPR.n.a0;
@@ -174,7 +174,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
m_availableFDs.push_back(fd);
}
m_regs.pc += 4;
return;
return false;
}
case 0x105: { // PCread
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a1);
@@ -182,21 +182,21 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
IO<File> file = *filei;
if (file->failed() || file->eof()) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
auto slice = file->read(regs.a2);
regs.v0 = 0;
regs.v1 = slice.size();
memFile->writeAt(std::move(slice), regs.a3);
m_regs.pc += 4;
return;
return false;
}
case 0x106: { // PCwrite
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a1);
@@ -204,7 +204,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
IO<File> file = *filei;
auto slice = memFile->readAt(regs.a2, regs.a3);
@@ -214,7 +214,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = 0;
}
m_regs.pc += 4;
return;
return false;
}
case 0x107: { // PClseek
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a0);
@@ -222,7 +222,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
IO<File> file = *filei;
int wheel;
@@ -240,14 +240,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v0 = -1;
regs.v1 = -1;
m_regs.pc += 4;
return;
return false;
}
ssize_t ret = file->rSeek(regs.a2, wheel);
if (ret < 0) {
regs.v0 = -1;
regs.v1 = ret;
m_regs.pc += 4;
return;
return false;
}
if (file->writable()) {
file->wSeek(regs.a2, wheel);
@@ -261,7 +261,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
regs.v1 = ret;
}
m_regs.pc += 4;
return;
return false;
}
default:
break;
@@ -298,6 +298,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
m_regs.CP0.n.Cause = code;
// Set the Status
m_regs.CP0.n.Status = (m_regs.CP0.n.Status & ~0x3f) | ((m_regs.CP0.n.Status & 0xf) << 2);
return true;
}
void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, uint16_t fd) {
@@ -329,73 +330,49 @@ void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, ui
}
void PCSX::R3000Acpu::branchTest() {
#if 0
if( SPU_async )
{
static int init;
int elapsed;
if( init == 0 ) {
// 10 apu cycles
// - Final Fantasy Tactics (distorted - dropped sound effects)
m_regs.intCycle[PSXINT_SPUASYNC].cycle = g_emulator->m_psxClockSpeed / 44100 * 10;
init = 1;
}
elapsed = m_regs.cycle - m_regs.intCycle[PSXINT_SPUASYNC].sCycle;
if (elapsed >= m_regs.intCycle[PSXINT_SPUASYNC].cycle) {
SPU_async( elapsed );
m_regs.intCycle[PSXINT_SPUASYNC].sCycle = m_regs.cycle;
}
}
#endif
const uint64_t cycle = m_regs.cycle;
if (cycle >= g_emulator->m_counters->m_psxNextCounter) g_emulator->m_counters->update();
if (m_regs.spuInterrupt.exchange(false)) g_emulator->m_spu->interrupt();
const uint32_t interrupts = m_regs.interrupt;
const uint32_t scheduleMask = m_regs.scheduleMask;
int32_t lowestDistance = std::numeric_limits<int64_t>::max();
uint64_t lowestTarget = cycle;
uint64_t* targets = m_regs.intTargets;
uint64_t* targets = m_regs.scheduleTargets;
if ((interrupts != 0) && (m_regs.lowestTarget < cycle)) {
#define checkAndUpdate(irq, act) \
{ \
constexpr uint32_t mask = 1 << irq; \
if ((interrupts & mask) != 0) { \
uint64_t target = targets[irq]; \
int64_t dist = target - cycle; \
if (dist > 0) { \
if (lowestDistance > dist) { \
lowestDistance = dist; \
lowestTarget = target; \
} \
} else { \
m_regs.interrupt &= ~mask; \
PSXIRQ_LOG("Triggering interrupt %08x\n", magic_enum::enum_integer(irq)); \
act(); \
} \
} \
if ((scheduleMask != 0) && (m_regs.lowestTarget < cycle)) {
#define checkAndUpdate(irq_, act) \
{ \
constexpr unsigned irq = static_cast<unsigned>(irq_); \
constexpr uint32_t mask = 1 << irq; \
if ((scheduleMask & mask) != 0) { \
uint64_t target = targets[irq]; \
int64_t dist = target - cycle; \
if (dist > 0) { \
if (lowestDistance > dist) { \
lowestDistance = dist; \
lowestTarget = target; \
} \
} else { \
m_regs.scheduleMask &= ~mask; \
PSXIRQ_LOG("Calling scheduled callback %08x\n", irq); \
act(); \
} \
} \
}
checkAndUpdate(PSXINT_SIO, g_emulator->m_sio->interrupt);
checkAndUpdate(PSXINT_SIO1, g_emulator->m_sio1->interrupt);
checkAndUpdate(PSXINT_CDR, g_emulator->m_cdrom->interrupt);
checkAndUpdate(PSXINT_CDREAD, g_emulator->m_cdrom->readInterrupt);
checkAndUpdate(PSXINT_GPUDMA, GPU::gpuInterrupt);
checkAndUpdate(PSXINT_MDECOUTDMA, g_emulator->m_mdec->mdec1Interrupt);
checkAndUpdate(PSXINT_SPUDMA, spuInterrupt);
checkAndUpdate(PSXINT_MDECINDMA, g_emulator->m_mdec->mdec0Interrupt);
checkAndUpdate(PSXINT_GPUOTCDMA, gpuotcInterrupt);
checkAndUpdate(PSXINT_CDRDMA, g_emulator->m_cdrom->dmaInterrupt);
checkAndUpdate(PSXINT_CDRPLAY, g_emulator->m_cdrom->playInterrupt);
checkAndUpdate(PSXINT_CDRDBUF, g_emulator->m_cdrom->decodedBufferInterrupt);
checkAndUpdate(PSXINT_CDRLID, g_emulator->m_cdrom->lidSeekInterrupt);
checkAndUpdate(Schedule::SIO, g_emulator->m_sio->scheduledCallback);
checkAndUpdate(Schedule::SIO1, g_emulator->m_sio1->scheduledCallback);
checkAndUpdate(Schedule::CDRFIFO, g_emulator->m_cdrom->fifoScheduledCallback);
checkAndUpdate(Schedule::CDRCOMMANDS, g_emulator->m_cdrom->commandsScheduledCallback);
checkAndUpdate(Schedule::CDREAD, g_emulator->m_cdrom->readScheduledCallback);
checkAndUpdate(Schedule::GPUDMA, GPU::gpuInterrupt);
checkAndUpdate(Schedule::MDECOUTDMA, g_emulator->m_mdec->scheduledCallback1);
checkAndUpdate(Schedule::SPUDMA, spuInterrupt);
checkAndUpdate(Schedule::MDECINDMA, g_emulator->m_mdec->scheduledCallback0);
checkAndUpdate(Schedule::GPUOTCDMA, gpuotcInterrupt);
checkAndUpdate(Schedule::CDRDMA, g_emulator->m_cdrom->scheduledDmaCallback);
m_regs.lowestTarget = lowestTarget;
}
auto& mem = g_emulator->m_mem;
@@ -413,6 +390,18 @@ void PCSX::R3000Acpu::branchTest() {
}
PSXIRQ_LOG("Interrupt: %x %x\n", istat, imask);
// A branch in the delay slot of a taken branch leaves its own jump pending here, and the next
// instruction is that branch's delay slot. The interrupt is taken on that delay slot: EPC is the
// branch with BD set, and the pending jump is dropped, since returning runs the branch again.
for (auto& pending : m_delayedLoadInfo) {
if (!pending.pcActive) continue;
pending.pcActive = false;
pending.fromLink = false;
m_nextIsDelaySlot = false;
m_regs.pc = pending.branchPC + 4;
exception(0x400, true);
return;
}
exception(0x400, 0);
}
}
+54 -48
View File
@@ -21,6 +21,7 @@
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <list>
#include <map>
@@ -162,22 +163,18 @@ typedef union {
PAIR p[32];
} psxCP2Ctrl;
enum {
PSXINT_SIO = 0,
PSXINT_SIO1,
PSXINT_CDR,
PSXINT_CDREAD,
PSXINT_GPUDMA,
PSXINT_MDECOUTDMA,
PSXINT_SPUDMA,
PSXINT_GPUBUSY,
PSXINT_MDECINDMA,
PSXINT_GPUOTCDMA,
PSXINT_CDRDMA,
PSXINT_SPUASYNC,
PSXINT_CDRDBUF,
PSXINT_CDRLID,
PSXINT_CDRPLAY
enum class Schedule : unsigned {
SIO = 0,
SIO1,
CDRFIFO,
CDRCOMMANDS,
CDREAD,
GPUDMA,
MDECOUTDMA,
SPUDMA,
MDECINDMA,
GPUOTCDMA,
CDRDMA,
};
struct psxRegisters {
@@ -189,32 +186,24 @@ struct psxRegisters {
uint32_t code; // The current instruction
uint64_t cycle;
uint64_t previousCycles;
uint32_t interrupt;
uint32_t scheduleMask;
std::atomic<bool> spuInterrupt;
uint64_t intTargets[32];
uint64_t scheduleTargets[32];
uint64_t lowestTarget;
uint64_t muldivReady; // cycle at which hi/lo can be read without stalling
uint64_t gteReady; // cycle at which the GTE can be accessed without stalling
uint8_t iCacheAddr[0x1000];
uint8_t iCacheCode[0x1000];
uint32_t getFutureCycle(std::chrono::nanoseconds delay) const { return cycle + durationToCycles(delay); }
std::chrono::nanoseconds getFutureTime(uint32_t futureCycle) const {
return std::chrono::nanoseconds(int64_t(int32_t(futureCycle - cycle)) * 1'000'000'000 /
Emulator::m_psxClockSpeed);
}
static constexpr uint32_t durationToCycles(std::chrono::nanoseconds duration) {
return duration.count() * Emulator::m_psxClockSpeed / 1'000'000'000;
}
};
// U64 and S64 are used to wrap long integer constants.
#define U64(val) val##ULL
#define S64(val) val##LL
#if defined(__BIGENDIAN__)
#define _i32(x) reinterpret_cast<int32_t *>(&x)[0]
#define _u32(x) reinterpret_cast<uint32_t *>(&x)[0]
#else
#define _i32(x) reinterpret_cast<int32_t *>(&x)[0]
#define _u32(x) reinterpret_cast<uint32_t *>(&x)[0]
#endif
// R3000A Instruction Macros
#define _PC_ PCSX::g_emulator->m_cpu->m_regs.pc // The next PC to be executed
@@ -300,21 +289,35 @@ class R3000Acpu {
CoprocessorUnusable = 11,
ArithmeticOverflow = 12,
};
void exception(Exception e, bool bd, bool cop0 = false) {
exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
bool exception(Exception e, bool bd, bool cop0 = false) {
return exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
}
void exception(uint32_t code, bool bd, bool cop0 = false);
bool exception(uint32_t code, bool bd, bool cop0 = false);
void branchTest();
void psxSetPGXPMode(uint32_t pgxpMode);
void scheduleInterrupt(unsigned interrupt, uint32_t eCycle) {
PSXIRQ_LOG("Scheduling interrupt %08x at %08x\n", interrupt, eCycle);
void schedule(Schedule s_, uint32_t eCycle) {
unsigned s = static_cast<unsigned>(s_);
PSXIRQ_LOG("Scheduling callback %08x at %08x\n", s, eCycle);
const uint64_t cycle = m_regs.cycle;
uint64_t target = cycle + uint64_t(eCycle * m_interruptScales[interrupt]);
m_regs.interrupt |= (1 << interrupt);
m_regs.intTargets[interrupt] = target;
if (target < m_regs.lowestTarget) m_regs.lowestTarget = target;
uint64_t target = cycle + uint64_t(double(eCycle) * m_scheduleScales[s]);
m_regs.scheduleMask |= (1 << s);
m_regs.scheduleTargets[s] = target;
int64_t lowest = m_regs.lowestTarget - cycle;
int64_t maybeNewLowest = target - cycle;
if (maybeNewLowest < lowest) m_regs.lowestTarget = target;
}
void unschedule(Schedule s_) {
unsigned s = static_cast<unsigned>(s_);
PSXIRQ_LOG("Unscheduling callback %08x\n", s);
m_regs.scheduleMask &= ~(1 << s);
}
bool isScheduled(Schedule s_) {
unsigned s = static_cast<unsigned>(s_);
return (m_regs.scheduleMask & (1 << s)) != 0;
}
// GTE command execution time, indexed by the command's function field.
@@ -364,13 +367,13 @@ class R3000Acpu {
void muldivCancel() { m_regs.muldivReady = 0; }
psxRegisters m_regs;
float m_interruptScales[15] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
float m_scheduleScales[static_cast<unsigned>(Schedule::CDRDMA) + 1] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
bool m_shellStarted = false;
virtual void Reset() {
invalidateCache();
m_regs.interrupt = 0;
m_regs.scheduleMask = 0;
}
bool m_inISR = false;
bool m_nextIsDelaySlot = false;
@@ -380,18 +383,20 @@ class R3000Acpu {
uint32_t value = 0;
uint32_t mask = 0;
uint32_t pcValue = 0;
uint32_t branchPC = 0;
bool active = false;
bool pcActive = false;
bool fromLink = false;
} m_delayedLoadInfo[2];
unsigned m_currentDelayedLoad = 0;
uint32_t &delayedLoadRef(unsigned reg, uint32_t mask = 0) {
template <typename T = uint32_t>
T &delayedLoadRef(unsigned reg, uint32_t mask = 0) {
if (reg >= 32) abort();
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
delayedLoad.active = true;
delayedLoad.index = reg;
delayedLoad.mask = mask;
return delayedLoad.value;
return reinterpret_cast<T &>(delayedLoad.value);
}
void delayedLoad(unsigned reg, uint32_t value, uint32_t mask = 0) {
auto &ref = delayedLoadRef(reg, mask);
@@ -401,6 +406,7 @@ class R3000Acpu {
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
delayedLoad.pcActive = true;
delayedLoad.pcValue = value;
delayedLoad.branchPC = m_regs.pc - 4;
delayedLoad.fromLink = fromLink;
}
void flushCurrentDelayedLoad() {
+2 -2
View File
@@ -308,7 +308,7 @@ void PCSX::SIO::writeCtrl16(uint16_t value) {
m_bufferIndex = 0;
m_regs.status = StatusFlags::TX_DATACLEAR | StatusFlags::TX_FINISHED;
g_emulator->m_mem->writeHardwareRegister<0x1044>(m_regs.status);
PCSX::g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO);
g_emulator->m_cpu->unschedule(Schedule::SIO);
m_currentDevice = DeviceType::None;
}
@@ -349,7 +349,7 @@ uint16_t PCSX::SIO::readStatus16() {
return hard;
}
void PCSX::SIO::interrupt() {
void PCSX::SIO::scheduledCallback() {
SIO0_LOG("Sio Interrupt (CP0.Status = %x)\n", PCSX::g_emulator->m_cpu->m_regs.CP0.n.Status);
m_regs.status |= StatusFlags::IRQ;
g_emulator->m_mem->writeHardwareRegister<0x1044>(m_regs.status);
+2 -2
View File
@@ -93,7 +93,7 @@ class SIO {
void acknowledge();
void init();
void interrupt();
void scheduledCallback();
void reset();
bool copyMcdFile(McdBlock block);
@@ -234,7 +234,7 @@ class SIO {
bool isReceiveIRQReady();
bool isTransmitReady();
static inline void scheduleInterrupt(uint64_t eCycle) {
g_emulator->m_cpu->scheduleInterrupt(PSXINT_SIO, eCycle);
g_emulator->m_cpu->schedule(Schedule::SIO, eCycle);
#if 0
// Breaks Twisted Metal 2 intro
m_statusReg &= ~RX_FIFONOTEMPTY;
+2 -2
View File
@@ -127,7 +127,7 @@ void PCSX::SIO1::sio1StateMachine() {
}
}
void PCSX::SIO1::interrupt() {
void PCSX::SIO1::scheduledCallback() {
SIO1_LOG("SIO1 Interrupt (CP0.Status = %x)\n", PCSX::g_emulator->m_cpu->m_regs.CP0.n.Status);
g_emulator->m_mem->setIRQ(IRQ8_SIO);
m_regs.status |= SR_IRQ;
@@ -282,7 +282,7 @@ void PCSX::SIO1::writeCtrl16(uint16_t v) {
m_sio1fifo.asA<Fifo>()->reset();
}
PCSX::g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO1);
g_emulator->m_cpu->unschedule(Schedule::SIO1);
}
if (!(m_regs.control & CR_RXEN)) {
+3 -3
View File
@@ -70,7 +70,7 @@ class SIO1 {
enum class SIO1Mode { Raw, Protobuf };
SIO1Mode m_sio1Mode = SIO1Mode::Protobuf;
SIO1Mode getSIO1Mode() { return m_sio1Mode; }
void interrupt();
void scheduledCallback();
void poll() {
if (fifoError()) return;
@@ -93,7 +93,7 @@ class SIO1 {
m_decodeState = READ_SIZE;
messageSize = 0;
initialMessage = true;
g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO1);
g_emulator->m_cpu->unschedule(Schedule::SIO1);
}
void stopSIO1Connection() {
@@ -247,7 +247,7 @@ class SIO1 {
enum { READ_SIZE, READ_MESSAGE } m_decodeState = READ_SIZE;
inline void scheduleInterrupt(uint32_t eCycle) { g_emulator->m_cpu->scheduleInterrupt(PSXINT_SIO1, eCycle); }
inline void scheduleInterrupt(uint32_t eCycle) { g_emulator->m_cpu->schedule(Schedule::SIO1, eCycle); }
void updateStat();
void transmitData();
+2
View File
@@ -44,6 +44,8 @@ class SPUInterface {
virtual void writeRegister(uint32_t, uint16_t) = 0;
virtual void playCDDAchannel(int16_t *, int) = 0;
virtual void playADPCMchannel(xa_decode_t *) = 0;
// CD audio volume matrix (ATV0..ATV3) for XA, applied after the interpolator.
virtual void setXAVolume(const uint8_t atv[4]) = 0;
virtual void writeDMAMem(uint16_t *, int) = 0;
virtual void readDMAMem(uint16_t *, int) = 0;
virtual void lockSPURAM() = 0;
+155 -120
View File
@@ -34,71 +34,71 @@
#include "spu/interface.h"
PCSX::SaveStates::SaveState PCSX::SaveStates::constructSaveState() {
// clang-format off
return SaveState {
SaveStateInfo {
VersionString {},
Version {},
auto* cd = g_emulator->m_cdrom.get();
return SaveState{
SaveStateInfo{
VersionString{},
Version{},
},
Thumbnail {},
Memory {
RAM { g_emulator->m_mem->m_wram },
ROM { g_emulator->m_mem->m_bios },
EXP1 { g_emulator->m_mem->m_exp1 },
HardwareMemory { g_emulator->m_mem->m_hard },
SRAM { g_emulator->m_mem->m_sram },
Thumbnail{},
Memory{
RAM{g_emulator->m_mem->m_wram},
ROM{g_emulator->m_mem->m_bios},
EXP1{g_emulator->m_mem->m_exp1},
HardwareMemory{g_emulator->m_mem->m_hard},
SRAM{g_emulator->m_mem->m_sram},
},
Registers {
GPR { g_emulator->m_cpu->m_regs.GPR.r },
CP0 { g_emulator->m_cpu->m_regs.CP0.r },
CP2D { g_emulator->m_cpu->m_regs.CP2D.r },
CP2C { g_emulator->m_cpu->m_regs.CP2C.r },
PC { g_emulator->m_cpu->m_regs.pc },
Code { g_emulator->m_cpu->m_regs.code },
Cycle { g_emulator->m_cpu->m_regs.cycle },
Interrupt { g_emulator->m_cpu->m_regs.interrupt },
ICacheAddr { g_emulator->m_cpu->m_regs.iCacheAddr },
ICacheCode { g_emulator->m_cpu->m_regs.iCacheCode },
NextIsDelaySlot { g_emulator->m_cpu->m_nextIsDelaySlot },
DelaySlotInfo1 {
DelaySlotIndex { g_emulator->m_cpu->m_delayedLoadInfo[0].index },
DelaySlotValue { g_emulator->m_cpu->m_delayedLoadInfo[0].value },
DelaySlotMask { g_emulator->m_cpu->m_delayedLoadInfo[0].mask },
DelaySlotPcValue { g_emulator->m_cpu->m_delayedLoadInfo[0].pcValue },
DelaySlotActive { g_emulator->m_cpu->m_delayedLoadInfo[0].active },
DelaySlotPcActive { g_emulator->m_cpu->m_delayedLoadInfo[0].pcActive },
DelaySlotFromLink { g_emulator->m_cpu->m_delayedLoadInfo[0].fromLink }
Registers{
GPR{g_emulator->m_cpu->m_regs.GPR.r},
CP0{g_emulator->m_cpu->m_regs.CP0.r},
CP2D{g_emulator->m_cpu->m_regs.CP2D.r},
CP2C{g_emulator->m_cpu->m_regs.CP2C.r},
PC{g_emulator->m_cpu->m_regs.pc},
Code{g_emulator->m_cpu->m_regs.code},
Cycle{g_emulator->m_cpu->m_regs.cycle},
ScheduleMask{g_emulator->m_cpu->m_regs.scheduleMask},
ICacheAddr{g_emulator->m_cpu->m_regs.iCacheAddr},
ICacheCode{g_emulator->m_cpu->m_regs.iCacheCode},
NextIsDelaySlot{g_emulator->m_cpu->m_nextIsDelaySlot},
DelaySlotInfo1{
DelaySlotIndex{g_emulator->m_cpu->m_delayedLoadInfo[0].index},
DelaySlotValue{g_emulator->m_cpu->m_delayedLoadInfo[0].value},
DelaySlotMask{g_emulator->m_cpu->m_delayedLoadInfo[0].mask},
DelaySlotPcValue{g_emulator->m_cpu->m_delayedLoadInfo[0].pcValue},
DelaySlotActive{g_emulator->m_cpu->m_delayedLoadInfo[0].active},
DelaySlotPcActive{g_emulator->m_cpu->m_delayedLoadInfo[0].pcActive},
DelaySlotFromLink{g_emulator->m_cpu->m_delayedLoadInfo[0].fromLink},
},
DelaySlotInfo2 {
DelaySlotIndex { g_emulator->m_cpu->m_delayedLoadInfo[1].index },
DelaySlotValue { g_emulator->m_cpu->m_delayedLoadInfo[1].value },
DelaySlotMask { g_emulator->m_cpu->m_delayedLoadInfo[1].mask },
DelaySlotPcValue { g_emulator->m_cpu->m_delayedLoadInfo[1].pcValue },
DelaySlotActive { g_emulator->m_cpu->m_delayedLoadInfo[1].active },
DelaySlotPcActive { g_emulator->m_cpu->m_delayedLoadInfo[1].pcActive },
DelaySlotFromLink { g_emulator->m_cpu->m_delayedLoadInfo[1].fromLink }
DelaySlotInfo2{
DelaySlotIndex{g_emulator->m_cpu->m_delayedLoadInfo[1].index},
DelaySlotValue{g_emulator->m_cpu->m_delayedLoadInfo[1].value},
DelaySlotMask{g_emulator->m_cpu->m_delayedLoadInfo[1].mask},
DelaySlotPcValue{g_emulator->m_cpu->m_delayedLoadInfo[1].pcValue},
DelaySlotActive{g_emulator->m_cpu->m_delayedLoadInfo[1].active},
DelaySlotPcActive{g_emulator->m_cpu->m_delayedLoadInfo[1].pcActive},
DelaySlotFromLink{g_emulator->m_cpu->m_delayedLoadInfo[1].fromLink},
},
CurrentDelayedLoad { g_emulator->m_cpu->m_currentDelayedLoad },
IntTargetsField { g_emulator->m_cpu->m_regs.intTargets },
InISR { g_emulator->m_cpu->m_inISR },
MulDivReady { g_emulator->m_cpu->m_regs.muldivReady },
GteReady { g_emulator->m_cpu->m_regs.gteReady },
CurrentDelayedLoad{g_emulator->m_cpu->m_currentDelayedLoad},
ScheduleTargetsField{g_emulator->m_cpu->m_regs.scheduleTargets},
InISR{g_emulator->m_cpu->m_inISR},
MulDivReady{g_emulator->m_cpu->m_regs.muldivReady},
GteReady{g_emulator->m_cpu->m_regs.gteReady},
},
GPU {},
SPU {},
SIO {
SIOBuffer { g_emulator->m_sio->m_buffer },
SIOStatusReg { g_emulator->m_sio->m_regs.status },
SIOModeReg { g_emulator->m_sio->m_regs.mode },
SIOCtrlReg { g_emulator->m_sio->m_regs.control },
SIOBaudReg { g_emulator->m_sio->m_regs.baud },
SIOBufferMaxIndex { g_emulator->m_sio->m_maxBufferIndex },
SIOBufferIndex { g_emulator->m_sio->m_bufferIndex },
SIOPadState { g_emulator->m_sio->m_padState },
SIOCurrentDevice { g_emulator->m_sio->m_currentDevice },
SIOMCD1TempBuffer { g_emulator->m_sio->m_memoryCard[0].m_tempBuffer },
SIOMCD1DirectoryFlag { g_emulator->m_sio->m_memoryCard[0].m_directoryFlag },
SIOMCD1ChecksumIn{ g_emulator->m_sio->m_memoryCard[0].m_checksumIn},
GPU{},
SPU{},
SIO{
SIOBuffer{g_emulator->m_sio->m_buffer},
SIOStatusReg{g_emulator->m_sio->m_regs.status},
SIOModeReg{g_emulator->m_sio->m_regs.mode},
SIOCtrlReg{g_emulator->m_sio->m_regs.control},
SIOBaudReg{g_emulator->m_sio->m_regs.baud},
SIOBufferMaxIndex{g_emulator->m_sio->m_maxBufferIndex},
SIOBufferIndex{g_emulator->m_sio->m_bufferIndex},
SIOPadState{g_emulator->m_sio->m_padState},
SIOCurrentDevice{g_emulator->m_sio->m_currentDevice},
SIOMCD1TempBuffer{g_emulator->m_sio->m_memoryCard[0].m_tempBuffer},
SIOMCD1DirectoryFlag{g_emulator->m_sio->m_memoryCard[0].m_directoryFlag},
SIOMCD1ChecksumIn{g_emulator->m_sio->m_memoryCard[0].m_checksumIn},
SIOMCD1ChecksumOut{g_emulator->m_sio->m_memoryCard[0].m_checksumOut},
SIOMCD1CommandTicks{g_emulator->m_sio->m_memoryCard[0].m_commandTicks},
SIOMCD1CurrentCommand{g_emulator->m_sio->m_memoryCard[0].m_currentCommand},
@@ -113,68 +113,97 @@ PCSX::SaveStates::SaveState PCSX::SaveStates::constructSaveState() {
SIOMCD2Sector{g_emulator->m_sio->m_memoryCard[1].m_sector},
SIOMCD2DataOffset{g_emulator->m_sio->m_memoryCard[1].m_dataOffset},
},
CDRom {
CDReg1Mode { g_emulator->m_cdrom->m_reg1Mode },
CDReg2 { g_emulator->m_cdrom->m_reg2 },
CDCmdProcess { g_emulator->m_cdrom->m_cmdProcess },
CDCtrl { g_emulator->m_cdrom->m_ctrl },
CDStat { g_emulator->m_cdrom->m_stat },
CDStatP { g_emulator->m_cdrom->m_statP },
CDTransfer { reinterpret_cast<uint8_t*>(g_emulator->m_cdrom->m_transfer) },
CDTransferIndex { g_emulator->m_cdrom->m_transferIndex },
CDPrev { g_emulator->m_cdrom->m_prev.data },
CDParam { g_emulator->m_cdrom->m_param },
CDResult { g_emulator->m_cdrom->m_result },
CDParamC { g_emulator->m_cdrom->m_paramC },
CDResultC { g_emulator->m_cdrom->m_resultC },
CDResultP { g_emulator->m_cdrom->m_resultP },
CDResultReady { g_emulator->m_cdrom->m_resultReady },
CDCmd { g_emulator->m_cdrom->m_cmd },
CDRead { g_emulator->m_cdrom->m_read },
CDSetLocPending { g_emulator->m_cdrom->m_setlocPending },
CDReading { g_emulator->m_cdrom->m_reading },
CDSetSectorPlay { g_emulator->m_cdrom->m_setSectorPlay.data },
CDSetSectorEnd { g_emulator->m_cdrom->m_setSectorEnd.data },
CDSetSector { g_emulator->m_cdrom->m_setSector.data },
CDTrack { g_emulator->m_cdrom->m_track },
CDPlay { g_emulator->m_cdrom->m_play },
CDMuted { g_emulator->m_cdrom->m_muted },
CDCurTrack { g_emulator->m_cdrom->m_curTrack },
CDMode { g_emulator->m_cdrom->m_mode },
CDFile { g_emulator->m_cdrom->m_file },
CDChannel { g_emulator->m_cdrom->m_channel },
CDSuceeded { g_emulator->m_cdrom->m_suceeded },
CDFirstSector { g_emulator->m_cdrom->m_firstSector },
CDIRQ { g_emulator->m_cdrom->m_irq },
CDIrqRepeated { g_emulator->m_cdrom->m_irqRepeated },
CDECycle { g_emulator->m_cdrom->m_eCycle },
CDSeeked { g_emulator->m_cdrom->m_seeked },
CDReadRescheduled { g_emulator->m_cdrom->m_readRescheduled },
CDDriveState { g_emulator->m_cdrom->m_driveState },
CDFastForward { g_emulator->m_cdrom->m_fastForward },
CDFastBackward { g_emulator->m_cdrom->m_fastBackward },
CDAttenuatorLeftToLeft { g_emulator->m_cdrom->m_attenuatorLeftToLeft },
CDAttenuatorLeftToRight { g_emulator->m_cdrom->m_attenuatorLeftToRight },
CDAttenuatorRightToRight { g_emulator->m_cdrom->m_attenuatorRightToRight },
CDAttenuatorRightToLeft { g_emulator->m_cdrom->m_attenuatorRightToLeft },
CDAttenuatorLeftToLeftT { g_emulator->m_cdrom->m_attenuatorLeftToLeftT },
CDAttenuatorLeftToRightT { g_emulator->m_cdrom->m_attenuatorLeftToRightT },
CDAttenuatorRightToRightT { g_emulator->m_cdrom->m_attenuatorRightToRightT },
CDAttenuatorRightToLeftT { g_emulator->m_cdrom->m_attenuatorRightToLeftT },
CDSubQTrack { g_emulator->m_cdrom->m_subq.track },
CDSubQIndex { g_emulator->m_cdrom->m_subq.index },
CDSubQRelative { g_emulator->m_cdrom->m_subq.relative },
CDSubQAbsolute { g_emulator->m_cdrom->m_subq.absolute },
CDTrackChanged { g_emulator->m_cdrom->m_trackChanged },
CDLocationChanged { g_emulator->m_cdrom->m_locationChanged },
Hardware{},
Counters{},
MDEC{},
PCdrvFilesField{},
CallStacks{},
CDRom{
CDDataFIFO{cd->m_dataFIFO},
CDDataFIFOIndex{cd->m_dataFIFOIndex},
CDDataFIFOSize{cd->m_dataFIFOSize},
CDDataFIFOPending{cd->m_dataFIFOPending},
CDRegisterAddress{cd->m_registerAddress},
CDMotorOn{cd->m_motorOn},
CDSpeedChanged{cd->m_speedChanged},
CDInvalidLocL{cd->m_invalidLocL},
CDDataRequested{cd->m_dataRequested},
CDSubheaderFilter{cd->m_subheaderFilter},
CDRealtime{cd->m_realtime},
CDReadingState{reinterpret_cast<uint8_t&>(cd->m_readingState)},
CDStartPlaying{cd->m_startPlaying},
CDReadingType{reinterpret_cast<uint8_t&>(cd->m_readingType)},
CDAutoPause{cd->m_autoPause},
CDReport{cd->m_report},
CDSetLocPending{cd->m_setLocPending},
CDMuted{cd->m_muted},
CDPeakFlag{cd->m_peakFlag},
CDPlayTrack{cd->m_playTrack},
CDPlayStartCycle{cd->m_playStartCycle},
CDStatus{reinterpret_cast<uint8_t&>(cd->m_status)},
CDSpeed{reinterpret_cast<uint8_t&>(cd->m_speed)},
CDReadSpan{reinterpret_cast<uint8_t&>(cd->m_readSpan)},
CDInterruptCauseMask{cd->m_interruptCauseMask},
CDATV{cd->m_atv},
CDATVPending{cd->m_atvPending},
CDADPCMMuted{cd->m_adpcmMuted},
CDMode{cd->m_mode},
CDFilterFile{cd->m_filterFile},
CDFilterChannel{cd->m_filterChannel},
CDXAEnded{cd->m_xaEnded},
CDXAFirstSector{cd->m_xaFirstSector},
CDSoundMapEnabled{cd->m_soundMapEnabled},
CDCurrentPosition{CDMSF{CDMSFMinute{cd->m_currentPosition.m}, CDMSFSecond{cd->m_currentPosition.s},
CDMSFFrame{cd->m_currentPosition.f}}},
CDSeekPosition{CDMSF{CDMSFMinute{cd->m_seekPosition.m}, CDMSFSecond{cd->m_seekPosition.s},
CDMSFFrame{cd->m_seekPosition.f}}},
CDLastLocP{cd->m_lastLocP},
CDLastLocL{cd->m_lastLocL},
CDSeed{cd->m_seed},
CDLidOpen{cd->m_lidOpen},
CDWasLidOpened{cd->m_wasLidOpened},
CDLidCloseScheduled{cd->m_lidCloseScheduled},
CDLidCloseAtCycles{cd->m_lidCloseAtCycles},
CDCommandFifo{CDQueueElement{
CDQueuePayload{cd->m_commandFifo.payload},
CDQueueValue{cd->m_commandFifo.value},
CDQueueValueRead{cd->m_commandFifo.valueRead},
CDQueueHasValue{cd->m_commandFifo.hasValue},
CDQueueHitMax{cd->m_commandFifo.hitMax},
CDQueuePayloadSize{cd->m_commandFifo.payloadSize},
CDQueuePayloadIndex{cd->m_commandFifo.payloadIndex},
}},
CDCommandExecuting{CDQueueElement{
CDQueuePayload{cd->m_commandExecuting.payload},
CDQueueValue{cd->m_commandExecuting.value},
CDQueueValueRead{cd->m_commandExecuting.valueRead},
CDQueueHasValue{cd->m_commandExecuting.hasValue},
CDQueueHitMax{cd->m_commandExecuting.hitMax},
CDQueuePayloadSize{cd->m_commandExecuting.payloadSize},
CDQueuePayloadIndex{cd->m_commandExecuting.payloadIndex},
}},
CDResponseFifo0{CDQueueElement{
CDQueuePayload{cd->m_responseFifo[0].payload},
CDQueueValue{cd->m_responseFifo[0].value},
CDQueueValueRead{cd->m_responseFifo[0].valueRead},
CDQueueHasValue{cd->m_responseFifo[0].hasValue},
CDQueueHitMax{cd->m_responseFifo[0].hitMax},
CDQueuePayloadSize{cd->m_responseFifo[0].payloadSize},
CDQueuePayloadIndex{cd->m_responseFifo[0].payloadIndex},
}},
CDResponseFifo1{CDQueueElement{
CDQueuePayload{cd->m_responseFifo[1].payload},
CDQueueValue{cd->m_responseFifo[1].value},
CDQueueValueRead{cd->m_responseFifo[1].valueRead},
CDQueueHasValue{cd->m_responseFifo[1].hasValue},
CDQueueHitMax{cd->m_responseFifo[1].hitMax},
CDQueuePayloadSize{cd->m_responseFifo[1].payloadSize},
CDQueuePayloadIndex{cd->m_responseFifo[1].payloadIndex},
}},
CDXALeft{CDADPCM{CDADPCMY0{cd->m_xa.left.y0}, CDADPCMY1{cd->m_xa.left.y1}}},
CDXARight{CDADPCM{CDADPCMY0{cd->m_xa.right.y0}, CDADPCMY1{cd->m_xa.right.y1}}},
},
Hardware {},
Counters {},
MDEC {},
PCdrvFilesField {},
CallStacks {},
};
// clang-format on
}
namespace PCSX {
@@ -389,13 +418,16 @@ bool PCSX::SaveStates::load(std::string_view data) {
g_emulator->m_cpu->m_regs.pc &= ~3;
g_emulator->m_gpu->deserialize(&wrapper);
g_emulator->m_spu->load(state.get<SPUField>());
#if 0
g_emulator->m_cdrom->load();
#endif
g_emulator->m_counters->deserialize(&wrapper);
g_emulator->m_mdec->deserialize(&wrapper);
auto& xa = state.get<SPUField>().get<SaveStates::XAField>();
#if 0
g_emulator->m_cdrom->m_xa.freq = xa.get<SaveStates::XAFrequency>().value;
g_emulator->m_cdrom->m_xa.nbits = xa.get<SaveStates::XANBits>().value;
g_emulator->m_cdrom->m_xa.nsamples = xa.get<SaveStates::XANSamples>().value;
@@ -408,6 +440,7 @@ bool PCSX::SaveStates::load(std::string_view data) {
g_emulator->m_cdrom->m_xa.right.y1 = right.get<SaveStates::ADPCMDecodeY1>().value;
xa.get<SaveStates::XAPCM>().copyTo(reinterpret_cast<uint8_t*>(g_emulator->m_cdrom->m_xa.pcm));
g_emulator->m_spu->playADPCMchannel(&g_emulator->m_cdrom->m_xa);
#endif
g_emulator->m_cpu->closeAllPCdrvFiles();
for (auto& file : state.get<PCdrvFilesField>().value) {
@@ -500,6 +533,8 @@ void PCSX::GPU::deserialize(const SaveStateWrapper* w) {
writeStatus(m_statusControl[7]);
writeStatus(m_statusControl[5]);
writeStatus(m_statusControl[4]);
// The replayed GP1(00h)/GP1(02h) above clear the IRQ1 flag; put back the saved one.
m_irq1Flag = gpu.get<GPUStatus>().value & 0x01000000;
}
void PCSX::MDEC::deserialize(const SaveStateWrapper* w) {
+89 -77
View File
@@ -57,7 +57,7 @@ typedef Protobuf::RepeatedFieldRef<Protobuf::UInt32, 32, TYPESTRING("cp2c"), 4>
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("pc"), 5> PC;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("code"), 6> Code;
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("cycle"), 7> Cycle;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("interrupt"), 8> Interrupt;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("schedule_mask"), 8> ScheduleMask;
// skip id 9
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<0x1000>, TYPESTRING("icache_addr"), 10> ICacheAddr;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<0x1000>, TYPESTRING("icache_code"), 11> ICacheCode;
@@ -76,13 +76,13 @@ typedef Protobuf::Message<TYPESTRING("DelaySlotInfo"), DelaySlotIndex, DelaySlot
typedef Protobuf::MessageField<DelaySlotInfo, TYPESTRING("delay_slot_info_1"), 14> DelaySlotInfo1;
typedef Protobuf::MessageField<DelaySlotInfo, TYPESTRING("delay_slot_info_2"), 15> DelaySlotInfo2;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("current_delayed_load"), 16> CurrentDelayedLoad;
typedef Protobuf::RepeatedFieldRef<Protobuf::UInt64, 32, TYPESTRING("interrupt_targets"), 17> IntTargetsField;
typedef Protobuf::RepeatedFieldRef<Protobuf::UInt64, 32, TYPESTRING("schedule_targets"), 17> ScheduleTargetsField;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("in_isr"), 18> InISR;
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("muldiv_ready"), 19> MulDivReady;
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("gte_ready"), 20> GteReady;
typedef Protobuf::Message<TYPESTRING("Registers"), GPR, CP0, CP2D, CP2C, PC, Code, Cycle, Interrupt, ICacheAddr,
typedef Protobuf::Message<TYPESTRING("Registers"), GPR, CP0, CP2D, CP2C, PC, Code, Cycle, ScheduleMask, ICacheAddr,
ICacheCode, NextIsDelaySlot, DelaySlotInfo1, DelaySlotInfo2, CurrentDelayedLoad,
IntTargetsField, InISR, MulDivReady, GteReady>
ScheduleTargetsField, InISR, MulDivReady, GteReady>
Registers;
typedef Protobuf::MessageField<Registers, TYPESTRING("registers"), 4> RegistersField;
@@ -182,76 +182,7 @@ typedef Protobuf::Message<TYPESTRING("SIO"), SIOBuffer, SIOStatusReg, SIOModeReg
SIOMCD2CurrentCommand, SIOMCD2Sector, SIOMCD2DataOffset>
SIO;
typedef Protobuf::MessageField<SIO, TYPESTRING("sio"), 7> SIOField;
// skip id 1
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("reg1_mode"), 2> CDReg1Mode;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("reg2"), 3> CDReg2;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("cmd_process"), 4> CDCmdProcess;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("ctrl"), 5> CDCtrl;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("stat"), 6> CDStat;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("stat_p"), 7> CDStatP;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<2352>, TYPESTRING("transfer"), 8> CDTransfer;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("transfer_index"), 9> CDTransferIndex;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("prev"), 10> CDPrev;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("param"), 11> CDParam;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<16>, TYPESTRING("result"), 12> CDResult;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("param_c"), 13> CDParamC;
// skip id 14
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_c"), 15> CDResultC;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_p"), 16> CDResultP;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_ready"), 17> CDResultReady;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("cmd"), 18> CDCmd;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("read"), 19> CDRead;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("set_loc_pending"), 20> CDSetLocPending;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("reading"), 21> CDReading;
// skip id 22
// skip id 23
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector_play"), 24> CDSetSectorPlay;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector_end"), 25> CDSetSectorEnd;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector"), 26> CDSetSector;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("track"), 27> CDTrack;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("play"), 28> CDPlay;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("muted"), 29> CDMuted;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("cur_track"), 30> CDCurTrack;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("mode"), 31> CDMode;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("file"), 32> CDFile;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("channel"), 33> CDChannel;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("suceeded"), 34> CDSuceeded;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("first_sector"), 35> CDFirstSector;
typedef Protobuf::FieldRef<Protobuf::UInt16, TYPESTRING("irq"), 36> CDIRQ;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("irq_repeated"), 37> CDIrqRepeated;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("e_cycle"), 38> CDECycle;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("seeked"), 39> CDSeeked;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("read_scheduled"), 40> CDReadRescheduled;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("drive_state"), 41> CDDriveState;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("fast_forward"), 42> CDFastForward;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("fast_backward"), 43> CDFastBackward;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_left"), 44> CDAttenuatorLeftToLeft;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_right"), 45> CDAttenuatorLeftToRight;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_right"), 46> CDAttenuatorRightToRight;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_left"), 47> CDAttenuatorRightToLeft;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_left_t"), 48> CDAttenuatorLeftToLeftT;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_right_t"), 49> CDAttenuatorLeftToRightT;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_right_t"), 50> CDAttenuatorRightToRightT;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_left_t"), 51> CDAttenuatorRightToLeftT;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("subq_track"), 52> CDSubQTrack;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("subq_index"), 53> CDSubQIndex;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<3>, TYPESTRING("subq_relative"), 54> CDSubQRelative;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<3>, TYPESTRING("subq_absolute"), 55> CDSubQAbsolute;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("track_changed"), 56> CDTrackChanged;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("location_changed"), 57> CDLocationChanged;
typedef Protobuf::Message<
TYPESTRING("CDRom"), CDReg1Mode, CDReg2, CDCmdProcess, CDCtrl, CDStat, CDStatP, CDTransfer, CDTransferIndex, CDPrev,
CDParam, CDResult, CDParamC, CDResultC, CDResultP, CDResultReady, CDCmd, CDRead, CDSetLocPending, CDReading,
CDSetSectorPlay, CDSetSectorEnd, CDSetSector, CDTrack, CDPlay, CDMuted, CDCurTrack, CDMode, CDFile, CDChannel,
CDSuceeded, CDFirstSector, CDIRQ, CDIrqRepeated, CDECycle, CDSeeked, CDReadRescheduled, CDDriveState, CDFastForward,
CDFastBackward, CDAttenuatorLeftToLeft, CDAttenuatorLeftToRight, CDAttenuatorRightToRight, CDAttenuatorRightToLeft,
CDAttenuatorLeftToLeftT, CDAttenuatorLeftToRightT, CDAttenuatorRightToRightT, CDAttenuatorRightToLeftT, CDSubQTrack,
CDSubQIndex, CDSubQRelative, CDSubQAbsolute, CDTrackChanged, CDLocationChanged>
CDRom;
typedef Protobuf::MessageField<CDRom, TYPESTRING("cdrom"), 8> CDRomField;
// skip id 8
typedef Protobuf::EmptyMessage<TYPESTRING("Hardware")> Hardware;
typedef Protobuf::MessageField<Hardware, TYPESTRING("hardware"), 9> HardwareField;
@@ -313,6 +244,7 @@ typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("sp"), 2> CallSP;
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("fp"), 3> CallFP;
typedef Protobuf::Field<Protobuf::Bool, TYPESTRING("shadow"), 4> Shadow;
typedef Protobuf::Message<TYPESTRING("Call"), CallRA, CallSP, CallFP, Shadow> Call;
typedef Protobuf::RepeatedVariableField<Call, TYPESTRING("calls"), 1> Calls;
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("low"), 2> LowSP;
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("high"), 3> HighSP;
@@ -321,19 +253,99 @@ typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("fp"), 5> PresumedFP;
typedef Protobuf::Field<Protobuf::Bool, TYPESTRING("iscurrent"), 6> CallstackIsCurrent;
typedef Protobuf::Message<TYPESTRING("Calls"), Calls, LowSP, HighSP, PresumedRA, PresumedFP, CallstackIsCurrent>
CallStack;
typedef Protobuf::RepeatedVariableField<CallStack, TYPESTRING("CallStacks"), 1> CallStacksMessageField;
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("currentSP"), 2> CallStacksCurrentSP;
typedef Protobuf::Message<TYPESTRING("CallStacks"), CallStacksMessageField, CallStacksCurrentSP> CallStacks;
typedef Protobuf::MessageField<CallStacks, TYPESTRING("callstacks"), 14> CallStacksField;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<16>, TYPESTRING("payload"), 1> CDQueuePayload;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("value"), 2> CDQueueValue;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("value_read"), 3> CDQueueValueRead;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("has_value"), 4> CDQueueHasValue;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("hit_max"), 5> CDQueueHitMax;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("payload_size"), 6> CDQueuePayloadSize;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("payload_index"), 7> CDQueuePayloadIndex;
typedef Protobuf::Message<TYPESTRING("CDQueueElement"), CDQueuePayload, CDQueueValue, CDQueueValueRead, CDQueueHasValue,
CDQueueHitMax, CDQueuePayloadSize, CDQueuePayloadIndex>
CDQueueElement;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("m"), 1> CDMSFMinute;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("s"), 2> CDMSFSecond;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("f"), 3> CDMSFFrame;
typedef Protobuf::Message<TYPESTRING("CDMSF"), CDMSFMinute, CDMSFSecond, CDMSFFrame> CDMSF;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("y0"), 1> CDADPCMY0;
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("y1"), 2> CDADPCMY1;
typedef Protobuf::Message<TYPESTRING("CDADPCM"), CDADPCMY0, CDADPCMY1> CDADPCM;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<2352>, TYPESTRING("dataFIFO"), 1> CDDataFIFO;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOIndex"), 2> CDDataFIFOIndex;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOSize"), 3> CDDataFIFOSize;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOPending"), 4> CDDataFIFOPending;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("registerAddress"), 5> CDRegisterAddress;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("motorOn"), 6> CDMotorOn;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("speedChanged"), 7> CDSpeedChanged;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("invalidLocL"), 8> CDInvalidLocL;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("dataRequested"), 9> CDDataRequested;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("subheaderFilter"), 10> CDSubheaderFilter;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("realtime"), 11> CDRealtime;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readingState"), 12> CDReadingState;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("startPlaying"), 13> CDStartPlaying;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readingType"), 14> CDReadingType;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("autoPause"), 15> CDAutoPause;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("report"), 16> CDReport;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("setLocPending"), 17> CDSetLocPending;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("muted"), 18> CDMuted;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("peakFlag"), 19> CDPeakFlag;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("playTrack"), 20> CDPlayTrack;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("playStartCycle"), 21> CDPlayStartCycle;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("status"), 22> CDStatus;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("speed"), 23> CDSpeed;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readSpan"), 24> CDReadSpan;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("interruptCauseMask"), 25> CDInterruptCauseMask;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("atv"), 26> CDATV;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("atvPending"), 27> CDATVPending;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("adpcmMuted"), 28> CDADPCMMuted;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("mode"), 29> CDMode;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("filterFile"), 30> CDFilterFile;
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("filterChannel"), 31> CDFilterChannel;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("xaEnded"), 32> CDXAEnded;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("xaFirstSector"), 33> CDXAFirstSector;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("soundMapEnabled"), 34> CDSoundMapEnabled;
typedef Protobuf::MessageField<CDMSF, TYPESTRING("currentPosition"), 35> CDCurrentPosition;
typedef Protobuf::MessageField<CDMSF, TYPESTRING("seekPosition"), 36> CDSeekPosition;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("lastLocP"), 37> CDLastLocP;
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("lastLocL"), 38> CDLastLocL;
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("seed"), 39> CDSeed;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("lidOpen"), 40> CDLidOpen;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("wasLidOpened"), 41> CDWasLidOpened;
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("lidCloseScheduled"), 42> CDLidCloseScheduled;
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("lidCloseAtCycles"), 43> CDLidCloseAtCycles;
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("commandFifo"), 44> CDCommandFifo;
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("commandExecuting"), 45> CDCommandExecuting;
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("responseFifo0"), 46> CDResponseFifo0;
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("responseFifo1"), 47> CDResponseFifo1;
typedef Protobuf::MessageField<CDADPCM, TYPESTRING("xaLeft"), 48> CDXALeft;
typedef Protobuf::MessageField<CDADPCM, TYPESTRING("xaRight"), 49> CDXARight;
typedef Protobuf::Message<
TYPESTRING("CDRom"), CDDataFIFO, CDDataFIFOIndex, CDDataFIFOSize, CDDataFIFOPending, CDRegisterAddress, CDMotorOn,
CDSpeedChanged, CDInvalidLocL, CDDataRequested, CDSubheaderFilter, CDRealtime, CDReadingState, CDStartPlaying,
CDReadingType, CDAutoPause, CDReport, CDSetLocPending, CDMuted, CDPeakFlag, CDPlayTrack, CDPlayStartCycle, CDStatus,
CDSpeed, CDReadSpan, CDInterruptCauseMask, CDATV, CDATVPending, CDADPCMMuted, CDMode, CDFilterFile, CDFilterChannel,
CDXAEnded, CDXAFirstSector, CDSoundMapEnabled, CDCurrentPosition, CDSeekPosition, CDLastLocP, CDLastLocL, CDSeed,
CDLidOpen, CDWasLidOpened, CDLidCloseScheduled, CDLidCloseAtCycles, CDCommandFifo, CDCommandExecuting,
CDResponseFifo0, CDResponseFifo1, CDXALeft, CDXARight>
CDRom;
typedef Protobuf::MessageField<CDRom, TYPESTRING("cdrom"), 15> CDRomField;
typedef Protobuf::Message<TYPESTRING("SaveState"), SaveStateInfoField, ThumbnailField, MemoryField, RegistersField,
GPUField, SPUField, SIOField, CDRomField, HardwareField, CountersField, MDECField,
PCdrvFilesField, CallStacksField>
GPUField, SPUField, SIOField, HardwareField, CountersField, MDECField, PCdrvFilesField,
CallStacksField, CDRomField>
SaveState;
typedef Protobuf::ProtoFile<SaveStateInfo, Thumbnail, Memory, DelaySlotInfo, Registers, GPU, ADPCMDecode, XA,
::PCSX::SPU::Chan::Data, ::PCSX::SPU::ADSRInfo, ::PCSX::SPU::ADSRInfoEx, Channel, SPU, SIO,
CDRom, Hardware, Rcnt, Counters, MDEC, PCdrvFile, Call, CallStack, CallStacks, SaveState>
CDQueueElement, CDMSF, CDADPCM, CDRom, Hardware, Rcnt, Counters, MDEC, PCdrvFile, Call,
CallStack, CallStacks, SaveState>
ProtoFile;
SaveState constructSaveState();
+8 -4
View File
@@ -584,17 +584,21 @@ void PCSX::SoftGPU::impl::write0(BlitVramVram *prim) {
imageSX = prim->w;
imageSY = prim->h;
if ((imageX0 == imageX1) && (imageY0 == imageY1)) return;
// An in-place copy still rewrites every pixel, so it is only a no-op without set-mask.
if ((imageX0 == imageX1) && (imageY0 == imageY1) && !getSetMask16()) return;
if (imageSX <= 0) return;
if (imageSY <= 0) return;
// The copy honours the GP0(E6h) mask settings, per halfword. The masked case takes
// this per-pixel path, which walks in the same order as the fast paths below.
if ((imageY0 + imageSY) > VRAM_HEIGHT || (imageX0 + imageSX) > 1024 || (imageY1 + imageSY) > VRAM_HEIGHT ||
(imageX1 + imageSX) > 1024) {
(imageX1 + imageSX) > 1024 || getCheckMask() || getSetMask16()) {
int i, j;
for (j = 0; j < imageSY; j++) {
for (i = 0; i < imageSX; i++) {
m_vram16[(1024 * ((imageY1 + j) & VRAM_Y_MASK)) + ((imageX1 + i) & VRAM_X_MASK)] =
m_vram16[(1024 * ((imageY0 + j) & VRAM_Y_MASK)) + ((imageX0 + i) & VRAM_X_MASK)];
uint16_t &dst = m_vram16[(1024 * ((imageY1 + j) & VRAM_Y_MASK)) + ((imageX1 + i) & VRAM_X_MASK)];
if (getCheckMask() && (dst & 0x8000)) continue;
dst = m_vram16[(1024 * ((imageY0 + j) & VRAM_Y_MASK)) + ((imageX0 + i) & VRAM_X_MASK)] | getSetMask16();
}
}
+3
View File
@@ -55,6 +55,9 @@ struct SoftRenderer {
void drawingAreaEnd(GPU::DrawingAreaEnd *prim);
void drawingOffset(GPU::DrawingOffset *prim);
void maskBit(GPU::MaskBit *prim);
// GP0(E6h) state, for the VRAM transfers that honour it outside the rasterizer.
bool getCheckMask() const { return m_checkMask; }
uint16_t getSetMask16() const { return m_setMask16; }
bool checkCoord4(int16_t &x0, int16_t &y0, int16_t &x1, int16_t &y1, int16_t &x2, int16_t &y2, int16_t &x3,
int16_t &y3);
+18 -21
View File
@@ -568,7 +568,7 @@ void PCSX::GUI::init(std::function<void()> applyArguments) {
});
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
m_hasCoreProfile = true;
@@ -582,12 +582,12 @@ void PCSX::GUI::init(std::function<void()> applyArguments) {
}
// SDL splits window and GL context creation, so the 3.0 fallback has to
// cover both: a 3.2-core context can fail to materialize even after the
// cover both: a 3.3-core context can fail to materialize even after the
// window itself succeeded. On platforms where the pixel format binds at
// window creation (Win32 WGL is the strict case) a clean retry needs a
// fresh window too, so we destroy and recreate both.
if (!m_window || !m_glContext) {
g_system->log(LogClass::UI, "SDL failed to create OpenGL 3.2 core context, retrying with any 3.0 profile\n");
g_system->log(LogClass::UI, "SDL failed to create OpenGL 3.3 core context, retrying with any 3.0 profile\n");
if (m_glContext) {
SDL_GL_DestroyContext(m_glContext);
m_glContext = nullptr;
@@ -1316,12 +1316,12 @@ void PCSX::GUI::endFrame() {
if (ImGui::MenuItem(_("Reload Disk Image"), nullptr, nullptr, currentIso && !currentIso->failed())) {
PCSX::g_emulator->m_cdrom->clearIso();
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(currentIso->getIsoPath()));
PCSX::g_emulator->m_cdrom->check();
PCSX::g_emulator->m_cdrom->parseIso();
g_system->hardReset();
}
if (ImGui::MenuItem(_("Close Disk Image"))) {
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile));
PCSX::g_emulator->m_cdrom->check();
PCSX::g_emulator->m_cdrom->parseIso();
}
if (ImGui::MenuItem(_("Load binary"))) {
showOpenBinaryDialog = true;
@@ -1390,16 +1390,13 @@ void PCSX::GUI::endFrame() {
ImGui::Separator();
if (ImGui::MenuItem(_("Open LID"))) {
PCSX::g_emulator->m_cdrom->setLidOpenTime(-1);
PCSX::g_emulator->m_cdrom->lidInterrupt();
PCSX::g_emulator->m_cdrom->openLid();
}
if (ImGui::MenuItem(_("Close LID"))) {
PCSX::g_emulator->m_cdrom->setLidOpenTime(0);
PCSX::g_emulator->m_cdrom->lidInterrupt();
PCSX::g_emulator->m_cdrom->closeLid();
}
if (ImGui::MenuItem(_("Open and close LID"))) {
PCSX::g_emulator->m_cdrom->setLidOpenTime((int64_t)time(nullptr) + 2);
PCSX::g_emulator->m_cdrom->lidInterrupt();
PCSX::g_emulator->m_cdrom->scheduleCloseLid();
}
ImGui::Separator();
if (ImGui::MenuItem(_("Reset settings..."))) {
@@ -1649,7 +1646,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
std::vector<PCSX::u8string> fileToOpen = m_openIsoFileDialog.selected();
if (!fileToOpen.empty()) {
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(reinterpret_cast<const char*>(fileToOpen[0].c_str())));
PCSX::g_emulator->m_cdrom->check();
PCSX::g_emulator->m_cdrom->parseIso();
}
}
@@ -2321,8 +2318,8 @@ which may include additional checks.
Also will make the boot time substantially
faster by not displaying the logo.)"));
auto bios = settings.get<Emulator::SettingBios>().string();
ImGui::InputText(_("BIOS file"), const_cast<char*>(reinterpret_cast<const char*>(bios.c_str())), bios.length(),
ImGuiInputTextFlags_ReadOnly);
ImGui::InputText(_("BIOS file"), const_cast<char*>(reinterpret_cast<const char*>(bios.c_str())),
bios.length() + 1, ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
selectBiosDialog = ImGui::Button("...");
if (ImGui::Checkbox(_("Enable Debugger"), &debugSettings.get<Emulator::DebugSettings::Debug>().value)) {
@@ -2508,18 +2505,18 @@ of the emulator to take effect.)");
void PCSX::GUI::interruptsScaler() {
static const char* names[] = {
"SIO", "SIO1", "CDR", "CDR Read", "GPU DMA", "MDEC Out DMA", "SPU DMA",
"GPU Busy", "MDEC In DMA", "GPU OTC DMA", "CDR DMA", "SPU", "CDR Decoded Buffer", "CDR Lid Seek",
"CDR Play",
"SIO", "SIO1", "CDR FIFO", "CDR Command", "CDR Reads", "GPU DMA",
"MDEC Out DMA", "SPU DMA", "MDEC In DMA", "GPU OTC DMA", "CDR DMA",
};
if (ImGui::Begin(_("Interrupt Scaler"), &m_showInterruptsScaler)) {
static_assert(std::size(names) == std::extent_v<decltype(R3000Acpu::m_scheduleScales)>);
if (ImGui::Begin(_("Scheduler Scaler"), &m_showInterruptsScaler)) {
if (ImGui::Button(_("Reset all"))) {
for (auto& scale : g_emulator->m_cpu->m_interruptScales) {
for (auto& scale : g_emulator->m_cpu->m_scheduleScales) {
scale = 1.0f;
}
}
unsigned counter = 0;
for (auto& scale : g_emulator->m_cpu->m_interruptScales) {
for (auto& scale : g_emulator->m_cpu->m_scheduleScales) {
ImGui::SliderFloat(names[counter], &scale, 0.0f, 100.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
counter++;
}
@@ -2837,7 +2834,7 @@ void PCSX::GUI::magicOpen(const char* pathStr) {
// Iso loader is last because its detection is the most broken at the moment.
g_emulator->m_cdrom->setIso(new CDRIso(path));
g_emulator->m_cdrom->check();
g_emulator->m_cdrom->parseIso();
}
bool PCSX::GUI::getSaveStateExists(uint32_t slot) {
+13
View File
@@ -44,6 +44,13 @@ std::string_view PCSX::Shaders::CrtLottes::Output::frag() {
// from Timothy Lottes
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
#endif
uniform sampler2D Texture;
uniform vec2 u_srcSize;
@@ -149,6 +156,12 @@ std::string_view PCSX::Shaders::CrtLottes::Offscreen::frag() {
// from Timothy Lottes
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
#endif
uniform sampler2D Texture;
in vec2 Frag_UV;
+6
View File
@@ -61,6 +61,12 @@ void main() {
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp usampler2D;
#endif
uniform usampler2D u_dataHeatmap;
uniform usampler2D u_audioHeatmap;
+2 -2
View File
@@ -539,7 +539,7 @@ void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) {
showOpenIsoFileDialog = ImGui::MenuItem(_("Open Disk Image"));
if (ImGui::MenuItem(_("Close Disk Image"))) {
g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile));
g_emulator->m_cdrom->check();
g_emulator->m_cdrom->parseIso();
}
ImGui::EndMenu();
}
@@ -559,7 +559,7 @@ void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) {
std::vector<PCSX::u8string> fileToOpen = m_openIsoFileDialog.selected();
if (!fileToOpen.empty()) {
g_emulator->m_cdrom->setIso(new CDRIso(reinterpret_cast<const char*>(fileToOpen[0].c_str())));
g_emulator->m_cdrom->check();
g_emulator->m_cdrom->parseIso();
}
}
auto iso = cdrom->m_iso.get();
+2
View File
@@ -108,6 +108,8 @@ bool PCSX::Widgets::Log::draw(GUI* gui, const char* title) {
auto& debugSettings = g_emulator->settings.get<Emulator::SettingDebugSettings>();
changed |= ImGui::MenuItem(_("Log CD-ROM commands"), nullptr,
&debugSettings.get<Emulator::DebugSettings::LoggingCDROM>().value);
changed |= ImGui::MenuItem(_("Log CD-ROM hardware access"), nullptr,
&debugSettings.get<Emulator::DebugSettings::LoggingHWCDROM>().value);
changed |=
ImGui::MenuItem(_("CPU trace"), nullptr, &debugSettings.get<Emulator::DebugSettings::Trace>().value);
changed |= ImGui::MenuItem(_("Skip ISR during CPU traces"), nullptr,
+1 -1
View File
@@ -33,7 +33,7 @@ bool PCSX::Widgets::PIOCart::draw(const char* title) {
return false;
} else {
auto pio_rom = settings.get<Emulator::SettingEXP1Filepath>().string();
ImGui::InputText("ROM", const_cast<char*>(reinterpret_cast<const char*>(pio_rom.c_str())), pio_rom.length(),
ImGui::InputText("ROM", const_cast<char*>(reinterpret_cast<const char*>(pio_rom.c_str())), pio_rom.length() + 1,
ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
selectEXP1Dialog = ImGui::Button("...");
+7
View File
@@ -50,6 +50,13 @@ void main() {
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
precision highp usampler2D;
#endif
uniform sampler2D u_ramTexture;
uniform usampler2D u_readHeatmap;
+1 -1
View File
@@ -377,7 +377,7 @@ void PCSX::Widgets::Registers::draw(PCSX::GUI* gui, PCSX::psxRegisters* register
contextMenu("##pc", registers->pc, 16);
ImGui::PopItemWidth();
ImGui::Text("cycle: %08x", registers->cycle);
ImGui::Text("int : %08x", registers->interrupt);
ImGui::Text("sched: %08x", registers->scheduleMask);
ImGui::EndTabItem();
}
ImGui::PopFont();
+12
View File
@@ -82,6 +82,12 @@ void main() {
static const GLchar *const c_defaultPixelShader = GL_SHADER_VERSION R"(
// The Pixel Shader is most likely what the user will want to change.
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
#endif
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
@@ -137,6 +143,12 @@ end
)";
static const GLchar *const c_24bppConversionFragShader = GL_SHADER_VERSION R"(
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
#endif
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
+7
View File
@@ -52,6 +52,13 @@ void main() {
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
precision highp float;
#if __VERSION__ == 300
// On MacOS, with shader version 410, these are illegal, but on
// Windows, with shader version 300 es and old drivers, these
// are mandatory.
precision highp sampler2D;
precision highp usampler2D;
#endif
uniform int u_24shift;
uniform bool u_alpha;
+2
View File
@@ -69,6 +69,8 @@ local bufferMeta = {
end
elseif index == 'pbSlice' then
return Support._internal.createPBSliceFromBuffer(buffer)
elseif index == 'cast' then
return function(buffer, ctype) return ffi.cast(ctype, buffer.data) end
end
error('Unknown index `' .. index .. '` for LuaBuffer')
end,
+8 -4
View File
@@ -413,7 +413,7 @@ int pcsxMain(int argc, char **argv) {
if (isoToOpen.empty()) isoToOpen = args.get<std::string>("loadiso", "");
if (isoToOpen.empty()) isoToOpen = args.get<std::string>("disk", "");
if (!isoToOpen.empty()) emulator->m_cdrom->setIso(new PCSX::CDRIso(isoToOpen));
emulator->m_cdrom->check();
emulator->m_cdrom->parseIso();
// After settings are loaded, we're fine setting the SPU part of the emulation.
emulator->m_spu->init();
@@ -557,9 +557,13 @@ runner.init({
// This will ensure we don't do certain cleanups that are awaiting other tasks,
// which could result in deadlocks on exit in case we encountered a serious problem.
// This may cause data loss when writing files, but that's life when encountering
// a serious problem in a software.
system->setEmergencyExit();
uvThread.setEmergencyExit();
// a serious problem in a software. An error from the command-line archives or
// Lua scripts happens before anything is running, so that one still shuts down
// normally; skipping the uv thread's join there aborts the process on exit.
if (!system->m_inStartup) {
system->setEmergencyExit();
uvThread.setEmergencyExit();
}
throw;
}
}
+22 -1
View File
@@ -37,8 +37,15 @@ namespace EnvelopeTables {
// steps: a phase counts samples in the fraction accumulator up to
// denominator[rate], then steps the level by numerator_increase[rate] (rising)
// or numerator_decrease[rate] (falling, negative).
// The hardware adds 0x8000 >> (shift - 11) to its step counter each sample and clamps
// that increment to at least 1, so every shift from 26 up steps once per 32768 samples
// (psx-spx: "a rate of 0x76 behaves like 0x6A").
constexpr int kMaxEffectiveShift = 26;
struct DenominatorGenerator {
static consteval int32_t calculateValue(std::size_t rate) { return (rate < 48) ? 1 : (1 << ((rate >> 2) - 11)); }
static consteval int32_t calculateValue(std::size_t rate) {
const int shift = static_cast<int>(rate >> 2);
return (rate < 48) ? 1 : (1 << (std::min(shift, kMaxEffectiveShift) - 11));
}
};
struct NumeratorIncreaseGenerator {
@@ -56,6 +63,10 @@ struct NumeratorDecreaseGenerator {
constexpr auto denominator = PCSX::generateTable<128, DenominatorGenerator>();
constexpr auto numerator_increase = PCSX::generateTable<128, NumeratorIncreaseGenerator>();
constexpr auto numerator_decrease = PCSX::generateTable<128, NumeratorDecreaseGenerator>();
static_assert(denominator.data[0x6a] == 32768);
static_assert(denominator.data[0x76] == denominator.data[0x6a]);
static_assert(denominator.data[0x7c] == 32768);
} // namespace EnvelopeTables
namespace {
@@ -71,6 +82,10 @@ constexpr int32_t kSustainLevelMask = 0xf; // ...compared against the 0..15
// (val & 0x7f00) >> 8, sustain is (val & 0x1fc0) >> 6), so the knee's rate-step lands past the
// end of the 128-entry tables on the slowest rates. Saturate instead of reading off the end.
constexpr int kMaxRateIndex = 127;
// A rate field of all ones (step and shift bits together) never steps. That is 0x7f for
// attack and sustain and 0x1f for release, tested on the register value before the knee.
constexpr int kFrozenRate = 0x7f;
constexpr int kFrozenCoarseRate = 0x1f;
} // namespace
// Write the freshly computed envelope state back and return the full 15-bit
@@ -93,6 +108,8 @@ int PCSX::SPU::AdsrEnvelope::Attack() {
int32_t envelopeVolFraction = m_adsrx.get<exEnvelopeVolF>().value;
const bool exponential = m_adsrx.get<exAttackModeExp>().value != 0;
if (rateIndex == kFrozenRate) return commit(envelopeVol, envelopeVolFraction);
// Past the knee the exponential attack curve flattens to a slower rate.
if (exponential && envelopeVol >= kExponentialKnee) {
rateIndex = std::min(rateIndex + kExponentialKneeRateStep, kMaxRateIndex);
@@ -139,6 +156,8 @@ int PCSX::SPU::AdsrEnvelope::Sustain() {
const bool exponential = m_adsrx.get<exSustainModeExp>().value != 0;
const bool increase = m_adsrx.get<exSustainIncrease>().value != 0;
if (rateIndex == kFrozenRate) return commit(envelopeVol, envelopeVolFraction);
if (increase) {
// Past the knee the exponential rise flattens to a slower rate.
if (exponential && envelopeVol >= kExponentialKnee) {
@@ -171,6 +190,8 @@ int PCSX::SPU::AdsrEnvelope::Release(bool &channelOn) {
int32_t envelopeVolFraction = m_adsrx.get<exEnvelopeVolF>().value;
const bool exponential = m_adsrx.get<exReleaseModeExp>().value != 0;
if (m_adsrx.get<exReleaseRate>().value == kFrozenCoarseRate) return commit(envelopeVol, envelopeVolFraction);
if (++envelopeVolFraction >= EnvelopeTables::denominator.data[rateIndex]) {
envelopeVolFraction = 0;
envelopeVol += exponential ? (EnvelopeTables::numerator_decrease.data[rateIndex] * envelopeVol) >>
+8 -5
View File
@@ -86,6 +86,9 @@ class impl final : public SPUInterface {
void writeDMAMem(uint16_t *, int) final;
void readDMAMem(uint16_t *, int) final;
virtual void playADPCMchannel(xa_decode_t *) final;
void setXAVolume(const uint8_t atv[4]) final {
for (unsigned i = 0; i < 4; i++) xaAtv[i] = atv[i];
}
void save(SaveStates::SPU &) final;
void load(const SaveStates::SPU &) final;
@@ -173,8 +176,6 @@ class impl final : public SPUInterface {
// Sound buffer sizes.
// 400 ms complete sound buffer.
static const size_t SOUNDSIZE = 70560;
// 137 ms test buffer. If less than this is buffered, a new upload happens.
static const size_t TESTSIZE = 24192;
// Roughly 1 ms of data.
static const size_t NSSIZE = 45;
@@ -254,6 +255,10 @@ class impl final : public SPUInterface {
// The temporary capture buffer for CD audio left/right.
CaptureBuffer captureBuffer;
// Emulated cycle of the last CD audio fed to the capture buffer. While the CD is
// feeding, an empty buffer means the emulation is behind the mixer, not silence.
// 0 means the CD has not fed anything yet.
std::atomic<uint64_t> cdFeedCycle = 0;
// The capture buffer index for voice 1 and voice 3.
int32_t capBufVoiceIndex = 0;
@@ -340,9 +345,6 @@ class impl final : public SPUInterface {
int iCycle = 0;
int16_t *pS;
// Secure start counter.
int secureStart = 0;
// XA
xa_decode_t *xapGlobal = 0;
@@ -355,6 +357,7 @@ class impl final : public SPUInterface {
unsigned xaRingPos = 0;
int xaSixStep = 6;
int16_t xaLastL = 0, xaLastR = 0;
uint8_t xaAtv[4] = {0x80, 0, 0x80, 0};
int16_t zigzag(const int16_t *ring, unsigned table);
SDLAudio m_audioOut = {settings};
+70 -72
View File
@@ -158,79 +158,77 @@ int PCSX::SPU::ReverbUnit::mixLeft(int ns, uint16_t* spuMem, uint16_t spuCtrl) {
// master enable bit is clear. That bit only gates writes into the buffer.
// Games that stream audio into the work area depend on this.
const bool writeEnabled = (spuCtrl & kReverbMasterEnable) != 0;
{
firPushIn(*(mixStart + (ns << 1)), *(mixStart + (ns << 1) + 1));
altLeftTick = (callCount & 1) != 0;
const int in = sat16(altLeftTick ? firDecimate(h44L) : firDecimate(h44R));
const int sSame = altLeftTick ? rvb.IIR_SRC_A0 : rvb.IIR_SRC_A1;
const int dSame = altLeftTick ? rvb.IIR_DEST_A0 : rvb.IIR_DEST_A1;
const int sDiff = altLeftTick ? rvb.IIR_SRC_B0 : rvb.IIR_SRC_B1;
const int dDiff = altLeftTick ? rvb.IIR_DEST_B0 : rvb.IIR_DEST_B1;
const int inCoef = altLeftTick ? rvb.IN_COEF_L : rvb.IN_COEF_R;
const int c1 = altLeftTick ? rvb.ACC_SRC_A0 : rvb.ACC_SRC_A1;
const int c2 = altLeftTick ? rvb.ACC_SRC_B0 : rvb.ACC_SRC_B1;
const int c3 = altLeftTick ? rvb.ACC_SRC_C0 : rvb.ACC_SRC_C1;
const int c4 = altLeftTick ? rvb.ACC_SRC_D0 : rvb.ACC_SRC_D1;
const int mA = altLeftTick ? rvb.MIX_DEST_A0 : rvb.MIX_DEST_A1;
const int mB = altLeftTick ? rvb.MIX_DEST_B0 : rvb.MIX_DEST_B1;
auto at = [&](int off, int extra) { return (int)reinterpret_cast<int16_t*>(spuMem)[wrapOffset(off, extra)]; };
const int iirSame =
sat16(rdiv15((int64_t)(getBuffer(sSame, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
const int iirDiff =
sat16(rdiv15((int64_t)(getBuffer(sDiff, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
// psx-spx stores the IIR result at [mLSAME] and reads the previous value back
// from [mLSAME-2], i.e. the destination cell itself one 16-bit sample earlier -
// hence the -1 extraSample on the read and none on the store.
if (writeEnabled) {
setBuffer<0>(dSame,
rdiv15((int64_t)(iirSame * rvb.IIR_ALPHA)) +
rdiv15((int64_t)(at(dSame, -1) * (32768L - rvb.IIR_ALPHA))),
spuMem);
setBuffer<0>(dDiff,
rdiv15((int64_t)(iirDiff * rvb.IIR_ALPHA)) +
rdiv15((int64_t)(at(dDiff, -1) * (32768L - rvb.IIR_ALPHA))),
spuMem);
}
int out = sat16(rdiv15((int64_t)(getBuffer(c1, spuMem) * rvb.ACC_COEF_A)) +
rdiv15((int64_t)(getBuffer(c2, spuMem) * rvb.ACC_COEF_B)) +
rdiv15((int64_t)(getBuffer(c3, spuMem) * rvb.ACC_COEF_C)) +
rdiv15((int64_t)(getBuffer(c4, spuMem) * rvb.ACC_COEF_D)));
// psx-spx "SPU Reverb Formula", APF1 then APF2, three clauses each:
// Lout=Lout-vAPF1*[mLAPF1-dAPF1], [mLAPF1]=Lout, Lout=Lout*vAPF1+[mLAPF1-dAPF1]
// Lout=Lout-vAPF2*[mLAPF2-dAPF2], [mLAPF2]=Lout, Lout=Lout*vAPF2+[mLAPF2-dAPF2]
// LeftOutput = Lout*vLOUT (all products divided by 8000h)
const int tapA = getBuffer(mA - rvb.FB_SRC_A, spuMem);
out = sat16(out - rdiv15((int64_t)(tapA * rvb.FB_ALPHA)));
if (writeEnabled) setBuffer<0>(mA, out, spuMem);
out = sat16(rdiv15((int64_t)(out * rvb.FB_ALPHA)) + tapA);
const int tapB = getBuffer(mB - rvb.FB_SRC_B, spuMem);
out = sat16(out - rdiv15((int64_t)(tapB * rvb.FB_X)));
if (writeEnabled) setBuffer<0>(mB, out, spuMem);
out = sat16(rdiv15((int64_t)(out * rvb.FB_X)) + tapB);
// vLOUT/vROUT are signed 16bit and their products divide by 8000h, not 4000h.
// registers.cc assigns these from a uint16_t with no cast, unlike every other
// reverb coefficient there, so sign-extend at the point of use.
const int vol = altLeftTick ? (int)(int16_t)rvb.VolLeft : (int)(int16_t)rvb.VolRight;
const int wet = rdiv15((int64_t)out * vol);
if (altLeftTick) {
rvb.wetLeft = wet;
for (int i = 19; i > 0; i--) h22L[i] = h22L[i - 1];
h22L[0] = wet;
} else {
rvb.wetRight = wet;
for (int i = 19; i > 0; i--) h22R[i] = h22R[i - 1];
h22R[0] = wet;
// Address advances once per COMPLETE 22.05kHz iteration, after both halves.
rvb.CurrAddr++;
if (rvb.CurrAddr > 0x3ffff) rvb.CurrAddr = rvb.StartAddr;
}
// Emit the even (interpolating) polyphase on this channel's own compute cycle
// and the centre-tap passthrough on the other one, which puts the wet output one
// phase earlier than pairing them the obvious way round. h22[9] and not h22[10]:
// on the off cycle h22[10] is 21 ticks old, so the parity swap on its own would
// jitter the group delay between 19 and 21.
return altLeftTick ? firInterp(h22L, false) : h22L[9];
firPushIn(*(mixStart + (ns << 1)), *(mixStart + (ns << 1) + 1));
altLeftTick = (callCount & 1) != 0;
const int in = sat16(altLeftTick ? firDecimate(h44L) : firDecimate(h44R));
const int sSame = altLeftTick ? rvb.IIR_SRC_A0 : rvb.IIR_SRC_A1;
const int dSame = altLeftTick ? rvb.IIR_DEST_A0 : rvb.IIR_DEST_A1;
const int sDiff = altLeftTick ? rvb.IIR_SRC_B0 : rvb.IIR_SRC_B1;
const int dDiff = altLeftTick ? rvb.IIR_DEST_B0 : rvb.IIR_DEST_B1;
const int inCoef = altLeftTick ? rvb.IN_COEF_L : rvb.IN_COEF_R;
const int c1 = altLeftTick ? rvb.ACC_SRC_A0 : rvb.ACC_SRC_A1;
const int c2 = altLeftTick ? rvb.ACC_SRC_B0 : rvb.ACC_SRC_B1;
const int c3 = altLeftTick ? rvb.ACC_SRC_C0 : rvb.ACC_SRC_C1;
const int c4 = altLeftTick ? rvb.ACC_SRC_D0 : rvb.ACC_SRC_D1;
const int mA = altLeftTick ? rvb.MIX_DEST_A0 : rvb.MIX_DEST_A1;
const int mB = altLeftTick ? rvb.MIX_DEST_B0 : rvb.MIX_DEST_B1;
auto at = [&](int off, int extra) { return (int)reinterpret_cast<int16_t*>(spuMem)[wrapOffset(off, extra)]; };
const int iirSame =
sat16(rdiv15((int64_t)(getBuffer(sSame, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
const int iirDiff =
sat16(rdiv15((int64_t)(getBuffer(sDiff, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
// psx-spx stores the IIR result at [mLSAME] and reads the previous value back
// from [mLSAME-2], i.e. the destination cell itself one 16-bit sample earlier -
// hence the -1 extraSample on the read and none on the store.
if (writeEnabled) {
setBuffer<0>(
dSame,
rdiv15((int64_t)(iirSame * rvb.IIR_ALPHA)) + rdiv15((int64_t)(at(dSame, -1) * (32768L - rvb.IIR_ALPHA))),
spuMem);
setBuffer<0>(
dDiff,
rdiv15((int64_t)(iirDiff * rvb.IIR_ALPHA)) + rdiv15((int64_t)(at(dDiff, -1) * (32768L - rvb.IIR_ALPHA))),
spuMem);
}
int out = sat16(rdiv15((int64_t)(getBuffer(c1, spuMem) * rvb.ACC_COEF_A)) +
rdiv15((int64_t)(getBuffer(c2, spuMem) * rvb.ACC_COEF_B)) +
rdiv15((int64_t)(getBuffer(c3, spuMem) * rvb.ACC_COEF_C)) +
rdiv15((int64_t)(getBuffer(c4, spuMem) * rvb.ACC_COEF_D)));
// psx-spx "SPU Reverb Formula", APF1 then APF2, three clauses each:
// Lout=Lout-vAPF1*[mLAPF1-dAPF1], [mLAPF1]=Lout, Lout=Lout*vAPF1+[mLAPF1-dAPF1]
// Lout=Lout-vAPF2*[mLAPF2-dAPF2], [mLAPF2]=Lout, Lout=Lout*vAPF2+[mLAPF2-dAPF2]
// LeftOutput = Lout*vLOUT (all products divided by 8000h)
const int tapA = getBuffer(mA - rvb.FB_SRC_A, spuMem);
out = sat16(out - rdiv15((int64_t)(tapA * rvb.FB_ALPHA)));
if (writeEnabled) setBuffer<0>(mA, out, spuMem);
out = sat16(rdiv15((int64_t)(out * rvb.FB_ALPHA)) + tapA);
const int tapB = getBuffer(mB - rvb.FB_SRC_B, spuMem);
out = sat16(out - rdiv15((int64_t)(tapB * rvb.FB_X)));
if (writeEnabled) setBuffer<0>(mB, out, spuMem);
out = sat16(rdiv15((int64_t)(out * rvb.FB_X)) + tapB);
// vLOUT/vROUT are signed 16bit and their products divide by 8000h, not 4000h.
// registers.cc assigns these from a uint16_t with no cast, unlike every other
// reverb coefficient there, so sign-extend at the point of use.
const int vol = altLeftTick ? (int)(int16_t)rvb.VolLeft : (int)(int16_t)rvb.VolRight;
const int wet = rdiv15((int64_t)out * vol);
if (altLeftTick) {
rvb.wetLeft = wet;
for (int i = 19; i > 0; i--) h22L[i] = h22L[i - 1];
h22L[0] = wet;
} else {
rvb.wetRight = wet;
for (int i = 19; i > 0; i--) h22R[i] = h22R[i - 1];
h22R[0] = wet;
// Address advances once per COMPLETE 22.05kHz iteration, after both halves.
rvb.CurrAddr++;
if (rvb.CurrAddr > 0x3ffff) rvb.CurrAddr = rvb.StartAddr;
}
// Emit the even (interpolating) polyphase on this channel's own compute cycle
// and the centre-tap passthrough on the other one, which puts the wet output one
// phase earlier than pairing them the obvious way round. h22[9] and not h22[10]:
// on the off cycle h22[10] is 21 ticks old, so the parity swap on its own would
// jitter the group delay between 19 and 21.
return altLeftTick ? firInterp(h22L, false) : h22L[9];
}
////////////////////////////////////////////////////////////////////////
+1 -1
View File
@@ -136,7 +136,7 @@ class SDLAudio {
return false;
}
}
size_t getBytesBuffered(unsigned streamId = 0) {
size_t getFramesBuffered(unsigned streamId = 0) {
switch (streamId) {
case 0:
return m_voicesStream.buffered();
+14 -31
View File
@@ -23,6 +23,8 @@
#include <cstdlib>
#include <thread>
#include "core/psxemulator.h"
#include "core/r3000a.h"
#include "spu/adsr.h"
#include "spu/externals.h"
#include "spu/interface.h"
@@ -424,35 +426,8 @@ void PCSX::SPU::impl::MainThread() {
// Run until we are shutting down.
while (!endThread) {
int volumeDivisor = 4 - settings.get<Volume>();
//--------------------------------------------------//
// At the start of each pass, check whether there is enough free space in the audio output
// buffer to fill in new data, or whether there is a new channel to start. If neither, wait
// until free space is available or a new channel gets started.
// Should a new channel start immediately, that is, is at least one bit in 0..MAXCHANNEL
// set?
if (newChannelMask) {
// Set secureStart.
secureStart++;
if (secureStart > 5)
// If it has been set 5 times in a row, meaning a new sample has been started on 5
// tries in a row, reset it to give the sound update a chance.
secureStart = 0;
} else
// 0: no new channel should start.
secureStart = 0;
// No new start, no thread end, and still enough data in the sound buffer?
while (!secureStart && !endThread && (m_audioOut.getBytesBuffered() > TESTSIZE)) {
// Reset secureStart.
secureStart = 0;
using namespace std::chrono_literals;
std::this_thread::sleep_for(5ms);
// If a new channel kicks in, or the sound buffer runs low, leave the loop.
if (newChannelMask) secureStart = 1;
}
// Pacing comes from the blocking enqueue in feedStreamData at the bottom of the loop: the
// output ring only drains as the audio output consumes it.
{
// capBufVoiceIndex is reset from the emulation thread (resetCaptureBuffer).
@@ -571,10 +546,18 @@ void PCSX::SPU::impl::MainThread() {
void PCSX::SPU::impl::writeCaptureBufferCD(int numbSamples) {
std::lock_guard<std::mutex> lock(cbMtx);
if (mixIrqAddress) {
// CD audio arrives once per sector, 1/75 s apart in emulated time. Within a few
// sectors of the last one the CD is still feeding, and an empty buffer only means
// the emulation runs slower than the mixer: wait for it instead of writing silence.
// The mixer starts before the CPU exists.
const auto *cpu = PCSX::g_emulator->m_cpu.get();
const uint64_t feeding = PCSX::g_emulator->m_psxClockSpeed * 4 / 75;
const uint64_t lastFeed = cdFeedCycle;
const bool cdFeeding = cpu && (lastFeed != 0) && (cpu->m_regs.cycle - lastFeed < feeding);
for (int n = 0; n < numbSamples; n++) {
if (captureBuffer.startIndex == captureBuffer.endIndex) {
// If there are no samples left in the temp buffer,
// we still HAVE to keep writing to the capture buffer.
if (cdFeeding) break;
// Nothing is feeding the CD input: the capture records silence.
spuMem[captureBuffer.currIndex] = 0;
spuMem[captureBuffer.currIndex + 0x200] = 0;
} else {
+37 -2
View File
@@ -19,6 +19,8 @@
#include <algorithm>
#include "core/psxemulator.h"
#include "core/r3000a.h"
#include "spu/externals.h"
#include "spu/interface.h"
@@ -76,6 +78,32 @@ void PCSX::SPU::impl::FeedXA(xa_decode_t *xap) {
// mixer thread. Taken unconditionally: gating it on an unlocked read of mixIrqAddress
// is itself a race, and could pair a skipped lock with a later unlock.
std::unique_lock<std::mutex> cbLock(cbMtx);
cdFeedCycle = PCSX::g_emulator->m_cpu->m_regs.cycle;
if (xap->freq == 44100) {
// CD-DA is already at the output rate and skips the zigzag interpolator.
for (int i = 0; i < xap->nsamples; i++) {
int16_t l = xap->pcm[i * 2];
int16_t r = xap->pcm[i * 2 + 1];
if (mixIrqAddress) {
captureBuffer.CDCapLeft[captureBuffer.endIndex] = (uint16_t)l;
captureBuffer.CDCapRight[captureBuffer.endIndex] = (uint16_t)r;
captureBuffer.endIndex = (captureBuffer.endIndex + 1) % CaptureBuffer::CB_SIZE;
if (captureBuffer.endIndex == captureBuffer.startIndex) {
g_system->log(LogClass::SPU, "Capture buffer is overflowing. Increase CB_SIZE.\n");
}
}
SDLAudio::Frame f;
f.L = l / voldiv;
f.R = r / voldiv;
*XAFeed++ = f;
}
cbLock.unlock();
if (XAFeed != XABuffer) {
m_audioOut.feedStreamData(reinterpret_cast<SDLAudio::Frame *>(XABuffer), (XAFeed - XABuffer), 1);
}
return;
}
for (int i = 0; i < xap->nsamples; i++) {
int16_t l, r;
@@ -98,8 +126,15 @@ void PCSX::SPU::impl::FeedXA(xa_decode_t *xap) {
if (--xaSixStep > 0) continue;
xaSixStep = 6;
for (unsigned t = 0; t < 7; t++) {
int16_t rawSampleL = zigzag(xaRingL, t);
int16_t rawSampleR = zigzag(xaRingR, t);
int64_t zl = zigzag(xaRingL, t);
int64_t zr = zigzag(xaRingR, t);
// ATV matrix after the interpolator, then 1.026 before the 16-bit clamp and 0.973
// after it. On a SCPH-9002 the level rises linearly with ATV up to the clamp, and the
// clamped level sits at 31880, as with CD-DA.
int outL = std::clamp(int(((zl * xaAtv[0] + zr * xaAtv[3]) * 33617) >> 22), -0x8000, 0x7fff);
int outR = std::clamp(int(((zr * xaAtv[2] + zl * xaAtv[1]) * 33617) >> 22), -0x8000, 0x7fff);
int16_t rawSampleL = (outL * 31880) >> 15;
int16_t rawSampleR = (outR * 31880) >> 15;
if (mixIrqAddress) {
captureBuffer.CDCapLeft[captureBuffer.endIndex] = (uint16_t)rawSampleL;
captureBuffer.CDCapRight[captureBuffer.endIndex] = (uint16_t)rawSampleR;
+90
View File
@@ -128,6 +128,49 @@ struct MSF {
++(*this);
return tmp;
}
MSF &operator--() {
if (f == 0) {
f = 74;
if (s == 0) {
s = 59;
if (m == 0) {
m = 99;
} else {
m--;
}
} else {
s--;
}
} else {
f--;
}
return *this;
}
MSF operator--(int) {
MSF tmp = *this;
--(*this);
return tmp;
}
MSF operator+(const MSF &other) const {
MSF tmp = *this;
tmp += other;
return tmp;
}
MSF operator-(const MSF &other) const {
MSF tmp = *this;
tmp -= other;
return tmp;
}
MSF &operator+=(const MSF &other) {
uint32_t lba = toLBA() + other.toLBA();
*this = MSF(lba);
return *this;
}
MSF &operator-=(const MSF &other) {
uint32_t lba = toLBA() - other.toLBA();
*this = MSF(lba);
return *this;
}
constexpr uint32_t toLBA() const { return (m * 60 + s) * 75 + f; }
constexpr void toBCD(uint8_t *dst) const {
dst[0] = itob(m);
@@ -157,6 +200,53 @@ void computeEDCECC(uint8_t *sector);
// Compute the CRC-16 for the SubQ channel.
uint16_t subqCRC(const uint8_t *d, int len = 10);
struct SubHeaders {
uint8_t fileNumber;
uint8_t channelNumber;
uint8_t subMode;
uint8_t codingInfo;
bool fromBuffer(const uint8_t buffer[]) {
fileNumber = buffer[0];
channelNumber = buffer[1];
subMode = buffer[2];
codingInfo = buffer[3];
return (buffer[0] == buffer[4]) && (buffer[1] == buffer[5]) && (buffer[2] == buffer[6]) &&
(buffer[3] == buffer[7]);
}
void toBuffer(uint8_t buffer[]) const {
buffer[0] = buffer[4] = fileNumber;
buffer[1] = buffer[5] = channelNumber;
buffer[2] = buffer[6] = subMode;
buffer[3] = buffer[7] = codingInfo;
}
bool isEndOfRecord() const { return subMode & 0x01; }
bool isVideo() const { return subMode & 0x02; }
bool isAudio() const { return subMode & 0x04; }
bool isData() const { return subMode & 0x08; }
bool isTrigger() const { return subMode & 0x10; }
bool isForm2() const { return subMode & 0x20; }
bool isRealTime() const { return subMode & 0x40; }
bool isEOF() const { return subMode & 0x80; }
void setEndOfRecord() { subMode |= 0x01; }
void setVideo() { subMode |= 0x02; }
void setAudio() { subMode |= 0x04; }
void setData() { subMode |= 0x08; }
void setTrigger() { subMode |= 0x10; }
void setForm2() { subMode |= 0x20; }
void setRealTime() { subMode |= 0x40; }
void setEOF() { subMode |= 0x80; }
void clearEndOfRecord() { subMode &= ~0x01; }
void clearVideo() { subMode &= ~0x02; }
void clearAudio() { subMode &= ~0x04; }
void clearData() { subMode &= ~0x08; }
void clearTrigger() { subMode &= ~0x10; }
void clearForm2() { subMode &= ~0x20; }
void clearRealTime() { subMode &= ~0x40; }
void clearEOF() { subMode &= ~0x80; }
};
} // namespace IEC60908b
} // namespace PCSX
+1 -1
View File
@@ -105,7 +105,7 @@ PCSX::IEC60908b::MSF PCSX::ISO9660Builder::writeSectorAt(const uint8_t* sectorDa
ptr[15] = 2;
ptr[16] = ptr[20] = 0;
ptr[17] = ptr[21] = 0;
ptr[18] = ptr[22] = 8;
ptr[18] = ptr[22] = 8 | 0x20; // form 2 flag
ptr[19] = ptr[23] = 0;
memcpy(ptr + 24, sectorData, 2324);
compute_edcecc(ptr);
+3 -2
View File
@@ -59,6 +59,7 @@ enum MidiMeta : uint8_t {
MIDI_META_COPYRIGHT = 0x02,
MIDI_META_TRACK_NAME = 0x03,
MIDI_META_MARKER = 0x06,
MIDI_META_CUE_POINT = 0x07,
MIDI_META_END_OF_TRACK = 0x2F,
MIDI_META_TEMPO = 0x51,
};
@@ -302,8 +303,8 @@ inline void MidiFile::parseTrack(PCSX::IO<PCSX::File> track) {
uint8_t b2 = track->byte();
ev.tempo = (b0 << 16) | (b1 << 8) | b2;
events.push_back(ev);
} else if (metaType == MIDI_META_MARKER || metaType == MIDI_META_TEXT || metaType == MIDI_META_TRACK_NAME ||
metaType == MIDI_META_COPYRIGHT) {
} else if (metaType == MIDI_META_MARKER || metaType == MIDI_META_CUE_POINT || metaType == MIDI_META_TEXT ||
metaType == MIDI_META_TRACK_NAME || metaType == MIDI_META_COPYRIGHT) {
// Text-based meta events
MidiEvent ev = {};
ev.absoluteTick = absTick;
+6
View File
@@ -31,3 +31,9 @@ function TestBasic:test_coroutine()
local r = coroutine.yield()
lu.assertEquals(r, 42)
end
function TestBasic:test_unknown_event_name()
local ok, err = pcall(PCSX.Events.createEventListener, 'GPU::VSync', function() end)
lu.assertFalse(ok)
lu.assertStrContains(err, "'GPU::VSync'")
end
+90
View File
@@ -328,6 +328,96 @@ function TestIsoBuilder:test_m2RawRoundTrip()
end
end
-- Payload size per sector for each sector mode, where that payload sits in the raw frame, and the form bit
-- the builder has to put in the subheader for the modes where it synthesizes one.
local sectorLayouts = {
{ mode = 'RAW', size = 2352, offset = 0 },
{ mode = 'M2_RAW', size = 2336, offset = 16 },
{ mode = 'M2_FORM1', size = 2048, offset = 24, form = 0x00 },
{ mode = 'M2_FORM2', size = 2324, offset = 24, form = 0x20 },
}
local function patternByte(i) return (i * 7 + 3) % 251 end
local function patternData(size)
local data = ffi.new('uint8_t[?]', size)
for i = 0, size - 1 do data[i] = patternByte(i) end
return data
end
-- Counts the bytes of a read back payload that differ from the pattern, reporting the first one.
local function assertPattern(file, base, size, what)
local bad, first = 0, nil
for i = 0, size - 1 do
if file:readU8At(base + i) ~= patternByte(i) then
bad = bad + 1
first = first or i
end
end
lu.assertEquals(bad, 0, string.format('%s: %d of %d payload bytes corrupted, first at byte %s', what, bad,
size, tostring(first)))
end
local function assertForm(file, base, form, what)
for _, offset in ipairs({ 18, 22 }) do
lu.assertEquals(bit.band(file:readU8At(base + offset), 0x20), form,
string.format('%s: wrong form bit in subheader byte %d', what, offset))
end
end
function TestIsoBuilder:test_writeSectorRoundTrip()
-- Every mode writeSectorAt takes must land the caller's payload in the frame untouched.
for _, layout in ipairs(sectorLayouts) do
local out = Support.File.buffer()
local builder = PCSX.isoBuilder(out)
for lba = 0, 1 do
builder:writeSectorAt(patternData(layout.size), layout.size, lba, layout.mode)
end
out:rSeek(0)
for lba = 0, 1 do
local what = string.format('writeSectorAt %s sector %d', layout.mode, lba)
assertPattern(out, lba * 2352 + layout.offset, layout.size, what)
if layout.form then assertForm(out, lba * 2352, layout.form, what) end
end
end
end
function TestIsoBuilder:test_closeRoundTrip()
-- Same for file content laid out by close(), through both the single threaded writer and the parallel one.
for _, layout in ipairs(sectorLayouts) do
for _, threads in ipairs({ 1, 4 }) do
local what = string.format('close(%d) %s', threads, layout.mode)
local size = 2 * layout.size
local payload = Support.File.buffer()
local data = ffi.new('uint8_t[?]', size)
for i = 0, size - 1 do data[i] = patternByte(i % layout.size) end
payload:write(ffi.cast('const char*', data), size)
payload:rSeek(0)
local out = Support.File.buffer()
local builder = PCSX.isoBuilder(out)
builder:writeLicense()
builder:setVolumeIdent('ROUNDTRIP')
local root = builder:createRoot(1)
local file = builder:createFile(root, 'PATTERN.BIN', payload)
file:setSectorMode(layout.mode)
builder:close(threads)
out:rSeek(0)
local iso = PCSX.openIso(out)
local lba = file:getLBA()
local rb = iso:open(lba, size, layout.mode)
for s = 0, 1 do
assertPattern(rb, s * layout.size, layout.size, what .. ' sector ' .. s)
end
if layout.form then
local raw = iso:open(lba, 2 * 2352, 'RAW')
for s = 0, 1 do assertForm(raw, s * 2352, layout.form, what .. ' sector ' .. s) end
end
end
end
end
function TestIsoBuilder:test_anchorErrorOnBackwardLBA()
-- Anchoring to an LBA that's already passed must raise an error at close time.
local out = Support.File.buffer()
+87
View File
@@ -0,0 +1,87 @@
-- Copyright (C) 2026 PCSX-Redux authors
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the
-- Free Software Foundation, Inc.,
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local lu = require 'luaunit'
TestMds = {}
local content = string.rep('0123456789ABCDEF', 256)
local function u16(v) return string.char(v % 256, math.floor(v / 256) % 256) end
local function u32(v) return u16(v % 65536) .. u16(math.floor(v / 65536)) end
local function pad(s, n) return s .. string.rep('\0', n - #s) end
-- Build a one-track ISO, then store it as an .mdf with 96 bytes of
-- interleaved subchannel after each 2352-byte frame, plus a minimal .mds.
local function writeImage(base)
local out = Support.File.buffer()
local builder = PCSX.isoBuilder(out)
builder:writeLicense()
local root = builder:createRoot(1)
local data = Support.File.buffer()
data:write(content)
data:rSeek(0)
builder:createFile(root, 'TEST.DAT', data)
builder:close()
local size = out:size()
lu.assertEquals(size % 2352, 0)
local sectors = size / 2352
out:rSeek(0)
local mdf = io.open(base .. '.mdf', 'wb')
for _ = 1, sectors do
mdf:write(tostring(out:read(2352)), string.rep('\0', 96))
end
mdf:close()
-- Header: signature, session block at 0x58, one track block at 0x70,
-- its extra block (pregap, length) at 0xc0.
local mds = pad('MEDIA DESCRIPTOR', 0x50) .. u32(0x58) .. u32(0)
mds = mds .. pad(string.rep('\0', 14) .. u16(1) .. u32(0) .. u32(0x70), 0x18)
local track = string.char(0xec, 0x08, 0x14, 0x00, 0x01) .. string.rep('\0', 4) .. string.char(0, 2, 0) ..
u32(0xc0) .. u16(2448)
mds = mds .. pad(pad(track, 0x28) .. u32(0) .. u32(0), 0x50)
mds = mds .. u32(0) .. u32(sectors)
local f = io.open(base .. '.mds', 'wb')
f:write(mds)
f:close()
end
local function checkRead(path)
local iso = PCSX.openIso(path)
lu.assertFalse(iso:failed())
local file = iso:createReader():open('TEST.DAT;1')
lu.assertFalse(file:failed())
lu.assertEquals(file:size(), #content)
lu.assertEquals(tostring(file:read(#content)), content)
end
function TestMds:setUp()
self.base = os.tmpname()
writeImage(self.base)
end
function TestMds:tearDown()
os.remove(self.base .. '.mdf')
os.remove(self.base .. '.mds')
os.remove(self.base)
end
function TestMds:test_openMdf() checkRead(self.base .. '.mdf') end
function TestMds:test_openMds() checkRead(self.base .. '.mds') end
+18
View File
@@ -0,0 +1,18 @@
-- Copyright (C) 2026 PCSX-Redux authors
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the
-- Free Software Foundation, Inc.,
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
error('startup script error')
+72
View File
@@ -0,0 +1,72 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
TEST(cdrom, Interpreter) {
MainInvoker invoker("-run", "-stdout", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-iso", "test.cue", "-loadexe", "src/mips/tests/cdrom/cdrom.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
TEST(cdrom, Dynarec) {
MainInvoker invoker("-run", "-stdout", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-iso",
"test.cue", "-loadexe", "src/mips/tests/cdrom/cdrom.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
// Boots the test disc through OpenBIOS's shell and kernel loader, with no -loadexe. Passes once
// the disc's boot executable is in RAM and the CPU is running inside it, fails after 6000 frames.
static const char c_discBoot[] = R"(
local f = assert(io.open('src/mips/monitor/hosts/retail/monitor-retail.ps-exe', 'rb'))
local exe = f:read('*a')
f:close()
local function u32(o)
local a, b, c, d = exe:byte(o + 1, o + 4)
return a + b * 0x100 + c * 0x10000 + d * 0x1000000
end
local tAddr, tSize = u32(0x18), u32(0x1c)
local text = exe:sub(0x801, 0x800 + tSize)
local frames = 0
DiscBootListener = PCSX.Events.createEventListener('GPU::Vsync', function()
frames = frames + 1
local pc = PCSX.getRegisters().pc
if pc >= tAddr and pc < tAddr + tSize then
local ram = ffi.string(PCSX.getMemPtr() + bit.band(tAddr, 0x1fffff), tSize)
if ram == text then PCSX.quit(0) end
end
if frames >= 6000 then PCSX.quit(1) end
end)
)";
TEST(cdrom, OpenBIOSBootsDiscInterpreter) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-iso", "test.cue", "-exec", c_discBoot);
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
TEST(cdrom, OpenBIOSBootsDiscDynarec) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-iso",
"test.cue", "-exec", c_discBoot);
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+7
View File
@@ -28,6 +28,13 @@ TEST(COPBranch, Interpreter) {
EXPECT_EQ(ret, 0);
}
TEST(COPBranch, Dynarec) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-luacov",
"-loadexe", "src/mips/tests/cop-branch/cop-branch.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
TEST(COPBranch, Disassembler) {
static constexpr struct {
uint32_t code;
+30
View File
@@ -0,0 +1,30 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
// Boots the gpu-command guest suite headless under OpenBIOS. ret == 0 means
// every GP0 env command's GPUSTAT reflection matched the soft GPU.
TEST(GPUCommand, StatusAfterWrite) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-loadexe", "src/mips/tests/gpu-command/gpu-command.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+32
View File
@@ -0,0 +1,32 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
// Boots the gpu-fbrect guest suite headless under OpenBIOS. The guest
// returns cester's failed-test count as its exit code; -testmode maps that
// to the process exit code, so ret == 0 means every GP0(0x02) FillVram
// assertion matched the soft GPU.
TEST(GPUFBRect, FillVram) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-loadexe", "src/mips/tests/gpu-fbrect/gpu-fbrect.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+80
View File
@@ -0,0 +1,80 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
// Boots the gpu-irq guest suite headless under OpenBIOS. ret == 0 means the
// GP0(1Fh)/GP1(02h) IRQ flag behaviour in GPUSTAT.24 matched the soft GPU.
TEST(GPUIRQ, RequestAcknowledge) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-loadexe", "src/mips/tests/gpu-irq/gpu-irq.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
// IRQ1 survives a save state. Code poked into RAM raises it with GP0(1Fh) and keeps copying
// GPUSTAT to 0x80100100; a state is saved, GP1(02h) acks the flag, the state is loaded, and
// GPUSTAT.24 must be set again. Loading replays GP1 writes that clear the flag before
// GPU::deserialize puts it back.
static const char c_irq1SaveState[] = R"(
local ram = ffi.cast('uint32_t*', PCSX.getMemPtr())
local base = 0x100000 / 4
local out = 0x100100 / 4
local stubs = {
-- 0x80100000: GP0(1Fh) raises IRQ1, then GPUSTAT -> 0x80100100 forever
0x3c081f80, 0x3c091f00, 0xad091810, 0x3c0b8010,
0x8d0a1814, 0x00000000, 0xad6a0100, 0x08040004, 0x00000000,
0, 0, 0, 0, 0, 0, 0,
-- 0x80100040: GP1(02h) acks IRQ1, then the same loop
0x3c081f80, 0x3c090200, 0xad091814, 0x3c0b8010,
0x8d0a1814, 0x00000000, 0xad6a0100, 0x08040014, 0x00000000,
}
local sentinel = 0xdeadbeef
local frames = 0
local state
local function irq1() return bit.band(ram[out], 0x01000000) ~= 0 and ram[out] ~= sentinel end
IRQ1SaveStateListener = PCSX.Events.createEventListener('GPU::Vsync', function()
frames = frames + 1
if frames == 120 then
for i, w in ipairs(stubs) do ram[base + i - 1] = w end
ram[out] = sentinel
PCSX.getRegisters().pc = 0x80100000
elseif frames == 122 then
if not irq1() then print('IRQ1 never raised') PCSX.quit(2) return end
state = PCSX.createSaveState()
ram[out] = sentinel
PCSX.getRegisters().pc = 0x80100040
elseif frames == 124 then
if ram[out] == sentinel or irq1() then print('GP1(02h) did not ack IRQ1') PCSX.quit(3) return end
PCSX.loadSaveState(state)
elseif frames == 125 then
ram[out] = sentinel
elseif frames == 127 then
if irq1() then PCSX.quit(0) else print('IRQ1 lost across the save state') PCSX.quit(1) end
end
end)
)";
TEST(GPUIRQ, SaveStateKeepsFlag) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-exec", c_irq1SaveState);
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+32
View File
@@ -0,0 +1,32 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
// Boots the gpu-polyline guest suite headless under OpenBIOS. The guest
// returns cester's failed-test count as its exit code; -testmode maps that
// to the process exit code, so ret == 0 means every GP0(0x48) terminator
// assertion matched the soft GPU.
TEST(GPUPolyline, Terminator) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-loadexe", "src/mips/tests/gpu-polyline/gpu-polyline.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+30
View File
@@ -0,0 +1,30 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "gtest/gtest.h"
#include "main/main.h"
// Boots the gpu-vram-move guest suite headless under OpenBIOS. ret == 0 means
// every GP0(0x80) VRAM-to-VRAM copy assertion matched the soft GPU.
TEST(GPUVRAMMove, Copy) {
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-loadexe", "src/mips/tests/gpu-vram-move/gpu-vram-move.ps-exe");
int ret = invoker.invoke();
EXPECT_EQ(ret, 0);
}
+13
View File
@@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include <stdexcept>
#include "gtest/gtest.h"
#include "main/main.h"
@@ -73,3 +75,14 @@ TEST(LuaRewind, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.rewind"), 0);
TEST(LuaRewind, Dynarec) { EXPECT_EQ(runLuaDynTest("tests.lua.rewind"), 0); }
TEST(LuaIsoBuilder, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.isobuilder"), 0); }
TEST(LuaIsoBuilder, Dynarec) { EXPECT_EQ(runLuaDynTest("tests.lua.isobuilder"), 0); }
TEST(LuaMds, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.mds"), 0); }
TEST(LuaStartup, DofileError) {
MainInvoker failing("-no-ui", "-cli", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
"-dofile", "tests/lua/startup-error.lua");
EXPECT_THROW(failing.invoke(), std::runtime_error);
// The failed startup still has to shut down cleanly, or nothing else can start afterwards.
MainInvoker next("-no-ui", "-cli", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter", "-exec",
"PCSX.quit(0)");
EXPECT_EQ(next.invoke(), 0);
}
+301
View File
@@ -0,0 +1,301 @@
/*
* Every field of the CD-ROM save state has to come back out of a save state
* exactly as it went in.
*/
#include <stdint.h>
#include <string>
#include "core/sstate.h"
#include "gtest/gtest.h"
namespace {
using namespace PCSX::SaveStates;
struct Queue {
uint8_t payload[16] = {};
uint8_t value = 0;
bool valueRead = false;
bool hasValue = false;
bool hitMax = false;
uint8_t payloadSize = 0;
uint8_t payloadIndex = 0;
};
struct State {
uint8_t datafifo[2352] = {};
uint32_t datafifoindex = 0;
uint32_t datafifosize = 0;
uint32_t datafifopending = 0;
uint8_t registeraddress = 0;
bool motoron = false;
bool speedchanged = false;
bool invalidlocl = false;
bool datarequested = false;
bool subheaderfilter = false;
bool realtime = false;
uint8_t readingstate = 0;
bool startplaying = false;
uint8_t readingtype = 0;
bool autopause = false;
bool report = false;
bool setlocpending = false;
bool muted = false;
bool peakflag = false;
uint8_t playtrack = 0;
uint32_t playstartcycle = 0;
uint8_t status = 0;
uint8_t speed = 0;
uint8_t readspan = 0;
uint8_t interruptcausemask = 0;
uint8_t atv[4] = {};
uint8_t atvpending[4] = {};
bool adpcmmuted = false;
uint8_t mode = 0;
uint8_t filterfile = 0;
uint8_t filterchannel = 0;
bool xaended = false;
bool xafirstsector = false;
bool soundmapenabled = false;
uint8_t currentposition[3] = {};
uint8_t seekposition[3] = {};
uint8_t lastlocp[8] = {};
uint8_t lastlocl[8] = {};
uint64_t seed = 0;
bool lidopen = false;
bool waslidopened = false;
bool lidclosescheduled = false;
uint32_t lidcloseatcycles = 0;
Queue commandfifo;
Queue commandexecuting;
Queue responsefifo0;
Queue responsefifo1;
int32_t xaleft[2] = {};
int32_t xaright[2] = {};
};
CDRom bind(State& s) {
return CDRom{
CDDataFIFO{s.datafifo},
CDDataFIFOIndex{s.datafifoindex},
CDDataFIFOSize{s.datafifosize},
CDDataFIFOPending{s.datafifopending},
CDRegisterAddress{s.registeraddress},
CDMotorOn{s.motoron},
CDSpeedChanged{s.speedchanged},
CDInvalidLocL{s.invalidlocl},
CDDataRequested{s.datarequested},
CDSubheaderFilter{s.subheaderfilter},
CDRealtime{s.realtime},
CDReadingState{s.readingstate},
CDStartPlaying{s.startplaying},
CDReadingType{s.readingtype},
CDAutoPause{s.autopause},
CDReport{s.report},
CDSetLocPending{s.setlocpending},
CDMuted{s.muted},
CDPeakFlag{s.peakflag},
CDPlayTrack{s.playtrack},
CDPlayStartCycle{s.playstartcycle},
CDStatus{s.status},
CDSpeed{s.speed},
CDReadSpan{s.readspan},
CDInterruptCauseMask{s.interruptcausemask},
CDATV{s.atv},
CDATVPending{s.atvpending},
CDADPCMMuted{s.adpcmmuted},
CDMode{s.mode},
CDFilterFile{s.filterfile},
CDFilterChannel{s.filterchannel},
CDXAEnded{s.xaended},
CDXAFirstSector{s.xafirstsector},
CDSoundMapEnabled{s.soundmapenabled},
CDCurrentPosition{CDMSF{CDMSFMinute{s.currentposition[0]}, CDMSFSecond{s.currentposition[1]}, CDMSFFrame{s.currentposition[2]}}},
CDSeekPosition{CDMSF{CDMSFMinute{s.seekposition[0]}, CDMSFSecond{s.seekposition[1]}, CDMSFFrame{s.seekposition[2]}}},
CDLastLocP{s.lastlocp},
CDLastLocL{s.lastlocl},
CDSeed{s.seed},
CDLidOpen{s.lidopen},
CDWasLidOpened{s.waslidopened},
CDLidCloseScheduled{s.lidclosescheduled},
CDLidCloseAtCycles{s.lidcloseatcycles},
CDCommandFifo{CDQueueElement{CDQueuePayload{s.commandfifo.payload}, CDQueueValue{s.commandfifo.value}, CDQueueValueRead{s.commandfifo.valueRead}, CDQueueHasValue{s.commandfifo.hasValue}, CDQueueHitMax{s.commandfifo.hitMax}, CDQueuePayloadSize{s.commandfifo.payloadSize}, CDQueuePayloadIndex{s.commandfifo.payloadIndex}}},
CDCommandExecuting{CDQueueElement{CDQueuePayload{s.commandexecuting.payload}, CDQueueValue{s.commandexecuting.value}, CDQueueValueRead{s.commandexecuting.valueRead}, CDQueueHasValue{s.commandexecuting.hasValue}, CDQueueHitMax{s.commandexecuting.hitMax}, CDQueuePayloadSize{s.commandexecuting.payloadSize}, CDQueuePayloadIndex{s.commandexecuting.payloadIndex}}},
CDResponseFifo0{CDQueueElement{CDQueuePayload{s.responsefifo0.payload}, CDQueueValue{s.responsefifo0.value}, CDQueueValueRead{s.responsefifo0.valueRead}, CDQueueHasValue{s.responsefifo0.hasValue}, CDQueueHitMax{s.responsefifo0.hitMax}, CDQueuePayloadSize{s.responsefifo0.payloadSize}, CDQueuePayloadIndex{s.responsefifo0.payloadIndex}}},
CDResponseFifo1{CDQueueElement{CDQueuePayload{s.responsefifo1.payload}, CDQueueValue{s.responsefifo1.value}, CDQueueValueRead{s.responsefifo1.valueRead}, CDQueueHasValue{s.responsefifo1.hasValue}, CDQueueHitMax{s.responsefifo1.hitMax}, CDQueuePayloadSize{s.responsefifo1.payloadSize}, CDQueuePayloadIndex{s.responsefifo1.payloadIndex}}},
CDXALeft{CDADPCM{CDADPCMY0{s.xaleft[0]}, CDADPCMY1{s.xaleft[1]}}},
CDXARight{CDADPCM{CDADPCMY0{s.xaright[0]}, CDADPCMY1{s.xaright[1]}}},
};
}
// No field may hold its default, so a field that is lost or swapped shows up.
void fill(State& s) {
for (unsigned i = 0; i < 2352; i++) s.datafifo[i] = static_cast<uint8_t>(i * 7 + 2);
s.datafifoindex = 0x80000000u + 314187;
s.datafifosize = 0x80000000u + 418916;
s.datafifopending = 0x80000000u + 523645;
s.registeraddress = 79;
s.motoron = true;
s.speedchanged = true;
s.invalidlocl = true;
s.datarequested = true;
s.subheaderfilter = true;
s.realtime = true;
s.readingstate = 92;
s.startplaying = true;
s.readingtype = 105;
s.autopause = true;
s.report = true;
s.setlocpending = true;
s.muted = true;
s.peakflag = true;
s.playtrack = 118;
s.playstartcycle = 0x80000000u + 1047290;
s.status = 144;
s.speed = 157;
s.readspan = 170;
s.interruptcausemask = 183;
for (unsigned i = 0; i < 4; i++) s.atv[i] = static_cast<uint8_t>(i * 7 + 15);
for (unsigned i = 0; i < 4; i++) s.atvpending[i] = static_cast<uint8_t>(i * 7 + 16);
s.adpcmmuted = true;
s.mode = 222;
s.filterfile = 235;
s.filterchannel = 248;
s.xaended = true;
s.xafirstsector = true;
s.soundmapenabled = true;
for (unsigned i = 0; i < 3; i++) s.currentposition[i] = static_cast<uint8_t>(i * 7 + 20);
for (unsigned i = 0; i < 3; i++) s.seekposition[i] = static_cast<uint8_t>(i * 7 + 21);
for (unsigned i = 0; i < 8; i++) s.lastlocp[i] = static_cast<uint8_t>(i * 7 + 22);
for (unsigned i = 0; i < 8; i++) s.lastlocl[i] = static_cast<uint8_t>(i * 7 + 23);
s.seed = 0x8000000000000000ull + 24000072;
s.lidopen = true;
s.waslidopened = true;
s.lidclosescheduled = true;
s.lidcloseatcycles = 0x80000000u + 2618225;
for (unsigned i = 0; i < 16; i++) s.commandfifo.payload[i] = static_cast<uint8_t>(i * 7 + 26);
s.commandfifo.value = 101;
s.commandfifo.valueRead = true;
s.commandfifo.hasValue = true;
s.commandfifo.hitMax = true;
s.commandfifo.payloadSize = 114;
s.commandfifo.payloadIndex = 127;
for (unsigned i = 0; i < 16; i++) s.commandexecuting.payload[i] = static_cast<uint8_t>(i * 7 + 30);
s.commandexecuting.value = 153;
s.commandexecuting.valueRead = true;
s.commandexecuting.hasValue = true;
s.commandexecuting.hitMax = true;
s.commandexecuting.payloadSize = 166;
s.commandexecuting.payloadIndex = 179;
for (unsigned i = 0; i < 16; i++) s.responsefifo0.payload[i] = static_cast<uint8_t>(i * 7 + 34);
s.responsefifo0.value = 205;
s.responsefifo0.valueRead = true;
s.responsefifo0.hasValue = true;
s.responsefifo0.hitMax = true;
s.responsefifo0.payloadSize = 218;
s.responsefifo0.payloadIndex = 231;
for (unsigned i = 0; i < 16; i++) s.responsefifo1.payload[i] = static_cast<uint8_t>(i * 7 + 38);
s.responsefifo1.value = 6;
s.responsefifo1.valueRead = true;
s.responsefifo1.hasValue = true;
s.responsefifo1.hitMax = true;
s.responsefifo1.payloadSize = 19;
s.responsefifo1.payloadIndex = 32;
s.xaleft[0] = -332598;
s.xaleft[1] = -340517;
s.xaright[0] = -348436;
s.xaright[1] = -356355;
}
} // namespace
TEST(SaveStateCDRom, EveryFieldRoundTrips) {
State in, out;
fill(in);
PCSX::Protobuf::OutSlice outSlice;
bind(in).serialize(&outSlice);
std::string data = outSlice.finalize();
CDRom loaded = bind(out);
PCSX::Protobuf::InSlice inSlice(reinterpret_cast<const uint8_t*>(data.data()), data.size());
loaded.deserialize(&inSlice, 0);
loaded.commit();
for (unsigned i = 0; i < 2352; i++) EXPECT_EQ(out.datafifo[i], in.datafifo[i]) << "datafifo[" << i << "]";
EXPECT_EQ(out.datafifoindex, in.datafifoindex) << "datafifoindex";
EXPECT_EQ(out.datafifosize, in.datafifosize) << "datafifosize";
EXPECT_EQ(out.datafifopending, in.datafifopending) << "datafifopending";
EXPECT_EQ(out.registeraddress, in.registeraddress) << "registeraddress";
EXPECT_EQ(out.motoron, in.motoron) << "motoron";
EXPECT_EQ(out.speedchanged, in.speedchanged) << "speedchanged";
EXPECT_EQ(out.invalidlocl, in.invalidlocl) << "invalidlocl";
EXPECT_EQ(out.datarequested, in.datarequested) << "datarequested";
EXPECT_EQ(out.subheaderfilter, in.subheaderfilter) << "subheaderfilter";
EXPECT_EQ(out.realtime, in.realtime) << "realtime";
EXPECT_EQ(out.readingstate, in.readingstate) << "readingstate";
EXPECT_EQ(out.startplaying, in.startplaying) << "startplaying";
EXPECT_EQ(out.readingtype, in.readingtype) << "readingtype";
EXPECT_EQ(out.autopause, in.autopause) << "autopause";
EXPECT_EQ(out.report, in.report) << "report";
EXPECT_EQ(out.setlocpending, in.setlocpending) << "setlocpending";
EXPECT_EQ(out.muted, in.muted) << "muted";
EXPECT_EQ(out.peakflag, in.peakflag) << "peakflag";
EXPECT_EQ(out.playtrack, in.playtrack) << "playtrack";
EXPECT_EQ(out.playstartcycle, in.playstartcycle) << "playstartcycle";
EXPECT_EQ(out.status, in.status) << "status";
EXPECT_EQ(out.speed, in.speed) << "speed";
EXPECT_EQ(out.readspan, in.readspan) << "readspan";
EXPECT_EQ(out.interruptcausemask, in.interruptcausemask) << "interruptcausemask";
for (unsigned i = 0; i < 4; i++) EXPECT_EQ(out.atv[i], in.atv[i]) << "atv[" << i << "]";
for (unsigned i = 0; i < 4; i++) EXPECT_EQ(out.atvpending[i], in.atvpending[i]) << "atvpending[" << i << "]";
EXPECT_EQ(out.adpcmmuted, in.adpcmmuted) << "adpcmmuted";
EXPECT_EQ(out.mode, in.mode) << "mode";
EXPECT_EQ(out.filterfile, in.filterfile) << "filterfile";
EXPECT_EQ(out.filterchannel, in.filterchannel) << "filterchannel";
EXPECT_EQ(out.xaended, in.xaended) << "xaended";
EXPECT_EQ(out.xafirstsector, in.xafirstsector) << "xafirstsector";
EXPECT_EQ(out.soundmapenabled, in.soundmapenabled) << "soundmapenabled";
for (unsigned i = 0; i < 3; i++) EXPECT_EQ(out.currentposition[i], in.currentposition[i]) << "currentposition[" << i << "]";
for (unsigned i = 0; i < 3; i++) EXPECT_EQ(out.seekposition[i], in.seekposition[i]) << "seekposition[" << i << "]";
for (unsigned i = 0; i < 8; i++) EXPECT_EQ(out.lastlocp[i], in.lastlocp[i]) << "lastlocp[" << i << "]";
for (unsigned i = 0; i < 8; i++) EXPECT_EQ(out.lastlocl[i], in.lastlocl[i]) << "lastlocl[" << i << "]";
EXPECT_EQ(out.seed, in.seed) << "seed";
EXPECT_EQ(out.lidopen, in.lidopen) << "lidopen";
EXPECT_EQ(out.waslidopened, in.waslidopened) << "waslidopened";
EXPECT_EQ(out.lidclosescheduled, in.lidclosescheduled) << "lidclosescheduled";
EXPECT_EQ(out.lidcloseatcycles, in.lidcloseatcycles) << "lidcloseatcycles";
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.commandfifo.payload[i], in.commandfifo.payload[i]) << "commandfifo.payload[" << i << "]";
EXPECT_EQ(out.commandfifo.value, in.commandfifo.value) << "commandfifo.value";
EXPECT_EQ(out.commandfifo.valueRead, in.commandfifo.valueRead) << "commandfifo.valueRead";
EXPECT_EQ(out.commandfifo.hasValue, in.commandfifo.hasValue) << "commandfifo.hasValue";
EXPECT_EQ(out.commandfifo.hitMax, in.commandfifo.hitMax) << "commandfifo.hitMax";
EXPECT_EQ(out.commandfifo.payloadSize, in.commandfifo.payloadSize) << "commandfifo.payloadSize";
EXPECT_EQ(out.commandfifo.payloadIndex, in.commandfifo.payloadIndex) << "commandfifo.payloadIndex";
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.commandexecuting.payload[i], in.commandexecuting.payload[i]) << "commandexecuting.payload[" << i << "]";
EXPECT_EQ(out.commandexecuting.value, in.commandexecuting.value) << "commandexecuting.value";
EXPECT_EQ(out.commandexecuting.valueRead, in.commandexecuting.valueRead) << "commandexecuting.valueRead";
EXPECT_EQ(out.commandexecuting.hasValue, in.commandexecuting.hasValue) << "commandexecuting.hasValue";
EXPECT_EQ(out.commandexecuting.hitMax, in.commandexecuting.hitMax) << "commandexecuting.hitMax";
EXPECT_EQ(out.commandexecuting.payloadSize, in.commandexecuting.payloadSize) << "commandexecuting.payloadSize";
EXPECT_EQ(out.commandexecuting.payloadIndex, in.commandexecuting.payloadIndex) << "commandexecuting.payloadIndex";
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.responsefifo0.payload[i], in.responsefifo0.payload[i]) << "responsefifo0.payload[" << i << "]";
EXPECT_EQ(out.responsefifo0.value, in.responsefifo0.value) << "responsefifo0.value";
EXPECT_EQ(out.responsefifo0.valueRead, in.responsefifo0.valueRead) << "responsefifo0.valueRead";
EXPECT_EQ(out.responsefifo0.hasValue, in.responsefifo0.hasValue) << "responsefifo0.hasValue";
EXPECT_EQ(out.responsefifo0.hitMax, in.responsefifo0.hitMax) << "responsefifo0.hitMax";
EXPECT_EQ(out.responsefifo0.payloadSize, in.responsefifo0.payloadSize) << "responsefifo0.payloadSize";
EXPECT_EQ(out.responsefifo0.payloadIndex, in.responsefifo0.payloadIndex) << "responsefifo0.payloadIndex";
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.responsefifo1.payload[i], in.responsefifo1.payload[i]) << "responsefifo1.payload[" << i << "]";
EXPECT_EQ(out.responsefifo1.value, in.responsefifo1.value) << "responsefifo1.value";
EXPECT_EQ(out.responsefifo1.valueRead, in.responsefifo1.valueRead) << "responsefifo1.valueRead";
EXPECT_EQ(out.responsefifo1.hasValue, in.responsefifo1.hasValue) << "responsefifo1.hasValue";
EXPECT_EQ(out.responsefifo1.hitMax, in.responsefifo1.hitMax) << "responsefifo1.hitMax";
EXPECT_EQ(out.responsefifo1.payloadSize, in.responsefifo1.payloadSize) << "responsefifo1.payloadSize";
EXPECT_EQ(out.responsefifo1.payloadIndex, in.responsefifo1.payloadIndex) << "responsefifo1.payloadIndex";
EXPECT_EQ(out.xaleft[0], in.xaleft[0]) << "xaleft[0]";
EXPECT_EQ(out.xaleft[1], in.xaleft[1]) << "xaleft[1]";
EXPECT_EQ(out.xaright[0], in.xaright[0]) << "xaright[0]";
EXPECT_EQ(out.xaright[1], in.xaright[1]) << "xaright[1]";
}
+165 -118
View File
@@ -6,15 +6,18 @@
#include "fmt/format.h"
#include "imgui_stdlib.h"
#ifdef _MSC_VER
#define _PRISizeT "I"
#if defined(_MSC_VER) && !defined(snprintf)
#define ImSnprintf _snprintf
#else
#define _PRISizeT "z"
#define ImSnprintf snprintf
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define _PRISizeT "I"
#else
#define _PRISizeT "z"
#endif
#ifdef _MSC_VER
#if defined(_MSC_VER) || defined(_UCRT)
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
@@ -34,13 +37,16 @@ MemoryEditor::MemoryEditor(bool& show, size_t base_addr, size_t &goto_addr) : Op
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
HighlightFn = NULL;
HighlightFn = nullptr;
MouseHovered = false;
MouseHoveredAddr = 0;
// State/Internals
ContentsWidthChanged = false;
DataPreviewAddr = DataEditingAddr = (size_t)-1;
DataEditingTakeFocus = false;
GotoAddr = (size_t)-1;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0;
PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32;
RestoreOffset = true;
}
@@ -66,17 +72,17 @@ void MemoryEditor::CalcSizes(Sizes& s, size_t mem_size)
s.ByteSpacingWidth = (float)(int)(s.GlyphWidth * 0.5f);
s.HexCellWidth = (float)(int)((s.ByteWidth * (float)(int)DataTypeGetSize(PreviewDataType) + s.ByteSpacingWidth)); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * (float)(int)(Cols / DataTypeGetSize(PreviewDataType)));
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * (float)(int)(Cols / DataTypeGetSize(PreviewDataType)));
s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii)
{
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
}
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
if (PushMonoFont) ImGui::PopFont();
}
@@ -138,19 +144,22 @@ void MemoryEditor::DrawContents(size_t mem_size)
// Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
if (ReadOnly || !WriteFn || DataEditingAddr >= mem_size)
// Without a WriteFn, behave as ReadOnly: bytes can still be selected and previewed, but not written.
const bool read_only = ReadOnly || !WriteFn;
if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1;
@@ -182,30 +191,39 @@ void MemoryEditor::DrawContents(size_t mem_size)
return fmt::format(fmt::runtime(OptUpperCaseHex ? "{:02X}" : "{:02x}"), byte);
};
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{
size_t addr = (size_t)(line_i * Cols);
size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, BaseAddr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
float byte_pos_x = s.PosHexStart + s.ByteWidth * n + s.ByteSpacingWidth * (n / preview_data_type_size);
float byte_pos_x = s.OffsetHexMinX + s.ByteWidth * n + s.ByteSpacingWidth * (n / preview_data_type_size);
if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x);
// Draw highlight
// Draw highlight or custom background color
size_t DataPreviewHighlightBase = DataPreviewAddr & ~(preview_data_type_size - 1);
bool is_highlight_from_user_range = (HighlightMin && addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(addr));
bool is_highlight_from_preview = (addr >= DataPreviewHighlightBase && addr < DataPreviewHighlightBase + preview_data_type_size);
ImU32 bg_color = 0;
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
bg_color = HighlightColor;
else if (BgColorFn)
bg_color = BgColorFn(addr);
if (bg_color != 0)
{
ImVec2 pos = ImGui::GetCursorScreenPos();
float highlight_width = s.ByteWidth;
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
float bg_width = s.ByteWidth;
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
}
if (DataEditingAddr == addr)
@@ -245,8 +263,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
};
UserData user_data;
user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadByte(addr));
ImSnprintf(user_data.CurrentBufOverwrite, 3, format_byte, ReadByte(addr));
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
if (read_only)
flags |= ImGuiInputTextFlags_ReadOnly;
#if IMGUI_VERSION_NUM >= 18104
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
#else
@@ -263,12 +283,15 @@ void MemoryEditor::DrawContents(size_t mem_size)
if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false;
unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf.c_str(), "%X", &data_input_value) == 1)
if (!read_only && data_write && sscanf(DataInputBuf.c_str(), "%X", &data_input_value) == 1)
{
if (WriteFn) {
WriteFn(addr, (ImU8)data_input_value);
Cache.invalidate();
}
WriteFn(addr, (ImU8)data_input_value);
Cache.invalidate();
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID();
}
@@ -295,10 +318,15 @@ void MemoryEditor::DrawContents(size_t mem_size)
else
ImGui::Text(format_byte_space, b);
}
if (!ReadOnly && WriteFn && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
if (ImGui::IsItemHovered())
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
MouseHovered = true;
MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
}
}
}
@@ -306,17 +334,23 @@ void MemoryEditor::DrawContents(size_t mem_size)
if (OptShowAscii)
{
// Draw ASCII values
ImGui::SameLine(s.PosAsciiStart);
ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols;
addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
{
DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
if (!ReadOnly && WriteFn) {
DataEditingAddr = DataPreviewAddr;
DataEditingTakeFocus = true;
}
DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true;
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
@@ -326,6 +360,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
}
else if (BgColorFn)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(addr));
}
unsigned char c = ReadByte(addr);
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
@@ -338,7 +376,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size)
{
@@ -368,6 +409,20 @@ void MemoryEditor::DrawContents(size_t mem_size)
ImGui::Separator();
DrawPreviewLine(s, mem_size);
}
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
// s.LineHeight is measured with the mono font (see CalcSizes), matching the clipper's line height.
ImGui::BeginChild("##scrolling");
ImGui::SetScrollY((GotoAddr / Cols) * s.LineHeight - avail_size.y * 0.5f);
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
}
void MemoryEditor::DrawOptionsLine(const Sizes& s, size_t mem_size)
@@ -411,21 +466,6 @@ void MemoryEditor::DrawOptionsLine(const Sizes& s, size_t mem_size)
}
}
if (GotoAddr != (size_t)-1) {
if (GotoAddr < mem_size) {
ImGui::BeginChild("##scrolling");
if (PushMonoFont) PushMonoFont();
ImGui::SetScrollFromPosY(
ImGui::GetCursorStartPos().y +
(GotoAddr / Cols) * ImGui::GetTextLineHeight());
if (PushMonoFont) ImGui::PopFont();
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// Clear Input Address and reset to beginning of address space
ImGui::SameLine();
if (ImGui::Button("Clear Input")) {
@@ -450,16 +490,21 @@ void MemoryEditor::DrawPreviewLine(const Sizes& s, size_t mem_size)
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double, ImGuiDataType_Bool };
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{
for (int n = 0; n < ImGuiDataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
PreviewDataType = (ImGuiDataType)n;
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
{
ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
@@ -476,18 +521,19 @@ void MemoryEditor::DrawPreviewLine(const Sizes& s, size_t mem_size)
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
}
// Utilities for Data Preview
// Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* MemoryEditor::DataTypeGetDesc(ImGuiDataType data_type) const
{
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double", "Bool" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type];
}
size_t MemoryEditor::DataTypeGetSize(ImGuiDataType data_type) const
{
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double), 1 };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type];
}
@@ -500,18 +546,18 @@ const char* MemoryEditor::DataFormatGetDesc(DataFormat data_format) const
bool MemoryEditor::IsBigEndian() const
{
uint16_t x = 1;
ImU16 x = 1;
char c[2];
memcpy(c, &x, 2);
return c[0] != 0;
}
void* MemoryEditor::EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
void* MemoryEditor::EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
@@ -522,7 +568,7 @@ void* MemoryEditor::EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int
}
}
void* MemoryEditor::EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
void* MemoryEditor::EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
@@ -530,23 +576,23 @@ void* MemoryEditor::EndianessCopyLittleEndian(void* _dst, void* _src, size_t s,
}
else
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
}
void* MemoryEditor::EndianessCopy(void* dst, void* src, size_t size) const
void* MemoryEditor::EndiannessCopy(void* dst, void* src, size_t size) const
{
static void* (*fp)(void*, void*, size_t, int) = NULL;
if (fp == NULL)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess);
static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == nullptr)
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianness);
}
const char* MemoryEditor::FormatBinary(const uint8_t* buf, int width) const
const char* MemoryEditor::FormatBinary(const ImU8* buf, int width) const
{
IM_ASSERT(width <= 64);
size_t out_n = 0;
@@ -567,7 +613,7 @@ const char* MemoryEditor::FormatBinary(const uint8_t* buf, int width) const
void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{
if (addr >= mem_size) { out_buf[0] = 0; return; }
uint8_t buf[8];
ImU8 buf[8];
size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
for (int i = 0, n = (int)size; i < n; ++i)
@@ -575,8 +621,8 @@ void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType d
if (data_format == DataFormat_Bin)
{
uint8_t binbuf[8];
EndianessCopy(binbuf, buf, size);
ImU8 binbuf[8];
EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return;
}
@@ -586,94 +632,95 @@ void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType d
{
case ImGuiDataType_S8:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
ImS8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break;
}
case ImGuiDataType_U8:
{
uint8_t uint8 = 0;
EndianessCopy(&uint8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
ImU8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break;
}
case ImGuiDataType_S16:
{
int16_t int16 = 0;
EndianessCopy(&int16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
ImS16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_U16:
{
uint16_t uint16 = 0;
EndianessCopy(&uint16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
ImU16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_S32:
{
int32_t int32 = 0;
EndianessCopy(&int32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
ImS32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_U32:
{
uint32_t uint32 = 0;
EndianessCopy(&uint32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
ImU32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_S64:
{
int64_t int64 = 0;
EndianessCopy(&int64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
ImS64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_U64:
{
uint64_t uint64 = 0;
EndianessCopy(&uint64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
ImU64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_Float:
{
float float32 = 0.0f;
EndianessCopy(&float32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
float data = 0.0f;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
case ImGuiDataType_Double:
{
double float64 = 0.0;
EndianessCopy(&float64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
double data = 0.0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
case ImGuiDataType_Bool:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
if (int8 == 0)
ImS8 data = 0;
EndiannessCopy(&data, buf, size);
if (data == 0)
memcpy(out_buf, "false", 6);
else
memcpy(out_buf, "true", 5);
return;
}
default:
case ImGuiDataType_COUNT:
break;
} // Switch
+22 -17
View File
@@ -88,6 +88,7 @@ struct MemoryEditor
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
bool (*HighlightFn)(size_t off); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
std::function<ImU32(size_t off)> BgColorFn = nullptr; // optional handler to return custom background color of individual bytes.
std::function<void()> PushMonoFont = nullptr;
size_t& OffsetAddr; // referenced from PCSX-Redux Settings
@@ -134,6 +135,10 @@ struct MemoryEditor
mutable ReadCache Cache;
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
private:
// [Internal State]
bool ContentsWidthChanged;
@@ -144,7 +149,7 @@ private:
std::string AddrInputBuf;
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianess;
int PreviewEndianness;
bool RestoreOffset;
const size_t BaseAddr;
@@ -156,18 +161,18 @@ public:
struct Sizes
{
int AddrDigitsCount;
float LineHeight;
float GlyphWidth;
float ByteWidth;
float ByteSpacingWidth;
float HexCellWidth;
float SpacingBetweenMidCols;
float PosHexStart;
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; // Height of each line (no spacing), measured with the mono font.
float GlyphWidth; // Glyph width (assume mono-space).
float ByteWidth; // Width of a single byte "FF" ~2.0f * GlyphWidth.
float ByteSpacingWidth; // Spacing after each preview-type group of bytes ~0.5f * GlyphWidth.
float HexCellWidth; // Width of a hex edit cell (one preview-type group of bytes plus trailing spacing).
float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float OffsetHexMinX;
float OffsetHexMaxX;
float OffsetAsciiMinX;
float OffsetAsciiMaxX;
float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); }
};
@@ -184,10 +189,10 @@ public:
size_t DataTypeGetSize(ImGuiDataType data_type) const;
const char* DataFormatGetDesc(DataFormat data_format) const;
bool IsBigEndian() const;
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian);
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian);
void* EndianessCopy(void* dst, void* src, size_t size) const;
const char* FormatBinary(const uint8_t* buf, int width) const;
static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian);
static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian);
void* EndiannessCopy(void* dst, void* src, size_t size) const;
const char* FormatBinary(const ImU8* buf, int width) const;
private:
// [Internal]
+23
View File
@@ -18,6 +18,7 @@
***************************************************************************/
#include <algorithm>
#include <charconv>
#include <cmath>
#include <cstdint>
#include <cstring>
@@ -55,6 +56,7 @@ using PCSX::MidiConverter::MIDI_CC_VOLUME;
using PCSX::MidiConverter::MIDI_DRUM_CHANNEL;
using PCSX::MidiConverter::MIDI_META;
using PCSX::MidiConverter::MIDI_META_COPYRIGHT;
using PCSX::MidiConverter::MIDI_META_CUE_POINT;
using PCSX::MidiConverter::MIDI_META_MARKER;
using PCSX::MidiConverter::MIDI_META_TRACK_NAME;
using PCSX::MidiConverter::MIDI_META_TEMPO;
@@ -92,6 +94,7 @@ enum PsmEventType : uint8_t {
PSM_TEMPO_CHANGE = 0x0A,
PSM_LOOP_POINT = 0x0B,
PSM_END = 0x0C,
PSM_USER = 0x0D,
PSM_LONG_WAIT = 0xFF,
};
@@ -498,6 +501,26 @@ void ConvertContext::generate() {
continue;
}
// A cue point whose text is "tag:payload" becomes a USER event for the program playing the song.
if (mev.type == MIDI_META && mev.data1 == MIDI_META_CUE_POINT) {
std::string_view text = mev.textData;
auto colon = text.find(':');
unsigned tag = 0;
uint32_t payload = 0;
if (colon != std::string_view::npos) {
auto end = text.data() + colon;
auto [p1, e1] = std::from_chars(text.data(), end, tag);
auto [p2, e2] = std::from_chars(end + 1, text.data() + text.size(), payload);
if (e1 == std::errc() && p1 == end && e2 == std::errc() && p2 == text.data() + text.size() &&
tag <= 255) {
emitEvent(PSM_USER, tag, payload, mev.absoluteTick);
continue;
}
}
fmt::print(stderr, "Ignoring cue point \"{}\" at tick {}: expected tag:payload\n", text, mev.absoluteTick);
continue;
}
if (mev.type == MIDI_META) continue;
if (mev.type == MIDI_PROGRAM_CHANGE) {
+4 -1
View File
@@ -125,7 +125,10 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\src\gui\gui.cc" />
<ClCompile Include="..\..\src\gui\gui.cc">
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">/bigobj /utf-8 /validate-charset %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">/bigobj /utf-8 /validate-charset %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ClCompile Include="..\..\src\gui\luaimguiextra.cc" />
<ClCompile Include="..\..\src\gui\luaimplot.cc" />
<ClCompile Include="..\..\src\gui\luatvg.cc" />
@@ -260,6 +260,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\tests\pcsxrunner\basic.cc" />
<ClCompile Include="..\..\..\tests\pcsxrunner\cdrom.cc" />
<ClCompile Include="..\..\..\tests\pcsxrunner\cop0.cc" />
<ClCompile Include="..\..\..\tests\pcsxrunner\copbranch.cc" />
<ClCompile Include="..\..\..\tests\pcsxrunner\cpu.cc" />
@@ -30,6 +30,9 @@
<ClCompile Include="..\..\..\tests\pcsxrunner\cpu.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\tests\pcsxrunner\cdrom.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\tests\pcsxrunner\cop0.cc">
<Filter>Source Files</Filter>
</ClCompile>