mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Feature/agressive recompiler (#146)
* feat: added guestBranchKind enum to categorize branch types feat: added missingFunctionPolicy enum to define behaviors for missing function scenarios refactor: added handle guest branches and report missing functions feat lookupFunction to utilize new dispatch logic and improve error handling for unregistered functions * fix: fix test conflict * feat: added debug sound driver logs * feat: emmiter for return * feat: added recompiler reporter feat: added strict diagnostics flag for heavy debug calls * feat: staticc table insted of hashmap for runtime * feat: back file to ignore * feat: explode code across helpers and classes * feat: update codegen test feat: better guest nop check * feat: fix link problem on linux * feat: fix Segmentation fault * feat: added recompile replace for DMA and MMIO feat: added a clean memory helpers feat: use memory helpers across the project feat: fix ucrt on msvc * feat: undo messup merge
This commit is contained in:
@@ -19,7 +19,8 @@ namespace ps2recomp
|
||||
const Instruction &branchInst,
|
||||
const Instruction &delaySlot,
|
||||
const Function &function,
|
||||
const CodeGenerator::AnalysisResult &analysisResult);
|
||||
const CodeGenerator::AnalysisResult &analysisResult,
|
||||
std::string delaySlotOverride = {});
|
||||
|
||||
std::string emit();
|
||||
|
||||
@@ -41,6 +42,7 @@ namespace ps2recomp
|
||||
const Instruction &m_delaySlot;
|
||||
const Function &m_function;
|
||||
const CodeGenerator::AnalysisResult &m_analysisResult;
|
||||
std::string m_delaySlotOverride;
|
||||
std::stringstream m_ss;
|
||||
|
||||
uint32_t branchPc() const;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
@@ -12,9 +14,20 @@ namespace ps2recomp
|
||||
{
|
||||
public:
|
||||
explicit InstructionTranslator(CodeGenerator &codeGenerator);
|
||||
std::string translate(const Instruction &inst);
|
||||
std::string translate(const Instruction &inst, const MemoryAccessHint &memoryHint);
|
||||
|
||||
private:
|
||||
MemoryAccessHint effectiveMemoryHintFor(const Instruction &inst, const MemoryAccessHint &memoryHint) const;
|
||||
std::string translateMemoryRead(const Instruction &inst,
|
||||
const MemoryAccessHint &memoryHint,
|
||||
int width,
|
||||
const std::string &addr) const;
|
||||
std::string translateMemoryWrite(const Instruction &inst,
|
||||
const MemoryAccessHint &memoryHint,
|
||||
int width,
|
||||
const std::string &addr,
|
||||
const std::string &value) const;
|
||||
|
||||
CodeGenerator &m_codeGenerator;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace ps2recomp
|
||||
struct JumpTableEntry;
|
||||
struct JumpTable;
|
||||
struct Instruction;
|
||||
struct MemoryAccessHint;
|
||||
struct Function;
|
||||
struct Symbol;
|
||||
struct Section;
|
||||
@@ -43,6 +44,9 @@ namespace ps2recomp
|
||||
std::string generateFunctionRegistration(const std::vector<Function> &functions, const std::map<uint32_t, std::string> &stubs);
|
||||
std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot,
|
||||
const Function &function, const AnalysisResult &analysisResult);
|
||||
std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot,
|
||||
const Function &function, const AnalysisResult &analysisResult,
|
||||
std::string delaySlotOverride);
|
||||
|
||||
void setRenamedFunctions(const std::unordered_map<uint32_t, std::string> &renames);
|
||||
void setBootstrapInfo(const BootstrapInfo &info);
|
||||
@@ -69,6 +73,7 @@ namespace ps2recomp
|
||||
std::string m_currentFunctionName;
|
||||
|
||||
std::string translateInstruction(const Instruction &inst);
|
||||
std::string translateInstruction(const Instruction &inst, const MemoryAccessHint &memoryHint);
|
||||
std::string emitUnhandledInstruction(const Instruction &inst, const std::string &message);
|
||||
std::string translateMMIInstruction(const Instruction &inst);
|
||||
std::string translateVUInstruction(const Instruction &inst);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef PS2RECOMP_GIF_DMA_KICK_ANALYZER_H
|
||||
#define PS2RECOMP_GIF_DMA_KICK_ANALYZER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "ps2recomp/types.h"
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
struct Instruction;
|
||||
|
||||
struct ConstantRegisterState
|
||||
{
|
||||
std::array<bool, 32> known{};
|
||||
std::array<uint32_t, 32> values{};
|
||||
|
||||
ConstantRegisterState();
|
||||
void clear();
|
||||
bool read(uint32_t reg, uint32_t &value) const;
|
||||
void write(uint32_t reg, uint32_t value);
|
||||
void invalidate(uint32_t reg);
|
||||
};
|
||||
|
||||
struct GifDmaKickPlan
|
||||
{
|
||||
bool valid = false;
|
||||
std::array<size_t, 4> storeIndices{};
|
||||
std::array<std::string, 4> values{};
|
||||
std::array<std::string, 4> captureExpressions{};
|
||||
std::array<bool, 4> captures{};
|
||||
size_t branchIndex = std::numeric_limits<size_t>::max();
|
||||
size_t endIndex = 0;
|
||||
bool completesInDelaySlot = false;
|
||||
|
||||
bool suppresses(size_t index) const;
|
||||
bool completesAt(size_t index) const;
|
||||
size_t slotFor(size_t index) const;
|
||||
};
|
||||
|
||||
bool isDirectMemoryAccess(const Instruction &inst);
|
||||
MemoryAccessHint resolveMemoryAccessHint(const Instruction &inst, const ConstantRegisterState &constants);
|
||||
void updateConstantRegisters(const Instruction &inst, ConstantRegisterState &constants);
|
||||
|
||||
GifDmaKickPlan tryBuildGifDmaKickPlan(const std::vector<Instruction> &instructions,
|
||||
size_t startIndex,
|
||||
const ConstantRegisterState &constants,
|
||||
const std::unordered_set<uint32_t> &internalTargets);
|
||||
|
||||
std::string gifDmaKickCall(const GifDmaKickPlan &plan);
|
||||
void emitGifDmaCapture(std::ostream &out, const GifDmaKickPlan &plan, size_t slot, std::string_view indent);
|
||||
std::string gifDmaDelaySlotOverride(const Instruction &delaySlot, const GifDmaKickPlan &plan, bool emitComments);
|
||||
}
|
||||
|
||||
#endif // PS2RECOMP_GIF_DMA_KICK_ANALYZER_H
|
||||
@@ -80,6 +80,12 @@ namespace ps2recomp
|
||||
}
|
||||
};
|
||||
|
||||
struct MemoryAccessHint
|
||||
{
|
||||
bool hasAddress = false;
|
||||
uint32_t address = 0;
|
||||
};
|
||||
|
||||
// Function information
|
||||
struct Function
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
@@ -209,11 +210,27 @@ namespace ps2recomp
|
||||
return emitter.emit();
|
||||
}
|
||||
|
||||
std::string CodeGenerator::handleBranchDelaySlots(
|
||||
const Instruction &branchInst,
|
||||
const Instruction &delaySlot,
|
||||
const Function &function,
|
||||
const AnalysisResult &analysisResult,
|
||||
std::string delaySlotOverride)
|
||||
{
|
||||
ControlFlowEmitter emitter(*this, branchInst, delaySlot, function, analysisResult, std::move(delaySlotOverride));
|
||||
return emitter.emit();
|
||||
}
|
||||
|
||||
CodeGenerator::~CodeGenerator() = default;
|
||||
|
||||
std::string CodeGenerator::translateInstruction(const Instruction &inst)
|
||||
{
|
||||
return InstructionTranslator(*this).translate(inst);
|
||||
return InstructionTranslator(*this).translate(inst, {});
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateInstruction(const Instruction &inst, const MemoryAccessHint &memoryHint)
|
||||
{
|
||||
return InstructionTranslator(*this).translate(inst, memoryHint);
|
||||
}
|
||||
|
||||
std::string CodeGenerator::translateSpecialInstruction(const Instruction &inst)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
@@ -15,12 +16,14 @@ namespace ps2recomp
|
||||
const Instruction &branchInst,
|
||||
const Instruction &delaySlot,
|
||||
const Function &function,
|
||||
const CodeGenerator::AnalysisResult &analysisResult)
|
||||
const CodeGenerator::AnalysisResult &analysisResult,
|
||||
std::string delaySlotOverride)
|
||||
: m_gen(generator),
|
||||
m_branchInst(branchInst),
|
||||
m_delaySlot(delaySlot),
|
||||
m_function(function),
|
||||
m_analysisResult(analysisResult)
|
||||
m_analysisResult(analysisResult),
|
||||
m_delaySlotOverride(std::move(delaySlotOverride))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -41,7 +44,7 @@ namespace ps2recomp
|
||||
|
||||
bool ControlFlowEmitter::hasRealDelaySlot() const
|
||||
{
|
||||
return !isGuestNop(m_delaySlot);
|
||||
return !m_delaySlotOverride.empty() || !isGuestNop(m_delaySlot);
|
||||
}
|
||||
|
||||
bool ControlFlowEmitter::isCallLikeEdge() const
|
||||
@@ -100,6 +103,11 @@ namespace ps2recomp
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!m_delaySlotOverride.empty())
|
||||
{
|
||||
return m_delaySlotOverride;
|
||||
}
|
||||
|
||||
std::string code;
|
||||
if (m_gen.m_emitInstructionComments)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ps2recomp/Emitters/function_emitter.h"
|
||||
#include "ps2recomp/code_generator.h"
|
||||
#include "ps2recomp/gif_dma_kick_analyzer.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
@@ -69,6 +70,8 @@ namespace ps2recomp
|
||||
}
|
||||
|
||||
const std::unordered_set<uint32_t> &internalTargets = analysisResult.entryPoints;
|
||||
ConstantRegisterState constantRegisters;
|
||||
GifDmaKickPlan gifDmaKickPlan{};
|
||||
ss << "// Function: " << function.name << "\n";
|
||||
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
|
||||
|
||||
@@ -106,6 +109,7 @@ namespace ps2recomp
|
||||
|
||||
if (internalTargets.contains(inst.address))
|
||||
{
|
||||
constantRegisters.clear();
|
||||
ss << "label_" << std::hex << inst.address << std::dec << ":\n";
|
||||
}
|
||||
|
||||
@@ -145,24 +149,65 @@ namespace ps2recomp
|
||||
ss << "label_" << std::hex << delaySlot->address << std::dec << ":\n";
|
||||
}
|
||||
|
||||
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
|
||||
if (gifDmaKickPlan.valid &&
|
||||
gifDmaKickPlan.completesInDelaySlot &&
|
||||
gifDmaKickPlan.branchIndex == i &&
|
||||
hasDecodedDelaySlot)
|
||||
{
|
||||
ss << cg.handleBranchDelaySlots(
|
||||
inst,
|
||||
*delaySlot,
|
||||
function,
|
||||
analysisResult,
|
||||
gifDmaDelaySlotOverride(*delaySlot, gifDmaKickPlan, cg.m_emitInstructionComments));
|
||||
gifDmaKickPlan = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << cg.handleBranchDelaySlots(inst, *delaySlot, function, analysisResult);
|
||||
}
|
||||
|
||||
if (hasDecodedDelaySlot)
|
||||
{
|
||||
++i; // Skip delay slot instruction (handled inside branch logic)
|
||||
}
|
||||
constantRegisters.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!gifDmaKickPlan.valid)
|
||||
{
|
||||
gifDmaKickPlan = tryBuildGifDmaKickPlan(instructions, i, constantRegisters, internalTargets);
|
||||
}
|
||||
|
||||
if (gifDmaKickPlan.suppresses(i))
|
||||
{
|
||||
const size_t slot = gifDmaKickPlan.slotFor(i);
|
||||
emitGifDmaCapture(ss, gifDmaKickPlan, slot, " ");
|
||||
|
||||
if (gifDmaKickPlan.completesAt(i))
|
||||
{
|
||||
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
|
||||
<< std::dec;
|
||||
ss << " " << gifDmaKickCall(gifDmaKickPlan) << "\n";
|
||||
gifDmaKickPlan = {};
|
||||
}
|
||||
|
||||
updateConstantRegisters(inst, constantRegisters);
|
||||
continue;
|
||||
}
|
||||
|
||||
ss << " ctx->pc = 0x" << std::hex << inst.address << "u;\n"
|
||||
<< std::dec;
|
||||
|
||||
ss << " " << cg.translateInstruction(inst);
|
||||
const MemoryAccessHint memoryHint = resolveMemoryAccessHint(inst, constantRegisters);
|
||||
ss << " " << cg.translateInstruction(inst, memoryHint);
|
||||
if (inst.isMmio)
|
||||
{
|
||||
ss << " // MMIO: 0x" << std::hex << inst.mmioAddress << std::dec;
|
||||
}
|
||||
ss << "\n";
|
||||
|
||||
updateConstantRegisters(inst, constantRegisters);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
#include "ps2recomp/gif_dma_kick_analyzer.h"
|
||||
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/r5900_decoder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint32_t add32(uint32_t lhs, uint32_t rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
std::string formatU32Literal(uint32_t value)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "0x" << std::hex << value << "u";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string gprU32Expression(uint32_t reg)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "GPR_U32(ctx, " << std::dec << reg << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string gifDmaKickTempName(uint32_t address, size_t slot)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "gifDmaKickValue_" << std::hex << address << "_" << std::dec << slot;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool isReturnWithDelaySlot(const Instruction &inst)
|
||||
{
|
||||
return inst.opcode == OPCODE_SPECIAL &&
|
||||
inst.function == SPECIAL_JR &&
|
||||
inst.rs == 31u &&
|
||||
inst.hasDelaySlot;
|
||||
}
|
||||
|
||||
bool tryMatchGifDmaStore(size_t instructionIndex,
|
||||
const Instruction &inst,
|
||||
const MemoryAccessHint &hint,
|
||||
const ConstantRegisterState &constants,
|
||||
uint32_t target,
|
||||
size_t slot,
|
||||
GifDmaKickPlan &plan)
|
||||
{
|
||||
if (inst.opcode != OPCODE_SW || !hint.hasAddress || hint.address != target)
|
||||
return false;
|
||||
|
||||
plan.storeIndices[slot] = instructionIndex;
|
||||
uint32_t constantValue = 0u;
|
||||
if (constants.read(inst.rt, constantValue))
|
||||
{
|
||||
plan.values[slot] = formatU32Literal(constantValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string tempName = gifDmaKickTempName(inst.address, slot);
|
||||
plan.values[slot] = tempName;
|
||||
plan.captureExpressions[slot] = gprU32Expression(inst.rt);
|
||||
plan.captures[slot] = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ConstantRegisterState::ConstantRegisterState()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void ConstantRegisterState::clear()
|
||||
{
|
||||
known.fill(false);
|
||||
values.fill(0u);
|
||||
known[0] = true;
|
||||
}
|
||||
|
||||
bool ConstantRegisterState::read(uint32_t reg, uint32_t &value) const
|
||||
{
|
||||
if (reg >= known.size() || !known[reg])
|
||||
return false;
|
||||
|
||||
value = values[reg];
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConstantRegisterState::write(uint32_t reg, uint32_t value)
|
||||
{
|
||||
if (reg == 0 || reg >= known.size())
|
||||
return;
|
||||
|
||||
known[reg] = true;
|
||||
values[reg] = value;
|
||||
}
|
||||
|
||||
void ConstantRegisterState::invalidate(uint32_t reg)
|
||||
{
|
||||
if (reg == 0 || reg >= known.size())
|
||||
return;
|
||||
|
||||
known[reg] = false;
|
||||
values[reg] = 0u;
|
||||
}
|
||||
|
||||
bool GifDmaKickPlan::suppresses(size_t index) const
|
||||
{
|
||||
return valid && std::find(storeIndices.begin(), storeIndices.end(), index) != storeIndices.end();
|
||||
}
|
||||
|
||||
bool GifDmaKickPlan::completesAt(size_t index) const
|
||||
{
|
||||
return valid && index == endIndex;
|
||||
}
|
||||
|
||||
size_t GifDmaKickPlan::slotFor(size_t index) const
|
||||
{
|
||||
for (size_t i = 0; i < storeIndices.size(); ++i)
|
||||
{
|
||||
if (storeIndices[i] == index)
|
||||
return i;
|
||||
}
|
||||
return storeIndices.size();
|
||||
}
|
||||
|
||||
bool isDirectMemoryAccess(const Instruction &inst)
|
||||
{
|
||||
switch (inst.opcode)
|
||||
{
|
||||
case OPCODE_LB:
|
||||
case OPCODE_LH:
|
||||
case OPCODE_LW:
|
||||
case OPCODE_LBU:
|
||||
case OPCODE_LHU:
|
||||
case OPCODE_LWU:
|
||||
case OPCODE_LQ:
|
||||
case OPCODE_LD:
|
||||
case OPCODE_LWC1:
|
||||
case OPCODE_LDC2:
|
||||
case OPCODE_SB:
|
||||
case OPCODE_SH:
|
||||
case OPCODE_SW:
|
||||
case OPCODE_SQ:
|
||||
case OPCODE_SD:
|
||||
case OPCODE_SWC1:
|
||||
case OPCODE_SDC2:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryAccessHint resolveMemoryAccessHint(const Instruction &inst, const ConstantRegisterState &constants)
|
||||
{
|
||||
MemoryAccessHint hint{};
|
||||
if (!isDirectMemoryAccess(inst))
|
||||
return hint;
|
||||
|
||||
uint32_t base = 0u;
|
||||
if (!constants.read(inst.rs, base))
|
||||
return hint;
|
||||
|
||||
hint.hasAddress = true;
|
||||
hint.address = add32(base, inst.simmediate);
|
||||
return hint;
|
||||
}
|
||||
|
||||
void updateConstantRegisters(const Instruction &inst, ConstantRegisterState &constants)
|
||||
{
|
||||
uint32_t lhs = 0u;
|
||||
uint32_t rhs = 0u;
|
||||
|
||||
switch (inst.opcode)
|
||||
{
|
||||
case OPCODE_LUI:
|
||||
constants.write(inst.rt, inst.immediate << 16);
|
||||
return;
|
||||
case OPCODE_ORI:
|
||||
if (constants.read(inst.rs, lhs))
|
||||
constants.write(inst.rt, lhs | (inst.immediate & 0xFFFFu));
|
||||
else
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_ADDIU:
|
||||
if (constants.read(inst.rs, lhs))
|
||||
constants.write(inst.rt, add32(lhs, inst.simmediate));
|
||||
else
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_ANDI:
|
||||
if (constants.read(inst.rs, lhs))
|
||||
constants.write(inst.rt, lhs & (inst.immediate & 0xFFFFu));
|
||||
else
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_XORI:
|
||||
if (constants.read(inst.rs, lhs))
|
||||
constants.write(inst.rt, lhs ^ (inst.immediate & 0xFFFFu));
|
||||
else
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_LB:
|
||||
case OPCODE_LH:
|
||||
case OPCODE_LWL:
|
||||
case OPCODE_LW:
|
||||
case OPCODE_LBU:
|
||||
case OPCODE_LHU:
|
||||
case OPCODE_LWR:
|
||||
case OPCODE_LWU:
|
||||
case OPCODE_LDL:
|
||||
case OPCODE_LDR:
|
||||
case OPCODE_LQ:
|
||||
case OPCODE_LL:
|
||||
case OPCODE_LD:
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_SC:
|
||||
constants.invalidate(inst.rt);
|
||||
return;
|
||||
case OPCODE_SPECIAL:
|
||||
switch (inst.function)
|
||||
{
|
||||
case SPECIAL_ADDU:
|
||||
case SPECIAL_DADDU:
|
||||
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
|
||||
constants.write(inst.rd, add32(lhs, rhs));
|
||||
else
|
||||
constants.invalidate(inst.rd);
|
||||
return;
|
||||
case SPECIAL_OR:
|
||||
if (constants.read(inst.rs, lhs) && constants.read(inst.rt, rhs))
|
||||
constants.write(inst.rd, lhs | rhs);
|
||||
else
|
||||
constants.invalidate(inst.rd);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (inst.modificationInfo.modifiesGPR || inst.modificationInfo.modifiesControl)
|
||||
constants.clear();
|
||||
}
|
||||
|
||||
std::string gifDmaKickCall(const GifDmaKickPlan &plan)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "runtime->kickGifDmaChainFromMMIO(rdram, ctx, "
|
||||
<< plan.values[0] << ", "
|
||||
<< plan.values[1] << ", "
|
||||
<< plan.values[2] << ", "
|
||||
<< plan.values[3] << ");";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void emitGifDmaCapture(std::ostream &out, const GifDmaKickPlan &plan, size_t slot, std::string_view indent)
|
||||
{
|
||||
if (slot >= plan.captures.size() || !plan.captures[slot])
|
||||
return;
|
||||
|
||||
out << indent << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
|
||||
}
|
||||
|
||||
std::string gifDmaDelaySlotOverride(const Instruction &delaySlot, const GifDmaKickPlan &plan, bool emitComments)
|
||||
{
|
||||
std::ostringstream code;
|
||||
const size_t slot = plan.slotFor(plan.endIndex);
|
||||
if (emitComments)
|
||||
{
|
||||
code << "// 0x" << std::hex << delaySlot.address << ": 0x" << delaySlot.raw << std::dec;
|
||||
std::string disassembly = R5900Decoder::disassembleInstruction(delaySlot);
|
||||
if (!disassembly.empty())
|
||||
code << " " << disassembly;
|
||||
code << " (Delay Slot)\n";
|
||||
}
|
||||
|
||||
if (slot < plan.captures.size() && plan.captures[slot])
|
||||
code << "uint32_t " << plan.values[slot] << " = " << plan.captureExpressions[slot] << ";\n";
|
||||
code << gifDmaKickCall(plan);
|
||||
return code.str();
|
||||
}
|
||||
|
||||
GifDmaKickPlan tryBuildGifDmaKickPlan(const std::vector<Instruction> &instructions,
|
||||
size_t startIndex,
|
||||
const ConstantRegisterState &constants,
|
||||
const std::unordered_set<uint32_t> &internalTargets)
|
||||
{
|
||||
static constexpr std::array<uint32_t, 4> kTargets = {
|
||||
0x1000E020u, // D_PCR
|
||||
0x1000E010u, // D_STAT
|
||||
0x1000A030u, // GIF TADR
|
||||
0x1000A000u, // GIF CHCR
|
||||
};
|
||||
static constexpr size_t kMaxScanInstructions = 32u;
|
||||
|
||||
GifDmaKickPlan plan{};
|
||||
if (startIndex >= instructions.size())
|
||||
return plan;
|
||||
|
||||
ConstantRegisterState scanConstants = constants;
|
||||
size_t matched = 0;
|
||||
const size_t scanEnd = std::min(instructions.size(), startIndex + kMaxScanInstructions);
|
||||
|
||||
for (size_t j = startIndex; j < scanEnd; ++j)
|
||||
{
|
||||
const Instruction &inst = instructions[j];
|
||||
if (j != startIndex && matched > 0 && internalTargets.contains(inst.address))
|
||||
return {};
|
||||
|
||||
const MemoryAccessHint hint = resolveMemoryAccessHint(inst, scanConstants);
|
||||
if (isDirectMemoryAccess(inst))
|
||||
{
|
||||
if (matched >= kTargets.size() ||
|
||||
!tryMatchGifDmaStore(j, inst, hint, scanConstants, kTargets[matched], matched, plan))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
++matched;
|
||||
updateConstantRegisters(inst, scanConstants);
|
||||
if (matched == kTargets.size())
|
||||
{
|
||||
plan.valid = true;
|
||||
plan.endIndex = j;
|
||||
return plan;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isReturnWithDelaySlot(inst))
|
||||
{
|
||||
const size_t delayIndex = j + 1u;
|
||||
if (matched != kTargets.size() - 1u ||
|
||||
delayIndex >= instructions.size() ||
|
||||
instructions[delayIndex].address != inst.address + 4u ||
|
||||
internalTargets.contains(instructions[delayIndex].address))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const Instruction &delayInst = instructions[delayIndex];
|
||||
const MemoryAccessHint delayHint = resolveMemoryAccessHint(delayInst, scanConstants);
|
||||
if (!tryMatchGifDmaStore(delayIndex, delayInst, delayHint, scanConstants, kTargets[matched], matched, plan))
|
||||
return {};
|
||||
|
||||
plan.valid = true;
|
||||
plan.completesInDelaySlot = true;
|
||||
plan.branchIndex = j;
|
||||
plan.endIndex = delayIndex;
|
||||
return plan;
|
||||
}
|
||||
|
||||
if (inst.hasDelaySlot || inst.isBranch || inst.isJump || inst.modificationInfo.modifiesControl)
|
||||
return {};
|
||||
|
||||
updateConstantRegisters(inst, scanConstants);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -4,42 +4,145 @@
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include "ps2recomp/control_flow_utils.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string addressLiteral(uint32_t address)
|
||||
{
|
||||
return fmt::format("0x{:X}u", address);
|
||||
}
|
||||
|
||||
uint32_t memoryAccessSize(int width)
|
||||
{
|
||||
return static_cast<uint32_t>(width / 8);
|
||||
}
|
||||
|
||||
std::string memoryValueType(int width)
|
||||
{
|
||||
switch (width)
|
||||
{
|
||||
case 8:
|
||||
return "uint8_t";
|
||||
case 16:
|
||||
return "uint16_t";
|
||||
case 32:
|
||||
return "uint32_t";
|
||||
case 64:
|
||||
return "uint64_t";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string genFastWrite(int width, uint32_t address, const std::string &val)
|
||||
{
|
||||
const std::string addr = addressLiteral(address);
|
||||
if (width == 128)
|
||||
{
|
||||
return fmt::format(
|
||||
"do {{ __m128i _value = ({}); "
|
||||
"const uint64_t _lo = static_cast<uint64_t>(PS2_EXTRACT_EPI64_0(_value)); "
|
||||
"const uint64_t _hi = static_cast<uint64_t>(PS2_EXTRACT_EPI64_1(_value)); "
|
||||
"ps2TraceGuestWrite(rdram, {}, 16u, _lo, _hi, \"WRITE128\", ctx); "
|
||||
"FAST_WRITE128({}, _value); }} while (0)",
|
||||
val, addr, addr);
|
||||
}
|
||||
|
||||
const std::string valueType = memoryValueType(width);
|
||||
return fmt::format(
|
||||
"do {{ {} _value = static_cast<{}>({}); "
|
||||
"ps2TraceGuestWrite(rdram, {}, {}u, _value, 0u, \"WRITE{}\", ctx); "
|
||||
"FAST_WRITE{}({}, _value); }} while (0)",
|
||||
valueType, valueType, val, addr, memoryAccessSize(width), width, width, addr);
|
||||
}
|
||||
}
|
||||
|
||||
InstructionTranslator::InstructionTranslator(CodeGenerator &codeGenerator)
|
||||
: m_codeGenerator(codeGenerator)
|
||||
{
|
||||
}
|
||||
|
||||
std::string InstructionTranslator::translate(const Instruction &inst)
|
||||
MemoryAccessHint InstructionTranslator::effectiveMemoryHintFor(const Instruction &inst, const MemoryAccessHint &memoryHint) const
|
||||
{
|
||||
MemoryAccessHint effectiveMemoryHint = memoryHint;
|
||||
if (inst.isMmio)
|
||||
{
|
||||
effectiveMemoryHint.hasAddress = true;
|
||||
effectiveMemoryHint.address = inst.mmioAddress;
|
||||
}
|
||||
|
||||
return effectiveMemoryHint;
|
||||
}
|
||||
|
||||
std::string InstructionTranslator::translateMemoryRead(const Instruction &inst,
|
||||
const MemoryAccessHint &memoryHint,
|
||||
int width,
|
||||
const std::string &addr) const
|
||||
{
|
||||
if (memoryHint.hasAddress)
|
||||
{
|
||||
const uint32_t resolvedAddress = memoryHint.address;
|
||||
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
|
||||
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
|
||||
{
|
||||
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, resolvedAddressExpr);
|
||||
}
|
||||
return fmt::format("FAST_READ{}({})", width, resolvedAddressExpr);
|
||||
}
|
||||
|
||||
if (inst.isMmio)
|
||||
{
|
||||
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
|
||||
}
|
||||
return fmt::format("READ{}({})", width, addr);
|
||||
}
|
||||
|
||||
std::string InstructionTranslator::translateMemoryWrite(const Instruction &inst,
|
||||
const MemoryAccessHint &memoryHint,
|
||||
int width,
|
||||
const std::string &addr,
|
||||
const std::string &value) const
|
||||
{
|
||||
if (memoryHint.hasAddress)
|
||||
{
|
||||
const uint32_t resolvedAddress = memoryHint.address;
|
||||
const std::string resolvedAddressExpr = addressLiteral(resolvedAddress);
|
||||
if (inst.isMmio || Ps2IsSpecialAddress(resolvedAddress))
|
||||
{
|
||||
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, resolvedAddressExpr, value);
|
||||
}
|
||||
return genFastWrite(width, resolvedAddress, value);
|
||||
}
|
||||
|
||||
if (inst.isMmio)
|
||||
{
|
||||
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, value);
|
||||
}
|
||||
return fmt::format("WRITE{}({}, {})", width, addr, value);
|
||||
}
|
||||
|
||||
std::string InstructionTranslator::translate(const Instruction &inst, const MemoryAccessHint &memoryHint)
|
||||
{
|
||||
if (inst.isMMI)
|
||||
{
|
||||
return m_codeGenerator.translateMMIInstruction(inst);
|
||||
}
|
||||
|
||||
const MemoryAccessHint effectiveMemoryHint = effectiveMemoryHintFor(inst, memoryHint);
|
||||
|
||||
auto genRead = [&](int width, const std::string &addr)
|
||||
{
|
||||
if (inst.isMmio)
|
||||
{
|
||||
return fmt::format("runtime->Load{}(rdram, ctx, {})", width, addr);
|
||||
}
|
||||
return fmt::format("READ{}({})", width, addr);
|
||||
return translateMemoryRead(inst, effectiveMemoryHint, width, addr);
|
||||
};
|
||||
|
||||
auto genWrite = [&](int width, const std::string &addr, const std::string &val)
|
||||
{
|
||||
if (inst.isMmio)
|
||||
{
|
||||
return fmt::format("runtime->Store{}(rdram, ctx, {}, {})", width, addr, val);
|
||||
}
|
||||
return fmt::format("WRITE{}({}, {})", width, addr, val);
|
||||
return translateMemoryWrite(inst, effectiveMemoryHint, width, addr, val);
|
||||
};
|
||||
|
||||
switch (inst.opcode)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <iomanip>
|
||||
|
||||
#include "ps2_log.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
#include "runtime/ps2_gif_arbiter.h"
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
@@ -480,36 +481,16 @@ public:
|
||||
void Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value);
|
||||
void Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value);
|
||||
void Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value);
|
||||
void kickGifDmaChainFromMMIO(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t dPcrValue,
|
||||
uint32_t dStatValue,
|
||||
uint32_t tadr,
|
||||
uint32_t chcr);
|
||||
|
||||
static inline bool isSpecialAddress(uint32_t addr)
|
||||
{
|
||||
auto inRange = [](uint32_t value, uint32_t base, uint32_t size) -> bool
|
||||
{
|
||||
return (value - base) < size;
|
||||
};
|
||||
|
||||
auto isPhysicalSpecial = [&](uint32_t physAddr) -> bool
|
||||
{
|
||||
if (inRange(physAddr, PS2_BIOS_BASE, PS2_BIOS_SIZE))
|
||||
return true;
|
||||
if (inRange(physAddr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE))
|
||||
return true;
|
||||
if (inRange(physAddr, PS2_IO_BASE, PS2_IO_SIZE))
|
||||
return true;
|
||||
if (inRange(physAddr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE))
|
||||
return true;
|
||||
if (physAddr >= PS2_VU0_DATA_BASE && physAddr < (PS2_VU1_CODE_BASE + PS2_VU1_CODE_SIZE))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// KSEG2/KSEG3 (TLB mapped)
|
||||
if (addr >= 0xC0000000u)
|
||||
return true;
|
||||
|
||||
// KSEG0/KSEG1 aliases → physical
|
||||
const uint32_t physAddr = (addr >= 0x80000000u) ? (addr & 0x1FFFFFFFu) : addr;
|
||||
return isPhysicalSpecial(physAddr);
|
||||
return Ps2IsSpecialAddress(addr);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef PS2_ADDRESS_H
|
||||
#define PS2_ADDRESS_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "runtime/ps2_memory.h"
|
||||
|
||||
static inline constexpr uint32_t PS2_EE_UNCACHED_RAM_MIRROR_BASE = 0x20000000u;
|
||||
static inline constexpr uint32_t PS2_EE_UNCACHED_RAM_MIRROR_SIZE = 0x20000000u;
|
||||
static inline constexpr uint32_t PS2_KSEG0_BASE = 0x80000000u;
|
||||
static inline constexpr uint32_t PS2_KSEG0_KSEG1_SIZE = 0x40000000u;
|
||||
static inline constexpr uint32_t PS2_KSEG2_BASE = 0xC0000000u;
|
||||
|
||||
static inline constexpr bool Ps2AddressInRange(uint32_t value, uint32_t base, uint32_t size)
|
||||
{
|
||||
return (value - base) < size;
|
||||
}
|
||||
|
||||
static inline constexpr bool Ps2IsUncachedRamMirrorAddress(uint32_t addr)
|
||||
{
|
||||
return Ps2AddressInRange(addr, PS2_EE_UNCACHED_RAM_MIRROR_BASE, PS2_EE_UNCACHED_RAM_MIRROR_SIZE);
|
||||
}
|
||||
|
||||
static inline constexpr bool Ps2IsKseg01Address(uint32_t addr)
|
||||
{
|
||||
return Ps2AddressInRange(addr, PS2_KSEG0_BASE, PS2_KSEG0_KSEG1_SIZE);
|
||||
}
|
||||
|
||||
static inline constexpr bool Ps2IsKseg23Address(uint32_t addr)
|
||||
{
|
||||
return addr >= PS2_KSEG2_BASE;
|
||||
}
|
||||
|
||||
static inline constexpr uint32_t Ps2DirectMappedPhysicalAddress(uint32_t addr)
|
||||
{
|
||||
return addr & 0x1FFFFFFFu;
|
||||
}
|
||||
|
||||
static inline constexpr uint32_t Ps2PhysicalAddress(uint32_t addr)
|
||||
{
|
||||
return (addr >= PS2_KSEG0_BASE) ? Ps2DirectMappedPhysicalAddress(addr) : addr;
|
||||
}
|
||||
|
||||
static inline constexpr bool Ps2IsPhysicalSpecialAddress(uint32_t physAddr)
|
||||
{
|
||||
return Ps2AddressInRange(physAddr, PS2_BIOS_BASE, PS2_BIOS_SIZE) ||
|
||||
Ps2AddressInRange(physAddr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE) ||
|
||||
Ps2AddressInRange(physAddr, PS2_IO_BASE, PS2_IO_SIZE) ||
|
||||
Ps2AddressInRange(physAddr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE) ||
|
||||
(physAddr >= PS2_VU0_DATA_BASE && physAddr < (PS2_VU1_CODE_BASE + PS2_VU1_CODE_SIZE));
|
||||
}
|
||||
|
||||
static inline constexpr bool Ps2IsSpecialAddress(uint32_t addr)
|
||||
{
|
||||
if (Ps2IsKseg23Address(addr))
|
||||
return true;
|
||||
|
||||
return Ps2IsPhysicalSpecialAddress(Ps2PhysicalAddress(addr));
|
||||
}
|
||||
|
||||
#endif // PS2_ADDRESS_H
|
||||
@@ -33,6 +33,7 @@ public:
|
||||
void submit(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool path2DirectHl = false);
|
||||
|
||||
void drain();
|
||||
bool empty() const { return m_queue.empty(); }
|
||||
|
||||
private:
|
||||
ProcessPacketFn m_processFn;
|
||||
|
||||
@@ -321,6 +321,13 @@ public:
|
||||
void reset();
|
||||
|
||||
void processGIFPacket(const uint8_t *data, uint32_t sizeBytes);
|
||||
bool processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes);
|
||||
void uploadImageNative(uint64_t bitbltbuf,
|
||||
uint64_t trxpos,
|
||||
uint64_t trxreg,
|
||||
uint64_t trxdir,
|
||||
const uint8_t *data,
|
||||
uint32_t sizeBytes);
|
||||
void writeRegister(uint8_t regAddr, uint64_t value);
|
||||
|
||||
const uint8_t *lockDisplaySnapshot(uint32_t &outSize);
|
||||
@@ -346,6 +353,8 @@ public:
|
||||
bool *outUsedPreferred = nullptr) const;
|
||||
bool clearFramebufferContext(uint32_t contextIndex, uint32_t rgba);
|
||||
bool clearActiveFramebuffer(uint32_t rgba);
|
||||
uint64_t nativeImageUploadCount() const { return m_nativeImageUploadCount; }
|
||||
uint64_t nativePackedGIFPacketCount() const { return m_nativePackedGIFPacketCount; }
|
||||
|
||||
uint32_t consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes);
|
||||
|
||||
@@ -369,6 +378,7 @@ private:
|
||||
void recordPresentDebugEventUnlocked(uint32_t displayFbp, uint32_t sourceFbp, uint32_t width, uint32_t height, bool usedPreferred);
|
||||
|
||||
void processImageData(const uint8_t *data, uint32_t sizeBytes);
|
||||
bool tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes);
|
||||
void performLocalToLocalTransfer();
|
||||
void performLocalToHostToBuffer();
|
||||
bool copyFrameToHostRgbaUnlocked(const GSFrameReg &frame,
|
||||
@@ -433,6 +443,8 @@ private:
|
||||
uint32_t m_hostPresentationSourceFbp = 0;
|
||||
bool m_hostPresentationUsedPreferred = false;
|
||||
bool m_hasHostPresentationFrame = false;
|
||||
uint64_t m_nativeImageUploadCount = 0;
|
||||
uint64_t m_nativePackedGIFPacketCount = 0;
|
||||
|
||||
std::vector<uint8_t> m_localToHostBuffer;
|
||||
size_t m_localToHostReadPos = 0;
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <smmintrin.h> // For SSE4.1 instructions
|
||||
#endif
|
||||
|
||||
class GS;
|
||||
|
||||
constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
|
||||
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
|
||||
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
|
||||
@@ -332,6 +334,8 @@ public:
|
||||
void submitGifPacket(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool drainImmediately = true, bool path2DirectHl = false);
|
||||
void processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount);
|
||||
void processGIFPacket(const uint8_t *data, uint32_t sizeBytes);
|
||||
bool tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr);
|
||||
bool tryProcessNativeGifPackedChain(GS &gs, uint32_t tadr, uint32_t chcr);
|
||||
void processVIF0Data(uint32_t srcPhysAddr, uint32_t sizeBytes);
|
||||
void processVIF0Data(const uint8_t *data, uint32_t sizeBytes);
|
||||
void processVIF1Data(uint32_t srcPhysAddr, uint32_t sizeBytes);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Common.h"
|
||||
#include "SIF.h"
|
||||
#include "../Syscalls/RPC.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -186,22 +187,22 @@ namespace ps2_stubs
|
||||
|
||||
bool isCopyableGuestAddress(uint32_t addr)
|
||||
{
|
||||
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
|
||||
if (Ps2AddressInRange(addr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addr < 0x20000000u)
|
||||
if (addr < PS2_EE_UNCACHED_RAM_MIRROR_BASE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addr >= 0x20000000u && addr < 0x40000000u)
|
||||
if (Ps2IsUncachedRamMirrorAddress(addr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addr >= 0x80000000u && addr < 0xC0000000u)
|
||||
if (Ps2IsKseg01Address(addr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,63 @@ namespace
|
||||
return v;
|
||||
}
|
||||
|
||||
struct PackedGifPacketTag
|
||||
{
|
||||
uint64_t lo = 0u;
|
||||
uint64_t hi = 0u;
|
||||
uint32_t payloadOffset = 0u;
|
||||
uint32_t nloop = 0u;
|
||||
uint32_t nreg = 0u;
|
||||
uint8_t regs[16]{};
|
||||
};
|
||||
|
||||
template <typename Visitor>
|
||||
bool visitPackedGifPacket(const uint8_t *data, uint32_t sizeBytes, Visitor &&visitor)
|
||||
{
|
||||
uint32_t offset = 0u;
|
||||
while (offset + 16u <= sizeBytes)
|
||||
{
|
||||
PackedGifPacketTag tag{};
|
||||
tag.lo = loadLE64(data + offset);
|
||||
tag.hi = loadLE64(data + offset + 8u);
|
||||
|
||||
const uint8_t flg = static_cast<uint8_t>((tag.lo >> 58u) & 0x3u);
|
||||
if (flg != GIF_FMT_PACKED)
|
||||
return false;
|
||||
|
||||
tag.nloop = static_cast<uint32_t>(tag.lo & 0x7FFFu);
|
||||
tag.nreg = static_cast<uint32_t>((tag.lo >> 60u) & 0xFu);
|
||||
if (tag.nreg == 0u)
|
||||
tag.nreg = 16u;
|
||||
|
||||
const uint64_t payloadBytes64 =
|
||||
static_cast<uint64_t>(tag.nloop) * static_cast<uint64_t>(tag.nreg) * 16ull;
|
||||
if (payloadBytes64 > 0xFFFFFFFFull)
|
||||
return false;
|
||||
|
||||
offset += 16u;
|
||||
const uint32_t payloadBytes = static_cast<uint32_t>(payloadBytes64);
|
||||
if (payloadBytes > sizeBytes - offset)
|
||||
return false;
|
||||
|
||||
tag.payloadOffset = offset;
|
||||
for (uint32_t i = 0u; i < tag.nreg; ++i)
|
||||
tag.regs[i] = static_cast<uint8_t>((tag.hi >> (i * 4u)) & 0xFu);
|
||||
|
||||
if (!visitor(tag))
|
||||
return false;
|
||||
|
||||
offset += payloadBytes;
|
||||
}
|
||||
|
||||
return offset == sizeBytes;
|
||||
}
|
||||
|
||||
bool validatePackedGifPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &) { return true; });
|
||||
}
|
||||
|
||||
void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight)
|
||||
{
|
||||
const uint32_t dx = static_cast<uint32_t>((display64 >> 0) & 0x0FFFu);
|
||||
@@ -1221,6 +1278,9 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
if (!data || sizeBytes < 16 || !m_vram)
|
||||
return;
|
||||
|
||||
if (tryProcessNativeImageUploadPacket(data, sizeBytes))
|
||||
return;
|
||||
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (packetIndex < 48u)
|
||||
@@ -1311,6 +1371,136 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
}
|
||||
}
|
||||
|
||||
bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!data || sizeBytes < 16u || !m_vram)
|
||||
return false;
|
||||
|
||||
if (!validatePackedGifPacket(data, sizeBytes))
|
||||
return false;
|
||||
|
||||
const bool processed = visitPackedGifPacket(data, sizeBytes, [&](const PackedGifPacketTag &tag)
|
||||
{
|
||||
m_curQ = 1.0f;
|
||||
|
||||
recordGifTagDebugEventUnlocked(sizeBytes, tag.nloop, GIF_FMT_PACKED, tag.nreg);
|
||||
|
||||
const bool pre = ((tag.lo >> 46u) & 1u) != 0u;
|
||||
if (pre)
|
||||
writeRegister(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu);
|
||||
|
||||
uint32_t offset = tag.payloadOffset;
|
||||
for (uint32_t loop = 0u; loop < tag.nloop; ++loop)
|
||||
{
|
||||
for (uint32_t r = 0u; r < tag.nreg; ++r)
|
||||
{
|
||||
const uint64_t lo = loadLE64(data + offset);
|
||||
const uint64_t hi = loadLE64(data + offset + 8u);
|
||||
offset += 16u;
|
||||
writeRegisterPacked(tag.regs[r], lo, hi);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!processed)
|
||||
return false;
|
||||
|
||||
++m_nativePackedGIFPacketCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GS::uploadImageNative(uint64_t bitbltbuf,
|
||||
uint64_t trxpos,
|
||||
uint64_t trxreg,
|
||||
uint64_t trxdir,
|
||||
const uint8_t *data,
|
||||
uint32_t sizeBytes)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!data || sizeBytes == 0 || !m_vram)
|
||||
return;
|
||||
|
||||
writeRegister(GS_REG_BITBLTBUF, bitbltbuf);
|
||||
writeRegister(GS_REG_TRXPOS, trxpos);
|
||||
writeRegister(GS_REG_TRXREG, trxreg);
|
||||
writeRegister(GS_REG_TRXDIR, trxdir);
|
||||
processImageData(data, sizeBytes);
|
||||
++m_nativeImageUploadCount;
|
||||
}
|
||||
|
||||
bool GS::tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
constexpr uint32_t kSetupRegisters = 4u;
|
||||
constexpr uint32_t kPackedAdPayloadBytes = kSetupRegisters * 16u;
|
||||
constexpr uint64_t kPackedAdDescriptor = 0x0Eull;
|
||||
|
||||
if (!data || sizeBytes < 16u + kPackedAdPayloadBytes + 16u)
|
||||
return false;
|
||||
|
||||
const uint64_t setupTagLo = loadLE64(data);
|
||||
const uint64_t setupTagHi = loadLE64(data + 8u);
|
||||
const uint32_t setupNloop = static_cast<uint32_t>(setupTagLo & 0x7FFFu);
|
||||
const uint8_t setupFlg = static_cast<uint8_t>((setupTagLo >> 58u) & 0x3u);
|
||||
uint32_t setupNreg = static_cast<uint32_t>((setupTagLo >> 60u) & 0xFu);
|
||||
if (setupNreg == 0u)
|
||||
setupNreg = 16u;
|
||||
|
||||
if (setupNloop != kSetupRegisters ||
|
||||
setupFlg != GIF_FMT_PACKED ||
|
||||
setupNreg != 1u ||
|
||||
(setupTagHi & 0xFull) != kPackedAdDescriptor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t regs[kSetupRegisters] = {};
|
||||
uint32_t offset = 16u;
|
||||
constexpr uint8_t expectedRegs[kSetupRegisters] = {
|
||||
GS_REG_BITBLTBUF,
|
||||
GS_REG_TRXPOS,
|
||||
GS_REG_TRXREG,
|
||||
GS_REG_TRXDIR,
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < kSetupRegisters; ++i)
|
||||
{
|
||||
regs[i] = loadLE64(data + offset);
|
||||
const uint64_t reg = loadLE64(data + offset + 8u);
|
||||
if ((reg & 0xFFu) != expectedRegs[i])
|
||||
return false;
|
||||
offset += 16u;
|
||||
}
|
||||
|
||||
const uint32_t trxdirMode = static_cast<uint32_t>(regs[3] & 0x3ull);
|
||||
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
|
||||
const uint32_t rrh = static_cast<uint32_t>((regs[2] >> 32u) & 0xFFFull);
|
||||
if (trxdirMode != 0u || rrw == 0u || rrh == 0u)
|
||||
return false;
|
||||
|
||||
if (offset + 16u > sizeBytes)
|
||||
return false;
|
||||
|
||||
const uint64_t imageTagLo = loadLE64(data + offset);
|
||||
const uint8_t imageFlg = static_cast<uint8_t>((imageTagLo >> 58u) & 0x3u);
|
||||
const uint32_t imageNloop = static_cast<uint32_t>(imageTagLo & 0x7FFFu);
|
||||
if (imageFlg != GIF_FMT_IMAGE || imageNloop == 0u)
|
||||
return false;
|
||||
|
||||
offset += 16u;
|
||||
const uint64_t imageBytes64 = static_cast<uint64_t>(imageNloop) * 16ull;
|
||||
if (imageBytes64 > 0xFFFFFFFFull)
|
||||
return false;
|
||||
const uint32_t imageBytes = static_cast<uint32_t>(imageBytes64);
|
||||
if (offset + imageBytes != sizeBytes)
|
||||
return false;
|
||||
|
||||
uploadImageNative(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
|
||||
{
|
||||
switch (regDesc)
|
||||
|
||||
@@ -791,8 +791,10 @@ void GSRasterizer::drawSprite(GS *gs)
|
||||
uint32_t texel = 0xFFFF00FFu;
|
||||
if (gs->m_prim.fst)
|
||||
{
|
||||
const uint16_t sampleU = static_cast<uint16_t>(clampInt(static_cast<int>(std::lround(texUf * 16.0f)), 0, 0xFFFF));
|
||||
const uint16_t sampleV = static_cast<uint16_t>(clampInt(static_cast<int>(std::lround(texVf * 16.0f)), 0, 0xFFFF));
|
||||
const int fixedU = static_cast<int>((texUf * 16.0f) + 0.5f);
|
||||
const int fixedV = static_cast<int>((texVf * 16.0f) + 0.5f);
|
||||
const uint16_t sampleU = static_cast<uint16_t>(clampInt(fixedU, 0, 0xFFFF));
|
||||
const uint16_t sampleV = static_cast<uint16_t>(clampInt(fixedV, 0, 0xFFFF));
|
||||
texel = sampleTexture(gs, 0.0f, 0.0f, 1.0f, sampleU, sampleV);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "ps2_log.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -36,7 +38,12 @@ namespace
|
||||
|
||||
inline bool isGsPrivReg(uint32_t addr)
|
||||
{
|
||||
return addr >= PS2_GS_PRIV_REG_BASE && addr < PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE;
|
||||
return Ps2AddressInRange(addr, PS2_GS_PRIV_REG_BASE, PS2_GS_PRIV_REG_SIZE);
|
||||
}
|
||||
|
||||
inline bool isIoRegister(uint32_t addr)
|
||||
{
|
||||
return Ps2AddressInRange(addr, PS2_IO_BASE, PS2_IO_SIZE);
|
||||
}
|
||||
|
||||
inline uint64_t *gsRegPtr(GSRegisters &gs, uint32_t addr)
|
||||
@@ -156,6 +163,42 @@ namespace
|
||||
return static_cast<uint64_t>(duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
struct DmaTagView
|
||||
{
|
||||
uint16_t qwc = 0;
|
||||
uint8_t id = 0;
|
||||
bool irq = false;
|
||||
uint32_t addr = 0;
|
||||
uint32_t upper = 0;
|
||||
};
|
||||
|
||||
inline DmaTagView decodeDmaTag(uint64_t tag)
|
||||
{
|
||||
DmaTagView out{};
|
||||
out.qwc = static_cast<uint16_t>(tag & 0xFFFFu);
|
||||
out.id = static_cast<uint8_t>((tag >> 28u) & 0x7u);
|
||||
out.irq = ((tag >> 31u) & 0x1ull) != 0ull;
|
||||
out.addr = static_cast<uint32_t>((tag >> 32u) & 0x7FFFFFFFu);
|
||||
out.upper = static_cast<uint32_t>((tag >> 16u) & 0xFFFFu);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline uint32_t gifTagNloop(uint64_t tagLo)
|
||||
{
|
||||
return static_cast<uint32_t>(tagLo & 0x7FFFu);
|
||||
}
|
||||
|
||||
inline uint8_t gifTagFlg(uint64_t tagLo)
|
||||
{
|
||||
return static_cast<uint8_t>((tagLo >> 58u) & 0x3u);
|
||||
}
|
||||
|
||||
inline uint32_t gifTagNreg(uint64_t tagLo)
|
||||
{
|
||||
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60u) & 0xFu);
|
||||
return nreg == 0u ? 16u : nreg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helpers for GS VRAM addressing (PSMCT32 path).
|
||||
@@ -412,15 +455,15 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
|
||||
// EE uncached aliases of main RAM (per PS2 memory map):
|
||||
// 0x20000000-0x3FFFFFFF -> 32MB mirror of RDRAM
|
||||
// This includes the accelerated window rooted at 0x30100000.
|
||||
if (virtualAddress >= 0x20000000u && virtualAddress < 0x40000000u)
|
||||
if (Ps2IsUncachedRamMirrorAddress(virtualAddress))
|
||||
{
|
||||
return virtualAddress & PS2_RAM_MASK;
|
||||
}
|
||||
|
||||
// KSEG0/KSEG1 direct-mapped window.
|
||||
if (virtualAddress >= 0x80000000 && virtualAddress < 0xC0000000)
|
||||
if (Ps2IsKseg01Address(virtualAddress))
|
||||
{
|
||||
return virtualAddress & 0x1FFFFFFF;
|
||||
return Ps2DirectMappedPhysicalAddress(virtualAddress);
|
||||
}
|
||||
|
||||
// In this runtime, low segments are treated as physical-style addresses already.
|
||||
@@ -430,7 +473,7 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
|
||||
}
|
||||
|
||||
// KSEG2/KSEG3 are TLB mapped.
|
||||
if (virtualAddress >= 0xC0000000)
|
||||
if (Ps2IsKseg23Address(virtualAddress))
|
||||
{
|
||||
for (const auto &entry : m_tlbEntries)
|
||||
{
|
||||
@@ -526,7 +569,7 @@ uint8_t PS2Memory::read8(uint32_t address)
|
||||
(void)vuLimit;
|
||||
return vuMem[vuOffset];
|
||||
}
|
||||
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
else if (isIoRegister(physAddr))
|
||||
{
|
||||
uint32_t regAddr = physAddr & ~0x3;
|
||||
uint32_t value = readIORegister(regAddr);
|
||||
@@ -561,7 +604,7 @@ uint16_t PS2Memory::read16(uint32_t address)
|
||||
{
|
||||
return loadScalar<uint16_t>(vuMem, vuOffset, vuLimit, "read16 vu", address);
|
||||
}
|
||||
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
else if (isIoRegister(physAddr))
|
||||
{
|
||||
uint32_t regAddr = physAddr & ~0x3;
|
||||
uint32_t value = readIORegister(regAddr);
|
||||
@@ -612,7 +655,7 @@ uint32_t PS2Memory::read32(uint32_t address)
|
||||
{
|
||||
return loadScalar<uint32_t>(vuMem, vuOffset, vuLimit, "read32 vu", address);
|
||||
}
|
||||
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
else if (isIoRegister(physAddr))
|
||||
{
|
||||
return readIORegister(physAddr);
|
||||
}
|
||||
@@ -658,7 +701,7 @@ uint64_t PS2Memory::read64(uint32_t address)
|
||||
|
||||
// 64-bit IO read: compose from the two adjacent 32-bit IO register slots
|
||||
// to avoid any side-effects from read32 handlers.
|
||||
if (address >= PS2_IO_BASE && address < (PS2_IO_BASE + PS2_IO_SIZE))
|
||||
if (isIoRegister(address))
|
||||
{
|
||||
uint32_t lo = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u;
|
||||
uint32_t hi = m_ioRegisters.count(address + 4) ? m_ioRegisters[address + 4] : 0u;
|
||||
@@ -724,7 +767,7 @@ void PS2Memory::write8(uint32_t address, uint8_t value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
if (isIoRegister(physAddr))
|
||||
{
|
||||
// IO registers - handle byte writes by modifying the appropriate byte in the word
|
||||
uint32_t regAddr = physAddr & ~0x3;
|
||||
@@ -763,7 +806,7 @@ void PS2Memory::write16(uint32_t address, uint16_t value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
if (isIoRegister(physAddr))
|
||||
{
|
||||
uint32_t regAddr = physAddr & ~0x3;
|
||||
uint32_t shift = (physAddr & 2) * 8;
|
||||
@@ -823,7 +866,7 @@ void PS2Memory::write32(uint32_t address, uint32_t value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
if (isIoRegister(physAddr))
|
||||
{
|
||||
writeIORegister(physAddr, value);
|
||||
}
|
||||
@@ -874,7 +917,7 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
if (isIoRegister(physAddr))
|
||||
{
|
||||
write32(address, (uint32_t)value);
|
||||
write32(address + 4, (uint32_t)(value >> 32));
|
||||
@@ -913,7 +956,7 @@ void PS2Memory::write128(uint32_t address, __m128i value)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
|
||||
if (isIoRegister(physAddr))
|
||||
{
|
||||
// Non-RAM 128-bit stores are modeled as two 64-bit stores.
|
||||
uint64_t lo = _mm_extract_epi64(value, 0);
|
||||
@@ -1693,6 +1736,278 @@ void PS2Memory::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
m_gifPacketCallback(data, sizeBytes);
|
||||
}
|
||||
|
||||
bool PS2Memory::tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr)
|
||||
{
|
||||
static constexpr uint32_t GIF_CHANNEL = 0x1000A000u;
|
||||
static constexpr uint32_t D_STAT = 0x1000E010u;
|
||||
static constexpr uint32_t D_CTRL = 0x1000E000u;
|
||||
|
||||
if (!m_rdram || !m_gsVRAM || m_path3Masked)
|
||||
return false;
|
||||
if (m_gifArbiter && !m_gifArbiter->empty())
|
||||
return false;
|
||||
if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u)
|
||||
return false;
|
||||
if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u)
|
||||
return false;
|
||||
|
||||
const auto dctrlIt = m_ioRegisters.find(D_CTRL);
|
||||
if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u))
|
||||
return false;
|
||||
|
||||
auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool
|
||||
{
|
||||
try
|
||||
{
|
||||
const bool scratch = isScratchpad(guestAddr);
|
||||
const uint32_t phys = translateAddress(guestAddr);
|
||||
const uint8_t *base = scratch ? m_scratchpad : m_rdram;
|
||||
const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
|
||||
if (!base || phys > limit || bytes > limit - phys)
|
||||
return false;
|
||||
out = base + phys;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
auto loadDmaTagAt = [&](uint32_t guestAddr, DmaTagView &out) -> bool
|
||||
{
|
||||
const uint8_t *ptr = nullptr;
|
||||
if (!resolveContiguous(guestAddr, 16u, ptr))
|
||||
return false;
|
||||
out = decodeDmaTag(loadScalar<uint64_t>(ptr, 0u, 16u, "native gif dma tag", guestAddr));
|
||||
return true;
|
||||
};
|
||||
|
||||
auto decodeSetupPayload = [&](const uint8_t *payload, uint64_t (®s)[4]) -> bool
|
||||
{
|
||||
const uint64_t tagLo = loadScalar<uint64_t>(payload, 0u, 80u, "native gif setup tag", 0u);
|
||||
const uint64_t tagHi = loadScalar<uint64_t>(payload, 8u, 80u, "native gif setup regs", 0u);
|
||||
if (gifTagNloop(tagLo) != 4u ||
|
||||
gifTagFlg(tagLo) != GIF_FMT_PACKED ||
|
||||
gifTagNreg(tagLo) != 1u ||
|
||||
(tagHi & 0xFull) != 0x0Eull)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr uint8_t kExpectedRegs[4] = {
|
||||
GS_REG_BITBLTBUF,
|
||||
GS_REG_TRXPOS,
|
||||
GS_REG_TRXREG,
|
||||
GS_REG_TRXDIR,
|
||||
};
|
||||
|
||||
uint32_t offset = 16u;
|
||||
for (uint32_t i = 0; i < 4u; ++i)
|
||||
{
|
||||
regs[i] = loadScalar<uint64_t>(payload, offset, 80u, "native gif setup value", 0u);
|
||||
const uint64_t reg = loadScalar<uint64_t>(payload, offset + 8u, 80u, "native gif setup register", 0u);
|
||||
if ((reg & 0xFFu) != kExpectedRegs[i])
|
||||
return false;
|
||||
offset += 16u;
|
||||
}
|
||||
|
||||
const uint32_t trxdirMode = static_cast<uint32_t>(regs[3] & 0x3ull);
|
||||
const uint32_t rrw = static_cast<uint32_t>(regs[2] & 0xFFFull);
|
||||
const uint32_t rrh = static_cast<uint32_t>((regs[2] >> 32u) & 0xFFFull);
|
||||
return trxdirMode == 0u && rrw != 0u && rrh != 0u;
|
||||
};
|
||||
|
||||
DmaTagView setupTag{};
|
||||
if (!loadDmaTagAt(tadr, setupTag) ||
|
||||
setupTag.id != 1u ||
|
||||
setupTag.qwc != 5u ||
|
||||
setupTag.irq)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *setupPayload = nullptr;
|
||||
const uint32_t setupPayloadAddr = tadr + 16u;
|
||||
if (!resolveContiguous(setupPayloadAddr, 5u * 16u, setupPayload))
|
||||
return false;
|
||||
|
||||
uint64_t setupRegs[4] = {};
|
||||
if (!decodeSetupPayload(setupPayload, setupRegs))
|
||||
return false;
|
||||
|
||||
uint32_t imageTagDmaAddr = setupPayloadAddr + 5u * 16u;
|
||||
DmaTagView imageTagDma{};
|
||||
if (!loadDmaTagAt(imageTagDmaAddr, imageTagDma) ||
|
||||
imageTagDma.id != 1u ||
|
||||
imageTagDma.qwc != 1u ||
|
||||
imageTagDma.irq)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *imageGifTag = nullptr;
|
||||
if (!resolveContiguous(imageTagDmaAddr + 16u, 16u, imageGifTag))
|
||||
return false;
|
||||
|
||||
const uint64_t imageTagLo = loadScalar<uint64_t>(imageGifTag, 0u, 16u, "native gif image tag", imageTagDmaAddr + 16u);
|
||||
if (gifTagFlg(imageTagLo) != GIF_FMT_IMAGE)
|
||||
return false;
|
||||
|
||||
const uint32_t imageQwc = gifTagNloop(imageTagLo);
|
||||
if (imageQwc == 0u)
|
||||
return false;
|
||||
|
||||
const uint64_t imageBytes64 = static_cast<uint64_t>(imageQwc) * 16ull;
|
||||
if (imageBytes64 > 0xFFFFFFFFull)
|
||||
return false;
|
||||
const uint32_t imageBytes = static_cast<uint32_t>(imageBytes64);
|
||||
|
||||
const uint32_t payloadTagAddr = imageTagDmaAddr + 32u;
|
||||
DmaTagView payloadTag{};
|
||||
if (!loadDmaTagAt(payloadTagAddr, payloadTag) ||
|
||||
payloadTag.qwc != imageQwc ||
|
||||
payloadTag.irq)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t imageDataAddr = 0u;
|
||||
uint32_t finalTadr = payloadTagAddr;
|
||||
uint32_t lastTagUpper = payloadTag.upper;
|
||||
if (payloadTag.id == 3u || payloadTag.id == 4u)
|
||||
{
|
||||
imageDataAddr = payloadTag.addr;
|
||||
const uint32_t terminalTagAddr = payloadTagAddr + 16u;
|
||||
DmaTagView terminalTag{};
|
||||
if (!loadDmaTagAt(terminalTagAddr, terminalTag) ||
|
||||
terminalTag.qwc != 0u ||
|
||||
terminalTag.irq ||
|
||||
(terminalTag.id != 0u && terminalTag.id != 7u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finalTadr = (terminalTag.id == 0u) ? (terminalTagAddr + 16u) : terminalTagAddr;
|
||||
lastTagUpper = terminalTag.upper;
|
||||
}
|
||||
else if (payloadTag.id == 7u)
|
||||
{
|
||||
imageDataAddr = payloadTagAddr + 16u;
|
||||
finalTadr = payloadTagAddr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *imageData = nullptr;
|
||||
if (!resolveContiguous(imageDataAddr, imageBytes, imageData))
|
||||
return false;
|
||||
|
||||
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
|
||||
m_seenGifCopy = true;
|
||||
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
|
||||
gs.uploadImageNative(setupRegs[0], setupRegs[1], setupRegs[2], setupRegs[3], imageData, imageBytes);
|
||||
|
||||
m_ioRegisters[GIF_CHANNEL + 0x30u] = finalTadr;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (lastTagUpper << 16u)) & ~0x100u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u;
|
||||
|
||||
uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u;
|
||||
dstat |= (1u << 2u);
|
||||
const uint32_t status = dstat & 0x3FFu;
|
||||
const uint32_t mask = (dstat >> 16u) & 0x3FFu;
|
||||
if ((status & mask) != 0u)
|
||||
dstat |= (1u << 31u);
|
||||
else
|
||||
dstat &= ~(1u << 31u);
|
||||
m_ioRegisters[D_STAT] = dstat;
|
||||
queueCompletedDmacCause(2u);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2Memory::tryProcessNativeGifPackedChain(GS &gs, uint32_t tadr, uint32_t chcr)
|
||||
{
|
||||
static constexpr uint32_t GIF_CHANNEL = 0x1000A000u;
|
||||
static constexpr uint32_t D_STAT = 0x1000E010u;
|
||||
static constexpr uint32_t D_CTRL = 0x1000E000u;
|
||||
|
||||
if (!m_rdram || !m_gsVRAM || m_path3Masked)
|
||||
return false;
|
||||
if (m_gifArbiter && !m_gifArbiter->empty())
|
||||
return false;
|
||||
if ((chcr & 0x100u) == 0u || ((chcr >> 2u) & 0x3u) != 1u)
|
||||
return false;
|
||||
if ((chcr & (1u << 7u)) != 0u || ((chcr >> 4u) & 0x3u) != 0u)
|
||||
return false;
|
||||
|
||||
const auto dctrlIt = m_ioRegisters.find(D_CTRL);
|
||||
if (dctrlIt != m_ioRegisters.end() && ((dctrlIt->second & 0x1u) == 0u))
|
||||
return false;
|
||||
|
||||
auto resolveContiguous = [&](uint32_t guestAddr, uint32_t bytes, const uint8_t *&out) -> bool
|
||||
{
|
||||
try
|
||||
{
|
||||
const bool scratch = isScratchpad(guestAddr);
|
||||
const uint32_t phys = translateAddress(guestAddr);
|
||||
const uint8_t *base = scratch ? m_scratchpad : m_rdram;
|
||||
const uint32_t limit = scratch ? PS2_SCRATCHPAD_SIZE : PS2_RAM_SIZE;
|
||||
if (!base || phys > limit || bytes > limit - phys)
|
||||
return false;
|
||||
out = base + phys;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const uint8_t *tagPtr = nullptr;
|
||||
if (!resolveContiguous(tadr, 16u, tagPtr))
|
||||
return false;
|
||||
|
||||
const DmaTagView tag = decodeDmaTag(loadScalar<uint64_t>(tagPtr, 0u, 16u, "native packed gif dma tag", tadr));
|
||||
if (tag.id != 7u || tag.qwc == 0u || tag.irq)
|
||||
return false;
|
||||
|
||||
const uint64_t payloadBytes64 = static_cast<uint64_t>(tag.qwc) * 16ull;
|
||||
if (payloadBytes64 > 0xFFFFFFFFull)
|
||||
return false;
|
||||
const uint32_t payloadBytes = static_cast<uint32_t>(payloadBytes64);
|
||||
|
||||
const uint8_t *payload = nullptr;
|
||||
if (!resolveContiguous(tadr + 16u, payloadBytes, payload))
|
||||
return false;
|
||||
if (!gs.processNativePackedGIFPacket(payload, payloadBytes))
|
||||
return false;
|
||||
|
||||
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
|
||||
m_seenGifCopy = true;
|
||||
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
m_ioRegisters[GIF_CHANNEL + 0x30u] = tadr;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x40u] = 0u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x50u] = 0u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x00u] = ((chcr & 0x0000FFFFu) | (tag.upper << 16u)) & ~0x100u;
|
||||
m_ioRegisters[GIF_CHANNEL + 0x20u] = 0u;
|
||||
|
||||
uint32_t dstat = m_ioRegisters.count(D_STAT) ? m_ioRegisters[D_STAT] : 0u;
|
||||
dstat |= (1u << 2u);
|
||||
const uint32_t status = dstat & 0x3FFu;
|
||||
const uint32_t mask = (dstat >> 16u) & 0x3FFu;
|
||||
if ((status & mask) != 0u)
|
||||
dstat |= (1u << 31u);
|
||||
else
|
||||
dstat &= ~(1u << 31u);
|
||||
m_ioRegisters[D_STAT] = dstat;
|
||||
queueCompletedDmacCause(2u);
|
||||
return true;
|
||||
}
|
||||
|
||||
int PS2Memory::pollDmaRegisters()
|
||||
{
|
||||
return 0;
|
||||
|
||||
@@ -2114,6 +2114,40 @@ void PS2Runtime::Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::kickGifDmaChainFromMMIO(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
uint32_t dPcrValue,
|
||||
uint32_t dStatValue,
|
||||
uint32_t tadr,
|
||||
uint32_t chcr)
|
||||
{
|
||||
constexpr uint32_t D_PCR = 0x1000E020u;
|
||||
constexpr uint32_t D_STAT = 0x1000E010u;
|
||||
constexpr uint32_t GIF_TADR = 0x1000A030u;
|
||||
constexpr uint32_t GIF_CHCR = 0x1000A000u;
|
||||
|
||||
ps2TraceGuestWrite(rdram, D_PCR, 4u, dPcrValue, 0u, "WRITE32", ctx);
|
||||
m_memory.writeIORegister(D_PCR, dPcrValue);
|
||||
ps2TraceGuestWrite(rdram, D_STAT, 4u, dStatValue, 0u, "WRITE32", ctx);
|
||||
m_memory.writeIORegister(D_STAT, dStatValue);
|
||||
ps2TraceGuestWrite(rdram, GIF_TADR, 4u, tadr, 0u, "WRITE32", ctx);
|
||||
m_memory.writeIORegister(GIF_TADR, tadr);
|
||||
ps2TraceGuestWrite(rdram, GIF_CHCR, 4u, chcr, 0u, "WRITE32", ctx);
|
||||
if (m_memory.tryProcessNativeGifImageUploadChain(m_gs, tadr, chcr))
|
||||
{
|
||||
drainCompletedDmacHandlers(rdram);
|
||||
return;
|
||||
}
|
||||
if (m_memory.tryProcessNativeGifPackedChain(m_gs, tadr, chcr))
|
||||
{
|
||||
drainCompletedDmacHandlers(rdram);
|
||||
return;
|
||||
}
|
||||
m_memory.writeIORegister(GIF_CHCR, chcr);
|
||||
m_memory.processPendingTransfers();
|
||||
drainCompletedDmacHandlers(rdram);
|
||||
}
|
||||
|
||||
void PS2Runtime::requestStop()
|
||||
{
|
||||
m_stopRequested.store(true, std::memory_order_relaxed);
|
||||
|
||||
@@ -35,6 +35,50 @@ static Instruction makeNop(uint32_t address)
|
||||
return inst;
|
||||
}
|
||||
|
||||
static uint32_t signExtend16(uint16_t value)
|
||||
{
|
||||
return static_cast<uint32_t>(static_cast<int32_t>(static_cast<int16_t>(value)));
|
||||
}
|
||||
|
||||
static Instruction makeIType(uint32_t address, uint32_t opcode, uint8_t rs, uint8_t rt, uint16_t immediate)
|
||||
{
|
||||
Instruction inst{};
|
||||
inst.address = address;
|
||||
inst.opcode = opcode;
|
||||
inst.rs = rs;
|
||||
inst.rt = rt;
|
||||
inst.immediate = immediate;
|
||||
inst.simmediate = signExtend16(immediate);
|
||||
inst.raw = (opcode << 26) | (static_cast<uint32_t>(rs) << 21) |
|
||||
(static_cast<uint32_t>(rt) << 16) | immediate;
|
||||
return inst;
|
||||
}
|
||||
|
||||
static Instruction makeLui(uint32_t address, uint8_t rt, uint16_t immediate)
|
||||
{
|
||||
return makeIType(address, OPCODE_LUI, 0, rt, immediate);
|
||||
}
|
||||
|
||||
static Instruction makeOri(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate)
|
||||
{
|
||||
return makeIType(address, OPCODE_ORI, rs, rt, immediate);
|
||||
}
|
||||
|
||||
static Instruction makeAddiu(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate)
|
||||
{
|
||||
return makeIType(address, OPCODE_ADDIU, rs, rt, immediate);
|
||||
}
|
||||
|
||||
static Instruction makeLw(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate)
|
||||
{
|
||||
return makeIType(address, OPCODE_LW, rs, rt, immediate);
|
||||
}
|
||||
|
||||
static Instruction makeSw(uint32_t address, uint8_t rt, uint8_t rs, uint16_t immediate)
|
||||
{
|
||||
return makeIType(address, OPCODE_SW, rs, rt, immediate);
|
||||
}
|
||||
|
||||
static std::string readFileFromCandidates(const std::vector<std::string> &candidates)
|
||||
{
|
||||
for (const auto &path : candidates)
|
||||
@@ -152,6 +196,130 @@ void register_code_generator_tests()
|
||||
"MULT1 should write low product to rd on R5900");
|
||||
});
|
||||
|
||||
tc.Run("constant MMIO store emits direct runtime store", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "mmio_store";
|
||||
func.start = 0x1000;
|
||||
func.end = 0x1010;
|
||||
func.isRecompiled = true;
|
||||
|
||||
std::vector<Instruction> instructions;
|
||||
instructions.push_back(makeLui(0x1000, 1, 0x1000));
|
||||
instructions.push_back(makeOri(0x1004, 1, 1, 0xE020));
|
||||
instructions.push_back(makeSw(0x1008, 2, 1, 0));
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
std::string generated = gen.generateFunction(func, instructions, false);
|
||||
printGeneratedCode("constant MMIO store emits direct runtime store", generated);
|
||||
|
||||
t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000E020u, GPR_U32(ctx, 2));") != std::string::npos,
|
||||
"constant MMIO SW should emit a direct runtime Store32");
|
||||
t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 1)") == std::string::npos,
|
||||
"constant MMIO SW should not go through WRITE32 address classification");
|
||||
});
|
||||
|
||||
tc.Run("constant RDRAM load and store emit fast memory access", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "rdram_access";
|
||||
func.start = 0x2000;
|
||||
func.end = 0x2014;
|
||||
func.isRecompiled = true;
|
||||
|
||||
std::vector<Instruction> instructions;
|
||||
instructions.push_back(makeLui(0x2000, 1, 0x0012));
|
||||
instructions.push_back(makeOri(0x2004, 1, 1, 0x3450));
|
||||
instructions.push_back(makeLw(0x2008, 3, 1, 0x0010));
|
||||
instructions.push_back(makeSw(0x200C, 4, 1, 0x0014));
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
std::string generated = gen.generateFunction(func, instructions, false);
|
||||
printGeneratedCode("constant RDRAM load and store emit fast memory access", generated);
|
||||
|
||||
t.IsTrue(generated.find("SET_GPR_S32(ctx, 3, (int32_t)FAST_READ32(0x123460u));") != std::string::npos,
|
||||
"constant RDRAM LW should emit FAST_READ32 with the resolved address");
|
||||
t.IsTrue(generated.find("FAST_WRITE32(0x123464u, _value);") != std::string::npos,
|
||||
"constant RDRAM SW should emit FAST_WRITE32 with the resolved address");
|
||||
t.IsTrue(generated.find("READ32(ADD32(GPR_U32(ctx, 1)") == std::string::npos,
|
||||
"constant RDRAM LW should not go through READ32 address classification");
|
||||
t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 1)") == std::string::npos,
|
||||
"constant RDRAM SW should not go through WRITE32 address classification");
|
||||
});
|
||||
|
||||
tc.Run("known GIF DMA MMIO sequence emits native kick helper", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "gif_dma_kick";
|
||||
func.start = 0x3000;
|
||||
func.end = 0x3030;
|
||||
func.isRecompiled = true;
|
||||
|
||||
std::vector<Instruction> instructions;
|
||||
instructions.push_back(makeAddiu(0x3000, 2, 0, 4));
|
||||
instructions.push_back(makeLui(0x3004, 1, 0x1000));
|
||||
instructions.push_back(makeOri(0x3008, 1, 1, 0xE020));
|
||||
instructions.push_back(makeSw(0x300C, 2, 1, 0));
|
||||
instructions.push_back(makeLui(0x3010, 1, 0x1000));
|
||||
instructions.push_back(makeOri(0x3014, 1, 1, 0xE010));
|
||||
instructions.push_back(makeSw(0x3018, 2, 1, 0));
|
||||
instructions.push_back(makeLui(0x301C, 1, 0x1000));
|
||||
instructions.push_back(makeOri(0x3020, 1, 1, 0xA030));
|
||||
instructions.push_back(makeSw(0x3024, 4, 1, 0));
|
||||
instructions.push_back(makeAddiu(0x3028, 5, 0, 0x0105));
|
||||
instructions.push_back(makeAddiu(0x302C, 1, 1, 0xFFD0));
|
||||
instructions.push_back(makeSw(0x3030, 5, 1, 0));
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
std::string generated = gen.generateFunction(func, instructions, false);
|
||||
printGeneratedCode("known GIF DMA MMIO sequence emits native kick helper", generated);
|
||||
|
||||
t.IsTrue(generated.find("uint32_t gifDmaKickValue_3024_2 = GPR_U32(ctx, 4);") != std::string::npos,
|
||||
"dynamic GIF TADR source should be captured when the store is coalesced");
|
||||
t.IsTrue(generated.find("runtime->kickGifDmaChainFromMMIO(rdram, ctx, 0x4u, 0x4u, gifDmaKickValue_3024_2, 0x105u);") != std::string::npos,
|
||||
"known GIF DMA MMIO stores should coalesce into the native kick helper");
|
||||
t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000E020u") == std::string::npos,
|
||||
"coalesced D_PCR store should not remain as an individual Store32");
|
||||
t.IsTrue(generated.find("runtime->Store32(rdram, ctx, 0x1000A000u") == std::string::npos,
|
||||
"coalesced GIF CHCR store should not remain as an individual Store32");
|
||||
});
|
||||
|
||||
tc.Run("GIF DMA kick coalesces when CHCR store is a return delay slot", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "loadImage_like";
|
||||
func.start = 0x2E7C90;
|
||||
func.end = 0x2E7CC8;
|
||||
func.isRecompiled = true;
|
||||
|
||||
std::vector<Instruction> instructions;
|
||||
instructions.push_back(makeBranch(0x2E7C90, 2));
|
||||
instructions.push_back(makeLui(0x2E7C94, 5, 0x1000));
|
||||
instructions.push_back(makeLui(0x2E7C98, 5, 0x1000));
|
||||
instructions.push_back(makeAddiu(0x2E7C9C, 6, 0, 4));
|
||||
instructions.push_back(makeOri(0x2E7CA0, 3, 5, 0xE020));
|
||||
instructions.push_back(makeSw(0x2E7CA4, 6, 3, 0));
|
||||
instructions.push_back(makeOri(0x2E7CA8, 3, 5, 0xE010));
|
||||
instructions.push_back(makeSw(0x2E7CAC, 6, 3, 0));
|
||||
instructions.push_back(makeOri(0x2E7CB0, 3, 5, 0xA030));
|
||||
instructions.push_back(makeSw(0x2E7CB4, 4, 3, 0));
|
||||
instructions.push_back(makeAddiu(0x2E7CB8, 4, 0, 0x0105));
|
||||
instructions.push_back(makeOri(0x2E7CBC, 3, 5, 0xA000));
|
||||
instructions.push_back(makeJr(0x2E7CC0, 31));
|
||||
instructions.push_back(makeSw(0x2E7CC4, 4, 3, 0));
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
std::string generated = gen.generateFunction(func, instructions, false);
|
||||
printGeneratedCode("GIF DMA kick coalesces when CHCR store is a return delay slot", generated);
|
||||
|
||||
t.IsTrue(generated.find("label_2e7c9c:") != std::string::npos,
|
||||
"test should cover a branch target inside the GIF DMA setup");
|
||||
t.IsTrue(generated.find("uint32_t gifDmaKickValue_2e7cb4_2 = GPR_U32(ctx, 4);") != std::string::npos,
|
||||
"TADR value should be captured before a0 is reused for CHCR");
|
||||
t.IsTrue(generated.find("ctx->in_delay_slot = true;") != std::string::npos,
|
||||
"coalesced helper should still run as the return delay slot");
|
||||
t.IsTrue(generated.find("runtime->kickGifDmaChainFromMMIO(rdram, ctx, 0x4u, 0x4u, gifDmaKickValue_2e7cb4_2, 0x105u);") != std::string::npos,
|
||||
"loadImage-like GIF DMA stores should coalesce into the native kick helper");
|
||||
t.IsTrue(generated.find("WRITE32(ADD32(GPR_U32(ctx, 3), 0), GPR_U32(ctx, 4));") == std::string::npos,
|
||||
"coalesced delay-slot CHCR store should not remain as an individual WRITE32");
|
||||
});
|
||||
|
||||
tc.Run("emits labels and gotos for internal branches", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "test_func";
|
||||
|
||||
@@ -65,6 +65,12 @@ namespace
|
||||
std::memcpy(dst.data() + pos, &value, sizeof(uint64_t));
|
||||
}
|
||||
|
||||
void appendGifAd(std::vector<uint8_t> &dst, uint64_t value, uint64_t reg)
|
||||
{
|
||||
appendU64(dst, value);
|
||||
appendU64(dst, reg);
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
bool waitUntil(Predicate pred, std::chrono::milliseconds timeout)
|
||||
{
|
||||
@@ -1917,6 +1923,67 @@ void register_ps2_gs_tests()
|
||||
t.IsTrue(same, "GIF IMAGE transfer should write payload bytes into GS VRAM");
|
||||
});
|
||||
|
||||
tc.Run("GIF load-image packet uses native upload fast path", [](TestCase &t)
|
||||
{
|
||||
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
|
||||
GS gs;
|
||||
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
|
||||
|
||||
const uint64_t bitblt =
|
||||
(static_cast<uint64_t>(0u) << 0) |
|
||||
(static_cast<uint64_t>(1u) << 16) |
|
||||
(static_cast<uint64_t>(0u) << 24) |
|
||||
(static_cast<uint64_t>(0u) << 32) |
|
||||
(static_cast<uint64_t>(1u) << 48) |
|
||||
(static_cast<uint64_t>(0u) << 56);
|
||||
const uint64_t trxpos = 0ull;
|
||||
const uint64_t trxreg = (2ull << 0) | (2ull << 32);
|
||||
const uint64_t trxdir = 0ull;
|
||||
|
||||
const uint8_t payload[16] = {
|
||||
0x10u, 0x11u, 0x12u, 0x13u,
|
||||
0x20u, 0x21u, 0x22u, 0x23u,
|
||||
0x30u, 0x31u, 0x32u, 0x33u,
|
||||
0x40u, 0x41u, 0x42u, 0x43u,
|
||||
};
|
||||
|
||||
std::vector<uint8_t> packet;
|
||||
appendU64(packet, makeGifTag(4u, GIF_FMT_PACKED, 1u, false));
|
||||
appendU64(packet, 0x0Eull);
|
||||
appendGifAd(packet, bitblt, GS_REG_BITBLTBUF);
|
||||
appendGifAd(packet, trxpos, GS_REG_TRXPOS);
|
||||
appendGifAd(packet, trxreg, GS_REG_TRXREG);
|
||||
appendGifAd(packet, trxdir, GS_REG_TRXDIR);
|
||||
appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
|
||||
appendU64(packet, 0ull);
|
||||
packet.insert(packet.end(), payload, payload + sizeof(payload));
|
||||
|
||||
gs.processGIFPacket(packet.data(), static_cast<uint32_t>(packet.size()));
|
||||
|
||||
t.Equals(gs.nativeImageUploadCount(), 1ull, "load-image packet should use the native image upload fast path");
|
||||
|
||||
bool same = true;
|
||||
for (uint32_t y = 0; y < 2u && same; ++y)
|
||||
{
|
||||
for (uint32_t x = 0; x < 2u; ++x)
|
||||
{
|
||||
const uint32_t pixelIndex = y * 2u + x;
|
||||
const uint32_t off = referenceAddrPSMCT32(0u, 1u, x, y);
|
||||
for (uint32_t c = 0; c < 4u; ++c)
|
||||
{
|
||||
if (vram[off + c] != payload[pixelIndex * 4u + c])
|
||||
{
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same)
|
||||
break;
|
||||
}
|
||||
}
|
||||
t.IsTrue(same, "native load-image upload should preserve pixel payload");
|
||||
});
|
||||
|
||||
tc.Run("GS local-to-host transfer supports partial incremental reads", [](TestCase &t)
|
||||
{
|
||||
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
|
||||
|
||||
@@ -113,6 +113,49 @@ namespace
|
||||
return tag;
|
||||
}
|
||||
|
||||
uint64_t makeGifTagPrim(uint16_t nloop, uint16_t prim, uint8_t flg, uint8_t nreg, bool eop = true, bool pre = true)
|
||||
{
|
||||
uint64_t tag = makeGifTag(nloop, flg, nreg, eop);
|
||||
if (pre)
|
||||
tag |= (1ull << 46);
|
||||
tag |= (static_cast<uint64_t>(prim & 0x7FFu) << 47);
|
||||
return tag;
|
||||
}
|
||||
|
||||
uint64_t makeGsFrame(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t mask = 0u)
|
||||
{
|
||||
return static_cast<uint64_t>(fbp & 0x1FFu) |
|
||||
(static_cast<uint64_t>(fbw & 0x3Fu) << 16u) |
|
||||
(static_cast<uint64_t>(psm & 0x3Fu) << 24u) |
|
||||
(static_cast<uint64_t>(mask) << 32u);
|
||||
}
|
||||
|
||||
uint64_t makeGsScissor(uint32_t x0, uint32_t x1, uint32_t y0, uint32_t y1)
|
||||
{
|
||||
return static_cast<uint64_t>(x0 & 0x7FFu) |
|
||||
(static_cast<uint64_t>(x1 & 0x7FFu) << 16u) |
|
||||
(static_cast<uint64_t>(y0 & 0x7FFu) << 32u) |
|
||||
(static_cast<uint64_t>(y1 & 0x7FFu) << 48u);
|
||||
}
|
||||
|
||||
void appendPackedRgbaq(std::vector<uint8_t> &packet, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
appendU64(packet, static_cast<uint64_t>(r) | (static_cast<uint64_t>(g) << 32u));
|
||||
appendU64(packet, static_cast<uint64_t>(b) | (static_cast<uint64_t>(a) << 32u));
|
||||
}
|
||||
|
||||
void appendPackedXyzf2(std::vector<uint8_t> &packet, uint32_t x, uint32_t y, uint32_t z)
|
||||
{
|
||||
appendU64(packet, static_cast<uint64_t>(x & 0xFFFFu) | (static_cast<uint64_t>(y & 0xFFFFu) << 32u));
|
||||
appendU64(packet, static_cast<uint64_t>(z & 0xFFFFFFu) << 4u);
|
||||
}
|
||||
|
||||
void appendPackedUv(std::vector<uint8_t> &packet, uint32_t u, uint32_t v)
|
||||
{
|
||||
appendU64(packet, static_cast<uint64_t>(u & 0x3FFFu) | (static_cast<uint64_t>(v & 0x3FFFu) << 32u));
|
||||
appendU64(packet, 0u);
|
||||
}
|
||||
|
||||
uint32_t makeVuLowerSpecial(uint8_t specialOp, uint8_t is, uint8_t it = 0u, uint8_t id = 0u, uint8_t dest = 0u)
|
||||
{
|
||||
return (0x40u << 25) |
|
||||
@@ -970,6 +1013,126 @@ void register_ps2_memory_tests()
|
||||
t.IsTrue(contentOk, "scratchpad alias chain payload should match scratchpad bytes");
|
||||
});
|
||||
|
||||
tc.Run("native GIF image upload recognizes canonical load-image chain", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
GS gs;
|
||||
gs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
|
||||
|
||||
constexpr uint32_t kGifCh = 0x1000A000u;
|
||||
constexpr uint32_t kDStat = 0x1000E010u;
|
||||
constexpr uint32_t kChain = 0x00028000u;
|
||||
constexpr uint32_t kPixels = 0x00029000u;
|
||||
constexpr uint32_t kQwc = 1u;
|
||||
|
||||
uint8_t *rdram = mem.getRDRAM();
|
||||
for (uint32_t i = 0; i < kQwc * 16u; ++i)
|
||||
{
|
||||
rdram[kPixels + i] = static_cast<uint8_t>(0x40u + i);
|
||||
}
|
||||
|
||||
uint32_t chain = kChain;
|
||||
chain = writeTextureUploadSetup(rdram, chain, 0u, GS_PSM_CT32);
|
||||
chain = writeTextureImageRef(rdram, chain, kQwc, kPixels);
|
||||
writeDmaTag(rdram, chain, makeDmaTag(0u, 7u, 0u, false)); // END.
|
||||
|
||||
t.IsTrue(mem.writeIORegister(kGifCh + 0x30u, kChain), "write GIF TADR should succeed");
|
||||
t.IsTrue(mem.tryProcessNativeGifImageUploadChain(gs, kChain, 0x105u),
|
||||
"canonical load-image chain should use the native upload path");
|
||||
|
||||
t.Equals(gs.nativeImageUploadCount(), 1ull, "native GIF DMA chain should upload through GS fast path");
|
||||
t.Equals(mem.gifCopyCount(), 1ull, "native GIF DMA chain should still count as a GIF DMA copy");
|
||||
t.IsTrue((mem.readIORegister(kDStat) & (1u << 2u)) != 0u,
|
||||
"native GIF DMA chain should raise D_STAT GIF completion");
|
||||
t.Equals(mem.readIORegister(kGifCh + 0x20u), 0u, "native GIF DMA chain should clear GIF QWC");
|
||||
t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x100u, 0u,
|
||||
"native GIF DMA chain should clear GIF STR");
|
||||
t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x70000000u, 0x70000000u,
|
||||
"native GIF DMA chain should latch the terminal END tag id");
|
||||
|
||||
bool pixelsOk = true;
|
||||
for (uint32_t x = 0; x < 4u && pixelsOk; ++x)
|
||||
{
|
||||
const uint32_t dstOff = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u);
|
||||
const uint32_t srcOff = kPixels + x * 4u;
|
||||
for (uint32_t c = 0; c < 4u; ++c)
|
||||
{
|
||||
if (mem.getGSVRAM()[dstOff + c] != rdram[srcOff + c])
|
||||
{
|
||||
pixelsOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
t.IsTrue(pixelsOk, "native GIF DMA chain should upload image payload into GS VRAM");
|
||||
});
|
||||
|
||||
tc.Run("native GIF packed chain matches generic packed primitive packet", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
|
||||
|
||||
GS nativeGs;
|
||||
nativeGs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
|
||||
|
||||
GSRegisters genericRegs{};
|
||||
std::vector<uint8_t> genericVram(PS2_GS_VRAM_SIZE, 0u);
|
||||
GS genericGs;
|
||||
genericGs.init(genericVram.data(), static_cast<uint32_t>(genericVram.size()), &genericRegs);
|
||||
|
||||
std::vector<uint8_t> packet;
|
||||
appendU64(packet, makeGifTag(4u, GIF_FMT_PACKED, 1u, false));
|
||||
appendU64(packet, 0x0Eull);
|
||||
appendU64(packet, makeGsFrame(0u, 1u, GS_PSM_CT32));
|
||||
appendU64(packet, GS_REG_FRAME_1);
|
||||
appendU64(packet, makeGsScissor(0u, 7u, 0u, 7u));
|
||||
appendU64(packet, GS_REG_SCISSOR_1);
|
||||
appendU64(packet, 1ull << 17u); // ZTST always.
|
||||
appendU64(packet, GS_REG_TEST_1);
|
||||
appendU64(packet, 1ull << 32u); // Mask Z writes so the test framebuffer remains visible.
|
||||
appendU64(packet, GS_REG_ZBUF_1);
|
||||
|
||||
constexpr uint16_t kSpritePrim = static_cast<uint16_t>(GS_PRIM_SPRITE);
|
||||
appendU64(packet, makeGifTagPrim(2u, kSpritePrim, GIF_FMT_PACKED, 3u, true, true));
|
||||
appendU64(packet, static_cast<uint64_t>(GS_REG_UV) |
|
||||
(static_cast<uint64_t>(GS_REG_RGBAQ) << 4u) |
|
||||
(static_cast<uint64_t>(GS_REG_XYZF2) << 8u));
|
||||
appendPackedUv(packet, 0u, 0u);
|
||||
appendPackedRgbaq(packet, 0x20u, 0x40u, 0x80u, 0x80u);
|
||||
appendPackedXyzf2(packet, 0u, 0u, 0u);
|
||||
appendPackedUv(packet, 0u, 0u);
|
||||
appendPackedRgbaq(packet, 0xE0u, 0x30u, 0x10u, 0x80u);
|
||||
appendPackedXyzf2(packet, 64u, 64u, 0u);
|
||||
|
||||
genericGs.processGIFPacket(packet.data(), static_cast<uint32_t>(packet.size()));
|
||||
|
||||
constexpr uint32_t kGifCh = 0x1000A000u;
|
||||
constexpr uint32_t kDStat = 0x1000E010u;
|
||||
constexpr uint32_t kScratchTag = 0xF0000000u;
|
||||
uint8_t *scratch = mem.getScratchpad();
|
||||
writeDmaTag(scratch, 0u, makeDmaTag(static_cast<uint16_t>(packet.size() / 16u), 7u, 0u, false));
|
||||
std::memcpy(scratch + 16u, packet.data(), packet.size());
|
||||
|
||||
t.IsTrue(mem.writeIORegister(kGifCh + 0x30u, kScratchTag), "write GIF TADR scratchpad alias should succeed");
|
||||
t.IsTrue(mem.tryProcessNativeGifPackedChain(nativeGs, kScratchTag, 0x105u),
|
||||
"packed primitive chain should use the native packed GIF path");
|
||||
|
||||
t.Equals(nativeGs.nativePackedGIFPacketCount(), 1ull, "native packed GIF packet counter should increment");
|
||||
t.Equals(mem.gifCopyCount(), 1ull, "native packed GIF chain should still count as a GIF DMA copy");
|
||||
t.IsTrue((mem.readIORegister(kDStat) & (1u << 2u)) != 0u,
|
||||
"native packed GIF chain should raise D_STAT GIF completion");
|
||||
t.Equals(mem.readIORegister(kGifCh + 0x20u), 0u, "native packed GIF chain should clear GIF QWC");
|
||||
t.Equals(mem.readIORegister(kGifCh + 0x00u) & 0x100u, 0u,
|
||||
"native packed GIF chain should clear GIF STR");
|
||||
|
||||
const uint32_t nativePixel = nativeGs.ReadVram(GS_PSM_CT32, 0u, 1u, 1u, 1u);
|
||||
const uint32_t genericPixel = genericGs.ReadVram(GS_PSM_CT32, 0u, 1u, 1u, 1u);
|
||||
t.IsTrue(genericPixel != 0u, "generic packed primitive packet should draw a test pixel");
|
||||
t.Equals(nativePixel, genericPixel, "native packed GIF chain should match generic GS packet output");
|
||||
});
|
||||
|
||||
tc.Run("GIF DMA chain REF keeps CT32 image data after paletted upload", [](TestCase &t)
|
||||
{
|
||||
PS2Memory mem;
|
||||
|
||||
@@ -554,6 +554,58 @@ void register_ps2_runtime_interrupt_tests()
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("native GIF DMA MMIO kick dispatches completed DMAC handler", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kHandlerAddr = 0x00ABD1C0u;
|
||||
constexpr uint32_t kDStat = 0x1000E010u;
|
||||
constexpr uint32_t kDPcr = 0x1000E020u;
|
||||
constexpr uint32_t kTag0 = 0x00028400u;
|
||||
|
||||
uint8_t *rdram = env.runtime.memory().getRDRAM();
|
||||
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 7u, 0u, false)); // END
|
||||
writeGuestU64(rdram, kTag0 + 0x10u, 0x1122334455667788ull);
|
||||
writeGuestU64(rdram, kTag0 + 0x18u, 0x99AABBCCDDEEFF00ull);
|
||||
|
||||
g_dmacSendHits.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
|
||||
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
|
||||
|
||||
R5900Context addCtx{};
|
||||
setRegU32(addCtx, 4, 2u);
|
||||
setRegU32(addCtx, 5, kHandlerAddr);
|
||||
setRegU32(addCtx, 6, 0u);
|
||||
setRegU32(addCtx, 7, 0u);
|
||||
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
|
||||
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register GIF handler");
|
||||
|
||||
R5900Context enableCtx{};
|
||||
setRegU32(enableCtx, 4, 2u);
|
||||
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
|
||||
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable GIF cause");
|
||||
|
||||
R5900Context kickCtx{};
|
||||
env.runtime.kickGifDmaChainFromMMIO(rdram, &kickCtx, 4u, 4u, kTag0, 0x105u);
|
||||
|
||||
t.Equals(env.runtime.memory().readIORegister(kDPcr), 4u, "native GIF kick should preserve D_PCR write");
|
||||
t.IsTrue((env.runtime.memory().readIORegister(kDStat) & (1u << 2)) != 0u,
|
||||
"native GIF kick should raise D_STAT GIF completion status");
|
||||
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u,
|
||||
"native GIF kick should dispatch the GIF DMAC handler");
|
||||
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 2u,
|
||||
"DMAC handler should observe GIF cause");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u,
|
||||
"handler should see GIF STR cleared");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u,
|
||||
"handler should see the latched END tag id");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("negative interrupt-safe EE syscall ids dispatch", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
|
||||
Reference in New Issue
Block a user