refactor: refactor VU1 (#191)

* feat: implement fix and changes based on dark cloud report
fix: fix GS AFAIL for RGB/alpha/Z, ZMSK
fix: fix VU1 flags mask and pipeline
fix: small VU1 cache fix
feat: __ct__, __sinit_ are not sillent stubs anymore

* feat: fix song JP pulling

* feat: sound update for lotR

* feat: prevent guest execution to be very slow

* fix: small gs size bug

* feat: refactor VU
fix: fix cliping and other issues on gs
fix: fix wrong vu0 register on recompiler

* fix fix ACC scheduler stall
feat: remove unused test
fix: .fix overflow e underflow on FMAC

* feat: small setting  for windows test
This commit is contained in:
Ranieri
2026-08-05 14:50:24 -03:00
committed by GitHub
parent 61300792a0
commit f49ca4edbc
34 changed files with 5506 additions and 1015 deletions
+2 -1
View File
@@ -70,7 +70,8 @@ namespace ps2x::iop::detail
.responseCounterOffset = 4u,
.zeroReceiveBuffer = true,
.signalNowaitCompletion = true,
.suppressedCompletionCallbacks = {0x001FFD70u},
.completeQueuedPlayStreams = true,
.suppressedCompletionCallbacks = {},
};
}
+1
View File
@@ -107,6 +107,7 @@ namespace ps2x::iop::detail
uint32_t responseCounterOffset = 0u;
bool zeroReceiveBuffer = true;
bool signalNowaitCompletion = false;
bool completeQueuedPlayStreams = false;
std::vector<uint32_t> suppressedCompletionCallbacks;
};
+118 -4
View File
@@ -7,11 +7,20 @@
#include <stdexcept>
#include <unordered_set>
#include <utility>
#include <vector>
namespace ps2x::iop::detail
{
namespace
{
constexpr uint16_t kPlayStreamCommand = 1u;
constexpr uint32_t kResponseRecordStride = 0x20u;
constexpr uint32_t kPackedStreamOffset = 4u;
constexpr uint32_t kStreamSlotMask = 0x3Fu;
constexpr uint32_t kStreamSlotCount = 48u;
constexpr uint32_t kCommandStreamSlotShift = 8u;
constexpr uint32_t kResponseStreamSlotShift = 4u;
class SoundUpdateStubService final : public IopService
{
public:
@@ -34,6 +43,7 @@ namespace ps2x::iop::detail
{
std::lock_guard<std::mutex> lock(m_mutex);
m_updateCounter = 0u;
m_completedStreamCount = 0u;
}
[[nodiscard]] RpcResult handleRpc(const RpcRequest &request) override
@@ -62,13 +72,23 @@ namespace ps2x::iop::detail
(void)m_host.zeroGuest(request.receive.address, request.receive.size);
}
std::vector<uint32_t> activeStreamSlots;
if (m_bindings.completeQueuedPlayStreams && request.receive.address != 0u)
{
// PlayStream leaves the EE slot in state 2. One active record moves it
// to state 1; the following empty update lets SOUND_CopyIOPBuffer clear it.
activeStreamSlots = findQueuedPlayStreams(request);
trimToReceiveCapacity(activeStreamSlots, request.receive.size);
}
uint32_t counter = 0u;
{
std::lock_guard<std::mutex> lock(m_mutex);
counter = ++m_updateCounter;
m_completedStreamCount += activeStreamSlots.size();
}
constexpr uint32_t activeStreams = 0u;
const uint32_t activeStreams = static_cast<uint32_t>(activeStreamSlots.size());
if (request.receive.address != 0u &&
request.receive.size >= m_bindings.activeStreamCountOffset + sizeof(activeStreams))
{
@@ -76,10 +96,20 @@ namespace ps2x::iop::detail
(void)m_host.writeGuest(address, &activeStreams, sizeof(activeStreams));
}
if (request.receive.address != 0u &&
request.receive.size >= m_bindings.responseCounterOffset + sizeof(counter))
for (size_t index = 0u; index < activeStreamSlots.size(); ++index)
{
const uint32_t address = request.receive.address + m_bindings.responseCounterOffset;
const uint32_t packedStream = activeStreamSlots[index] << kResponseStreamSlotShift;
const uint32_t offset = m_bindings.activeStreamCountOffset + static_cast<uint32_t>(index) * kResponseRecordStride + kPackedStreamOffset;
const uint32_t address = request.receive.address + offset;
(void)m_host.writeGuest(address, &packedStream, sizeof(packedStream));
}
const uint32_t counterOffset = m_bindings.responseCounterOffset +
activeStreams * kResponseRecordStride;
if (request.receive.address != 0u &&
request.receive.size >= counterOffset + sizeof(counter))
{
const uint32_t address = request.receive.address + counterOffset;
(void)m_host.writeGuest(address, &counter, sizeof(counter));
}
@@ -90,14 +120,98 @@ namespace ps2x::iop::detail
{
std::lock_guard<std::mutex> lock(m_mutex);
metrics.push_back({"update_counter", m_updateCounter, false});
metrics.push_back({"completed_streams", m_completedStreamCount, false});
}
private:
[[nodiscard]] std::vector<uint32_t> findQueuedPlayStreams(const RpcRequest &request) const
{
std::vector<uint32_t> slots;
if (request.send.address == 0u || request.send.size < sizeof(uint16_t))
{
return slots;
}
uint16_t commandCount = 0u;
if (!m_host.readGuest(request.send.address, &commandCount, sizeof(commandCount)))
{
return slots;
}
uint32_t offset = sizeof(commandCount);
for (uint32_t commandIndex = 0u; commandIndex < commandCount; ++commandIndex)
{
constexpr uint32_t headerSize = sizeof(uint16_t) * 2u;
if (offset > request.send.size || request.send.size - offset < headerSize)
{
break;
}
std::array<uint16_t, 2> header{};
if (!m_host.readGuest(request.send.address + offset,
header.data(),
sizeof(header)))
{
break;
}
offset += headerSize;
const uint32_t argumentBytes =
static_cast<uint32_t>(header[1]) * sizeof(uint16_t);
if (argumentBytes > request.send.size - offset)
{
break;
}
if (header[0] == kPlayStreamCommand && header[1] >= 2u)
{
uint16_t encodedSlot = 0u;
if (m_host.readGuest(request.send.address + offset + sizeof(uint16_t),
&encodedSlot,
sizeof(encodedSlot)))
{
const uint32_t slot =
(encodedSlot >> kCommandStreamSlotShift) & kStreamSlotMask;
if (slot < kStreamSlotCount &&
std::find(slots.begin(), slots.end(), slot) == slots.end())
{
slots.push_back(slot);
}
}
}
offset += argumentBytes;
}
return slots;
}
void trimToReceiveCapacity(std::vector<uint32_t> &slots, uint32_t receiveSize) const
{
size_t count = 0u;
for (; count < slots.size(); ++count)
{
const uint64_t recordOffset =
static_cast<uint64_t>(m_bindings.activeStreamCountOffset) +
static_cast<uint64_t>(count) * kResponseRecordStride +
kPackedStreamOffset;
const uint64_t counterOffset =
static_cast<uint64_t>(m_bindings.responseCounterOffset) +
static_cast<uint64_t>(count + 1u) * kResponseRecordStride;
if (recordOffset + sizeof(uint32_t) > receiveSize ||
counterOffset + sizeof(uint32_t) > receiveSize)
{
break;
}
}
slots.resize(count);
}
IopHost &m_host;
SoundUpdateStubBindings m_bindings;
std::array<uint32_t, 1> m_sids;
mutable std::mutex m_mutex;
uint32_t m_updateCounter = 0u;
uint64_t m_completedStreamCount = 0u;
};
}
+13 -29
View File
@@ -670,37 +670,21 @@ namespace ps2recomp
// VU0_VLDQ = 0x1F // VU0 Load/Store Quad with Decrement
// };
// VU0 Control Register Numbers (used with CFC2/CTC2)
// VU0 COP2 control register numbers used by CFC2/CTC2.
// Registers 0..15 address VI0..VI15 directly.
enum VU0ControlRegisters
{
VU0_CR_STATUS = 0, // Status/Control register
VU0_CR_MAC = 1, // MAC flags register
VU0_CR_CLIP = 5, // Clipping flags register
VU0_CR_R = 3, // R register (Random number)
VU0_CR_I = 4, // I register (Immediate)
// Add missing registers
VU0_CR_VPU_STAT = 2, // VPU-STAT register
VU0_CR_TPC = 6, // T (program counter) register
VU0_CR_CMSAR0 = 7, // Call/return address 0
VU0_CR_FBRST = 8, // VIF/VU reset register
VU0_CR_VPU_STAT2 = 9, // VPU-STAT register 2
VU0_CR_TPC2 = 10, // T (program counter) register 2
VU0_CR_CMSAR1 = 11, // Call/return address 1
VU0_CR_FBRST2 = 12, // VIF/VU reset register 2
VU0_CR_VPU_STAT3 = 13, // VPU-STAT register 3
VU0_CR_CMSAR2 = 14, // Call/return address 2
VU0_CR_FBRST3 = 15, // VIF/VU reset register 3
VU0_CR_VPU_STAT4 = 16, // VPU-STAT register 4
VU0_CR_CMSAR3 = 17, // Call/return address 3
VU0_CR_FBRST4 = 18, // VIF/VU reset register 4
VU0_CR_ACC = 20, // Accumulator register
VU0_CR_INFO = 21, // Information register
VU0_CR_CLIP2 = 22, // Clipping flags register 2
VU0_CR_P = 26, // P register
VU0_CR_XITOP = 27, // XITOP register
VU0_CR_ITOP = 28, // ITOP register
VU0_CR_TOP = 29 // TOP register
VU0_CR_STATUS = 16,
VU0_CR_MAC = 17,
VU0_CR_CLIP = 18,
VU0_CR_R = 20,
VU0_CR_I = 21,
VU0_CR_Q = 22,
VU0_CR_TPC = 26,
VU0_CR_CMSAR0 = 27,
VU0_CR_FBRST = 28,
VU0_CR_VPU_STAT = 29,
VU0_CR_CMSAR1 = 31
};
enum VU0OPSFunctions
{
@@ -34,8 +34,10 @@ namespace ps2recomp
bool recompile();
void generateOutput();
void printReport() const;
const RecompilerReporter::Counters &reportCounters() const { return m_reporter.counters(); }
static StubTarget resolveStubTarget(const std::string& name);
static bool IsCorrectnessCriticalFunctionName(const std::string &name);
static size_t DiscoverAdditionalEntryPoints(
std::vector<Function> &functions,
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
@@ -65,6 +67,7 @@ namespace ps2recomp
std::unordered_set<std::string> m_stubFunctions;
std::unordered_set<uint32_t> m_stubFunctionStarts;
std::unordered_map<uint32_t, std::string> m_stubHandlerBindingsByStart;
std::unordered_set<uint32_t> m_correctnessCriticalFunctionStarts;
std::map<uint32_t, std::string> m_generatedStubs;
std::unordered_map<uint32_t, std::string> m_functionRenames;
std::unordered_map<uint32_t, std::vector<uint32_t>> m_resumeEntryTargetsByOwner;
@@ -74,6 +77,9 @@ namespace ps2recomp
void discoverAdditionalEntryPoints();
bool shouldSkipFunction(const Function &function) const;
bool isStubFunction(const Function &function) const;
bool isCorrectnessCriticalFunction(const Function &function) const;
bool hasResolvedStubHandler(const Function &function) const;
void collectCorrectnessCriticalFunctionStarts();
bool generateFunctionHeader();
bool generateStubHeader();
bool writeToFile(const std::string &path, const std::string &content);
@@ -46,6 +46,8 @@ namespace ps2recomp
size_t unhandledInstructions = 0;
size_t indirectFallbackPromotions = 0;
size_t indirectFallbackEntries = 0;
size_t correctnessCriticalGuestFallbacks = 0;
size_t correctnessCriticalFailures = 0;
};
void progress(const std::string &message);
@@ -63,6 +65,8 @@ namespace ps2recomp
void recordDecodeFailure();
void recordAdditionalEntryPoints(size_t count);
void recordGeneratedFunctions(size_t count);
void recordCorrectnessCriticalGuestFallback();
void recordCorrectnessCriticalFailure();
void recordIndirectFallbackPromotion(const std::string &functionName,
const std::vector<uint32_t> &jumpAddresses,
size_t promotedEntryCount);
+143 -10
View File
@@ -12,6 +12,7 @@
#include <filesystem>
#include <cctype>
#include <condition_variable>
#include <cstring>
#include <exception>
#include <mutex>
#include <queue>
@@ -750,6 +751,7 @@ namespace ps2recomp
m_stubFunctions.clear();
m_stubFunctionStarts.clear();
m_stubHandlerBindingsByStart.clear();
m_correctnessCriticalFunctionStarts.clear();
for (const auto &name : m_config.skipFunctions)
{
@@ -809,6 +811,7 @@ namespace ps2recomp
m_symbols = m_elfParser->extractSymbols();
m_sections = m_elfParser->getSections();
m_relocations = m_elfParser->getRelocations();
collectCorrectnessCriticalFunctionStarts();
if (m_functions.empty())
{
@@ -937,24 +940,81 @@ namespace ps2recomp
size_t processedCount = 0;
size_t failedCount = 0;
size_t correctnessCriticalFailureCount = 0;
for (uint32_t initializerStart : m_correctnessCriticalFunctionStarts)
{
const auto functionIt = std::find_if(
m_functions.begin(), m_functions.end(),
[initializerStart](const Function &function)
{ return function.start == initializerStart; });
if (functionIt == m_functions.end())
{
const auto bindingIt = m_stubHandlerBindingsByStart.find(initializerStart);
if (bindingIt != m_stubHandlerBindingsByStart.end() &&
resolveStubTarget(bindingIt->second) != StubTarget::Unknown)
{
Function manualInitializer{};
manualInitializer.name = "manual_initializer_" + bindingIt->second;
manualInitializer.start = initializerStart;
manualInitializer.end = initializerStart + 4u;
m_functions.push_back(std::move(manualInitializer));
m_reporter.info(
"correctness-critical",
"Synthesized initializer entry for resolved handler '" +
bindingIt->second + "'");
continue;
}
++correctnessCriticalFailureCount;
m_reporter.recordCorrectnessCriticalFailure();
m_reporter.errorAt(
"correctness-critical",
".ctors/.init_array",
initializerStart,
"Initializer table target has no discovered guest function or manual handler");
}
}
for (auto &function : m_functions)
{
m_reporter.recordFunctionProcessed();
const bool correctnessCritical = isCorrectnessCriticalFunction(function);
if (isStubFunction(function))
{
function.isStub = true;
function.isSkipped = false;
m_reporter.recordFunctionStubbed();
continue;
if (!correctnessCritical || hasResolvedStubHandler(function))
{
function.isStub = true;
function.isSkipped = false;
m_reporter.recordFunctionStubbed();
continue;
}
m_reporter.recordCorrectnessCriticalGuestFallback();
m_reporter.warningAt(
"correctness-critical",
function.name,
function.start,
"Unresolved initializer stub ignored; recompiling the original guest function");
}
if (shouldSkipFunction(function))
{
function.isSkipped = true;
function.isStub = false;
m_reporter.recordFunctionSkipped();
continue;
if (!correctnessCritical)
{
function.isSkipped = true;
function.isStub = false;
m_reporter.recordFunctionSkipped();
continue;
}
m_reporter.recordCorrectnessCriticalGuestFallback();
m_reporter.warningAt(
"correctness-critical",
function.name,
function.start,
"Initializer skip ignored; recompiling the original guest function");
}
if (!decodeFunction(function))
@@ -962,11 +1022,26 @@ namespace ps2recomp
++failedCount;
m_reporter.recordDecodeFailure();
m_reporter.recordFunctionSkipped();
m_reporter.warningAt("decode", function.name, function.start, "Skipping function due decode failure");
function.isSkipped = true;
if (correctnessCritical)
{
++correctnessCriticalFailureCount;
m_reporter.recordCorrectnessCriticalFailure();
m_reporter.errorAt(
"correctness-critical",
function.name,
function.start,
"Initializer could not be recompiled and has no resolved manual handler");
}
else
{
m_reporter.warningAt("decode", function.name, function.start, "Skipping function due decode failure");
}
continue;
}
function.isStub = false;
function.isSkipped = false;
function.isRecompiled = true;
m_reporter.recordFunctionRecompiled();
#if _DEBUG
@@ -992,7 +1067,7 @@ namespace ps2recomp
}
m_reporter.progress("recompilation pass completed");
return true;
return correctnessCriticalFailureCount == 0u;
}
catch (const std::exception &e)
{
@@ -1964,6 +2039,64 @@ namespace ps2recomp
return ps2_runtime_calls::isStubName(function.name);
}
bool PS2Recompiler::IsCorrectnessCriticalFunctionName(const std::string &name)
{
static constexpr const char *kPrefixes[] = {
"__ct__",
"__sinit_",
"_GLOBAL__sub_I_",
"GLOBAL__sub_I_",
"__static_initialization_and_destruction_0",
"__do_global_ctors",
};
for (const char *prefix : kPrefixes)
{
if (name.rfind(prefix, 0u) == 0u)
return true;
}
return false;
}
bool PS2Recompiler::isCorrectnessCriticalFunction(const Function &function) const
{
return IsCorrectnessCriticalFunctionName(function.name) ||
m_correctnessCriticalFunctionStarts.contains(function.start);
}
bool PS2Recompiler::hasResolvedStubHandler(const Function &function) const
{
std::string handlerName = function.name;
const auto bindingIt = m_stubHandlerBindingsByStart.find(function.start);
if (bindingIt != m_stubHandlerBindingsByStart.end() && !bindingIt->second.empty())
handlerName = bindingIt->second;
return resolveStubTarget(handlerName) != StubTarget::Unknown;
}
void PS2Recompiler::collectCorrectnessCriticalFunctionStarts()
{
m_correctnessCriticalFunctionStarts.clear();
for (const Section &section : m_sections)
{
if (section.name != ".ctors" &&
section.name != ".init_array" &&
section.name != ".preinit_array")
{
continue;
}
if (section.data == nullptr || section.size < sizeof(uint32_t))
continue;
for (uint32_t offset = 0; offset + sizeof(uint32_t) <= section.size; offset += sizeof(uint32_t))
{
uint32_t target = 0u;
std::memcpy(&target, section.data + offset, sizeof(target));
if (target != 0u && target != 0xFFFFFFFFu)
m_correctnessCriticalFunctionStarts.insert(target);
}
}
}
bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content)
{
std::ofstream file(path);
@@ -113,6 +113,18 @@ namespace ps2recomp
m_counters.generatedFunctions += count;
}
void RecompilerReporter::recordCorrectnessCriticalGuestFallback()
{
std::lock_guard<std::mutex> lock(m_mutex);
++m_counters.correctnessCriticalGuestFallbacks;
}
void RecompilerReporter::recordCorrectnessCriticalFailure()
{
std::lock_guard<std::mutex> lock(m_mutex);
++m_counters.correctnessCriticalFailures;
}
void RecompilerReporter::recordIndirectFallbackPromotion(const std::string &functionName,
const std::vector<uint32_t> &jumpAddresses,
size_t promotedEntryCount)
@@ -183,6 +195,8 @@ namespace ps2recomp
os << "Indirect fallback promotions: " << m_counters.indirectFallbackPromotions
<< " (" << m_counters.indirectFallbackEntries << " fallback entries)" << std::endl;
os << "Unhandled instructions: " << m_counters.unhandledInstructions << std::endl;
os << "Correctness-critical guest fallbacks: " << m_counters.correctnessCriticalGuestFallbacks
<< ", failures: " << m_counters.correctnessCriticalFailures << std::endl;
size_t warnings = 0;
size_t errors = 0;
+18 -22
View File
@@ -209,8 +209,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs, vft, vft, shuffle_pattern,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -230,8 +229,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], {})); "
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs, vft, vft, shuffle_pattern,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -285,8 +283,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs, vft,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -301,8 +298,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -317,8 +313,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
"__m128 res = PS2_VADD(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -402,12 +397,13 @@ namespace ps2recomp
uint8_t vfs = inst.rd;
uint8_t vft = inst.rt;
uint8_t dest_mask = inst.vectorInfo.vectorField;
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
return fmt::format("{{ __m128 fs_yzx = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(3,0,2,1)); "
"__m128 ft_zxy = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(3,1,0,2)); "
"__m128 mul_res = PS2_VMUL(fs_yzx, ft_zxy); "
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
vfs, vft,
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs, vfs, vft, vft,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
vfd, vfd);
@@ -450,8 +446,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); "
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs, vft,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -509,8 +504,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_q)); "
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -525,8 +519,7 @@ namespace ps2recomp
return fmt::format("{{ __m128 mul_res = PS2_VMUL(ctx->vu0_vf[{}], _mm_set1_ps(ctx->vu0_i)); "
"__m128 res = PS2_VSUB(ctx->vu0_acc, mul_res); "
"__m128i mask = _mm_set_epi32({}, {}, {}, {}); "
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); "
"ctx->vu0_acc = res; }}",
"ctx->vu0_vf[{}] = _mm_blendv_ps(ctx->vu0_vf[{}], res, _mm_castsi128_ps(mask)); }}",
vfs,
(dest_mask & 0x1) ? -1 : 0, (dest_mask & 0x2) ? -1 : 0,
(dest_mask & 0x4) ? -1 : 0, (dest_mask & 0x8) ? -1 : 0,
@@ -730,8 +723,11 @@ namespace ps2recomp
uint8_t vfs = inst.rd;
uint8_t vft = inst.rt;
uint8_t dest_mask = inst.vectorInfo.vectorField;
return fmt::format("{{ __m128 res = PS2_VMUL(ctx->vu0_vf[{}], ctx->vu0_vf[{}]); ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
vfs, vft, codegen::vuMaskExpr(dest_mask));
return fmt::format("{{ __m128 fs_yzx = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(3,0,2,1)); "
"__m128 ft_zxy = _mm_shuffle_ps(ctx->vu0_vf[{}], ctx->vu0_vf[{}], _MM_SHUFFLE(3,1,0,2)); "
"__m128 res = PS2_VMUL(fs_yzx, ft_zxy); "
"ctx->vu0_acc = _mm_blendv_ps(ctx->vu0_acc, res, {}); }}",
vfs, vfs, vft, vft, codegen::vuMaskExpr(dest_mask));
}
std::string CodeGenerator::translateVU_VITOF(const Instruction &inst, int shift)
+35 -82
View File
@@ -29,124 +29,77 @@ namespace ps2recomp
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_vf[{}]));", rt, rd);
case COP2_CFC2:
{
switch (rd) // Control register number is in rd
// CFC2/CTC2 use the same 5-bit register field for VI0..VI15 and the VU special control registers.
if (rd < 16)
{
return fmt::format("SET_GPR_U32(ctx, {}, static_cast<uint32_t>(ctx->vi[{}]));", rt, rd);
}
switch (rd)
{
case VU0_CR_STATUS:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_status);", rt);
case VU0_CR_MAC:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_mac_flags);", rt);
case VU0_CR_VPU_STAT:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat);", rt);
case VU0_CR_CLIP:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags & 0x00FFFFFFu);", rt);
case VU0_CR_R:
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_r));", rt);
return fmt::format("SET_GPR_U32(ctx, {}, static_cast<uint32_t>(_mm_cvtsi128_si32(_mm_castps_si128(ctx->vu0_r))));", rt);
case VU0_CR_I:
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_i, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt);
case VU0_CR_CLIP:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags);", rt);
case VU0_CR_Q:
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_q, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt);
case VU0_CR_TPC:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_tpc);", rt);
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_tpc >> 3);", rt);
case VU0_CR_CMSAR0:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar0);", rt);
case VU0_CR_FBRST:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst);", rt);
case VU0_CR_VPU_STAT2:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat2);", rt);
case VU0_CR_TPC2:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_tpc2);", rt);
case VU0_CR_VPU_STAT:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat);", rt);
case VU0_CR_CMSAR1:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar1);", rt);
case VU0_CR_FBRST2:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst2);", rt);
case VU0_CR_VPU_STAT3:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat3);", rt);
case VU0_CR_CMSAR2:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar2);", rt);
case VU0_CR_FBRST3:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst3);", rt);
case VU0_CR_VPU_STAT4:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_vpu_stat4);", rt);
case VU0_CR_CMSAR3:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_cmsar3);", rt);
case VU0_CR_FBRST4:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_fbrst4);", rt);
case VU0_CR_ACC:
return fmt::format("SET_GPR_VEC(ctx, {}, _mm_castps_si128(ctx->vu0_acc));", rt);
case VU0_CR_INFO: // I dd found on offical docs but ok
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_info);", rt);
case VU0_CR_CLIP2:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_clip_flags2);", rt);
case VU0_CR_P:
return fmt::format("{{ uint32_t bits; std::memcpy(&bits, &ctx->vu0_p, sizeof(bits)); SET_GPR_U32(ctx, {}, bits); }}", rt);
case VU0_CR_XITOP: // Maybe this does not exist, maybe we handle to vu0_itop
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_xitop);", rt);
case VU0_CR_ITOP:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_itop);", rt);
case VU0_CR_TOP:
return fmt::format("SET_GPR_U32(ctx, {}, ctx->vu0_top);", rt);
default:
return fmt::format("// Unimplemented CFC2 VU CReg: {}", rt);
return fmt::format("// Unimplemented CFC2 VU control register: {}", rd);
}
}
case COP2_QMTC2:
return fmt::format("ctx->vu0_vf[{}] = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rd, rt);
case COP2_CTC2:
{
switch (rd) // Control register number is in rd
if (rd < 16)
{
if (rd == 0)
{
return "// CTC2 write to VI0 ignored";
}
return fmt::format("ctx->vi[{}] = static_cast<uint16_t>(GPR_U32(ctx, {}));", rd, rt);
}
switch (rd)
{
case VU0_CR_STATUS:
return fmt::format("ctx->vu0_status = GPR_U32(ctx, {}) & 0xFFFF;", rt);
return fmt::format("ctx->vu0_status = static_cast<uint16_t>(GPR_U32(ctx, {}) & 0xFFFFu);", rt);
case VU0_CR_MAC:
return fmt::format("ctx->vu0_mac_flags = GPR_U32(ctx, {});", rt);
case VU0_CR_TPC:
case VU0_CR_VPU_STAT:
return fmt::format("ctx->vu0_vpu_stat = GPR_U32(ctx, {});", rt);
return fmt::format("// CTC2 write to read-only VU control register {} ignored", rd);
case VU0_CR_CLIP:
return fmt::format("ctx->vu0_clip_flags = GPR_U32(ctx, {});", rt);
return fmt::format("ctx->vu0_clip_flags = GPR_U32(ctx, {}) & 0x00FFFFFFu;", rt);
case VU0_CR_R:
return fmt::format("ctx->vu0_r = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt);
return fmt::format("ctx->vu0_r = _mm_castsi128_ps(_mm_set1_epi32(static_cast<int32_t>(GPR_U32(ctx, {}))));", rt);
case VU0_CR_I:
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_i, &tmp, sizeof(tmp)); }}", rt);
case VU0_CR_TPC:
return fmt::format("ctx->vu0_tpc = GPR_U32(ctx, {});", rt);
case VU0_CR_Q:
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_q, &tmp, sizeof(tmp)); }}", rt);
case VU0_CR_CMSAR0:
return fmt::format("ctx->vu0_cmsar0 = GPR_U32(ctx, {});", rt);
case VU0_CR_FBRST:
return fmt::format("ctx->vu0_fbrst = GPR_U32(ctx, {});", rt);
case VU0_CR_VPU_STAT2:
return fmt::format("ctx->vu0_vpu_stat2 = GPR_U32(ctx, {});", rt);
case VU0_CR_TPC2:
return fmt::format("ctx->vu0_tpc2 = GPR_U32(ctx, {});", rt);
return fmt::format("ctx->vu0_fbrst = GPR_U32(ctx, {}) & 0x00000C0Cu;", rt);
case VU0_CR_CMSAR1:
return fmt::format("ctx->vu0_cmsar1 = GPR_U32(ctx, {});", rt);
case VU0_CR_FBRST2:
return fmt::format("ctx->vu0_fbrst2 = GPR_U32(ctx, {});", rt);
case VU0_CR_VPU_STAT3:
return fmt::format("ctx->vu0_vpu_stat3 = GPR_U32(ctx, {});", rt);
case VU0_CR_CMSAR2:
return fmt::format("ctx->vu0_cmsar2 = GPR_U32(ctx, {});", rt);
case VU0_CR_FBRST3:
return fmt::format("ctx->vu0_fbrst3 = GPR_U32(ctx, {});", rt);
case VU0_CR_VPU_STAT4:
return fmt::format("ctx->vu0_vpu_stat4 = GPR_U32(ctx, {});", rt);
case VU0_CR_CMSAR3:
return fmt::format("ctx->vu0_cmsar3 = GPR_U32(ctx, {});", rt);
case VU0_CR_FBRST4:
return fmt::format("ctx->vu0_fbrst4 = GPR_U32(ctx, {});", rt);
case VU0_CR_ACC:
return fmt::format("ctx->vu0_acc = _mm_castsi128_ps(GPR_VEC(ctx, {}));", rt);
case VU0_CR_INFO:
return fmt::format("ctx->vu0_info = GPR_U32(ctx, {});", rt);
case VU0_CR_CLIP2:
return fmt::format("ctx->vu0_clip_flags2 = GPR_U32(ctx, {});", rt);
case VU0_CR_P:
return fmt::format("{{ uint32_t tmp = GPR_U32(ctx, {}); std::memcpy(&ctx->vu0_p, &tmp, sizeof(tmp)); }}", rt);
case VU0_CR_XITOP:
return fmt::format("ctx->vu0_xitop = GPR_U32(ctx, {}) & 0x3FF;", rt);
case VU0_CR_ITOP:
return fmt::format("ctx->vu0_itop = GPR_U32(ctx, {}) & 0x3FF;", rt);
case VU0_CR_TOP:
return fmt::format("ctx->vu0_top = GPR_U32(ctx, {}) & 0x3FF;", rt);
default:
return fmt::format("// Unimplemented CTC2 VU CReg: {}", rd);
return fmt::format("// Unimplemented CTC2 VU control register: {}", rd);
}
}
case COP2_BC:
+44 -43
View File
@@ -15,6 +15,7 @@ option(PS2X_ENABLE_AGRESSIVE_LOGS "Enable very verbose/agressive PS2 runtime log
option(PS2X_ENABLE_IOP_RPC_TRACE "Log unhandled IOP/SIF RPC trace suggestions" ON)
option(PS2X_STRICT_RETURN_DIAGNOSTICS "Route generated JR $ra returns through runtime branch diagnostics" OFF)
option(PS2X_SHOW_WINDOWS_CONSOLE "Show a console window for ps2EntryRunner on Windows release builds" ON)
option(PS2X_ENABLE_DEBUG_UI "Build the desktop runtime debug UI" ON)
if(PS2X_ENABLE_SCCACHE)
find_program(PS2X_SCCACHE_PROGRAM sccache)
@@ -82,50 +83,50 @@ else()
)
FetchContent_MakeAvailable(raylib)
if(NOT PS2X_IS_ANDROID)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG "docking"
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(imgui)
if(PS2X_ENABLE_DEBUG_UI AND NOT PS2X_IS_ANDROID)
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG "v1.92.7-docking"
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(imgui)
if(NOT imgui_POPULATED)
FetchContent_Populate(imgui)
if(NOT imgui_POPULATED)
FetchContent_Populate(imgui)
endif()
set(PS2X_IMGUI_SOURCE_DIR "${imgui_SOURCE_DIR}")
add_library(imgui STATIC
"${PS2X_IMGUI_SOURCE_DIR}/imgui.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_draw.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_tables.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_widgets.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_demo.cpp"
)
target_include_directories(imgui PUBLIC "${PS2X_IMGUI_SOURCE_DIR}")
FetchContent_Declare(
rlImGui
GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git
GIT_TAG "Raylib_5_5"
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(rlImGui)
if(NOT rlimgui_POPULATED)
FetchContent_Populate(rlImGui)
endif()
set(PS2X_RLIMGUI_SOURCE_DIR "${rlimgui_SOURCE_DIR}")
add_library(rlImGui STATIC
"${PS2X_RLIMGUI_SOURCE_DIR}/rlImGui.cpp"
)
target_include_directories(rlImGui PUBLIC "${PS2X_RLIMGUI_SOURCE_DIR}")
target_link_libraries(rlImGui PUBLIC raylib imgui)
endif()
set(PS2X_IMGUI_SOURCE_DIR "${imgui_SOURCE_DIR}")
add_library(imgui STATIC
"${PS2X_IMGUI_SOURCE_DIR}/imgui.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_draw.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_tables.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_widgets.cpp"
"${PS2X_IMGUI_SOURCE_DIR}/imgui_demo.cpp"
)
target_include_directories(imgui PUBLIC "${PS2X_IMGUI_SOURCE_DIR}")
FetchContent_Declare(
rlImGui
GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git
GIT_TAG "Raylib_5_5"
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(rlImGui)
if(NOT rlimgui_POPULATED)
FetchContent_Populate(rlImGui)
endif()
set(PS2X_RLIMGUI_SOURCE_DIR "${rlimgui_SOURCE_DIR}")
add_library(rlImGui STATIC
"${PS2X_RLIMGUI_SOURCE_DIR}/rlImGui.cpp"
)
target_include_directories(rlImGui PUBLIC "${PS2X_RLIMGUI_SOURCE_DIR}")
target_link_libraries(rlImGui PUBLIC raylib imgui)
endif() # NOT PS2X_IS_ANDROID
endif()
add_library(ps2_host_backend INTERFACE)
@@ -509,7 +510,7 @@ target_link_libraries(ps2EntryRunner
ps2_runtime
)
if(NOT PS2X_IS_VITA AND NOT PS2X_IS_ANDROID)
if(PS2X_ENABLE_DEBUG_UI AND NOT PS2X_IS_VITA AND NOT PS2X_IS_ANDROID)
target_sources(ps2EntryRunner PRIVATE
src/lib/ps2_debug_panel.cpp
)
+12
View File
@@ -19,6 +19,18 @@
#define AGRESSIVE_LOGS 0
#endif
#define RUNTIME_ERROR(x) \
do \
{ \
std::ostringstream _ps2_runtime_error_stream; \
_ps2_runtime_error_stream << x; \
const std::string _ps2_runtime_error_text = \
_ps2_runtime_error_stream.str(); \
\
std::cerr << _ps2_runtime_error_text; \
ps2_log::append_runtime_log_text(_ps2_runtime_error_text); \
} while (0)
namespace ps2_log
{
struct RuntimeLogEntry
+2 -2
View File
@@ -527,8 +527,8 @@ private:
std::unique_ptr<ps2x::iop::IopSubsystem> m_iopSubsystem;
PS2AudioBackend m_audioBackend;
PSPadBackend m_padBackend;
VU1Interpreter m_vu0;
VU1Interpreter m_vu1;
VU1Interpreter m_vu0{VU1Interpreter::Unit::VU0};
VU1Interpreter m_vu1{VU1Interpreter::Unit::VU1};
R5900Context m_cpuContext;
mutable std::recursive_mutex m_guestExecutionMutex;
mutable std::atomic<uint32_t> m_guestExecutionWaiters{0u};
+6 -2
View File
@@ -399,12 +399,15 @@ private:
GSContext m_ctx[2];
GSPrimReg m_prim{};
GSPrimReg m_primRegister{};
GSPrimReg m_prmodeRegister{};
uint8_t m_curR = 0x80, m_curG = 0x80, m_curB = 0x80, m_curA = 0x80;
float m_curQ = 1.0f;
float m_curS = 0.0f, m_curT = 0.0f;
uint16_t m_curU = 0, m_curV = 0;
uint8_t m_curFog = 0;
uint8_t m_fogR = 0, m_fogG = 0, m_fogB = 0;
bool m_prmodecont = true;
bool m_pabe = false;
@@ -462,8 +465,9 @@ private:
using WriteVramFunc = std::function<void(u8*, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)>;
using ReadVramFunc = std::function<u32(u8*, u32, u32, u32, u32)>;
std::array<ReadVramFunc, 0x3F> m_read_vram_funcs{ };
std::array<WriteVramFunc, 0x3F> m_write_vram_funcs{ };
static constexpr size_t kPsmHandlerCount = 1u << 6u;
std::array<ReadVramFunc, kPsmHandlerCount> m_read_vram_funcs{ };
std::array<WriteVramFunc, kPsmHandlerCount> m_write_vram_funcs{ };
};
inline u32 GS::ReadVram(u32 psm, u32 base, u32 bw, u32 x, u32 y) const
@@ -9,7 +9,7 @@ class GSRasterizer
{
public:
void drawPrimitive(GS *gs);
void writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
void writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog);
uint32_t sampleTexture(GS *gs, float s, float t, float q, uint16_t u, uint16_t v);
uint32_t lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm);
+3
View File
@@ -285,6 +285,7 @@ public:
uint64_t gifCopyCount() const { return m_gifCopyCount.load(std::memory_order_relaxed); }
uint64_t gsWriteCount() const { return m_gsWriteCount.load(std::memory_order_relaxed); }
uint64_t vifWriteCount() const { return m_vifWriteCount.load(std::memory_order_relaxed); }
uint64_t getVU0CodeGeneration() const { return m_vu0CodeGeneration.load(std::memory_order_relaxed); }
uint64_t getVU1CodeGeneration() const { return m_vu1CodeGeneration.load(std::memory_order_relaxed); }
// Read/write memory
@@ -372,6 +373,7 @@ public:
std::atomic<uint64_t> m_gifCopyCount{0};
std::atomic<uint64_t> m_gsWriteCount{0};
std::atomic<uint64_t> m_vifWriteCount{0};
std::atomic<uint64_t> m_vu0CodeGeneration{0};
std::atomic<uint64_t> m_vu1CodeGeneration{0};
// I/O registers
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
@@ -431,6 +433,7 @@ public:
bool isAddressInRegion(uint32_t address, const CodeRegion &region);
void markModified(uint32_t address, uint32_t size);
void markVU0CodeModified() { m_vu0CodeGeneration.fetch_add(1, std::memory_order_relaxed); }
void markVU1CodeModified() { m_vu1CodeGeneration.fetch_add(1, std::memory_order_relaxed); }
bool isScratchpad(uint32_t address) const;
uint8_t *mapVuMemory(uint32_t physAddr, uint32_t size, uint32_t &offset, uint32_t &limit);
+221 -10
View File
@@ -1,8 +1,8 @@
#ifndef PS2_VU1_H
#define PS2_VU1_H
#include <array>
#include <cstdint>
#include <vector>
class GS;
class PS2Memory;
@@ -15,13 +15,20 @@ struct VU1State
float q;
float p;
float i;
uint32_t r;
uint32_t pc;
uint32_t mac;
uint32_t clip;
uint32_t status;
uint64_t cycles;
bool ebit;
uint32_t top; // VIF1 TOP visible to VU1 XTOP
uint32_t itop; // VIF1 ITOP visible to VU1 XITOP
bool haltAfterDelaySlot;
bool dBitEnabled;
bool tBitEnabled;
bool stoppedByD;
bool stoppedByT;
uint32_t top; // VIF TOP visible to XTOP
uint32_t itop; // VIF ITOP visible to XITOP
bool branchPending;
uint32_t branchTarget;
@@ -31,7 +38,13 @@ struct VU1State
class VU1Interpreter
{
public:
VU1Interpreter();
enum class Unit : uint8_t
{
VU0,
VU1
};
explicit VU1Interpreter(Unit unit = Unit::VU1);
void reset();
@@ -50,38 +63,236 @@ public:
const VU1State &state() const { return m_state; }
private:
enum Pipeline : uint8_t
{
PipelineNone = 0,
PipelineFmac,
PipelineLsu,
PipelineFdiv,
PipelineEfu,
PipelineIalu,
PipelineBranch,
PipelineXgkick
};
struct VfAccess
{
uint8_t reg = 0;
uint8_t lanes = 0;
};
struct InstructionUsage
{
std::array<VfAccess, 2> vfRead{};
VfAccess vfWrite{};
uint8_t vfReadCount = 0;
uint16_t viRead = 0;
uint16_t viWrite = 0;
uint8_t accRead = 0;
uint8_t accWrite = 0;
uint8_t latency = 0;
uint8_t vfLatency = 0;
uint8_t viLatency = 0;
Pipeline pipeline = PipelineNone;
bool waitQ = false;
bool waitP = false;
bool readsClip = false;
bool writesClip = false;
bool delaysNextBranchRead = false;
bool reserved = false;
};
struct DecodedInstructionPair
{
uint32_t lower = 0;
uint32_t upper = 0;
InstructionUsage lowerUsage{};
InstructionUsage upperUsage{};
bool iBit = false;
bool eBit = false;
bool lowerBeforeUpper = false;
bool mBit = false;
bool dBit = false;
bool tBit = false;
uint8_t upperVfShadowReg = 0;
uint8_t suppressedLowerVf = 0;
};
struct FlagPipelineEntry
{
uint64_t readyCycle = 0;
uint64_t issueCycle = 0;
uint32_t mac = 0;
uint32_t status = 0;
uint32_t extraSticky = 0;
uint32_t clip = 0;
bool valid = false;
bool writesMac = false;
bool writesStatus = false;
bool writesSticky = false;
bool writesClip = false;
};
struct ScalarPipelineEntry
{
uint64_t readyCycle = 0;
float value = 0.0f;
uint32_t statusDi = 0;
bool valid = false;
};
struct PendingStore
{
uint64_t readyCycle = 0;
uint32_t address = 0;
std::array<uint32_t, 4> words{};
uint8_t laneMask = 0;
bool valid = false;
};
struct PendingVfWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
std::array<float, 4> value{};
uint8_t reg = 0;
uint8_t laneMask = 0;
bool valid = false;
};
struct PendingViWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
int32_t value = 0;
uint8_t reg = 0;
bool valid = false;
};
struct PendingAccWrite
{
uint64_t readyCycle = 0;
uint64_t sequence = 0;
std::array<float, 4> value{};
uint8_t laneMask = 0;
bool valid = false;
};
struct XgkickPipeline
{
static constexpr uint32_t kBufferSize = 0x10000u;
std::array<uint8_t, kBufferSize> packet{};
uint32_t sourceAddress = 0;
uint32_t totalBytes = 0;
uint32_t copiedBytes = 0;
uint32_t currentTagEnd = 0;
uint32_t cycleCredit = 0;
uint64_t issueCycle = 0;
bool active = false;
bool currentTagEop = false;
};
static constexpr uint32_t kFmacLatency = 4u;
static constexpr uint32_t kAccForwardLatency = 1u;
static constexpr uint32_t kMaxFlagEntries = 8u;
static constexpr uint32_t kMaxPendingStores = 8u;
static constexpr uint32_t kMaxPendingVfWrites = 16u;
static constexpr uint32_t kMaxPendingViWrites = 8u;
static constexpr uint32_t kMaxPendingAccWrites = 8u;
static constexpr uint32_t kMaxDecodedPairs = 0x4000u / 8u;
Unit m_unit;
VU1State m_state;
std::vector<DecodedInstructionPair> m_decodedCodeCache;
std::array<DecodedInstructionPair, kMaxDecodedPairs> m_decodedCodeCache{};
const uint8_t *m_cachedVuCode = nullptr;
const PS2Memory *m_cachedMemory = nullptr;
uint32_t m_cachedCodeSize = 0;
uint64_t m_cachedCodeGeneration = 0;
bool m_decodedCodeCacheValid = false;
std::array<FlagPipelineEntry, kMaxFlagEntries> m_flagPipeline{};
ScalarPipelineEntry m_fdiv{};
std::array<ScalarPipelineEntry, 2> m_efu{};
std::array<PendingStore, kMaxPendingStores> m_storePipeline{};
std::array<PendingVfWrite, kMaxPendingVfWrites> m_vfWritePipeline{};
std::array<PendingViWrite, kMaxPendingViWrites> m_viWritePipeline{};
std::array<PendingAccWrite, kMaxPendingAccWrites> m_accWritePipeline{};
XgkickPipeline m_xgkick{};
std::array<std::array<uint64_t, 4>, 32> m_vfReady{};
std::array<uint64_t, 16> m_viReady{};
std::array<uint64_t, 4> m_accReady{};
std::array<std::array<uint64_t, 4>, 32> m_vfLatestWrite{};
std::array<uint64_t, 16> m_viLatestWrite{};
std::array<uint64_t, 4> m_accLatestWrite{};
uint64_t m_cycle = 0;
uint64_t m_nextWriteSequence = 0;
uint64_t m_efuResourceReady = 0;
uint32_t m_workingClip = 0;
uint32_t m_currentUpperInstruction = 0;
int32_t m_viBranchBackupValue = 0;
uint8_t m_viBranchBackupReg = 0;
bool m_viBranchBackupValid = false;
uint8_t *m_activeVuData = nullptr;
uint32_t m_activeVuDataSize = 0;
GS *m_activeGs = nullptr;
PS2Memory *m_activeMemory = nullptr;
bool m_stopRequested = false;
bool m_pendingHaltD = false;
bool m_pendingHaltT = false;
void run(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory, uint32_t maxCycles);
InstructionUsage decodeUpperUsage(uint32_t upper) const;
InstructionUsage decodeLowerUsage(uint32_t lower) const;
static void addVfRead(InstructionUsage &usage, uint8_t reg, uint8_t lanes);
static void addVfWrite(InstructionUsage &usage, uint8_t reg, uint8_t lanes);
static uint8_t vfReadLanes(const InstructionUsage &usage, uint8_t reg);
DecodedInstructionPair decodeInstructionPair(const uint8_t *vuCode, uint32_t pc) const;
DecodedInstructionPair getDecodedInstructionPairForPc(const uint8_t *vuCode, uint32_t codeSize,
PS2Memory *memory, uint32_t pc);
void rebuildDecodedCodeCache(const uint8_t *vuCode, uint32_t codeSize,
const PS2Memory *memory, uint64_t generation);
DecodedInstructionPair getDecodedInstructionPairForPc(const uint8_t *vuCode, uint32_t codeSize, PS2Memory *memory, uint32_t pc);
void rebuildDecodedCodeCache(const uint8_t *vuCode, uint32_t codeSize, const PS2Memory *memory, uint64_t generation);
void execUpper(uint32_t instr);
void execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSize, GS &gs, PS2Memory *memory, uint32_t upperInstr);
void applyDest(float *dst, const float *result, uint8_t dest);
void applyDestAcc(const float *result, uint8_t dest);
void applyFmacDest(float *dst, float *result, uint8_t dest);
void applyFmacDestAcc(float *result, uint8_t dest);
void normalizeFmacResult(float *result, uint8_t dest, uint8_t laneFlags[4]);
bool calculateFmacExactResult(uint32_t component, long double &result) const;
uint8_t normalizeFmacExactResult(float &value, long double exactResult) const;
uint32_t calculateFmacProductSticky(uint8_t dest) const;
void updateFmacFlags(const uint8_t laneFlags[4], uint8_t dest, uint32_t extraSticky);
void queueFsset(uint16_t immediate);
void queueClip(uint32_t clip);
void queueFcset(uint32_t clip);
void queueQ(float value, uint32_t latency, uint32_t statusDi);
void queueP(float value, uint32_t latency);
void queueStore(uint32_t address, const uint32_t words[4], uint8_t laneMask);
void queueVfWrite(uint8_t reg, uint8_t laneMask, const float value[4], uint32_t latency);
void queueViWrite(uint8_t reg, int32_t value, uint32_t latency);
void queueAccWrite(uint8_t laneMask, const float value[4], uint32_t latency);
void startXgkick(uint32_t qwordAddress);
void resetScheduler();
void commitReadyPipelines();
void advanceOneCycle();
void advanceTo(uint64_t targetCycle);
void flushPipelines();
void progressXgkick();
void finishXgkick();
uint64_t calculatePairReadyCycle(const DecodedInstructionPair &decoded) const;
void markPairWrites(const DecodedInstructionPair &decoded);
bool pipelinesPending() const;
float normalizeOperand(float value) const;
float normalizeResult(float value, uint32_t &laneFlags) const;
uint32_t microAddressMask() const;
int32_t readBranchVi(uint8_t reg) const;
void recordViWriteForBranch(uint8_t reg, int32_t oldValue);
void reportReservedInstruction(bool upper, uint32_t instruction);
float broadcast(const float *vf, uint8_t bc);
};
+58 -32
View File
@@ -23,6 +23,21 @@ namespace
static constexpr uint32_t kHostFrameWidth = 640u;
static constexpr uint32_t kHostFrameHeight = 512u;
GSPrimReg decodePrimRegister(uint64_t value)
{
GSPrimReg prim{};
prim.type = static_cast<GSPrimType>(value & 0x7u);
prim.iip = ((value >> 3) & 1u) != 0u;
prim.tme = ((value >> 4) & 1u) != 0u;
prim.fge = ((value >> 5) & 1u) != 0u;
prim.abe = ((value >> 6) & 1u) != 0u;
prim.aa1 = ((value >> 7) & 1u) != 0u;
prim.fst = ((value >> 8) & 1u) != 0u;
prim.ctxt = ((value >> 9) & 1u) != 0u;
prim.fix = ((value >> 10) & 1u) != 0u;
return prim;
}
uint16_t encodeFramePixelPSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return static_cast<uint16_t>(((r >> 3) & 0x1Fu) |
@@ -109,7 +124,8 @@ namespace
bool validatePackedGifPacket(const uint8_t *data, uint32_t sizeBytes)
{
return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &) { return true; });
return visitPackedGifPacket(data, sizeBytes, [](const PackedGifPacketTag &)
{ return true; });
}
void decodeDisplaySize(uint64_t display64, uint32_t &outWidth, uint32_t &outHeight)
@@ -265,7 +281,7 @@ namespace
return count;
}
bool clearFramebufferRect(GS* gs, const GSContext &ctx, uint32_t rgba)
bool clearFramebufferRect(GS *gs, const GSContext &ctx, uint32_t rgba)
{
if (ctx.frame.fbw == 0u)
{
@@ -365,7 +381,7 @@ GS::GS()
InitLookupTables();
for (usz i = 0; i < 0x3F; ++i)
for (usz i = 0; i < m_read_vram_funcs.size(); ++i)
{
switch (i)
{
@@ -444,6 +460,8 @@ void GS::reset()
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
std::memset(m_ctx, 0, sizeof(m_ctx));
m_prim = {};
m_primRegister = {};
m_prmodeRegister = {};
m_curR = 0x80;
m_curG = 0x80;
m_curB = 0x80;
@@ -454,6 +472,9 @@ void GS::reset()
m_curU = 0;
m_curV = 0;
m_curFog = 0;
m_fogR = 0;
m_fogG = 0;
m_fogB = 0;
m_prmodecont = true;
m_pabe = false;
m_texa = {0u, false, 0u};
@@ -553,7 +574,6 @@ GSDebugSnapshot GS::getDebugSnapshot() const
return snapshot;
}
std::vector<GSDebugHistoryEntry> GS::getDebugHistory() const
{
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
@@ -1315,7 +1335,6 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
}
});
uint32_t offset = 0;
while (offset + 16 <= sizeBytes)
{
@@ -1394,7 +1413,7 @@ bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t 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);
@@ -1415,8 +1434,7 @@ bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes)
}
}
return true;
});
return true; });
if (!processed)
return false;
@@ -1775,15 +1793,17 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
{
case GS_REG_PRIM:
{
m_prim.type = static_cast<GSPrimType>(value & 0x7);
m_prim.iip = ((value >> 3) & 1) != 0;
m_prim.tme = ((value >> 4) & 1) != 0;
m_prim.fge = ((value >> 5) & 1) != 0;
m_prim.abe = ((value >> 6) & 1) != 0;
m_prim.aa1 = ((value >> 7) & 1) != 0;
m_prim.fst = ((value >> 8) & 1) != 0;
m_prim.ctxt = ((value >> 9) & 1) != 0;
m_prim.fix = ((value >> 10) & 1) != 0;
m_primRegister = decodePrimRegister(value);
if (m_prmodecont)
{
m_prim = m_primRegister;
}
else
{
// PRIM always selects the primitive topology. With AC=0, all
// rendering attributes remain sourced from PRMODE.
m_prim.type = m_primRegister.type;
}
m_vtxCount = 0;
m_vtxIndex = 0;
break;
@@ -1912,21 +1932,24 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
break;
}
case GS_REG_PRMODECONT:
{
m_prmodecont = (value & 1) != 0;
const GSPrimType type = m_primRegister.type;
m_prim = m_prmodecont ? m_primRegister : m_prmodeRegister;
m_prim.type = type;
break;
}
case GS_REG_PRMODE:
{
m_prmodeRegister = decodePrimRegister(value);
if (!m_prmodecont)
{
m_prim.iip = ((value >> 3) & 1) != 0;
m_prim.tme = ((value >> 4) & 1) != 0;
m_prim.fge = ((value >> 5) & 1) != 0;
m_prim.abe = ((value >> 6) & 1) != 0;
m_prim.aa1 = ((value >> 7) & 1) != 0;
m_prim.fst = ((value >> 8) & 1) != 0;
m_prim.ctxt = ((value >> 9) & 1) != 0;
m_prim.fix = ((value >> 10) & 1) != 0;
const GSPrimType type = m_primRegister.type;
m_prim = m_prmodeRegister;
m_prim.type = type;
}
break;
}
case GS_REG_TEXCLUT:
m_texclut.cbw = static_cast<uint8_t>(value & 0x3Fu);
m_texclut.cou = static_cast<uint8_t>((value >> 6) & 0x3Fu);
@@ -2041,9 +2064,13 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
case GS_REG_PABE:
m_pabe = (value & 1u) != 0u;
break;
case GS_REG_FOGCOL:
m_fogR = static_cast<uint8_t>(value & 0xFFu);
m_fogG = static_cast<uint8_t>((value >> 8) & 0xFFu);
m_fogB = static_cast<uint8_t>((value >> 16) & 0xFFu);
break;
case GS_REG_TEXFLUSH:
case GS_REG_SCANMSK:
case GS_REG_FOGCOL:
case GS_REG_DIMX:
case GS_REG_DTHE:
case GS_REG_COLCLAMP:
@@ -2180,7 +2207,6 @@ void GS::performLocalToLocalTransfer()
}
break;
// left -> right
// bottom -> top (invert y)
case 1:
@@ -2271,9 +2297,6 @@ void GS::vertexKick(bool drawing)
}
});
if (!drawing)
return;
int needed = 0;
switch (m_prim.type)
{
@@ -2305,8 +2328,11 @@ void GS::vertexKick(bool drawing)
if (m_vtxCount < needed)
return;
m_rasterizer.drawPrimitive(this);
recordDrawDebugEventUnlocked(needed);
if (drawing)
{
m_rasterizer.drawPrimitive(this);
recordDrawDebugEventUnlocked(needed);
}
switch (m_prim.type)
{
+236 -143
View File
@@ -14,7 +14,6 @@
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace GSInternal;
@@ -27,8 +26,8 @@ namespace
u16 Rgba8888ToRgba5551(u32 c)
{
uint32_t r = ((c >> 0) & 0xFF) >> 3;
uint32_t g = ((c >> 8) & 0xFF) >> 3;
uint32_t r = ((c >> 0) & 0xFF) >> 3;
uint32_t g = ((c >> 8) & 0xFF) >> 3;
uint32_t b = ((c >> 16) & 0xFF) >> 3;
uint32_t a = ((c >> 24) & 0xFF) >> 7;
@@ -37,8 +36,8 @@ namespace
u32 Rgba5551ToRgba8888(u16 c)
{
u32 r = ((c >> 0) & 0x1F) << 3;
u32 g = ((c >> 5) & 0x1F) << 3;
u32 r = ((c >> 0) & 0x1F) << 3;
u32 g = ((c >> 5) & 0x1F) << 3;
u32 b = ((c >> 10) & 0x1F) << 3;
u32 a = ((c >> 15) & 0x01) << 7;
@@ -101,6 +100,28 @@ namespace
std::atomic<uint32_t> s_debugPixelCount{0};
std::atomic<uint32_t> s_debugContext1PrimitiveCount{0};
std::atomic<uint32_t> s_debugFbp150PixelCount{0};
int wrapTextureCoordinate(int coordinate,
int textureSize,
uint8_t mode,
uint16_t regionMin,
uint16_t regionMax)
{
switch (mode & 0x3u)
{
case 0: // REPEAT
return static_cast<int>(static_cast<uint32_t>(coordinate) & static_cast<uint32_t>(textureSize - 1));
case 1: // CLAMP
return clampInt(coordinate, 0, textureSize - 1);
case 2: // REGION_CLAMP
return std::min(std::max(coordinate, static_cast<int>(regionMin)), static_cast<int>(regionMax));
case 3: // REGION_REPEAT
return static_cast<int>((static_cast<uint32_t>(coordinate) & static_cast<uint32_t>(regionMin)) | static_cast<uint32_t>(regionMax));
default:
return coordinate;
}
}
bool passesAlphaTest(uint64_t testReg, uint8_t alpha)
{
if ((testReg & 0x1u) == 0u)
@@ -132,29 +153,67 @@ namespace
}
}
struct AlphaTestResult
struct PixelWriteMask
{
bool writeFramebuffer;
bool preserveDestinationAlpha;
bool writeRgb = true;
bool writeAlpha = true;
bool writeDepth = true;
bool writesFramebuffer() const
{
return writeRgb || writeAlpha;
}
bool writesAnything() const
{
return writesFramebuffer() || writeDepth;
}
};
AlphaTestResult classifyAlphaTest(uint64_t testReg, uint8_t alpha)
PixelWriteMask classifyAlphaTest(uint64_t testReg, uint8_t alpha, uint8_t framePsm)
{
const bool pass = passesAlphaTest(testReg, alpha);
if (pass)
return {true, false};
return {};
// TEST.AFAIL controls what happens when the alpha comparison fails.
switch (static_cast<uint8_t>((testReg >> 12) & 0x3u))
{
case 1: // FB_ONLY
return {true, false};
case 3: // RGB_ONLY
return {true, true};
case 0: // KEEP
return {true, true, false};
case 2: // ZB_ONLY
return {false, false, true};
case 3: // RGB_ONLY
// RGB_ONLY is only distinct for RGBA32. The GS treats it as
// FB_ONLY for RGB24 and RGBA16 framebuffers.
if (framePsm == GS_PSM_CT32)
return {true, false, false};
return {true, true, false};
case 0: // KEEP
default:
return {false, false};
return {false, false, false};
}
}
bool passesDestinationAlphaTest(uint64_t testReg, uint8_t framePsm, uint32_t rawFramebufferPixel)
{
const bool date = ((testReg >> 14) & 0x1u) != 0u;
if (!date)
return true;
const bool datm = ((testReg >> 15) & 0x1u) != 0u;
switch (framePsm)
{
case GS_PSM_CT32:
return (((rawFramebufferPixel >> 31) & 0x1u) != 0u) == datm;
case GS_PSM_CT16:
case GS_PSM_CT16S:
return (((rawFramebufferPixel >> 15) & 0x1u) != 0u) == datm;
case GS_PSM_CT24:
// RGB24 has no destination alpha, so DATE always passes.
return true;
default:
return true;
}
}
@@ -218,36 +277,52 @@ namespace
uint32_t swizzleClutIndexCSM1(uint32_t index)
{
return (index & 0xE7u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u);
// CSM1 swaps address bits 3 and 4. Preserve the remaining bits:
// 16-bit CLUTs expose a ninth address bit through CSA[4].
return (index & ~0x18u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u);
}
// TODO: clut cache
uint32_t resolveClutIndex(uint8_t index, uint8_t csm, uint8_t csa, uint8_t sourcePsm)
uint32_t resolveClutIndex(uint8_t index, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm)
{
uint32_t clutIndex = static_cast<uint32_t>(index);
// CSM2 addresses the source directly through TEXCLUT. CSA is required
// to be zero there, so it must not offset the source coordinates.
if (csm != 0u)
return (sourcePsm == GS_PSM_T4 ||
sourcePsm == GS_PSM_T4HH ||
sourcePsm == GS_PSM_T4HL)
? (clutIndex & 0x0Fu)
: clutIndex;
const bool is16BitClut = cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S;
const uint32_t csaMask = is16BitClut ? 0x1Fu : 0x0Fu;
const uint32_t clutIndexMask = is16BitClut ? 0x1FFu : 0x0FFu;
const uint32_t clutBase = (static_cast<uint32_t>(csa) & csaMask) << 4u;
switch (sourcePsm)
{
case GS_PSM_T4:
case GS_PSM_T4HH:
case GS_PSM_T4HL:
{
clutIndex = (static_cast<uint32_t>(csa) << 4u) | (clutIndex & 0x0Fu);
if (csm == 0u)
clutIndex = swizzleClutIndexCSM1(clutIndex);
}
break;
clutIndex = clutBase + (clutIndex & 0x0Fu);
break;
case GS_PSM_T8:
case GS_PSM_T8H:
if (csm == 0)
clutIndex = swizzleClutIndexCSM1(clutIndex);
clutIndex = clutBase + clutIndex;
break;
default:
break;
return clutIndex;
}
return clutIndex;
return swizzleClutIndexCSM1(clutIndex & clutIndexMask);
}
int textureDimension(uint8_t exponent)
{
// TEX0.TW/TH saturate at 1024 pixels on the GS.
return 1 << std::min<uint32_t>(exponent, 10u);
}
bool tex1UsesLinearFilter(uint64_t tex1)
@@ -399,7 +474,7 @@ void GSRasterizer::drawPrimitive(GS *gs)
const auto &ctx = gs->activeContext();
int px = static_cast<int>(v.x) - (ctx.xyoffset.ofx >> 4);
int py = static_cast<int>(v.y) - (ctx.xyoffset.ofy >> 4);
writePixel(gs, px, py, static_cast<u32>(v.z), v.r, v.g, v.b, v.a);
writePixel(gs, px, py, static_cast<u32>(v.z), v.r, v.g, v.b, v.a, v.fog);
break;
}
default:
@@ -407,51 +482,72 @@ void GSRasterizer::drawPrimitive(GS *gs)
}
}
void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog)
{
const auto &ctx = gs->activeContext();
if (x < ctx.scissor.x0 || x > ctx.scissor.x1 ||
y < ctx.scissor.y0 || y > ctx.scissor.y1)
if (x < ctx.scissor.x0 || x > ctx.scissor.x1 || y < ctx.scissor.y0 || y > ctx.scissor.y1)
return;
const AlphaTestResult alphaTest = classifyAlphaTest(ctx.test, a);
if (gs->m_prim.fge)
{
const uint32_t inverseFog = 255u - fog;
auto applyFog = [&](uint8_t input, uint8_t fogColor) -> uint8_t
{
return static_cast<uint8_t>(((static_cast<uint32_t>(fog) * input) >> 8) + ((inverseFog * fogColor) >> 8));
};
if (!alphaTest.writeFramebuffer)
return;
r = applyFog(r, gs->m_fogR);
g = applyFog(g, gs->m_fogG);
b = applyFog(b, gs->m_fogB);
}
u8* vram = gs->m_vram;
const u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp);
const u32 fbw = std::max<u32>(ctx.frame.fbw, 1u);
const u32 fbp = GSInternal::framePageBaseToBlock(ctx.frame.fbp);
const u32 fbw = std::max<u32>(ctx.frame.fbw, 1u);
const u32 fpsm = ctx.frame.psm;
const u32 fmsk = ctx.frame.fbmsk;
const u32 zbp = GSInternal::framePageBaseToBlock(ctx.zbuf.zbp);
const u32 zpsm = ctx.zbuf.psm;
const PixelWriteMask writeMask = classifyAlphaTest(ctx.test, a, static_cast<uint8_t>(fpsm));
if (!writeMask.writesAnything())
{
return;
}
const uint32_t ztestMethod = static_cast<uint32_t>((ctx.test >> 17) & 3u);
const bool alphaBlendEnabled = gs->m_prim.abe;
const bool destinationAlpha = alphaTest.preserveDestinationAlpha;
const bool preserveDestinationAlpha = writeMask.writeRgb && !writeMask.writeAlpha && fpsm == GS_PSM_CT32;
const bool destinationAlphaTestNeedsRead = ((ctx.test >> 14) & 0x1u) != 0u && (fpsm == GS_PSM_CT32 || fpsm == GS_PSM_CT16 || fpsm == GS_PSM_CT16S);
// small optimization, avoid reading the framebuffer for simple draws
// TODO: only one address lookup for rmw
const bool frmw = (ctx.frame.fbmsk != 0) || alphaBlendEnabled || destinationAlpha;
const bool frmw = destinationAlphaTestNeedsRead || (writeMask.writesFramebuffer() && ((ctx.frame.fbmsk != 0) || alphaBlendEnabled || preserveDestinationAlpha));
u32 rawFramebufferPixel = 0;
u32 fbrgba = 0;
if (frmw)
{
fbrgba = gs->ReadVram(fpsm, fbp, fbw, x, y);
rawFramebufferPixel = gs->ReadVram(fpsm, fbp, fbw, x, y);
fbrgba = rawFramebufferPixel;
if (bitsPerPixel(fpsm) == 16)
{
fbrgba = Rgba5551ToRgba8888(fbrgba);
}
else if (fpsm == GS_PSM_CT24)
{
// The GS supplies 0x80 as destination alpha for RGB24 blending.
fbrgba |= 0x80000000u;
}
}
uint ztest_method = (ctx.test >> 17) & 3;
if (!passesDestinationAlphaTest(ctx.test, static_cast<uint8_t>(fpsm), rawFramebufferPixel))
{
return;
}
bool zpass = false;
switch (ztest_method)
uint32_t storedZ = 0u;
switch (ztestMethod)
{
case 0:
zpass = false;
@@ -460,10 +556,12 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g,
zpass = true;
break;
case 2:
zpass = z >= gs->ReadVram(zpsm, zbp, fbw, x, y);
storedZ = gs->ReadVram(zpsm, zbp, fbw, x, y);
zpass = static_cast<uint32_t>(z) >= storedZ;
break;
case 3:
zpass = z > gs->ReadVram(zpsm, zbp, fbw, x, y);
storedZ = gs->ReadVram(zpsm, zbp, fbw, x, y);
zpass = static_cast<uint32_t>(z) > storedZ;
break;
}
@@ -472,81 +570,79 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, int z, uint8_t r, uint8_t g,
return;
}
const u8 srcR = r;
const u8 srcG = g;
const u8 srcB = b;
if (gs->m_prim.abe)
if (writeMask.writesFramebuffer())
{
uint8_t dr = fbrgba & 0xFF;
uint8_t dg = (fbrgba >> 8) & 0xFF;
uint8_t db = (fbrgba >> 16) & 0xFF;
uint8_t da = (fbrgba >> 24) & 0xFF;
const u8 srcR = r;
const u8 srcG = g;
const u8 srcB = b;
// PABE disables alpha blending when the source alpha MSB is clear.
if (!(gs->m_pabe && (a & 0x80u) == 0u))
if (gs->m_prim.abe)
{
uint64_t alphaReg = ctx.alpha;
uint8_t asel = alphaReg & 3;
uint8_t bsel = (alphaReg >> 2) & 3;
uint8_t csel = (alphaReg >> 4) & 3;
uint8_t dsel = (alphaReg >> 6) & 3;
uint8_t fix = static_cast<uint8_t>((alphaReg >> 32) & 0xFF);
uint8_t dr = fbrgba & 0xFF;
uint8_t dg = (fbrgba >> 8) & 0xFF;
uint8_t db = (fbrgba >> 16) & 0xFF;
uint8_t da = (fbrgba >> 24) & 0xFF;
auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int
// PABE disables alpha blending when the source alpha MSB is clear.
if (!(gs->m_pabe && (a & 0x80u) == 0u))
{
if (sel == 0)
return cs;
if (sel == 1)
return cd;
return 0;
};
int cAlpha = (csel == 0) ? a : (csel == 1) ? da
: fix;
uint64_t alphaReg = ctx.alpha;
uint8_t asel = alphaReg & 3;
uint8_t bsel = (alphaReg >> 2) & 3;
uint8_t csel = (alphaReg >> 4) & 3;
uint8_t dsel = (alphaReg >> 6) & 3;
uint8_t fix = static_cast<uint8_t>((alphaReg >> 32) & 0xFF);
r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr));
g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg));
b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db));
auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int
{
if (sel == 0)
return cs;
if (sel == 1)
return cd;
return 0;
};
int cAlpha = (csel == 0) ? a : (csel == 1) ? da
: fix;
r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr));
g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg));
b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db));
}
else
{
r = srcR;
g = srcG;
b = srcB;
}
}
else
if (writeMask.writeAlpha && (ctx.fba & 0x1ull) != 0ull && ctx.frame.psm != GS_PSM_CT24)
{
r = srcR;
g = srcG;
b = srcB;
a = static_cast<uint8_t>(a | 0x80u);
}
u32 pixel = pack32(r, g, b, a);
if (ctx.frame.fbmsk != 0)
{
pixel = (pixel & ~ctx.frame.fbmsk) | (fbrgba & ctx.frame.fbmsk);
}
if (preserveDestinationAlpha)
{
pixel = (pixel & 0x00FFFFFFu) | (fbrgba & 0xFF000000u);
}
// format conversion
if (bitsPerPixel(fpsm) == 16)
{
pixel = Rgba8888ToRgba5551(pixel);
}
gs->WriteVram(fpsm, fbp, fbw, x, y, pixel);
}
u32 fbmask = ctx.frame.fbmsk;
bool zmask = ctx.zbuf.zmask;
if (!alphaTest.preserveDestinationAlpha &&
(ctx.fba & 0x1ull) != 0ull &&
ctx.frame.psm != GS_PSM_CT24)
{
a = static_cast<uint8_t>(a | 0x80u);
}
u32 pixel = pack32(r, g, b, a);
if (fbmask != 0)
{
pixel = (pixel & ~fbmask) | (fbrgba & fbmask);
}
if (alphaTest.preserveDestinationAlpha)
{
pixel = (pixel & 0x00FFFFFFu) | (fbrgba & 0xFF000000u);
}
// format conversion
if (bitsPerPixel(fpsm) == 16)
{
pixel = Rgba8888ToRgba5551(pixel);
}
gs->WriteVram(fpsm, fbp, fbw, x, y, pixel);
if (!zmask)
if (writeMask.writeDepth && !ctx.zbuf.zmask)
{
gs->WriteVram(zpsm, zbp, fbw, x, y, z);
}
@@ -560,12 +656,11 @@ uint32_t GSRasterizer::lookupCLUT(GS *gs,
uint8_t csa,
uint8_t sourcePsm)
{
const uint32_t clutIndex = resolveClutIndex(index, csm, csa, sourcePsm);
const uint32_t clutIndex = resolveClutIndex(index, cpsm, csm, csa, sourcePsm);
const uint32_t clutWidth = (gs->m_texclut.cbw != 0u) ? static_cast<uint32_t>(gs->m_texclut.cbw) : 1u;
const uint32_t clutX = static_cast<uint32_t>(gs->m_texclut.cou) + (clutIndex & 0x0Fu);
const uint32_t clutY = static_cast<uint32_t>(gs->m_texclut.cov) + (clutIndex >> 4);
switch (cpsm)
{
case GS_PSM_CT32:
@@ -588,8 +683,15 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, float q, uint16_t
const auto &ctx = gs->activeContext();
const auto &tex = ctx.tex0;
int texW = 1 << tex.tw;
int texH = 1 << tex.th;
const int texW = textureDimension(tex.tw);
const int texH = textureDimension(tex.th);
const uint64_t clamp = ctx.clamp;
const uint8_t wrapU = static_cast<uint8_t>(clamp & 0x3u);
const uint8_t wrapV = static_cast<uint8_t>((clamp >> 2) & 0x3u);
const uint16_t minU = static_cast<uint16_t>((clamp >> 4) & 0x3FFu);
const uint16_t maxU = static_cast<uint16_t>((clamp >> 14) & 0x3FFu);
const uint16_t minV = static_cast<uint16_t>((clamp >> 24) & 0x3FFu);
const uint16_t maxV = static_cast<uint16_t>((clamp >> 34) & 0x3FFu);
float texUf, texVf;
if (gs->m_prim.fst)
@@ -606,8 +708,8 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, float q, uint16_t
auto samplePoint = [&](int sampleU, int sampleV) -> uint32_t
{
sampleU = clampInt(sampleU, 0, texW - 1);
sampleV = clampInt(sampleV, 0, texH - 1);
sampleU = wrapTextureCoordinate(sampleU, texW, wrapU, minU, maxU);
sampleV = wrapTextureCoordinate(sampleV, texH, wrapV, minV, maxV);
u32 out = gs->ReadVram(tex.psm, tex.tbp0, tex.tbw, sampleU, sampleV);
@@ -747,12 +849,8 @@ void GSRasterizer::drawSprite(GS *gs)
if (gs->m_prim.tme)
{
const auto &tex = ctx.tex0;
int texW = 1 << tex.tw;
int texH = 1 << tex.th;
if (texW == 0)
texW = 1;
if (texH == 0)
texH = 1;
const int texW = textureDimension(tex.tw);
const int texH = textureDimension(tex.th);
float u0f, v0f, u1f, v1f;
if (gs->m_prim.fst)
@@ -799,10 +897,7 @@ void GSRasterizer::drawSprite(GS *gs)
}
else
{
texel = sampleTexture(gs,
texUf / static_cast<float>(texW),
texVf / static_cast<float>(texH),
1.0f, 0u, 0u);
texel = sampleTexture(gs, texUf / static_cast<float>(texW), texVf / static_cast<float>(texH), 1.0f, 0u, 0u);
}
uint8_t tr = static_cast<uint8_t>(texel & 0xFF);
@@ -811,7 +906,7 @@ void GSRasterizer::drawSprite(GS *gs)
uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF);
const TextureCombineResult color = combineTexture(tex, r, g, b, a, tr, tg, tb, ta);
writePixel(gs, x, y, z1, color.r, color.g, color.b, color.a);
writePixel(gs, x, y, z1, color.r, color.g, color.b, color.a, v1.fog);
}
}
}
@@ -819,7 +914,7 @@ void GSRasterizer::drawSprite(GS *gs)
{
for (int y = drawY0; y <= drawY1; ++y)
for (int x = drawX0; x <= drawX1; ++x)
writePixel(gs, x, y, z1, r, g, b, a);
writePixel(gs, x, y, z1, r, g, b, a, v1.fog);
}
}
@@ -904,15 +999,12 @@ void GSRasterizer::drawTriangle(GS *gs)
}
else
{
const float invQ0 = 1.0f / fabsQ(v0.q);
const float invQ1 = 1.0f / fabsQ(v1.q);
const float invQ2 = 1.0f / fabsQ(v2.q);
const float sOverQ = (v0.s * invQ0) * w0 + (v1.s * invQ1) * w1 + (v2.s * invQ2) * w2;
const float tOverQ = (v0.t * invQ0) * w0 + (v1.t * invQ1) * w1 + (v2.t * invQ2) * w2;
const float invQ = invQ0 * w0 + invQ1 * w1 + invQ2 * w2;
iq = (std::fabs(invQ) > 1.0e-8f) ? (1.0f / invQ) : 1.0f;
is = sOverQ * iq;
it = tOverQ * iq;
// The GS DDA interpolates the homogeneous S, T and Q
// values. Texel coordinates are calculated from S/Q and
// T/Q only after interpolation.
is = v0.s * w0 + v1.s * w1 + v2.s * w2;
it = v0.t * w0 + v1.t * w1 + v2.t * w2;
iq = v0.q * w0 + v1.q * w1 + v2.q * w2;
iu = 0;
iv = 0;
}
@@ -937,7 +1029,8 @@ void GSRasterizer::drawTriangle(GS *gs)
a = color.a;
}
writePixel(gs, x, y, static_cast<u32>(z + 0.5), r, g, b, a);
const uint8_t fog = clampU8(static_cast<int>(v0.fog * w0 + v1.fog * w1 + v2.fog * w2));
writePixel(gs, x, y, static_cast<u32>(z + 0.5), r, g, b, a, fog);
}
}
}
@@ -987,8 +1080,8 @@ void GSRasterizer::drawLine(GS *gs)
}
double z = (v0.z + (v1.z - v0.z) * t);
writePixel(gs, x0, y0, static_cast<u32>(z), r, g, b, a);
const uint8_t fog = clampU8(static_cast<int>(v0.fog + (v1.fog - v0.fog) * t));
writePixel(gs, x0, y0, static_cast<u32>(z), r, g, b, a, fog);
if (x0 == x1 && y0 == y1)
break;
+16 -5
View File
@@ -351,6 +351,7 @@ bool PS2Memory::initialize(size_t ramSize)
m_vu1Data = new uint8_t[PS2_VU1_DATA_SIZE];
std::memset(m_vu1Code, 0, PS2_VU1_CODE_SIZE);
std::memset(m_vu1Data, 0, PS2_VU1_DATA_SIZE);
markVU0CodeModified();
markVU1CodeModified();
// Initialize VIF registers
@@ -765,7 +766,9 @@ void PS2Memory::write8(uint32_t address, uint8_t value)
{
(void)vuLimit;
vuMem[vuOffset] = value;
if (vuMem == m_vu1Code)
if (vuMem == m_vu0Code)
markVU0CodeModified();
else if (vuMem == m_vu1Code)
markVU1CodeModified();
return;
}
@@ -806,7 +809,9 @@ void PS2Memory::write16(uint32_t address, uint16_t value)
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint16_t), vuOffset, vuLimit))
{
storeScalar<uint16_t>(vuMem, vuOffset, vuLimit, value, "write16 vu", address);
if (vuMem == m_vu1Code)
if (vuMem == m_vu0Code)
markVU0CodeModified();
else if (vuMem == m_vu1Code)
markVU1CodeModified();
return;
}
@@ -868,7 +873,9 @@ void PS2Memory::write32(uint32_t address, uint32_t value)
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint32_t), vuOffset, vuLimit))
{
storeScalar<uint32_t>(vuMem, vuOffset, vuLimit, value, "write32 vu", address);
if (vuMem == m_vu1Code)
if (vuMem == m_vu0Code)
markVU0CodeModified();
else if (vuMem == m_vu1Code)
markVU1CodeModified();
return;
}
@@ -921,7 +928,9 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
if (uint8_t *vuMem = mapVuMemory(physAddr, sizeof(uint64_t), vuOffset, vuLimit))
{
storeScalar<uint64_t>(vuMem, vuOffset, vuLimit, value, "write64 vu", address);
if (vuMem == m_vu1Code)
if (vuMem == m_vu0Code)
markVU0CodeModified();
else if (vuMem == m_vu1Code)
markVU1CodeModified();
return;
}
@@ -962,7 +971,9 @@ void PS2Memory::write128(uint32_t address, __m128i value)
{
inRange(vuOffset, sizeof(__m128i), vuLimit, "write128 vu", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(vuMem + vuOffset), value);
if (vuMem == m_vu1Code)
if (vuMem == m_vu0Code)
markVU0CodeModified();
else if (vuMem == m_vu1Code)
markVU1CodeModified();
return;
}
+79 -9
View File
@@ -209,6 +209,7 @@ namespace
ctx->vu0_mac_flags = 0;
ctx->vu0_status = 0;
ctx->vu0_q = 1.0f;
ctx->vu0_r = _mm_castsi128_ps(_mm_set1_epi32(0x3F800000));
ctx->vu0_vpu_stat = 0;
ctx->vu0_vpu_stat2 = 0;
}
@@ -230,11 +231,16 @@ namespace
state.q = ctx->vu0_q;
state.p = ctx->vu0_p;
state.i = ctx->vu0_i;
alignas(16) uint32_t rWords[4]{};
_mm_storeu_si128(reinterpret_cast<__m128i *>(rWords), _mm_castps_si128(ctx->vu0_r));
state.r = 0x3F800000u | (rWords[0] & 0x007FFFFFu);
state.pc = ctx->vu0_pc;
state.mac = ctx->vu0_mac_flags;
state.clip = ctx->vu0_clip_flags;
state.status = ctx->vu0_status;
state.itop = ctx->vu0_itop;
state.dBitEnabled = (ctx->vu0_fbrst & (1u << 2)) != 0u;
state.tBitEnabled = (ctx->vu0_fbrst & (1u << 3)) != 0u;
state.vf[0][0] = 0.0f;
state.vf[0][1] = 0.0f;
@@ -258,6 +264,7 @@ namespace
ctx->vu0_q = state.q;
ctx->vu0_p = state.p;
ctx->vu0_i = state.i;
ctx->vu0_r = _mm_castsi128_ps(_mm_set1_epi32(static_cast<int32_t>(state.r)));
ctx->vu0_mac_flags = state.mac;
ctx->vu0_clip_flags = state.clip;
ctx->vu0_clip_flags2 = state.clip;
@@ -265,7 +272,7 @@ namespace
ctx->vu0_itop = state.itop;
ctx->vu0_pc = state.pc;
ctx->vu0_tpc = state.pc;
ctx->vu0_vpu_stat = 0;
ctx->vu0_vpu_stat = (ctx->vu0_vpu_stat & 0xFF00u) | (state.stoppedByD ? (1u << 1) : 0u) | (state.stoppedByT ? (1u << 2) : 0u);
ctx->vu0_vpu_stat2 = 0;
ctx->vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f);
@@ -523,6 +530,9 @@ PS2Runtime::PS2Runtime()
// R0 is always zero in MIPS
m_cpuContext.r[0] = _mm_set1_epi32(0);
m_cpuContext.vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f);
m_cpuContext.vu0_q = 1.0f;
m_cpuContext.vu0_r = _mm_castsi128_ps(_mm_set1_epi32(0x3F800000));
// Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF
@@ -647,13 +657,31 @@ bool PS2Runtime::syncCoreSubsystems()
{ m_gs.processGIFPacket(data, size); });
m_memory.setGifArbiter(&m_gifArbiter);
m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t top, uint32_t itop)
{ m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, startPC, top, itop, 65536); });
{
m_vu1.state().dBitEnabled =
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
m_vu1.state().tBitEnabled =
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, startPC, top, itop, 65536);
m_cpuContext.vu0_vpu_stat =
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
m_memory.setVu1MscntCallback([this](uint32_t top, uint32_t itop)
{ m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, top, itop, 65536); });
{
m_vu1.state().dBitEnabled =
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
m_vu1.state().tBitEnabled =
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, top, itop, 65536);
m_cpuContext.vu0_vpu_stat =
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
resetIop();
m_vu0.reset();
m_vu1.reset();
@@ -1157,6 +1185,10 @@ void PS2Runtime::reportMissingFunction(uint8_t *rdram,
const uint32_t gp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0));
const uint32_t a0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[4], 0));
const uint32_t a1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[5], 0));
const uint32_t a2 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[6], 0));
const uint32_t a3 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[7], 0));
const uint32_t s0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[16], 0));
const uint32_t s1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[17], 0));
const uint32_t v0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[2], 0));
const uint32_t v1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[3], 0));
@@ -1194,6 +1226,27 @@ void PS2Runtime::reportMissingFunction(uint8_t *rdram,
readGuestU32Offset(a0, 0x08u, a0Word8) &&
readGuestU32Offset(a0, 0x0cu, a0WordC);
uint32_t s0Word0 = 0u;
uint32_t s0Word4 = 0u;
uint32_t s0Word8 = 0u;
uint32_t s0WordC = 0u;
const bool s0Readable =
readGuestU32Offset(s0, 0x00u, s0Word0) &&
readGuestU32Offset(s0, 0x04u, s0Word4) &&
readGuestU32Offset(s0, 0x08u, s0Word8) &&
readGuestU32Offset(s0, 0x0cu, s0WordC);
uint32_t recordWord0 = 0u;
uint32_t recordWord4 = 0u;
uint32_t recordWord8 = 0u;
uint32_t recordWordC = 0u;
const bool recordReadable =
s0Readable && s0Word4 != 0u &&
readGuestU32Offset(s0Word4, 0x00u, recordWord0) &&
readGuestU32Offset(s0Word4, 0x04u, recordWord4) &&
readGuestU32Offset(s0Word4, 0x08u, recordWord8) &&
readGuestU32Offset(s0Word4, 0x0cu, recordWordC);
uint32_t vtableSlot0 = 0u;
uint32_t vtableSlot4 = 0u;
uint32_t vtableSlot8 = 0u;
@@ -1218,6 +1271,10 @@ void PS2Runtime::reportMissingFunction(uint8_t *rdram,
<< " gp=0x" << gp
<< " a0=0x" << a0
<< " a1=0x" << a1
<< " a2=0x" << a2
<< " a3=0x" << a3
<< " s0=0x" << s0
<< " s1=0x" << s1
<< " v0=0x" << v0
<< " v1=0x" << v1
<< " a0Readable=" << (a0Readable ? "yes" : "no")
@@ -1225,6 +1282,16 @@ void PS2Runtime::reportMissingFunction(uint8_t *rdram,
<< " a0[4]=0x" << a0Word4
<< " a0[8]=0x" << a0Word8
<< " a0[c]=0x" << a0WordC
<< " s0Readable=" << (s0Readable ? "yes" : "no")
<< " s0[0]=0x" << s0Word0
<< " s0[4]=0x" << s0Word4
<< " s0[8]=0x" << s0Word8
<< " s0[c]=0x" << s0WordC
<< " recordReadable=" << (recordReadable ? "yes" : "no")
<< " record[0]=0x" << recordWord0
<< " record[4]=0x" << recordWord4
<< " record[8]=0x" << recordWord8
<< " record[c]=0x" << recordWordC
<< " vtableReadable=" << (vtableReadable ? "yes" : "no")
<< " vtbl[0]=0x" << vtableSlot0
<< " vtbl[4]=0x" << vtableSlot4
@@ -2152,16 +2219,19 @@ void PS2Runtime::yieldGuestExecutionAfterWake()
{
GuestExecutionReleaseScope releaseGuestExecution(this);
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
m_guestExecutionHandoffCv.wait_for(lock, std::chrono::milliseconds(2), [&]()
m_guestExecutionHandoffCv.wait_for(lock, std::chrono::milliseconds(1), [&]()
{ return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire) != handoffEpoch; });
}
}
bool PS2Runtime::shouldPreemptGuestExecution()
{
constexpr uint32_t kContendedYieldInterval = 1024u;
constexpr uint32_t kUncontendedYieldInterval = 16384u;
thread_local uint32_t s_backEdgeYieldCounter = 0u;
const uint32_t waiterCount = m_guestExecutionWaiters.load(std::memory_order_acquire);
const uint32_t yieldInterval = (waiterCount != 0u) ? 64u : 100u;
const uint32_t yieldInterval = (waiterCount != 0u) ? kContendedYieldInterval : kUncontendedYieldInterval;
if (++s_backEdgeYieldCounter < yieldInterval)
{
return false;
@@ -144,7 +144,10 @@ void PS2Memory::processVIF0Data(const uint8_t *data, uint32_t sizeBytes)
if (destAddr + copyBytes > PS2_VU0_CODE_SIZE)
copyBytes = PS2_VU0_CODE_SIZE - destAddr;
if (pos + copyBytes <= sizeBytes)
{
std::memcpy(m_vu0Code + destAddr, data + pos, copyBytes);
markVU0CodeModified();
}
}
pos += mpgBytes;
File diff suppressed because it is too large Load Diff
+1 -129
View File
@@ -26,132 +26,4 @@ static inline int16_t IMM15(uint32_t i)
return (int16_t)(int32_t)((int32_t)(raw << 17) >> 17);
}
static inline uint8_t vuUpperVfWriteReg(uint32_t upper)
{
const uint8_t op = upper & 0x3Fu;
const uint8_t dest = DEST(upper);
const uint8_t ft = FT(upper);
const uint8_t fd = FD(upper);
if (dest == 0u)
return 0u;
if (op <= 0x2Fu)
return fd;
if (op >= 0x3Cu)
{
const uint8_t specialOp = static_cast<uint8_t>((upper & 0x3u) | ((upper >> 4) & 0x7Cu));
switch (specialOp)
{
// Upper special ops that write a VF register use FT as destination.
case 0x10: // ITOF0
case 0x11: // ITOF4
case 0x12: // ITOF12
case 0x13: // ITOF15
case 0x14: // FTOI0
case 0x15: // FTOI4
case 0x16: // FTOI12
case 0x17: // FTOI15
case 0x1D: // ABS
return ft;
default:
return 0u; // ACC/NOP/CLIP/etc.
}
}
return 0u;
}
static inline void vuSetRegBit(uint32_t &mask, uint8_t reg)
{
if (reg != 0u && reg < 32u)
mask |= (1u << reg);
}
static inline void vuLowerVfReadWriteMasks(uint32_t lower, uint32_t &readMask, uint32_t &writeMask)
{
readMask = 0u;
writeMask = 0u;
if (lower == 0u || lower == 0x8000033Cu)
return;
const uint8_t opHi = static_cast<uint8_t>((lower >> 25) & 0x7Fu);
const uint8_t it = LIT(lower);
const uint8_t is = LIS(lower);
if ((lower & 0x80000000u) != 0u)
{
const uint8_t funct = lower & 0x3Fu;
if (funct >= 0x3Cu && funct <= 0x3Fu)
{
const uint8_t specialOp = static_cast<uint8_t>((lower & 0x3u) | ((lower >> 4) & 0x7Cu));
switch (specialOp)
{
case 0x30: // MOVE
case 0x31: // MR32
vuSetRegBit(readMask, is);
vuSetRegBit(writeMask, it);
return;
case 0x34: // LQI
case 0x36: // LQD
vuSetRegBit(writeMask, it);
return;
case 0x35: // SQI
case 0x37: // SQD
vuSetRegBit(readMask, is);
return;
case 0x38: // DIV
case 0x3A: // RSQRT
vuSetRegBit(readMask, is);
vuSetRegBit(readMask, it);
return;
case 0x39: // SQRT
vuSetRegBit(readMask, it);
return;
case 0x3C: // MTIR
case 0x3E: // ILWR source base is integer, but field source is VF for MTIR only.
if (specialOp == 0x3C)
vuSetRegBit(readMask, is);
return;
case 0x3D: // MFIR
case 0x64: // MFP
vuSetRegBit(writeMask, it);
return;
default:
return;
}
}
return;
}
switch (opHi)
{
case 0x00: // LQ
vuSetRegBit(writeMask, it);
return;
case 0x01: // SQ
vuSetRegBit(readMask, is);
return;
default:
return;
}
}
static inline bool vuLowerShouldRunBeforeUpper(uint32_t upper, uint32_t lower)
{
const uint8_t upperWrite = vuUpperVfWriteReg(upper);
if (upperWrite == 0u)
return false;
uint32_t lowerReads = 0u;
uint32_t lowerWrites = 0u;
vuLowerVfReadWriteMasks(lower, lowerReads, lowerWrites);
const uint32_t upperBit = (1u << upperWrite);
return ((lowerReads | lowerWrites) & upperBit) != 0u;
}
#endif
#endif
+298 -227
View File
@@ -7,7 +7,64 @@
#include <cmath>
#include <cstring>
#include <limits>
#include <vector>
namespace
{
float vuEatan(float value)
{
constexpr float coefficients[] = {
0.999999344348907f,
-0.333298563957214f,
0.199465364217758f,
-0.13085337519646f,
0.096420042216778f,
-0.055909886956215f,
0.021861229091883f,
-0.004054057877511f};
constexpr float quarterPi = 0.785398185253143f;
const float squared = value * value;
float polynomial = coefficients[7];
for (int index = 6; index >= 0; --index)
polynomial = coefficients[index] + squared * polynomial;
return quarterPi + value * polynomial;
}
float vuEsin(float value)
{
constexpr float coefficients[] = {
1.0f,
-0.166666567325592f,
0.008333025500178f,
-0.000198074136279f,
0.000002601886990f};
const float squared = value * value;
float polynomial = coefficients[4];
for (int index = 3; index >= 0; --index)
polynomial = coefficients[index] + squared * polynomial;
return value * polynomial;
}
float vuEexp(float value)
{
constexpr float coefficients[] = {
0.249998688697815f,
0.031257584691048f,
0.002591371303424f,
0.000171562001924f,
0.000005430199963f,
0.000000690600018f};
float polynomial = coefficients[5];
for (int index = 4; index >= 0; --index)
polynomial = coefficients[index] + value * polynomial;
polynomial = 1.0f + value * polynomial;
polynomial *= polynomial;
polynomial *= polynomial;
return polynomial != 0.0f ? 1.0f / polynomial : std::numeric_limits<float>::max();
}
}
// ============================================================================
// Lower instructions
@@ -19,14 +76,15 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
return;
uint8_t opHi = (instr >> 25) & 0x7F;
const uint32_t pcMask = microAddressMask();
// The lower instruction encoding uses bits 31:25 for the primary opcode
switch (opHi)
{
case 0x00: // LQ (Load Quadword from VU data memory)
{
uint8_t it = FT(instr); // VF destination
uint8_t is = VIS(instr); // VI base
uint8_t it = FT(instr); // VF destination
uint8_t is = VIS(instr); // VI base
uint8_t dest = (instr >> 21) & 0xF;
int16_t imm = IMM11(instr);
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
@@ -41,32 +99,24 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
}
case 0x01: // SQ (Store Quadword to VU data memory)
{
uint8_t is = FS(instr); // VF source
uint8_t it = VIT(instr); // VI base
uint8_t is = FS(instr); // VF source
uint8_t it = VIT(instr); // VI base
uint8_t dest = (instr >> 21) & 0xF;
int16_t imm = IMM11(instr);
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[it] + imm)) * 16u;
addr &= (dataSize - 1);
if (addr + 16 <= dataSize)
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8)
tmp[0] = m_state.vf[is][0];
if (dest & 0x4)
tmp[1] = m_state.vf[is][1];
if (dest & 0x2)
tmp[2] = m_state.vf[is][2];
if (dest & 0x1)
tmp[3] = m_state.vf[is][3];
std::memcpy(vuData + addr, tmp, 16);
uint32_t words[4]{};
std::memcpy(words, m_state.vf[is], sizeof(words));
queueStore(addr, words, dest);
}
return;
}
case 0x04: // ILW (Integer Load Word from VU data memory)
{
uint8_t it = VIT(instr); // VI destination
uint8_t is = VIS(instr); // VI base
uint8_t it = VIT(instr); // VI destination
uint8_t is = VIS(instr); // VI base
uint8_t dest = (instr >> 21) & 0xF;
int16_t imm = IMM11(instr);
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
@@ -91,23 +141,17 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
}
case 0x05: // ISW (Integer Store Word to VU data memory)
{
uint8_t it = VIT(instr); // VI source
uint8_t is = VIS(instr); // VI base
uint8_t it = VIT(instr); // VI source
uint8_t is = VIS(instr); // VI base
uint8_t dest = (instr >> 21) & 0xF;
int16_t imm = IMM11(instr);
uint32_t addr = ((uint32_t)(int32_t)(m_state.vi[is] + imm)) * 16u;
addr &= (dataSize - 1);
if (addr + 16 <= dataSize)
{
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[it] & 0xFFFF);
if (dest & 0x8)
std::memcpy(vuData + addr + 0, &val, 4);
if (dest & 0x4)
std::memcpy(vuData + addr + 4, &val, 4);
if (dest & 0x2)
std::memcpy(vuData + addr + 8, &val, 4);
if (dest & 0x1)
std::memcpy(vuData + addr + 12, &val, 4);
const uint32_t val = static_cast<uint32_t>(static_cast<uint16_t>(m_state.vi[it] & 0xFFFF));
const uint32_t words[4] = {val, val, val, val};
queueStore(addr, words, dest);
}
return;
}
@@ -138,7 +182,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
}
case 0x11: // FCSET
{
m_state.clip = instr & 0xFFFFFF;
queueFcset(instr & 0xFFFFFFu);
return;
}
case 0x12: // FCAND
@@ -157,39 +201,35 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
}
case 0x14: // FSEQ
{
uint16_t imm12 = instr & 0xFFF;
if (1 != 0)
m_state.vi[1] = ((m_state.status & 0xFFF) == imm12) ? 1 : 0;
const uint8_t it = VIT(instr);
const uint16_t imm12 = static_cast<uint16_t>((((instr >> 21) & 0x1u) << 11) | (instr & 0x7FFu));
if (it != 0)
m_state.vi[it] = ((m_state.status & 0xFFFu) == imm12) ? 1 : 0;
return;
}
case 0x15: // FSSET
{
m_state.status = (instr >> 6) & 0xFC0;
const uint16_t imm12 = static_cast<uint16_t>((((instr >> 21) & 0x1u) << 11) | (instr & 0x7FFu));
queueFsset(imm12);
return;
}
case 0x16: // FSAND
{
uint16_t imm12 = instr & 0xFFF;
if (1 != 0)
m_state.vi[1] = (int32_t)(m_state.status & imm12);
const uint8_t it = VIT(instr);
const uint16_t imm12 = static_cast<uint16_t>((((instr >> 21) & 0x1u) << 11) | (instr & 0x7FFu));
if (it != 0)
m_state.vi[it] = static_cast<int32_t>((m_state.status & 0xFFFu) & imm12);
return;
}
case 0x17: // FSOR
{
uint16_t imm12 = instr & 0xFFF;
if (1 != 0)
m_state.vi[1] = ((m_state.status | imm12) == 0xFFF) ? 1 : 0;
return;
}
case 0x18: // FMAND
{
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
const uint8_t it = VIT(instr);
const uint16_t imm12 = static_cast<uint16_t>((((instr >> 21) & 0x1u) << 11) | (instr & 0x7FFu));
if (it != 0)
m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]);
m_state.vi[it] = static_cast<int32_t>((m_state.status & 0xFFFu) | imm12);
return;
}
case 0x1A: // FMEQ
case 0x18: // FMEQ
{
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
@@ -197,7 +237,15 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
m_state.vi[it] = ((m_state.mac & 0xFFFF) == (uint32_t)(uint16_t)m_state.vi[is]) ? 1 : 0;
return;
}
case 0x1C: // FMOR
case 0x1A: // FMAND
{
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
if (it != 0)
m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]);
return;
}
case 0x1B: // FMOR
{
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
@@ -205,10 +253,17 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
m_state.vi[it] = (int32_t)(m_state.mac | (uint32_t)(uint16_t)m_state.vi[is]);
return;
}
case 0x1C: // FCGET
{
const uint8_t it = VIT(instr);
if (it != 0)
m_state.vi[it] = static_cast<int32_t>(m_state.clip & 0x0FFFu);
return;
}
case 0x20: // B (unconditional branch)
{
int16_t imm = IMM11(instr);
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
@@ -218,7 +273,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t it = VIT(instr);
int16_t imm = IMM11(instr);
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
if (it != 0)
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
m_state.branchPending = true;
@@ -229,7 +284,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
case 0x24: // JR
{
uint8_t is = VIS(instr);
uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF;
uint32_t target = ((uint32_t)(uint16_t)readBranchVi(is) * 8u) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
@@ -239,7 +294,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF;
uint32_t target = ((uint32_t)(uint16_t)readBranchVi(is) * 8u) & pcMask;
if (it != 0)
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
m_state.branchPending = true;
@@ -252,12 +307,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] == (int16_t)m_state.vi[it])
if ((int16_t)readBranchVi(is) == (int16_t)readBranchVi(it))
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -266,12 +321,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
uint8_t it = VIT(instr);
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] != (int16_t)m_state.vi[it])
if ((int16_t)readBranchVi(is) != (int16_t)readBranchVi(it))
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -279,12 +334,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] < 0)
if ((int16_t)readBranchVi(is) < 0)
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -292,12 +347,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] > 0)
if ((int16_t)readBranchVi(is) > 0)
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -305,12 +360,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] <= 0)
if ((int16_t)readBranchVi(is) <= 0)
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -318,12 +373,12 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
uint8_t is = VIS(instr);
int16_t imm = IMM11(instr);
if ((int16_t)m_state.vi[is] >= 0)
if ((int16_t)readBranchVi(is) >= 0)
{
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
uint32_t target = (m_state.pc + 8 + imm * 8) & pcMask;
m_state.branchPending = true;
m_state.branchTarget = target;
m_state.branchDelay = 1;
m_state.branchTarget = target;
m_state.branchDelay = 1;
}
return;
}
@@ -338,95 +393,6 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
const uint8_t viD = VID(instr);
const uint8_t dest = (instr >> 21) & 0xF;
auto doXgkick = [&]()
{
if (!vuData || dataSize < 16u)
return;
auto wrapOffset = [&](uint32_t off) -> uint32_t
{
return off % dataSize;
};
auto read64Wrap = [&](uint32_t off) -> uint64_t
{
uint8_t bytes[8];
for (uint32_t i = 0; i < 8u; ++i)
{
bytes[i] = vuData[wrapOffset(off + i)];
}
uint64_t value = 0;
std::memcpy(&value, bytes, sizeof(value));
return value;
};
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[viS]) * 16u;
addr = wrapOffset(addr);
uint32_t pktOff = addr;
uint32_t totalBytes = 0u;
bool done = false;
for (int safety = 0; safety < 256 && !done; ++safety)
{
uint64_t tagLo = read64Wrap(pktOff);
uint32_t nloop = (uint32_t)(tagLo & 0x7FFFu);
uint8_t flg = (uint8_t)((tagLo >> 58) & 0x3u);
uint32_t nreg = (uint32_t)((tagLo >> 60) & 0xFu);
if (nreg == 0u)
nreg = 16u;
bool eop = ((tagLo >> 15) & 0x1ull) != 0ull;
uint32_t pktSize = 16u;
if (flg == 0u)
{
pktSize += nloop * nreg * 16u;
}
else if (flg == 1u)
{
uint32_t regs = nloop * nreg;
pktSize += regs * 8u;
if ((regs & 1u) != 0u)
pktSize += 8u;
}
else if (flg == 2u)
{
pktSize += nloop * 16u;
}
if (pktSize == 0u)
break;
totalBytes += pktSize;
pktOff = wrapOffset(pktOff + pktSize);
if (eop)
done = true;
}
if (totalBytes == 0u)
return;
if (addr + totalBytes <= dataSize)
{
if (memory)
memory->submitGifPacket(GifPathId::Path1, vuData + addr, totalBytes);
else
gs.processGIFPacket(vuData + addr, totalBytes);
}
else
{
std::vector<uint8_t> wrappedPacket(totalBytes);
for (uint32_t i = 0; i < totalBytes; ++i)
{
wrappedPacket[i] = vuData[wrapOffset(addr + i)];
}
if (memory)
memory->submitGifPacket(GifPathId::Path1, wrappedPacket.data(), totalBytes);
else
gs.processGIFPacket(wrappedPacket.data(), totalBytes);
}
};
switch (funct)
{
case 0x30: // IADD
@@ -494,17 +460,9 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
addr &= (dataSize - 1);
if (addr + 16 <= dataSize)
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8)
tmp[0] = m_state.vf[vfS][0];
if (dest & 0x4)
tmp[1] = m_state.vf[vfS][1];
if (dest & 0x2)
tmp[2] = m_state.vf[vfS][2];
if (dest & 0x1)
tmp[3] = m_state.vf[vfS][3];
std::memcpy(vuData + addr, tmp, 16);
uint32_t words[4]{};
std::memcpy(words, m_state.vf[vfS], sizeof(words));
queueStore(addr, words, dest);
}
if (viT != 0)
m_state.vi[viT] = (int16_t)(m_state.vi[viT] + 1);
@@ -532,17 +490,9 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
addr &= (dataSize - 1);
if (addr + 16 <= dataSize)
{
float tmp[4];
std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8)
tmp[0] = m_state.vf[vfS][0];
if (dest & 0x4)
tmp[1] = m_state.vf[vfS][1];
if (dest & 0x2)
tmp[2] = m_state.vf[vfS][2];
if (dest & 0x1)
tmp[3] = m_state.vf[vfS][3];
std::memcpy(vuData + addr, tmp, 16);
uint32_t words[4]{};
std::memcpy(words, m_state.vf[vfS], sizeof(words));
queueStore(addr, words, dest);
}
return;
}
@@ -550,46 +500,64 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{
int fsf = (instr >> 21) & 0x3;
int ftf = (instr >> 23) & 0x3;
float num = m_state.vf[vfS][fsf];
float den = m_state.vf[vfT][ftf];
if (den != 0.0f)
m_state.q = num / den;
const float num = normalizeOperand(m_state.vf[vfS][fsf]);
const float den = normalizeOperand(m_state.vf[vfT][ftf]);
uint32_t statusDi = 0u;
float result = 0.0f;
if (den == 0.0f)
{
statusDi = num == 0.0f ? 0x10u : 0x20u;
result = std::signbit(num) != std::signbit(den)
? -std::numeric_limits<float>::max()
: std::numeric_limits<float>::max();
}
else
m_state.q = (num >= 0.0f) ? std::numeric_limits<float>::max() : -std::numeric_limits<float>::max();
{
result = num / den;
}
uint32_t ignoredFlags = 0u;
result = normalizeResult(result, ignoredFlags);
queueQ(result, 7u, statusDi);
return;
}
case 0x39: // SQRT
{
int ftf = (instr >> 23) & 0x3;
float val = m_state.vf[vfT][ftf];
m_state.q = std::sqrt(std::fabs(val));
const float val = normalizeOperand(m_state.vf[vfT][ftf]);
queueQ(std::sqrt(std::fabs(val)), 7u,
val < 0.0f ? 0x10u : 0u);
return;
}
case 0x3A: // RSQRT
{
int fsf = (instr >> 21) & 0x3;
int ftf = (instr >> 23) & 0x3;
float num = m_state.vf[vfS][fsf];
float den = std::sqrt(std::fabs(m_state.vf[vfT][ftf]));
const float num = normalizeOperand(m_state.vf[vfS][fsf]);
const float radicand = normalizeOperand(m_state.vf[vfT][ftf]);
const float den = std::sqrt(std::fabs(radicand));
uint32_t statusDi = radicand < 0.0f ? 0x10u : 0u;
float result = 0.0f;
if (den != 0.0f)
m_state.q = num / den;
result = num / den;
else
m_state.q = std::numeric_limits<float>::max();
{
statusDi = num == 0.0f ? 0x10u : 0x20u;
result = std::signbit(num)
? -std::numeric_limits<float>::max()
: std::numeric_limits<float>::max();
}
uint32_t ignoredFlags = 0u;
result = normalizeResult(result, ignoredFlags);
queueQ(result, 13u, statusDi);
return;
}
case 0x3B: // WAITQ
return;
case 0x3C: // MTIR (Move To Integer Register)
{
int comp = 0;
if (dest & 0x8)
comp = 0;
else if (dest & 0x4)
comp = 1;
else if (dest & 0x2)
comp = 2;
else
comp = 3;
// MTIR encodes a two-bit fsf component selector in bits
// 22:21. It is not a four-bit destination mask.
const uint32_t comp = (instr >> 21) & 0x3u;
uint32_t fval;
std::memcpy(&fval, &m_state.vf[vfS][comp], 4);
if (viT != 0)
@@ -635,26 +603,49 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
addr &= (dataSize - 1);
if (addr + 16 <= dataSize)
{
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[viT] & 0xFFFF);
if (dest & 0x8)
std::memcpy(vuData + addr + 0, &val, 4);
if (dest & 0x4)
std::memcpy(vuData + addr + 4, &val, 4);
if (dest & 0x2)
std::memcpy(vuData + addr + 8, &val, 4);
if (dest & 0x1)
std::memcpy(vuData + addr + 12, &val, 4);
const uint32_t val =
static_cast<uint32_t>(static_cast<uint16_t>(m_state.vi[viT] & 0xFFFF));
const uint32_t words[4] = {val, val, val, val};
queueStore(addr, words, dest);
}
return;
}
case 0x40: // RNEXT
{
const uint32_t x = (m_state.r >> 4) & 1u;
const uint32_t y = (m_state.r >> 22) & 1u;
m_state.r = ((m_state.r << 1) ^ x ^ y) & 0x007FFFFFu;
m_state.r |= 0x3F800000u;
float value = 0.0f;
std::memcpy(&value, &m_state.r, sizeof(value));
const float result[4] = {value, value, value, value};
applyDest(m_state.vf[vfT], result, dest);
return;
}
case 0x41: // RGET
{
float value = 0.0f;
std::memcpy(&value, &m_state.r, sizeof(value));
const float result[4] = {value, value, value, value};
applyDest(m_state.vf[vfT], result, dest);
return;
}
case 0x42: // RINIT
{
const uint32_t component = (instr >> 21) & 3u;
uint32_t bits = 0u;
std::memcpy(&bits, &m_state.vf[vfS][component], sizeof(bits));
m_state.r = 0x3F800000u | (bits & 0x007FFFFFu);
return;
}
case 0x43: // RXOR
{
const uint32_t component = (instr >> 21) & 3u;
uint32_t bits = 0u;
std::memcpy(&bits, &m_state.vf[vfS][component], sizeof(bits));
m_state.r = 0x3F800000u | ((m_state.r ^ bits) & 0x007FFFFFu);
return;
}
case 0x64: // MFP (Move From P register)
{
float result[4] = {m_state.p, m_state.p, m_state.p, m_state.p};
@@ -674,45 +665,125 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
return;
}
case 0x6C: // XGKICK - send GIF packet from VU1 data memory
doXgkick();
startXgkick(static_cast<uint32_t>(static_cast<uint16_t>(m_state.vi[viS])));
return;
case 0x70: // ESADD
{
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float y = normalizeOperand(m_state.vf[vfS][1]);
const float z = normalizeOperand(m_state.vf[vfS][2]);
queueP(x * x + y * y + z * z, 11u);
return;
}
case 0x71: // ERSADD
{
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float y = normalizeOperand(m_state.vf[vfS][1]);
const float z = normalizeOperand(m_state.vf[vfS][2]);
const float sum = x * x + y * y + z * z;
queueP(sum != 0.0f ? 1.0f / sum : sum, 18u);
return;
}
case 0x72: // ELENG
{
float s = m_state.vf[vfS][0] * m_state.vf[vfS][0] + m_state.vf[vfS][1] * m_state.vf[vfS][1] + m_state.vf[vfS][2] * m_state.vf[vfS][2];
m_state.p = std::sqrt(s);
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float y = normalizeOperand(m_state.vf[vfS][1]);
const float z = normalizeOperand(m_state.vf[vfS][2]);
queueP(std::sqrt(x * x + y * y + z * z), 18u);
return;
}
case 0x73: // ERLENG
{
float s = m_state.vf[vfS][0] * m_state.vf[vfS][0] + m_state.vf[vfS][1] * m_state.vf[vfS][1] + m_state.vf[vfS][2] * m_state.vf[vfS][2];
float len = std::sqrt(s);
m_state.p = (len != 0.0f) ? (1.0f / len) : std::numeric_limits<float>::max();
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float y = normalizeOperand(m_state.vf[vfS][1]);
const float z = normalizeOperand(m_state.vf[vfS][2]);
const float len = std::sqrt(x * x + y * y + z * z);
queueP(len != 0.0f ? 1.0f / len : len, 24u);
return;
}
case 0x74: // EATANxy
{
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float y = normalizeOperand(m_state.vf[vfS][1]);
queueP(x != 0.0f ? vuEatan(y / x) : 0.0f, 54u);
return;
}
case 0x75: // EATANxz
{
const float x = normalizeOperand(m_state.vf[vfS][0]);
const float z = normalizeOperand(m_state.vf[vfS][2]);
queueP(x != 0.0f ? vuEatan(z / x) : 0.0f, 54u);
return;
}
case 0x76: // ESUM
{
float sum = 0.0f;
for (uint32_t component = 0; component < 4u; ++component)
sum += normalizeOperand(m_state.vf[vfS][component]);
queueP(sum, 12u);
return;
}
case 0x77: // ERSQRT
{
const uint32_t component = (instr >> 21) & 3u;
const float value = normalizeOperand(m_state.vf[vfS][component]);
float result = value;
if (result >= 0.0f)
{
result = std::sqrt(result);
if (result != 0.0f)
result = 1.0f / result;
}
queueP(result, 18u);
return;
}
case 0x78: // ESQRT
{
const uint32_t component = (instr >> 21) & 3u;
const float value = normalizeOperand(m_state.vf[vfS][component]);
queueP(value >= 0.0f ? std::sqrt(value) : value, 12u);
return;
}
case 0x79: // ESIN
{
const uint32_t component = (instr >> 21) & 3u;
const float value = normalizeOperand(m_state.vf[vfS][component]);
queueP(vuEsin(value), 29u);
return;
}
case 0x7A: // ERCPR
{
int fsf = (instr >> 21) & 0x3;
float val = m_state.vf[vfS][fsf];
m_state.p = (val != 0.0f) ? (1.0f / val) : std::numeric_limits<float>::max();
const uint32_t component = (instr >> 21) & 3u;
const float value = normalizeOperand(m_state.vf[vfS][component]);
queueP(value != 0.0f ? 1.0f / value : value, 12u);
return;
}
case 0x7B: // WAITP
return;
case 0x7D: // EATAN / EATANxy / EATANxz placeholder
case 0x7C: // EATAN
{
const uint32_t component = (instr >> 21) & 3u;
queueP(vuEatan(normalizeOperand(m_state.vf[vfS][component])), 54u);
return;
}
case 0x7D: // EEXP
{
const uint32_t component = (instr >> 21) & 3u;
queueP(vuEexp(normalizeOperand(m_state.vf[vfS][component])), 44u);
return;
}
default:
reportReservedInstruction(false, instr);
return;
}
}
default:
reportReservedInstruction(false, instr);
return;
}
}
default:
reportReservedInstruction(false, instr);
break;
}
}
+142 -94
View File
@@ -3,12 +3,27 @@
#include <cmath>
#include <cstring>
#include <limits>
namespace
{
int32_t vuFloatToInt(float value, float scale)
{
const double scaled = static_cast<double>(value) * static_cast<double>(scale);
if (scaled >= static_cast<double>(std::numeric_limits<int32_t>::max()))
return std::numeric_limits<int32_t>::max();
if (scaled <= static_cast<double>(std::numeric_limits<int32_t>::min()))
return std::numeric_limits<int32_t>::min();
return static_cast<int32_t>(scaled);
}
}
// ============================================================================
// Upper instructions (FMAC pipeline)
// ============================================================================
void VU1Interpreter::execUpper(uint32_t instr)
{
m_currentUpperInstruction = instr;
uint8_t dest = DEST(instr);
uint8_t ft = FT(instr);
uint8_t fs = FS(instr);
@@ -16,8 +31,20 @@ void VU1Interpreter::execUpper(uint32_t instr)
uint8_t op = instr & 0x3F;
float *vd = m_state.vf[fd];
const float *vs = m_state.vf[fs];
const float *vt = m_state.vf[ft];
float normalizedVs[4];
float normalizedVt[4];
float normalizedAcc[4];
for (uint32_t component = 0; component < 4u; ++component)
{
normalizedVs[component] = normalizeOperand(m_state.vf[fs][component]);
normalizedVt[component] = normalizeOperand(m_state.vf[ft][component]);
normalizedAcc[component] = normalizeOperand(m_state.acc[component]);
}
const float *vs = normalizedVs;
const float *vt = normalizedVt;
const float *acc = normalizedAcc;
const float q = normalizeOperand(m_state.q);
const float i = normalizeOperand(m_state.i);
float result[4];
// Upper opcode decoding (bits 5:0 of upper word)
@@ -31,7 +58,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] + bc;
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
}
case 0x04:
@@ -42,7 +69,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] - bc;
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
}
case 0x08:
@@ -52,8 +79,8 @@ void VU1Interpreter::execUpper(uint32_t instr)
{
float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * bc;
applyDest(vd, result, dest);
result[c] = acc[c] + vs[c] * bc;
applyFmacDest(vd, result, dest);
return;
}
case 0x0C:
@@ -63,8 +90,8 @@ void VU1Interpreter::execUpper(uint32_t instr)
{
float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * bc;
applyDest(vd, result, dest);
result[c] = acc[c] - vs[c] * bc;
applyFmacDest(vd, result, dest);
return;
}
case 0x10:
@@ -97,83 +124,83 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] * bc;
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
}
case 0x1C: // MULq
for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.q;
applyDest(vd, result, dest);
result[c] = vs[c] * q;
applyFmacDest(vd, result, dest);
return;
case 0x1D: // MAXi
for (int c = 0; c < 4; c++)
result[c] = (vs[c] > m_state.i) ? vs[c] : m_state.i;
result[c] = (vs[c] > i) ? vs[c] : i;
applyDest(vd, result, dest);
return;
case 0x1E: // MULi
for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.i;
applyDest(vd, result, dest);
result[c] = vs[c] * i;
applyFmacDest(vd, result, dest);
return;
case 0x1F: // MINIi
for (int c = 0; c < 4; c++)
result[c] = (vs[c] < m_state.i) ? vs[c] : m_state.i;
result[c] = (vs[c] < i) ? vs[c] : i;
applyDest(vd, result, dest);
return;
case 0x20: // ADDq
for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.q;
applyDest(vd, result, dest);
result[c] = vs[c] + q;
applyFmacDest(vd, result, dest);
return;
case 0x21: // MADDq
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.q;
applyDest(vd, result, dest);
result[c] = acc[c] + vs[c] * q;
applyFmacDest(vd, result, dest);
return;
case 0x22: // ADDi
for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.i;
applyDest(vd, result, dest);
result[c] = vs[c] + i;
applyFmacDest(vd, result, dest);
return;
case 0x23: // MADDi
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.i;
applyDest(vd, result, dest);
result[c] = acc[c] + vs[c] * i;
applyFmacDest(vd, result, dest);
return;
case 0x24: // SUBq
for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.q;
applyDest(vd, result, dest);
result[c] = vs[c] - q;
applyFmacDest(vd, result, dest);
return;
case 0x25: // MSUBq
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.q;
applyDest(vd, result, dest);
result[c] = acc[c] - vs[c] * q;
applyFmacDest(vd, result, dest);
return;
case 0x26: // SUBi
for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.i;
applyDest(vd, result, dest);
result[c] = vs[c] - i;
applyFmacDest(vd, result, dest);
return;
case 0x27: // MSUBi
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.i;
applyDest(vd, result, dest);
result[c] = acc[c] - vs[c] * i;
applyFmacDest(vd, result, dest);
return;
case 0x28: // ADD
for (int c = 0; c < 4; c++)
result[c] = vs[c] + vt[c];
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
case 0x29: // MADD
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * vt[c];
applyDest(vd, result, dest);
result[c] = acc[c] + vs[c] * vt[c];
applyFmacDest(vd, result, dest);
return;
case 0x2A: // MUL
for (int c = 0; c < 4; c++)
result[c] = vs[c] * vt[c];
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
case 0x2B: // MAX
for (int c = 0; c < 4; c++)
@@ -183,19 +210,19 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x2C: // SUB
for (int c = 0; c < 4; c++)
result[c] = vs[c] - vt[c];
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
case 0x2D: // MSUB
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * vt[c];
applyDest(vd, result, dest);
result[c] = acc[c] - vs[c] * vt[c];
applyFmacDest(vd, result, dest);
return;
case 0x2E: // OPMSUB
result[0] = m_state.acc[0] - vs[1] * vt[2];
result[1] = m_state.acc[1] - vs[2] * vt[0];
result[2] = m_state.acc[2] - vs[0] * vt[1];
result[0] = acc[0] - vs[1] * vt[2];
result[1] = acc[1] - vs[2] * vt[0];
result[2] = acc[2] - vs[0] * vt[1];
result[3] = 0.0f;
applyDest(vd, result, dest);
applyFmacDest(vd, result, dest);
return;
case 0x2F: // MINI
for (int c = 0; c < 4; c++)
@@ -225,7 +252,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, specialOp & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] + bc;
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
}
case 0x04:
@@ -236,7 +263,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, specialOp & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] - bc;
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
}
case 0x08:
@@ -246,8 +273,8 @@ void VU1Interpreter::execUpper(uint32_t instr)
{
float bc = broadcast(vt, specialOp & 3);
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * bc;
applyDestAcc(result, dest);
result[c] = acc[c] + vs[c] * bc;
applyFmacDestAcc(result, dest);
return;
}
case 0x0C:
@@ -257,15 +284,15 @@ void VU1Interpreter::execUpper(uint32_t instr)
{
float bc = broadcast(vt, specialOp & 3);
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * bc;
applyDestAcc(result, dest);
result[c] = acc[c] - vs[c] * bc;
applyFmacDestAcc(result, dest);
return;
}
case 0x10: // ITOF0
for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
std::memcpy(&iv, &m_state.vf[fs][c], 4);
result[c] = static_cast<float>(iv);
}
applyDest(vtDest, result, dest);
@@ -274,7 +301,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
std::memcpy(&iv, &m_state.vf[fs][c], 4);
result[c] = static_cast<float>(iv) / 16.0f;
}
applyDest(vtDest, result, dest);
@@ -283,7 +310,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
std::memcpy(&iv, &m_state.vf[fs][c], 4);
result[c] = static_cast<float>(iv) / 4096.0f;
}
applyDest(vtDest, result, dest);
@@ -292,7 +319,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
std::memcpy(&iv, &m_state.vf[fs][c], 4);
result[c] = static_cast<float>(iv) / 32768.0f;
}
applyDest(vtDest, result, dest);
@@ -300,7 +327,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x14: // FTOI0
for (int c = 0; c < 4; c++)
{
int32_t iv = static_cast<int32_t>(vs[c]);
int32_t iv = vuFloatToInt(vs[c], 1.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vtDest, result, dest);
@@ -308,7 +335,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x15: // FTOI4
for (int c = 0; c < 4; c++)
{
int32_t iv = static_cast<int32_t>(vs[c] * 16.0f);
int32_t iv = vuFloatToInt(vs[c], 16.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vtDest, result, dest);
@@ -316,7 +343,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x16: // FTOI12
for (int c = 0; c < 4; c++)
{
int32_t iv = static_cast<int32_t>(vs[c] * 4096.0f);
int32_t iv = vuFloatToInt(vs[c], 4096.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vtDest, result, dest);
@@ -324,7 +351,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x17: // FTOI15
for (int c = 0; c < 4; c++)
{
int32_t iv = static_cast<int32_t>(vs[c] * 32768.0f);
int32_t iv = vuFloatToInt(vs[c], 32768.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vtDest, result, dest);
@@ -337,13 +364,13 @@ void VU1Interpreter::execUpper(uint32_t instr)
float bc = broadcast(vt, specialOp & 3);
for (int c = 0; c < 4; c++)
result[c] = vs[c] * bc;
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
}
case 0x1C: // MULAq
for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.q;
applyDestAcc(result, dest);
result[c] = vs[c] * q;
applyFmacDestAcc(result, dest);
return;
case 0x1D: // ABS
for (int c = 0; c < 4; c++)
@@ -352,98 +379,118 @@ void VU1Interpreter::execUpper(uint32_t instr)
return;
case 0x1E: // MULAi
for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.i;
applyDestAcc(result, dest);
result[c] = vs[c] * i;
applyFmacDestAcc(result, dest);
return;
case 0x1F: // CLIP
{
float w = std::fabs(vt[3]);
uint32_t flags = 0;
if (vs[0] > +w) flags |= 0x01;
if (vs[0] < -w) flags |= 0x02;
if (vs[1] > +w) flags |= 0x04;
if (vs[1] < -w) flags |= 0x08;
if (vs[2] > +w) flags |= 0x10;
if (vs[2] < -w) flags |= 0x20;
m_state.clip = (m_state.clip << 6) | flags;
uint32_t wBits = 0u;
std::memcpy(&wBits, &m_state.vf[ft][3], sizeof(wBits));
const int32_t limit = (wBits & 0x7F800000u) != 0u ? static_cast<int32_t>(wBits & 0x7FFFFFFFu) : 0x007FFFFF;
const auto exceedsClipPlane = [limit](float value, uint32_t signMask)
{
uint32_t bits = 0u;
std::memcpy(&bits, &value, sizeof(bits));
bits ^= signMask;
int32_t orderedBits = 0;
std::memcpy(&orderedBits, &bits, sizeof(orderedBits));
return orderedBits > limit;
};
uint32_t flags = 0u;
if (exceedsClipPlane(m_state.vf[fs][0], 0x00000000u))
flags |= 0x01u;
if (exceedsClipPlane(m_state.vf[fs][0], 0x80000000u))
flags |= 0x02u;
if (exceedsClipPlane(m_state.vf[fs][1], 0x00000000u))
flags |= 0x04u;
if (exceedsClipPlane(m_state.vf[fs][1], 0x80000000u))
flags |= 0x08u;
if (exceedsClipPlane(m_state.vf[fs][2], 0x00000000u))
flags |= 0x10u;
if (exceedsClipPlane(m_state.vf[fs][2], 0x80000000u))
flags |= 0x20u;
queueClip(flags);
return;
}
case 0x20: // ADDAq
for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.q;
applyDestAcc(result, dest);
result[c] = vs[c] + q;
applyFmacDestAcc(result, dest);
return;
case 0x21: // MADDAq
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.q;
applyDestAcc(result, dest);
result[c] = acc[c] + vs[c] * q;
applyFmacDestAcc(result, dest);
return;
case 0x22: // ADDAi
for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.i;
applyDestAcc(result, dest);
result[c] = vs[c] + i;
applyFmacDestAcc(result, dest);
return;
case 0x23: // MADDAi
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.i;
applyDestAcc(result, dest);
result[c] = acc[c] + vs[c] * i;
applyFmacDestAcc(result, dest);
return;
case 0x24: // SUBAq
for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.q;
applyDestAcc(result, dest);
result[c] = vs[c] - q;
applyFmacDestAcc(result, dest);
return;
case 0x25: // MSUBAq
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.q;
applyDestAcc(result, dest);
result[c] = acc[c] - vs[c] * q;
applyFmacDestAcc(result, dest);
return;
case 0x26: // SUBAi
for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.i;
applyDestAcc(result, dest);
result[c] = vs[c] - i;
applyFmacDestAcc(result, dest);
return;
case 0x27: // MSUBAi
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.i;
applyDestAcc(result, dest);
result[c] = acc[c] - vs[c] * i;
applyFmacDestAcc(result, dest);
return;
case 0x28: // ADDA
for (int c = 0; c < 4; c++)
result[c] = vs[c] + vt[c];
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
case 0x29: // MADDA
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * vt[c];
applyDestAcc(result, dest);
result[c] = acc[c] + vs[c] * vt[c];
applyFmacDestAcc(result, dest);
return;
case 0x2A: // MULA
for (int c = 0; c < 4; c++)
result[c] = vs[c] * vt[c];
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
case 0x2C: // SUBA
for (int c = 0; c < 4; c++)
result[c] = vs[c] - vt[c];
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
case 0x2D: // MSUBA
for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * vt[c];
applyDestAcc(result, dest);
result[c] = acc[c] - vs[c] * vt[c];
applyFmacDestAcc(result, dest);
return;
case 0x2E: // OPMULA
result[0] = vs[1] * vt[2];
result[1] = vs[2] * vt[0];
result[2] = vs[0] * vt[1];
result[3] = 0.0f;
applyDestAcc(result, dest);
applyFmacDestAcc(result, dest);
return;
case 0x2F:
case 0x30: // NOP
return;
default:
reportReservedInstruction(true, instr);
return;
}
}
@@ -453,6 +500,7 @@ void VU1Interpreter::execUpper(uint32_t instr)
case 0x32:
case 0x33:
default:
reportReservedInstruction(true, instr);
return;
}
}
+9
View File
@@ -117,6 +117,15 @@ target_link_libraries(ps2x_tests PRIVATE
ps2_runtime
)
# VU/GS tests intentionally instantiate large interpreter state.
# Windows executables default to a much smaller stack than Linux, and
# sanitizer/debug instrumentation can push these test frames over 1 MiB.
if(MSVC)
target_link_options(ps2x_tests PRIVATE "/STACK:8388608")
elseif(MINGW)
target_link_options(ps2x_tests PRIVATE "--stack,8388608")
endif()
if(COMMAND ps2x_stage_ffmpeg_runtime_dlls)
ps2x_stage_ffmpeg_runtime_dlls(ps2x_tests)
endif()
+126 -22
View File
@@ -7,6 +7,7 @@
#include <fstream>
#include <regex>
#include <sstream>
#include <utility>
using namespace ps2recomp;
@@ -823,32 +824,71 @@ void register_code_generator_tests()
t.IsTrue(ctc1Code.find("ignored") == std::string::npos, "CTC1 FCR31 should not be ignored");
});
tc.Run("VU CReg access uses CFC2/CTC2", [](TestCase &t) {
CodeGenerator gen({}, {});
tc.Run("VU CFC2/CTC2 access VI registers directly", [](TestCase& t)
{
CodeGenerator gen({}, {});
Instruction cfc2{};
cfc2.opcode = OPCODE_COP2;
cfc2.rs = COP2_CFC2;
cfc2.rt = 2;
cfc2.rd = VU0_CR_STATUS;
Instruction cfc2{};
cfc2.opcode = OPCODE_COP2;
cfc2.rs = COP2_CFC2;
cfc2.rt = 2;
cfc2.rd = 11;
std::string cfc2Code = gen.translateInstruction(cfc2);
printGeneratedCode("VU CReg access uses CFC2/CTC2 (CFC2)", cfc2Code);
t.IsTrue(cfc2Code.find("SET_GPR_U32(ctx, 2") != std::string::npos, "CFC2 should write to rt");
t.IsTrue(cfc2Code.find("ctx->vu0_status") != std::string::npos, "CFC2 STATUS should read vu0_status");
t.IsTrue(cfc2Code.find("Unimplemented CFC2 VU CReg") == std::string::npos, "CFC2 should not hit unimplemented CReg path");
std::string cfc2Code = gen.translateInstruction(cfc2);
printGeneratedCode("VU CFC2/CTC2 access VI registers directly (CFC2)", cfc2Code);
Instruction ctc2{};
ctc2.opcode = OPCODE_COP2;
ctc2.rs = COP2_CTC2;
ctc2.rt = 3;
ctc2.rd = VU0_CR_ITOP;
t.IsTrue(cfc2Code.find("SET_GPR_U32(ctx, 2") != std::string::npos, "CFC2 should write to rt");
std::string ctc2Code = gen.translateInstruction(ctc2);
printGeneratedCode("VU CReg access uses CFC2/CTC2 (CTC2)", ctc2Code);
t.IsTrue(ctc2Code.find("ctx->vu0_itop") != std::string::npos, "CTC2 ITOP should write vu0_itop");
t.IsTrue(ctc2Code.find("GPR_U32(ctx, 3) & 0x3FF") != std::string::npos, "CTC2 ITOP should mask to 10 bits");
t.IsTrue(ctc2Code.find("Unimplemented CTC2 VU CReg") == std::string::npos, "CTC2 should not hit unimplemented CReg path");
t.IsTrue(cfc2Code.find("ctx->vi[11]") != std::string::npos, "CFC2 VI11 should read VI11");
t.IsTrue(cfc2Code.find("vu0_cmsar1") == std::string::npos, "CFC2 VI11 must not read CMSAR1");
t.IsTrue(cfc2Code.find("Unimplemented") == std::string::npos, "CFC2 VI11 should be implemented");
Instruction ctc2{};
ctc2.opcode = OPCODE_COP2;
ctc2.rs = COP2_CTC2;
ctc2.rt = 3;
ctc2.rd = 4;
std::string ctc2Code = gen.translateInstruction(ctc2);
printGeneratedCode("VU CFC2/CTC2 access VI registers directly (CTC2)", ctc2Code);
t.IsTrue(ctc2Code.find("ctx->vi[4]") != std::string::npos, "CTC2 VI4 should write VI4");
t.IsTrue(ctc2Code.find("static_cast<uint16_t>(GPR_U32(ctx, 3))") != std::string::npos, "CTC2 VI4 should store the low 16 bits");
t.IsTrue(ctc2Code.find("vu0_i") == std::string::npos, "CTC2 VI4 must not write the I register");
t.IsTrue(ctc2Code.find("Unimplemented") == std::string::npos, "CTC2 VI4 should be implemented");
});
tc.Run("VU special control registers use hardware indices", [](TestCase& t)
{
CodeGenerator gen({}, {});
Instruction cfc2{};
cfc2.opcode = OPCODE_COP2;
cfc2.rs = COP2_CFC2;
cfc2.rt = 2;
cfc2.rd = VU0_CR_STATUS;
std::string cfc2Code = gen.translateInstruction(cfc2);
printGeneratedCode("VU special control registers use hardware indices (STATUS)", cfc2Code);
t.IsTrue(cfc2Code.find("SET_GPR_U32(ctx, 2") != std::string::npos,"CFC2 should write to rt");
t.IsTrue(cfc2Code.find("ctx->vu0_status") != std::string::npos, "CFC2 STATUS should read vu0_status");
t.IsTrue(cfc2Code.find("Unimplemented") == std::string::npos,"CFC2 STATUS should be implemented");
Instruction ctc2{};
ctc2.opcode = OPCODE_COP2;
ctc2.rs = COP2_CTC2;
ctc2.rt = 3;
ctc2.rd = VU0_CR_FBRST;
std::string ctc2Code = gen.translateInstruction(ctc2);
printGeneratedCode("VU special control registers use hardware indices (FBRST)", ctc2Code);
t.IsTrue(ctc2Code.find("ctx->vu0_fbrst") != std::string::npos, "CTC2 register 28 should write FBRST");
t.IsTrue(ctc2Code.find("vu0_itop") == std::string::npos, "CTC2 register 28 must not write ITOP");
t.IsTrue(ctc2Code.find("Unimplemented") == std::string::npos, "CTC2 FBRST should be implemented");
});
tc.Run("scalar logical immediates emit low64 operations", [](TestCase &t) {
@@ -1095,6 +1135,70 @@ void register_code_generator_tests()
t.IsTrue(out.find("ctx->vu0_vf[25]") == std::string::npos, "S1 q/i must not use rs(format) as register index");
});
tc.Run("VU0 destination MADD and MSUB forms preserve ACC", [](TestCase &t) {
Instruction inst{};
inst.rt = 7;
inst.rd = 11;
inst.sa = 3;
inst.function = 0;
inst.vectorInfo.vectorField = 0xE;
CodeGenerator gen({}, {});
const std::vector<std::pair<const char *, std::string>> emitted = {
{"MADD field", gen.translateVU_VMADD_Field(inst)},
{"MADD", gen.translateVU_VMADD(inst)},
{"MADDq", gen.translateVU_VMADDq(inst)},
{"MADDi", gen.translateVU_VMADDi(inst)},
{"MSUB field", gen.translateVU_VMSUB_Field(inst)},
{"MSUB", gen.translateVU_VMSUB(inst)},
{"MSUBq", gen.translateVU_VMSUBq(inst)},
{"MSUBi", gen.translateVU_VMSUBi(inst)},
{"OPMSUB", gen.translateVU_VOPMSUB(inst)},
};
for (const auto &[name, code] : emitted)
{
const std::string message =
std::string(name) + " writes VF and must not overwrite ACC";
t.IsTrue(code.find("ctx->vu0_acc = res") == std::string::npos,
message.c_str());
t.IsTrue(code.find("PS2_VADD(ctx->vu0_acc") != std::string::npos ||
code.find("PS2_VSUB(ctx->vu0_acc") != std::string::npos,
(std::string(name) + " must still read ACC").c_str());
}
const std::string madda = gen.translateVU_VMADDA(inst);
t.IsTrue(madda.find("ctx->vu0_acc =") != std::string::npos,
"MADDA must continue writing ACC");
});
tc.Run("VU0 OPMULA and OPMSUB use cross-product lane permutations", [](TestCase &t) {
Instruction inst{};
inst.rt = 7;
inst.rd = 11;
inst.sa = 3;
inst.vectorInfo.vectorField = 0xE;
CodeGenerator gen({}, {});
const std::string opmula = gen.translateVU_VOPMULA(inst);
const std::string opmsub = gen.translateVU_VOPMSUB(inst);
for (const std::string *code : {&opmula, &opmsub})
{
t.IsTrue(code->find("_MM_SHUFFLE(3,0,2,1)") != std::string::npos,
"OPM source Fs must be permuted to y,z,x");
t.IsTrue(code->find("_MM_SHUFFLE(3,1,0,2)") != std::string::npos,
"OPM source Ft must be permuted to z,x,y");
t.IsTrue(code->find("PS2_VMUL(fs_yzx, ft_zxy)") != std::string::npos,
"OPM product must use the permuted operands");
}
t.IsTrue(opmula.find("ctx->vu0_acc =") != std::string::npos,
"OPMULA must write the permuted product to ACC");
t.IsTrue(opmsub.find("ctx->vu0_acc = res") == std::string::npos,
"OPMSUB must preserve ACC after producing the cross product");
});
tc.Run("VU0 S2 vector ops use rd as source and rt as destination", [](TestCase &t) {
Instruction inst{};
inst.opcode = OPCODE_COP2;
+625 -69
View File
@@ -5,6 +5,7 @@
#include "ps2_syscalls.h"
#include "runtime/ps2_gs_gpu.h"
#include "runtime/ps2_gs_memory.h"
#include "runtime/ps2_gs_rasterizer.h"
#include "runtime/ps2_gs_psmct32.h"
#include "runtime/ps2_gs_psmt4.h"
#include "runtime/ps2_gs_psmt8.h"
@@ -297,6 +298,57 @@ namespace
t.Equals(probe, expectedBase, message);
runtime.guestFree(probe);
}
struct GsPixelTestResult
{
uint32_t framebuffer = 0u;
uint32_t depth = 0u;
};
GsPixelTestResult drawGsPixelForTests(uint8_t framePsm,
uint64_t testReg,
bool zmask,
uint32_t initialFramebuffer,
uint32_t initialDepth,
uint8_t sourceAlpha)
{
constexpr uint32_t kFrameBlock = 0u;
constexpr uint32_t kDepthBlock = 32u;
constexpr uint32_t kSourceDepth = 0x22222222u;
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
gs.WriteVram(framePsm, kFrameBlock, 1u, 0u, 0u, initialFramebuffer);
gs.WriteVram(GS_PSM_Z32, kDepthBlock, 1u, 0u, 0u, initialDepth);
const uint64_t frame =
(1ull << 16) |
(static_cast<uint64_t>(framePsm) << 24);
const uint64_t zbuf =
1ull |
(static_cast<uint64_t>(zmask ? 1u : 0u) << 32);
const uint64_t rgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(static_cast<uint64_t>(sourceAlpha) << 24) |
(0x3F800000ull << 32);
gs.writeRegister(GS_REG_FRAME_1, frame);
gs.writeRegister(GS_REG_ZBUF_1, zbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, testReg);
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_POINT));
gs.writeRegister(GS_REG_RGBAQ, rgbaq);
gs.writeRegister(GS_REG_XYZ2, static_cast<uint64_t>(kSourceDepth) << 32);
return {
gs.ReadVram(framePsm, kFrameBlock, 1u, 0u, 0u),
gs.ReadVram(GS_PSM_Z32, kDepthBlock, 1u, 0u, 0u),
};
}
}
void register_ps2_gs_tests()
@@ -623,6 +675,126 @@ void register_ps2_gs_tests()
"context-targeted clear should leave the other context framebuffer untouched");
});
tc.Run("XYZ3 culls a triangle strip primitive without desynchronizing the vertex queue", [](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);
constexpr uint32_t kColor = 0xFF0000FFu;
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(6ull << 16) |
(6ull << 48);
auto xyz = [](uint32_t x, uint32_t y) -> uint64_t
{
return static_cast<uint64_t>(x * 16u) |
(static_cast<uint64_t>(y * 16u) << 16);
};
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_TRISTRIP));
gs.writeRegister(GS_REG_RGBAQ, kColor);
// ABC is rejected by XYZ3. D must then draw BCD, not stale ABC.
gs.writeRegister(GS_REG_XYZ2, xyz(0u, 0u));
gs.writeRegister(GS_REG_XYZ2, xyz(6u, 0u));
gs.writeRegister(GS_REG_XYZ3, xyz(0u, 6u));
gs.writeRegister(GS_REG_XYZ2, xyz(6u, 6u));
t.Equals(readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u), 0u,
"XYZ3 should suppress the completed ABC triangle");
t.Equals(readReferencePSMCT32Pixel(vram, 0u, 1u, 4u, 4u), kColor,
"the next XYZ2 should draw BCD from the advanced strip queue");
});
tc.Run("GS fog blends the shaded color toward FOGCOL before framebuffer blending", [](TestCase &t)
{
auto renderFoggedPoint = [](bool fogEnabled, uint8_t fog, uint32_t fogColor = 0u) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kWhite = 0x80FFFFFFull;
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_FOGCOL, fogColor);
gs.writeRegister(
GS_REG_PRIM,
static_cast<uint64_t>(GS_PRIM_POINT) |
(static_cast<uint64_t>(fogEnabled ? 1u : 0u) << 5));
gs.writeRegister(GS_REG_RGBAQ, kWhite);
gs.writeRegister(GS_REG_FOG, static_cast<uint64_t>(fog) << 56);
gs.writeRegister(GS_REG_XYZ2, 0ull);
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
t.Equals(renderFoggedPoint(false, 0x80u), 0x80FFFFFFu,
"FOG and FOGCOL must not affect primitives with FGE disabled");
t.Equals(renderFoggedPoint(true, 0x80u), 0x807F7F7Fu,
"F=0x80 over black FOGCOL should halve the point RGB and preserve alpha");
t.Equals(renderFoggedPoint(true, 0x00u), 0x80000000u,
"F=0 should replace the point RGB with black FOGCOL");
t.Equals(renderFoggedPoint(true, 0x00u, 0x00302010u), 0x802F1F0Fu,
"F=0 should replace point RGB with the programmed FOGCOL");
});
tc.Run("PRMODE supplies primitive attributes while PRMODECONT AC is clear", [](TestCase &t)
{
auto renderPoint = [](bool usePrmodeAttributes) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_FOGCOL, 0ull);
gs.writeRegister(GS_REG_RGBAQ, 0x80FFFFFFull);
gs.writeRegister(GS_REG_FOG, 0ull);
gs.writeRegister(GS_REG_PRMODE, 1ull << 5);
gs.writeRegister(GS_REG_PRMODECONT, usePrmodeAttributes ? 0ull : 1ull);
// FGE is clear in PRIM. AC decides whether that clear bit or
// PRMODE's set bit supplies the effective fog enable.
gs.writeRegister(GS_REG_PRIM, static_cast<uint64_t>(GS_PRIM_POINT));
gs.writeRegister(GS_REG_XYZ2, 0ull);
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
t.Equals(renderPoint(true), 0x80000000u,
"AC=0 should retain FGE from PRMODE across a PRIM write");
t.Equals(renderPoint(false), 0x80FFFFFFu,
"AC=1 should source FGE from PRIM instead of PRMODE");
});
tc.Run("PABE bypasses alpha blend for low-alpha source pixels", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -1557,6 +1729,20 @@ void register_ps2_gs_tests()
t.Equals(regs.display2, display2, "A+D should write GS DISPLAY2");
});
tc.Run("reserved PSM 0x3F uses null VRAM handlers", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0xA5u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
t.Equals(gs.ReadVram(0x3Fu, 0u, 1u, 0u, 0u), 0u,
"reserved PSM reads should use the null handler");
gs.WriteVram(0x3Fu, 0u, 1u, 0u, 0u, 0x0005180Bu);
t.Equals(static_cast<uint32_t>(vram[0]), 0xA5u,
"reserved PSM writes should leave VRAM unchanged");
});
tc.Run("PSMT4 address mapping matches GS manual layout", [](TestCase &t)
{
constexpr uint32_t kBaseBlock = 0u;
@@ -2488,6 +2674,161 @@ void register_ps2_gs_tests()
"T8 CSM1 CLUT sampling should read CT32-uploaded palette entries through GS swizzled addressing");
});
tc.Run("GS T8 CSM1 applies CSA and masks CSA bit 4 for CT32 CLUTs", [](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);
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kClutCbp = 128u;
constexpr uint64_t kFrameReg =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_T8) << 20) |
(0ull << 26) |
(0ull << 30) |
(1ull << 34) |
(1ull << 35) |
(static_cast<uint64_t>(kClutCbp) << 37) |
(static_cast<uint64_t>(GS_PSM_CT32) << 51) |
(17ull << 56);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_SPRITE) |
(1ull << 4) |
(1ull << 8);
constexpr uint32_t kExpectedColor = 0xFF204080u;
constexpr uint32_t kWrongNoCsaColor = 0xFF00FF00u;
constexpr uint32_t kWrongBit4Color = 0xFFFF0000u;
const uint32_t texOff = GSPSMT8::addrPSMT8(kTexTbp, 1u, 0u, 0u);
vram[texOff] = 0u;
// CSA=17 is CSA=1 for a CT32 CLUT. Logical entry 16 is at
// physical CSM1 entry 8 after address bits 3 and 4 are swapped.
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 0u, 0u, kWrongNoCsaColor);
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 8u, 0u, kExpectedColor);
gs.WriteVram(GS_PSM_CT32, kClutCbp, 1u, 8u, 16u, kWrongBit4Color);
gs.writeRegister(GS_REG_FRAME_1, kFrameReg);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_ALPHA_1, 0ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, kExpectedColor,
"T8 CSM1 should offset by CSA while CT32 ignores the fifth CSA bit");
});
tc.Run("GS T4 CSM1 preserves CSA bit 4 for CT16 CLUTs", [](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);
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kClutCbp = 128u;
constexpr uint64_t kFrameReg =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_T4) << 20) |
(0ull << 26) |
(0ull << 30) |
(1ull << 34) |
(1ull << 35) |
(static_cast<uint64_t>(kClutCbp) << 37) |
(static_cast<uint64_t>(GS_PSM_CT16) << 51) |
(16ull << 56);
constexpr uint64_t kTexa = (0x80ull << 32);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_SPRITE) |
(1ull << 4) |
(1ull << 8);
constexpr uint16_t kExpectedRed = 0x801Fu;
constexpr uint16_t kWrongGreen = 0x83E0u;
constexpr uint32_t kExpectedColor = 0x800000F8u;
writePSMT4Texel(vram, kTexTbp, 1u, 0u, 0u, 1u);
// CSA=16 selects the upper half of a CT16 CLUT. CSM1 swaps bits
// 3 and 4 but must preserve address bit 8.
gs.WriteVram(GS_PSM_CT16, kClutCbp, 1u, 1u, 0u, kWrongGreen);
gs.WriteVram(GS_PSM_CT16, kClutCbp, 1u, 1u, 16u, kExpectedRed);
gs.writeRegister(GS_REG_FRAME_1, kFrameReg);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_ALPHA_1, 0ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_TEXA, kTexa);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, kExpectedColor,
"CT16 CSM1 should retain CSA[4] instead of aliasing the upper palette onto the lower one");
});
tc.Run("GS TEX0 dimensions saturate at 1024 pixels", [](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);
GSRasterizer rasterizer;
constexpr uint32_t kTexTbp = 64u;
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(16ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(15ull << 26) |
(15ull << 30) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4);
constexpr uint32_t kExpectedColor = 0xFF3366CCu;
constexpr uint32_t kUnsaturatedColor = 0xFF00FF00u;
gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 1u, 0u, kExpectedColor);
gs.WriteVram(GS_PSM_CT32, kTexTbp, 16u, 32u, 0u, kUnsaturatedColor);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_PRIM, kPrim);
const uint32_t sampled =
rasterizer.sampleTexture(&gs, 1.0f / 1024.0f, 0.0f, 1.0f, 0u, 0u);
t.Equals(sampled, kExpectedColor,
"TW/TH values above 10 should address a 1024-pixel texture instead of growing beyond GS limits");
});
tc.Run("GS TEX2 updates CLUT state independently from TEX0", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -2975,97 +3316,312 @@ void register_ps2_gs_tests()
"linear filtering should preserve the shared opaque alpha from the CLUT entries");
});
tc.Run("GS alpha test AFAIL framebuffer-only still writes the pixel", [](TestCase &t)
tc.Run("GS CLAMP modes transform texture coordinates before sampling", [](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);
auto renderConstantUv = [](uint64_t clampReg,
uint16_t fixedU,
uint16_t fixedV) -> uint32_t
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(1ull << 12) | // AFAIL = FB_ONLY
(1ull << 17); // ZTST = ALWAYS
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kTexel0 = 0x800000FFu;
constexpr uint32_t kTexel1 = 0x8000FF00u;
constexpr uint32_t kTexel2 = 0x80FF0000u;
constexpr uint32_t kTexel3 = 0x80FFFFFFu;
constexpr uint32_t kTexelV3 = 0x80FFFF00u;
constexpr uint64_t kFrame =
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(2ull << 26) |
(2ull << 30) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4) |
(1ull << 8);
constexpr uint64_t kRgbaq = 0x3F80000080808080ull;
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 0u, 0u, kTexel0);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 1u, 0u, kTexel1);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 2u, 0u, kTexel2);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 3u, 0u, kTexel3);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 0u, 3u, kTexelV3);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0x00563412u,
"AFAIL=FB_ONLY should still update the framebuffer when the alpha test fails");
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, (3ull << 16) | (3ull << 48));
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_CLAMP_1, clampReg);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
const uint64_t uv =
static_cast<uint64_t>(fixedU) |
(static_cast<uint64_t>(fixedV) << 16);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, 32ull);
gs.writeRegister(GS_REG_UV, uv);
gs.writeRegister(GS_REG_XYZ2, (32ull << 16));
return readReferencePSMCT32Pixel(vram, 0u, 1u, 0u, 0u);
};
constexpr uint64_t kClamp = 1ull;
constexpr uint64_t kRegionClamp =
2ull |
(1ull << 4) |
(2ull << 14);
constexpr uint64_t kRegionRepeat =
3ull |
(1ull << 4) |
(2ull << 14);
t.Equals(renderConstantUv(0ull, 4u * 16u, 0u), 0x800000FFu,
"REPEAT should wrap texel 4 to texel 0 for a four-wide texture");
t.Equals(renderConstantUv(0ull, 0u, 4u * 16u), 0x800000FFu,
"REPEAT should wrap texel row 4 to row 0 for a four-high texture");
t.Equals(renderConstantUv(kClamp, 4u * 16u, 0u), 0x80FFFFFFu,
"CLAMP should hold texel 4 at the last texel");
t.Equals(renderConstantUv(kRegionClamp, 3u * 16u, 0u), 0x80FF0000u,
"REGION_CLAMP should hold texel 3 at MAXU=2");
t.Equals(renderConstantUv(kRegionRepeat, 4u * 16u, 0u), 0x80FF0000u,
"REGION_REPEAT should calculate (U & UMSK) | UFIX");
});
tc.Run("GS alpha test AFAIL RGB-only preserves destination alpha", [](TestCase &t)
tc.Run("GS STQ triangle interpolation divides homogeneous coordinates after DDA", [](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);
constexpr uint32_t kTexTbp = 64u;
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kZbuf = (1ull << 32);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(3ull << 12) | // AFAIL = RGB_ONLY
(1ull << 17); // ZTST = ALWAYS
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_CT32) << 20) |
(2ull << 26) |
(1ull << 34) |
(1ull << 35);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
constexpr uint32_t kExisting = 0xAB030201u;
static_cast<uint64_t>(GS_PRIM_TRIANGLE) |
(1ull << 4);
constexpr uint32_t kAffineTexel = 0x800000FFu;
constexpr uint32_t kHomogeneousTexel = 0x8000FF00u;
std::memcpy(vram.data(), &kExisting, sizeof(kExisting));
auto packFloat = [](float value) -> uint32_t
{
uint32_t bits = 0u;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
};
auto packSt = [&](float s, float tVal) -> uint64_t
{
return static_cast<uint64_t>(packFloat(s)) |
(static_cast<uint64_t>(packFloat(tVal)) << 32);
};
auto packRgbaq = [&](float q) -> uint64_t
{
return 0x80808080ull |
(static_cast<uint64_t>(packFloat(q)) << 32);
};
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 1u, 0u, kAffineTexel);
writeReferencePSMCT32Pixel(vram, kTexTbp, 1u, 2u, 0u, kHomogeneousTexel);
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_ZBUF_1, kZbuf);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_SCISSOR_1, (4ull << 16) | (4ull << 48));
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0x30000ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_CLAMP_1, 1ull);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0xAB563412u,
"AFAIL=RGB_ONLY should update RGB while preserving destination alpha");
gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(1.0f));
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_ST, packSt(2.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(2.0f));
gs.writeRegister(GS_REG_XYZ2, 64ull);
gs.writeRegister(GS_REG_ST, packSt(0.0f, 0.0f));
gs.writeRegister(GS_REG_RGBAQ, packRgbaq(1.0f));
gs.writeRegister(GS_REG_XYZ2, (64ull << 16));
const uint32_t pixel =
readReferencePSMCT32Pixel(vram, 0u, 1u, 1u, 1u);
t.Equals(pixel, kHomogeneousTexel,
"the DDA should interpolate S=0.75 and Q=1.375, selecting texel 2 after S/Q");
});
tc.Run("GS alpha-test AFAIL independently masks framebuffer and depth", [](TestCase &t)
{
constexpr uint32_t kInitialFramebuffer = 0xAB030201u;
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTestBase =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(1ull << 16) | // ZTE
(1ull << 17); // ZTST = ALWAYS
const GsPixelTestResult keep =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (0ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(keep.framebuffer, kInitialFramebuffer,
"AFAIL=KEEP should preserve the framebuffer");
t.Equals(keep.depth, kInitialDepth,
"AFAIL=KEEP should preserve depth");
const GsPixelTestResult framebufferOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (1ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(framebufferOnly.framebuffer, 0x00563412u,
"AFAIL=FB_ONLY should update RGBA");
t.Equals(framebufferOnly.depth, kInitialDepth,
"AFAIL=FB_ONLY should preserve depth");
const GsPixelTestResult depthOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (2ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(depthOnly.framebuffer, kInitialFramebuffer,
"AFAIL=ZB_ONLY should preserve the framebuffer");
t.Equals(depthOnly.depth, 0x22222222u,
"AFAIL=ZB_ONLY should update depth");
const GsPixelTestResult rgbOnly =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (3ull << 12), false,
kInitialFramebuffer, kInitialDepth, 0x00u);
t.Equals(rgbOnly.framebuffer, 0xAB563412u,
"AFAIL=RGB_ONLY should preserve destination alpha on CT32");
t.Equals(rgbOnly.depth, kInitialDepth,
"AFAIL=RGB_ONLY should preserve depth");
});
tc.Run("GS RGB_ONLY falls back to FB_ONLY outside CT32", [](TestCase &t)
{
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTest =
1ull |
(5ull << 1) |
(0x80ull << 4) |
(3ull << 12) |
(1ull << 16) |
(1ull << 17);
const GsPixelTestResult ct24 =
drawGsPixelForTests(GS_PSM_CT24, kTest, false,
0x00030201u, kInitialDepth, 0x00u);
t.Equals(ct24.framebuffer, 0x00563412u,
"RGB_ONLY should write the full CT24 framebuffer pixel");
t.Equals(ct24.depth, kInitialDepth,
"RGB_ONLY-as-FB_ONLY should preserve CT24 depth");
const GsPixelTestResult ct16 =
drawGsPixelForTests(GS_PSM_CT16, kTest, false,
0x8001u, kInitialDepth, 0x00u);
t.Equals(ct16.framebuffer, 0x28C2u,
"RGB_ONLY should write RGB and alpha for CT16");
t.Equals(ct16.depth, kInitialDepth,
"RGB_ONLY-as-FB_ONLY should preserve CT16 depth");
});
tc.Run("GS ZMSK suppresses depth without suppressing framebuffer writes", [](TestCase &t)
{
constexpr uint64_t kTest =
1ull |
(5ull << 1) |
(0x80ull << 4) |
(1ull << 16) |
(1ull << 17);
const GsPixelTestResult result =
drawGsPixelForTests(GS_PSM_CT32, kTest, true,
0xAB030201u, 0x11111111u, 0x80u);
t.Equals(result.framebuffer, 0x80563412u,
"a passing alpha test should write the framebuffer");
t.Equals(result.depth, 0x11111111u,
"ZMSK should preserve depth");
});
tc.Run("GS DATE and DATM inspect the framebuffer-format alpha bit", [](TestCase &t)
{
constexpr uint32_t kInitialDepth = 0x11111111u;
constexpr uint64_t kTestBase =
(1ull << 14) | // DATE
(1ull << 16) | // ZTE
(1ull << 17); // ZTST = ALWAYS
const GsPixelTestResult ct32ZeroPass =
drawGsPixelForTests(GS_PSM_CT32, kTestBase, false,
0x00030201u, kInitialDepth, 0x80u);
t.Equals(ct32ZeroPass.framebuffer, 0x80563412u,
"DATM=0 should accept a clear CT32 alpha bit");
t.Equals(ct32ZeroPass.depth, 0x22222222u,
"a passing CT32 DATE should allow depth");
const GsPixelTestResult ct32OneFail =
drawGsPixelForTests(GS_PSM_CT32, kTestBase, false,
0x80030201u, kInitialDepth, 0x80u);
t.Equals(ct32OneFail.framebuffer, 0x80030201u,
"DATM=0 should reject a set CT32 alpha bit");
t.Equals(ct32OneFail.depth, kInitialDepth,
"a failing CT32 DATE should reject depth");
const GsPixelTestResult ct32OnePass =
drawGsPixelForTests(GS_PSM_CT32, kTestBase | (1ull << 15), false,
0x80030201u, kInitialDepth, 0x80u);
t.Equals(ct32OnePass.framebuffer, 0x80563412u,
"DATM=1 should accept a set CT32 alpha bit");
const GsPixelTestResult ct16ZeroPass =
drawGsPixelForTests(GS_PSM_CT16, kTestBase, false,
0x0001u, kInitialDepth, 0x80u);
t.Equals(ct16ZeroPass.framebuffer, 0xA8C2u,
"DATM=0 should accept a clear CT16 alpha bit");
const GsPixelTestResult ct16OneFail =
drawGsPixelForTests(GS_PSM_CT16, kTestBase, false,
0x8001u, kInitialDepth, 0x80u);
t.Equals(ct16OneFail.framebuffer, 0x8001u,
"DATM=0 should reject a set CT16 alpha bit");
t.Equals(ct16OneFail.depth, kInitialDepth,
"a failing CT16 DATE should reject depth");
const GsPixelTestResult ct16OnePass =
drawGsPixelForTests(GS_PSM_CT16, kTestBase | (1ull << 15), false,
0x8001u, kInitialDepth, 0x80u);
t.Equals(ct16OnePass.framebuffer, 0xA8C2u,
"DATM=1 should accept a set CT16 alpha bit");
const GsPixelTestResult ct24DatmZero =
drawGsPixelForTests(GS_PSM_CT24, kTestBase, false,
0x00030201u, kInitialDepth, 0x80u);
const GsPixelTestResult ct24DatmOne =
drawGsPixelForTests(GS_PSM_CT24, kTestBase | (1ull << 15), false,
0x00030201u, kInitialDepth, 0x80u);
t.Equals(ct24DatmZero.framebuffer, 0x00563412u,
"CT24 DATE should pass for DATM=0");
t.Equals(ct24DatmOne.framebuffer, 0x00563412u,
"CT24 DATE should pass for DATM=1");
t.Equals(ct24DatmOne.depth, 0x22222222u,
"CT24 DATE should not block depth");
});
tc.Run("GS triangle fan subpixel quad fills rows without interior holes", [](TestCase &t)
+63
View File
@@ -440,6 +440,69 @@ void register_ps2_iop_tests()
"reset should restore per-instance service state");
});
tc.Run("LotR sound update completes queued PlayStream slots", [](TestCase &t)
{
FakeIopHost host;
ps2x::iop::IopSubsystem subsystem(host);
std::string error;
t.IsTrue(subsystem.configure({"SLUS_205.78", 0u, 0u}, &error),
"LotR profile should configure");
constexpr uint32_t kSendAddress = 0x0800u;
constexpr uint32_t kReceiveAddress = 0x1000u;
constexpr uint16_t kStreamSlot = 7u;
const std::array<uint16_t, 10> playStreamPacket = {
1u, // command count
1u, // PlayStream
7u, // argument count
0u,
static_cast<uint16_t>(kStreamSlot << 8u),
0u,
0u,
0u,
0u,
0u,
};
t.IsTrue(host.writeGuest(kSendAddress,
playStreamPacket.data(),
sizeof(playStreamPacket)),
"PlayStream command packet should fit in guest memory");
ps2x::iop::RpcRequest request{};
request.sid = 0x00012345u;
request.send = {kSendAddress, sizeof(playStreamPacket)};
request.receive = {kReceiveAddress, 0x100u};
t.IsTrue(subsystem.handleRpc(request).handled,
"LotR sound service should handle PlayStream");
t.Equals(host.readWord(kReceiveAddress), 1u,
"PlayStream response should expose one active record");
const uint32_t packedStream = host.readWord(kReceiveAddress + 4u);
t.Equals((packedStream >> 4u) & 0x3Fu,
static_cast<uint32_t>(kStreamSlot),
"active record should identify the queued EE stream slot");
t.Equals(host.readWord(kReceiveAddress + 0x24u), 1u,
"response counter should follow the active record");
const std::array<uint16_t, 5> statusPacket = {
1u, // command count
9u, // GetStatus
2u, // argument count
kStreamSlot,
0u,
};
t.IsTrue(host.writeGuest(kSendAddress, statusPacket.data(), sizeof(statusPacket)),
"GetStatus command packet should fit in guest memory");
request.send.size = sizeof(statusPacket);
t.IsTrue(subsystem.handleRpc(request).handled,
"LotR sound service should handle the following status update");
t.Equals(host.readWord(kReceiveAddress), 0u,
"the update after PlayStream should report no active records");
t.Equals(host.readWord(kReceiveAddress + 4u), 2u,
"empty response counter should return to the base offset");
});
tc.Run("TSNDDRV uses profile checksum bindings without writing invalid ports", [](TestCase &t)
{
FakeIopHost host(0x02000000u);
+188
View File
@@ -155,6 +155,102 @@ static bool writeMinimalMipsElfWithJalFallbackTarget(const std::filesystem::path
return writer.save(elfPath.string());
}
static bool writeMinimalMipsElfWithInitializer(const std::filesystem::path &elfPath,
const std::string &functionName,
uint32_t initializerTarget)
{
ELFIO::elfio writer;
writer.create(ELFIO::ELFCLASS32, ELFIO::ELFDATA2LSB);
writer.set_os_abi(ELFIO::ELFOSABI_NONE);
writer.set_type(ELFIO::ET_EXEC);
writer.set_machine(ELFIO::EM_MIPS);
writer.set_entry(0x00100000u);
ELFIO::section *text = writer.sections.add(".text");
text->set_type(ELFIO::SHT_PROGBITS);
text->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR);
text->set_addr_align(4);
text->set_address(0x00100000u);
const std::array<uint32_t, 2> textWords = {
0x03E00008u, // jr $ra
0x00000000u, // nop
};
text->set_data(reinterpret_cast<const char *>(textWords.data()),
static_cast<ELFIO::Elf_Word>(textWords.size() * sizeof(uint32_t)));
ELFIO::section *ctors = writer.sections.add(".ctors");
ctors->set_type(ELFIO::SHT_PROGBITS);
ctors->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE);
ctors->set_addr_align(4);
ctors->set_address(0x00200000u);
ctors->set_data(reinterpret_cast<const char *>(&initializerTarget),
static_cast<ELFIO::Elf_Word>(sizeof(initializerTarget)));
ELFIO::section *strtab = writer.sections.add(".strtab");
strtab->set_type(ELFIO::SHT_STRTAB);
strtab->set_addr_align(1);
ELFIO::section *symtab = writer.sections.add(".symtab");
symtab->set_type(ELFIO::SHT_SYMTAB);
symtab->set_info(1);
symtab->set_link(strtab->get_index());
symtab->set_addr_align(4);
symtab->set_entry_size(writer.get_default_entry_size(ELFIO::SHT_SYMTAB));
ELFIO::symbol_section_accessor symbols(writer, symtab);
ELFIO::string_section_accessor strings(strtab);
symbols.add_symbol(strings, "", 0, 0,
ELFIO::STB_LOCAL, ELFIO::STT_NOTYPE, 0, ELFIO::SHN_UNDEF);
symbols.add_symbol(strings, functionName.c_str(), text->get_address(), text->get_size(),
ELFIO::STB_GLOBAL, ELFIO::STT_FUNC, 0, text->get_index());
ELFIO::segment *textSegment = writer.segments.add();
textSegment->set_type(ELFIO::PT_LOAD);
textSegment->set_flags(ELFIO::PF_R | ELFIO::PF_X);
textSegment->set_align(0x1000);
textSegment->add_section_index(text->get_index(), text->get_addr_align());
ELFIO::segment *dataSegment = writer.segments.add();
dataSegment->set_type(ELFIO::PT_LOAD);
dataSegment->set_flags(ELFIO::PF_R | ELFIO::PF_W);
dataSegment->set_align(0x1000);
dataSegment->add_section_index(ctors->get_index(), ctors->get_addr_align());
return writer.save(elfPath.string());
}
static bool writeRecompilerTestConfig(const std::filesystem::path &configPath,
const std::filesystem::path &elfPath,
const std::filesystem::path &outputPath,
const std::vector<std::string> &skip,
const std::vector<std::string> &stubs = {})
{
std::ofstream config(configPath);
if (!config)
return false;
config << "[general]\n";
config << "input = \"" << elfPath.generic_string() << "\"\n";
config << "output = \"" << outputPath.generic_string() << "\"\n";
config << "skip = [";
for (size_t i = 0; i < skip.size(); ++i)
{
if (i != 0u)
config << ", ";
config << '"' << skip[i] << '"';
}
config << "]\n";
config << "stubs = [";
for (size_t i = 0; i < stubs.size(); ++i)
{
if (i != 0u)
config << ", ";
config << '"' << stubs[i] << '"';
}
config << "]\n";
return static_cast<bool>(config);
}
void register_ps2_recompiler_tests()
{
MiniTest::Case("PS2Recompiler", [](TestCase &tc)
@@ -890,6 +986,98 @@ void register_ps2_recompiler_tests()
"__sbprintf should be left for recompilation");
});
tc.Run("initializer skips fall back to guest recompilation", [](TestCase &t) {
const std::string uniqueSuffix =
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const std::filesystem::path tempRoot =
std::filesystem::temp_directory_path() / ("ps2recomp-initializer-" + uniqueSuffix);
const std::filesystem::path elfPath = tempRoot / "initializer.elf";
const std::filesystem::path configPath = tempRoot / "initializer.toml";
const std::filesystem::path outputPath = tempRoot / "output";
std::filesystem::create_directories(tempRoot);
const bool elfWritten =
writeMinimalMipsElfWithInitializer(elfPath, "__sinit_test.cpp", 0x00100000u);
const bool configWritten =
writeRecompilerTestConfig(configPath, elfPath, outputPath, {"__sinit_test.cpp"});
t.IsTrue(elfWritten && configWritten,
"initializer regression inputs should be generated");
if (elfWritten && configWritten)
{
PS2Recompiler recompiler(configPath.string());
t.IsTrue(recompiler.initialize(),
"initializer regression config should initialize");
t.IsTrue(recompiler.recompile(),
"a decodable skipped initializer should use guest fallback");
const RecompilerReporter::Counters &counters = recompiler.reportCounters();
t.Equals(counters.correctnessCriticalGuestFallbacks, static_cast<size_t>(1u),
"the ignored initializer skip should be reported");
t.Equals(counters.correctnessCriticalFailures, static_cast<size_t>(0u),
"guest fallback should avoid a correctness-critical failure");
t.Equals(counters.functionsSkipped, static_cast<size_t>(0u),
"the initializer should not remain skipped");
t.Equals(counters.functionsRecompiled, static_cast<size_t>(1u),
"the original initializer body should be recompiled");
}
std::error_code removeError;
std::filesystem::remove_all(tempRoot, removeError);
});
tc.Run("missing constructor-table targets fail recompilation", [](TestCase &t) {
const std::string uniqueSuffix =
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const std::filesystem::path tempRoot =
std::filesystem::temp_directory_path() / ("ps2recomp-missing-initializer-" + uniqueSuffix);
const std::filesystem::path elfPath = tempRoot / "initializer.elf";
const std::filesystem::path configPath = tempRoot / "initializer.toml";
const std::filesystem::path outputPath = tempRoot / "output";
std::filesystem::create_directories(tempRoot);
const bool elfWritten =
writeMinimalMipsElfWithInitializer(elfPath, "ordinary_entry", 0x00100040u);
const bool configWritten =
writeRecompilerTestConfig(configPath, elfPath, outputPath, {});
t.IsTrue(elfWritten && configWritten,
"missing-initializer regression inputs should be generated");
if (elfWritten && configWritten)
{
{
PS2Recompiler recompiler(configPath.string());
t.IsTrue(recompiler.initialize(),
"missing-initializer regression config should initialize");
t.IsFalse(recompiler.recompile(),
"an unresolved .ctors target should be correctness-fatal");
t.Equals(recompiler.reportCounters().correctnessCriticalFailures,
static_cast<size_t>(1u),
"the unresolved constructor target should appear in the report");
}
const bool overrideWritten =
writeRecompilerTestConfig(
configPath, elfPath, outputPath, {},
{"memclr@0x00100040"});
t.IsTrue(overrideWritten,
"manual initializer override config should be generated");
if (overrideWritten)
{
PS2Recompiler overridden(configPath.string());
t.IsTrue(overridden.initialize(),
"manual initializer override should initialize");
t.IsTrue(overridden.recompile(),
"a resolved address-bound handler should satisfy the constructor target");
t.Equals(overridden.reportCounters().functionsStubbed,
static_cast<size_t>(1u),
"the resolved manual initializer should be emitted as a stub binding");
}
}
std::error_code removeError;
std::filesystem::remove_all(tempRoot, removeError);
});
tc.Run("respect max length for .cpp filenames", [](TestCase& t) {
t.IsTrue(PS2Recompiler::ClampFilenameLength("ReallyLongFunctionNameReallyLongFunctionNameReallyLongFunctionName_0x12345678",".cpp",50).length() <= 50,"Function name must be max 50 characters");
+148 -2
View File
@@ -82,12 +82,38 @@ namespace
0x28u;
}
uint32_t makeVuIaddiu(uint8_t it, uint8_t is, int16_t immediate)
{
return (0x08u << 25) |
(static_cast<uint32_t>(it & 0xFu) << 16) |
(static_cast<uint32_t>(is & 0xFu) << 11) |
(static_cast<uint32_t>(immediate) & 0x7FFu);
}
uint32_t makeVuLowerSpecial(uint8_t specialOp, uint8_t is,
uint8_t it = 0u, uint8_t dest = 0u)
{
return (0x40u << 25) |
(static_cast<uint32_t>(dest & 0xFu) << 21) |
(static_cast<uint32_t>(it & 0x1Fu) << 16) |
(static_cast<uint32_t>(is & 0x1Fu) << 11) |
(static_cast<uint32_t>(specialOp & 0x7Cu) << 4) |
static_cast<uint32_t>(specialOp & 0x3u) |
0x3Cu;
}
void writeVuInstructionPair(uint8_t *code, uint32_t pc, uint32_t lower, uint32_t upper)
{
std::memcpy(code + pc, &lower, sizeof(lower));
std::memcpy(code + pc + sizeof(lower), &upper, sizeof(upper));
}
uint64_t packVuInstructionPair(uint32_t lower, uint32_t upper)
{
return static_cast<uint64_t>(lower) |
(static_cast<uint64_t>(upper) << 32);
}
bool hasSignedRdWrite(const std::string &generated, uint8_t rd)
{
if (rd == 0u)
@@ -182,7 +208,7 @@ namespace
}
bool shouldPreempt = false;
for (int attempt = 0; attempt < 256 &&
for (int attempt = 0; attempt < 2048 &&
!shouldPreempt;
++attempt)
{
@@ -571,6 +597,30 @@ void register_ps2_runtime_expansion_tests()
t.IsFalse(innerPending, "inner scope must stay untouched");
});
tc.Run("guest preemption policy amortizes uncontended back-edge checks", [](TestCase &t)
{
PS2Runtime runtime;
uint32_t firstPreemptionCall = 0u;
// Use a fresh host thread so this assertion starts with a fresh
// thread-local back-edge counter.
std::thread worker([&]()
{
for (uint32_t call = 1u; call <= 32768u; ++call)
{
if (runtime.shouldPreemptGuestExecution())
{
firstPreemptionCall = call;
break;
}
}
});
worker.join();
t.Equals(firstPreemptionCall, 16384u,
"uncontended parser loops should amortize dispatcher handoffs across many back edges");
});
tc.Run("guest preemption policy requests a dispatcher handoff when another guest thread contends", [](TestCase &t)
{
PS2Runtime runtime;
@@ -1693,7 +1743,7 @@ void register_ps2_runtime_expansion_tests()
writeVuInstructionPair(code, 8u, 0u, makeVuAdd(0xFu, 2u, 1u, 1u));
writeVuInstructionPair(code, 16u, makeVuSq(0xFu, 2u, 0u, 1), kVuEndNop);
R5900Context ctx;
R5900Context ctx{};
runtime.executeVU0Microprogram(runtime.memory().getRDRAM(), &ctx, 0u);
float output[4]{};
@@ -1709,6 +1759,102 @@ void register_ps2_runtime_expansion_tests()
t.Equals(static_cast<uint32_t>(ctx.vi[0]), 0u, "VU0 VI0 should remain zero");
});
tc.Run("VU0 microprogram preserves the architectural RNG state", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "PS2Memory initialize should succeed");
t.IsTrue(runtime.syncCoreSubsystems(), "runtime core subsystems should bind");
uint8_t *const code = runtime.memory().getVU0Code();
std::memset(code, 0, PS2_VU0_CODE_SIZE);
constexpr uint32_t kVuUpperNop = 0x000002FFu;
constexpr uint32_t kVuUpperEndNop = 0x400002FFu;
writeVuInstructionPair(
code, 0u,
makeVuLowerSpecial(0x40u, 0u, 1u, 0x8u),
kVuUpperEndNop); // RNEXT.x vf1
writeVuInstructionPair(code, 8u, 0u, kVuUpperNop);
constexpr uint32_t seed = 0x3FC00000u;
const uint32_t x = (seed >> 4) & 1u;
const uint32_t y = (seed >> 22) & 1u;
const uint32_t expected =
(((seed << 1) ^ x ^ y) & 0x007FFFFFu) | 0x3F800000u;
R5900Context ctx{};
ctx.vu0_r = _mm_castsi128_ps(
_mm_set1_epi32(static_cast<int32_t>(seed)));
runtime.executeVU0Microprogram(runtime.memory().getRDRAM(), &ctx, 0u);
alignas(16) uint32_t rWords[4]{};
_mm_storeu_si128(reinterpret_cast<__m128i *>(rWords),
_mm_castps_si128(ctx.vu0_r));
t.Equals(rWords[0], expected, "VU0 micro RNG should advance the imported R seed");
t.Equals(rWords[1], expected, "VU0 R should remain replicated for macro-mode access");
alignas(16) uint32_t vf1Words[4]{};
_mm_storeu_si128(reinterpret_cast<__m128i *>(vf1Words),
_mm_castps_si128(ctx.vu0_vf[1]));
t.Equals(vf1Words[0], expected, "RNEXT should expose the same R value through VF1.x");
});
tc.Run("VU0 direct MicroMem writes invalidate the fixed decode cache", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "PS2Memory initialize should succeed");
t.IsTrue(runtime.syncCoreSubsystems(), "runtime core subsystems should bind");
constexpr uint32_t kVuUpperNop = 0x000002FFu;
constexpr uint32_t kVuUpperEndNop = 0x400002FFu;
runtime.memory().write64(
PS2_VU0_CODE_BASE,
packVuInstructionPair(makeVuIaddiu(1u, 0u, 1), kVuUpperEndNop));
runtime.memory().write64(
PS2_VU0_CODE_BASE + 8u,
packVuInstructionPair(0u, kVuUpperNop));
R5900Context first{};
runtime.executeVU0Microprogram(runtime.memory().getRDRAM(), &first, 0u);
t.Equals(static_cast<uint32_t>(first.vi[1]), 1u,
"first cached VU0 microprogram should execute");
runtime.memory().write64(
PS2_VU0_CODE_BASE,
packVuInstructionPair(makeVuIaddiu(1u, 0u, 2), kVuUpperEndNop));
R5900Context second{};
runtime.executeVU0Microprogram(runtime.memory().getRDRAM(), &second, 0u);
t.Equals(static_cast<uint32_t>(second.vi[1]), 2u,
"VU0 cache should rebuild after a direct MicroMem write");
});
tc.Run("VU0 FBRST TE gates a T-bit microprogram stop", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "PS2Memory initialize should succeed");
t.IsTrue(runtime.syncCoreSubsystems(), "runtime core subsystems should bind");
uint8_t *const code = runtime.memory().getVU0Code();
std::memset(code, 0, PS2_VU0_CODE_SIZE);
constexpr uint32_t kVuUpperNop = 0x000002FFu;
writeVuInstructionPair(
code, 0u, makeVuIaddiu(1u, 0u, 7),
kVuUpperNop | 0x08000000u);
writeVuInstructionPair(
code, 8u, makeVuIaddiu(2u, 0u, 9),
kVuUpperNop);
R5900Context ctx{};
ctx.vu0_fbrst = 1u << 3; // TE0
runtime.executeVU0Microprogram(runtime.memory().getRDRAM(), &ctx, 0u);
t.Equals(static_cast<uint32_t>(ctx.vi[1]), 7u,
"the T-marked instruction should execute");
t.Equals(static_cast<uint32_t>(ctx.vi[2]), 0u,
"TE0 should stop VU0 before the following instruction");
t.IsTrue((ctx.vu0_vpu_stat & (1u << 2)) != 0u,
"VPU-STAT should report a VU0 T-bit stop");
t.Equals(ctx.vu0_tpc, 8u,
"TPC should point at the first instruction not executed");
});
tc.Run("GS sprite draw applies XYOFFSET and fully-outside scissor should not render", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
+5 -5
View File
@@ -162,7 +162,7 @@ namespace
constexpr uint32_t K_DTX_DISPATCH_RESULT_ADDR = 0x0002D800u;
constexpr uint32_t K_DTX_DISPATCH_RESULT_MARKER = 0xD15CA7C1u;
void lotrSoundEndCallbackShouldNotRun(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void lotrSoundEndCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
@@ -637,7 +637,7 @@ void register_ps2_sif_rpc_tests()
PS2Runtime::setIoPaths(oldPaths);
});
tc.Run("LotR sound RPC completes HLE callback without invoking guest loop", [](TestCase &t)
tc.Run("LotR sound RPC invokes guest callback to consume HLE response", [](TestCase &t)
{
TestEnv env;
configureProfile(env, "SLUS_205.78");
@@ -648,7 +648,7 @@ void register_ps2_sif_rpc_tests()
constexpr uint32_t kRecvAddr = 0x0003C000u;
constexpr uint32_t kEndFunc = 0x001FFD70u;
env.runtime.registerFunction(kEndFunc, lotrSoundEndCallbackShouldNotRun);
env.runtime.registerFunction(kEndFunc, lotrSoundEndCallback);
g_lotrSoundCallbackHits = 0u;
SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
@@ -681,8 +681,8 @@ void register_ps2_sif_rpc_tests()
SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime);
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc should succeed for LotR sound RPC");
t.Equals(g_lotrSoundCallbackHits.load(), 0u,
"HLE-completed LotR sound callback should not invoke the guest callback");
t.Equals(g_lotrSoundCallbackHits.load(), 1u,
"LotR SOUND_JP callback should consume the HLE response");
t.Equals(readGuestStruct<uint32_t>(env.rdram.data(), kRecvAddr + 0u), 0u,
"LotR sound response should report no active stream records");
t.IsTrue(readGuestStruct<uint32_t>(env.rdram.data(), kRecvAddr + 4u) != 0u,
File diff suppressed because it is too large Load Diff