From 84974a9b2a244bc61bdff8ae1e570e0f65e30a02 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:29:48 +0200 Subject: [PATCH 01/55] fix(shader): locate image LOD, mip and sample index by the instruction's dimension The address operands of a MIMG instruction are laid out by its own DIM field: the decoder counts them that way. The SPIR-V emitter located the gradients, the LOD, the LOD clamp, the mip level and the sample index after the coordinate count of the bound T# instead, so an array instruction on a non-array texture took the slice as its LOD. An instruction that addresses fewer coordinates than its image has is refused instead of reading the next address component as a coordinate. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/ImageAddressDimension.cpp | 188 ++++++++++++++++++ .../SpirvBackend/src/SpirvImageEmitter.cpp | 31 +-- 2 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp new file mode 100644 index 000000000..0910f962a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Words = 32; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ArrayInput = 0; +constexpr std::uint32_t ArrayResult = 8; +constexpr std::uint32_t PlainInput = 16; +constexpr std::uint32_t PlainResult = 24; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xe0301018, 0x80000801, 0xe030101c, + 0x80000901, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xe0301048, 0x80001201, 0xe030104c, + 0x80001301, 0xe0301050, 0x80001401, 0xe0301054, 0x80001501, 0xbf8c3f70, 0xf0900f28, 0x00610c02, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0xf0041f28, 0x00010c06, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, 0xe0701038, 0x80000e01, + 0xe070103c, 0x80000f01, 0xf0900f08, 0x00610c10, 0xe0701060, 0x80000c01, 0xe0701064, 0x80000d01, + 0xe0701068, 0x80000e01, 0xe070106c, 0x80000f01, 0xf0041f08, 0x00010c13, 0xe0701070, 0x80000c01, + 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, 0xbf810000, +}; + +alignas(256) constexpr std::array NarrowCode{ + 0x34020087, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xbf8c3f70, 0xf0900f00, 0x00610c10, + 0xe0701070, 0x80000c01, 0xbf810000, +}; + +struct Sample { + std::uint32_t level; + std::uint32_t x; + std::uint32_t y; +}; + +Sample SampleOf(std::uint32_t tid) { + return {tid % Levels, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size}; +} + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const auto u = Bits((static_cast(sample.x) + 0.5f) / static_cast(Size)); + const auto v = Bits((static_cast(sample.y) + 0.5f) / static_cast(Size)); + const auto lod = Bits(static_cast(sample.level)); + const auto x = sample.x >> sample.level; + const auto y = sample.y >> sample.level; + auto* words = &Buffer[tid * Words]; + const std::array array{u, v, Bits(0.0f), lod, x, y, 0u, sample.level}; + const std::array plain{u, v, lod, x, y, sample.level}; + std::copy(array.begin(), array.end(), words + ArrayInput); + std::copy(plain.begin(), plain.end(), words + PlainInput); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image address dimension: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + constexpr std::array names{"image_sample_l 2d_array", "image_load_mip 2d_array", "image_sample_l 2d", "image_load_mip 2d"}; + constexpr std::array results{ArrayResult, ArrayResult + 4u, PlainResult, PlainResult + 4u}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array expected{sample.level, sample.x >> sample.level, sample.y >> sample.level, 255u}; + for (std::uint32_t index = 0; index < names.size(); ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + results[index] + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + " on a 2D texture: thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +void RequireRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + Run(device, NarrowCode); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("too few coordinate components") != std::string::npos, "image_sample_l 1d on a 2D texture was not refused: " + refusal); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + FillTexture(); + Run(*device, Code); + Check(); + RequireRefused(*device); + std::puts("image address dimension tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 5edd79713..9fe529a7c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -82,6 +82,10 @@ bool HasFlag(const MemoryInfo& mem, std::uint32_t flag) { return (mem.imageSampleFlags & flag) != 0u; } +const RdnaImageDimensionInfo& AddressDimension(const ImageEmitAccess& access) { + return RdnaImageDimensionInfoFor(access.mem.imageDimension); +} + std::uint32_t ZeroF32(SpirvEmitterState& state) { return ConstantF32(state, 0); } @@ -111,10 +115,10 @@ std::uint32_t AddressF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& acce return GetRdnaImageAddressComponentLayout(access.mem.imageSampleFlags, component).bitWidth == 16u ? EmitF16BitsToF32(ctx.state, value) : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); } -ImageSampleLayout Layout(const MemoryInfo& mem, RdnaImageDimension dimension) { +ImageSampleLayout Layout(const MemoryInfo& mem) { ImageSampleLayout layout; std::uint32_t cursor = 0; - const auto& info = RdnaImageDimensionInfoFor(dimension); + const auto& info = RdnaImageDimensionInfoFor(mem.imageDimension); if (HasFlag(mem, RdnaImageSampleFlagOffset)) { layout.offset = cursor++; } @@ -155,8 +159,8 @@ std::uint32_t CubeLayer(SpirvEmitterState& state, std::uint32_t value) { return result; } -std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components) { - if (first == NoImageComponent || access.mem.imageAddressComponents < first + components) { +std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components, std::uint32_t encoded) { + if (first == NoImageComponent || encoded < components || access.mem.imageAddressComponents < first + encoded) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const bool cube = access.image.cube; @@ -184,7 +188,7 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; - if (access.mem.imageAddressComponents < components) { + if (AddressDimension(access).coordinateComponents < components || access.mem.imageAddressComponents < components) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const auto x = AddressU32(ctx, access, 0); @@ -206,7 +210,7 @@ std::uint32_t LodU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) if (!access.mem.imageHasMip) { return ConstantU32(ctx.state, 0); } - const auto component = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; + const auto component = AddressDimension(access).coordinateComponents; if (access.mem.imageAddressComponents <= component) { ctx.Fail(access.inst, "has an image address without the mip level component"); } @@ -691,7 +695,7 @@ void EmitQueryLodOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { auto& state = ctx.state; state.module.EmitCapability(spv::CapabilityImageQuery); const auto sampled = MakeSampledImage(state, access.mem.resource, access.mem.sampler, access.slot); - const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents); + const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents, AddressDimension(access).spatialComponents); const auto lod = state.module.AllocateId(); state.module.AddFunction(spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled, coord); std::uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0)}; @@ -715,10 +719,11 @@ void EmitReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto color = state.module.AllocateId(); const auto coord = CoordU32(ctx, access); if (dimensionInfo.multisampled != 0u) { - if (access.mem.imageAddressComponents <= dimensionInfo.coordinateComponents) { + const auto& addressInfo = AddressDimension(access); + if (addressInfo.multisampled == 0u || access.mem.imageAddressComponents <= addressInfo.coordinateComponents) { ctx.Fail(access.inst, "has no sample index in the image address"); } - state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, dimensionInfo.coordinateComponents)); + state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, addressInfo.coordinateComponents)); } else { state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsLodMask, LodU32(ctx, access)); } @@ -783,12 +788,12 @@ void EmitAtomicOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { SampleSetup MakeSampleSetup(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto& dimensionInfo = RdnaImageDimensionInfoFor(access.image.dimension); - const auto layout = Layout(access.mem, access.image.dimension); + const auto layout = Layout(access.mem); const bool dref = HasFlag(access.mem, RdnaImageSampleFlagCompare); if (dref && access.image.conversionFormat != IrBufferFormat::Invalid) { ctx.Fail(access.inst, "uses depth comparison with a packed integer image"); } - const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents); + const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); return {dimensionInfo, layout, access.image.numericClass, dref, coord}; } @@ -908,8 +913,8 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con std::vector operands; if (HasFlag(mem, RdnaImageSampleFlagDerivative)) { operandMask |= spv::ImageOperandsGradMask; - operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents)); - operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); } else if (explicitLod) { operandMask |= spv::ImageOperandsLodMask; operands.push_back(HasFlag(mem, RdnaImageSampleFlagLod) ? AddressF32(ctx, access, setup.layout.lod) : ZeroF32(state)); From 3a30b5bae0502bc86a10d9add22e605cbd018ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:37:55 -0300 Subject: [PATCH 02/55] fix(rtc): reject invalid RFC 3339 formatting offsets --- core/libs/prx/libSceRtc/Export.cpp | 1 + core/libs/tests/GuestRtc.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 3a0895ae1..08bf4bb06 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -272,6 +272,7 @@ int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER; + if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; RtcTick local{}; if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; const RtcDateTime time = fromTick(local.tick); diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index fca1f2688..2d843ebfe 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include extern "C" { @@ -97,6 +98,22 @@ int main() { Require(sceRtcFormatRFC3339(text, &tick, 0) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0); Require(sceRtcFormatRFC3339(text, &tick, 90) == 0 && std::strcmp(text, "2024-02-29T14:04:56.78+01:30") == 0); Require(sceRtcFormatRFC3339(text, &tick, -300) == 0 && std::strcmp(text, "2024-02-29T07:34:56.78-05:00") == 0); + Require(sceRtcFormatRFC3339(text, &tick, 1439) == 0 && std::strcmp(text, "2024-03-01T12:33:56.78+23:59") == 0); + Require(sceRtcFormatRFC3339(text, &tick, -1439) == 0 && std::strcmp(text, "2024-02-28T12:35:56.78-23:59") == 0); + for (int offset : {0, 1, -1, 59, -59, 60, -60, 90, -300, 1439, -1439}) { + RtcTick parsed{}; + Require(sceRtcFormatRFC3339(text, &tick, offset) == 0); + Require(sceRtcParseRFC3339(&parsed, text) == 0 && parsed.tick == leapDayTick - 9000); + } + for (int offset : {1440, -1440, 6000, -6000, std::numeric_limits::min(), std::numeric_limits::max()}) { + std::memset(text, 'x', sizeof(text)); + char original[sizeof(text)]; + std::memcpy(original, text, sizeof(text)); + Require(sceRtcFormatRFC3339(text, &tick, offset) == invalidValue); + Require(std::memcmp(text, original, sizeof(text)) == 0); + } + Require(sceRtcFormatRFC3339(nullptr, &tick, 1440) == invalidPointer); + Require(sceRtcFormatRFC3339(text, nullptr, 1440) == invalidPointer); Require(sceRtcParseRFC3339(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick); From 9f9e4e28b851e7e440ac2835727d1c4a2d326c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:44:55 -0300 Subject: [PATCH 03/55] fix(relinker): reject invalid and overflowing alignment --- core/relinker/io/include/io/BufferUtils.hpp | 11 +++++- core/relinker/io/src/BufferUtils.cpp | 6 +-- core/relinker/io/tests/BufferBoundsTests.cpp | 41 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/core/relinker/io/include/io/BufferUtils.hpp b/core/relinker/io/include/io/BufferUtils.hpp index 365a50847..82cc8d0ec 100644 --- a/core/relinker/io/include/io/BufferUtils.hpp +++ b/core/relinker/io/include/io/BufferUtils.hpp @@ -2,6 +2,8 @@ #define IO_BUFFERUTILS_HPP #include +#include +#include #include #include @@ -27,7 +29,14 @@ std::uint64_t ReadU64(const std::vector& buf, std::size_t offset); template constexpr T AlignUp(T value, T alignment) { - return (value + alignment - 1) / alignment * alignment; + if (value < 0 || alignment <= 0) + throw std::invalid_argument("AlignUp requires a nonnegative value and a positive alignment"); + const T remainder = value % alignment; + if (remainder == 0) return value; + const T padding = alignment - remainder; + if (value > std::numeric_limits::max() - padding) + throw std::overflow_error("Aligned value exceeds integer range"); + return value + padding; } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment); diff --git a/core/relinker/io/src/BufferUtils.cpp b/core/relinker/io/src/BufferUtils.cpp index c0d389588..d4ce96336 100644 --- a/core/relinker/io/src/BufferUtils.cpp +++ b/core/relinker/io/src/BufferUtils.cpp @@ -60,11 +60,7 @@ void AppendString(std::vector& buf, const std::string& str) { } void AlignBuffer(std::vector& buf, std::size_t alignment) { - const std::size_t remainder = buf.size() % alignment; - if (remainder != 0) { - const std::size_t padding = alignment - remainder; - buf.resize(buf.size() + padding, 0); - } + buf.resize(AlignUp(buf.size(), alignment), 0); } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) { diff --git a/core/relinker/io/tests/BufferBoundsTests.cpp b/core/relinker/io/tests/BufferBoundsTests.cpp index 2de9beb8b..6767291ff 100644 --- a/core/relinker/io/tests/BufferBoundsTests.cpp +++ b/core/relinker/io/tests/BufferBoundsTests.cpp @@ -11,6 +11,8 @@ namespace { using Bytes = std::vector; +static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32); + void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -71,12 +73,51 @@ void writeBounds(const TWrite& write, const std::string& name) { invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } +template +void requireThrows(const TOperation& operation, const std::string& message) { + try { + operation(); + } catch (const TError&) { + return; + } + throw std::runtime_error(message); +} + +template +void alignmentBounds() { + constexpr auto maximum = std::numeric_limits::max(); + require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero"); + require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up"); + require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment"); + require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum"); + require(Io::AlignUp(static_cast(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum"); + require(Io::AlignUp(static_cast(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result"); + requireThrows([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result"); + requireThrows([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment"); +} + } int main() { try { const Io::ByteReader reader; const Io::ByteWriter writer; + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + requireThrows([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value"); + requireThrows([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment"); + require(Io::AlignUp64(std::numeric_limits::max() - 1, std::numeric_limits::max()) == std::numeric_limits::max(), "AlignUp64 overflowed for a representable result"); + requireThrows([] { Io::AlignUp64(std::numeric_limits::max(), 16); }, "AlignUp64 accepted an overflowing result"); + Bytes aligned{1, 2, 3}; + const auto original = aligned; + requireThrows([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment"); + require(aligned == original, "AlignBuffer modified the buffer on failure"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); From e1bfd03f2360e493529658ed0f948db8ea269e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:57:56 -0300 Subject: [PATCH 04/55] fix(libc): return null when strdup allocation fails --- core/libs/prx/libc/src/AllocatingStrings.cpp | 4 ++++ core/libs/prx/libc/src/Strings.cpp | 8 ------- core/libs/tests/ApplicationHeap.cpp | 22 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index d3539587b..74779c475 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -30,6 +30,10 @@ char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { } } +char* APS5_VABI strdup_nid_postfix(const char* source) { + return strndup_nid_postfix(source, static_cast(-1)); +} + int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { if (!destination) { errno = 22; return -1; } *destination = nullptr; diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 5bcad4d2a..971b12cfe 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,4 +1,3 @@ -#include "prx/libc/include/ApplicationHeap.hpp" #include #include #include @@ -156,13 +155,6 @@ int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n) return static_cast(*s1) - static_cast(*s2); } -char* APS5_VABI strdup_nid_postfix(const char* s) { - std::size_t len = std::strlen(s) + 1; - char* copy = static_cast(ApplicationHeapAllocate_nid_no_patch(len)); - std::memcpy(copy, s, len); - return copy; -} - int APS5_VABI bcmp_nid_postfix(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); } diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 3fb557ed1..0f08924bb 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -14,6 +14,9 @@ void APS5_VABI _ZdlPv_nid_postfix(void*); void APS5_VABI _ZdaPv_nid_postfix(void*); void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); void* ApplicationHeapRealign_nid_no_patch(void*, std::size_t, std::size_t); +char* APS5_VABI strdup_nid_postfix(const char*); +char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); } namespace { @@ -192,4 +195,23 @@ int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); recurse = false; require(ApplicationHeapAllocate_nid_no_patch(64) == storage.data()); + const char text[] = "guest string"; + char* copy = strdup_nid_postfix(text); + require(copy == reinterpret_cast(storage.data()) && lastSize == sizeof(text)); + require(std::strcmp(copy, text) == 0 && copy != text); + ApplicationHeapFree_nid_no_patch(copy); + copy = strdup_nid_postfix(""); + require(lastSize == 1 && copy[0] == '\0'); + ApplicationHeapFree_nid_no_patch(copy); + copy = strndup_nid_postfix(text, 5); + require(lastSize == 6 && std::strcmp(copy, "guest") == 0); + ApplicationHeapFree_nid_no_patch(copy); + fail = true; + *__error_nid_postfix() = 0; + require(strdup_nid_postfix(text) == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strdup_nid_postfix("") == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strndup_nid_postfix(text, 5) == nullptr && *__error_nid_postfix() == 12); + fail = false; } From 23008762cc685b5e4d05d1685018b3e1f5064f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 09:13:05 -0300 Subject: [PATCH 05/55] fix(libkernel): preserve 64-bit metadata download sizes --- .../AppMetadata/src/ParamJsonParser.cpp | 14 ++++- core/libs/prx/libkernel/CMakeLists.txt | 9 +++ core/libs/tests/ParamJsonParser.cpp | 56 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 core/libs/tests/ParamJsonParser.cpp diff --git a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp index 89b53c3f2..aa36abe72 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" #include +#include #include #include #include @@ -21,6 +22,7 @@ struct JsonValue { JsonType type = JsonType::Null; bool boolValue = false; double numberValue = 0.0; + std::string numberText; std::string stringValue; JsonArray arrayValue; JsonObject objectValue; @@ -193,7 +195,8 @@ JsonValue parseJsonNumber(const std::string& text, std::size_t& position) { } JsonValue value; value.type = JsonType::Number; - value.numberValue = std::stod(text.substr(start, position - start)); + value.numberText = text.substr(start, position - start); + value.numberValue = std::stod(value.numberText); return value; } @@ -261,7 +264,14 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { result.titleId = titleId; if (const JsonValue* downloadData = findObjectMember(root, "downloadDataSize")) { if (downloadData->type != JsonType::Number || downloadData->numberValue < 0) throw std::runtime_error("param.json downloadDataSize is not a size"); - result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + if (downloadData->numberText.find_first_of("-.eE") == std::string::npos) { + const auto& number = downloadData->numberText; + const auto parsed = std::from_chars(number.data(), number.data() + number.size(), result.downloadDataSizeMiB); + if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size()) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + } else { + if (downloadData->numberValue >= 0x1p64) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + } } return result; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2e5364b1b..43949deb4 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -70,6 +70,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(param_json_parser_tests ${LIBS_INCLUDE_DIR}/tests/ParamJsonParser.cpp) + target_include_directories(param_json_parser_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(param_json_parser_tests PRIVATE libkernel libc) + configure_windows_unwind(param_json_parser_tests) + add_test(NAME param_json_parser COMMAND param_json_parser_tests) + if(WIN32) + set_tests_properties(param_json_parser PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_posix_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSem.cpp) target_include_directories(guest_posix_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_posix_sem_tests PRIVATE libkernel libc) diff --git a/core/libs/tests/ParamJsonParser.cpp b/core/libs/tests/ParamJsonParser.cpp new file mode 100644 index 000000000..fc3c06cb0 --- /dev/null +++ b/core/libs/tests/ParamJsonParser.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +static ParsedParamJson ParseSize(const std::filesystem::path& path, const std::string& size) { + { + std::ofstream file(path); + file << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}})"; + if (!size.empty()) file << ",\"downloadDataSize\":" << size; + file << '}'; + Require(static_cast(file), "Cannot write param.json fixture"); + } + return parseParamJson(path); +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-param-json-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".json"); + int result = 0; + try { + const std::pair sizes[] = { + {"", 0}, {"0", 0}, {"-0", 0}, {"4096", 4096}, + {"9007199254740993", 9007199254740993ull}, + {"18446744073709551614", std::numeric_limits::max() - 1}, + {"18446744073709551615", std::numeric_limits::max()}, + {"1e3", 1000}, {"1.25", 1} + }; + for (const auto& [text, expected] : sizes) { + const auto parsed = ParseSize(path, text); + Require(parsed.downloadDataSizeMiB == expected, std::string("Incorrect download size: ") + text); + Require(parsed.titleId == "PPSA00000" && parsed.title == "Example", "Title metadata changed"); + } + for (const auto* text : {"18446744073709551616", "18446744073709551617", "1.8446744073709552e19", + "1e40", "-1", "-0.5", "true", "null", "\"4096\""}) { + bool rejected = false; + try { ParseSize(path, text); } catch (const std::exception&) { rejected = true; } + Require(rejected, std::string("Accepted invalid download size: ") + text); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::filesystem::remove(path); + return result; +} From 1fa44c7b5c8815514a2a4d3b04459cf9d5df40de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 09:23:47 -0300 Subject: [PATCH 06/55] fix(relinker): decode autorun exit status for the host --- core/relinker/CMakeLists.txt | 6 +++ core/relinker/cli/src/Autorun.cpp | 13 +++--- core/relinker/cli/tests/AutorunExitTests.cpp | 46 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 core/relinker/cli/tests/AutorunExitTests.cpp diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a388ec7a3..fb705627a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -125,6 +125,12 @@ endif() if(BUILD_TESTING) + add_test_executable(autorun_exit_tests + ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/AutorunExitTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp) + target_include_directories(autorun_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include) + add_test(NAME autorun_exit_status COMMAND autorun_exit_tests) + set_tests_properties(autorun_exit_status PROPERTIES TIMEOUT 20) add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index 30979a985..3bb7016be 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -20,12 +20,15 @@ int Autorun(const std::string& absPath, bool toWindows) { const int rawCode = std::system(cmd.c_str()); int exitCode = rawCode; - if (toWindows) { - std::cout << "\nExit code: " << rawCode << '\n'; - } else { - if (rawCode != -1) exitCode = (rawCode >> 8) & 0xFF; - std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#ifdef _WIN32 + std::cout << "\nExit code: " << rawCode << '\n'; +#else + if (rawCode != -1) { + const int signal = rawCode & 0x7F; + exitCode = signal ? 128 + signal : (rawCode >> 8) & 0xFF; } + std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#endif std::cout << "\nPress Enter to exit...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp new file mode 100644 index 000000000..f6fc70829 --- /dev/null +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int, char* argv[]) { + const auto executable = std::filesystem::absolute(argv[0]); + const auto name = executable.stem().string(); + if (name.starts_with("anyps5-autorun-child-")) { + if (name.find("signal") != std::string::npos) std::raise(SIGILL); + return 42; + } + const auto child = std::filesystem::temp_directory_path() / ("anyps5-autorun-child-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + " with spaces" + executable.extension().string()); +#ifndef _WIN32 + const auto signaled = child.parent_path() / ("anyps5-autorun-child-signal-" + child.filename().string()); +#endif + std::istringstream input("\n\n\n"); + auto* savedInput = std::cin.rdbuf(input.rdbuf()); + int result = 0; + try { + std::filesystem::copy_file(executable, child); + for (const bool windows : {false, true}) { + const int code = Cli::Autorun(child.string(), windows); + if (code != 42) throw std::runtime_error("Autorun changed exit code 42 to " + std::to_string(code)); + } +#ifndef _WIN32 + std::filesystem::rename(child, signaled); + const int code = Cli::Autorun(signaled.string(), false); + if (code != 128 + SIGILL) throw std::runtime_error("Autorun lost the child signal"); +#endif + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::cin.rdbuf(savedInput); + std::filesystem::remove(child); +#ifndef _WIN32 + std::filesystem::remove(signaled); +#endif + return result; +} From 7e90590146dcc836575c19c17fe1f09d2de9de69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:50:46 +0200 Subject: [PATCH 07/55] feat(shader): accept every SDWA selector on VOP2 float ops Co-Authored-By: Claude --- .../tests/execution/SdwaFloatSelectors.cpp | 218 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 29 +-- 2 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp new file mode 100644 index 000000000..10863bc09 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp @@ -0,0 +1,218 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x06140af9, 0x06000604, 0x7e160307, + 0x08160af9, 0x05061504, 0x0a180af9, 0x06142a04, 0x101a0af9, 0x03260404, 0x0e1c0af9, 0x06010604, + 0x1e1e0af9, 0x160a0604, 0x7e200307, 0x20200af9, 0x08061104, 0x20220af9, 0x060d2604, 0x64240af9, + 0x05010004, 0x66260af9, 0x02150d04, 0x7e280307, 0x68280af9, 0x24031304, 0x6a2a0af9, 0x00040604, + 0x7e2c0307, 0x742c0af9, 0x05081404, 0x722e0af9, 0x19050204, 0x06300af9, 0x06052b04, 0x6a320af9, + 0x06020904, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x40404040u, 0x8b2af49fu}, + {0x00000000u, 0x10663f9du, 0x8b35e743u, 0x7d7d9db4u}, + {0x0008dbf2u, 0x47004700u, 0x3c000000u, 0x80000000u}, + {0x05c21fabu, 0x00470047u, 0x80000000u, 0x47004700u}, + {0x1a0e7cdeu, 0x00000000u, 0xefc2742bu, 0xcd8d3db8u}, + {0xc4de998au, 0xaf1fba34u, 0x0000bc00u, 0xbc00bc00u}, + {0x00000000u, 0x00470047u, 0xc2c2c2c2u, 0xfda0095du}, + {0x3c000000u, 0x80808080u, 0x5960b098u, 0xadd5eff7u}, + {0xbc00bc00u, 0x00000000u, 0x42a502c0u, 0x3c000000u}, + {0x80808080u, 0x1a64a58eu, 0xfe242714u, 0x3c003c00u}, + {0x00000000u, 0x7c007c00u, 0x7f7f7f7fu, 0x3c003c00u}, + {0x00470047u, 0x00000000u, 0x3468787du, 0x7e7103e1u}, + {0x00470047u, 0x80000000u, 0x702bd672u, 0x47004700u}, + {0x00000000u, 0x00800080u, 0x4659ff82u, 0xc2c2c2c2u}, + {0x7f800000u, 0x00000000u, 0x01000100u, 0x1b1c3f27u}, + {0x80000000u, 0x00800080u, 0xbf800000u, 0x829ea49cu}, + {0x00000000u, 0x0000bc00u, 0x3343e632u, 0x13e60ef5u}, + {0x00000000u, 0x00470047u, 0xb5a54b0bu, 0x6769ec45u}, + {0x00000000u, 0x00800080u, 0xa2e7068bu, 0x871c3c9cu}, + {0x00000000u, 0x00800080u, 0xafe5c4b5u, 0x3c003c00u}, + {0x00000000u, 0x00ff00ffu, 0x0000bc00u, 0x40404040u}, + {0x00000000u, 0x00ff00ffu, 0x7f800000u, 0x00ff00ffu}, + {0x00000000u, 0x00ff00ffu, 0xb4ad5866u, 0xe4ea7accu}, + {0x00000000u, 0x00ff00ffu, 0xd03c7dbeu, 0x5de25876u}, + {0x00000000u, 0x01000100u, 0x52eff50fu, 0x00000000u}, + {0x00000000u, 0x182f8e4au, 0xe117e1fau, 0x0000bc00u}, + {0x00000000u, 0x20d8e2bcu, 0xe73074b1u, 0xbf800000u}, + {0x00000000u, 0x2333ad9cu, 0x00800080u, 0x3f800000u}, + {0x00000000u, 0x24f70eabu, 0xfdb88e95u, 0x17e6487au}, + {0x00000000u, 0x2a778095u, 0x00470047u, 0x7c007c00u}, + {0x00000000u, 0x2b119b05u, 0x0000bc00u, 0x4ce5144fu}, + {0x00000000u, 0x2b5d55fdu, 0x03f55439u, 0x7f7f7f7fu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x0000f49fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x8b2a009fu, 0x00000000u, 0x00000000u, 0x80000000u, 0x002af49fu, 0x00000000u, 0x8b2a0000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x10663f9du, 0x10669db4u, 0xff9d0000u, 0x00000000u, 0x00000000u, 0x90663f9du, 0x7d7d00b4u, 0x10663f9du, 0x00000066u, 0x80660000u, 0x9d7d9db4u, 0x00000000u, 0x7d7d0000u, 0x00000000u, 0x9d000000u, 0x00000000u}, + {0x47004700u, 0x94f20000u, 0x00000000u, 0x00000000u, 0x006dbcbdu, 0xc7004700u, 0x8000f200u, 0x3f800000u, 0x00000000u, 0x80080000u, 0x00000000u, 0x00008000u, 0x80004700u, 0x00080000u, 0x00000000u, 0x00003800u}, + {0x004700f2u, 0x1fab4700u, 0xfff20000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x4700ab00u, 0x00470047u, 0x00000066u, 0x86090000u, 0x42004700u, 0x00000001u, 0x47000047u, 0x00c20000u, 0x09000000u, 0x00000000u}, + {0x000000deu, 0x7cde3db8u, 0xffde0000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xcd8ddeb8u, 0x00001a0eu, 0x0000007cu, 0x9a0e0000u, 0x1a8d3db8u, 0x00007edeu, 0xcd8d0000u, 0x000e0000u, 0x0e000000u, 0x00000000u}, + {0xaf1fba34u, 0x998abc00u, 0x00000000u, 0x0000c158u, 0x80000000u, 0x2f1fba34u, 0xbc003400u, 0x00000000u, 0x0000001fu, 0x44de0000u, 0x3400bc00u, 0x00008000u, 0xbc00af1fu, 0x00de0000u, 0x00000000u, 0xffffac00u}, + {0x00470047u, 0x0047095du, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0xfda0475du, 0x00470047u, 0x00000047u, 0x80470000u, 0x47a0095du, 0x00000000u, 0xfda00000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x80808080u, 0x0000eff7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xadd500f7u, 0x00003c00u, 0x00000080u, 0xbc000000u, 0x44d5eff7u, 0x00000000u, 0xadd58080u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3c000000u, 0x00000000u, 0x000000bcu, 0x3c000000u, 0xbc000000u, 0x00008000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a64a58eu, 0x9ae43c00u, 0xff8e0000u, 0x00000000u, 0x00000000u, 0x9a64a58eu, 0x3c008000u, 0x1a64a58eu, 0x00000068u, 0x001c0000u, 0x8e003c00u, 0x00008000u, 0x3c001a64u, 0x00800000u, 0x8e000000u, 0x00000300u}, + {0x7c007c00u, 0x7c003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfc007c00u, 0x3c000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000047u, 0x004703e1u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7e7147e1u, 0x00000047u, 0x00000000u, 0x80470000u, 0x007103e1u, 0x00000000u, 0x7e710000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00000047u, 0x80474700u, 0x00470000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x47004700u, 0x00000047u, 0x00000000u, 0x80470000u, 0x00004700u, 0x00000000u, 0x47008000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x0080c2c2u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0xc2c200c2u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80c2c2c2u, 0x00000000u, 0xc2c20000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00003f27u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x1b1c0027u, 0x00007f80u, 0x00000000u, 0xff800000u, 0x7f1c3f27u, 0x00000000u, 0x1b1c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x0080a49cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x829e009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x009ea49cu, 0x00000000u, 0x829e0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x0000bc00u, 0x00000ef5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8000bc00u, 0x13e600f5u, 0x0000bc00u, 0x00000000u, 0x80000000u, 0x00e60ef5u, 0x00000000u, 0x13e60000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00470047u, 0x0047ec45u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x67694745u, 0x00470047u, 0x00000047u, 0x80470000u, 0x4769ec45u, 0x00000000u, 0x67690000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x00803c9cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x871c009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x801c3c9cu, 0x00000000u, 0x871c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x00803c00u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x3c000000u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff4040u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x40400040u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xff404040u, 0x00000000u, 0x40400000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffff00ffu, 0x00000000u, 0x00ff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff7accu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0xe4ea00ccu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffea7accu, 0x00000000u, 0xe4ea0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff5876u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x5de20076u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffe25876u, 0x00000000u, 0x5de20000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x01000100u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81000100u, 0x00000000u, 0x01000100u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x182f8e4au, 0x182fbc00u, 0x004a0000u, 0x00000000u, 0x00000000u, 0x982f8e4au, 0x00004a00u, 0x182f8e4au, 0x0000002fu, 0x802f0000u, 0x4a00bc00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x4a000000u, 0x00000000u}, + {0x20d8e2bcu, 0x20d80000u, 0xffbc0000u, 0x00000000u, 0x00000000u, 0xa0d8e2bcu, 0xbf800000u, 0x20d8e2bcu, 0x000000d8u, 0x80d80000u, 0xbc800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbc000000u, 0x00000000u}, + {0x2333ad9cu, 0x23330000u, 0xff9c0000u, 0x00000000u, 0x00000000u, 0xa333ad9cu, 0x3f800000u, 0x2333ad9cu, 0x00000033u, 0x80330000u, 0x9c800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x9c000000u, 0x00000000u}, + {0x24f70eabu, 0x24f7487au, 0xffab0000u, 0x00000000u, 0x00000000u, 0xa4f70eabu, 0x17e6007au, 0x24f70eabu, 0x000000f7u, 0x80f70000u, 0xabe6487au, 0x00000000u, 0x17e60000u, 0x00000000u, 0xab000000u, 0x00000000u}, + {0x2a778095u, 0x2a777c00u, 0xff950000u, 0x00000000u, 0x00000000u, 0xaa778095u, 0x7c000000u, 0x2a778095u, 0x00000077u, 0x80770000u, 0x95007c00u, 0x00000000u, 0x7c000000u, 0x00000000u, 0x95000000u, 0x00000000u}, + {0x2b119b05u, 0x2b11144fu, 0x00050000u, 0x00000000u, 0x00000000u, 0xab119b05u, 0x4ce5054fu, 0x2b119b05u, 0x00000011u, 0x80110000u, 0x05e5144fu, 0x00000000u, 0x4ce50000u, 0x00000000u, 0x05000000u, 0x00000000u}, + {0x2b5d55fdu, 0x2b5d7f7fu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0xab5d55fdu, 0x7f7f007fu, 0x2b5d55fdu, 0x0000005du, 0x805d0000u, 0xfd7f7f7fu, 0x00000000u, 0x7f7f0000u, 0x00000000u, 0xfd000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xfdfdu, 0x5dadu, 0xa05au, 0xb27au, 0x7fa7u, 0xb818u, 0xea77u, 0xfaffu, 0x5cfdu, 0x7ffdu, 0x9d38u, 0x9d38u, 0xf8fdu, 0xb977u, 0xfcfdu, 0xff5eu, 0xb818u, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xfffdu, 0xfdbdu, 0xfdfdu, 0xfdfdu, 0xfdfdu, 0xf9fdu, 0xfdbdu, 0xfdfdu}; +constexpr const char* Names[16] = { + "add_f32 BYTE_0/DWORD -> DWORD", + "sub_f32 DWORD/WORD_1 -> WORD_1", + "subrev_f32 WORD_0/DWORD -> BYTE_2", + "mul_f32 DWORD/BYTE_3 -> WORD_0", + "mul_legacy_f32 BYTE_1/DWORD -> DWORD", + "min_f32 BYTE_2/DWORD -> DWORD", + "max_f32 DWORD/BYTE_0 -> BYTE_1", + "max_f32 WORD_1/DWORD -> DWORD", + "add_f16 BYTE_1/WORD_1 -> BYTE_0", + "sub_f16 WORD_1/BYTE_2 -> WORD_1", + "subrev_f16 BYTE_3/WORD_0 -> BYTE_3", + "mul_f16 WORD_0/BYTE_0 -> DWORD", + "min_f16 BYTE_0/WORD_1 -> WORD_0", + "max_f16 WORD_1/BYTE_1 -> BYTE_2", + "add_f32 WORD_1/DWORD -> BYTE_3", + "mul_f16 BYTE_2/DWORD -> BYTE_1", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa float selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckByteSelectorClampRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array, 6> forms{{ + {0x640a0cf9u, 0x04052004u}, + {0x660a0cf9u, 0x04022504u}, + {0x680a0cf9u, 0x03042604u}, + {0x6a0a0cf9u, 0x05043304u}, + {0x740a0cf9u, 0x06012404u}, + {0x720a0cf9u, 0x00062604u}, + }}; + for (const auto& form : forms) { + alignas(256) const std::array code{form[0], form[1], 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP2 SDWA clamp with byte selectors is not implemented") != std::string::npos, "f16 SDWA form " + Hex(form[0]) + " " + Hex(form[1]) + " with clamp was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckByteSelectorClampRefused(*device); + std::puts("sdwa float selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index f91cfbcd5..4a94c8482 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -23,10 +23,9 @@ struct VectorOpcodeInfo { enum class Vop2SdwaProfile { None, - Float32, - Float16, - PackedFloat16, + Float, Integer, + Float16, }; struct Vop2OpcodeInfo { @@ -44,17 +43,17 @@ struct VopcOpcodeInfo { constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x01u, RdnaOpcode::VCndmaskB32, Vop2SdwaProfile::Integer}, {0x02u, RdnaOpcode::VDot2cF32F16}, - {0x03u, RdnaOpcode::VAddF32, Vop2SdwaProfile::Float32}, - {0x04u, RdnaOpcode::VSubF32, Vop2SdwaProfile::Float32}, - {0x05u, RdnaOpcode::VSubrevF32}, - {0x08u, RdnaOpcode::VMulF32, Vop2SdwaProfile::Float32}, + {0x03u, RdnaOpcode::VAddF32, Vop2SdwaProfile::Float}, + {0x04u, RdnaOpcode::VSubF32, Vop2SdwaProfile::Float}, + {0x05u, RdnaOpcode::VSubrevF32, Vop2SdwaProfile::Float}, + {0x08u, RdnaOpcode::VMulF32, Vop2SdwaProfile::Float}, {0x09u, RdnaOpcode::VMulI32I24, Vop2SdwaProfile::Integer}, {0x0au, RdnaOpcode::VMulHiI32I24, Vop2SdwaProfile::Integer}, {0x0bu, RdnaOpcode::VMulU32U24, Vop2SdwaProfile::Integer}, {0x0cu, RdnaOpcode::VMulHiU32U24, Vop2SdwaProfile::Integer}, {0x0du, RdnaOpcode::VDot4cI32I8}, - {0x0fu, RdnaOpcode::VMinF32}, - {0x10u, RdnaOpcode::VMaxF32}, + {0x0fu, RdnaOpcode::VMinF32, Vop2SdwaProfile::Float}, + {0x10u, RdnaOpcode::VMaxF32, Vop2SdwaProfile::Float}, {0x11u, RdnaOpcode::VMinI32, Vop2SdwaProfile::Integer}, {0x12u, RdnaOpcode::VMaxI32, Vop2SdwaProfile::Integer}, {0x13u, RdnaOpcode::VMinU32, Vop2SdwaProfile::Integer}, @@ -84,7 +83,7 @@ constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x2bu, RdnaOpcode::VMacF32}, {0x2cu, RdnaOpcode::VMadmkF32}, {0x2du, RdnaOpcode::VMadakF32}, - {0x2fu, RdnaOpcode::VCvtPkrtzF16F32, Vop2SdwaProfile::PackedFloat16}, + {0x2fu, RdnaOpcode::VCvtPkrtzF16F32, Vop2SdwaProfile::Float}, {0x32u, RdnaOpcode::VAddF16, Vop2SdwaProfile::Float16}, {0x33u, RdnaOpcode::VSubF16, Vop2SdwaProfile::Float16}, {0x34u, RdnaOpcode::VSubrevF16, Vop2SdwaProfile::Float16}, @@ -96,7 +95,7 @@ constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x3au, RdnaOpcode::VMinF16, Vop2SdwaProfile::Float16}, {0x3cu, RdnaOpcode::VPkFmacF16}, {0x06u, RdnaOpcode::VMacLegacyF32}, - {0x07u, RdnaOpcode::VMulLegacyF32}, + {0x07u, RdnaOpcode::VMulLegacyF32, Vop2SdwaProfile::Float}, {0x3bu, RdnaOpcode::VLdexpF16}, }; @@ -1298,14 +1297,14 @@ struct Vop2SdwaRule { std::uint32_t src1Selectors = sdwaSelFull(); bool partialDst = false; bool sourceModifiers = false; + bool byteSelectorClamp = true; }; constexpr Vop2SdwaRule vop2SdwaRules[] = { {}, - {sdwaSelFull(), sdwaSelFull(), sdwaSelFull(), false, true}, - {sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, false}, + {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true, false}, }; const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { @@ -1365,6 +1364,10 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (!rule->sourceModifiers && hasSourceModifiers && !cndmaskFloatModifiers) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } + const bool byteSelector = fields.src0Sel < 4u || fields.src1Sel < 4u || fields.dstSel < 4u; + if (!rule->byteSelectorClamp && fields.clamp != 0u && byteSelector) { + throw std::invalid_argument("VOP2 SDWA clamp with byte selectors is not implemented"); + } } void finalizeVop2Instruction(std::span code, std::uint32_t wordIndex, RdnaInstruction& instruction) { From 62ad3a68026eeb772642791baa358a999fdcaf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 15:05:14 +0200 Subject: [PATCH 08/55] test(shader): cover the bitwise and unsigned image atomics Co-Authored-By: Claude --- .../tests/execution/ImageAtomicsBitwise.cpp | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp new file mode 100644 index 000000000..59fbc3c05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp @@ -0,0 +1,142 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Operations = 5; +constexpr std::uint32_t TextureHeight = 8; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(4096) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e100300, 0x7e120280, 0x7e140304, + 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0602108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, + 0xbf8c3f70, 0x7e28030b, 0x7e2a030c, 0x7e120281, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, + 0xf0642108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e2c030b, 0x7e2e030c, + 0x7e120282, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0682108, 0x00020b08, 0x7e180280, + 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e30030b, 0x7e32030c, 0x7e120283, 0x7e140304, 0xf03c0108, + 0x00020a08, 0x7e160305, 0xf0542108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, + 0x7e34030b, 0x7e36030c, 0x7e120284, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf05c2108, + 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e38030b, 0x7e3a030c, 0xe0701000, + 0x80011403, 0xe0701004, 0x80011503, 0xe0701008, 0x80011603, 0xe070100c, 0x80011703, 0xe0701010, + 0x80011803, 0xe0701014, 0x80011903, 0xe0701018, 0x80011a03, 0xe070101c, 0x80011b03, 0xe0701020, + 0x80011c03, 0xe0701024, 0x80011d03, 0xbf810000, +}; + +constexpr std::array, 14> Edges{{ + {0x0u, 0x0u}, {0x0u, 0xffffffffu}, {0xffffffffu, 0x0u}, {0xffffffffu, 0xffffffffu}, {0x7fffffffu, 0x80000000u}, {0x80000000u, 0x7fffffffu}, + {0xffffffffu, 0x1u}, {0x1u, 0xffffffffu}, {0xaaaaaaaau, 0x55555555u}, {0xf0f0f0f0u, 0xff00ff00u}, {0x5u, 0x5u}, {0x4u, 0x5u}, + {0x6u, 0x5u}, {0x80000000u, 0x80000000u}, +}}; + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + for (std::uint32_t j = 0; j < 2u; ++j) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + words[j] = static_cast(state >> 32u); + } + if (tid < Edges.size()) { + words[0] = Edges[tid][0]; + words[1] = Edges[tid][1]; + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Output.fill(0xdeadbeefu); + Texels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const char* run) { + constexpr std::array names{"image_atomic_and", "image_atomic_or", "image_atomic_xor", "image_atomic_umin", "image_atomic_umax"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const std::uint32_t a = in[0]; + const std::uint32_t b = in[1]; + const std::array expected{a & b, a | b, a ^ b, std::min(a, b), std::max(a, b)}; + for (std::uint32_t j = 0; j < Operations; ++j) { + const std::uint32_t returned = Output[tid * Results + 2u * j]; + const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; + const auto where = std::string(names[j]) + " " + run + ": thread " + std::to_string(tid); + Require(returned == a, where + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], where + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device, 32, device->Target()); + Check("wave32"); + Run(*device, 64, device->Target()); + Check("wave64"); + Run(*device, 64, device->ComputeTarget(32)); + Check("wave64 split"); + std::puts("image atomics bitwise tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From a93faff19f21ead39f5a16ba47c07a42cdaacddf Mon Sep 17 00:00:00 2001 From: Nahuel Genchi <65604505+NahuelGenchi@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:32:39 -0300 Subject: [PATCH 09/55] feat(shader): implement global_atomic_csub GLOBAL opcode 0x34 subtracts the data register from a global dword and clamps the result to zero, returning the previous value. It only exists in the global segment, so the flat form throws in the decoder, and the form without glc throws like buffer_atomic_csub does. The clamped subtract is now a shared helper used by both opcodes. --- .../tests/execution/GlobalAtomicCsub.cpp | 249 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 1 + .../src/IrOpcode.cpp | 2 + .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 5 + .../SpirvBackend/SpirvEmitterHelpers.hpp | 1 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 1 + .../SpirvMemory/SpirvMemoryInstructions.cpp | 15 +- .../MemoryInstructionDispatch.cpp | 5 + 9 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp new file mode 100644 index 000000000..7c84e3e8c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp @@ -0,0 +1,249 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Written = 3; +constexpr std::uint32_t SharedOffset = Threads * 4u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +constexpr std::size_t BlockBytes = 4096; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060082, 0xe0301000, 0x80000a01, 0xe0301004, 0x80000b01, 0xe0301008, 0x80000c01, + 0x7e0e02ff, 0x00000100, 0xbf8c3f70, 0xdcd18000, 0x14080a03, 0xdcd18000, 0x15080b07, 0xdcd18004, + 0x16080c07, 0xbf8c3f70, 0xe0701000, 0x80011401, 0xe0701004, 0x80011501, 0xe0701008, 0x80011601, + 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xdcd08000, 0x00080a03, 0xbf810000, +}; + +alignas(256) constexpr std::array FlatSegmentCode{ + 0xdcd10000, 0x147f0a03, 0xbf810000, +}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomic csub: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint32_t value) { + for (std::uint32_t byte = 0; byte < 4u; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, tid * 4u, RowOf(tid).memory); + } + Put(image, SharedOffset, SharedPartial); + Put(image, SharedOffset + 4u, SharedExhausted); + return image; +} + +std::vector Expected() { + auto image = Initial(); + std::uint32_t partial = SharedPartial; + std::uint32_t exhausted = SharedExhausted; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + Put(image, tid * 4u, SubtractClamped(row.memory, row.subtrahend)); + partial = SubtractClamped(partial, SharedPartialStep(tid)); + exhausted = SubtractClamped(exhausted, SharedExhaustedStep(tid)); + } + Require(partial != 0u && exhausted == 0u, "global atomic csub: the shared dwords do not cover both outcomes"); + Put(image, SharedOffset, partial); + Put(image, SharedOffset + 4u, exhausted); + return image; +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::vector UserData(const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void CheckChain(const std::string& where, std::uint32_t column, std::uint32_t initial, std::uint32_t (*step)(std::uint32_t)) { + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + order.emplace_back(Output[tid * Results + column], tid); + } + std::sort(order.begin(), order.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); + std::uint32_t running = initial; + for (const auto& [returned, tid] : order) { + Require(returned == running, where + " returned " + Hex(returned) + " to thread " + std::to_string(tid) + ", expected " + Hex(running)); + running = SubtractClamped(running, step(tid)); + } +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * Inputs] = RowOf(tid).subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + } + Output.fill(Untouched); + const auto userData = UserData(guest.Data()); + const std::span code(Code); + const auto result = Recompile(device, code, waveSize, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + const auto wave = "global atomic csub: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + const auto returned = Output[tid * Results]; + Require(returned == row.memory, wave + "thread " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + for (std::uint32_t j = Written; j < Results; ++j) { + Require(Output[tid * Results + j] == Untouched, wave + "thread " + std::to_string(tid) + " result " + std::to_string(j) + " was written"); + } + } + CheckChain(wave + "dword shared by all lanes", 1u, SharedPartial, SharedPartialStep); + CheckChain(wave + "dword exhausted by all lanes", 2u, SharedExhausted, SharedExhaustedStep); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, const std::string& reason) { + const auto userData = UserData(guest.Data()); + std::string failure; + try { + static_cast(Recompile(device, code, 32, userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + Run(*device, guest, 64); + CheckRejected(*device, guest, NoReturnCode, "global_atomic_csub without glc is not supported"); + CheckRejected(*device, guest, FlatSegmentCode, "global_atomic_csub is available only in the global segment"); + std::puts("global atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index b1fac1488..805a366f9 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -303,6 +303,7 @@ enum class IrOpcode : std::uint16_t { AddressAtomicXor32, AddressAtomicInc32, AddressAtomicDec32, + AddressAtomicUSubSat32, AddressAtomicSwap64, AddressAtomicCmpSwap64, AddressAtomicIAdd64, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index eb7d22e56..7057ea930 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -344,6 +344,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("AddressAtomicXor32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicInc32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicDec32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUSubSat32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicSwap64", U64, AddressResource, U32, U32, U64, U1), makeMeta("AddressAtomicCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), makeMeta("AddressAtomicIAdd64", U64, AddressResource, U32, U32, U64, U1), @@ -744,6 +745,7 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { case IrOpcode::AddressAtomicXor32: case IrOpcode::AddressAtomicInc32: case IrOpcode::AddressAtomicDec32: + case IrOpcode::AddressAtomicUSubSat32: case IrOpcode::AddressAtomicFCmpSwap32: case IrOpcode::AddressAtomicFMin32: case IrOpcode::AddressAtomicFMax32: diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 14cc7e3a1..23d5277fa 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -832,6 +832,7 @@ enum class RdnaOpcode : std::uint16_t { FlatAtomicFmaxX2, FlatAtomicIncX2, FlatAtomicDecX2, + GlobalAtomicCsub, GlobalStoreDwordAddtid, DsAddU32, DsAddRtnU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 53e6a4605..2544e10eb 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -190,6 +190,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x31u, RdnaOpcode::FlatAtomicCmpswap, 1, 32, false, false, false}, {0x32u, RdnaOpcode::FlatAtomicAdd, 1, 32, false, false, false}, {0x33u, RdnaOpcode::FlatAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::GlobalAtomicCsub, 1, 32, false, false, false}, {0x35u, RdnaOpcode::FlatAtomicSmin, 1, 32, false, false, false}, {0x36u, RdnaOpcode::FlatAtomicUmin, 1, 32, false, false, false}, {0x37u, RdnaOpcode::FlatAtomicSmax, 1, 32, false, false, false}, @@ -602,6 +603,7 @@ bool isFlatAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatAtomicFmaxX2: case RdnaOpcode::FlatAtomicIncX2: case RdnaOpcode::FlatAtomicDecX2: + case RdnaOpcode::GlobalAtomicCsub: return true; default: return false; } @@ -820,6 +822,9 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span Date: Mon, 5 Oct 2026 10:34:45 -0300 Subject: [PATCH 10/55] docs: record the fused assumption of v_fma_legacy_f32 in TechnicalDebt --- docs/dev/TechnicalDebt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 7468ef292..ff50930ec 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -84,6 +84,7 @@ Throughout the project, every function at every stage either **does exactly what - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. +- [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. From 4066e6dd29df6d9acb959a81d115cd1c43ec10c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 16:54:53 +0200 Subject: [PATCH 11/55] test(shader): keep the flat cache bits test within CI's time limit Co-Authored-By: Claude --- .../tests/execution/FlatCacheBits.cpp | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp index 9b1650f3f..717f9f028 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp @@ -23,62 +23,58 @@ namespace { using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Threads = 64; -constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; constexpr std::uint32_t Sentinel = 0x05e471e1; constexpr std::size_t BlockBytes = 65536; constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Output{}; -alignas(256) constexpr std::array FlatCacheBitsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0xd70f6a1e, 0x02020208, 0x7e3e0209, - 0x503e3e80, 0xdc318000, 0x0a080001, 0xdc328004, 0x0b080001, 0xdc309008, 0x0c080001, 0xdc33900c, - 0x0d080001, 0xdc359004, 0x0e080001, 0xdc228001, 0x10080001, 0xdc259007, 0x11080001, 0xbf8c3f70, - 0xdc739000, 0x00080501, 0xdc628006, 0x00080601, 0xbf8c3f70, 0xdc359000, 0x12080001, 0xdccb9008, - 0x14080701, 0xdcce900c, 0x00080401, 0xbf8c3f70, 0xdc358008, 0x15080001, 0xdc311000, 0x177d001e, - 0xdc730000, 0x007d071e, 0xbf8c0070, 0xdcef1000, 0x187d061e, 0xbf8c0070, 0xdc320000, 0x197d001e, - 0xbf8c0070, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, - 0x80011602, 0xbf810000 +alignas(256) constexpr std::array FlatCacheBitsCode{ + 0x34020084, 0x34040084, 0xd70f6a1e, 0x02020208, 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023c8c, + 0x503e3e80, 0xdc379000, 0x0a080001, 0xdc331000, 0x0d7d001e, 0xbf8c0070, 0xdc739004, 0x00080d01, + 0xdccb9008, 0x0c080a01, 0xdc731000, 0x007d0b1e, 0xbf8c0070, 0xe0781000, 0x80010a02, 0xbf810000 }; struct Row { std::array memory; std::array results; + std::array final; }; constexpr std::array Rows{{ - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678, 0x00000001, 0xffffffff, 0x00000000, 0x00000000, 0x00000001, 0x00ff0001, 0xffffffff, 0x12345677, 0x92345678, 0x00000001, 0x12345678, 0xedcba987}}, - {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f, 0x00000080, 0x80ff7f01, 0x000000ff, 0x00000000, 0x00000080, 0x00010080, 0x80ff7f01, 0x80ff7f80, 0x00000080, 0x00000080, 0x0000007f, 0x80ff7f7e}}, - {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff, 0x7fffffff, 0x0000007f, 0x00000056, 0x0000007f, 0x7fffffff, 0x7f7fffff, 0x0000007f, 0x0000007e, 0xedcba987, 0x7fffffff, 0xffffffff, 0xffffff80}}, - {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001, 0xffffffff, 0x7fffffff, 0x000000be, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, 0x80000000, 0x21524112, 0xffffffff, 0x00000001, 0x7ffffffe}}, - {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000, 0xdeadbeef, 0x12345678, 0x0000007f, 0xffffffde, 0xdeadbeef, 0xde78beef, 0x12345678, 0x92345678, 0xff0080ff, 0xdeadbeef, 0x80000000, 0x92345678}}, - {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}}, - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30, 0x91b7584a, 0xd8f16adf, 0x000000b1, 0xffffff91, 0x91b7584a, 0x91df584a, 0xd8f16adf, 0xa652a90f, 0xaafb8c3b, 0x91b7584a, 0xcd613e30, 0x159054ef}}, - {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89, 0x1027c4d1, 0x414c343c, 0x000000bb, 0x00000010, 0x1027c4d1, 0x103cc4d1, 0x414c343c, 0x5f7c1fc5, 0x5aa92fc5, 0x1027c4d1, 0x1e2feb89, 0x5f63dfb5}}, - {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061, 0xc2ce6f44, 0x7311d8a3, 0x000000d5, 0xffffffc2, 0xc2ce6f44, 0xc2a36f44, 0x7311d8a3, 0xebf6e904, 0xfa103ae6, 0xc2ce6f44, 0x78e51061, 0x0bf4c8c2}}, - {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d, 0x612e7696, 0xc9e9c616, 0x000000cc, 0x00000061, 0x612e7696, 0x61167696, 0xc9e9c616, 0xffa95f43, 0x8ef0cd12, 0x612e7696, 0x35bf992d, 0xfc565f3b}}, - {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6, 0x7ce42c82, 0x0741c7a8, 0x0000002e, 0x0000007c, 0x7ce42c82, 0x7ca82c82, 0x0741c7a8, 0xebf2348e, 0xcca93e5a, 0x7ce42c82, 0xe4b06ce6, 0xe3f1ab4e}}, - {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76, 0x63ca828d, 0x6ec9d286, 0x000000b3, 0x00000063, 0x63ca828d, 0x6386828d, 0x6ec9d286, 0x0a4ae0fc, 0xc58c5ac4, 0x63ca828d, 0x9b810e76, 0xf548dcf0}}, - {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58, 0xc4647159, 0x008a05a6, 0x000000c9, 0xffffffc4, 0xc4647159, 0xc4a67159, 0x008a05a6, 0xb2ac1ffe, 0xeefd50d3, 0xc4647159, 0xb2221a58, 0xb2a81ffe}}, - {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35, 0x442e3d43, 0xb8b6d8fe, 0x000000e5, 0x00000044, 0x442e3d43, 0x44fe3d43, 0xb8b6d8fe, 0x85fb5733, 0x5b3f9908, 0x442e3d43, 0xcd447e35, 0x75f2a6cb}}, - {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f, 0x9755d4c1, 0xf1fd42a2, 0x00000029, 0xffffff97, 0x9755d4c1, 0x97a2d4c1, 0xf1fd42a2, 0x0c28d1c1, 0xdf9b65ee, 0x9755d4c1, 0x1a2b8f1f, 0xebd6cdbd}}, - {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3, 0x51431193, 0x07d4bedc, 0x000000f3, 0x00000051, 0x51431193, 0x51dc1193, 0x07d4bedc, 0x0d8ba5bf, 0x1ef2f3aa, 0x51431193, 0x05b6e6e3, 0x0262583f}}, - {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f, 0xa648a7dd, 0x8a9a021e, 0x0000009e, 0xffffffa6, 0xa648a7dd, 0xa61ea7dd, 0x8a9a021e, 0x8cf5435d, 0xfbd7a286, 0xa648a7dd, 0x025b413f, 0x88c14321}}, - {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9, 0xe1988ad9, 0x619699cf, 0x00000014, 0xffffffe1, 0xe1988ad9, 0xe1cf8ad9, 0x619699cf, 0x115401c8, 0xbf5153af, 0xe1988ad9, 0xafbd67f9, 0xce2bfe36}}, - {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0, 0xf8130c42, 0x6c0fd4f5, 0x0000000e, 0xfffffff8, 0xf8130c42, 0xf8f50c42, 0x6c0fd4f5, 0x25e14ed5, 0x825e6b01, 0xf8130c42, 0xb9d179e0, 0xd5dead15}}, - {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f, 0x8712b8bc, 0x38c0c8fd, 0x00000037, 0xffffff87, 0x8712b8bc, 0x87fdb8bc, 0x38c0c8fd, 0xfc42b18c, 0xbc12b108, 0x8712b8bc, 0xc381e88f, 0xfb412072}}, - {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a, 0xf06d3fef, 0x7eed8d14, 0x00000066, 0xfffffff0, 0xf06d3fef, 0xf0143fef, 0x7eed8d14, 0x0c75c19e, 0x1d6ecdea, 0xf06d3fef, 0x8d88348a, 0xf365b99e}}, - {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d, 0x587fd280, 0x3b1a11df, 0x0000006c, 0x00000058, 0x587fd280, 0x58dfd280, 0x3b1a11df, 0xe860041c, 0x719a8604, 0x587fd280, 0xad45f23d, 0x965fe3e2}} + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x12345678, 0x7fffffff, 0x00000001}}, + {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x0000007f, 0x80ff7f00, 0x00000080}}, + {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0xffffffff, 0x123456f7, 0x7fffffff}}, + {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0x00000001, 0x5eadbeee, 0xffffffff}}, + {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0x80000000, 0x9333d579, 0xdeadbeef}}, + {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}}, + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0xcd613e30, 0xfb571cd4, 0x91b7584a}}, + {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1e2feb89, 0x04d2f000, 0x1027c4d1}}, + {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0x78e51061, 0xf1e6ae1e, 0xc2ce6f44}}, + {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x35bf992d, 0x70b89231, 0x612e7696}}, + {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0xe4b06ce6, 0x1f48f634, 0x7ce42c82}}, + {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x9b810e76, 0x44be8638, 0x63ca828d}}, + {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xb2221a58, 0xc3aecf2b, 0xc4647159}}, + {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0xcd447e35, 0x2abbbe2b, 0x442e3d43}}, + {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x1a2b8f1f, 0x2c8d6bd3, 0x9755d4c1}}, + {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x05b6e6e3, 0xee98b215, 0x51431193}}, + {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0x025b413f, 0x911da0d7, 0xa648a7dd}}, + {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xafbd67f9, 0x5202ae19, 0xe1988ad9}}, + {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xb9d179e0, 0xa382e3d4, 0xf8130c42}}, + {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0xc381e88f, 0x40300084, 0x8712b8bc}}, + {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0x8d88348a, 0xef06f3b4, 0xf06d3fef}}, + {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0xad45f23d, 0x76c57e18, 0x587fd280}} }}; class GuestBlock { @@ -117,11 +113,6 @@ std::string Hex(std::uint32_t value) { return text; } -std::array FinalMemory(const Row& row) { - const auto [a, b, c, d] = row.memory; - return {d ^ c, (b & ~0xff0000u) | ((c & 0xffu) << 16u), c + d, d - a}; -} - void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { const auto address = static_cast(reinterpret_cast(base)); std::vector userData(10, 0u); @@ -160,9 +151,8 @@ void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveS } std::array memory{}; std::memcpy(memory.data(), guest.Data() + tid * 16u, 16u); - const auto expected = FinalMemory(row); for (std::uint32_t dword = 0; dword < 4u; ++dword) { - Require(memory[dword] == expected[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + Require(memory[dword] == row.final[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(row.final[dword])); } } for (std::size_t offset = Threads * 16u; offset < BlockBytes; ++offset) { @@ -178,7 +168,6 @@ int main() { if (!device) return VulkanTestSkipped; GuestBlock guest; Run(*device, guest, 32); - Run(*device, guest, 64); std::puts("flat cache bits tests passed"); return 0; } catch (const std::exception& error) { From 23c2816d2167b488b15520ba07bd8e19cb24883e Mon Sep 17 00:00:00 2001 From: raghav314 Date: Mon, 5 Oct 2026 20:16:38 +0530 Subject: [PATCH 12/55] fix(ajm): release a batch when sceAjmBatchWait returns sceAjmBatchStart records the batch id it hands out and sceAjmBatchWait removes it. Waiting on a batch id that was never issued or was already waited on returns INVALID_BATCH instead of 0, as in shadPS4. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 16 ++++++++++++++-- core/libs/tests/GuestAjm.cpp | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 50cbee420..3b30d8d53 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -16,6 +16,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ extern "C" { namespace { constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast(0x80930003); +constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast(0x80930004); constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast(0x80930005); constexpr int SCE_AJM_ERROR_OUT_OF_RESOURCES = static_cast(0x80930007); @@ -127,6 +129,8 @@ std::map> g_instances; std::atomic g_nextContext{1}; std::atomic g_nextInstance{1}; std::atomic g_nextBatch{1}; +std::mutex g_batchLock; +std::set g_batches; enum class JobKind : std::uint32_t { Initialize = 1, @@ -985,14 +989,22 @@ int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int p cursor += record.bytes; } if (error) std::memset(error, 0, sizeof(*error)); - *batch = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lock(g_batchLock); + g_batches.insert(id); + } + *batch = id; return 0; } int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error) { (void)context; - (void)batch; (void)timeout; + { + std::lock_guard lock(g_batchLock); + if (g_batches.erase(batch) == 0) return SCE_AJM_ERROR_INVALID_BATCH; + } if (error) std::memset(error, 0, sizeof(*error)); return 0; } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 8d8504257..71524e885 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -321,6 +321,24 @@ void TestCodecInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestBatchWaitRelease(std::uint32_t context) { + constexpr int invalidBatch = static_cast(0x80930004); + std::vector batch(64); + AjmBatchInfo info{}; + AjmBatchError error{}; + std::uint32_t first = 0; + std::uint32_t second = 0; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &first) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &second) == 0 && second != first); + Require(sceAjmBatchWait(context, second, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, 0, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second + 1, 0, &error) == invalidBatch); +} + } int main() { @@ -343,5 +361,6 @@ int main() { TestDecodeSingle(context); TestGaplessDecode(context); TestCodecInfo(context); + TestBatchWaitRelease(context); Require(sceAjmFinalize(context) == 0); } From 14a00e6963bd5f33c94d888c55b8302542d3ab4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 17:19:12 +0200 Subject: [PATCH 13/55] test(shader): split the global atomics test to keep it within CI's time limit Co-Authored-By: Claude --- .../tests/execution/GlobalAtomics.cpp | 246 +---------- .../tests/execution/GlobalAtomics64.cpp | 400 ++++++++++++++++++ .../tests/execution/GlobalAtomicsLanes.cpp | 251 +++++++++++ 3 files changed, 655 insertions(+), 242 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp index 736dd9cdc..3409e7768 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp @@ -26,7 +26,7 @@ using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t RowsPerOp = 16; -constexpr std::uint32_t OpCount = 24; +constexpr std::uint32_t OpCount = 13; constexpr std::uint32_t NarrowOps = 13; constexpr std::uint32_t OpStride = 0x200; constexpr std::uint32_t Special = OpCount * OpStride; @@ -36,7 +36,7 @@ constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(256) constexpr std::array GlobalAtomicsCode{ +alignas(256) constexpr std::array GlobalAtomicsCode{ 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcc18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, @@ -60,31 +60,7 @@ alignas(256) constexpr std::array GlobalAtomicsCode{ 0x4a0804ff, 0x00002c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001600, 0xdcf18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001800, 0x4a0804ff, 0x00003000, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001800, 0xdcf58000, 0x14080a03, 0xbf8c3f70, - 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001a00, 0x4a0804ff, 0x00003400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00001a00, 0xdd418000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00001c00, 0x4a0804ff, 0x00003800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00001c00, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001e00, - 0x4a0804ff, 0x00003c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001e00, 0xdd498000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002000, 0x4a0804ff, 0x00004000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002000, 0xdd4d8000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002200, 0x4a0804ff, 0x00004400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002200, 0xdd558000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002400, 0x4a0804ff, 0x00004800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002400, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002600, - 0x4a0804ff, 0x00004c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002600, 0xdd5d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002800, 0x4a0804ff, 0x00005000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002800, 0xdd618000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002a00, 0x4a0804ff, 0x00005400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002a00, 0xdd658000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002c00, 0x4a0804ff, 0x00005800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002c00, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002e00, - 0x4a0804ff, 0x00005c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002e00, 0xdd6d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00003000, 0x7e2802ff, 0x05e471e1, - 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, - 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, 0x147d0a1e, 0xbf8c0070, - 0x4a0c0aff, 0x00003000, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00003400, 0x4a1414ff, 0x00000100, - 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00003200, 0xe0701000, 0x80011406, 0x7da80090, - 0x4a0602ff, 0x00003600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, 0xbf810000 + 0xe0701000, 0x80011406, 0xbf810000 }; struct Row { @@ -96,7 +72,7 @@ struct Row { std::uint64_t final; }; -constexpr std::array Rows{{ +constexpr std::array Rows{{ {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, {0, 0x1ull, 0x80000000ull, 0xf9677975ull, 0x1ull, 0x80000000ull}, {0, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, @@ -305,182 +281,6 @@ constexpr std::array Rows{{ {12, 0xf9a9360ull, 0xf9a9360ull, 0xf85b81cdull, 0xf9a9360ull, 0xf9a935full}, {12, 0x6e723d5cull, 0x6e723d5dull, 0x6e723d5cull, 0x6e723d5cull, 0x6e723d5bull}, {12, 0x1f609b4full, 0x1f609b4full, 0x69d177f8ull, 0x1f609b4full, 0x1f609b4eull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {13, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {13, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {13, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {13, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, - {13, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, - {13, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, - {13, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, - {13, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {14, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, - {14, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {14, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {14, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {14, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {14, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {14, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, - {14, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, - {14, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, - {14, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {15, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, - {15, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {15, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {15, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, - {15, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, - {15, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, - {15, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, - {15, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, - {16, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {16, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {16, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, - {16, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {16, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, - {16, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, - {16, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, - {16, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, - {17, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {17, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {17, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {17, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {17, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, - {17, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, - {17, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, - {17, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {18, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {18, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {18, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {18, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, - {18, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, - {18, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, - {18, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, - {18, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {19, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {19, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {19, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {19, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, - {19, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, - {19, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, - {19, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, - {19, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, - {20, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {20, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {20, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {20, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {20, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, - {20, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, - {20, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, - {20, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, - {21, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {21, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {21, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, - {21, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, - {21, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, - {21, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, - {21, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, - {21, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {22, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {22, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {22, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {22, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {22, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, - {22, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, - {22, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, - {22, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {23, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {23, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {23, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {23, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {23, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, - {23, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, - {23, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, - {23, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, }}; class GuestBlock { @@ -539,12 +339,6 @@ std::vector Initial() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); } } - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid, 4); - Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); - Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); - } - Put(image, Special + Threads * 16u + 4u, 0x11u, 4); return image; } @@ -555,14 +349,6 @@ std::vector Expected() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); } } - std::uint32_t total = 0x11u; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); - Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); - if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); - total += (tid + 1u) << 8u; - } - Put(image, Special + Threads * 16u + 4u, total, 4); return image; } @@ -625,18 +411,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); } } - std::vector> order; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto flat = Output[(OpCount * Threads + tid) * 2u]; - Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); - order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); - } - std::sort(order.begin(), order.end()); - std::uint32_t running = 0x11u; - for (const auto& [returned, tid] : order) { - Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); - running += (tid + 1u) << 8u; - } const auto expected = Expected(); for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { const auto actual = guest.Data()[offset]; @@ -644,15 +418,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ } } -void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { - const auto initial = Initial(); - std::memcpy(guest.Data(), initial.data(), BlockBytes); - FillInput(); - Dispatch(device, 32, guest.Data()); - for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { - Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); - } -} } @@ -661,10 +426,7 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; GuestBlock writable(true); - GuestBlock readOnly(false); RunAtomics(*device, writable, 32); - RunAtomics(*device, writable, 64); - RunReadOnly(*device, readOnly); std::puts("global atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp new file mode 100644 index 000000000..541e26eb0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp @@ -0,0 +1,400 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 11; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdd418000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdd498000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd4d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdd558000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd5d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdd618000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdd658000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdd6d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {0, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {0, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {0, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {0, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, + {0, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {0, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, + {0, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, + {0, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, + {0, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {1, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, + {1, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {1, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {1, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {1, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {1, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {1, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, + {1, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, + {1, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, + {1, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {2, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, + {2, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {2, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {2, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, + {2, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, + {2, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, + {2, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, + {2, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, + {3, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {3, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {3, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, + {3, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {3, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, + {3, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, + {3, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, + {3, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {4, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {4, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {4, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {4, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, + {4, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, + {4, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, + {4, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {5, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {5, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {5, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {5, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, + {5, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, + {5, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, + {5, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, + {5, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {6, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {6, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {6, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {6, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, + {6, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, + {6, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, + {6, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, + {7, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {7, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {7, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {7, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, + {7, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, + {7, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, + {7, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, + {8, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {8, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {8, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, + {8, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, + {8, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, + {8, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, + {8, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, + {8, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {9, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {9, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {9, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {9, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {9, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, + {9, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, + {9, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, + {9, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {10, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {10, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {10, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {10, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {10, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, + {10, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, + {10, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, + {10, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + RunAtomics(*device, writable, 32); + std::puts("global atomics 64 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp new file mode 100644 index 000000000..7629fb5af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp @@ -0,0 +1,251 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 0; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x7e2802ff, 0x05e471e1, 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, + 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, + 0x147d0a1e, 0xbf8c0070, 0x4a0c0a80, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00000400, 0x4a1414ff, + 0x00000100, 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a1400ff, 0x00000100, 0x7e160280, 0xdd488000, 0x00080a03, 0xbf8c3f70, + 0x7da80090, 0x4a0602ff, 0x00000600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid, 4); + Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); + Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid, 8); + } + Put(image, Special + Threads * 16u + 4u, 0x11u, 4); + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + std::uint32_t total = 0x11u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); + Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); + if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid + 0x100u + tid, 8); + total += (tid + 1u) << 8u; + } + Put(image, Special + Threads * 16u + 4u, total, 4); + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto flat = Output[(OpCount * Threads + tid) * 2u]; + Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); + order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); + } + std::sort(order.begin(), order.end()); + std::uint32_t running = 0x11u; + for (const auto& [returned, tid] : order) { + Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); + running += (tid + 1u) << 8u; + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global atomics lanes tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 93d14fa5f43617007082a7e336449dc226a979dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 17:32:46 +0200 Subject: [PATCH 14/55] test(shader): split the flat and global loads test to keep it within CI's time limit Co-Authored-By: Claude --- ...balLoads.cpp => FlatGlobalLoadsRegion.hpp} | 201 +----------------- .../tests/execution/FlatLoads.cpp | 182 ++++++++++++++++ .../tests/execution/FlatLoadsX2X3.cpp | 181 ++++++++++++++++ .../tests/execution/FlatLoadsX4.cpp | 181 ++++++++++++++++ .../tests/execution/GlobalLoads.cpp | 182 ++++++++++++++++ .../tests/execution/GlobalLoadsX2X3.cpp | 181 ++++++++++++++++ .../tests/execution/GlobalLoadsX4.cpp | 181 ++++++++++++++++ 7 files changed, 1093 insertions(+), 196 deletions(-) rename core/libs/prx/libSceAgcDriver/tests/execution/{FlatGlobalLoads.cpp => FlatGlobalLoadsRegion.hpp} (64%) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp similarity index 64% rename from core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp rename to core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp index 3ca6fa1b2..8a6272e69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp @@ -1,65 +1,10 @@ -#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" -#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" -#include "prx/libc/include/GuestAllocations.hpp" -#include "Recompiler.hpp" -#include "VulkanTestDevice.hpp" -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#endif -#include +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP + #include -#include -#include -#include -#include -#include -#include -#include +#include -namespace { - -using AgcDriver::Graphics::Require; -using ShaderRecompiler::ShaderStage; - -constexpr std::uint32_t Threads = 32; -constexpr std::uint32_t Results = 16; -constexpr std::uint32_t Sentinel = 0x05e471e1; -constexpr std::size_t BlockBytes = 65536; -constexpr std::size_t RegionOffset = Threads * 16u; -alignas(256) std::array Output{}; - -alignas(256) constexpr std::array GlobalLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, - 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc348ffa, - 0x0f7d0022, 0xdc3c8003, 0x11080020, 0xdc3887f8, 0x147d0022, 0xdc308000, 0x18080021, 0xdc208800, - 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -alignas(256) constexpr std::array FlatLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, - 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc340001, - 0x0f7d0024, 0xdc3c0007, 0x117d0024, 0xdc3807f1, 0x147d0024, 0xdc300400, 0x187d0024, 0xdc2807ff, - 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -constexpr std::array Region{ +inline constexpr std::array FlatGlobalLoadsRegion{ 0xd2, 0xf2, 0x26, 0x65, 0xa6, 0x0c, 0x12, 0xd2, 0x89, 0x18, 0x5d, 0x95, 0x0e, 0xe8, 0x81, 0x36, 0x09, 0x16, 0x6f, 0x6b, 0x11, 0x3d, 0x17, 0x8d, 0x6c, 0x0f, 0xd3, 0x90, 0x1f, 0xf2, 0x39, 0xa1, 0xa0, 0x95, 0xf2, 0x0f, 0x93, 0x95, 0x65, 0x0c, 0xf9, 0x38, 0x0b, 0x8e, 0xdb, 0x22, 0x4a, 0x6b, @@ -446,140 +391,4 @@ constexpr std::array Region{ 0xcd, 0x85, 0x85, 0x4d, 0x73, 0xa9, 0x16, 0x46, 0x65, 0x4a, 0xff, 0x72, 0xb1, 0x1c, 0x73, 0xa2 }; -struct Row { - std::array input; - std::array global; - std::array flat; -}; - -constexpr std::array Rows{{ - {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x6526f2d2, 0x000000d2}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0xfe1ec477, 0x0000357a}}, - {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0xa66526f2, 0x000000f2}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0xe4fe1ec4, 0x00009735}}, - {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x0ca66526, 0x00000026}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x37307a70, 0x0000b65a}}, - {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x120ca665, 0x00000065}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x307a70d9, 0x00005a60}}, - {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0xa2731cb1, 0x00000077}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x8ce4fe1e, 0x00004397}}, - {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x1cb172ff, 0x00000049}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5131541e, 0x0000295a}}, - {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0xdc295aec, 0x000000c6}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x4cb65131, 0x0000f3dc}}, - {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x7dee2657, 0x000000f1}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0xb4de5e8e, 0x00003c58}}, - {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0xf610a667, 0x000000a8}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x401ae5e1, 0x00009c8b}}, - {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x3e052289, 0x000000d7}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x3f5aefae, 0x00000e3d}}, - {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0xdebaea1d, 0x0000003b}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0xda660308, 0x0000e774}}, - {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x7f9859b1, 0x00000021}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0xb19c74d6, 0x0000f666}}, - {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x224cb259, 0x00000011}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x3c3e2f3f, 0x0000506e}}, - {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x99571c29, 0x00000065}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x936f25cd, 0x0000d7be}}, - {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x034ab01d, 0x00000053}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x6b431e8e, 0x0000f86b}}, - {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x15602efe, 0x000000fd}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0xd052e9fc, 0x00005bd3}}, - {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x65d656c0, 0x00000086}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x3a515bd3, 0x000041fd}}, - {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0xd14b0cf5, 0x00000019}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0xf83f261f, 0x00009bb0}}, - {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x1d426570, 0x0000009e}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x892f4c71, 0x0000deba}}, - {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x79fec2ec, 0x00000065}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x19598433, 0x00000233}}, - {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x524da73e, 0x000000a2}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x1588587f, 0x0000071b}}, - {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x660a3790, 0x0000008c}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0xe242e585, 0x0000380d}}, - {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0xaedff3c7, 0x00000047}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x72ee855b, 0x0000a4ef}}, - {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x92b36eda, 0x000000c7}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0xde591a71, 0x0000bf15}}, - {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x63779354, 0x00000025}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0xe9fccd90, 0x0000a52f}}, - {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x764984b5, 0x000000c2}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0xa50e02f7, 0x00001fde}}, - {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x1d9033df, 0x00000049}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x30633e8d, 0x0000489a}}, - {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} -}}; - -class GuestBlock { -public: - GuestBlock() { -#ifdef _WIN32 - block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); -#else - block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); #endif - Require(block != nullptr, "flat global loads: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, BlockBytes, true, false); - } - - ~GuestBlock() { - GuestAllocations::Mutation().Remove(block); -#ifdef _WIN32 - VirtualFree(block, 0, MEM_RELEASE); -#else - std::free(block); -#endif - } - - GuestBlock(const GuestBlock&) = delete; - GuestBlock& operator=(const GuestBlock&) = delete; - - std::uint8_t* Data() { return block; } - -private: - std::uint8_t* block = nullptr; -}; - -std::array BufferDescriptor(const void* data, std::uint32_t bytes) { - const auto address = reinterpret_cast(data); - return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; -} - -std::string Hex(std::uint32_t value) { - char text[16]; - std::snprintf(text, sizeof(text), "0x%x", value); - return text; -} - -void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { - const auto address = static_cast(reinterpret_cast(base)); - std::vector userData(10, 0u); - const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - std::copy(output.begin(), output.end(), userData.begin() + 4); - userData[8] = static_cast(address); - userData[9] = static_cast(address >> 32u); - const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; - ShaderRecompiler::RecompileRequest request{ - {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, - {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, - device.Target(), - {0, 0, 0, 128} - }; - request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); - device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); - device.WaitIdle(); -} - -void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, bool flat) { - Output.fill(Sentinel); - Dispatch(device, code, guest.Data()); - const auto wave = std::string(flat ? "flat" : "global") + " loads: lane "; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto& expected = flat ? Rows[tid].flat : Rows[tid].global; - for (std::uint32_t result = 0; result < Results; ++result) { - const auto actual = Output[tid * Results + result]; - Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); - } - } -} - -} - -int main() { - try { - const auto device = OpenVulkanTestDevice(); - if (!device) return VulkanTestSkipped; - GuestBlock guest; - std::memset(guest.Data(), 0, BlockBytes); - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); - } - std::memcpy(guest.Data() + RegionOffset, Region.data(), Region.size()); - Run(*device, guest, GlobalLoadsCode, false); - Run(*device, guest, FlatLoadsCode, true); - std::puts("flat global loads tests passed"); - return 0; - } catch (const std::exception& error) { - std::cerr << error.what() << '\n'; - return 1; - } -} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp new file mode 100644 index 000000000..6e3677e26 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, + 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc300400, + 0x187d0024, 0xdc2807ff, 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xfe1ec477, 0x0000357a}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe4fe1ec4, 0x00009735}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x37307a70, 0x0000b65a}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x307a70d9, 0x00005a60}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x8ce4fe1e, 0x00004397}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5131541e, 0x0000295a}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x4cb65131, 0x0000f3dc}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb4de5e8e, 0x00003c58}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x401ae5e1, 0x00009c8b}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3f5aefae, 0x00000e3d}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xda660308, 0x0000e774}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb19c74d6, 0x0000f666}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3c3e2f3f, 0x0000506e}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x936f25cd, 0x0000d7be}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6b431e8e, 0x0000f86b}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd052e9fc, 0x00005bd3}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3a515bd3, 0x000041fd}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf83f261f, 0x00009bb0}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x892f4c71, 0x0000deba}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x19598433, 0x00000233}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1588587f, 0x0000071b}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe242e585, 0x0000380d}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x72ee855b, 0x0000a4ef}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xde591a71, 0x0000bf15}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe9fccd90, 0x0000a52f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa50e02f7, 0x00001fde}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x30633e8d, 0x0000489a}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp new file mode 100644 index 000000000..2642ebc78 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc340001, 0x0f7d0024, 0xdc3c0007, + 0x117d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp new file mode 100644 index 000000000..008004a55 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3807f1, 0x147d0024, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp new file mode 100644 index 000000000..ad91fd604 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, + 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc308000, + 0x18080021, 0xdc208800, 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6526f2d2, 0x000000d2}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa66526f2, 0x000000f2}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x0ca66526, 0x00000026}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x120ca665, 0x00000065}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa2731cb1, 0x00000077}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1cb172ff, 0x00000049}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdc295aec, 0x000000c6}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7dee2657, 0x000000f1}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf610a667, 0x000000a8}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3e052289, 0x000000d7}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdebaea1d, 0x0000003b}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7f9859b1, 0x00000021}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x224cb259, 0x00000011}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x99571c29, 0x00000065}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x034ab01d, 0x00000053}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x15602efe, 0x000000fd}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x65d656c0, 0x00000086}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd14b0cf5, 0x00000019}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d426570, 0x0000009e}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x79fec2ec, 0x00000065}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x524da73e, 0x000000a2}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x660a3790, 0x0000008c}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xaedff3c7, 0x00000047}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x92b36eda, 0x000000c7}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x63779354, 0x00000025}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x764984b5, 0x000000c2}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d9033df, 0x00000049}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp new file mode 100644 index 000000000..49ee77d74 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc348ffa, 0x0f7d0022, 0xdc3c8003, + 0x11080020, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp new file mode 100644 index 000000000..d0bb6392d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3887f8, 0x147d0022, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 2eff3c838613afcc8d7f388d6e813215c2ecadfd Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:35:29 +0200 Subject: [PATCH 15/55] feat(agc): accept linear attribute clipping in PA_CL_CLIP_CNTL DecodeState and the draw precheck rejected PA_CL_CLIP_CNTL bit 24 (DX_LINEAR_ATTR_CLIP_ENA), so every draw that sets it was skipped. Vulkan requires clipped NoPerspective outputs to vary linearly in framebuffer space, and RADV and radeonsi set this bit for every draw they emit. With the bit set the console therefore clips the way the host driver already does, and the pipeline needs no extra state. Clip space, depth clamping and the viewport depth range decode as with the bit clear. Every other unsupported bit still rejects the draw, with or without bit 24. TechnicalDebt records that a clear bit, which was already accepted, gets the same clipping. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/src/State.cpp | 2 +- .../prx/libSceAgcDriver/tests/Graphics.cpp | 20 ++++++++++++++++--- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 48b9a1e9c..c8d156bf5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -70,7 +70,7 @@ constexpr std::uint32_t ScanModeMask = ~2u; constexpr std::uint32_t ScanControlMask = ~0x06003fffu; constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. -constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x0c000000u); +constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u); // Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth // target as if their tests always passed (wrong occlusion, but the draws run), so stages that diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 3cd226c5a..75b240db0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -728,9 +728,23 @@ void DepthClipTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "inverted viewport depth clamp"); queue.context[0xb4] = 0; for (std::uint32_t bit = 0; bit < 32; ++bit) { - if (bit == 19 || bit == 26 || bit == 27) continue; - queue.context[0x204] = 1u << bit; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + if (bit == 19 || bit == 24 || bit == 26 || bit == 27) continue; + for (const auto linearBit : {0u, 0x01000000u}) { + queue.context[0x204] = (1u << bit) | linearBit; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).find("PA_CL_CLIP_CNTL") != std::string::npos, "the precheck accepted an unsupported PA_CL_CLIP_CNTL bit"); + } + } + queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + for (const auto clip : {0x00080000u, 0x00000000u, 0x0c080000u, 0x04000000u}) { + queue.context[0x204] = clip; + const auto plain = AgcDriver::Graphics::DecodeState(queue); + queue.context[0x204] = clip | 0x01000000u; + const auto linearClip = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DX_LINEAR_ATTR_CLIP_ENA"); + Require(linearClip.negativeOneToOne == plain.negativeOneToOne && linearClip.depthClamp == plain.depthClamp && linearClip.viewport.minDepth == plain.viewport.minDepth && linearClip.viewport.maxDepth == plain.viewport.maxDepth, "DX_LINEAR_ATTR_CLIP_ENA changed the clip space, depth clamping or depth range"); } } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..31a5f5dc0 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -88,6 +88,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. +- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. - `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. From f7ede9cd7d938fb3254245d4a4583f5c28f152a6 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:25:23 +0200 Subject: [PATCH 16/55] feat(shader): implement the DPP16 row_share controls EmitDppTargetLane threw "DPP control 336 is not supported" for the DPP16 row_share controls 0x150-0x15f. row_share:sel now reads lane sel of the reading lane's own row, (lane & ~15) | sel, as the RDNA2 ISA defines it. The source never leaves the row, so the target is always valid and an inactive source follows the existing fi and bound_ctrl rule, as for row_xmask. The new agc_driver_dpp16_row_share_xmask execution test covers v_mov_b32, v_add_nc_u32, v_sub_nc_u32 and v_max_f32 with row_share and row_xmask under a partial exec mask, with fi, bound_ctrl and row/bank masks, as wave32 and wave64; row_xmask had no test before. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/Dpp16RowShareXmask.cpp | 289 ++++++++++++++++++ .../src/SpirvMemory/SpirvSubgroup.cpp | 7 + 2 files changed, 296 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp new file mode 100644 index 000000000..0fd6a6861 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp @@ -0,0 +1,289 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, + 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, 0xff0d5804, 0x7e1c02fa, + 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, 0xff096f04, 0x7e2402fa, + 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, 0x69055404, 0x4a2c0afa, + 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, 0xff056906, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0x7e1402fa, 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, + 0xff0d5804, 0x7e1c02fa, 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, + 0xff096f04, 0x7e2402fa, 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, + 0x69055404, 0x4a2c0afa, 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, + 0xff056906, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6ecd1cd2u, 0x4bef5ab2u, 0xc1f00000u, 0xc0c00000u}, + {0x6a003be9u, 0xe12916b9u, 0x422a0000u, 0x41fc0000u}, + {0xe8ace817u, 0x43f615feu, 0x42600000u, 0xc21e0000u}, + {0x4e3aa244u, 0x2d47d909u, 0x42540000u, 0xc2220000u}, + {0x68060c75u, 0xd69d14d5u, 0x41940000u, 0xc1d40000u}, + {0x5af9fb5au, 0x54393673u, 0x42440000u, 0xc1080000u}, + {0xad52f58fu, 0x3c3ff249u, 0x3fc00000u, 0xc26c0000u}, + {0xcb826265u, 0xd06f4fc2u, 0x425c0000u, 0xc2420000u}, + {0xc99d74ceu, 0xcb3eacf5u, 0x41ac0000u, 0x42320000u}, + {0x9c7fb5dbu, 0xa5e8a4bau, 0xc2340000u, 0xc24a0000u}, + {0x1e7cc79eu, 0x3197b5a3u, 0x41c00000u, 0x42420000u}, + {0x099b18b9u, 0xc7d8a2a5u, 0xc26a0000u, 0xc1980000u}, + {0x1504efdfu, 0x7e04a9d5u, 0xc2520000u, 0x422a0000u}, + {0xf200add6u, 0xd20e47a3u, 0xc1680000u, 0xc1380000u}, + {0xe31908c0u, 0x60de8b85u, 0xc20c0000u, 0x421a0000u}, + {0xf1c81bb0u, 0xa39b139du, 0x40000000u, 0xc2300000u}, + {0xdd7dd010u, 0x93d28ae8u, 0xc1f80000u, 0xc2460000u}, + {0xea239e60u, 0x1136bab0u, 0xc0900000u, 0xc1840000u}, + {0x4a248cbdu, 0x2cde59ccu, 0xc1380000u, 0xc1c40000u}, + {0x06734af7u, 0xfc5d75e7u, 0x40b00000u, 0xc2200000u}, + {0x76646c94u, 0x33cb3f0cu, 0xc25e0000u, 0xc2080000u}, + {0xc3f02221u, 0xba35f124u, 0xc2600000u, 0x424e0000u}, + {0xb955e2e5u, 0xb0a01d78u, 0xc1a40000u, 0x41300000u}, + {0xad47bf7du, 0x7e37ee41u, 0xc2380000u, 0x41e80000u}, + {0x4467b3a9u, 0xc243faa9u, 0x42400000u, 0x42280000u}, + {0xee39a32fu, 0x5d27404bu, 0xc1e00000u, 0xc2020000u}, + {0x362b62e4u, 0x40e07905u, 0x425e0000u, 0xc2640000u}, + {0x3597987bu, 0x9f2dda53u, 0xc1280000u, 0xc0200000u}, + {0x8cdfd404u, 0xaa825860u, 0x42700000u, 0x41940000u}, + {0x2883e9fdu, 0x6fcb4509u, 0xc1bc0000u, 0xc2700000u}, + {0xa64c74f6u, 0x6c225e76u, 0x41c40000u, 0xc22a0000u}, + {0xc9c4946du, 0x27f8b552u, 0x41300000u, 0xbf000000u}, + {0xec1dd6b9u, 0x37eae67cu, 0x42300000u, 0xc1fc0000u}, + {0x080a718au, 0xdd3a87b9u, 0xc1480000u, 0x426c0000u}, + {0x4d7d2f79u, 0x242b5b96u, 0xc1b80000u, 0xc25e0000u}, + {0xda0d5bd5u, 0xbd1eab24u, 0x41f40000u, 0xc2060000u}, + {0x30aa4ea5u, 0x908d264eu, 0xc0800000u, 0xc0c00000u}, + {0x4d4b8ec9u, 0x59f9bbd9u, 0xc1c40000u, 0x40c00000u}, + {0x008d7334u, 0x81e7a48bu, 0x3f800000u, 0x423a0000u}, + {0xa985a1d6u, 0x6454c697u, 0xc2300000u, 0xc22a0000u}, + {0x6945a98eu, 0x56efdfffu, 0x425e0000u, 0x41200000u}, + {0x543a17e0u, 0x9680cbfau, 0xc2360000u, 0xc20e0000u}, + {0x0e1e5311u, 0x8dbd6564u, 0xc2480000u, 0xc24c0000u}, + {0xd6c814a2u, 0x7f2c1289u, 0xc0b00000u, 0x41e00000u}, + {0x248db44au, 0xc60fd986u, 0x42400000u, 0xc2160000u}, + {0xd6c5e906u, 0x10117cecu, 0xc1f80000u, 0x41a80000u}, + {0x324909c5u, 0xdcd7b8c2u, 0x42080000u, 0x42220000u}, + {0xf995627fu, 0xc6e555f6u, 0xc2260000u, 0xc1800000u}, + {0x735c003eu, 0xc54e7632u, 0x41900000u, 0xc25a0000u}, + {0xfc3effbbu, 0x46c20a66u, 0xc1ec0000u, 0xc20a0000u}, + {0x0986feb5u, 0x040b21dfu, 0xc2180000u, 0x3f800000u}, + {0xacca236du, 0x7cfef2f2u, 0x40f00000u, 0x41cc0000u}, + {0x00bd61d8u, 0x5654123eu, 0x41400000u, 0xc1100000u}, + {0xe319a18bu, 0x5d8bc32du, 0x41100000u, 0xc2480000u}, + {0x6583147du, 0x72ce7a51u, 0x41780000u, 0x426e0000u}, + {0x49981da9u, 0x9b4ba109u, 0xc1a40000u, 0xc1e00000u}, + {0xe341cf98u, 0x3ec5433cu, 0xc0000000u, 0xc23c0000u}, + {0xc861e665u, 0xda64e2ddu, 0xc1f00000u, 0x41900000u}, + {0x8c31f3d4u, 0x807eed38u, 0xc1300000u, 0xc24e0000u}, + {0xf5411da7u, 0x6886f7e2u, 0xc1580000u, 0x420e0000u}, + {0x56aecd4cu, 0x7cfe2052u, 0xc23a0000u, 0x423a0000u}, + {0x29fc3f6du, 0x843d8b71u, 0x41d40000u, 0xbfc00000u}, + {0xf14c9634u, 0x6de904e9u, 0xc1c40000u, 0xc1180000u}, + {0x0524b659u, 0xb07d24c5u, 0xc22e0000u, 0xc23a0000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x080a718au, 0x008d7334u, 0xf995627fu, 0xdeaddeadu, 0x11f84251u, 0x5c789ac6u, 0x68953521u, 0xdeaddeadu, 0x9edb028au, 0x00000000u, 0xc1fc0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xec1dd6b9u, 0xa985a1d6u, 0x00000000u, 0xdeaddeadu, 0xb747e38eu, 0x01c83c03u, 0x0de4d65eu, 0xdeaddeadu, 0x47532c91u, 0x426c0000u, 0x426c0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xda0d5bd5u, 0x30aa4ea5u, 0x00000000u, 0xdeaddeadu, 0xfe38b76bu, 0x48b90fe0u, 0x54d5aa3bu, 0xdeaddeadu, 0xd56a06e9u, 0x00000000u, 0xc0b00000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x4d7d2f79u, 0x4d4b8ec9u, 0x248db44au, 0xdeaddeadu, 0x972c06f9u, 0xe1ac5f6eu, 0xedc8f9c9u, 0xdeaddeadu, 0x752a5ea1u, 0x00000000u, 0xc2060000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0xf995627fu, 0x00000000u, 0xdeaddeadu, 0x708e27cfu, 0xbb0e8044u, 0xdeaddeadu, 0xdeaddeadu, 0x9a2982abu, 0x00000000u, 0x42300000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0x248db44au, 0x00000000u, 0xdeaddeadu, 0x67cac139u, 0xb24b19aeu, 0xdeaddeadu, 0xdeaddeadu, 0x1bc83c72u, 0x00000000u, 0x41f40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x0e1e5311u, 0xda0d5bd5u, 0xdeaddeadu, 0xa01d355bu, 0xea9d8dd0u, 0xf6ba282bu, 0xdeaddeadu, 0x41fa9804u, 0x00000000u, 0xc1c40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x543a17e0u, 0xec1dd6b9u, 0xdeaddeadu, 0xa0f2b1cbu, 0xeb730a40u, 0xf78fa49bu, 0xdeaddeadu, 0x8697d983u, 0x00000000u, 0x3f800000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x6583147du, 0x0524b659u, 0xdeaddeadu, 0xdeaddeadu, 0xc54e7632u, 0xdeaddeadu, 0xdeaddeadu, 0x64adc93bu, 0x00000000u, 0xc1f00000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x00bd61d8u, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x040b21dfu, 0xdeaddeadu, 0xdeaddeadu, 0x0119947au, 0x3f800000u, 0x3f800000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xacca236du, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x5d8bc32du, 0xdeaddeadu, 0xdeaddeadu, 0x85b60c6bu, 0x00000000u, 0xc23a0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xfc3effbbu, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x9b4ba109u, 0xdeaddeadu, 0xdeaddeadu, 0xf0e652cbu, 0x00000000u, 0xc1c40000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xc861e665u, 0xf14c9634u, 0x49981da9u, 0x0986feb5u, 0xdeaddeadu, 0x3ec5433cu, 0xdeaddeadu, 0xdeaddeadu, 0xa4545e4fu, 0x00000000u, 0xc1ec0000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xe341cf98u, 0x0524b659u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xda64e2ddu, 0xdeaddeadu, 0xdeaddeadu, 0x26587efbu, 0x41900000u, 0x41900000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x0524b659u, 0xe341cf98u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x6de904e9u, 0xdeaddeadu, 0xdeaddeadu, 0x3ee11e84u, 0x00000000u, 0xc1180000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xf14c9634u, 0xc861e665u, 0x735c003eu, 0xdeaddeadu, 0xdeaddeadu, 0xb07d24c5u, 0xdeaddeadu, 0xdeaddeadu, 0x5909d9f0u, 0x00000000u, 0x41780000u} +}; + +constexpr const char* Names[Checked] = {"mov_row_share0", "mov_row_share5_fi", "mov_row_share12_bc", "mov_row_share8_bc_fi", "mov_row_share15_masks", "mov_row_xmask1", "mov_row_xmask6_fi", "mov_row_xmask15_bc", "mov_row_xmask10_masks", "add_row_share3", "add_row_share12_bc", "add_row_share4_fi_masks", "add_row_xmask4", "sub_row_xmask13_bc_fi", "max_f32_row_share4_bc", "max_f32_row_xmask9_fi"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 row share xmask wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 row share xmask tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 390ac734b..6532d500d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -141,6 +141,13 @@ namespace ShaderRecompiler if (control == 0x141u) { return EmitDppMirrorTargetLane(state, subid, true); } + if (control >= 0x150u && control <= 0x15fu) { + const auto row = state.module.AllocateId(); + const auto target = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)); + state.module.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, ConstantU32(state, control & 0xfu)); + return {target, ConstantBool(state, true)}; + } if (control >= 0x160u && control <= 0x16fu) { const auto target = state.module.AllocateId(); state.module.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)); From 51c98a3983ef2422f558e6c870b25dc1ea9335ac Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:32:34 +0200 Subject: [PATCH 17/55] fix(agc): store sceAgcGetIsTrinityMode's flag through its out pointer sceAgcGetIsTrinityMode takes a pointer to a one-byte flag; the only known caller ignores the return value. The export took no argument and never wrote the flag. That caller's flag is in zero-initialised .bss, so this corrects the signature; it does not fix a visible failure. The function now stores false, as sceKernelIsTrinityMode reports a base PS5, returns 0 and throws on a null pointer, whose error code is unknown. It moves from Unimplemented.cpp to Misc/src/Platform.cpp, and the new agc_platform_tests ctest target covers it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/CMakeLists.txt | 14 +++++ .../prx/libSceAgc/Misc/include/Platform.hpp | 4 ++ core/libs/prx/libSceAgc/Misc/src/Platform.cpp | 17 ++++++ core/libs/prx/libSceAgc/Unimplemented.cpp | 4 -- core/libs/prx/libSceAgc/tests/Platform.cpp | 53 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgc/Misc/include/Platform.hpp create mode 100644 core/libs/prx/libSceAgc/Misc/src/Platform.cpp create mode 100644 core/libs/prx/libSceAgc/tests/Platform.cpp diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 4e9a781f8..f72bd1472 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Registers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Workload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp @@ -69,6 +70,19 @@ target_link_libraries(agc_command_tests PRIVATE libSceAgc libc) configure_windows_unwind(agc_command_tests) configure_windows_unwind(libSceAgc) +add_test_executable(agc_platform_tests EXCLUDE_FROM_ALL tests/Platform.cpp) +target_include_directories(agc_platform_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_platform_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_platform_tests) +if(BUILD_TESTING) + set_target_properties(agc_platform_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_platform_tests COMMAND agc_platform_tests) + if(WIN32) + set_tests_properties(agc_platform_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL tests/RegisterCommands.cpp Command/src/Packet.cpp diff --git a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp new file mode 100644 index 000000000..fd07f976a --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp @@ -0,0 +1,4 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP +#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP + +#endif diff --git a/core/libs/prx/libSceAgc/Misc/src/Platform.cpp b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp new file mode 100644 index 000000000..de162d3a7 --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp @@ -0,0 +1,17 @@ +#include "prx/libSceAgc/Misc/include/Platform.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode) { + Agc::Command::CheckAddress(reinterpret_cast(isTrinityMode), alignof(bool), __func__); + *isTrinityMode = false; + return 0; +} + +} diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..97c5525cb 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -70,10 +70,6 @@ int APS5_VABI sceAgcGetGsOversubscription() { return 0; } -int APS5_VABI sceAgcGetIsTrinityMode() { - return 0; -} - int APS5_VABI sceAgcGetSemaphoreLabel() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAgc/tests/Platform.cpp b/core/libs/prx/libSceAgc/tests/Platform.cpp new file mode 100644 index 000000000..daa0634cf --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/Platform.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +extern "C" int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +void testTrinityMode() { + std::array flags{true, true, true}; + check(sceAgcGetIsTrinityMode(&flags[1]) == 0, "Trinity mode query did not return 0"); + check(!flags[1], "base PS5 GPU reported as Trinity"); + check(flags[0] && flags[2], "Trinity mode query wrote past its one-byte flag"); +} + +void testRejections() { + expectFailure([] { sceAgcGetIsTrinityMode(nullptr); }); +} + +} + +int main() { + try { + testTrinityMode(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC platform tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..3d65018a4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -32,6 +32,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted +- [sceAgcGetIsTrinityMode](../../core/libs/prx/libSceAgc/Misc/src/Platform.cpp) (libSceAgc) - signature from the only PPSA26344 call (a pointer to a one-byte flag, result ignored); a null pointer throws because its error code is unknown - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name From 73c68c04bd5175fb15b9a1869359214850e0b070 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:21:09 +0200 Subject: [PATCH 18/55] feat(agc): implement image atomics on 32_SINT and 32_FLOAT surfaces The resource materializer refused every atomic image descriptor whose format was not 32_UINT. RDNA2 image atomics act on the raw dword and the numeric format only affects addressing, so 32_SINT and 32_FLOAT surfaces are now accepted with the Uint numeric class and run through the existing integer atomics and float compare-exchange loops. Other formats still throw, and the message now names the format. DescriptorBinding gains imageAtomic, carried through the shader disk cache (format version 4) and the resource content key. The driver binds those elements through a cached R32_UINT view of R32_SINT and R32_SFLOAT storage images. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/include/ShaderResources.hpp | 1 + .../Graphics/include/Texture.hpp | 4 +- .../Graphics/src/ShaderResources.cpp | 4 +- .../libSceAgcDriver/Graphics/src/Texture.cpp | 25 +++++++++--- .../prx/libSceAgcDriver/tests/Recorder.cpp | 39 +++++++++++++++++++ .../libSceAgcDriver/tests/ShaderDiskCache.cpp | 3 +- .../tests/execution/ImageAtomics.cpp | 22 ++++++----- .../tests/execution/ImageAtomicsFloat.cpp | 35 ++++++++++++----- .../src/DescriptorBindingBuilder.cpp | 1 + .../Optimization/src/ResourceMaterializer.cpp | 6 +-- core/shader/recompiler/Recompiler.hpp | 1 + core/shader/recompiler/ShaderDiskCache.cpp | 4 +- core/shader/recompiler/ShaderDiskCache.hpp | 2 +- docs/dev/TechnicalDebt.md | 1 + 14 files changed, 117 insertions(+), 31 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 205551744..69d086ca4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -411,6 +411,7 @@ private: std::vector storageKeys; std::vector storageFirstLayer; std::vector storageWritten; + std::vector storageAtomic; std::vector> samplers; bool reusable = false; std::vector directRegions; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index f1ef0b0da..198939b7b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -114,6 +114,7 @@ public: // so successive mip writes of a chain share one image and one write-back. VkImageView View(std::uint32_t mip); VkImageView FirstLayerView(std::uint32_t mip); + VkImageView AtomicView(std::uint32_t mip, bool firstLayer); // Render targets live in the same images: draws attach mip 0 through a view of the color // buffer's format and mark the image dirty like a storage write. bool Attachable() const { return attachable; } @@ -394,7 +395,7 @@ private: bool clearByKeysFill(DccKeys keys, std::uint8_t key); bool overlaps(std::uint64_t address, std::size_t bytes) const; bool pendingUnitInside(std::uint64_t address, std::size_t bytes) const; - VkImageView createView(std::uint32_t mip, bool firstLayer = false) const; + VkImageView createView(std::uint32_t mip, bool firstLayer, VkFormat format) const; void release() noexcept; Context context; @@ -442,6 +443,7 @@ private: std::uint32_t defaultMip = 0; std::map extraViews; std::map firstLayerViews; + std::map, VkImageView> atomicViews; bool attachable = false; std::map, VkImageView> attachmentViews; VkFormat storageFormat = VK_FORMAT_UNDEFINED; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 0dafb11f8..c1fff993b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1056,7 +1056,7 @@ void ShaderResources::buildComplete() { break; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: write.pImageInfo = images.data() + images.size(); - for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); + for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); break; case VK_DESCRIPTOR_TYPE_SAMPLER: write.pImageInfo = images.data() + images.size(); @@ -1201,6 +1201,7 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha packBits(binding.imageWritten); packBits(binding.samplerDepthCompare); packBits(binding.imageDepthCompare); + packBits(binding.imageAtomic); // Read-only elements are bound without a write set: an object built for one written set // must not serve a build with another (the variant implies it, this makes it explicit). packBits(binding.bufferWritten); @@ -2564,6 +2565,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind storageFirstLayer.push_back(firstLayer); // Images the shader only reads have nothing to store back. storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]); + storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]); describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress}); item.imageAllocations.push_back(storageTextures.size() - 1); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 777529fa5..2bb762263 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -691,7 +691,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, uploadReason = "first"; upload(); defaultMip = mipLevel; - view = createView(mipLevel); + view = createView(mipLevel, false, storageFormat); { auto& live = Live(); std::lock_guard lock(live.mutex); @@ -942,15 +942,18 @@ bool AdjacentGenerationEnabled() { } -VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const { +VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer, VkFormat format) const { Require(mip < descriptor.mipCount, "storage texture mip level is outside the texture"); Require(!firstLayer || descriptor.dimension == TextureDimension::k2DArray, "a first-layer storage view needs a 2D array surface"); const auto viewLayerCount = firstLayer ? 1u : geometry.imageLayers - descriptor.baseArray; + VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + usage.usage = VK_IMAGE_USAGE_STORAGE_BIT; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.pNext = format == storageFormat ? nullptr : &usage; viewInfo.image = image; // Storage views address one mip; cube faces are written as array layers. viewInfo.viewType = firstLayer ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : descriptor.dimension == TextureDimension::k2D ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; - viewInfo.format = storageFormat; + viewInfo.format = format; viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, viewLayerCount}; VkImageView created = VK_NULL_HANDLE; @@ -984,7 +987,7 @@ VkImageView StorageTexture::View(std::uint32_t mip) { if (mip == defaultMip) return view; const auto found = extraViews.find(mip); if (found != extraViews.end()) return found->second; - const auto created = createView(mip); + const auto created = createView(mip, false, storageFormat); extraViews.emplace(mip, created); return created; } @@ -992,11 +995,21 @@ VkImageView StorageTexture::View(std::uint32_t mip) { VkImageView StorageTexture::FirstLayerView(std::uint32_t mip) { const auto found = firstLayerViews.find(mip); if (found != firstLayerViews.end()) return found->second; - const auto created = createView(mip, true); + const auto created = createView(mip, true, storageFormat); firstLayerViews.emplace(mip, created); return created; } +VkImageView StorageTexture::AtomicView(std::uint32_t mip, bool firstLayer) { + if (storageFormat == VK_FORMAT_R32_UINT) return firstLayer ? FirstLayerView(mip) : View(mip); + Require(storageFormat == VK_FORMAT_R32_SINT || storageFormat == VK_FORMAT_R32_SFLOAT, "storage image atomics need a surface of one 32-bit component"); + const auto found = atomicViews.find({mip, firstLayer}); + if (found != atomicViews.end()) return found->second; + const auto created = createView(mip, firstLayer, VK_FORMAT_R32_UINT); + atomicViews.emplace(std::pair{mip, firstLayer}, created); + return created; +} + const char* LookupOutcomes::Name(Kind kind) { static constexpr const char* names[Count] = {"sampled fast hit", "sampled fast miss", "sampled hit view", "sampled hit cleared view", "sampled hit snapshot", "sampled made view", "sampled made snapshot", "storage hit", "storage made", "refresh unchanged", "refresh compared", "upload direct", "upload cpu", "upload clear", "dcc scan", "pending flush"}; return kind < Count ? names[kind] : "?"; @@ -3559,6 +3572,8 @@ void StorageTexture::release() noexcept { extraViews.clear(); for (const auto& [mip, extra] : firstLayerViews) context.Function("vkDestroyImageView")(context.device, extra, nullptr); firstLayerViews.clear(); + for (const auto& [key, atomic] : atomicViews) context.Function("vkDestroyImageView")(context.device, atomic, nullptr); + atomicViews.clear(); for (const auto& [format, attachment] : attachmentViews) context.Function("vkDestroyImageView")(context.device, attachment, nullptr); attachmentViews.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 8ae3444be..795db774c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -2573,6 +2573,44 @@ void firstLayerViewTests(const Device& device, Recorder& recorder) { expectRed(program.Red(flatTexture.View(), flatTexture.Layout(), 0.0f), 0x20 / 255.0f, "a 2D texture over the surface does not read its first layer"); } +void atomicViewTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + TextureDetiler detiler(context); + auto withDetiler = context; + withDetiler.detiler = &detiler; + for (const std::uint32_t format : {20u, 21u, 22u, 56u}) { + GuestTextureResource resource{}; + resource.width = 64; + resource.height = 4; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = format; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + std::vector memory(static_cast(geometry.guestBytes) + 256); + resource.baseAddress = (reinterpret_cast(memory.data()) + 255) & ~std::uintptr_t{255}; + auto image = std::make_shared(withDetiler, detiler, resource, 0); + recorder.Keep(image); + if (format == 56u) { + bool refused = false; + try { + static_cast(image->AtomicView(0, false)); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "an 8_8_8_8 storage image has an atomic view"); + continue; + } + const auto atomic = image->AtomicView(0, false); + Require(atomic != VK_NULL_HANDLE && atomic == image->AtomicView(0, false), "atomic storage views are not reused"); + Require((atomic == image->View(0)) == (format == 20u), "only a 32_UINT storage image serves atomics through its own view"); + } +} + void keysFillTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -2724,6 +2762,7 @@ int main() { dataRefreshTests(device, recorder); minLodTests(device, recorder); firstLayerViewTests(device, recorder); + atomicViewTests(device, recorder); metadataPassTests(device, recorder); pendingKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 55e79fbc5..20dd3e202 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -31,7 +31,7 @@ void setEnvironment(const char* name, const std::string& value) { } bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) { - return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; + return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; } bool sameBindings(const std::vector& left, const std::vector& right) { @@ -92,6 +92,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.samplerDepthCompare = {true, false, true}; binding.imageDepthCompare = {false, true, false}; binding.imageWritten = {false, true, true}; + binding.imageAtomic = {false, true, false}; binding.bufferAtomic = {true}; binding.bufferWritten = {false, false, true, true, false}; return binding; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp index 7eaf3d20f..f27314c9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp @@ -21,6 +21,8 @@ constexpr std::uint32_t Results = 16; constexpr std::uint32_t Operations = 6; constexpr std::uint32_t TextureHeight = 8; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Input{}; alignas(256) std::array Output{}; @@ -71,24 +73,24 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t format) { Output.fill(0xdeadbeefu); Texels.fill(0xdeadbeefu); std::vector userData(16, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight, format); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); std::copy(texture.begin(), texture.end(), userData.begin() + 8); @@ -107,7 +109,7 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void Check() { +void Check(std::uint32_t format) { constexpr std::array names{"image_atomic_sub", "image_atomic_smin", "image_atomic_smax", "image_atomic_inc", "image_atomic_dec", "image_atomic_cmpswap"}; for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto* in = &Input[tid * Inputs]; @@ -127,8 +129,8 @@ void Check() { for (std::uint32_t j = 0; j < Operations; ++j) { const std::uint32_t returned = Output[tid * Results + 2u * j]; const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; - Require(returned == a, std::string(names[j]) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); - Require(stored == expected[j], std::string(names[j]) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + Require(returned == a, std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); } } } @@ -140,8 +142,10 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillInput(); - Run(*device); - Check(); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, format); + Check(format); + } std::puts("image atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp index ff553a2b3..1799bebc2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp @@ -24,6 +24,9 @@ constexpr std::uint32_t Rows = 800; constexpr std::uint32_t TextureWidth = 64; constexpr std::uint32_t TextureHeight = 4; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format16_16Float = 29; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Buffer{}; alignas(4096) std::array Texels{}; @@ -474,22 +477,22 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data) { +std::array TextureDescriptor(const void* data, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((TextureWidth - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((TextureWidth - 1u) & 3u) << 30u), ((TextureWidth - 1u) >> 2u) | ((TextureHeight - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name) { +ShaderRecompiler::RecompileResult Compile(std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, std::uint32_t format) { const std::span code(Code); std::vector userData(16, 0u); const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data()); + const auto texture = TextureDescriptor(Texels.data(), format); std::copy(buffer.begin(), buffer.end(), userData.begin()); std::copy(texture.begin(), texture.end(), userData.begin() + 4); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; @@ -501,7 +504,12 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe {0, 0, 0, 128} }; request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name, std::uint32_t format) { + const std::span code(Code); + const auto result = Compile(waveSize, target, format); Texels.fill(0xdeadbeefu); for (std::uint32_t first = 0; first < Rows; first += waveSize) { Buffer.fill(0xdeadbeefu); @@ -521,7 +529,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe for (std::uint32_t j = 0; j < Checked; ++j) { const auto actual = Buffer[MaxLanes * Inputs + lane * Results + j]; char message[192]; - std::snprintf(message, sizeof(message), "image float atomics %s: row %u %s is 0x%08x, expected 0x%08x", name, row, names[j], actual, expected[j]); + std::snprintf(message, sizeof(message), "image float atomics %s, format %u: row %u %s is 0x%08x, expected 0x%08x", name, format, row, names[j], actual, expected[j]); Require(actual == expected[j], message); } } @@ -534,9 +542,18 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device, 32, device->Target(), "wave32"); - Run(*device, 64, device->Target(), "wave64"); - Run(*device, 64, device->ComputeTarget(32), "wave64 split"); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, 32, device->Target(), "wave32", format); + Run(*device, 64, device->Target(), "wave64", format); + Run(*device, 64, device->ComputeTarget(32), "wave64 split", format); + } + bool refused = false; + try { + static_cast(Compile(32, device->Target(), Format16_16Float)); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("atomic image descriptor uses an unsupported format 29") != std::string::npos; + } + Require(refused, "image float atomics on a 16_16 float surface were not refused"); std::puts("image float atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 7fef993b7..2b06d89d7 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -241,6 +241,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con const auto& image = info.images.at(resource); physical.imageWritten.push_back(image.written || image.atomic); physical.imageDepthCompare.push_back(image.depthCompare); + physical.imageAtomic.push_back(image.atomic); } break; case DescriptorRole::GuestSamplers: diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 4308347e1..943b3e14e 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -151,8 +151,8 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.dimension = descriptorDimension(descriptor, base.dimension); decoded.cube = descriptorIsCube(descriptor); const auto format = rawImageFormat(descriptor); - if (base.atomic && format != IrBufferFormat::Format32UInt) { - throw std::runtime_error("atomic image descriptor uses an unsupported format"); + if (base.atomic && format != IrBufferFormat::Format32UInt && format != IrBufferFormat::Format32SInt && format != IrBufferFormat::Format32Float) { + throw std::runtime_error("atomic image descriptor uses an unsupported format " + std::to_string(static_cast(format))); } const bool storage = base.resourceClass == ImageResourceClass::Storage; decoded.fmask = IsFmaskTextureFormat(format); @@ -170,7 +170,7 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.shaderSwizzle = descriptorImageSwizzle(descriptor); } const bool rawSintStorage = storage && format == IrBufferFormat::Format32SInt && base.written && !base.read && !base.atomic; - decoded.numericClass = SampledTextureNumericClass(format); + decoded.numericClass = base.atomic ? IrTextureNumericClass::Uint : SampledTextureNumericClass(format); if (!storage && !base.depthCompare && IsDepthBitsTexture(descriptor.dwords[1], descriptor.dwords[3])) { decoded.depthBits = true; decoded.depthUnorm16 = DepthBitsTextureWidth(descriptor.dwords[1], descriptor.dwords[3]) == 16u; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 9cf9234e1..054374fb5 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -310,6 +310,7 @@ struct DescriptorBinding { // Guest image elements the shader stores to (or updates atomically); the others are only read. std::vector imageWritten; std::vector imageDepthCompare; + std::vector imageAtomic; // Guest buffer elements the shader updates atomically (one entry per element of a GuestBuffers // binding, empty otherwise). An atomic on a host-imported range is a serialized PCIe round trip // (~0.4-0.5 us each on NVIDIA), so a driver may keep these elements in device-local memory. diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index fd57be587..c93a751b1 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -64,7 +64,7 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(RecompileResult) == 176, "RecompileResult changed: update EncodeResult and DecodeResult"); -static_assert(sizeof(DescriptorBinding) == 264, "DescriptorBinding changed: update the binding encoder"); +static_assert(sizeof(DescriptorBinding) == 304, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 28, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: update the parameter encoder"); static_assert(sizeof(CompiledShaderInfo) == 304, "CompiledShaderInfo changed: update the info encoder"); @@ -248,6 +248,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.samplerDepthCompare); writer.Flags(binding.imageWritten); writer.Flags(binding.imageDepthCompare); + writer.Flags(binding.imageAtomic); writer.Flags(binding.bufferAtomic); writer.Flags(binding.bufferWritten); } @@ -266,6 +267,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) { reader.Flags(binding.samplerDepthCompare); reader.Flags(binding.imageWritten); reader.Flags(binding.imageDepthCompare); + reader.Flags(binding.imageAtomic); reader.Flags(binding.bufferAtomic); reader.Flags(binding.bufferWritten); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 216931eee..84c7a081b 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 3; +inline constexpr std::uint32_t FormatVersion = 4; enum class LoadStatus { Loaded, diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..2b6f95bd7 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -82,6 +82,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws. +- [Image atomics](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view; their float results are assumed to match those measured on 32_UINT surfaces. Other formats throw. - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. From 5e851fbb2629e934f4cd6cbdbb57a06d4c31e99d Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:32:17 +0200 Subject: [PATCH 19/55] feat(agc): implement the interpolant mapping variant for high-half f16 inputs PPSA26344 imports libSceAgc NID dbOlWdppb4o, whose name is unknown, and calls it with the (regs, gs, ps) arguments of sceAgcCreateInterpolantMapping to fill the 32 SPI_PS_INPUT_CNTL registers. It is exported as sceAgcUnknownCreateInterpolantMapping. It differs from sceAgcCreateInterpolantMapping only for inputs that carry the high f16 half alone (is_f16 2): these take the default bit and DEFAULT_VAL from the high half, as KytyPS5 does. Both exports share validation and the register loop. More than 32 pixel shader inputs now throw; before, they were written past the 32-register block. agc_interpolant_mapping covers both exports. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/CMakeLists.txt | 15 ++ .../Shader/src/InterpolantMapping.cpp | 75 ++++++++-- .../libSceAgc/tests/InterpolantMapping.cpp | 134 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 4e9a781f8..36e6c7a0e 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -121,6 +121,21 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp) +target_include_directories(agc_interpolant_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgc libc) +set_target_properties(agc_interpolant_mapping_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_interpolant_mapping_tests) + +if(BUILD_TESTING) + set_target_properties(agc_interpolant_mapping_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_interpolant_mapping COMMAND agc_interpolant_mapping_tests) + if(WIN32) + set_tests_properties(agc_interpolant_mapping PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_shader_link_tests ${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp) target_include_directories(guest_shader_link_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 515458391..5ce4d1555 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -8,11 +8,48 @@ #include "prx/libSceAgc/Shader/include/ShaderUtils.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" -extern "C" { +namespace { -APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); -int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - constexpr auto fn = __func__; +constexpr std::uint32_t InterpolantRegisterCount = 32; +constexpr std::uint32_t InterpolantF16HiOnly = 2; + +using CreateInterpolantValueFn = std::uint32_t (*)(std::uint32_t psWord, const ShaderSemantic* gsSemantic); + +std::uint32_t CreateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = ((psWord & 0x00300000u) != 0) + ? CreateInterpolantF16Value(psWord, gsSemantic) + : CreateInterpolantNonF16Value(psWord, gsSemantic); + + return (gsSemantic == nullptr) + ? CreateInterpolantDefaultValue(value, psWord) + : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); +} + +std::uint32_t CreateInterpolantF16HiValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + std::uint32_t value = ((psWord << 4u) & 0x03000000u) | 0x00080000u; + if (gsSemantic == nullptr || (psWord & ShaderSemanticWord(*gsSemantic) & 0x00200000u) == 0) { + value |= 0x00000020u; + } + value = ApplyInterpolantDefaultValueHi(value, psWord); + value |= ((psWord >> 30u) & 0x3u) << 8u; + if (gsSemantic == nullptr) { + return value; + } + + value |= (ShaderSemanticWord(*gsSemantic) >> 8u) & 0x1Fu; + if ((psWord & 0x01400000u) != 0) { + value |= 0x00000400u; + } + return value; +} + +std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + return (((psWord >> 20u) & 0x3u) == InterpolantF16HiOnly) + ? CreateInterpolantF16HiValue(psWord, gsSemantic) + : CreateInterpolantValue(psWord, gsSemantic); +} + +int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); } @@ -26,6 +63,10 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* throw std::runtime_error(std::string(fn) + ": ps->input_semantics is null but num_input_semantics != 0"); } + if (ps->num_input_semantics > InterpolantRegisterCount) { + throw std::runtime_error(std::string(fn) + ": ps->num_input_semantics exceeds the interpolant register count"); + } + if (gs == nullptr) { throw std::runtime_error(std::string(fn) + ": gs is null"); } @@ -37,17 +78,7 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) { const ShaderSemantic& psSemantic = ps->input_semantics[i]; const ShaderSemantic* gsSemantic = FindOutputSemantic(gs, psSemantic.semantic); - const std::uint32_t psWord = ShaderSemanticWord(psSemantic); - - std::uint32_t value = ((psWord & 0x00300000u) != 0) - ? CreateInterpolantF16Value(psWord, gsSemantic) - : CreateInterpolantNonF16Value(psWord, gsSemantic); - - value = (gsSemantic == nullptr) - ? CreateInterpolantDefaultValue(value, psWord) - : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); - - SetInterpolantRegister(regs, i, value); + SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic)); } FillIdentityInterpolants(regs, ps->num_input_semantics); @@ -55,3 +86,17 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* } } + +extern "C" { + +APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); +int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue); +} + +APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping); +int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16); +} + +} diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp new file mode 100644 index 000000000..bee89bb8d --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -0,0 +1,134 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" + +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); + +namespace { + +using Registers = std::array; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::exception& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +Registers filled() { + Registers regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + return regs; +} + +ShaderSemantic semantic(std::uint32_t id, std::uint32_t hardwareMapping, std::uint32_t f16) { + ShaderSemantic value{}; + value.semantic = id; + value.hardware_mapping = hardwareMapping; + value.is_f16 = f16; + return value; +} + +void checkIdentity(const Registers& regs, std::uint32_t first, const char* message) { + for (std::uint32_t i = first; i < regs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && regs[i].value == i, message); + } +} + +void testIdentity() { + auto regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, nullptr) == 0, "mapping without shaders failed"); + checkIdentity(regs, 0, "mapping without a pixel shader is not the identity"); + Shader ps{}; + regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed"); + checkIdentity(regs, 0, "mapping without inputs is not the identity"); +} + +void testMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + auto split = filled(); + check(sceAgcUnknownCreateInterpolantMapping(split.data(), &gs, &ps) == 0, "split f16 mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(split[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "interpolant register offset changed"); + } + check(split[0].value == 0x02680707u, "high f16 half provided by the gs is not mapped"); + check(split[1].value == 0x02280124u, "high f16 half missing from the gs does not use its default"); + check(split[2].value == 0x02480220u, "unmatched high f16 half does not use its default"); + check(split[3].value == 0x00000609u, "32-bit interpolant mapping changed"); + check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed"); + checkIdentity(split, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto regular = filled(); + check(sceAgcCreateInterpolantMapping(regular.data(), &gs, &ps) == 0, "mapping failed"); + check(regular[3].value == split[3].value && regular[4].value == split[4].value, "mappings differ outside the high f16 half mode"); + checkIdentity(regular, static_cast(inputs.size()), "unused interpolants are not the identity"); +} + +void testRejections() { + std::array inputs{}; + Shader gs{}; + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + std::array regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + const auto saved = regs; + expectFailure([&] { sceAgcCreateInterpolantMapping(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); }); + check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); + ps.num_input_semantics = 1; + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); }); +} + +} + +int main() { + try { + testIdentity(); + testMapping(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC interpolant mapping tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..612cd0f7f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -36,6 +36,7 @@ Throughout the project, every function at every stage either **does exactly what - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name - [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name - [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime From 9df0de2990383c677438c734975f406dc9244fdc Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:44:06 +0200 Subject: [PATCH 20/55] feat(agc): implement COND_EXEC Validate refused every COND_EXEC, so a submission that uses one was rejected as a whole. The packet now reads the dword at its address once the earlier packets of its queue and their GPU writes have completed, and skips the exec_count dwords after it when the dword is zero. The command copy that inlines INDIRECT_BUFFERs records where each range ends, so a range over an INDIRECT_BUFFER skips the whole buffer. Unaligned addresses, address bits 48 and up, and reserved control and count bits throw; the cache policy is accepted on compute queues only, as the MEC defines it. A flip inside a range, a range that ends inside a packet or past its command buffer, and a chained INDIRECT_BUFFER or REWIND inside a range throw. agc_driver_pm4 and agc_driver_flip cover the skips and the rejections. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/Driver/Driver.hpp | 2 +- .../include/Driver/Queues/Submission.hpp | 1 + .../libSceAgcDriver/Execution/include/Pm4.hpp | 2 + .../src/Driver/Packets/CommandValidation.cpp | 12 +- .../src/Driver/Packets/PacketExecution.cpp | 8 +- .../src/Driver/Queues/Submission.cpp | 32 +++- .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 27 ++- core/libs/prx/libSceAgcDriver/tests/Flip.cpp | 6 + core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 154 +++++++++++++++++- docs/dev/TechnicalDebt.md | 3 + 10 files changed, 236 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 00f21aa66..cf2941e62 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -128,7 +128,7 @@ private: static std::array& writeHistory(); static std::size_t& writeCursor(); static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr); - static void validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest = nullptr); + static void validate(const Submission& submission, const std::uint32_t* guest = nullptr); static void reportSkip(const char* kind, const std::string& what); static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); static bool matchesFillKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp index ed7da06d9..cdc9f1683 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -25,6 +25,7 @@ struct Submission { std::shared_ptr shaders; std::map> flips; std::map> renderingWaits; + std::map conditionalEnds; bool suspend = false; bool waitFree = false; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 3add10cd8..c413e357e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -96,6 +96,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +std::size_t ConditionalWords(std::span packet); +std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the // bytes it stores, so the write can be recorded on the GPU behind the work it signals. Packets // without a memory destination decode to nothing. No allocation per label (tens of thousands per diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp index 492086cc3..dd7c023ba 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp @@ -47,8 +47,12 @@ void Driver::dumpPackets(std::span commands, const std::uin } } -void Driver::validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest) { +void Driver::validate(const Submission& submission, const std::uint32_t* guest) { + const std::span commands = submission.commands; + const auto queue = submission.queue; + std::vector guarded; for (std::size_t cursor = 0; cursor < commands.size();) { + std::erase_if(guarded, [cursor](std::size_t end) { return end <= cursor; }); const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { ++cursor; continue; } if ((header & 0xc0000000u) != 0xc0000000u) { @@ -65,6 +69,12 @@ void Driver::validate(std::span commands, std::uint32_t que require(count <= commands.size() - cursor, "truncated PM4 packet"); try { Pm4::Validate(commands.subspan(cursor, count), queue); + if (header == FlipPacketHeader && !guarded.empty()) throw std::runtime_error("a flip inside a conditional execution range (conditional flip reservation) is not implemented"); + if (((header >> 8u) & 0xffu) == 0x22u) { + const auto end = submission.conditionalEnds.find(cursor); + if (end == submission.conditionalEnds.end()) throw std::runtime_error("conditional execution range is not made of whole packets of its command buffer"); + guarded.push_back(end->second); + } } catch (const std::exception& error) { dumpPackets(commands, guest); throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 80355fc7e..06dcb02cb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -111,6 +111,7 @@ void Driver::execute(const Submission& submission) { const auto count = Pm4::PacketWords(header); const auto packet = std::span(submission.commands).subspan(cursor, count); const auto opcode = (header >> 8u) & 0xffu; + auto nextCursor = cursor + count; std::shared_lock deviceUse(deviceReplacement, std::defer_lock); if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock(); @@ -272,6 +273,11 @@ void Driver::execute(const Submission& submission) { } }); }); finishDrawPacket(drawn); + } else if (opcode == 0x22) { + recordQueuedLabelsBeforeRead(submission.queue); + const auto condition = Pm4::ReadCondition(packet); + if (condition == 0) nextCursor = submission.conditionalEnds.at(cursor); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x COND_EXEC at DWORD %zu reads 0x%x at 0x%llx: %s %zu dwords\n", TraceMs(), submission.queue, cursor, condition, static_cast(packet[1] | (static_cast(packet[2]) << 32u)), condition == 0 ? "skips" : "executes", submission.conditionalEnds.at(cursor) - cursor - count); } else if (sampleDump && !wroteOnGpu) { dumpSampleCounters(packet[2] | (static_cast(packet[3]) << 32u)); } else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) { @@ -284,7 +290,7 @@ void Driver::execute(const Submission& submission) { if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue); } if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork(); - cursor += count; + cursor = nextCursor; } static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 4dbe52c02..edd9b5b70 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -11,6 +11,7 @@ namespace AgcDriver::DriverDetail { void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) { submission.commands.clear(); + submission.conditionalEnds.clear(); std::size_t budget = std::size_t{1} << 26u; copySegment(submission, guest, words, budget); } @@ -18,34 +19,57 @@ void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, st bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget) { require(words <= budget, "command buffer jumps exceed the copy limit (a jump loop?)"); budget -= words; + std::vector> guarded; + const auto reach = [&](std::size_t cursor, std::size_t next) { + std::erase_if(guarded, [&](const auto& range) { + if (range.first != cursor) return false; + submission.conditionalEnds.emplace(range.second, submission.commands.size()); + return true; + }); + for (const auto& range : guarded) require(range.first >= next, "conditional execution range ends inside a packet"); + }; for (std::size_t cursor = 0; cursor < words;) { const auto header = guest[cursor]; - if (Pm4::FillerPacket(header)) { submission.commands.push_back(header); ++cursor; continue; } + if (Pm4::FillerPacket(header)) { + reach(cursor, cursor + 1); + submission.commands.push_back(header); + ++cursor; + continue; + } const auto count = (header & 0xc0000000u) == 0xc0000000u ? Pm4::PacketWords(header) : words - cursor; if ((header & 0xc0000000u) != 0xc0000000u || count > words - cursor) { submission.commands.insert(submission.commands.end(), guest + cursor, guest + words); return false; } + reach(cursor, cursor + count); const auto opcode = (header >> 8u) & 0xffu; if (opcode == 0x3fu) { require(count == 4, "invalid INDIRECT_BUFFER size"); const auto* target = reinterpret_cast(static_cast(guest[cursor + 1] & ~3u) | (static_cast(guest[cursor + 2] & 0xffffu) << 32u)); const std::size_t targetWords = guest[cursor + 3] & 0xfffffu; const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0; + require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented"); GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t)); - if (copySegment(submission, target, targetWords, budget)) return true; + if (copySegment(submission, target, targetWords, budget)) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); + return true; + } if (chain) return false; cursor += count; continue; } + if (opcode == 0x22u && count == 5) guarded.emplace_back(cursor + count + Pm4::ConditionalWords(std::span(guest + cursor, count)), submission.commands.size()); submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count); cursor += count; if (opcode == 0x59u) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); submission.rewindTail = guest + cursor; submission.rewindWords = words - cursor; return true; } } + reach(words, words); + require(guarded.empty(), "conditional execution range exceeds its command buffer"); return false; } @@ -104,7 +128,7 @@ void Driver::executeRewindTail(const Submission& stalled) { Submission tail{}; tail.queue = stalled.queue; copyCommands(tail, stalled.rewindTail, stalled.rewindWords); - validate(tail.commands, tail.queue, stalled.rewindTail); + validate(tail, stalled.rewindTail); waitForFlipRoom(tail); { std::lock_guard lock(mutex); @@ -135,7 +159,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) { copyCommands(submission, descriptor.addr, descriptor.dw_num); } const auto copied = profile ? std::chrono::steady_clock::now() : start; - validate(submission.commands, queue, descriptor.addr); + validate(submission, descriptor.addr); waitForFlipRoom(submission); static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast(descriptor.addr)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..c4e8a16a9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } switch (opcode) { - case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: case 0x27: + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27: case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50: case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; @@ -176,7 +176,6 @@ std::string_view UnsupportedReason(std::uint32_t header) { case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x20: return "GPU query predication is not implemented"; - case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; case 0x39: @@ -194,6 +193,8 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } +constexpr std::uint32_t ConditionCachePolicy = 3u << 25u; +constexpr std::uint32_t ConditionalWordsMask = 0x3fffu; constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; @@ -296,6 +297,13 @@ void Validate(std::span packet, std::uint32_t queue) { require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size"); require((packet.back() & ~0xa020u) == 0x41u, "indirect dispatch modifiers are not implemented"); break; + case 0x22: + size(5); + require((packet[1] & 3u) == 0, "COND_EXEC reserved address bits are not implemented"); + require(packet[2] <= 0xffffu, "COND_EXEC address bits above 48 are not implemented"); + require((packet[3] & ~(queue == 0 ? 0u : ConditionCachePolicy)) == 0, "COND_EXEC reserved control fields are not implemented"); + require((packet[4] & ~ConditionalWordsMask) == 0, "COND_EXEC reserved count bits are not implemented"); + break; case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break; case 0x46: { require((packet[1] & ~0x73fu) == 0, "unsupported EVENT_WRITE flags or reserved bits"); @@ -462,6 +470,19 @@ std::size_t WaitAwaitedBytes(std::span packet) { return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; } +std::size_t ConditionalWords(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + return packet[4] & ConditionalWordsMask; +} + +std::uint32_t ReadCondition(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + std::uint32_t value = 0; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(&value, 1)), 4); + return value; +} + std::optional DecodeLabelWrite(std::span packet) { const auto opcode = (packet[0] >> 8u) & 0xffu; if (opcode == 0x49 && packet.size() >= 7) { @@ -537,7 +558,7 @@ bool UsesGpuCacheBarrier(std::span packet) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + case 0x22: case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; default: return false; } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index 9beb24074..596974c90 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -114,6 +114,12 @@ void testFlipAndBoundary() { Packet rejected{rollback.data(), 12, 0, {}}; expectFailure([&] { sceAgcDriverSubmitDcb(&rejected); }); check(output->state->alive == 0 && output->state->ready == 0, "rejected submission retained or executed a reservation"); + alignas(4) static std::uint32_t condition = 1; + const auto conditionAddress = reinterpret_cast(&condition); + std::array guarded{0xc0032200, static_cast(conditionAddress), static_cast(conditionAddress >> 32u), 0, 6, 0xc004105c, 7, 0, 1, 0, 0}; + Packet conditionalFlip{guarded.data(), 11, 0, {}}; + check(expectFailure([&] { sceAgcDriverSubmitDcb(&conditionalFlip); }).find("a flip inside a conditional execution range") != std::string::npos, "a flip inside a COND_EXEC range was not rejected"); + check(output->state->alive == 0 && output->state->ready == 0, "a rejected conditional flip retained or executed a reservation"); { std::lock_guard lock(output->state->mutex); output->state->block = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..d057b23ff 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -2,12 +2,16 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libc/include/Shutdown.hpp" +#include +#include #include #include #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -405,6 +409,60 @@ void testMemorySynchronization() { check(memory.destination == 42, "failed synchronization changed the destination"); } +void testConditionalValidation() { + alignas(4) static std::uint32_t condition = 0; + const auto valid = makePacket(0x22, {low(&condition), high(&condition), 0, 0x3fff}); + check(AgcDriver::Pm4::UnsupportedReason(valid[0]).empty() && AgcDriver::Pm4::AccessesMemory(valid[0]), "COND_EXEC is rejected or does not synchronize guest memory"); + AgcDriver::Pm4::Validate(valid, 0); + AgcDriver::Pm4::Validate(valid, 0x20); + check(AgcDriver::Pm4::ConditionalWords(valid) == 0x3fff, "COND_EXEC count decoded wrong"); + check(AgcDriver::Pm4::ConditionalWords(makePacket(0x22, {low(&condition), high(&condition), 0, 0})) == 0, "empty COND_EXEC range decoded wrong"); + AgcDriver::Pm4::Validate(makePacket(0x22, {low(&condition), high(&condition), 3u << 25u, 5}), 0x20); + const auto invalidWord = [&](std::size_t word, std::uint32_t value, std::uint32_t queue, const char* text) { + auto packet = valid; + packet[word] = value; + expectFailure([&] { AgcDriver::Pm4::Validate(packet, queue); }, text); + }; + invalidWord(1, low(&condition) | 1u, 0, "reserved address bits"); + invalidWord(1, low(&condition) | 2u, 0x20, "reserved address bits"); + invalidWord(2, 0x10000u, 0, "above 48"); + invalidWord(3, 3u << 25u, 0, "reserved control fields"); + invalidWord(3, 1u, 0x20, "reserved control fields"); + invalidWord(3, 1u << 27u, 0x20, "reserved control fields"); + invalidWord(4, 0x4000u, 0, "reserved count bits"); + invalidWord(4, 0x80000005u, 0x20, "reserved count bits"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 2), 0x20); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::ConditionalWords(makePacket(0x37, {0x100, 0, 0, 0})); }, "expected COND_EXEC"); + AgcDriver::QueueState state; + expectFailure([&] { AgcDriver::Pm4::Execute(valid, state); }, "driver execution"); +} + +void testConditionReadSynchronization() { + alignas(4) static std::uint32_t condition = 0; + static std::size_t flushed = 0; + condition = 0; + flushed = 0; + struct FlushHookReset { + ~FlushHookReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } reset; + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + check(address == reinterpret_cast(&condition) && bytes == sizeof(condition), "COND_EXEC synchronized an unrelated range"); + condition = 0x80; + ++flushed; + }); + const auto packet = makePacket(0x22, {low(&condition), high(&condition), 0, 5}); + check(AgcDriver::Pm4::ReadCondition(packet) == 0x80 && flushed == 1, "COND_EXEC read its condition before the GPU work that writes it"); + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t, std::size_t) { + throw std::runtime_error("condition synchronization failed"); + }); + expectFailure([&] { AgcDriver::Pm4::ReadCondition(packet); }, "condition synchronization failed"); + AgcDriver::GuestMemory::SetFlushHook(nullptr); + expectFailure([] { AgcDriver::Pm4::ReadCondition(makePacket(0x22, {0x1000, 0, 0, 5})); }, "guest"); +} + void testWriteChangedKeepsUntouchedBytes() { alignas(256) static std::uint8_t guest[256]; std::memset(guest, 0, sizeof(guest)); @@ -541,6 +599,97 @@ void testDriverSubmission() { check(destination[0] == 0, "rejected submission executed a prefix"); } +std::vector joinPackets(std::initializer_list> packets) { + std::vector words; + for (const auto& packet : packets) words.insert(words.end(), packet.begin(), packet.end()); + return words; +} + +std::vector writeWord(std::uint32_t& target, std::uint32_t value) { + return makePacket(0x37, {0x00100200, low(&target), high(&target), value}); +} + +std::vector conditional(const std::uint32_t& condition, std::uint32_t words, std::uint32_t control = 0) { + return makePacket(0x22, {low(&condition), high(&condition), control, words}); +} + +std::vector indirectBuffer(const std::vector& target, bool chain = false) { + return makePacket(0x3f, {low(target.data()), high(target.data()), static_cast(target.size()) | (chain ? 1u << 20u : 0u)}); +} + +void submitWords(std::vector& words, std::uint32_t queue = 0) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check((queue == 0 ? sceAgcDriverSubmitDcb(&packet) : sceAgcDriverSubmitAcb(queue, &packet)) == 0, "conditional submission failed"); +} + +void testConditionalSubmission() { + alignas(8) static std::uint32_t zero = 0, one = 1, condition = 0; + static std::array results{}; + results.fill(0); + const auto run = [](std::vector words, std::uint32_t queue = 0) { + submitWords(words, queue); + AgcDriverWaitIdle_nid_postfix(); + }; + run(joinPackets({conditional(one, 5), writeWord(results[0], 11), writeWord(results[1], 12)})); + check(results[0] == 11 && results[1] == 12, "COND_EXEC skipped a range whose condition is set"); + run(joinPackets({conditional(zero, 6), {0x80000000u}, writeWord(results[0], 21), writeWord(results[1], 22)})); + check(results[0] == 11 && results[1] == 22, "COND_EXEC did not skip exactly its range when the condition is zero"); + run(joinPackets({conditional(zero, 0), writeWord(results[2], 23)})); + check(results[2] == 23, "an empty COND_EXEC range skipped the next packet"); + alignas(4) static std::uint32_t never = 0; + never = 0; + auto skippedWait = joinPackets({conditional(zero, 9), makePacket(0x3c, {0x13, low(&never), high(&never), 1, 0xffffffffu, 0x190}), makePacket(0x10, {0}, 0x30), writeWord(results[14], 24)}); + submitWords(skippedWait); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (std::atomic_ref(results[14]).load() != 24 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const bool skipped = std::atomic_ref(results[14]).load() == 24; + std::atomic_ref(never).store(1); + AgcDriverWaitIdle_nid_postfix(); + check(skipped, "a WAIT_REG_MEM inside a skipped COND_EXEC range was executed"); + + condition = 1; + run(joinPackets({writeWord(condition, 0), conditional(condition, 5), writeWord(results[0], 31), writeWord(results[1], 32)})); + check(condition == 0 && results[0] == 11 && results[1] == 32, "COND_EXEC read its condition before an earlier packet of its queue stored it"); + run(joinPackets({writeWord(condition, 9), conditional(condition, 5), writeWord(results[0], 41)})); + check(results[0] == 41, "COND_EXEC skipped a range whose condition an earlier packet of its queue set"); + condition = 0; + run(joinPackets({conditional(condition, 5, 3u << 25u), writeWord(results[3], 42), writeWord(condition, 1), conditional(condition, 5, 1u << 25u), writeWord(results[4], 43)}), 0x20); + check(results[3] == 0 && results[4] == 43, "compute queue COND_EXEC with a cache policy evaluated wrong"); + + run(joinPackets({conditional(one, 10), conditional(zero, 10), writeWord(results[5], 51), writeWord(results[6], 52), writeWord(results[7], 53)})); + check(results[5] == 0 && results[6] == 0 && results[7] == 53, "a nested COND_EXEC range reaching past the outer range was not skipped exactly"); + run(joinPackets({conditional(zero, 10), conditional(one, 10), writeWord(results[5], 54), writeWord(results[6], 55), writeWord(results[7], 56)})); + check(results[5] == 0 && results[6] == 55 && results[7] == 56, "a skipped COND_EXEC range did not skip the COND_EXEC inside it"); + + static std::vector target; + target = joinPackets({writeWord(results[8], 61), conditional(zero, 5), writeWord(results[9], 62), writeWord(results[10], 63)}); + run(joinPackets({conditional(zero, 4), indirectBuffer(target), writeWord(results[11], 64)})); + check(results[8] == 0 && results[10] == 0 && results[11] == 64, "COND_EXEC over an INDIRECT_BUFFER did not skip the whole buffer"); + run(joinPackets({conditional(one, 4), indirectBuffer(target), writeWord(results[11], 65)})); + check(results[8] == 61 && results[9] == 0 && results[10] == 63 && results[11] == 65, "COND_EXEC over an INDIRECT_BUFFER or inside one evaluated wrong"); + + const auto rejected = [](std::vector words, const char* text) { + results[12] = 0; + expectFailure([&] { submitWords(words); }, text); + AgcDriverWaitIdle_nid_postfix(); + check(results[12] == 0, "a rejected conditional submission executed a prefix"); + }; + const auto sentinel = writeWord(results[12], 1); + rejected(joinPackets({sentinel, conditional(one, 3), writeWord(results[13], 1)}), "ends inside a packet"); + rejected(joinPackets({sentinel, conditional(one, 6), writeWord(results[13], 1)}), "exceeds its command buffer"); + rejected(joinPackets({sentinel, conditional(one, 6), {0xc004105cu, 7, 0, 1, 0, 0}}), "a flip inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 2), makePacket(0x59, {0x80000000u})}), "REWIND inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 4), indirectBuffer(target, true)}), "chained INDIRECT_BUFFER"); + rejected(joinPackets({sentinel, conditional(one, 2), {0x40000000u, 0}}), "whole packets"); + rejected(joinPackets({sentinel, makePacket(0x22, {low(&one), high(&one), 0, 0x4005}), writeWord(results[13], 1)}), "reserved count bits"); + static std::vector unaligned, overlong; + unaligned = joinPackets({conditional(one, 3), writeWord(results[13], 1)}); + overlong = joinPackets({conditional(one, 10), writeWord(results[13], 1)}); + rejected(joinPackets({sentinel, indirectBuffer(unaligned)}), "ends inside a packet"); + rejected(joinPackets({sentinel, indirectBuffer(overlong), writeWord(results[13], 1)}), "exceeds its command buffer"); + check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); +} + void testAsyncMemoryFailure() { auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -572,11 +721,14 @@ int main(int argc, char** argv) { testCopies(); testMemoryCopyDecode(); testMemorySynchronization(); + testConditionalValidation(); + testConditionReadSynchronization(); testEventWrite(); testAcquireMem(); testDriverSubmission(); + testConditionalSubmission(); LibcRunShutdown_nid_postfix(); - std::puts("PM4 catalog, registers, state, memory and submission tests passed"); + std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed"); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..63840037c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -85,6 +85,9 @@ Throughout the project, every function at every stage either **does exactly what - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. +- [COND_EXEC](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here. +- A [flip inside a COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp) throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown. +- A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. From 61d2fb0a5363ce3a26ba74f2718dc727165a757e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:57:06 +0200 Subject: [PATCH 21/55] feat(agc): accept the WRITE_DATA cache policy WRITE_DATA's control dword carries a two-bit cache policy in bits 26:25 (LRU, stream, no-allocate, bypass). Validate refused it, so a submission with a WRITE_DATA built with a non-zero policy was rejected as a whole. The policy only steers how the GPU L2 keeps the written lines; the stored words are the same, and Execute already writes them to guest memory in queue order. The mask now accepts bits 26:25, as was done for DMA_DATA's cache policy hints. The CE engine and the remaining reserved bits still throw, and the message no longer names the cache. agc_driver_pm4 stores both dwords of a WRITE_DATA with control 0x04100200 and checks that reserved bit 27 is still refused. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp | 3 ++- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..4b72ec3c5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -196,6 +196,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; +constexpr std::uint32_t WriteDataCachePolicy = 3u << 25u; void Validate(std::span packet, std::uint32_t queue) { require(!packet.empty(), "truncated PM4 header"); @@ -380,7 +381,7 @@ void Validate(std::span packet, std::uint32_t queue) { break; case 0x37: { require(packet.size() >= 5, "WRITE_DATA has no data"); - require((packet[1] & ~0x40110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented"); + require((packet[1] & ~(0x40110f00u | WriteDataCachePolicy)) == 0, "WRITE_DATA engine or reserved fields are not implemented"); const auto destination = (packet[1] >> 8u) & 0xfu; require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented"); require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..9195aa78c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -290,6 +290,9 @@ void testMemory() { check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed"); execute(state, makePacket(0x37, {0x40000100, low(data.data()), high(data.data()), 41, 42})); check(data[0] == 41 && data[1] == 42, "WRITE_DATA from the PFP failed"); + execute(state, makePacket(0x37, {0x04100200, low(data.data()), high(data.data()), 61, 62})); + check(data[0] == 61 && data[1] == 62, "WRITE_DATA with a cache policy failed"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x08000100, low(data.data()), high(data.data()), 71}), 0); }, "reserved"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x80000100, low(data.data()), high(data.data()), 51}), 0); }, "engine"); execute(state, makePacket(0x81, {4, 31, 32})); execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); From 040475211107d508eaa3beeed8c4f3c83a3be9b2 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:31:24 +0200 Subject: [PATCH 22/55] feat(audioout): implement sceAudioOut2Set3DLatency and accept sceAudioOut2MasteringInit(0) PPSA26344 calls sceAudioOut2Set3DLatency(0xFF, 2) right after sceAudioOut2Initialize and fails its audio initialization on a negative return; the export was missing, so the import did not resolve. Its audio subsystem setup also calls sceAudioOut2MasteringInit(0), which threw. sceAudioOut2Set3DLatency accepts a latency of 2 for the system user (0xFF) and throws for any other user or latency, as the unit and range are unknown. sceAudioOut2MasteringInit accepts flags 0 and throws for other flags. There is no 3D rendering or mastering stage, so neither call changes the output; TechnicalDebt lists this under Functional and the assumed signatures under Unknown function info. The new audio_out2_latency test covers the accepted and thrown values. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 10 ++++ .../libSceAudioOut/src/AudioOut2Mastering.cpp | 3 +- .../libSceAudioOut/src/AudioOut2System.cpp | 9 +++ .../libSceAudioOut/tests/AudioOut2Latency.cpp | 55 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 3 + 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 024083dad..6251abea9 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -52,4 +52,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out2_latency_tests tests/AudioOut2Latency.cpp) + target_include_directories(audio_out2_latency_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out2_latency_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out2_latency_tests) + add_test(NAME audio_out2_latency COMMAND audio_out2_latency_tests) + if(WIN32) + set_tests_properties(audio_out2_latency PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index d8b0455c2..ac3f33976 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -5,8 +5,7 @@ extern "C" { int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { - (void)flags; - NotImplemented_nid_no_patch(__func__); + if (flags != 0) NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index b690814de..f658ddf10 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,12 +1,21 @@ +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static constexpr int USER_ID_SYSTEM = 0xFF; +static constexpr std::uint32_t SUPPORTED_3D_LATENCY = 2; + extern "C" { int APS5_VABI sceAudioOut2Initialize(void) { return 0; } +int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t latency) { + if (userId != USER_ID_SYSTEM || latency != SUPPORTED_3D_LATENCY) NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { if (!state) return static_cast(0x80260502); state->loudness = 0.0f; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp new file mode 100644 index 000000000..7f3404f2a --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -0,0 +1,55 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOut2Initialize(); +int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); +int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +static bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +namespace { + +constexpr int systemUser = 0xFF; +constexpr int user = 0x10000000; + +void TestSet3DLatency() { + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 for the system user must be accepted"); + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 must be accepted again"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 1); }), "latency 1 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 3); }), "latency 3 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(user, 2); }), "a user other than the system user must throw"); +} + +void TestMasteringInit() { + Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); +} + +} + +int main() { + Require(sceAudioOut2Initialize() == 0, "initialization must succeed"); + TestSet3DLatency(); + TestMasteringInit(); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..de501d159 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -49,6 +49,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the only known call sets just edi and esi; only `(0xFF, 2)` is accepted +- [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device @@ -96,6 +98,7 @@ Throughout the project, every function at every stage either **does exactly what - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564). - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. From 5085746fce2bd8215347850c9bbd387f2f20c99d Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:00:19 +0200 Subject: [PATCH 23/55] fix(libc): reach the application map area end with the guest arena On Windows the guest arena ended at 0x72_0000_0000: #231 kept its 448 GiB size after #153 had moved its start down to 0x2_0000_0000. Titles map fixed addresses up to the application map area end, 0xFC_0000_0000 (SCE_KERNEL_APP_MAP_AREA_END_ADDR on the PS4), and a direct memory mapping above the old end threw "shared mapping outside the guest arena". The arena is now reserved up to 0xFC_0000_0000. The out-of-arena errors name the address and length, and a fixed mapping over a used arena range says whether it hit a host region. guest_memory maps direct memory at the last two pages below the end, reads it through an alias and checks that a mapping at the end is refused. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/GuestArena.cpp | 27 ++++++++++++++++------ core/libs/tests/GuestMemory.cpp | 33 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index b53450599..92f4877dc 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -21,7 +21,7 @@ namespace { constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; -constexpr std::size_t ArenaSize = 0x0000007000000000ull; +constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull; std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) { return (value + alignment - 1) & ~(static_cast(alignment) - 1); @@ -69,8 +69,11 @@ public: std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto next = _used.lower_bound(start); - if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) - throw std::runtime_error("fixed guest mapping overlaps a guest arena range"); + if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) { + char message[160]; + std::snprintf(message, sizeof(message), "fixed guest mapping 0x%llx+0x%zx overlaps %s", static_cast(start), bytes, OverlapsHostRegion(start, bytes) ? "a host region in the guest arena" : "a guest arena range"); + throw std::runtime_error(message); + } _used.emplace(start, start + bytes); } @@ -103,7 +106,7 @@ private: SYSTEM_INFO system{}; GetSystemInfo(&system); const std::uintptr_t granularity = system.dwAllocationGranularity; - const std::uintptr_t end = ArenaStart + ArenaSize; + const std::uintptr_t end = ApplicationAreaEnd; for (std::uintptr_t cursor = ArenaStart; cursor < end;) { MEMORY_BASIC_INFORMATION info{}; if (VirtualQuery(reinterpret_cast(cursor), &info, sizeof(info)) == 0) throw std::system_error(static_cast(GetLastError()), std::system_category(), "query the guest arena range"); @@ -197,18 +200,28 @@ bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_ return Arena::Get().OverlapsHostRegion(address, bytes); } +namespace { + +std::invalid_argument OutsideArena(const char* operation, const void* pointer, std::size_t bytes) { + char message[128]; + std::snprintf(message, sizeof(message), "%s 0x%llx+0x%zx outside the guest arena", operation, static_cast(reinterpret_cast(pointer)), bytes); + return std::invalid_argument(message); +} + +} + void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("commit", pointer, bytes); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); } void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("reset outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("reset", pointer, bytes); WindowsMappings::Get().Reset(pointer, bytes); } void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("shared mapping", pointer, bytes); WindowsMappings::Get().Map(pointer, bytes, section, offset, protection); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 4b6c3cbcd..f7ba8fa38 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -236,6 +236,38 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { GuestArena::GuestArenaReset_nid_postfix(target, page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } + +static void CheckFixedMappingsReachTheApplicationAreaEnd() { + constexpr std::size_t page = 0x4000; + constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull; + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + Require(base == 0x200000000ull && base + size == applicationAreaEnd); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[page * 2 - 1] = 7; + void* alias = nullptr; + Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); + Require(static_cast(alias)[page - 1] == 7); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd); + void* beyond = reinterpret_cast(applicationAreaEnd); + bool refused = false; + try { + refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0; + } catch (const std::exception&) { + refused = true; + } + Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); + Require(sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); +} #endif #if defined(__linux__) @@ -674,6 +706,7 @@ int main() { CheckHeapAfterMappingReuse(); #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); + CheckFixedMappingsReachTheApplicationAreaEnd(); #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..8a1728a15 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -42,6 +42,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted +- [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export From d926e7879c570886fc66219e3065e1e3463b244c Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:28:25 +0200 Subject: [PATCH 24/55] feat(http): implement sceHttpParseStatusLine Titles import sceHttpParseStatusLine from libSceHttp to read the status line of a response; without the export they fail at startup. Parse the HTTP/x.y version, the three-digit code and the reason phrase up to the line feed, and return the library's PARSE_HTTP error codes. The seven-argument signature is the one SharpProspero declares; the behaviour follows the shadPS4 reimplementation of the PS4 library, except that no byte past lineLen is read. A consumed count above INT32_MAX cannot be returned and throws. guest_http covers valid lines and each error path. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceHttp/CMakeLists.txt | 2 +- core/libs/prx/libSceHttp/src/HttpErrors.hpp | 2 + core/libs/prx/libSceHttp/src/Parse.cpp | 68 +++++++++++++++++++++ core/libs/tests/GuestHttp.cpp | 48 +++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceHttp/src/Parse.cpp diff --git a/core/libs/prx/libSceHttp/CMakeLists.txt b/core/libs/prx/libSceHttp/CMakeLists.txt index 0093dd1c0..473651532 100644 --- a/core/libs/prx/libSceHttp/CMakeLists.txt +++ b/core/libs/prx/libSceHttp/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Uri.cpp) +add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Parse.cpp src/Uri.cpp) target_include_directories(libSceHttp PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceHttp) diff --git a/core/libs/prx/libSceHttp/src/HttpErrors.hpp b/core/libs/prx/libSceHttp/src/HttpErrors.hpp index 66d70c7e4..b95f8d25b 100644 --- a/core/libs/prx/libSceHttp/src/HttpErrors.hpp +++ b/core/libs/prx/libSceHttp/src/HttpErrors.hpp @@ -5,5 +5,7 @@ inline constexpr int ERROR_OUT_OF_MEMORY = static_cast(0x80431022); inline constexpr int ERROR_NETWORK = static_cast(0x80431063); inline constexpr int ERROR_INVALID_VALUE = static_cast(0x804311FE); inline constexpr int ERROR_INVALID_URL = static_cast(0x80433060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast(0x80432060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_VALUE = static_cast(0x804321FE); #endif diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp new file mode 100644 index 000000000..93b9d109b --- /dev/null +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" +#include "prx/libSceHttp/src/HttpErrors.hpp" + +namespace { + +bool isAsciiDigit(char c) { + return c >= '0' && c <= '9'; +} + +bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { + if (index >= lineLen || !isAsciiDigit(line[index])) return false; + uint32_t number = 0; + while (index < lineLen && isAsciiDigit(line[index])) { + number = number * 10 + static_cast(line[index] - '0'); + ++index; + } + *value = static_cast(number); + return true; +} + +} + +extern "C" { + +int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, + int32_t* responseCode, const char** reasonPhrase, size_t* phraseLen) { + if (!statusLine) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) return ERROR_PARSE_HTTP_INVALID_VALUE; + + *httpMajorVer = 0; + *httpMinorVer = 0; + if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t index = 5; + if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + + if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!isAsciiDigit(statusLine[index]) || !isAsciiDigit(statusLine[index + 1]) || !isAsciiDigit(statusLine[index + 2])) { + return ERROR_PARSE_HTTP_INVALID_RESPONSE; + } + *responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 + (statusLine[index + 2] - '0'); + index += 3; + + const char* phrase = statusLine + index; + const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); + if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t length = static_cast(static_cast(lineFeed) - phrase); + const size_t consumed = index + length + 1; + if (consumed > static_cast(INT32_MAX)) { + NotImplemented_nid_no_patch("sceHttpParseStatusLine: status line longer than INT32_MAX"); + } + if (length > 0 && phrase[length - 1] == '\r') --length; + + *reasonPhrase = phrase; + *phraseLen = length; + return static_cast(consumed); +} + +} diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..951d90532 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -16,6 +16,7 @@ int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); int APS5_VABI sceHttpsLoadCert(int, int, void*, void*, void*); int APS5_VABI sceHttpGetLastErrno(int, int*); +int APS5_VABI sceHttpParseStatusLine(const char*, std::size_t, std::int32_t*, std::int32_t*, std::int32_t*, const char**, std::size_t*); } static void Require(bool value) { if (!value) std::abort(); } @@ -103,4 +104,51 @@ int main() { Require(sceHttpGetLastErrno(1, &httpErrno) == 0); Require(httpErrno == 0); Require(sceHttpGetLastErrno(1, nullptr) == invalidValue); + + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr int parseInvalidValue = static_cast(0x804321FE); + std::int32_t major = -1; + std::int32_t minor = -1; + std::int32_t code = -1; + const char* phrase = nullptr; + std::size_t phraseLength = 0; + auto parse = [&](const char* line, std::size_t length) { + return sceHttpParseStatusLine(line, length, &major, &minor, &code, &phrase, &phraseLength); + }; + + const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; + Require(parse(response, std::strlen(response)) == 17); + Require(major == 1 && minor == 1 && code == 200); + Require(phrase == response + 12 && phraseLength == 3 && std::strncmp(phrase, " OK", 3) == 0); + + const char* lineFeedOnly = "HTTP/10.25 404 Not Found\n"; + Require(parse(lineFeedOnly, std::strlen(lineFeedOnly)) == 25); + Require(major == 10 && minor == 25 && code == 404); + Require(phrase == lineFeedOnly + 14 && phraseLength == 10); + + const char* noPhrase = "HTTP/2.0 204\r\n"; + Require(parse(noPhrase, std::strlen(noPhrase)) == 14); + Require(major == 2 && minor == 0 && code == 204); + Require(phrase == noPhrase + 12 && phraseLength == 0); + + phrase = nullptr; + phraseLength = 0; + Require(parse(nullptr, 17) == parseInvalidResponse); + Require(sceHttpParseStatusLine(response, 17, nullptr, &minor, &code, &phrase, &phraseLength) == parseInvalidValue); + Require(sceHttpParseStatusLine(response, 17, &major, &minor, &code, &phrase, nullptr) == parseInvalidValue); + major = -1; + minor = -1; + Require(parse("HTTX/1.1 200 OK\n", 16) == parseInvalidResponse); + Require(major == 0 && minor == 0); + Require(parse("HTTP/1.1", 7) == parseInvalidResponse); + Require(parse("HTTP/123", 8) == parseInvalidResponse); + Require(parse("HTTP/x.1 200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1 200 OK\n", 14) == parseInvalidResponse); + Require(parse("HTTP/1. 200 OK\n", 15) == parseInvalidResponse); + Require(parse("HTTP/1.1/200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); + Require(parse(response, 15) == parseInvalidResponse); + Require(parse(response, 16) == parseInvalidResponse); + Require(phrase == nullptr && phraseLength == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..1a8f64327 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -65,6 +65,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttpParseStatusLine](../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented From 28b2a08fa71aa8800cecd78160eeea0d7c3c6b9a Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:27:49 +0200 Subject: [PATCH 25/55] feat(libkernel): implement sceKernelSyncOnAddressWait32 and sceKernelSyncOnAddressWait64 libc's __std_atomic_wait_direct_32 and __std_atomic_wait_direct_64 import these functions, so a libc.prx that contains the C++20 atomic wait wrappers does not load without them. Wait32 compares a 4-byte value and Wait64 the full 8-byte value. Both share the waiter queue of sceKernelSyncOnAddressWait, so sceKernelSyncOnAddressWake releases waiters of every size on the address. A null address, or one not aligned to the value size, throws. The by-value comparand, the return values and the 8-byte alignment of Wait64 are assumed and recorded in TechnicalDebt. Co-Authored-By: Claude Opus 5.5 --- .../SyncOnAddress/src/SyncOnAddress.cpp | 51 ++++++++---- core/libs/tests/GuestSyncOnAddress.cpp | 78 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 87944ae04..733ae7c41 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,23 +25,16 @@ struct AddressWaiter { std::mutex g_waitersLock; std::unordered_map> g_waiters; -bool IsWordAddress(std::uintptr_t address) { - return address != 0 && address % alignof(std::uint32_t) == 0; +template +bool IsAlignedAddress(std::uintptr_t address) { + return address != 0 && address % alignof(TValue) == 0; } -} // namespace - -extern "C" { - -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { - (void)name; +template +int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key)) { - APS5_INVALID_ARG_EX; - } - std::unique_lock lock(g_waitersLock); - if (std::atomic_ref(*address).load() != expected) { + if (std::atomic_ref(*address).load() != expected) { return SYNC_ON_ADDRESS_OK; } @@ -65,13 +58,41 @@ int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t e } } lock.unlock(); - KernelTraceWait_nid_postfix("addr", __builtin_return_address(0), static_cast(waited.count()), !woken); + KernelTraceWait_nid_postfix("addr", caller, static_cast(waited.count()), !woken); return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT; } +} // namespace + +extern "C" { + +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key) || count < 0) { + if (!IsAlignedAddress(key) || count < 0) { APS5_INVALID_ARG_EX; } diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index 363c35d9a..e5954051f 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -10,6 +10,8 @@ extern "C" { int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); } @@ -130,14 +132,87 @@ static void WaitsOnTheLowHalfOfAnOnceState() { waiter.join(); } +static void SizedWaitsTimeOutWhileTheValueMatches() { + std::uint32_t word = 5; + std::uint64_t value = 0x500000005ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK); + Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelUseconds timeout = 20000; + auto start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + +static void Wait64ComparesTheHighHalf() { + std::uint64_t value = 0x100000001ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT); +} + +static void WakeReleasesWaitersOfEverySize() { + std::uint64_t value = 1; + auto* word = reinterpret_cast(&value); + std::atomic started{0}; + std::atomic finished{0}; + std::vector results(3, -1); + + std::vector waiters; + waiters.emplace_back([&] { + ++started; + results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + AwaitCount(started, 3); + std::this_thread::sleep_for(SETTLE); + + Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK); + AwaitCount(finished, 1); + std::this_thread::sleep_for(SETTLE); + Require(finished.load() == 1); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (finished.load() != 3) { + Require(std::chrono::steady_clock::now() < deadline); + Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + for (auto& waiter : waiters) waiter.join(); + for (const int result : results) Require(result == SCE_OK); +} + static void RejectsInvalidArguments() { std::uint32_t words[2] = {1, 1}; auto* misaligned = reinterpret_cast(reinterpret_cast(words) + 1); Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(misaligned, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); })); + + std::uint64_t values[2] = {1, 1}; + auto* wordAligned = reinterpret_cast(reinterpret_cast(values) + sizeof(std::uint32_t)); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); })); } int main() { @@ -146,5 +221,8 @@ int main() { WakeReleasesAWaiterWithoutAValueChange(); WakeHonoursTheCountAndTheAddress(); WaitsOnTheLowHalfOfAnOnceState(); + SizedWaitsTimeOutWhileTheValueMatches(); + Wait64ComparesTheHighHalf(); + WakeReleasesWaitersOfEverySize(); RejectsInvalidArguments(); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..eb1c2b629 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -41,6 +41,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored +- [sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and Wait64's 8-byte alignment are assumed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature From 2dea7164e386fc5b9a145fa898cc843695fb82d5 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:34:43 +0200 Subject: [PATCH 26/55] feat(libcinternal): run thread_local destructors at thread exit A title's own libc.prx implements __cxa_thread_atexit and the destructor pass of exit() through _sceLibcInternalThreadAtexit and _sceLibcInternalThreadDtors. Its __cxa_finalize calls _sceLibcInternalForceTlsDestructor. Destructors are kept per thread and run newest first, including those registered while they run, as FreeBSD's __cxa_thread_call_dtors does. A null destructor or one outside every loaded image throws instead of being skipped. The first registration installs the pass as the libkernel thread exit hook, so it runs on every guest thread before the thread can be joined. _sceLibcInternalForceTlsDestructor throws. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceLibcInternal/CMakeLists.txt | 23 +++++ core/libs/prx/libSceLibcInternal/Export.cpp | 80 +++++++++++++++++ core/libs/tests/GuestThreadAtexit.cpp | 87 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 3 + 4 files changed, 193 insertions(+) create mode 100644 core/libs/prx/libSceLibcInternal/CMakeLists.txt create mode 100644 core/libs/tests/GuestThreadAtexit.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt new file mode 100644 index 000000000..3aae2c755 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) +configure_windows_unwind(libSceLibcInternal) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_thread_atexit_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadAtexit.cpp) + target_include_directories(guest_thread_atexit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_atexit_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_thread_atexit_tests) + add_test(NAME guest_thread_atexit COMMAND guest_thread_atexit_tests) + set_tests_properties(guest_thread_atexit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_thread_atexit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 49699b6ca..c84ee09a1 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,5 +1,69 @@ +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); + +namespace { + +using ThreadDestructorFunction = void (APS5_VABI*)(void*); + +struct ThreadDestructor { + ThreadDestructorFunction function; + void* object; + void* dsoSymbol; +}; + +struct ThreadDestructorsTag {}; + +std::vector& ThreadDestructors() { + return HostThreadLocal, ThreadDestructorsTag>(); +} + +bool IsInLoadedImage(const void* address) { + if (address == nullptr) + return false; +#ifdef _WIN32 + HMODULE module = nullptr; + return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &module) != 0; +#else + Dl_info info{}; + return dladdr(address, &info) != 0; +#endif +} + +void APS5_VABI RunThreadDestructors_nid_no_patch() { + auto& destructors = ThreadDestructors(); + while (!destructors.empty()) { + const ThreadDestructor destructor = destructors.back(); + destructors.pop_back(); + const auto* function = reinterpret_cast(destructor.function); + if (!IsInLoadedImage(function)) { + std::ostringstream message; + message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image"; + throw std::runtime_error(message.str()); + } + destructor.function(destructor.object); + } +} + +void RegisterThreadExitHook() { + [[maybe_unused]] static const bool registered = [] { + sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch); + return true; + }(); +} + +} extern "C" { @@ -13,4 +77,20 @@ void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) { LibcHeapTraceInfo_nid_no_patch(info); } +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) { + RegisterThreadExitHook(); + ThreadDestructors().push_back({destructor, object, dsoSymbol}); + return 0; +} + +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() { + RunThreadDestructors_nid_no_patch(); +} + +int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/tests/GuestThreadAtexit.cpp b/core/libs/tests/GuestThreadAtexit.cpp new file mode 100644 index 000000000..f37fd5d52 --- /dev/null +++ b/core/libs/tests/GuestThreadAtexit.cpp @@ -0,0 +1,87 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +Pthread APS5_VABI scePthreadSelf(); +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); +} + +namespace { + +using Destructor = void (APS5_VABI*)(void*); + +void Require(bool value) { if (!value) std::abort(); } + +int dsoHandle = 0; +std::vector calls; +std::vector callers; + +void* Object(std::intptr_t value) { return reinterpret_cast(value); } + +int Register(Destructor destructor, std::intptr_t object) { + return _sceLibcInternalThreadAtexit_nid_postfix(destructor, Object(object), &dsoHandle); +} + +void APS5_VABI Record(void* object) { + calls.push_back(reinterpret_cast(object)); + callers.push_back(scePthreadSelf()); +} + +void APS5_VABI RecordAndRegister(void* object) { + Record(object); + Require(Register(Record, 9) == 0); +} + +void* APS5_VABI Worker(void*) { + Require(Register(Record, 4) == 0); + Require(Register(Record, 5) == 0); + return nullptr; +} + +bool DestructorsThrow() { + try { + _sceLibcInternalThreadDtors_nid_postfix(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +} + +int main() { + const Pthread mainThread = scePthreadSelf(); + Require(Register(Record, 1) == 0); + Require(Register(RecordAndRegister, 2) == 0); + Require(Register(Record, 3) == 0); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 2, 9, 1}); + Require(callers == std::vector(4, mainThread)); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.size() == 4); + + calls.clear(); + callers.clear(); + Pthread worker = nullptr; + Require(scePthreadCreate(&worker, nullptr, Worker, nullptr, "ThreadAtexit") == 0); + Require(scePthreadJoin(worker, nullptr) == 0); + Require(calls == std::vector{5, 4}); + Require(callers == std::vector(2, worker)); + + calls.clear(); + std::vector heap(64); + Require(Register(Record, 6) == 0); + Require(Register(nullptr, 7) == 0); + Require(Register(reinterpret_cast(heap.data()), 8) == 0); + Require(DestructorsThrow()); + Require(DestructorsThrow()); + Require(calls.empty()); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{6}); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..a490dce87 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -44,6 +44,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw +- [_sceLibcInternalForceTlsDestructor](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - unknown return type and behaviour; a title's `libc.prx` calls it from `__cxa_finalize` with the module handle of a non-null dso - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [pthread_barrierattr_setpshared](../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - PTHREAD_PROCESS_SHARED throws: FreeBSD 9.0 libthr rejects it with EINVAL and 11.0 accepts it, and which one the console follows is unknown - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature @@ -101,3 +103,4 @@ Throughout the project, every function at every stage either **does exactly what - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. +- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped From 09218ee68a1db92e4dd8e1cd94191a3dae30729e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:24:55 +0200 Subject: [PATCH 27/55] feat(msgdialog): implement sceMsgDialogClose in libSceMsgDialog.native A title that loads libSceMsgDialog.native without libSceMsgDialog imports sceMsgDialogClose from it, so without the export it stops at startup on an unresolved import. Close turns a running dialog into a finished one and returns NOT_RUNNING in every other state, as libSceMsgDialog's Close does. The module keeps its own dialog status, so it cannot reuse that export. Open in this module finishes the dialog at once, so Close after Open returns NOT_RUNNING and leaves the result unchanged. The module is now listed under silent stubs in TechnicalDebt, and a CMakeLists.txt registers the guest_msg_dialog_native test. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceMsgDialog.native/CMakeLists.txt | 20 ++++++++ .../prx/libSceMsgDialog.native/Export.cpp | 8 +++ core/libs/tests/GuestMsgDialogNative.cpp | 49 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 78 insertions(+) create mode 100644 core/libs/prx/libSceMsgDialog.native/CMakeLists.txt create mode 100644 core/libs/tests/GuestMsgDialogNative.cpp diff --git a/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt new file mode 100644 index 000000000..111746d44 --- /dev/null +++ b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceMsgDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceMsgDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceMsgDialog.native) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_msg_dialog_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestMsgDialogNative.cpp) + target_include_directories(guest_msg_dialog_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_msg_dialog_native_tests PRIVATE libSceMsgDialog.native) + add_test(NAME guest_msg_dialog_native COMMAND guest_msg_dialog_native_tests) + if(WIN32) + set_tests_properties(guest_msg_dialog_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 1423e8fed..027eff38d 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -8,10 +8,12 @@ namespace { std::atomic g_status{0}; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_OK = 0; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int BUTTON_ID_OK = 1; } @@ -20,6 +22,12 @@ extern "C" { int SceMsgDialogNativeModuleLoaded_nid_no_patch = 1; +int APS5_VABI sceMsgDialogClose(void) { + int expected = COMMON_DIALOG_STATUS_RUNNING; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_FINISHED)) return COMMON_DIALOG_ERROR_NOT_RUNNING; + return 0; +} + int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result) { const int status = g_status.load(); if (status == 0) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp new file mode 100644 index 000000000..6e3b757e2 --- /dev/null +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -0,0 +1,49 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceMsgDialogInitialize(void); +int APS5_VABI sceMsgDialogOpen(const void* param); +int APS5_VABI sceMsgDialogUpdateStatus(void); +int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); +int APS5_VABI sceMsgDialogClose(void); +int APS5_VABI sceMsgDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kButtonIdOk = 1; + +} + +int main() { + std::uint8_t param[0x60]{}; + + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == 0); + + Require(sceMsgDialogInitialize() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusInitialized); + + Require(sceMsgDialogOpen(param) == 0); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + + MsgDialogResult result{}; + result.button_id = -1; + Require(sceMsgDialogGetResult(&result) == 0); + Require(result.result == 0); + Require(result.button_id == kButtonIdOk); + + Require(sceMsgDialogTerminate() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..7f30b5d62 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -12,6 +12,7 @@ Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: - [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) +- [libSceMsgDialog.native](../../core/libs/prx/libSceMsgDialog.native/Export.cpp) - dialogs finish at `sceMsgDialogOpen` without UI and answer with button 1, so `sceMsgDialogClose` returns `NOT_RUNNING`; its result before `sceMsgDialogInitialize` follows libSceMsgDialog - [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. From 05dc3e898208e94e44e07c2439ad6d14724710ce Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:26:53 +0200 Subject: [PATCH 28/55] feat(npcommerce): implement sceNpCommerceSetPsStoreIconLayout Titles set the PS Store icon layout before they show the icon, and a title that imports the call stops at startup on the unresolved import. No PS Store icon is drawn, so like the show and hide calls it accepts every layout and returns 0 without effect. The signature follows shadPS4 (a PS4 reference), which also returns OK for every layout. The libSceNpCommerce entry under "Silent stubs" in TechnicalDebt now names the function. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNpCommerce/Export.cpp | 5 +++++ docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index 6d8f3f581..6b2bd01d9 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -74,4 +74,9 @@ int APS5_VABI sceNpCommerceShowPsStoreIcon(int pos) { return 0; } +int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout) { + (void)layout; + return 0; +} + } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..6ff4ac3dd 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -16,7 +16,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. -- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing +- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. From 1a88fe66a8fd89c13198ab88727b8044e11bab9b Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:35:33 +0200 Subject: [PATCH 29/55] feat(npsessionsignaling): implement offline RequestPrepare and GetLocalNetInfo Without network a signaling context is never prepared. Like ActivateSession, ActivateUser and GetConnectionInfo, sceNpSessionSignalingRequestPrepare returns 0x80552D06 (UNAVAILABLE) and writes no request id. sceNpSessionSignalingGetLocalNetInfo no longer throws: it returns 0x80552D06 and leaves the info unchanged, as libSceNetCtl reports neither an address nor NAT info offline. A null request id or info returns 0x80552D02, as a null context id does in CreateContext2. GetConnectionStatus still throws. The new guest_np_session_signaling test checks the error codes and that failed calls leave the request id and info unchanged; it is registered in a new libSceNpSessionSignaling CMakeLists.txt. Co-Authored-By: Claude Opus 5.5 --- .../libSceNpSessionSignaling/CMakeLists.txt | 20 ++++++++ .../prx/libSceNpSessionSignaling/Export.cpp | 11 ++-- core/libs/tests/GuestNpSessionSignaling.cpp | 51 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt create mode 100644 core/libs/tests/GuestNpSessionSignaling.cpp diff --git a/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt new file mode 100644 index 000000000..6446469df --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceNpSessionSignaling SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSessionSignaling PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSessionSignaling) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_session_signaling_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSessionSignaling.cpp) + target_include_directories(guest_np_session_signaling_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_session_signaling_tests PRIVATE libSceNpSessionSignaling) + add_test(NAME guest_np_session_signaling COMMAND guest_np_session_signaling_tests) + if(WIN32) + set_tests_properties(guest_np_session_signaling PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index d140181b6..eb3ee27e0 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -69,9 +69,14 @@ int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, i int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; +} + +int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId) { + (void)contextId; + if (!requestId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp new file mode 100644 index 000000000..10ec8b222 --- /dev/null +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpSessionSignalingInitialize(void* param); +int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, std::uint32_t* contextId); +std::int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::int32_t contextId, void* info); +int APS5_VABI sceNpSessionSignalingRequestPrepare(std::uint32_t contextId, std::uint32_t* requestId); +int APS5_VABI sceNpSessionSignalingTerminate(void); +} + +namespace { + +constexpr int InvalidArgument = static_cast(0x80552D02u); +constexpr int Unavailable = static_cast(0x80552D06u); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpSessionSignaling: %s\n", message); + std::abort(); + } +} + +} + +int main() { + unsigned char initializeParam[32] = {}; + Require(sceNpSessionSignalingInitialize(initializeParam) == 0, "initialize failed"); + unsigned char contextParam[64] = {}; + std::uint32_t contextId = 0; + Require(sceNpSessionSignalingCreateContext2(contextParam, &contextId) == 0, "context creation failed"); + + std::uint32_t requestId = 0xa5a5a5a5u; + Require(sceNpSessionSignalingRequestPrepare(contextId, nullptr) == InvalidArgument, "null request id"); + Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == Unavailable, "prepare without network"); + Require(requestId == 0xa5a5a5a5u, "failed prepare wrote a request id"); + + const auto netContextId = static_cast(contextId); + unsigned char info[16]; + std::memset(info, 0xa5, sizeof(info)); + unsigned char untouched[sizeof(info)]; + std::memcpy(untouched, info, sizeof(info)); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, nullptr) == InvalidArgument, "null info"); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, info) == Unavailable, "local net info without network"); + Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed query modified the info"); + + Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); +} From 776ecd3b3581f843a145de5307bbeee033d4518a Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:25:33 +0200 Subject: [PATCH 30/55] feat(psml): implement the MFSR 1100 calls for hardware without PSSR scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100 and scePsmlMfsrGetDispatchMfsrPacket1100 were not exported, so a title importing them failed to load. In Sony's libScePsml all three return 0x8A810001 (not initialized) without writing their outputs until scePsmlMfsrInit has succeeded. AnyPS5's scePsmlMfsrInit throws, so that is the only state a title can reach, and the three calls now return that error. The existing stubs for scePsmlMfsrInit, scePsmlMfsrReleaseContext and scePsmlMfsrGetDispatchMfsrPacketSizeInDwords get their real names and still throw. libScePsml_debug gets a CMakeLists.txt for the new guest_psml test. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libScePsml_debug/CMakeLists.txt | 20 +++++++ core/libs/prx/libScePsml_debug/Export.cpp | 34 +++++++++--- core/libs/tests/GuestPsml.cpp | 53 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libScePsml_debug/CMakeLists.txt create mode 100644 core/libs/tests/GuestPsml.cpp diff --git a/core/libs/prx/libScePsml_debug/CMakeLists.txt b/core/libs/prx/libScePsml_debug/CMakeLists.txt new file mode 100644 index 000000000..ee07e525e --- /dev/null +++ b/core/libs/prx/libScePsml_debug/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libScePsml_debug SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePsml_debug PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePsml_debug) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_psml_tests ${LIBS_INCLUDE_DIR}/tests/GuestPsml.cpp) + target_include_directories(guest_psml_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_psml_tests PRIVATE libScePsml_debug) + add_test(NAME guest_psml COMMAND guest_psml_tests) + if(WIN32) + set_tests_properties(guest_psml PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 67d7cf76b..2e8ee6e64 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -3,22 +3,45 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); + +} + extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param) { + (void)requirement; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param) { + (void)context; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param) { + (void)context; + (void)commandBuffer; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("+2KpvixvL6E", scePsmlUnknown__P2KpvixvL6E); int APS5_VABI scePsmlUnknown__P2KpvixvL6E() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3WVD91e12ZQ", scePsmlUnknown_3WVD91e12ZQ); -int APS5_VABI scePsmlUnknown_3WVD91e12ZQ() { +int APS5_VABI scePsmlMfsrInit() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AHalTX9wFZY", scePsmlUnknown_AHalTX9wFZY); -int APS5_VABI scePsmlUnknown_AHalTX9wFZY() { +int APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -47,8 +70,7 @@ int APS5_VABI scePsmlUnknown_GJY0MvuTcs8() { return 0; } -APS5_EXPORT("JaLBe0P3jSU", scePsmlUnknown_JaLBe0P3jSU); -int APS5_VABI scePsmlUnknown_JaLBe0P3jSU() { +int APS5_VABI scePsmlMfsrReleaseContext() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp new file mode 100644 index 000000000..d0aac1d17 --- /dev/null +++ b/core/libs/tests/GuestPsml.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001); + +struct CommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +} + +int main() { + std::array param{}; + + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsrGetContextBufferRequirement1100(requirement.data(), param.data()) == kErrNotInitialized); + Require(requirement == untouchedRequirement); + Require(scePsmlMfsrGetContextBufferRequirement1100(nullptr, nullptr) == kErrNotInitialized); + + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + Require(scePsmlMfsrCreateContext1100(&context, param.data()) == kErrNotInitialized); + Require(context == sentinel); + Require(scePsmlMfsrCreateContext1100(nullptr, nullptr) == kErrNotInitialized); + + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); + Require(scePsmlMfsrGetDispatchMfsrPacket1100(nullptr, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(dwords == untouchedDwords); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..698d5c807 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -54,6 +54,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature - [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature +- [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures From 823c3caf4fdccd351992d803b26cc28c9673b374 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:32:49 +0200 Subject: [PATCH 31/55] fix(relinker): keep Windows startup stacks below the guest arena The PE writer marked relocatable executables HIGH_ENTROPY_VA. Windows then places the main thread stack, TEB, PEB and the loader threads' stacks, which exist before libc reserves the guest arena from 0x2_0000_0000, anywhere in the first TiB. Nearly half the time they land inside the arena and stay there as holes that fixed guest mappings cannot use. DllCharacteristics is now 0x140 (DYNAMIC_BASE, NX_COMPAT). The image is still relocated and bottom-up allocations keep the 8-bit randomization, but they start near the bottom of the address space, far below the arena. DLL placement does not depend on this flag. windows_address_space checks the flags and, on Windows, runs a relinked guest that returns the high dword of its stack pointer. Co-Authored-By: Claude Opus 5.5 --- core/relinker/CMakeLists.txt | 2 + .../src/windows/WindowsPeWriter.cpp | 2 +- .../tests/test_windows_address_space.py | 45 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 core/relinker/relinker/tests/test_windows_address_space.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a388ec7a3..c704aaf97 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -202,6 +202,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME windows_gui COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_gui.py $) + add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $) add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $) add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 61c0a567d..3c4e9ac34 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -30,7 +30,7 @@ std::vector WindowsPeWriter::Write(const std::vector& s Io::WriteU16(result, optionalOffset + 50, 2); Io::WriteU32(result, optionalOffset + 60, LoadRva); Io::WriteU16(result, optionalOffset + 68, windowsGui ? 2 : 3); - Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x160); + Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x140); Io::WriteU64(result, optionalOffset + 72, 0x210000); Io::WriteU64(result, optionalOffset + 80, 0x200000); Io::WriteU64(result, optionalOffset + 88, 0x100000); diff --git a/core/relinker/relinker/tests/test_windows_address_space.py b/core/relinker/relinker/tests/test_windows_address_space.py new file mode 100644 index 000000000..d2720daf6 --- /dev/null +++ b/core/relinker/relinker/tests/test_windows_address_space.py @@ -0,0 +1,45 @@ +"""Keep the main thread stack that Windows creates before libc reserves the guest arena below it.""" + +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_optional_plt import fixture + +GUEST_ARENA_START = 0x200000000 +DYNAMIC_BASE = 0x40 +NX_COMPAT = 0x100 + + +def dll_characteristics(pe): + pe_offset = struct.unpack_from("> 32, \ + f"stack not below {GUEST_ARENA_START:#x}: exit code {executed.returncode:#x}" + print("Windows address space integration tests passed") + + +if __name__ == "__main__": + main() From 15b191ef6a1c444b48bbeb9d14de3cc476adb1ff Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:27:44 +0200 Subject: [PATCH 32/55] feat(systemservice): implement sceSystemServiceParamGetString The export threw, and titles query the system name during startup. Parameter 6 (the system name) now writes the fixed name "PS5" into a buffer of at least SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH (65) bytes. A null buffer or a zero size returns SYSTEM_SERVICE_ERROR_PARAMETER, as in KytyPS5. Other parameter ids and buffers of 1 to 64 bytes still throw, because no PS5 source gives their result. guest_system_service covers the error rows, both throws and the returned name. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceSystemService/Export.cpp | 19 ++++++++++----- .../prx/libSceSystemService/SystemService.hpp | 4 ++++ core/libs/tests/GuestSystemService.cpp | 23 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 01a6365a5..205897fe2 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -80,12 +80,19 @@ int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceParamGetString(int param_id, char* buf, size_t buf_size) { - (void)param_id; - (void)buf; - (void)buf_size; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufSize) { + if (buf == nullptr || bufSize == 0) { + return SYSTEM_SERVICE_ERROR_PARAMETER; + } + if (paramId != SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: parameter other than the system name"); + } + if (bufSize < SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: buffer shorter than 65 bytes"); + } + constexpr char SystemName[] = "PS5"; + std::memcpy(buf, SystemName, sizeof(SystemName)); + return SYSTEM_SERVICE_OK; } int APS5_VABI sceSystemServicePowerTick(void) { diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index 7a2c84a12..e86820da4 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP #define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP +#include #include constexpr int SYSTEM_SERVICE_OK = 0; @@ -12,9 +13,12 @@ constexpr int SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT = 2; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_FORMAT = 3; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_ZONE = 4; constexpr int SYSTEM_SERVICE_PARAM_ID_SUMMERTIME = 5; +constexpr int SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME = 6; constexpr int SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL = 7; constexpr int SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN = 1000; +constexpr std::size_t SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH = 65; + constexpr int SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US = 1; constexpr int SYSTEM_SERVICE_PARAM_DATE_FORMAT_DDMMYYYY = 1; constexpr int SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR = 1; diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 88193689e..fe937fc57 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -1,13 +1,26 @@ #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" +#include #include +#include +#include extern "C" int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); +extern "C" int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, std::size_t bufSize); namespace { void Require(bool value) { if (!value) std::abort(); } +bool ParamGetStringThrows(int paramId, char* buf, std::size_t bufSize) { + try { + static_cast(sceSystemServiceParamGetString(paramId, buf, bufSize)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + } int main() { @@ -17,4 +30,14 @@ int main() { Require(luminance.max_full_frame_tone_map_luminance == 100.0f); Require(luminance.max_tone_map_luminance == 100.0f); Require(luminance.min_tone_map_luminance == 0.0f); + + char name[SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH]; + std::memset(name, 'x', sizeof(name)); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, nullptr, sizeof(name)) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, 0) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name) - 1)); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_LANG, name, sizeof(name))); + Require(name[0] == 'x'); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name)) == SYSTEM_SERVICE_OK); + Require(std::strcmp(name, "PS5") == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..1762bbdc5 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -74,6 +74,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. From 62c70fb1d5336b885f8c632d4271ea60225b2ab4 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:25:42 +0200 Subject: [PATCH 33/55] fix(libkernel): state the size of a direct memory backing Windows cannot create On Windows each direct memory allocation is backed by a page-file section that CreateFileMappingW commits in full. When the system commit limit cannot cover it, the title stopped with "create direct memory backing: Insufficient system resources exist to complete the requested service", which did not say how much was requested. The error now states the requested size in bytes and MiB, and the runtime layout notes in USAGE.md describe the commit requirement and the page file. Allocation behaviour is unchanged: committing at allocation matches the console, where allocated direct memory is always backed by physical memory. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp | 7 ++++++- docs/user/USAGE.md | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 25147d08a..ef114beba 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -226,7 +226,12 @@ public: #ifdef _WIN32 const auto size = static_cast(bytes); section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(size >> 32), static_cast(size), nullptr); - if (!section) throw std::system_error(static_cast(GetLastError()), std::system_category(), "create direct memory backing"); + if (!section) { + const auto error = static_cast(GetLastError()); + char message[96]; + std::snprintf(message, sizeof(message), "create direct memory backing of 0x%llx bytes (%llu MiB)", static_cast(size), static_cast((size + 0xFFFFF) >> 20)); + throw std::system_error(error, std::system_category(), message); + } #else file = memfd_create("direct memory", MFD_CLOEXEC); if (file < 0) throw std::system_error(errno, std::generic_category(), "create direct memory backing"); diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 3b8a408be..7cb429298 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -70,6 +70,8 @@ app0/ Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed in full when the title allocates it, not when its pages are first used. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover it together with all other committed memory. Otherwise the allocation throws `create direct memory backing of 0x bytes ( MiB)` with the Windows error; enlarge the page file or close other applications. + Linux: ```sh From e30336c8154b55d1cc32a6a5c34e09963981b2a6 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:23:20 +0200 Subject: [PATCH 34/55] feat(razor): implement sceRazorCpuPushMarkerStatic and sceRazorCpuPopMarker Ghost of Yotei (PPSA26344) imports both functions, so its import resolution failed at startup. Markers only feed an attached Razor capture. AnyPS5 hosts none and sceRazorCpuIsCapturing reports 0, so, like the other libSceRazorCpu exports, both do nothing and return 0. The push signature comes from the title's calls (a string literal, 0x80ffffff, 2). TechnicalDebt lists both functions with the assumed signatures. guest_razor_cpu covers nested and unbalanced markers. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceRazorCpu/Export.cpp | 11 +++++++++++ core/libs/tests/GuestRazorCpu.cpp | 8 ++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 20 insertions(+) diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 83a295d21..0a5ec3953 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -24,4 +24,15 @@ int APS5_VABI sceRazorCpuJobManagerSequence(const void* args) { return 0; } +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, uint32_t color, uint32_t flags) { + (void)name; + (void)color; + (void)flags; + return 0; +} + +int APS5_VABI sceRazorCpuPopMarker(void) { + return 0; +} + } diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index 2b966d8a6..0f1469a82 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -7,6 +7,8 @@ std::uint32_t APS5_VABI sceRazorCpuIsCapturing(void); int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args); int APS5_VABI sceRazorCpuJobManagerJob(const void* args); int APS5_VABI sceRazorCpuJobManagerSequence(const void* args); +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, std::uint32_t color, std::uint32_t flags); +int APS5_VABI sceRazorCpuPopMarker(void); } namespace { @@ -20,4 +22,10 @@ int main() { Require(sceRazorCpuJobManagerDispatch(nullptr) == 0); Require(sceRazorCpuJobManagerJob(nullptr) == 0); Require(sceRazorCpuJobManagerSequence(nullptr) == 0); + Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPushMarkerStatic("unbalanced", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuIsCapturing() == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..1477e6bb8 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -64,6 +64,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info +- [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted From 4450965721b2b2081e01deaffd18b66072f425c8 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:18:33 +0200 Subject: [PATCH 35/55] feat(voiceqos): declare the missing libSceVoiceQoS exports libSceVoiceQoS exported only sceVoiceQoSInit, so a title that imports the rest of the library failed at load on unresolved imports. Declare sceVoiceQoSEnd, the local and remote endpoint create and delete functions, sceVoiceQoSConnect, sceVoiceQoSDisconnect, both endpoint attribute setters, sceVoiceQoSReadPacket and sceVoiceQoSWritePacket. Each calls NotImplemented_nid_no_patch and reads no arguments, so the imports resolve and a call still stops with the function's name. sceVoiceQoSInit is unchanged. Their behaviour follows in a later change, once a title is seen to reach them. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceVoiceQoS/Export.cpp | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index a0af71ff3..4cb9ab16f 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -13,4 +13,59 @@ int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_ty return 0; } +int APS5_VABI sceVoiceQoSEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSConnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDisconnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSReadPacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSWritePacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } From d3e2e53a7cef0c1b4ea0152156e9398ae7dab44e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:23:54 +0200 Subject: [PATCH 36/55] feat(videoout): accept the STRICT_COLORIMETRY buffer option sceVideoOutRegisterBuffers2 and sceVideoOutSubmitChangeBufferAttribute2 threw for every buffer attribute option other than 0. PPSA26344 builds its display buffers with option 8 through the libSceAgc helper linked into it, so its first registration failed. Option 8, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY (name and value from KytyPS5), is now accepted and presented as option 0; every other option still throws. What the console changes for it is unknown and recorded in TechnicalDebt. The rejection now names the tiling mode, pitch, aspect ratio and option it was given. video_out_flip_tests decode checks that option 8 describes the same buffer as option 0 and that options 1, 32 and 40 are rejected. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceVideoOut/include/VideoOutDriver.hpp | 2 ++ core/libs/prx/libSceVideoOut/src/Buffer.cpp | 3 ++- core/libs/prx/libSceVideoOut/tests/Flip.cpp | 12 ++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index ea75f8d80..adf59924a 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -62,6 +62,8 @@ static constexpr std::size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8; static constexpr int VIDEO_OUT_EVENT_FLIP = 0; static constexpr int VIDEO_OUT_EVENT_VBLANK = 1; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index aa40461e1..25fb6a241 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -10,7 +11,7 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group) { const auto& attribute = group.attribute; if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set"); - if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option"); + if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || (attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE && attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY)) throw std::runtime_error("VideoOut: unsupported tiling " + std::to_string(attribute.tiling_mode) + ", pitch " + std::to_string(attribute.pitch_in_pixel) + ", aspect ratio " + std::to_string(attribute.aspect_ratio) + " or buffer option " + std::to_string(attribute.option)); if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented"); const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel}; static_cast(AgcDriverDisplayBufferSize_nid_postfix(result)); diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index ad78a9798..9dd1463b0 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -162,6 +162,18 @@ void testDecode() { expectFailure([&] { AgcDriver::ReadDisplayBuffer(buffer); }); buffer.address = std::numeric_limits::max() & ~uint64_t{65535}; expectFailure([&] { AgcDriver::DisplayBufferSize(buffer); }); + const VideoOutBuffer registered{0, reinterpret_cast(tiled.data()), 0}; + BufferAttributeGroup group{}; + sceVideoOutSetBufferAttribute2(&group.attribute, 0x8000000000000000ull, 0, 259, 137, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE, 0, 0); + group.occupied = true; + const auto plain = DescribeVideoOutBuffer(registered, group); + group.attribute.option = VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY; + const auto strict = DescribeVideoOutBuffer(registered, group); + check(strict.address == plain.address && strict.pixelFormat == plain.pixelFormat && strict.width == plain.width && strict.height == plain.height && strict.tilingMode == plain.tilingMode && strict.pitchInPixel == plain.pitchInPixel, "the STRICT_COLORIMETRY option changed the described buffer"); + for (const uint64_t option : {1ull, 32ull, 40ull}) { + group.attribute.option = option; + check(expectFailure([&] { DescribeVideoOutBuffer(registered, group); }).find("buffer option " + std::to_string(option)) != std::string::npos, "an unsupported buffer option was accepted"); + } } void testOpenParam() { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..b27c51fb1 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -25,6 +25,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info - [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown +- [sceVideoOutRegisterBuffers2](../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order From fa996bbccb8f7302cd8270b6aa8961725c8589a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:03:00 +0200 Subject: [PATCH 37/55] fix(shader): saturate VOP3 integer mad/sad results on clamp Co-Authored-By: Claude --- .../tests/execution/Vop3IntegerClamp.cpp | 188 ++++++++++++++++++ .../tests/execution/Vop3IntegerClampWide.cpp | 187 +++++++++++++++++ .../tests/execution/Vop3Sad.cpp | 14 -- .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 30 ++- .../Translation/TranslationContext.hpp | 2 + .../Translation/src/IntegerInstructions.cpp | 50 ++++- 6 files changed, 442 insertions(+), 29 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp new file mode 100644 index 000000000..4bc1207d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd543800a, 0x041a0b04, 0xd542800b, + 0x041a0b04, 0xd740800c, 0x041a0b04, 0xd75e800d, 0x041a0b04, 0xd773800e, 0x041a0b04, 0xd775800f, + 0x041a0b04, 0xd55a8010, 0x041a0b04, 0xd55b8011, 0x041a0b04, 0xd55c8012, 0x041a0b04, 0xd55d8013, + 0x041a0b04, 0xd5718014, 0x041a0b04, 0x7e2a0307, 0xd740d815, 0x041a0b04, 0xd75eb016, 0x041a0b04, + 0xd7758817, 0x041a0b04, 0xd7739018, 0x041a0b04, 0x7e320307, 0xd75ef819, 0x041a0b04, 0xe0781000, + 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x0000ffffu}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000fff0u}, + {0x01010101u, 0x01010101u, 0xffffffffu, 0xff00ff00u}, + {0x00000001u, 0xff00ff00u, 0x80000000u, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x506377feu, 0x09418c53u}, + {0x00000000u, 0x00000000u, 0xad078efau, 0x00000002u}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x293b6032u}, + {0x00000000u, 0x00000001u, 0x0000fff0u, 0x00ff00ffu}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0xdbd16e44u}, + {0x00000000u, 0x00000001u, 0x671b91d7u, 0xb1836b5au}, + {0x00000000u, 0x00000001u, 0xb51b0523u, 0x54b074b8u}, + {0x00000000u, 0x00000001u, 0xff0ec345u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0xfefefefeu}, + {0x00000000u, 0x0000007fu, 0x00000002u, 0xcee69ed0u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x40778188u}, + {0x00000000u, 0x0000007fu, 0x1bf35776u, 0x12345678u}, + {0x00000000u, 0x0000007fu, 0x29c0ba3bu, 0x48e98a0au}, + {0x00000000u, 0x0000007fu, 0x9d299c7bu, 0x39cdd549u}, + {0x00000000u, 0x0000007fu, 0x9ff9949eu, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0x000000ffu, 0x12345678u}, + {0x00000000u, 0x00000080u, 0x00008000u, 0x80000001u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xae15989du}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00000080u}, + {0x00000000u, 0x00000080u, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x00000080u, 0xaf4a9e38u, 0xd42344f4u}, + {0x00000000u, 0x00000080u, 0xea4d8c45u, 0x00625618u}, + {0x00000000u, 0x00000080u, 0xfd4fb563u, 0xff00ff00u}, + {0x00000000u, 0x000000ffu, 0x00000001u, 0x310733d9u}, + {0x00000000u, 0x000000ffu, 0x2f3bacc0u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0x85f55b11u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x9fc408e0u}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0xffff0000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000ffffu}, + {0xffffffffu, 0xffffffffu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u}, + {0xffffffffu, 0x7fffffffu, 0x0000ffffu, 0x00007fffu, 0xffffffffu, 0x00010200u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffff00u, 0x00007fffu, 0x00010200u, 0xffffffffu, 0x7fffff00u}, + {0x8000ff00u, 0x8000ff00u, 0x0000ff00u, 0x0000ff00u, 0x8000ff00u, 0x80000000u, 0x800001ffu, 0x81ff0000u, 0x8001fdffu, 0xffffffffu, 0x800001feu, 0x0000ffffu, 0x00008000u, 0x80000000u, 0x8000ff00u, 0x8000ffffu}, + {0x506377feu, 0x506377feu, 0x000077feu, 0x000077feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x77fe8c53u, 0x00005063u, 0x506377feu, 0x506377feu, 0x50638c53u}, + {0xad078efau, 0xad078efau, 0x00008efau, 0x00008efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0x8efa0002u, 0x0000ad07u, 0xad078efau, 0xad078efau, 0xad070002u}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000003u, 0x00010002u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00026032u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00006032u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff1u, 0x0001fff0u, 0x0000fff1u, 0x0000fff1u, 0x0000fff1u, 0xfff000ffu, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x000000ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff0100u, 0x010000ffu, 0x00ff0100u, 0x00ff0100u, 0x00ff0100u, 0x00ff6e44u, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff6e44u}, + {0x671b91d7u, 0x671b91d7u, 0x000091d7u, 0x000091d7u, 0x671b91d7u, 0x671b91d7u, 0x671b91d8u, 0x671c91d7u, 0x671b91d8u, 0x671b91d8u, 0x671b91d8u, 0x91d76b5au, 0x0000671bu, 0x671b91d7u, 0x671b91d7u, 0x671b6b5au}, + {0xb51b0523u, 0xb51b0523u, 0x00000523u, 0x00000523u, 0xb51b0523u, 0xb51b0523u, 0xb51b0524u, 0xb51c0523u, 0xb51b0524u, 0xb51b0524u, 0xb51b0524u, 0x052374b8u, 0x0000b51bu, 0xb51b0523u, 0xb51b0523u, 0xb51b74b8u}, + {0xff0ec345u, 0xff0ec345u, 0x0000c345u, 0x0000c345u, 0xff0ec345u, 0xff0ec345u, 0xff0ec346u, 0xff0fc345u, 0xff0ec346u, 0xff0ec346u, 0xff0ec346u, 0xc3457fffu, 0x0000ff0eu, 0xff0ec345u, 0xff0ec345u, 0xff0e7fffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00020000u, 0x00000002u, 0x00000002u, 0x00000002u, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fefeu}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000081u, 0x007f0002u, 0x00000081u, 0x00000081u, 0x00000081u, 0x00029ed0u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00009ed0u}, + {0x12345678u, 0x12345678u, 0x00005678u, 0x00005678u, 0x12345678u, 0x12345678u, 0x123456f7u, 0x12b35678u, 0x123456f7u, 0x123456f7u, 0x123456f7u, 0x56788188u, 0x00001234u, 0x12345678u, 0x12345678u, 0x12348188u}, + {0x1bf35776u, 0x1bf35776u, 0x00005776u, 0x00005776u, 0x1bf35776u, 0x1bf35776u, 0x1bf357f5u, 0x1c725776u, 0x1bf357f5u, 0x1bf357f5u, 0x1bf357f5u, 0x57765678u, 0x00001bf3u, 0x1bf35776u, 0x1bf35776u, 0x1bf35678u}, + {0x29c0ba3bu, 0x29c0ba3bu, 0x0000ba3bu, 0x0000ba3bu, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c0babau, 0x2a3fba3bu, 0x29c0babau, 0x29c0babau, 0x29c0babau, 0xba3b8a0au, 0x000029c0u, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c08a0au}, + {0x9d299c7bu, 0x9d299c7bu, 0x00009c7bu, 0x00009c7bu, 0x9d299c7bu, 0x9d299c7bu, 0x9d299cfau, 0x9da89c7bu, 0x9d299cfau, 0x9d299cfau, 0x9d299cfau, 0x9c7bd549u, 0x00009d29u, 0x9d299c7bu, 0x9d299c7bu, 0x9d29d549u}, + {0x9ff9949eu, 0x9ff9949eu, 0x0000949eu, 0x0000949eu, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9951du, 0xa078949eu, 0x9ff9951du, 0x9ff9951du, 0x9ff9951du, 0x949effffu, 0x00009ff9u, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9ffffu}, + {0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x0000017fu, 0x008000ffu, 0x0000017fu, 0x0000017fu, 0x0000017fu, 0x00ff5678u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00005678u}, + {0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008080u, 0x00808000u, 0x00008080u, 0x00008080u, 0x00008080u, 0x80000001u, 0x00000000u, 0x00008000u, 0x00008000u, 0x00000001u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x00010070u, 0x0080fff0u, 0x00010070u, 0x00010070u, 0x00010070u, 0xfff0989du, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x0000989du}, + {0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0001007fu, 0x0080ffffu, 0x0001007fu, 0x0001007fu, 0x0001007fu, 0xffff0080u, 0x00000000u, 0x0000ffffu, 0x0000ffffu, 0x00000080u}, + {0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x80000081u, 0x80800001u, 0x80000081u, 0x80000081u, 0x80000081u, 0x0001007fu, 0x00008000u, 0x80000001u, 0x80000001u, 0x8000007fu}, + {0xaf4a9e38u, 0xaf4a9e38u, 0x00009e38u, 0x00009e38u, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a9eb8u, 0xafca9e38u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0x9e3844f4u, 0x0000af4au, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a44f4u}, + {0xea4d8c45u, 0xea4d8c45u, 0x00008c45u, 0x00008c45u, 0xea4d8c45u, 0xea4d8c45u, 0xea4d8cc5u, 0xeacd8c45u, 0xea4d8cc5u, 0xea4d8cc5u, 0xea4d8cc5u, 0x8c455618u, 0x0000ea4du, 0xea4d8c45u, 0xea4d8c45u, 0xea4d5618u}, + {0xfd4fb563u, 0xfd4fb563u, 0x0000b563u, 0x0000b563u, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fb5e3u, 0xfdcfb563u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xb563ff00u, 0x0000fd4fu, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fff00u}, + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000100u, 0x00ff0001u, 0x00000100u, 0x00000100u, 0x00000100u, 0x000133d9u, 0x00000000u, 0x00000001u, 0x00000001u, 0x000033d9u}, + {0x2f3bacc0u, 0x2f3bacc0u, 0x0000acc0u, 0x0000acc0u, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3badbfu, 0x303aacc0u, 0x2f3badbfu, 0x2f3badbfu, 0x2f3badbfu, 0xacc00000u, 0x00002f3bu, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3b0000u}, + {0x85f55b11u, 0x85f55b11u, 0x00005b11u, 0x00005b11u, 0x85f55b11u, 0x85f55b11u, 0x85f55c10u, 0x86f45b11u, 0x85f55c10u, 0x85f55c10u, 0x85f55c10u, 0x5b110000u, 0x000085f5u, 0x85f55b11u, 0x85f55b11u, 0x85f50000u}, + {0xff00ff00u, 0xff00ff00u, 0x0000ff00u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff00ffffu, 0xffffff00u, 0xff00ffffu, 0xff00ffffu, 0xff00ffffu, 0xff0008e0u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff0008e0u}, + {0xffff0000u, 0xffff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffff0000u, 0xffff00ffu, 0xffffffffu, 0xffff00ffu, 0xffff00ffu, 0xffff00ffu, 0x00000000u, 0x0000ffffu, 0xffff0000u, 0xffff0000u, 0xffff0000u} +}; +constexpr const char* Names[16] = { + "mad_u32_u24", + "mad_i32_i24", + "mad_u16", + "mad_i16", + "mad_u32_u16", + "mad_i32_i16", + "sad_u8", + "sad_hi_u8", + "sad_u16", + "sad_u32", + "msad_u8", + "mad_u16 op_sel", + "mad_i16 op_sel", + "mad_i32_i16 op_sel", + "mad_u32_u16 op_sel", + "mad_i16 op_sel", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp new file mode 100644 index 000000000..adc82c757 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd572800a, 0x041a0d04, 0xd573800c, + 0x041a0d04, 0xd575800e, 0x04120d04, 0xd7558012, 0x041a0b04, 0xd7568013, 0x041a0b04, 0xd7528014, + 0x041a0b04, 0xd7538015, 0x041a0b04, 0xd7588016, 0x041a0b04, 0xd7598017, 0x041a0b04, 0xd54e8018, + 0x041a0b04, 0xd54f8019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x0000ffffu}, + {0x0000007fu, 0xe3718038u, 0xfffffffeu, 0xfd7c84feu}, + {0x0000007fu, 0xfffffffeu, 0x0000007fu, 0xfffffffeu}, + {0xfffffffeu, 0x00000002u, 0xb93201bfu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ff51543u, 0x8ca197b1u}, + {0x00000000u, 0x00000001u, 0x007fffffu, 0x29813b74u}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0x00007fffu}, + {0x00000000u, 0x00000001u, 0x01010101u, 0xa48d7f36u}, + {0x00000000u, 0x00000001u, 0x1ef5acc3u, 0x5d780492u}, + {0x00000000u, 0x00000001u, 0x41f8bfb7u, 0x3f57a9d3u}, + {0x00000000u, 0x00000001u, 0xfefefefeu, 0x0000007fu}, + {0x00000000u, 0x00000002u, 0x00ffffffu, 0x80000000u}, + {0x00000000u, 0x00000002u, 0x7351a059u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0xcb50b50fu, 0x7fffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x01010101u}, + {0x00000000u, 0x0000007fu, 0x0ac13011u, 0xffff0000u}, + {0x00000000u, 0x0000007fu, 0x4b88f735u, 0x000000ffu}, + {0x00000000u, 0x0000007fu, 0xffffffffu, 0x0000fff0u}, + {0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0xffffffffu, 0xde945808u}, + {0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000080u}, + {0x00000000u, 0x000000ffu, 0x7fffffffu, 0xca9c52c9u}, + {0x00000000u, 0x000000ffu, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0x00007fffu}, + {0x00000000u, 0x000000ffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00007fffu, 0x01010101u, 0xcff6ab04u}, + {0x00000000u, 0x00007fffu, 0x44e9da39u, 0x63548e6fu}, + {0x00000000u, 0x00007fffu, 0x4944fa2au, 0xf5524d50u}, + {0x00000000u, 0x00007fffu, 0xa1dfdb1eu, 0xa9e05a10u}, + {0x00000000u, 0x00007fffu, 0xffff0000u, 0x01010101u}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000007fu}, + {0x00000000u, 0x0000fff0u, 0x00000080u, 0x01010101u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x01000080u, 0x0080ffffu, 0x01000080u, 0x0080ffffu, 0x00000080u, 0x00000080u, 0x00800080u, 0x0001007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffff8841u, 0xffffffffu, 0xffff8841u, 0x000003fbu, 0xe37183fbu, 0xffffffffu, 0xfd7c87d0u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x000001ffu, 0x007fe371u}, + {0x017d007fu, 0xffffffffu, 0x007f007fu, 0xffffffffu, 0x0000007fu, 0xffffffffu, 0x000000feu, 0xffffffffu, 0x0000007fu, 0x0000fffeu, 0x0000fffeu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x000000ffu, 0x00007fffu}, + {0xbbf4040fu, 0x012c0227u, 0xbbf4040fu, 0x012c0227u, 0xffffffffu, 0x000002c4u, 0xb93203e6u, 0x0000012cu, 0x000001bfu, 0x0000fffeu, 0x0000fffeu, 0x00000002u, 0x00000002u, 0x000001bfu, 0xfffffffcu, 0xfffffe00u}, + {0x418116cfu, 0x8e2d993du, 0x418116cfu, 0x8e2d993du, 0x0000018cu, 0x0000018cu, 0x3ff516cfu, 0x8ca1993du, 0x00001543u, 0x00001543u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02fdffffu, 0x2bfd3df0u, 0x02fcffffu, 0x2bfd3df0u, 0x0000027du, 0x0000027eu, 0x0080027bu, 0x29813df0u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x02fe02fdu, 0x01ff81fcu, 0x02fd02fdu, 0x01fe81fcu, 0x000001feu, 0x000001ffu, 0x00ff02fcu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x01040105u, 0xa4907f39u, 0x01040105u, 0xa4907f39u, 0x00000004u, 0x00000004u, 0x01010104u, 0xa48d7f39u, 0x00000101u, 0x00000101u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x2176af45u, 0x5ff90713u, 0x2176af45u, 0x5ff90713u, 0x00000282u, 0x00000282u, 0x1ef5af44u, 0x5d780713u, 0x00000001u, 0x0000acc3u, 0x0000acc3u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x44a6c266u, 0x4205ac81u, 0x44a6c266u, 0x4205ac81u, 0x000002afu, 0x000002afu, 0x41f8c265u, 0x3f57ac81u, 0x00000001u, 0x0000bfb7u, 0x0000bfb7u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x03f70476u, 0xffffffffu, 0x03f70476u, 0x000003f8u, 0x000003f8u, 0xfeff02f5u, 0x00000476u, 0x00000001u, 0x0000fefeu, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x03feffffu, 0x82fb02fbu, 0x03fcffffu, 0x82fb02fbu, 0x000002fdu, 0x000002ffu, 0x010002fau, 0x800002fbu, 0x00000002u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x750ca216u, 0x01bb81bau, 0x750ca216u, 0x01bb81bau, 0x000001bdu, 0x000001bdu, 0x7351a214u, 0x000081bau, 0x00000002u, 0x0000a059u, 0x0000a059u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0xcd2db6eeu, 0x81dcffffu, 0xcd2db6eeu, 0x81dcffffu, 0x000001dfu, 0x000001dfu, 0xcb50b6ecu, 0x800001dcu, 0x00000002u, 0x0000b50fu, 0x0000b50fu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x027dffffu, 0x0280037eu, 0x01feffffu, 0x028002ffu, 0x000001feu, 0x0000027du, 0x000101fdu, 0x01010280u, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x0c38311du, 0xffff008du, 0x0c38311du, 0xffff008du, 0x0000010cu, 0x000001f6u, 0x0ac1309eu, 0xffff012bu, 0x00003011u, 0x00003011u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x4d70f934u, 0x0180027fu, 0x4d70f934u, 0x0180027fu, 0x000001ffu, 0x00000267u, 0x4b88f8b5u, 0x0000027fu, 0x0000007fu, 0x0000f735u, 0x0000f735u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x037dffffu, 0xffffffffu, 0x037dffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0x0001036du, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x017fffffu, 0x00000000u, 0x00ffffffu, 0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu, 0x00000080u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000080u}, + {0xffffffffu, 0xe2105b84u, 0xffffffffu, 0xe2105b84u, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xde945b84u, 0x00000080u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x01fe01feu, 0x01fe027eu, 0x00ff01feu, 0x00ff017fu, 0x000000ffu, 0x000001feu, 0x000001feu, 0x0000017fu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x837cffffu, 0xcd195546u, 0x837cffffu, 0xcd195546u, 0x0000037cu, 0x0000047cu, 0x8000027cu, 0xca9c5546u, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x80800082u, 0x018001ffu, 0x80800082u, 0x00810100u, 0x00000081u, 0x0000017fu, 0x80000082u, 0x00000100u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000007fu, 0x00000000u}, + {0xffff01feu, 0x02fd80feu, 0xffff01feu, 0x01fe80feu, 0x000001feu, 0x000001feu, 0xffff00ffu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x02020105u, 0xd174ac82u, 0x02020105u, 0xd174ac82u, 0x00000004u, 0x00008100u, 0x0101027fu, 0xcff6ac82u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000101u, 0x00000101u, 0x00003fffu, 0x0000007fu}, + {0x47a0dc79u, 0x64608fd3u, 0x47a0dc79u, 0x64608fd3u, 0x00000240u, 0x000082b6u, 0x44e9db9du, 0x63548f7bu, 0x00007fffu, 0x0000da39u, 0x0000da39u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x0000007fu}, + {0x4b62fbdbu, 0xf6054f65u, 0x4b62fbdbu, 0xf6054f65u, 0x000001b1u, 0x0000821du, 0x4944fc3fu, 0xf5524e03u, 0x00007fffu, 0x0000fa2au, 0x0000fa2au, 0x00000000u, 0x00000000u, 0x00007fffu, 0x0000001fu, 0x00000000u}, + {0xa415dd97u, 0xab235b4bu, 0xa415dd97u, 0xab235b4bu, 0x00000279u, 0x00008235u, 0xa1dfdc59u, 0xa9e05b53u, 0x00007fffu, 0x0000db1eu, 0x0000db1eu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0xffff01feu, 0x037f0181u, 0xffff01feu, 0x02800181u, 0x000001feu, 0x000080feu, 0xffff0080u, 0x01010280u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00007fffu}, + {0xffffffffu, 0x027e02fdu, 0xffffffffu, 0x027e02fdu, 0x000003fcu, 0x000082fcu, 0xffffffffu, 0x000002fdu, 0x00007fffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0x01700100u, 0x03700370u, 0x00800100u, 0x01810181u, 0x00000080u, 0x00010070u, 0x00000100u, 0x01010181u, 0x00000080u, 0x0000fff0u, 0x0000fff0u, 0x00000000u, 0x00000000u, 0x00000080u, 0x0000fff0u, 0x0000fff0u} +}; +constexpr const char* Names[16] = { + "qsad_pk_u16_u8 0", + "qsad_pk_u16_u8 1", + "mqsad_pk_u16_u8 0", + "mqsad_pk_u16_u8 1", + "mqsad_u32_u8 0", + "mqsad_u32_u8 1", + "mqsad_u32_u8 2", + "mqsad_u32_u8 3", + "max3_i16", + "max3_u16", + "min3_i16", + "min3_u16", + "med3_i16", + "med3_u16", + "alignbit_b32", + "alignbyte_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp wide: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp wide tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp index 00055340c..8d4c14553 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp @@ -90,19 +90,6 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void CheckClampRefused(AgcDriver::VulkanDevice& device) { - for (const std::uint32_t opcode : {0x15au, 0x15bu, 0x15cu, 0x171u}) { - alignas(256) const std::array code{0xd400800au | (opcode << 16u), 0x041a0b04u, 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with clamp was not refused"); - } -} - std::uint32_t Sad(std::uint32_t lhs, std::uint32_t rhs, std::uint32_t fieldBits, bool masked) { const std::uint32_t fieldMask = (1u << fieldBits) - 1u; std::uint32_t sum = 0u; @@ -154,7 +141,6 @@ int main() { FillInput(); Run(*device); Check(); - CheckClampRefused(*device); std::puts("vop3 sad tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index ba44a9edd..756a18296 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -753,8 +753,26 @@ bool isNativeVop3AlignOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VAlignbitB32 || opcode == RdnaOpcode::VAlignbyteB32; } -bool usesSignedSaturateClamp(RdnaOpcode opcode) { - return opcode == RdnaOpcode::VAddNcI32 || opcode == RdnaOpcode::VSubNcI32; +bool usesIntegerSaturateClamp(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::VAddNcI32: + case RdnaOpcode::VSubNcI32: + case RdnaOpcode::VMadU32U24: + case RdnaOpcode::VMadI32I24: + case RdnaOpcode::VMadU16: + case RdnaOpcode::VMadI16: + case RdnaOpcode::VMadU32U16: + case RdnaOpcode::VMadI32I16: + case RdnaOpcode::VSadU8: + case RdnaOpcode::VSadHiU8: + case RdnaOpcode::VSadU16: + case RdnaOpcode::VSadU32: + case RdnaOpcode::VMsadU8: + case RdnaOpcode::VQsadPkU16U8: + case RdnaOpcode::VMqsadPkU16U8: + case RdnaOpcode::VMqsadU32U8: return true; + default: return false; + } } bool isCubeOpcode(RdnaOpcode opcode) { @@ -1938,7 +1956,7 @@ bool isF32DivisionStepOpcode(RdnaOpcode opcode) { } bool supportsNativeVop3Clamp(RdnaOpcode opcode) { - return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode) || + return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesIntegerSaturateClamp(opcode) || isF32DivisionStepOpcode(opcode) || isCubeOpcode(opcode); } @@ -1956,13 +1974,13 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, return; } if (isNativeVop3I16TernaryOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } if (isNativeVop3AlignOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -1980,7 +1998,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (usesSignedSaturateClamp(opcode)) { + if (usesIntegerSaturateClamp(opcode)) { if (abs != 0u || opSel != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index b2e93e342..3bf343733 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -193,6 +193,8 @@ private: bool integer16Shift(const RdnaInstruction& inst, IrOpcode opcode, bool arithmetic); bool integer16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool integer16Mad(const RdnaInstruction& inst, bool sign, bool wide); + IrU32 saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign); + IrU32 wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign); bool vAddSubNcI32(const RdnaInstruction& inst, bool subtract); bool integer16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool vMed3I16(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 9e9949ba3..ba6b25f67 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -25,6 +25,33 @@ bool TranslationContext::integer16Binary(const RdnaInstruction& inst, IrOpcode o return true; } +IrU32 TranslationContext::saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign) { + if (!inst.destination.clamp) { + return value; + } + if (!sign) { + return IrU32(ir.Emit(IrOpcode::UMin32, IrType::U32, {&value.Value(), &ir.Constant(0xffffu)})); + } + const IrU32 upper(ir.Emit(IrOpcode::SMin32, IrType::U32, {&value.Value(), &ir.Constant(0x7fffu)})); + return IrU32(ir.Emit(IrOpcode::SMax32, IrType::U32, {&upper.Value(), &ir.Constant(0xffff8000u)})); +} + +IrU32 TranslationContext::wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign) { + const IrU32 sum(ir.IAdd(low.Value(), addend.Value())); + if (!inst.destination.clamp) { + return sum; + } + const IrU32 carry(ir.Select(ir.ULessThan(sum.Value(), low.Value()), ir.Constant(1u), ir.Constant(0u))); + IrU32 top(ir.IAdd(high.Value(), carry.Value())); + if (!sign) { + return IrU32(ir.Select(ir.INotEqual(top.Value(), ir.Constant(0u)), ir.Constant(0xffffffffu), sum.Value())); + } + top = IrU32(ir.IAdd(top.Value(), ir.ShiftRightArithmetic(addend.Value(), ir.Constant(31u)))); + const IrU1 overflow(ir.INotEqual(top.Value(), ir.ShiftRightArithmetic(sum.Value(), ir.Constant(31u)))); + IrValue& saturated = ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)); + return IrU32(ir.Select(overflow.Value(), saturated, sum.Value())); +} + bool TranslationContext::vMed3I16(const RdnaInstruction& inst) { const IrU32 first = readU16AsU32(sourceAt(inst, 0u), true); const IrU32 second = readU16AsU32(sourceAt(inst, 1u), true); @@ -46,11 +73,14 @@ bool TranslationContext::integer16Ternary(const RdnaInstruction& inst, IrOpcode bool TranslationContext::integer16Mad(const RdnaInstruction& inst, bool sign, bool wide) { const IrU32 lhs = readU16AsU32(sourceAt(inst, 0u), sign); const IrU32 rhs = readU16AsU32(sourceAt(inst, 1u), sign); - const IrU32 addend = wide ? readU32(sourceAt(inst, 2u)) : readU16AsU32(sourceAt(inst, 2u), sign); - const IrU32 result(ir.IAdd(ir.IMul(lhs.Value(), rhs.Value()), addend.Value())); + const IrU32 product(ir.IMul(lhs.Value(), rhs.Value())); if (wide) { + const IrU32 high(sign ? ir.ShiftRightArithmetic(product.Value(), ir.Constant(31u)) : ir.Constant(0u)); + const IrU32 result = wideAdd(inst, product, high, readU32(sourceAt(inst, 2u)), sign); writeOperand(inst.destination, &result.Value()); } else { + const IrU32 addend = readU16AsU32(sourceAt(inst, 2u), sign); + const IrU32 result = saturateU16Result(inst, IrU32(ir.IAdd(product.Value(), addend.Value())), sign); write16Bits(inst.destination, IrU32(ir.BitwiseAnd(result.Value(), ir.Constant(0xffffu)))); } return true; @@ -323,7 +353,8 @@ bool TranslationContext::integer24(const RdnaInstruction& inst, bool sign, bool result = IrU32(ir.Select(ir.INotEqual(top.Value(), expected), saturated, result.Value())); } if (addend) { - result = IrU32(ir.IAdd(result.Value(), readU32(sourceAt(inst, 2u)).Value())); + const IrU32 productHigh(inst.destination.clamp ? ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()}) : ir.Constant(0u)); + result = wideAdd(inst, result, productHigh, readU32(sourceAt(inst, 2u)), sign); } writeOperand(inst.destination, &result.Value()); return true; @@ -377,7 +408,7 @@ bool TranslationContext::vSadU32(const RdnaInstruction& inst) { const IrU32 lo(ir.Emit(IrOpcode::UMin32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 hi(ir.Emit(IrOpcode::UMax32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(ir.ISub(hi.Value(), lo.Value()), addend.Value())); + const IrU32 result = wideAdd(inst, IrU32(ir.ISub(hi.Value(), lo.Value())), IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -405,7 +436,7 @@ bool TranslationContext::subwordSad(const RdnaInstruction& inst, std::uint32_t f sum = IrU32(ir.ShiftLeftLogical(sum.Value(), ir.Constant(sumShift))); } const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(sum.Value(), addend.Value())); + const IrU32 result = wideAdd(inst, sum, IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -423,14 +454,15 @@ bool TranslationContext::vQsadU8(const RdnaInstruction& inst, bool masked, bool const std::array accumulator = readU32Pair(sourceAt(inst, 2u)); if (wide) { const std::array accumulatorHigh = readU32Pair(offsetOperand(sourceAt(inst, 2u), 2u)); - writeU32Pair(inst.destination, {IrU32(ir.IAdd(accumulator[0].Value(), sad(0u).Value())), IrU32(ir.IAdd(accumulator[1].Value(), sad(8u).Value()))}); + const IrU32 zero(ir.Constant(0u)); + writeU32Pair(inst.destination, {wideAdd(inst, sad(0u), zero, accumulator[0], false), wideAdd(inst, sad(8u), zero, accumulator[1], false)}); writeU32Pair(offsetOperand(inst.destination, 2u), - {IrU32(ir.IAdd(accumulatorHigh[0].Value(), sad(16u).Value())), IrU32(ir.IAdd(accumulatorHigh[1].Value(), sad(24u).Value()))}); + {wideAdd(inst, sad(16u), zero, accumulatorHigh[0], false), wideAdd(inst, sad(24u), zero, accumulatorHigh[1], false)}); return true; } const auto packed = [&](const IrU32& addend, std::uint32_t shift) { - const IrU32 low(ir.IAdd(addend.Value(), sad(shift).Value())); - const IrU32 high(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())); + const IrU32 low = saturateU16Result(inst, IrU32(ir.IAdd(ir.BitwiseAnd(addend.Value(), ir.Constant(0xffffu)), sad(shift).Value())), false); + const IrU32 high = saturateU16Result(inst, IrU32(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())), false); return IrU32(ir.BitwiseOr(ir.BitwiseAnd(low.Value(), ir.Constant(0xffffu)), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); }; writeU32Pair(inst.destination, {packed(accumulator[0], 0u), packed(accumulator[1], 16u)}); From 3299d56707f616e395dc904ac5c9e1f8709cd3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:09:52 +0200 Subject: [PATCH 38/55] fix(tools): count function-try-block exports in progress.py Co-Authored-By: Claude --- tools/progress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/progress.py b/tools/progress.py index b78a73e89..81ceeea05 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -10,7 +10,7 @@ PRX = ROOT / "core" / "libs" / "prx" OPCODES = ROOT / "core" / "shader" / "recompiler" / "RdnaDecoder" / "include" / "RdnaDecoder" / "RdnaOpcode.hpp" ISA = Path(__file__).resolve().parent / "rdna_isa.txt" SOURCE = f'https://github.com/{os.environ.get("GITHUB_REPOSITORY", "boykopovar/AnyPS5")}/blob/main' -DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?\{") +DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?(?:try\s*)?\{") STUB = "NotImplemented_nid_no_patch" STUB_WRAPPER = re.compile(r"\bstatic\s+(?:\[\[noreturn\]\]\s+)?void\s+(\w+)\s*\([^;{]*\)\s*\{") FLAT_SEGMENTS = ("GLOBAL_", "SCRATCH_") From f3e783b6cb1971fbda219580338e85bd8da73f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:14:49 +0200 Subject: [PATCH 39/55] feat(libs): implement sceNpTrophy2UnregisterUnlockCallback Co-Authored-By: Claude --- core/libs/prx/libSceNpTrophy2/CMakeLists.txt | 18 +++++++++++++++++ core/libs/prx/libSceNpTrophy2/src/Context.cpp | 3 +-- core/libs/tests/GuestNpTrophy2.cpp | 20 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 core/libs/tests/GuestNpTrophy2.cpp diff --git a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt index c86b204c3..06ccd898a 100644 --- a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt @@ -11,3 +11,21 @@ target_include_directories(libSceNpTrophy2 PRIVATE ) target_link_libraries(libSceNpTrophy2 PRIVATE libc) configure_windows_unwind(libSceNpTrophy2) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nptrophy2_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy2.cpp) + target_include_directories(guest_nptrophy2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nptrophy2_tests PRIVATE libSceNpTrophy2 libc) + configure_windows_unwind(guest_nptrophy2_tests) + add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests) + if(WIN32) + set_tests_properties(guest_nptrophy2 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 097a8fe0d..e259afb73 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti } int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SCE_NP_TROPHY2_OK; } } diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp new file mode 100644 index 000000000..5a286ab35 --- /dev/null +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -0,0 +1,20 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void*, void*); +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI OnUnlock(int, int, void*) {} + +int main() { + int userdata = 0; + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..d01404406 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -20,6 +20,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. +- [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. ### Unknown function info @@ -74,7 +75,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented -- [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature From ce4bdbf2eb8e2fc45b2c2099b78b45807418df5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:22:12 +0200 Subject: [PATCH 40/55] feat(libc): strtoimax and logbf Co-Authored-By: Claude --- core/libs/prx/libc/src/Math.cpp | 1 + core/libs/prx/libc/src/RuntimeSupport.cpp | 4 ++++ core/libs/tests/GuestMath.cpp | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..ee249e03b 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -13,6 +13,7 @@ float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } +float APS5_VABI logbf_nid_postfix(float x) { return std::logb(x); } double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); } double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); } diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index ef00dd455..e6bf99f61 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -93,6 +93,10 @@ unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, return GccAtomicLoad(target); } +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoimax(str, endptr, base); +} + std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base) { return std::strtoumax(str, endptr, base); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..ab9241755 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -10,6 +10,7 @@ float APS5_VABI strtof_nid_postfix(const char*, char**); long double APS5_VABI strtold_nid_postfix(const char*, char**); std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); @@ -17,6 +18,7 @@ float APS5_VABI acosf_nid_postfix(float); float APS5_VABI atan2f_nid_postfix(float, float); float APS5_VABI tanf_nid_postfix(float); float APS5_VABI log10f_nid_postfix(float); +float APS5_VABI logbf_nid_postfix(float); double APS5_VABI exp2_nid_postfix(double); double APS5_VABI ldexp_nid_postfix(double, int); double APS5_VABI scalbn_nid_postfix(double, int); @@ -69,6 +71,12 @@ static void CheckIntegerConversions() { std::fprintf(stderr, "Guest strtol failed for '%s' in base %d\n", test.text, test.base); std::abort(); } + *__error_nid_postfix() = 0; + const auto maxValue = strtoimax_nid_postfix(test.text, &end, test.base); + if (maxValue != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoimax failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } } struct UnsignedCase { const char* text; @@ -134,6 +142,11 @@ int main() { Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); Require(tanf_nid_postfix(0.f) == 0.f); Require(log10f_nid_postfix(100.f) == 2.f); + Require(logbf_nid_postfix(8.f) == 3.f && logbf_nid_postfix(-0.75f) == -1.f); + Require(logbf_nid_postfix(std::numeric_limits::denorm_min()) == -149.f); + Require(logbf_nid_postfix(0.f) == -std::numeric_limits::infinity()); + Require(logbf_nid_postfix(-std::numeric_limits::infinity()) == std::numeric_limits::infinity()); + Require(std::isnan(logbf_nid_postfix(std::numeric_limits::quiet_NaN()))); Require(exp2_nid_postfix(-3.) == 0.125); Require(ldexp_nid_postfix(0.75, 4) == 12.); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); From 4136a04ba7879fef358fb36bab01ed6ba91e5221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:25:46 +0200 Subject: [PATCH 41/55] feat(ulobjmgr): register and unregister object Co-Authored-By: Claude --- core/libs/prx/ulobjmgr/CMakeLists.txt | 20 +++++++++++++++++ core/libs/prx/ulobjmgr/Export.cpp | 18 ++++++++++----- core/libs/tests/GuestUlobjmgr.cpp | 32 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 ++ 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/ulobjmgr/CMakeLists.txt create mode 100644 core/libs/tests/GuestUlobjmgr.cpp diff --git a/core/libs/prx/ulobjmgr/CMakeLists.txt b/core/libs/prx/ulobjmgr/CMakeLists.txt new file mode 100644 index 000000000..84d347d1a --- /dev/null +++ b/core/libs/prx/ulobjmgr/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(ulobjmgr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(ulobjmgr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(ulobjmgr) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ulobjmgr_tests ${LIBS_INCLUDE_DIR}/tests/GuestUlobjmgr.cpp) + target_include_directories(guest_ulobjmgr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ulobjmgr_tests PRIVATE ulobjmgr) + add_test(NAME guest_ulobjmgr COMMAND guest_ulobjmgr_tests) + if(WIN32) + set_tests_properties(guest_ulobjmgr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/ulobjmgr/Export.cpp b/core/libs/prx/ulobjmgr/Export.cpp index 528a0dfc2..25b7f547b 100644 --- a/core/libs/prx/ulobjmgr/Export.cpp +++ b/core/libs/prx/ulobjmgr/Export.cpp @@ -3,17 +3,23 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ULOBJMGR_EINVAL = 22; +constexpr std::uint32_t ULOBJMGR_ID_LIMIT = 0x4000; + +} + extern "C" { -int APS5_VABI _sceUlobjmgrRegisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + if (object == 0 || kind == 0 || id == nullptr) return ULOBJMGR_EINVAL; + *id = 0; return 0; } -int APS5_VABI _sceUlobjmgrUnregisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id) { + if (id >= ULOBJMGR_ID_LIMIT) return ULOBJMGR_EINVAL; return 0; } diff --git a/core/libs/tests/GuestUlobjmgr.cpp b/core/libs/tests/GuestUlobjmgr.cpp new file mode 100644 index 000000000..0fd79500f --- /dev/null +++ b/core/libs/tests/GuestUlobjmgr.cpp @@ -0,0 +1,32 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); +} + +namespace { + +constexpr int einval = 22; + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + std::uint32_t id = 0xffffffffu; + Require(_sceUlobjmgrRegisterObject(0, 1, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 0, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 1, nullptr) == einval); + Require(_sceUlobjmgrRegisterObject(0x1000, -1, &id) == 0); + Require(id == 0); + + Require(_sceUlobjmgrUnregisterObject(id) == 0); + Require(_sceUlobjmgrUnregisterObject(0x3fff) == 0); + Require(_sceUlobjmgrUnregisterObject(0x4000) == einval); + Require(_sceUlobjmgrUnregisterObject(0xffffffffu) == einval); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..c3205955e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -21,6 +21,7 @@ Throughout the project, every function at every stage either **does exactly what - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [ulobjmgr](../../core/libs/prx/ulobjmgr/Export.cpp) registers no object: `_sceUlobjmgrRegisterObject` always hands out id 0 and `_sceUlobjmgrUnregisterObject` releases nothing, as shadPS4 does ### Unknown function info @@ -80,6 +81,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. +- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4 ### Functional From 7bcdc6f5780775c78bde7aa33526e7e1f0c2cd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:28:55 +0200 Subject: [PATCH 42/55] feat(http): name sceHttpSetInflateGZIPEnabled and sceHttpSetRequestStatusCallback Co-Authored-By: Claude --- core/libs/prx/libSceHttp/Export.cpp | 11 +++++------ core/libs/tests/GuestHttp.cpp | 5 +++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index d84ed5a4c..0bbfa9e09 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -246,15 +246,14 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { } -APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); -int APS5_VABI sceHttpUnknown00(void) { - NotImplemented_nid_no_patch("i9mhafzkEi8"); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable) { + (void)id; + if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; return 0; } -APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); -int APS5_VABI sceHttpUnknown01(void) { - NotImplemented_nid_no_patch("vO4B-42ef-k"); +int APS5_VABI sceHttpSetRequestStatusCallback(void) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..697279441 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -7,6 +7,7 @@ extern "C" { int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int, int); int APS5_VABI sceHttpUriBuild(char*, std::size_t*, std::size_t, const SceHttpUriElement*, std::uint32_t); int APS5_VABI sceHttpUriEscape(char*, std::size_t*, std::size_t, const char*); int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); @@ -97,6 +98,10 @@ int main() { Require(sceHttpReadData(1, data, sizeof(data)) == network); Require(sceHttpCreateRequest2(1, "GET", "/", 0) > 0); + Require(sceHttpSetInflateGZIPEnabled(1, 0) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 1) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 2) == invalidValue); + Require(sceHttpSetInflateGZIPEnabled(1, -1) == invalidValue); Require(sceHttpsEnableOption(1, 0) == 0); Require(sceHttpsLoadCert(1, 0, nullptr, nullptr, nullptr) == 0); int httpErrno = -1; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..2eae8a29a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -70,6 +70,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceHttpSetRequestStatusCallback](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - no reference implements it (shadPS4 only logs and returns 0); when the callback runs and what status it gets is unknown - [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature From cf7c5990873e6ff31fce6ce5003ed7c874efa228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:32:16 +0200 Subject: [PATCH 43/55] feat(libs): implement scePlayGoDialogGetStatus Rename the NOAMxY2EGS0 stub and return the dialog's current status without advancing it. Co-Authored-By: Claude --- core/libs/prx/libScePlayGoDialog/Export.cpp | 6 ++---- core/libs/tests/GuestPlayGoDialog.cpp | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libScePlayGoDialog/Export.cpp b/core/libs/prx/libScePlayGoDialog/Export.cpp index 5b85d52c3..16615b02e 100644 --- a/core/libs/prx/libScePlayGoDialog/Export.cpp +++ b/core/libs/prx/libScePlayGoDialog/Export.cpp @@ -74,10 +74,8 @@ int APS5_VABI scePlayGoDialogGetResult(void* result) { return 0; } -APS5_EXPORT("NOAMxY2EGS0", scePlayGoDialogUnknown00); -int APS5_VABI scePlayGoDialogUnknown00(void) { - NotImplemented_nid_no_patch("NOAMxY2EGS0"); - return 0; +int APS5_VABI scePlayGoDialogGetStatus(void) { + return g_status.load(); } } diff --git a/core/libs/tests/GuestPlayGoDialog.cpp b/core/libs/tests/GuestPlayGoDialog.cpp index 9c313bda1..40a6a6710 100644 --- a/core/libs/tests/GuestPlayGoDialog.cpp +++ b/core/libs/tests/GuestPlayGoDialog.cpp @@ -10,6 +10,7 @@ int APS5_VABI scePlayGoDialogTerminate(void); int APS5_VABI scePlayGoDialogOpen(const void* param); int APS5_VABI scePlayGoDialogClose(void); int APS5_VABI scePlayGoDialogUpdateStatus(void); +int APS5_VABI scePlayGoDialogGetStatus(void); int APS5_VABI scePlayGoDialogGetResult(void* result); } @@ -21,6 +22,7 @@ int main() { constexpr std::int32_t paramInvalid = static_cast(0x80ED0003); constexpr std::int32_t invalidState = static_cast(0x80ED0005); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogGetResult(nullptr) == notInitialized); @@ -29,11 +31,13 @@ int main() { Require(scePlayGoDialogOpen(&openParam) == invalidState); Require(scePlayGoDialogInitialize() == 0); + Require(scePlayGoDialogGetStatus() == 1); Require(scePlayGoDialogInitialize() == alreadyInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogUpdateStatus() == 1); Require(scePlayGoDialogOpen(&openParam) == 0); + Require(scePlayGoDialogGetStatus() == 3); Require(scePlayGoDialogUpdateStatus() == 3); PlayGoDialogResult result{}; @@ -41,6 +45,7 @@ int main() { Require(result.result == 0); Require(scePlayGoDialogClose() == 0); Require(scePlayGoDialogTerminate() == 0); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); return 0; } From 9b9194e543c8748e4ae1069a4efc20f0f398d89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:35:25 +0200 Subject: [PATCH 44/55] feat(libSceShare): sceShareCaptureScreenshotExtended Co-Authored-By: Claude --- core/libs/prx/libSceShare/CMakeLists.txt | 20 ++++++++++++++++ core/libs/prx/libSceShare/Export.cpp | 17 ++++++++++---- core/libs/tests/GuestShare.cpp | 30 ++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 - 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceShare/CMakeLists.txt create mode 100644 core/libs/tests/GuestShare.cpp diff --git a/core/libs/prx/libSceShare/CMakeLists.txt b/core/libs/prx/libSceShare/CMakeLists.txt new file mode 100644 index 000000000..74ac02fb2 --- /dev/null +++ b/core/libs/prx/libSceShare/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceShare SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceShare PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceShare) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_share_tests ${LIBS_INCLUDE_DIR}/tests/GuestShare.cpp) + target_include_directories(guest_share_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_share_tests PRIVATE libSceShare) + add_test(NAME guest_share COMMAND guest_share_tests) + if(WIN32) + set_tests_properties(guest_share PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index b75a31a22..caabed9b9 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -5,6 +5,13 @@ // Capture and sharing are not emulated; policy and parameter calls are accepted. +namespace { + +constexpr std::int32_t ERROR_NOT_SUPPORTED = static_cast(0x81960007); +constexpr std::int32_t REQUEST_ID_INVALID = -1; + +} + extern "C" { int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* req_id) { @@ -85,10 +92,12 @@ int APS5_VABI sceShareUnregisterContentEventCallback(void* callback) { return 0; } -APS5_EXPORT("GQTObcITIXI", sceShareUnknown_GQTObcITIXI); -int32_t APS5_VABI sceShareUnknown_GQTObcITIXI(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, int32_t* req_id) { + (void)extended_param; + if (req_id != nullptr) { + *req_id = REQUEST_ID_INVALID; + } + return ERROR_NOT_SUPPORTED; } } diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp new file mode 100644 index 000000000..353b80bb4 --- /dev/null +++ b/core/libs/tests/GuestShare.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + constexpr std::int32_t notSupported = static_cast(0x81960007); + std::uint8_t param[64]{}; + + std::int32_t reqId = 7; + Require(sceShareCaptureScreenshotExtended(param, &reqId) == notSupported); + Require(reqId == -1); + + reqId = 7; + Require(sceShareCaptureScreenshotExtended(nullptr, &reqId) == notSupported); + Require(reqId == -1); + + Require(sceShareCaptureScreenshotExtended(param, nullptr) == notSupported); + Require(sceShareCaptureScreenshotExtended(nullptr, nullptr) == notSupported); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..354745637 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -55,7 +55,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature -- [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature - [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask From e923bea317230767aa468f7ddfc22d5650881a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:41:59 +0200 Subject: [PATCH 45/55] feat(savedata): implement the PS4 transfer mount and directory search Rename the two unknown libSceSaveData.native stubs to sceSaveDataTransferringMountPs4 (RjMlsR8EXrw) and sceSaveDataDirNameSearchPs4 (X4MYzukPc3g). There is no PS4 save area: the mount returns NOT_FOUND like sceSaveDataTransferringMount, and the search reports zero hits. Co-Authored-By: Claude --- .../prx/libSceSaveData.native/CMakeLists.txt | 10 ++ .../libs/prx/libSceSaveData.native/Export.cpp | 26 ++++-- core/libs/tests/GuestSaveDataPs4.cpp | 93 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 core/libs/tests/GuestSaveDataPs4.cpp diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 42ebeecd4..163948431 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -17,4 +17,14 @@ if(BUILD_TESTING) set_tests_properties(guest_savedata_delete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_savedata_ps4_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataPs4.cpp) + target_include_directories(guest_savedata_ps4_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_ps4_tests) + add_test(NAME guest_savedata_ps4 COMMAND guest_savedata_ps4_tests) + if(WIN32) + set_tests_properties(guest_savedata_ps4 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 5c262309c..f0b798f5d 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -699,16 +699,24 @@ int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_ return rc; } - -APS5_EXPORT("RjMlsR8EXrw", sceSaveDataUnknown00); -int APS5_VABI sceSaveDataUnknown00(void) { - NotImplemented_nid_no_patch("RjMlsR8EXrw"); - return 0; +int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { + const int rc = transferringMount(mount, mount_result); + SAVEDATA_TRACE("transferringMountPs4 -> 0x%08x", static_cast(rc)); + return rc; } -APS5_EXPORT("X4MYzukPc3g", sceSaveDataUnknown01); -int APS5_VABI sceSaveDataUnknown01(void) { - NotImplemented_nid_no_patch("X4MYzukPc3g"); - return 0; +static int dirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + if (cond == nullptr || result == nullptr) { + throw std::runtime_error("sceSaveDataDirNameSearchPs4: null argument"); + } + result->hit_num = 0; + result->set_num = 0; + return SAVE_DATA_OK; +} + +int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + const int rc = dirNameSearchPs4(cond, result); + SAVEDATA_TRACE("dirNameSearchPs4 user=%d -> 0x%08x", cond->user_id, static_cast(rc)); + return rc; } } diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp new file mode 100644 index 000000000..39e7b14d1 --- /dev/null +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -0,0 +1,93 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); + +namespace { + +constexpr int SaveDataErrorNotFound = -2137063416; + +int failures = 0; + +void Check(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "savedata ps4 check failed: %s\n", what.c_str()); + ++failures; +} + +template +bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestTransferringMountPs4() { + SceSaveDataTitleId title{}; + std::strcpy(title.data, "CUSA00001"); + SceSaveDataDirName dir{}; + std::strcpy(dir.data, "kept"); + SaveDataTransferringMount mount{}; + mount.user_id = 1; + mount.title_id = &title; + mount.dir_name = &dir; + SaveDataMountResult result{}; + std::memset(&result, 0xAA, sizeof(result)); + Check(sceSaveDataTransferringMountPs4(&mount, &result) == SaveDataErrorNotFound, "TransferringMountPs4 returns NOT_FOUND"); + Check(result.mount_point.data[0] == '\0', "TransferringMountPs4 reports no mount point"); +} + +void TestDirNameSearchPs4() { + SceSaveDataDirName names[4]{}; + std::memset(names, 0x5A, sizeof(names)); + SaveDataDirNameSearchCond cond{}; + cond.user_id = 1; + SaveDataDirNameSearchResult result{}; + result.hit_num = 7; + result.set_num = 7; + result.dir_names = names; + result.dir_names_num = 4; + + Check(sceSaveDataDirNameSearch(&cond, &result) == 0 && result.hit_num == 1 && result.set_num == 1, "the PS5 search finds the PS5 save"); + + std::memset(names, 0x5A, sizeof(names)); + result.hit_num = 7; + result.set_num = 7; + Check(sceSaveDataDirNameSearchPs4(&cond, &result) == 0, "DirNameSearchPs4 succeeds"); + Check(result.hit_num == 0, "DirNameSearchPs4 writes zero hits"); + Check(result.set_num == 0, "DirNameSearchPs4 writes zero set entries"); + Check(static_cast(names[0].data[0]) == 0x5A, "DirNameSearchPs4 leaves the name buffer untouched"); + + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(nullptr, &result); }), "null cond throws"); + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-ps4-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root / "_sd" / "kept"); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + + TestTransferringMountPs4(); + TestDirNameSearchPs4(); + + std::filesystem::current_path(previous); + std::error_code error; + std::filesystem::remove_all(root, error); + if (failures != 0) return 1; + std::printf("savedata ps4 tests passed\n"); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..caecaa8c2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -31,6 +31,7 @@ Throughout the project, every function at every stage either **does exactly what - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` +- [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name From fbf0099790a00123e19fdb2ace5eed1972aad4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:49:33 +0200 Subject: [PATCH 46/55] feat(libc): implement std::_Xrange_error and the range_error type Co-Authored-By: Claude --- core/libs/prx/libc/Export.cpp | 6 ------ core/libs/prx/libc/src/exception/StandardExceptions.cpp | 7 +++++++ core/libs/tests/ExceptionRuntime.cpp | 8 ++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..5e68aa41a 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -30,12 +30,6 @@ extern "C" { return 0; } -APS5_EXPORT("Pu0Ecyk-7FU", libcUnknown_Pu0Ecyk_M7FU); -int APS5_VABI libcUnknown_Pu0Ecyk_M7FU() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - // Dead import of Cyberpunk 2077 (PPSA04029): no call sites, but the // Windows loader resolves imports strictly, so it must be present. APS5_EXPORT("u2tMGOLaqnE", libcUnknown_u2tMGOLaqnE); diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 353538a5f..16da81ad5 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -267,6 +267,12 @@ void APS5_VABI _ZNSt11regex_errorC1ERKS__nid_postfix(LibcException::ExceptionObj void APS5_VABI _ZNSt11regex_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt11regex_error_nid_postfix); } LibcException::ExceptionObject* APS5_VABI _ZNSt11regex_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { return LibcException::Assign(self, source); } +LibcException::TypeRecord _ZTISt11range_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11range_error", &_ZTISt13runtime_error_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt11range_error_nid_postfix {0, &_ZTISt11range_error_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; +void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); } + LibcException::TypeRecord _ZTINSt8ios_base7failureE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt8ios_base7failureE", &_ZTISt12system_error_nid_postfix}; LibcException::ExceptionVtable _ZTVNSt8ios_base7failureE_nid_postfix {0, &_ZTINSt8ios_base7failureE_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } @@ -281,6 +287,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12out_of_range_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12length_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt18_Xinvalid_argumentPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt16invalid_argument_nid_postfix, message); } +[[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt11range_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 2e50cd0ba..bb8df2c16 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -17,6 +17,7 @@ extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); +extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); @@ -190,6 +191,13 @@ int main() { try { throw Virtual(); } catch (Base& value) { assert(value.value == 7); } try { _ZSt14_Xout_of_rangePKc_nid_postfix("test message"); } catch (const std::logic_error& value) { assert(std::strcmp(value.what(), "test message") == 0); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("range message"); } + catch (const std::range_error& value) { if (std::strcmp(value.what(), "range message") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix(nullptr); } + catch (const std::runtime_error& value) { if (std::strcmp(value.what(), "") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("not a logic error"); } + catch (const std::logic_error&) { std::abort(); } + catch (const std::range_error&) {} try { __cxa_bad_cast_nid_postfix(); } catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } From afe2b14a846b69e4ec4645cbd0f4321517bc20a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:53:12 +0200 Subject: [PATCH 47/55] feat(libc): implement div Co-Authored-By: Claude --- core/libs/prx/libc/Export.cpp | 6 ------ core/libs/prx/libc/src/Strings.cpp | 4 ++++ core/libs/tests/GuestMath.cpp | 9 +++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..e1eaa4de0 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -70,12 +70,6 @@ int APS5_VABI libcCyberUnknown19(void) { } -APS5_EXPORT("2gbcltk3swE", libcCyberUnknown00); -std::uint64_t APS5_VABI libcCyberUnknown00(void) { - NotImplemented_nid_no_patch("2gbcltk3swE"); - return 0; -} - APS5_EXPORT("DiGVep5yB5w", libcCyberUnknown01); std::uint64_t APS5_VABI libcCyberUnknown01(void) { NotImplemented_nid_no_patch("DiGVep5yB5w"); diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 5bcad4d2a..b6264e15f 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -114,6 +114,10 @@ int APS5_VABI atoi_nid_postfix(const char* str) { return std::atoi(str); } +std::div_t APS5_VABI div_nid_postfix(int numerator, int denominator) { + return std::div(numerator, denominator); +} + const wchar_t* APS5_VABI wmemchr_nid_postfix(const wchar_t* s, wchar_t c, size_t n) { return std::wmemchr(s, c, n); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..0a00a3de3 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -24,6 +24,7 @@ float APS5_VABI scalbnf_nid_postfix(float, int); double APS5_VABI frexp_nid_postfix(double, int*); float APS5_VABI frexpf_nid_postfix(float, int*); std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); std::int64_t APS5_VABI lroundf_nid_postfix(float); std::int64_t APS5_VABI llround_nid_postfix(double); int APS5_VABI __isfinitef_nid_postfix(float); @@ -156,4 +157,12 @@ int main() { Require(__isnormalf_nid_postfix(std::numeric_limits::denorm_min()) == 0); Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0); Require(__isnormal_nid_postfix(std::numeric_limits::denorm_min()) == 0); + const auto quotient = div_nid_postfix(7, 2); + Require(quotient.quot == 3 && quotient.rem == 1); + const auto negativeNumerator = div_nid_postfix(-7, 2); + Require(negativeNumerator.quot == -3 && negativeNumerator.rem == -1); + const auto negativeDenominator = div_nid_postfix(7, -2); + Require(negativeDenominator.quot == -3 && negativeDenominator.rem == 1); + const auto minimum = div_nid_postfix(std::numeric_limits::min(), 10); + Require(minimum.quot == -214748364 && minimum.rem == -8); } From 85f16c56ee1e3610bc8bdf7ca64a57c1ed2f6d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:55:41 +0200 Subject: [PATCH 48/55] fix(libkernel): name five sceCoredump stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libkernel/System/src/Coredump.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 710d956cd..27efa3e65 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_ } -APS5_EXPORT("5nc2gdLNsok", sceCoredumpUnknown00); -int APS5_VABI sceCoredumpUnknown00(void) { +int APS5_VABI sceCoredumpAttachUserFile(void) { NotImplemented_nid_no_patch("5nc2gdLNsok"); return 0; } @@ -43,26 +42,22 @@ int APS5_VABI sceCoredumpUnknown01(void) { return 0; } -APS5_EXPORT("MEJ7tc7ThwM", sceCoredumpUnknown02); -int APS5_VABI sceCoredumpUnknown02(void) { +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(void) { NotImplemented_nid_no_patch("MEJ7tc7ThwM"); return 0; } -APS5_EXPORT("Uxqkdta7wEg", sceCoredumpUnknown03); -int APS5_VABI sceCoredumpUnknown03(void) { +int APS5_VABI sceCoredumpSetUserDataType(void) { NotImplemented_nid_no_patch("Uxqkdta7wEg"); return 0; } -APS5_EXPORT("dei8oUx6DbU", sceCoredumpUnknown04); -int APS5_VABI sceCoredumpUnknown04(void) { +int APS5_VABI sceCoredumpDebugTextOut(void) { NotImplemented_nid_no_patch("dei8oUx6DbU"); return 0; } -APS5_EXPORT("kK0DUW1Ukgc", sceCoredumpUnknown05); -int APS5_VABI sceCoredumpUnknown05(void) { +int APS5_VABI sceCoredumpGetStopInfoCpu(void) { NotImplemented_nid_no_patch("kK0DUW1Ukgc"); return 0; } From 6d81cce0dbd628483c6ac4f4e4a47b6d454dec29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:58:20 +0200 Subject: [PATCH 49/55] fix(libkernel): name three Unimplemented stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libkernel/Unimplemented.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index c969fc55e..96466573a 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -10,14 +10,12 @@ int APS5_VABI sceCoredumpWriteUserData() { return 0; } -APS5_EXPORT("vNe1w4diLCs", sceKernelUnknown00); -int APS5_VABI sceKernelUnknown00(void) { +int APS5_VABI __tls_get_addr_nid_postfix(void) { NotImplemented_nid_no_patch("vNe1w4diLCs"); return 0; } -APS5_EXPORT("teiItL2boFw", sceKernelUnknown01); -int APS5_VABI sceKernelUnknown01(void) { +int APS5_VABI sceKernelReleaseFlexibleMemory(void) { NotImplemented_nid_no_patch("teiItL2boFw"); return 0; } @@ -25,8 +23,7 @@ int APS5_VABI sceKernelUnknown01(void) { // Canonical lib is libScePosix (dead import of Cyberpunk 2077): 35 of its // 36 sibling imports resolve to libkernel, and libScePosix is not in the // game's NEEDED list so only a NEEDED module can satisfy the loader here. -APS5_EXPORT("0D4-FVvEikw", sceKernelUnknown02); -int APS5_VABI sceKernelUnknown02(void) { +int APS5_VABI pthread_cancel_nid_postfix(void) { NotImplemented_nid_no_patch("0D4-FVvEikw"); return 0; } From 357fd7e8063a7fa6263e7a5b8563ab911eca108c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:01:01 +0200 Subject: [PATCH 50/55] fix(agc): name nine Unimplemented stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libSceAgc/Unimplemented.cpp | 27 ++++++++--------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..94217d10f 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -84,8 +84,7 @@ int APS5_VABI sceAgcSetAmmSemaphoreMemory() { return 0; } -APS5_EXPORT("6nths4DHNrs", sceAgcUnknown_6nths4DHNrs); -int APS5_VABI sceAgcUnknown_6nths4DHNrs() { +int APS5_VABI sceAgcCbMemsetExclusive() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -96,38 +95,32 @@ int APS5_VABI sceAgcUnknown_7Wa3aeJgeVU() { return 0; } -APS5_EXPORT("EJBA4dbmvfg", sceAgcUnknown_EJBA4dbmvfg); -int APS5_VABI sceAgcUnknown_EJBA4dbmvfg() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICkECTBxrMw", sceAgcUnknown_ICkECTBxrMw); -int APS5_VABI sceAgcUnknown_ICkECTBxrMw() { +int APS5_VABI sceAgcGetGsPrimPayload() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("RTpj-tIlvZc", sceAgcUnknown_RTpj_MtIlvZc); -int APS5_VABI sceAgcUnknown_RTpj_MtIlvZc() { +int APS5_VABI sceAgcSetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("SwI6QxqwAC0", sceAgcUnknown_SwI6QxqwAC0); -int APS5_VABI sceAgcUnknown_SwI6QxqwAC0() { +int APS5_VABI sceAgcGetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("d4NZIlguzv0", sceAgcUnknown_d4NZIlguzv0); -int APS5_VABI sceAgcUnknown_d4NZIlguzv0() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("eAy8eGNsCuU", sceAgcUnknown_eAy8eGNsCuU); -int APS5_VABI sceAgcUnknown_eAy8eGNsCuU() { +int APS5_VABI sceAgcWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -138,14 +131,12 @@ int APS5_VABI sceAgcUnknown_rP5xLdOf26k() { return 0; } -APS5_EXPORT("tmy-+rBpspY", sceAgcUnknown_tmy_M_PrBpspY); -int APS5_VABI sceAgcUnknown_tmy_M_PrBpspY() { +int APS5_VABI sceAgcWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("y5K5tPktiL8", sceAgcUnknown_y5K5tPktiL8); -int APS5_VABI sceAgcUnknown_y5K5tPktiL8() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } From ea65b7e5bec315ef7bf99ffe1894f38a06548aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:03:12 +0200 Subject: [PATCH 51/55] fix(agcdriver): name the four SubmitValidation stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libSceAgcDriver/Unimplemented.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index 8166a546f..775fcced3 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -85,26 +85,22 @@ int APS5_VABI sceAgcDriverUnregisterWorkloadStream() { return 0; } -APS5_EXPORT("+iAOE3jCnkk", sceAgcDriverUnknown__PiAOE3jCnkk); -int APS5_VABI sceAgcDriverUnknown__PiAOE3jCnkk() { +int APS5_VABI sceAgcDriverSetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICaGtkEIXTk", sceAgcDriverUnknown_ICaGtkEIXTk); -int APS5_VABI sceAgcDriverUnknown_ICaGtkEIXTk() { +int APS5_VABI sceAgcDriverGetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("WHIOMbb+iIU", sceAgcDriverUnknown_WHIOMbb_PiIU); -int APS5_VABI sceAgcDriverUnknown_WHIOMbb_PiIU() { +int APS5_VABI sceAgcDriverGetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("chJWZcNSzjk", sceAgcDriverUnknown_chJWZcNSzjk); -int APS5_VABI sceAgcDriverUnknown_chJWZcNSzjk() { +int APS5_VABI sceAgcDriverSetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } From 68b71c971a94c5a2639f8f91d07abd7c456d2a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:10:34 +0200 Subject: [PATCH 52/55] feat(libc): implement _FDtest, _FInf and _FNan Co-Authored-By: Claude --- core/libs/prx/libc/src/Math.cpp | 18 ++++++++++++++++++ core/libs/tests/GuestMath.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..425a9edaf 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "prx/libc/include/General.hpp" @@ -78,6 +80,22 @@ double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); } float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); } + +struct alignas(16) LibcFloatConstant { std::uint32_t bits[4]; }; +LibcFloatConstant _FInf_nid_postfix {{0x7f800000u, 0, 0, 0}}; +LibcFloatConstant _FNan_nid_postfix {{0x7fc00000u, 0, 0, 0}}; + +short APS5_VABI _FDtest_nid_postfix(const float* value) { + constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + if (value == nullptr) throw std::invalid_argument("_FDtest: null value"); + std::uint32_t bits; + std::memcpy(&bits, value, sizeof(bits)); + const auto exponent = bits & 0x7f800000u; + const auto fraction = bits & 0x007fffffu; + if (exponent == 0x7f800000u) return fraction != 0 ? NotANumber : Infinite; + if (exponent == 0) return fraction != 0 ? Denormal : Zero; + return Finite; +} int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..8b8e10729 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include extern "C" { double APS5_VABI atof_nid_postfix(const char*); @@ -11,6 +12,10 @@ long double APS5_VABI strtold_nid_postfix(const char*, char**); std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); +struct LibcFloatConstant { std::uint32_t bits[4]; }; +extern LibcFloatConstant _FInf_nid_postfix; +extern LibcFloatConstant _FNan_nid_postfix; +short APS5_VABI _FDtest_nid_postfix(const float*); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); float APS5_VABI acosf_nid_postfix(float); @@ -113,7 +118,28 @@ static void CheckIntegerConversions() { *__error_nid_postfix() = 0; } +static void CheckFloatClassification() { + Require(_FInf_nid_postfix.bits[0] == 0x7f800000u && _FNan_nid_postfix.bits[0] == 0x7fc00000u); + for (int word = 1; word < 4; ++word) Require(_FInf_nid_postfix.bits[word] == 0 && _FNan_nid_postfix.bits[word] == 0); + const struct { std::uint32_t bits; short code; } cases[] = { + {0x00000000u, 0}, {0x80000000u, 0}, {0x00000001u, -2}, {0x807fffffu, -2}, {0x00800000u, -1}, + {0xbf800000u, -1}, {0x7f7fffffu, -1}, {0x7f800000u, 1}, {0xff800000u, 1}, {0x7f800001u, 2}, + {0x7fc00000u, 2}, {0xff810000u, 2}, {0x7f810000u, 2}, + }; + for (const auto& test : cases) { + float value; + std::memcpy(&value, &test.bits, sizeof(value)); + if (_FDtest_nid_postfix(&value) != test.code) { + std::fprintf(stderr, "Guest _FDtest failed for %08x\n", test.bits); + std::abort(); + } + } + Require(_FDtest_nid_postfix(reinterpret_cast(&_FInf_nid_postfix)) == 1); + Require(_FDtest_nid_postfix(reinterpret_cast(&_FNan_nid_postfix)) == 2); +} + int main() { + CheckFloatClassification(); CheckIntegerConversions(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; From 3f2e5150c26cc5716e5e765fa2361ce6ccf956f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:15:28 +0200 Subject: [PATCH 53/55] feat(libc): implement _Xtime_get_ticks Co-Authored-By: Claude --- core/libs/prx/libc/Export.cpp | 6 ------ core/libs/prx/libc/src/Time.cpp | 13 +++++++++++++ core/libs/tests/GuestFreebsdClocks.cpp | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..01f9abc13 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -106,12 +106,6 @@ std::uint64_t APS5_VABI libcCyberUnknown05(void) { return 0; } -APS5_EXPORT("Cj+Fw5q1tUo", libcCyberUnknown06); -std::uint64_t APS5_VABI libcCyberUnknown06(void) { - NotImplemented_nid_no_patch("Cj+Fw5q1tUo"); - return 0; -} - APS5_EXPORT("CyXs2l-1kNA", libcCyberUnknown07); std::uint64_t APS5_VABI libcCyberUnknown07(void) { NotImplemented_nid_no_patch("CyXs2l-1kNA"); diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 64bc20ddb..53199eed7 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -21,6 +21,19 @@ int64_t APS5_VABI time_nid_postfix(int64_t* timer) { return libc_time_nid_postfix(timer); } +int64_t APS5_VABI _Xtime_get_ticks_nid_postfix() { +#ifdef _WIN32 + FILETIME ft{}; + GetSystemTimePreciseAsFileTime(&ft); + const uint64_t t = ((static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ULL; + return static_cast(t / 10); +#else + timespec now{}; + clock_gettime(CLOCK_REALTIME, &now); + return static_cast(now.tv_sec) * 1000000 + now.tv_nsec / 1000; +#endif +} + double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0) { return std::difftime(static_cast(time1), static_cast(time0)); } diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 6c7f959f0..75a69daa6 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -8,6 +8,8 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); +int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz); +std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix(); } static constexpr int SCE_OK = 0; @@ -120,6 +122,21 @@ static void IdleProcessAccumulatesNone() { Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS); } +static std::int64_t Microseconds() { + KernelTimeval time{-1, -1}; + Require(gettimeofday_nid_postfix(&time, nullptr) == SCE_OK); + return time.tv_sec * 1000000LL + time.tv_usec; +} + +static void XtimeTicksAreGettimeofdayMicroseconds() { + for (int i = 0; i < 100; ++i) { + const std::int64_t before = Microseconds(); + const std::int64_t ticks = _Xtime_get_ticks_nid_postfix(); + const std::int64_t after = Microseconds(); + Require(before <= ticks && ticks <= after); + } +} + int main() { SecondClockReportsWholeSeconds(); @@ -135,4 +152,5 @@ int main() { ProcessClocksAreOrdered(); BusyProcessAccumulatesUserTime(); IdleProcessAccumulatesNone(); + XtimeTicksAreGettimeofdayMicroseconds(); } From 75e2e7252e88bff82a64af99e5537f5302801487 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:51:40 +0200 Subject: [PATCH 54/55] fix(agc): log the Vulkan features after tessellation is enabled The device constructor printed "Vulkan features tessellationAvailable" before it read VkPhysicalDeviceFeatures and set tessellationShader, so the line reported 0 on every GPU, including ones that enable the feature. The rect-list and tessellation paths already read the enabled value; only the diagnostic was wrong, and it suggested that tessellation was unavailable. Print the line right after the feature is enabled. The mesh, depth clip and unrestricted depth range values it also reports are set earlier and do not change. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 98d526ccb..b040bbb41 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -803,7 +803,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u state->capabilities.push_back(5283); state->spirvExtensions.push_back("SPV_EXT_mesh_shader"); } - APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); const float priority = 1.0f; VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; queueInfo.queueFamilyIndex = family; @@ -824,6 +823,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u enabled.tessellationShader = available.tessellationShader; state->tessellationShader = enabled.tessellationShader == VK_TRUE; if (state->tessellationShader) state->capabilities.push_back(3); + APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); require(available.samplerAnisotropy && available.textureCompressionBC, "device lacks sampler anisotropy or BC texture compression support required for texture sampling"); enabled.samplerAnisotropy = VK_TRUE; enabled.textureCompressionBC = VK_TRUE; From 55f548d6bcf3cc8c1bc632d4604a52cb901dc1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:28:41 +0200 Subject: [PATCH 55/55] feat(libc): implement wcsrtombs_s Co-Authored-By: Claude --- core/libs/prx/libc/CMakeLists.txt | 9 ++++ core/libs/prx/libc/Export.cpp | 6 --- core/libs/prx/libc/src/Multibyte.cpp | 73 ++++++++++++++++++++++++++ core/libs/tests/GuestWcsrtombsS.cpp | 78 ++++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 core/libs/tests/GuestWcsrtombsS.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 070d85afa..f5736578e 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -176,6 +176,15 @@ if(BUILD_TESTING) set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wcsrtombs_s_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcsrtombsS.cpp) + target_include_directories(guest_wcsrtombs_s_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wcsrtombs_s_tests PRIVATE libc) + configure_windows_unwind(guest_wcsrtombs_s_tests) + add_test(NAME guest_wcsrtombs_s COMMAND guest_wcsrtombs_s_tests) + if(WIN32) + set_tests_properties(guest_wcsrtombs_s PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(UNIX) add_test_executable(exception_runtime_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionRuntime.cpp) target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..0c1fcb59f 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -94,12 +94,6 @@ std::uint64_t APS5_VABI libcCyberUnknown03(void) { return 0; } -APS5_EXPORT("79s2tnYQI6I", libcCyberUnknown04); -std::uint64_t APS5_VABI libcCyberUnknown04(void) { - NotImplemented_nid_no_patch("79s2tnYQI6I"); - return 0; -} - APS5_EXPORT("7yMFgcS8EPA", libcCyberUnknown05); std::uint64_t APS5_VABI libcCyberUnknown05(void) { NotImplemented_nid_no_patch("7yMFgcS8EPA"); diff --git a/core/libs/prx/libc/src/Multibyte.cpp b/core/libs/prx/libc/src/Multibyte.cpp index 0ea78acb3..c521ced22 100644 --- a/core/libs/prx/libc/src/Multibyte.cpp +++ b/core/libs/prx/libc/src/Multibyte.cpp @@ -3,6 +3,7 @@ #include #include #include +#include extern "C" { @@ -47,6 +48,78 @@ std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value return 1; } +int APS5_VABI wcsrtombs_s_nid_postfix( + std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, + void* state +) { + constexpr int GuestEinval = 22; + constexpr int GuestErange = 34; + constexpr int GuestEilseq = 86; + constexpr auto Failed = static_cast(-1); + if (!result || !source || !state || !*source || (destination && (static_cast(capacity) <= 0 + || static_cast(limit) < 0))) { + if (destination && static_cast(capacity) > 0) destination[0] = '\0'; + if (result) *result = Failed; + return GuestEinval; + } + if (!destination && capacity) { + *result = Failed; + return GuestEinval; + } + const auto* wide = *source; + char bytes[MB_LEN_MAX]; + if (!destination) { + std::size_t total = 0; + for (;; ++wide) { + const auto length = wcrtomb_nid_postfix(bytes, *wide, state); + if (length == Failed) { + *result = Failed; + return GuestEilseq; + } + if (length && bytes[length - 1] == '\0') { + *result = total + length - 1; + return 0; + } + total += length; + } + } + std::size_t written = 0; + while (limit) { + const auto length = wcrtomb_nid_postfix(bytes, *wide, state); + if (length == Failed) { + destination[written] = '\0'; + *result = Failed; + return GuestEilseq; + } + if (length > limit || length > capacity) { + *source = wide; + destination[0] = '\0'; + *result = Failed; + return GuestErange; + } + std::memcpy(destination + written, bytes, length); + written += length; + if (length && destination[written - 1] == '\0') { + *source = nullptr; + *result = written - 1; + return 0; + } + ++wide; + capacity -= length; + if (!capacity) { + *source = wide; + destination[0] = '\0'; + *result = Failed; + return GuestErange; + } + limit -= length; + } + *source = wide; + destination[written] = '\0'; + *result = written; + return 0; +} + std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void*) { if (!destination) return std::strlen(*source); std::size_t converted = 0; diff --git a/core/libs/tests/GuestWcsrtombsS.cpp b/core/libs/tests/GuestWcsrtombsS.cpp new file mode 100644 index 000000000..758ff5d33 --- /dev/null +++ b/core/libs/tests/GuestWcsrtombsS.cpp @@ -0,0 +1,78 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" int APS5_VABI wcsrtombs_s_nid_postfix( + std::size_t*, char*, std::size_t, const std::uint16_t**, std::size_t, void*); +static void Require(bool value) { if (!value) std::abort(); } + +constexpr auto Failed = static_cast(-1); +constexpr auto Huge = std::size_t{1} << 63; +static const std::uint16_t text[] = {'a', 'b', 'c', 0}; +static const std::uint16_t wide[] = {'a', 0x100, 'b', 0}; + +struct Run { + int status; + std::size_t result = 7; + const std::uint16_t* source; + char out[8]; +}; + +static Run Convert(const std::uint16_t* input, std::size_t capacity, std::size_t limit, bool toBuffer = true) { + Run run{}; + std::memset(run.out, 'x', sizeof(run.out)); + run.source = input; + std::uint64_t state[2] = {}; + run.status = wcsrtombs_s_nid_postfix(&run.result, toBuffer ? run.out : nullptr, capacity, &run.source, limit, state); + return run; +} + +int main() { + auto run = Convert(text, 0, 0, false); + Require(run.status == 0 && run.result == 3 && run.source == text); + run = Convert(wide, 0, Huge, false); + Require(run.status == 86 && run.result == Failed && run.source == wide); + run = Convert(text, 5, 1, false); + Require(run.status == 22 && run.result == Failed && run.source == text); + + run = Convert(text, 8, 8); + Require(run.status == 0 && run.result == 3 && run.source == nullptr && std::memcmp(run.out, "abc\0x", 5) == 0); + run = Convert(text, 4, 4); + Require(run.status == 0 && run.result == 3 && run.source == nullptr && std::memcmp(run.out, "abc\0x", 5) == 0); + run = Convert(text, 8, 2); + Require(run.status == 0 && run.result == 2 && run.source == text + 2 && std::memcmp(run.out, "ab\0x", 4) == 0); + run = Convert(text, 8, 0); + Require(run.status == 0 && run.result == 0 && run.source == text && std::memcmp(run.out, "\0x", 2) == 0); + + run = Convert(text, 3, 8); + Require(run.status == 34 && run.result == Failed && run.source == text + 3 && std::memcmp(run.out, "\0bcx", 4) == 0); + run = Convert(text, 3, 3); + Require(run.status == 34 && run.result == Failed && run.source == text + 3 && std::memcmp(run.out, "\0bcx", 4) == 0); + run = Convert(text, 1, 8); + Require(run.status == 34 && run.result == Failed && run.source == text + 1 && std::memcmp(run.out, "\0x", 2) == 0); + + run = Convert(wide, 8, 8); + Require(run.status == 86 && run.result == Failed && run.source == wide && std::memcmp(run.out, "a\0xx", 4) == 0); + + run = Convert(text, 0, 8); + Require(run.status == 22 && run.result == Failed && run.source == text && run.out[0] == 'x'); + run = Convert(text, Huge, 8); + Require(run.status == 22 && run.result == Failed && run.out[0] == 'x'); + run = Convert(text, 8, Huge); + Require(run.status == 22 && run.result == Failed && run.source == text && run.out[0] == '\0'); + run = Convert(nullptr, 8, 8); + Require(run.status == 22 && run.result == Failed && run.out[0] == '\0'); + + char out[4] = {'x', 'x', 'x', 'x'}; + std::size_t result = 7; + const std::uint16_t* source = text; + std::uint64_t state[2] = {}; + Require(wcsrtombs_s_nid_postfix(nullptr, out, 4, &source, 4, state) == 22 && out[0] == '\0' && source == text); + out[0] = 'x'; + Require(wcsrtombs_s_nid_postfix(&result, out, 4, nullptr, 4, state) == 22 && result == Failed && out[0] == '\0'); + out[0] = 'x'; + result = 7; + Require(wcsrtombs_s_nid_postfix(&result, out, 4, &source, 4, nullptr) == 22 && result == Failed && out[0] == '\0'); + Require(source == text && std::memcmp(out, "\0xxx", 4) == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..c178995dd 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -104,4 +104,5 @@ Throughout the project, every function at every stage either **does exactly what - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. +- [wcsrtombs_s](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached - [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped