Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2478,7 +2478,12 @@ std::size_t ShaderResources::addGuestBuffer(std::span<const std::uint32_t> words
Require(descriptor.Type() == 0u, "buffer descriptor uses an unsupported type");
const auto address = descriptor.Base48();
const auto byteSize = descriptor.GetSize();
if (byteSize == 0 || address == 0) {
const auto describesBuffer = [&] {
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(address), 1)) return false;
const auto limit = context.limits.maxStorageBufferRange;
return byteSize <= limit || GuestMemory::Accessible(reinterpret_cast<const void*>(address), static_cast<std::size_t>(limit) + 1u);
};
if (byteSize == 0 || address == 0 || !describesBuffer()) {
allocations.push_back({0, EmptyBufferBytes, false, nullptr, ShaderRecompiler::DescriptorRole::GuestBuffers, false});
return allocations.size() - 1;
}
Expand Down
18 changes: 18 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,8 @@ PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
return it == table.end() ? nullptr : it->second;
}

const VkBuffer MockEmptyBuffer = reinterpret_cast<VkBuffer>(std::uintptr_t{0xe0e0});

AgcDriver::Graphics::Context mockContext() {
AgcDriver::Graphics::Context context{};
context.deviceProc = mockProc;
Expand All @@ -1446,6 +1448,7 @@ AgcDriver::Graphics::Context mockContext() {
context.limits.maxPerStageDescriptorStorageBuffers = 16;
context.limits.maxPerStageResources = 128;
context.limits.maxDescriptorSetStorageBuffers = 32;
context.emptyBuffer = MockEmptyBuffer;
return context;
}

Expand Down Expand Up @@ -1679,9 +1682,24 @@ void resourceTests() {
expectSingleFailure(changed([](auto& binding) { binding.role = Role::FlattenedSrt; binding.guestDescriptor.clear(); }), "empty shader data descriptor");
expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[1] |= 0x40000000u; }), "reserved bits");
expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[3] |= 0x40000000u; }), "unsupported type");
{
ShaderRecompiler::RecompileResult vertex;
ShaderRecompiler::RecompileResult fragment;
vertex.bindings.push_back(makeBinding(Role::GuestBuffers, 0, 2, join(vsharp(reinterpret_cast<const void*>(0x2000), 8), vsharp(reinterpret_cast<const void*>(0x1000), 8))));
mock = MockVulkan{};
const auto nullContext = mockContext();
{
AgcDriver::Graphics::ShaderResources resources(nullContext, vertex, fragment, state.color, 0, 0);
const auto& write = findWrite(0);
Require(write.buffers.size() == 2 && write.buffers[0].buffer == MockEmptyBuffer && write.buffers[1].buffer == MockEmptyBuffer, "unreachable V#s did not bind the placeholder buffer");
}
Require(mock.live == 0, "null V# resources leaked Vulkan objects");
}
expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[1] |= 0x3fffu << 16u; binding.guestDescriptor[2] = 0xffffffffu; }), "descriptor range limit");
expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[2] = 32768; }), "descriptor range limit");
expectSingleAccepted(changed([](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast<const void*>(0x1000), 8); }), "an unmapped V#");
expectSingleAccepted(changed([](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast<const void*>(0x1000), 8); binding.guestDescriptor[1] |= 0x3fffu << 16u; binding.guestDescriptor[2] = 0xffffffffu; }), "an unmapped V# beyond the descriptor range limit");
expectSingleAccepted(changed([](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast<const void*>(0xfffff0000000ull), 8); }), "a V# outside the user address space");
expectSingleFailure(changed([&](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast<const void*>(state.color.address), 64); }), "aliases the render target");
expectSingleAccepted(changed([](auto& binding) { binding.count = 3; binding.guestDescriptor = join(join(vsharp(guestFirst.data(), 16), vsharp(guestSecond.data(), 32)), vsharp(reinterpret_cast<const void*>(0x1000), 8)); }), "an unmapped V# element");
expectSingleFailure(changed([&](auto& binding) { binding.count = 2; binding.guestDescriptor = join(vsharp(guestFirst.data(), 16), vsharp(reinterpret_cast<const void*>(state.color.address), 64)); }), "aliases the render target");
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ Throughout the project, every function at every stage either **does exactly what
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
- [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.
- [Per-stage descriptor count](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a pixel shader's colour attachments are not counted against `maxPerStageResources`, so its buffers and images can fit the limit while the pipeline exceeds it
- [Unreachable buffer V#s](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) read as null, as invalid image descriptors do: a base address with no readable page, or a size over `maxStorageBufferRange` over memory that is not readable that far. PPSA29141's shaders declare SRT slots they do not reach on the path taken, and those keep unrelated words (pointers, terabyte sizes over host memory). A sparse heap bound whole whose first page is uncommitted also reads as null rather than through the sparse path
- [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.
- 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 data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
Expand Down
Loading