diff --git a/CMakeLists.txt b/CMakeLists.txt index 9236f55fb..4f75e9677 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,10 +34,8 @@ set(ANYPS5_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "") add_subdirectory(3rdparty/SDL2) set(CMAKE_EXE_LINKER_FLAGS "${ANYPS5_EXE_LINKER_FLAGS}") -# Prebuilt FFmpeg (AJM's MP3 decoder), downloaded for the submodule's commit as in Kyty. set(ARCHITECTURE x86_64) add_subdirectory(3rdparty/ffmpeg-core) -# The system libraries its pkg-config files name, which ffmpeg-core adds only for MSVC. target_link_libraries(ffmpeg INTERFACE bcrypt ole32 user32) set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE) diff --git a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp index f8ed2f09c..29b68fe01 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp @@ -17,11 +17,11 @@ std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, st } uint32_t APS5_VABI sceAgcCbSetShRegisterRangeDirectGetSize(uint32_t num_values) { - return (num_values + 2u) * 4u; // header + offset + values + return (num_values + 2u) * 4u; } std::uint32_t APS5_VABI sceAgcCbSetUcRegisterRangeDirectGetSize(std::uint32_t numValues) { - return (numValues + 2u) * 4u; // header + offset + values + return (numValues + 2u) * 4u; } std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) { diff --git a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp index 05d5395f8..ad70ced12 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp @@ -15,7 +15,7 @@ std::uint32_t* APS5_VABI sceAgcCbNop_nid_postfix(CommandBuffer* buf, std::uint32 } uint32_t APS5_VABI sceAgcCbNopGetSize(uint32_t size_in_dwords) { - return size_in_dwords * 4u; // a NOP packet is exactly size_in_dwords dwords including its header + return size_in_dwords * 4u; } std::uint32_t APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize() { diff --git a/core/libs/prx/libSceAgc/Command/src/Control.cpp b/core/libs/prx/libSceAgc/Command/src/Control.cpp index 9906cd432..e203a62d1 100644 --- a/core/libs/prx/libSceAgc/Command/src/Control.cpp +++ b/core/libs/prx/libSceAgc/Command/src/Control.cpp @@ -7,9 +7,7 @@ std::uint32_t* WriteJump(CommandBuffer* buffer, std::uint8_t chain, std::uint8_t CheckBits(chain, 1, function); CheckBits(cachePolicy, 3, function); CheckBits(sizeInDwords, 0xfffffu, function); - // Titles reserve a jump with a null target and write the address into the packet later. Require((guestAddress & 3u) == 0, function, "misaligned jump target"); - // INDIRECT_BUFFER: address, then size in bits 19:0, chain (no return) in bit 20, cache policy in 29:28. return Emit(buffer, 0x3fu, {static_cast(guestAddress) & ~3u, static_cast(guestAddress >> 32u), 0x0f200000u | (static_cast(cachePolicy) << 28u) | (static_cast(chain) << 20u) | sizeInDwords}, function); } diff --git a/core/libs/prx/libSceAgc/DcbDraw/src/DrawIndexed.cpp b/core/libs/prx/libSceAgc/DcbDraw/src/DrawIndexed.cpp index 6129e7174..88f863661 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/src/DrawIndexed.cpp +++ b/core/libs/prx/libSceAgc/DcbDraw/src/DrawIndexed.cpp @@ -32,7 +32,7 @@ std::uint32_t* APS5_VABI sceAgcDcbDrawIndexOffset(CommandBuffer* buf, std::uint3 } uint32_t APS5_VABI sceAgcDcbDrawIndexOffsetGetSize(void) { - return 20; // DRAW_INDEX_OFFSET_2: header + 4 dwords + return 20; } uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint64_t modifier) { diff --git a/core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp b/core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp index f96b5badd..e93ad3279 100644 --- a/core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp +++ b/core/libs/prx/libSceAgc/DcbDraw/src/IndexBuffer.cpp @@ -14,7 +14,7 @@ std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64 } std::uint32_t APS5_VABI sceAgcDcbSetIndexBufferGetSize() { - return 12; // INDEX_BASE: header + 2 dwords + return 12; } std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer* buf, std::uint32_t indexCount) { @@ -32,11 +32,9 @@ std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize(CommandBuffer* buf, std::uint8_t } std::uint32_t APS5_VABI sceAgcDcbSetIndexSizeGetSize() { - return 12; // SET_UCONFIG_REG_INDEX: header + 2 dwords + return 12; } -// Unnamed: sceAgcDcbSetIndexSize with the per-instance object id flag (VGT_INDEX_TYPE bit 14), as -// Kyty and SharpEmu build it. APS5_EXPORT("-KRzWekV120", sceAgcUnknown__MKRzWekV120); std::uint32_t* APS5_VABI sceAgcUnknown__MKRzWekV120(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy, std::uint8_t perInstanceObjectId) { Agc::Command::CheckBits(perInstanceObjectId, 1, __func__); diff --git a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp index 2e196b43d..e7271811c 100644 --- a/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp @@ -42,7 +42,7 @@ std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t ev } std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType) { - return (eventType & 0xfeu) == 0x38u ? 16 : 8; // EVENT_WRITE, plus the address for the 0x38/0x39 events + return (eventType & 0xfeu) == 0x38u ? 16 : 8; } std::uint32_t* APS5_VABI sceAgcDcbStallCommandBufferParser(CommandBuffer* buf) { diff --git a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp index 0f3cc6356..de4997d45 100644 --- a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp @@ -77,7 +77,6 @@ int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* const auto& frontRsrc2 = FindRegister(front->sh_registers, frontCount, isGs ? SPI_SHADER_PGM_RSRC2_GS : SPI_SHADER_PGM_RSRC2_HS); auto& fusedRsrc1 = FindRegister(fused, fusedCount, isGs ? SPI_SHADER_PGM_RSRC1_GS : SPI_SHADER_PGM_RSRC1_HS); auto& fusedRsrc2 = FindRegister(fused, fusedCount, isGs ? SPI_SHADER_PGM_RSRC2_GS : SPI_SHADER_PGM_RSRC2_HS); - // Both halves run in one wave, so VGPRs are the larger of the two and shared VGPRs are reallocated (after Kyty). const std::uint32_t frontVgprs = ((frontRsrc1.value & 0x3fu) + 1u) * 4u; const std::uint32_t backVgprs = ((fusedRsrc1.value & 0x3fu) + 1u) * 4u; const std::uint32_t frontTotal = frontVgprs + (frontRsrc2.value >> 28u) * 8u; @@ -94,7 +93,6 @@ int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* MergeMax(fusedRsrc1, frontRsrc1, 28, 0x3u); } fusedRsrc2.value = (fusedRsrc2.value & 0xf7ffffc1u) | (frontRsrc2.value & 0x0800003eu); - // The fused program starts at the front half; its address goes in the ES/LS program registers. const std::uint32_t lo = isGs ? SPI_SHADER_PGM_LO_ES : SPI_SHADER_PGM_LO_LS; auto& loRegister = FindRegister(fused, fusedCount, lo); auto& hiRegister = FindRegister(fused, fusedCount, lo + 1u); diff --git a/core/libs/prx/libSceAgc/Patch/src/Jump.cpp b/core/libs/prx/libSceAgc/Patch/src/Jump.cpp index 435489880..e31894dd9 100644 --- a/core/libs/prx/libSceAgc/Patch/src/Jump.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/Jump.cpp @@ -9,7 +9,6 @@ namespace { -// INDIRECT_BUFFER as sceAgcDcbJump writes it: [1..2] target, [3] size in 19:0, chain in 20, cache policy in 29:28. void PatchJump(std::uint32_t* cmd, const volatile std::uint32_t* target, std::uint32_t sizeInDwords, const char* function) { Agc::Command::ValidatePacket(cmd, 0x3fu, 4, function); const auto address = reinterpret_cast(target); @@ -29,7 +28,6 @@ int APS5_VABI sceAgcJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* t return 0; } -// Named after Kyty's reading: a jump patch that also sets the cache policy. APS5_EXPORT("Ikfdt-rIqCE", sceAgcUnknown_Ikfdt_MrIqCE); int APS5_VABI sceAgcUnknown_Ikfdt_MrIqCE(uint32_t* cmd, uint64_t cache_policy, const volatile uint32_t* target, uint32_t size_in_dwords) { Agc::Command::CheckBits(cache_policy, 3u, __func__); diff --git a/core/libs/prx/libSceAgc/Patch/src/QueueEndOfPipeAction.cpp b/core/libs/prx/libSceAgc/Patch/src/QueueEndOfPipeAction.cpp index d0fc39566..7e42fe012 100644 --- a/core/libs/prx/libSceAgc/Patch/src/QueueEndOfPipeAction.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/QueueEndOfPipeAction.cpp @@ -31,7 +31,6 @@ int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress(std::uint32_t* cmd, const v } int APS5_VABI sceAgcQueueEndOfPipeActionPatchData(uint32_t* cmd, uint64_t data) { - // RELEASE_MEM as built by sceAgcCbReleaseMem: [2] data select << 29, [5..6] data. Agc::Command::ValidatePacket(cmd, 0x49u, 8, __func__); if ((cmd[2] >> 29u) == 1u) Agc::Command::CheckBits(data, 0xffffffffu, __func__); cmd[5] = static_cast(data); diff --git a/core/libs/prx/libSceAgc/Patch/src/Rewind.cpp b/core/libs/prx/libSceAgc/Patch/src/Rewind.cpp index 26eabbc17..06dc2c9b9 100644 --- a/core/libs/prx/libSceAgc/Patch/src/Rewind.cpp +++ b/core/libs/prx/libSceAgc/Patch/src/Rewind.cpp @@ -10,8 +10,6 @@ namespace { -// The valid bit (31) releases a queue stalled on the REWIND; the release store publishes the commands -// the title wrote behind it before setting the bit. int SetRewindState(std::uint32_t* cmd, std::uint8_t state, const char* function) { Agc::Command::ValidatePacket(cmd, 0x59u, 2, function); Agc::Command::CheckBits(state, 1, function); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp index 8669b4859..e26b9c657 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp @@ -40,8 +40,6 @@ class IVideoOutput { public: virtual ~IVideoOutput() = default; virtual std::shared_ptr Reserve(const FlipInfo& info) = 0; - // Blocks a submission carrying a flip until the output's flip queue has room, as the system - // holds back a submitter that runs ahead of the display. Called without the driver's lock. virtual void WaitForFlipRoom() {} virtual std::shared_ptr CaptureRenderingWait(std::uint32_t index) { throw std::runtime_error("VideoOut: rendering waits are unsupported by this output"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index 3630973de..de0db5a3b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -203,8 +203,6 @@ struct Submission { // submission trusts only labels the recorder noted with a newer stamp (see Recorder::NoteLabel). std::uint64_t received = 0; std::chrono::steady_clock::time_point enqueuedAt{}; - // The guest commands behind a REWIND (commands stop at it): the title writes them after submitting - // and marks the REWIND valid, so they are read only then (Driver::executeRewindTail). const std::uint32_t* rewindTail = nullptr; std::size_t rewindWords = 0; }; @@ -528,17 +526,12 @@ private: public: - // Copies a submission's commands, stopping after the first REWIND: what follows it is read when the - // REWIND becomes valid. INDIRECT_BUFFER packets are followed as the CP follows them: a chained one - // (bit 20 of its size dword) replaces the rest of its buffer with the target, a call inserts the - // target and continues after the packet. static void copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) { submission.commands.clear(); std::size_t budget = std::size_t{1} << 26u; copySegment(submission, guest, words, budget); } - // Appends one command segment; true when a REWIND ended the copy. static bool 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; @@ -547,7 +540,6 @@ public: if (Pm4::FillerPacket(header)) { 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) { - // Malformed: copied as is for validate to report with its context. submission.commands.insert(submission.commands.end(), guest + cursor, guest + words); return false; } @@ -574,8 +566,6 @@ public: return false; } - // Holds the submitting thread while an output named by a flip in the submission has a full flip - // queue (mutex not held: the presenter needs the driver to finish flips). void waitForFlipRoom(const Submission& submission) { for (std::size_t cursor = 0; cursor < submission.commands.size(); cursor += Pm4::PacketWords(submission.commands[cursor])) { if (submission.commands[cursor] != FlipPacketHeader) continue; @@ -590,7 +580,6 @@ public: } } - // Takes the flip reservations and rendering waits the submission's packets name (mutex held). void reserveOutputs(Submission& submission) { for (std::size_t cursor = 0; cursor < submission.commands.size();) { const auto* words = submission.commands.data() + cursor; @@ -613,8 +602,6 @@ public: } } - // REWIND stalls the queue until the CPU marks it valid (sceAgcRewindPatchSetRewindState); the - // commands behind it are copied from guest memory only then and run as a continuation. void executeRewindTail(const Submission& stalled) { std::atomic_ref control(*const_cast(stalled.rewindTail - 1)); while ((control.load(std::memory_order_acquire) & 0x80000000u) == 0) { @@ -3250,8 +3237,6 @@ private: pendingDispatchPhases().outcome = DispatchOutcome::CopyHle; return; } - // USE_THREAD_DIMENSIONS that is no whole number of groups: the host launches whole groups, - // the partial-group variant retires the threads past the size. if (indirectArguments == 0 && (packet[4] & 0x20u) != 0) { const std::array threads{packet[1], packet[2], packet[3]}; for (std::uint32_t axis = 0; axis < 3; ++axis) { @@ -3322,7 +3307,6 @@ private: }; mix(address); mix(packet[4] & 0x8000u); - // The partial-group size is shader data of the compiled result. for (const auto threads : compute.partialThreads) mix(threads); for (const auto word : userData) mix(word); // Only the shader registers the request reads (thread counts and RSRC1/2; the program @@ -3997,9 +3981,6 @@ private: if (!drawParameters.indirect && !drawParameters.indexed && (drawParameters.indexCount == 0 || drawParameters.instanceCount == 0)) return DrawVerdict::Nothing; static const bool traceIndirect = std::getenv("APS5_TRACE_INDIRECT_DRAWS") != nullptr; if (traceIndirect && drawParameters.indirect) std::fprintf(stderr, "[draw] indirect packet %s args 0x%llx count %u reached\n", Pm4::Name(packet[0]).c_str(), static_cast(drawParameters.indirect->arguments), drawParameters.indirect->count); - // A draw without color writes or a pixel shader draws nothing, unless it tests depth or - // stencil against a bound surface: such depth-only draws need a pipeline without a - // fragment stage, which is not implemented. { const auto targetMask = queue.context.find(0x8e); const auto shaderMask = queue.context.find(0x8f); @@ -5156,13 +5137,6 @@ private: return true; } - // PIXEL_PIPE_STAT_DUMP, drained like a label: every draw before it completed, so the recorder's - // sample count is the DB counter at this point. The PS5 layout interleaves a begin and an end - // counter per DB (16 of them, 16 bytes apart; a query's begin dump writes the first of each - // pair, its end dump the second): the count goes to the first DB and the others stay at zero, - // all with bit 63 marking the result ready. - // ponytail: a GPU drain per dump; accumulate on the GPU behind the batch if titles dump many - // per frame. void dumpSampleCounters(std::uint64_t address) { std::uint64_t samples = 0; { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index c6f588ffe..c17e16f00 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -1365,8 +1365,6 @@ void WriteChanged(std::uint64_t address, std::span current, std std::size_t lastChanged = 0; for (std::size_t at = 0; at < size; at += block) { if (!differs(at)) continue; - // Only the bytes the GPU changed: the rest of the block holds the snapshot, and storing it - // would roll back what the CPU wrote there since (a title lost an object's vtable pointer). const auto blockEnd = std::min(at + block, size); for (std::size_t run = at; run < blockEnd;) { if (current[run] == original[run]) { ++run; continue; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 403435280..a914189c2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -313,7 +313,6 @@ void Validate(std::span packet, std::uint32_t queue) { require(eventIndex == 0 || eventIndex == 7, "invalid cache-flush event index"); break; case 0x39: - // PIXEL_PIPE_STAT_DUMP: the occlusion counters (see Driver::dumpSampleCounters). graphics(); size(4); require(eventIndex == 1, "invalid occlusion counter dump event index"); @@ -354,7 +353,6 @@ void Validate(std::span packet, std::uint32_t queue) { break; } case 0x59: - // REWIND: bit 31 is the valid flag the CPU sets; the driver waits for it (see Driver). size(2); require((packet[1] & 0x7fffffffu) == 0, "unsupported REWIND payload bits"); break; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index efeb1845f..b77359bef 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -218,7 +218,6 @@ struct VulkanDevice::State { std::map> computePipelines; std::unique_ptr colorTransfer; std::shared_ptr bufferPool; - // Bound for null V#s (Context::emptyBuffer). std::unique_ptr emptyBuffer; std::unique_ptr textureCache; std::unique_ptr pipelineCache; @@ -493,7 +492,6 @@ struct VulkanDevice::State { // Cached graphics pipelines (with their framebuffers, modules, render passes and layouts) // belong to this device and must be destroyed while it lives. Graphics::ClearCachedPipelines(device); - // Their framebuffers were the last users of the depth surfaces' views. Graphics::ClearDepthSurfaces(device); { std::lock_guard pipelines(computePipelinesMutex); @@ -780,7 +778,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u // PA_CL_CLIP_CNTL near/far clip disable maps to depth clamping. enabled.depthClamp = available.depthClamp; state->depthClamp = enabled.depthClamp == VK_TRUE; - // PIXEL_PIPE_STAT_DUMP reports sample counts (see Recorder::CountSamples). enabled.occlusionQueryPrecise = available.occlusionQueryPrecise; state->occlusionQueryPrecise = enabled.occlusionQueryPrecise == VK_TRUE; // Recompiled storage-image access declares no format (the guest descriptor decides it). @@ -789,7 +786,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u // Gathers with non-constant offsets (ImageGatherExtended). enabled.shaderImageGatherExtended = available.shaderImageGatherExtended; if (enabled.shaderImageGatherExtended) state->capabilities.push_back(spv::CapabilityImageGatherExtended); - // Sampling with an LOD clamp (image_sample_*_cl) as the MinLod image operand. enabled.shaderResourceMinLod = available.shaderResourceMinLod; if (enabled.shaderResourceMinLod) state->capabilities.push_back(spv::CapabilityMinLod); if (enabled.shaderStorageImageWriteWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageWriteWithoutFormat); @@ -1702,8 +1698,6 @@ bool NoResidentPresent() { // Debug aid: APS5_DUMP_FRAMES= saves the first n presented display buffers as frame_.bmp, // read back by the blit's own submission and written after its fence (RetireSlot), or with // APS5_NO_GPU_DUMP=1 decoded from the tiled guest buffer on the CPU, as before. -// APS5_DUMP_FRAMES_EVERY= dumps every k-th presented frame (up to APS5_DUMP_FRAMES of them) -// instead of the first ones, to reach screens far into a run. struct FrameDumps { int limit; bool cpu; @@ -2309,11 +2303,6 @@ bool TraceDispatchIo() { return traceIo; } -// Debug aid: APS5_WATCH_MEMORY=:[,:...] (with -// APS5_SYNC_DISPATCH=1) keeps the last 64 contents of the ranges, each with the dispatch that left it, -// and writes them to watch__.bin (the ranges back to back, in order) when the loop guard -// first trips: the dispatch that corrupted a structure a looping shader walks is the one whose -// snapshot first shows the damage. void WatchMemory(std::uint64_t programAddress) { struct Range { std::uint64_t address = 0; @@ -2341,8 +2330,6 @@ void WatchMemory(std::uint64_t programAddress) { static bool written = false; std::lock_guard lock(mutex); if (written) return; - // The heap base differs between runs above bit 36: the first of those bases each range is - // mapped at, once all are. static std::vector addresses; if (addresses.empty()) { std::vector found; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp index 922dbd2d8..301201afc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp @@ -5,7 +5,6 @@ namespace AgcDriver::Graphics { -// APS5_LOOP_GUARD: whether a dispatch has reported a loop that ran past the guard. bool LoopGuardTripped(); class BdaResources { @@ -41,8 +40,6 @@ private: // The page table is read-only to the shader, so consecutive builds mapping the same ranges to the // same device addresses share one buffer while one of them is alive (see the table cache in // BdaResources.cpp, which refers to it weakly). - // The pages stores through the table noted in the fault buffer (BdaAbi::WrittenPageSlots) are - // marked GPU-written, and the set emptied; CheckFault does it first. void markWrittenPages() const; std::shared_ptr table; std::unique_ptr fault; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 69dc27774..155caaeb4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -74,7 +74,6 @@ inline std::uint64_t& DeviceProcLookups() { return count; } -// The size of Context::emptyBuffer. inline constexpr std::size_t EmptyBufferBytes = 16; struct Context { @@ -119,10 +118,7 @@ struct Context { bool drawIndirectFirstInstance = false; bool multiDrawIndirect = false; bool drawIndirectCount = false; - // Occlusion queries count exact samples (occlusionQueryPrecise), not just any-passed. bool occlusionQueryPrecise = false; - // A small storage buffer bound for a null V# (no records): the recompiler specializes such a - // buffer as empty (BufferResource::empty), so shaders never access it. VkBuffer emptyBuffer = VK_NULL_HANDLE; // The device's list of recorded dispatches whose copied written buffers await a CPU write-back // (VulkanDevice's State::copiedWriters; the draw counterpart is DrawCopiedWriters): an indirect diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 551ceb320..7dbb70b89 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -11,20 +11,9 @@ namespace AgcDriver::Graphics { class Texture; -// The attachment view of the device's image for a depth/stencil target, made on first use. The -// image lives on the GPU only, always in the general layout: its guest surface (tiled, HTILE -// compressed) is never read or written, so a new image starts at the target's clear values, and -// shaders sample the image itself (DepthSurfaceTexture). Images stay until the device goes -// (ClearDepthSurfaces), so their views are stable for cached framebuffers. -// ponytail: never evicted, one image per distinct surface; evict (and keep recorded ones alive) if -// titles churn through many. VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target); void ClearDepthSurfaces(VkDevice device); -// Whether the depth or stencil plane of a surface drawn with starts at `address`. bool DepthSurfaceAt(std::uint64_t address); -// The texture sampling the depth or stencil plane that starts at the descriptor's base address, -// or null when no surface drawn with on this device has a plane there. The plane is read in place, -// so the descriptor must describe it (its format, extent, one level and slice); anything else throws. std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 00cbae6da..1256823fe 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -124,8 +124,6 @@ struct AddressSpaceStats { }; AddressSpaceStats AddressSpaceCounters(); -// The image and heap mirrors (see GuestBufferMemory.cpp): heap mirrors held and their bytes, then -// cumulative: mirrors made, 64 KiB blocks copied into them after their fill, heap mirrors read again. struct MirrorStats { std::uint64_t heapMirrors = 0; std::uint64_t heapBytes = 0; @@ -135,16 +133,12 @@ struct MirrorStats { }; MirrorStats MirrorCounters(); -// A registered range an address-based build copies instead of serving in place: its committed bytes -// and why no import or mirror serves it. struct AddressCopy { std::uint64_t begin; std::uint64_t end; std::uint64_t committed; const char* reason; }; -// The FATAL message for copies whose committed bytes exceed `limit` (APS5_ADDRESS_COPY_MAX_MIB), -// naming the largest ones; empty within the limit. std::string AddressCopyOverflow(std::vector copies, std::uint64_t limit); class GuestBufferMemory { @@ -187,9 +181,6 @@ public: void Upload(bool addressable); void UploadPrepare(bool addressable); void UploadFinish(bool addressable); - // A view off minStorageBufferOffsetAlignment in its GPU owner (a view inside a merged or - // imported range) is bound from the aligned offset below it; `adjustment` is the difference, - // which the shader adds to every access (RecompileResult::memoryOffsetDword). VkDescriptorBufferInfo Descriptor(std::uint64_t address, std::size_t bytes, std::uint32_t& adjustment) const; std::vector AddressRanges() const; // The BDA table of the cached address space when it serves this upload alone (an address-based @@ -325,8 +316,6 @@ private: // Records the import-to-buffer copies of the given gpuCopy regions into the open batch, with // the barriers that order them after earlier recorded writes and before the shaders reading them. void recordGpuCopies(std::span copies, bool addressable); - // Keeps the bytes of every merged written range a writable heap mirror serves, as uploaded: the - // mirror keeps no shadow, and write-back stores only what the GPU changed from them. void takeHeapReferences(); Context context; bool stagingAllowed = false; @@ -342,7 +331,6 @@ private: // regions follow the registry's order), so AddSnapshot can search instead of scanning. bool regionsSorted = false; std::vector> writes; - // By the begin of the merged written range (see takeHeapReferences). std::vector>> heapReferences; // UploadPrepare ran (regions are frozen); `uploaded` once UploadFinish ran. bool prepared = false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index 4e74c0cac..aebce3e9b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -40,18 +40,12 @@ public: Pipeline(const Pipeline&) = delete; Pipeline& operator=(const Pipeline&) = delete; VkPipelineLayout Layout() const; - // The framebuffer of these attachment views: the color views, then the depth surface's when the - // pipeline has one. A resident target's view (`owners[i]` set) is stable while its - // StorageTexture lives, and a depth surface's while its device does, so framebuffers made of - // resident views are kept in the pipeline and reused when every owner is still the same - // object; any other set is built per call. std::shared_ptr AcquireFramebuffer(std::span targets, std::span> owners, VkExtent2D extent); // Begins the render pass on the framebuffer, binds the pipeline and sets viewport and scissor. void Begin(VkCommandBuffer commands, const Framebuffer& framebuffer, VkExtent2D extent, const VkViewport& viewport, const VkRect2D& scissor) const; // The same inside a render pass another pipeline of the same attachments began (compatible by // construction: the attachment formats alone decide). void Continue(VkCommandBuffer commands, const VkViewport& viewport, const VkRect2D& scissor) const; - // An assembled block (AssemblePushConstants, then ShaderResources::PatchPushConstants). void PushConstants(VkCommandBuffer commands, VkShaderStageFlags stages, std::span bytes) const; // Forgets the Vulkan objects without destroying them: for entries of a device that is already gone. void Abandon() noexcept; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 176e0432c..02503c83c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -165,9 +165,6 @@ public: void FinishUpTo(std::uint64_t serial); // Submits and waits for every batch, running completions in order. void Sync(); - // Sample counting for PIXEL_PIPE_STAT_DUMP: once on (the first dump), every batch counts the - // samples its draws pass in an occlusion query begun outside any render pass, and adds them to - // SamplesPassed when it completes. Under the mutex; an open batch starts counting at once. void CountSamples(); static std::uint64_t SamplesPassed(); // Waits only for the batches up to the newest one that writes the range (submitting the open @@ -491,7 +488,6 @@ private: std::uint32_t queue = 0xffffffffu; std::chrono::steady_clock::time_point submittedAt{}; VkQueryPool queries = VK_NULL_HANDLE; - // The occlusion query counting this batch's samples (see CountSamples). VkQueryPool samples = VK_NULL_HANDLE; std::vector timedKeys; std::vector timedBytes; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 136607e0a..eaf43678a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -185,8 +185,6 @@ public: // compiled shader's descriptors. A recipe hit refreshes the template iff the two differ. std::uint64_t DataWordsHash() const { return dataWordsHash; } static std::uint64_t DataWordsHash(const CompiledShader& shader); - // Stores the byte offsets of the guest buffers bound below their views (GuestBufferMemory:: - // Descriptor) into a push constant block assembled from the shaders this object was built for. void PatchPushConstants(std::span bytes) const { for (const auto& [position, adjustment] : pushPatches) bytes[position] = static_cast(adjustment); } @@ -242,9 +240,6 @@ private: bool written = true; // Data buffers: the words the buffer holds once the recorded work ran (see RefreshData). std::vector dataWords; - // Guest buffers: how far below the view the descriptor starts (GuestBufferMemory:: - // Descriptor), and where in the push constant block the shader reads that (-1: the - // shader data is a buffer). std::uint32_t adjustment = 0; std::int32_t pushByte = -1; }; @@ -410,7 +405,6 @@ private: // FNV-1a offset basis: the hash of no data buffers (DataWordsHash). std::uint64_t dataWordsHash = 14695981039346656037ull; void rehashDataWords(); - // The nonzero guest buffer adjustments by push constant byte (PatchPushConstants). std::vector> pushPatches; BuildTiming timing; // Build state carried from stage A to stage B: the bindings in plan order, the image bindings diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index c4e32973a..94c91a071 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -40,10 +40,6 @@ struct ColorTarget { // DCC metadata of a compressed target (CB_COLOR_INFO DCC_ENABLE), or 0 (see DccMetadata.hpp). std::uint64_t dccAddress = 0; bool dccAlphaOnMsb = false; - // A view of one mip of a mipmapped surface (CB_COLOR_VIEW MIP_LEVEL, CB_COLOR_ATTRIB2 MAX_MIP): - // the surface's base, mip 0 extent and mip count, the rendered mip, and whether it lies in the - // packed mip tail (which shares one block with the smaller mips, so it is only rendered through - // the resident image of the whole chain). `address`, `extent` and `bytes` are the mip's. std::uint64_t surfaceAddress = 0; VkExtent2D surfaceExtent{}; std::uint32_t mipCount = 1; @@ -51,22 +47,17 @@ struct ColorTarget { bool mipTail = false; }; -// The depth/stencil surface a draw with depth or stencil tests renders with (DB_Z_INFO, -// DB_STENCIL_INFO and their bases). Its image lives on the GPU only (DepthSurface.hpp). struct DepthTarget { std::uint64_t address; - // The stencil plane, 0 without one. std::uint64_t stencilAddress; VkExtent2D extent; VkFormat format; - // DB_DEPTH_CLEAR / DB_STENCIL_CLEAR: what a new image starts as. float clearDepth; std::uint8_t clearStencil; }; struct State { ShaderStages stages; - // Set when the draw tests depth or stencil against a bound surface; the tests below are off otherwise. std::optional depth; bool depthTest = false; bool depthWrite = false; @@ -94,7 +85,6 @@ struct State { ShaderStages DecodeShaderStages(const QueueState& queue); State DecodeState(const QueueState& queue); -// The component mapping of each MRT slot's export (identity past the written slots). std::array ExportMappings(const State& state); // The message DecodeState (or the pixel stage decode after it) would throw for the register rules // this precheck covers, evaluated without exceptions before the draw is decoded; empty when they @@ -132,11 +122,6 @@ struct DrawKeyRange { std::uint32_t count; }; inline constexpr std::array DrawKeyRegisters{{ - // DB_RENDER_CONTROL, DB_DEPTH_VIEW, DB_DEPTH_SIZE_XY, DB_STENCIL_CLEAR/DB_DEPTH_CLEAR with - // PA_SC_SCREEN_SCISSOR, DB_Z_INFO .. DB_STENCIL_WRITE_BASE and their high words; the window - // offset/scissor and clip rect, the edge rule, the hardware screen offset, CB_TARGET_MASK/ - // CB_SHADER_MASK, the generic and viewport 0 scissors, the viewport 0 depth clamp, the blend - // constants, DB_STENCIL_CONTROL/DB_STENCILREFMASK(_BF), the viewport 0 transform. {RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 1}, {RegisterBank::Context, 0x007, 1}, {RegisterBank::Context, 0x00a, 4}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 4}, {RegisterBank::Context, 0x080, 4}, {RegisterBank::Context, 0x08c, 4}, {RegisterBank::Context, 0x090, 2}, {RegisterBank::Context, 0x094, 2}, {RegisterBank::Context, 0x0b4, 2}, {RegisterBank::Context, 0x105, 4}, {RegisterBank::Context, 0x10b, 3}, {RegisterBank::Context, 0x10f, 6}, // SPI_PS_INPUT_CNTL_0..31, SPI_PS_INPUT_ENA/ADDR, SPI_PS_IN_CONTROL, SPI_SHADER_POS/Z/COL_FORMAT, diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 640f3e9ab..6fc5ea467 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -57,8 +57,6 @@ public: // compute pass writing it and the next pass sampling it share one image and copy nothing. // CanCopyFrom says whether the two descriptors address the same surface compatibly. Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components); - // A view of one plane (aspect) of a depth surface's image, which stays in the general layout; - // the surface (DepthSurface.hpp) owns the image and its views. Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components); static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor); ~Texture(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp index 7268aea59..22bc67a98 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp @@ -255,9 +255,6 @@ void BdaResources::CheckFault() const { std::ostringstream message; message << "BDA access failed: address=0x" << std::hex << report.address << " instruction=0x" << report.instruction << std::dec << " bytes=" << report.bytes << " stage=" << report.stage << " reason=" << static_cast(report.reason); if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::Permission && table != nullptr) { - // Only a writable range imported in place is writable through the table (see - // GuestBufferMemory::addressRange): a store into a range served by a mirror or a copy would - // not reach guest memory, so it faults. Name the range and how it is served. const auto bytes = table->Bytes(); ShaderRecompiler::BdaAbi::Header header{}; std::memcpy(&header, bytes.data(), sizeof(header)); @@ -276,10 +273,6 @@ void BdaResources::CheckFault() const { namespace AgcDriver::Graphics { -// ponytail: the pages are marked once the work completed, so a CPU read of them before that (a -// capture or texture upload of later work in the same batch) is not made to wait for the stores, -// as descriptor-bound writes are (Recorder::NotePendingWrites); note the V#s' ranges at record -// time if that shows up. void BdaResources::markWrittenPages() const { namespace Abi = ShaderRecompiler::BdaAbi; auto* words = reinterpret_cast(fault->Bytes().data()); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp index 1af888ca6..4c74d01bd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp @@ -33,7 +33,6 @@ ColorTargetLayout::ColorTargetLayout(std::uint32_t width, std::uint32_t height, std::uint32_t paddedHeight = height; switch (mode) { case ColorTileMode::Linear: { - // Rows are padded to 256 bytes, addrlib's linear pitch alignment (linear textures use the same). const auto pitchAlignment = 256u / bytesPerElement; pitch = (width + pitchAlignment - 1u) / pitchAlignment * pitchAlignment; break; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 888197768..f8ae28b76 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -52,8 +52,6 @@ public: viewInfo.format = target.format; viewInfo.subresourceRange = {aspects, 0, 1, 0, 1}; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth"); - // Cleared in the general layout it keeps from then on, recorded ahead of the draw (a draw's - // pass begins behind a barrier from every earlier transfer write). auto* recorder = Recorder::Active(); std::unique_ptr batch; if (recorder == nullptr) batch = std::make_unique(context); @@ -82,7 +80,6 @@ public: DepthSurface(const DepthSurface&) = delete; DepthSurface& operator=(const DepthSurface&) = delete; - // The texture sampling the plane at the descriptor's base address, one per descriptor. std::shared_ptr Sampled(std::span words, const GuestTextureResource& resource, VkComponentMapping components) { std::array key{}; std::copy_n(words.begin(), std::min(words.size(), 8), key.begin()); @@ -95,8 +92,6 @@ public: const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT; const auto format = ResolveTextureFormat(resource.format); - // A 32-bit integer read of a D32 plane takes the float view: the shader keeps the bits - // (ShaderRecompiler::IsDepthBitsTexture). const bool depthBits = !stencil && !d16 && words.size() >= 4 && ShaderRecompiler::IsDepthBitsTexture(words[1], words[3]); if ((format != expected && !depthBits) || resource.dimension != TextureDimension::k2D || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0) { char text[448]; @@ -139,7 +134,6 @@ std::mutex& surfacesMutex() { return mutex; } -// Never destroyed with the statics: the images belong to a device that may be gone by then. std::vector>& surfaces() { static auto* list = new std::vector>(); return *list; @@ -164,7 +158,6 @@ void ClearDepthSurfaces(VkDevice device) { std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components) { std::lock_guard lock(surfacesMutex()); const auto& list = surfaces(); - // The newest surface made at the address. const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress)); }); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 98df34f00..8b46b0812 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -42,7 +42,6 @@ std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) { // The color buffer as a single-mip 2D surface descriptor (tile mode SW_64KB_R_X). GuestTextureResource SurfaceForTarget(const ColorTarget& color) { Require(color.tileMode == ColorTileMode::RenderTarget, "only 64 KiB tiled color targets are resident"); - // A mipmapped target is its whole chain; the attachment view picks the mip. const bool chain = color.mipCount > 1; GuestTextureResource surface{}; surface.baseAddress = chain ? color.surfaceAddress : color.address; @@ -503,7 +502,6 @@ void CheckBufferAliases(std::span shaders, const ColorTarg const ShaderRecompiler::ShaderBufferResource descriptor{{words[offset], words[offset + 1], words[offset + 2], words[offset + 3]}}; const auto address = descriptor.Base48(); const auto size = descriptor.GetSize(); - // A null V# binds nothing (ShaderResources::addGuestBuffer). if (size == 0 || address == 0) continue; Require(!overlap(address, size, target.address, target.bytes), "shader buffer aliases the render target"); Require(!overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer"); @@ -1140,8 +1138,6 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam if (continued) { record.pipeline->Continue(commands, state.viewport, state.scissor); } else { - // Depth surfaces stay in the general layout too; an earlier pass's depth writes (the recorder's - // trailing barrier after a pass does not name them) are made visible here. const VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT}; context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | inputs.shaderStages, 0, 1, &before, 0, nullptr, 0, nullptr); countBarrier(1); @@ -1331,7 +1327,6 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& binding.target = std::make_unique(context, color, state.blends[index].blendEnable != 0); targetViews.push_back(binding.target->View()); } - // The depth surface is the last attachment (Pipeline::AcquireFramebuffer). if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth)); timer.phase(PhasePrepare); const bool recordDraws = RecordDraws(); @@ -1477,7 +1472,6 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& // only a template the resource cache serves under this content key (reusable: no lease, // no copied writes, every direct region import- or mirror-served) of a direct draw, so a // hit's proof is the template's ProveCurrent and nothing needs completion work. - // ponytail: none for depth draws (the recipe replays color targets only); add the depth view when they need the hit path. if (recipeOut != nullptr && DrawRecipes() && cacheable && !outcome.waited && args == nullptr && resources->Reusable() && !state.depth) { auto recipe = std::make_shared(); recipe->device = context.device; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index e7f6beb9a..8b8846c77 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -36,19 +36,11 @@ namespace AgcDriver::Graphics { // mprotect, which replaces the registered Range object, and a mirror whose Range is gone is rebuilt. // Writable ranges keep a shadow of the guest bytes and copy the 64 KiB blocks that differ on each use. // A mirror never holds the Range shared_ptr: a pinned range makes the guest's mprotect spin and fail. -// Heap mirrors serve the readable registered heap ranges the host imports did not take (past -// APS5_HOST_IMPORT_MIB, or refused by the driver), which each address-based build copied instead. -// They keep no shadow: the arena's write tracking says which 64 KiB blocks changed since the mirror -// took them, and those blocks are read again. A writable one is written back per build from the -// bytes its written ranges held at upload (takeHeapReferences); stores through the BDA table fault -// there, as in a copy. struct ImageMirror { std::uint64_t base = 0; std::uint64_t bytes = 0; bool writable = false; bool heap = false; - // Heap mirrors only: per tracker block of the range (block 0 holds `base`), the generation - // collected before the block was last read into the buffer. std::vector generations; std::weak_ptr range; std::shared_ptr buffer; @@ -316,12 +308,9 @@ struct ImageMirrors { VkDevice device = VK_NULL_HANDLE; std::map> entries; std::set failed; - // Bytes of the heap mirrors in `entries` (against APS5_HEAP_MIRROR_MIB). std::uint64_t heapBytes = 0; // APS5_PROFILE_DRAW counters, reported every 10 s as [buffers] image mirrors. std::uint64_t builds = 0; - // Heap mirrors read again because their range changed, and heap ranges left to copies because - // the arena does not track their writes. std::uint64_t heapRefills = 0; std::uint64_t heapUnwatched = 0; std::uint64_t subranges = 0; @@ -377,7 +366,6 @@ bool mirrorsEnabled() { return !disabled; } -// The heap mirrors' limit: past it the process stops rather than copying gigabytes per dispatch. std::uint64_t heapMirrorBudget() { static const std::uint64_t bytes = [] { const char* value = std::getenv("APS5_HEAP_MIRROR_MIB"); @@ -401,14 +389,10 @@ struct RefreshBlock { ImageMirror* mirror; std::uint64_t address; std::size_t length; - // Heap mirrors: the generation the block's copy makes current, stored only once the copy ran, - // so a build that throws before its copies leaves the block stale in the tracker's eyes too. std::uint64_t generation = 0; }; // Compares a block's guest bytes with the mirror's shadow and copies a changed block into the device -// buffer and the shadow (a heap mirror's block is known to have changed and is copied). Blocks are -// distinct, so blocks of one refresh can be compared in parallel. bool compareBlock(const RefreshBlock& block) { const auto offset = static_cast(block.address - block.mirror->base); const auto* guest = reinterpret_cast(block.address); @@ -520,9 +504,6 @@ private: // completes first, so its results reach the guest and the shadow before the compare and no batch // writes a block being replaced; GPU reads of the mirror by earlier recorded work (an address-based // dispatch whose lease is released at completion) see the newer bytes, as they would on hardware. -// prepareRange does it for [address, address + bytes), prepareRefresh for a writable image mirror and -// returns whether there is anything to compare. Heap mirrors are prepared a run of adjacent ranges at -// a time, before their write tracking is asked what changed (refreshHeapMirrors). void prepareRange(std::uint64_t address, std::uint64_t bytes) { // The compare reads the pages directly; a mirror is only reused while its Range exists, but a // lease of a build in flight can keep a replaced Range alive past a protection change. @@ -574,11 +555,6 @@ void refreshMirror(ImageMirror& mirror, std::uint64_t address, std::uint64_t byt compareBlocks(blocks); } -// Appends to `blocks` the blocks of one build's heap mirrors that a CPU or driver store changed -// since the mirrors took them. The mirrors of adjacent ranges are prepared and collected as one run: a -// collect walks the run's page tables at a fixed cost of microseconds, which paid per mirror (thousands -// on a story-mode load) was most of an address-based build. The copies follow and store the run's -// collected generation (see RefreshBlock). void refreshHeapMirrors(std::vector& mirrors, std::vector& blocks) { constexpr std::uint64_t block = 65536; std::sort(mirrors.begin(), mirrors.end(), [](const ImageMirror* left, const ImageMirror* right) { return left->base < right->base; }); @@ -613,9 +589,6 @@ void refreshHeapMirrors(std::vector& mirrors, std::vector acquireMirror(const Context& context, const std::shared_ptr& range, std::vector& blocks, bool heap) { auto& state = Mirrors(); std::shared_ptr mirror; @@ -635,10 +608,6 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh if (!heap && prepareRefresh(*mirror, range->address, range->bytes)) appendBlocks(blocks, *mirror, range->address, range->bytes); return mirror; } - // The registered flags were taken from the pages; an image range that disagrees is copied by this - // build. A heap mirror needs readable pages only: a writable range may hold read-only pages (a - // direct memory page mapped at several addresses is read-only in every view), whose bytes the GPU - // reads like the others and whose write-back storeChanged refuses. if (!GuestMemory::Accessible(reinterpret_cast(range->address), range->bytes, range->writable && !heap)) return nullptr; mirror = std::make_shared(); mirror->base = range->address; @@ -647,7 +616,6 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh mirror->heap = heap; mirror->range = range; if (heap) { - // Pending GPU results reach the range before the collect, so the fill below is current. prepareRefresh(*mirror, range->address, range->bytes); const auto generation = GuestMemory::CollectWrites(range->address, range->bytes); constexpr std::uint64_t block = 65536; @@ -687,9 +655,6 @@ std::shared_ptr acquireMirror(const Context& context, const std::sh return mirror; } -// The image mirror containing [begin, end) whose registered range still exists, or null. A mirror -// whose Range was replaced is only rebuilt by the next address-based build; until then the range is -// copied. Heap mirrors are not given out: only an address-based build refreshes them. std::shared_ptr findMirror(const Context& context, std::uint64_t begin, std::uint64_t end) { auto& state = Mirrors(); std::lock_guard lock(state.mutex); @@ -1002,7 +967,6 @@ void GuestBufferMemory::AcquireRegistered() { // APS5_NO_SORTED_SNAPSHOT_LOOKUP=1: AddSnapshot scans the regions as before instead of searching. static const bool sortedLookup = std::getenv("APS5_NO_SORTED_SNAPSHOT_LOOKUP") == nullptr; std::vector blocks; - // The build's heap mirrors, refreshed together before the blocks are copied. std::vector heaps; bool mirrored = false; auto& spaces = Spaces(); @@ -1060,7 +1024,6 @@ void GuestBufferMemory::AcquireRegistered() { // The import lock is taken per range: a mirror's preparation may wait for recorded work, which // must not happen under it. regions.reserve(lease.size()); - // The ranges left to copies, with why no import or mirror serves them (the copy limit below). std::vector copies; for (const auto& range : lease) { if (!range->readable) continue; @@ -1081,8 +1044,6 @@ void GuestBufferMemory::AcquireRegistered() { continue; } if (mirrorsEnabled()) { - // Main image and heaps: served by a persistent mirror (see ImageMirror); the writable - // image ones are compared with guest memory below, all at once. region.mirror = acquireMirror(context, range, blocks, range->releasable); lap(timing.mirrorsUs, at); if (region.mirror != nullptr) { @@ -1099,13 +1060,10 @@ void GuestBufferMemory::AcquireRegistered() { committed = 0; for (const auto& [first, last] : copied.backed) committed += last - first; } - // A heap range with readable pages is mirrored unless the arena does not track its writes. const char* reason = !mirrorsEnabled() ? "mirrors disabled by APS5_NO_LEASE_MIRROR" : copied.sparse ? "unreadable pages" : !range->releasable ? "image mirror refused" : "outside the write-watched arena"; copies.push_back({copied.begin, copied.end, committed, reason}); } regionsSorted = sortedLookup; - // The ranges left to copies are copied by every build of this space; past the limit the - // process stops rather than copying them per dispatch. static const std::uint64_t copyLimit = [] { const char* value = std::getenv("APS5_ADDRESS_COPY_MAX_MIB"); return (value ? std::strtoull(value, nullptr, 10) : 64ull) << 20u; @@ -1189,7 +1147,6 @@ void GuestBufferMemory::addCopiedRange(const CopiedRange& range) { // cannot write them, so that equals a snapshot taken now without the extra copy. Reserved heaps // are registered whole while the guest commits pages on demand: committed pages only. region.hostBacked = !range.writable; - // Readable pages are copied in, a writable range's read-only ones included (see storeChanged). auto committed = GuestMemory::DescribeCommitted(range.begin, range.end - range.begin); if (!committed.whole) { region.sparse = true; @@ -1269,8 +1226,6 @@ void GuestBufferMemory::addDescriptorRegion(std::uint64_t address, std::size_t b auto committed = GuestMemory::DescribeCommitted(address, bytes); if (!committed.whole) { // A GPU heap bound whole while the guest commits its pages on demand, or a descriptor left - // pointing at unmapped memory: only readable pages are copied in and stored back (writable - // ones only, see storeChanged). Shaders must not touch the rest, which reads as zeros. region.sparse = true; region.backed = std::move(committed.ranges); } @@ -2110,7 +2065,6 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std: const auto base = region.direct != nullptr ? region.direct->base : region.mirror != nullptr ? region.mirror->base : region.begin; const auto offset = address - base; Require(context.limits.minStorageBufferOffsetAlignment != 0, "no storage buffer offset alignment"); - // At most 255 (the alignment is at most 256); the shader adds it in DWORDs. adjustment = static_cast(offset % context.limits.minStorageBufferOffsetAlignment); Require(adjustment % 4 == 0, "guest buffer view off the storage buffer offset alignment is not DWORD aligned"); Require(bytes + adjustment <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit"); @@ -2122,9 +2076,6 @@ ShaderRecompiler::BdaAbi::Range GuestBufferMemory::addressRange(const Region& re Require(region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr, "incomplete guest GPU upload"); const auto address = region.direct != nullptr ? region.direct->address + (region.begin - region.direct->base) : region.mirror != nullptr ? region.mirror->buffer->DeviceAddress() + (region.begin - region.mirror->base) : region.buffer->DeviceAddress(); Require(region.end - region.begin <= std::numeric_limits::max() - address, "GPU address range overflow"); - // Stores through the table (GPU-selected V#s) land in guest memory only where the range is the - // allocation itself: a writable one served in place by its import. Mirrors and copies would - // drop them, so a store there faults. const auto permissions = ShaderRecompiler::BdaAbi::Read | (region.direct != nullptr && region.writable ? ShaderRecompiler::BdaAbi::Write : 0u); return {region.begin, region.end, address, permissions, 0}; } @@ -2178,10 +2129,6 @@ void GuestBufferMemory::MarkDirectWrites() const { namespace { -// Stores what the GPU changed in [address, address + current.size()) against `original`. Only the -// writable pages are stored: a read-only page of a writable range (a direct memory page mapped at -// several addresses is read-only in every view, aliased writes are not implemented) must come back -// unchanged, else the write-back fails rather than dropping the GPU's store. void storeChanged(std::uint64_t address, std::span current, std::span original) { const auto writable = GuestMemory::DescribeCommitted(address, current.size(), true); if (writable.whole) { @@ -2206,7 +2153,6 @@ void storeChanged(std::uint64_t address, std::span current, std if (cursor < address + current.size()) unchanged(cursor, address + current.size()); } -// The written ranges sorted, overlapping ones merged. std::vector> mergeWrites(std::vector> writes) { std::sort(writes.begin(), writes.end()); std::vector> merged; @@ -2257,8 +2203,6 @@ void GuestBufferMemory::WriteBack() { const auto length = static_cast(end - begin); const auto current = mirror.buffer->Bytes().subspan(offset, length); if (mirror.heap) { - // The build's reference instead of a shadow; the stores stamp the blocks, and the - // next refresh takes them back from guest memory. const auto reference = std::find_if(heapReferences.begin(), heapReferences.end(), [&](const auto& entry) { return entry.first == begin; }); Require(reference != heapReferences.end() && reference->second.size() == length, "heap mirror write-back has no reference"); storeChanged(begin, current, reference->second); @@ -2327,8 +2271,6 @@ std::optional>> GuestBufferM } for (const auto& region : regions) { // A read-only mirror is as fixed as an import: its bytes and VkBuffer never change while its - // Range exists (see ImageMirrorSerial). A writable one is refreshed and written back per build, - // a heap one refreshed per build. // A staged region is keyed by its import like one bound in place: the import is the source // of its copy-in and the destination of its copy-back, both recorded per use. const bool fixedMirror = region.mirror != nullptr && !region.mirror->writable && !region.mirror->heap; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 6db1798dc..a18b2f49a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -103,7 +103,6 @@ GuestTextureResource DecodeTextureResource(std::span words) Require(!prtDefColor, "guest texture descriptor uses a partially resident default color which is not implemented"); Require(arrayPitch == 0, "guest texture descriptor uses a nonzero array pitch which is not implemented"); Require(!msaaDepth, "guest texture descriptor uses MSAA which is not implemented"); - // DCC block sizes only shape compressed blocks, and surfaces are read as stored (below). static_cast(maxUncompBlkSize); static_cast(maxCompBlkSize); // Surfaces are always written uncompressed here (render targets and storage images bypass DCC), so diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 558cefb4f..efca469c4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -105,7 +105,6 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = static_cast(references.size()); subpass.pColorAttachments = references.empty() ? nullptr : references.data(); - // The depth surface stays in the general layout (DepthSurface.hpp). const VkAttachmentReference depthReference{static_cast(colors.size()), VK_IMAGE_LAYOUT_GENERAL}; if (state.depth) { VkAttachmentDescription depth{}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 19355f2d2..95e471b7a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -264,10 +264,7 @@ std::shared_ptr sampledStorageSource(const Context& context, con // `guestBytes` is the surface size when the caller described the surface already (0: described here). std::shared_ptr cachedTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components, std::uint64_t guestBytes = 0) { CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast(resource.baseAddress), resource.width, resource.height, static_cast(resource.dccAddress)); - // A depth surface's contents live in its GPU image; its guest memory is stale. if (auto depth = DepthSurfaceTexture(context, words, resource, components)) return depth; - // The recompiler reads such a T# through a float view (IsDepthBitsTexture), which only a depth - // surface provides. if (words.size() >= 4 && ShaderRecompiler::IsDepthBitsTexture(words[1], words[3])) { char text[160]; std::snprintf(text, sizeof(text), "AGC graphics: 32-bit integer read of the depth-layout texture 0x%llx, which is no depth surface drawn with, is not implemented", static_cast(resource.baseAddress)); @@ -829,7 +826,6 @@ void ShaderResources::buildPrepare(std::span shaders, cons const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element]; const bool atomic = element < binding.bufferAtomic.size() && binding.bufferAtomic[element]; const auto index = addGuestBuffer(std::span(binding.guestDescriptor).subspan(static_cast(element) * 4, 4), target, indexAddress, indexBytes, written, atomic); - // The element's byte offset in the shader data (RecompileResult::memoryOffsetDword). const auto& push = shader.program->pushConstants; if (!push.empty()) { const auto position = shader.program->memoryOffsetDword * 4u + element; @@ -2029,7 +2025,6 @@ std::size_t ShaderResources::addGuestBuffer(std::span words const auto address = descriptor.Base48(); const auto byteSize = descriptor.GetSize(); if (byteSize == 0 || address == 0) { - // A null V#: every access is out of bounds in the shader (BufferResource::empty). allocations.push_back({0, EmptyBufferBytes, false, nullptr, ShaderRecompiler::DescriptorRole::GuestBuffers, false}); return allocations.size() - 1; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 9c771a7ec..828f1e626 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -79,17 +79,12 @@ bool IgnoreDepthTest() { return ignore; } -// Whether DB_Z_INFO or DB_STENCIL_INFO names a plane (an absent register names none). bool depthSurfaceBound(const Registers& cx) { const auto z = find(cx, 0x010); const auto stencil = find(cx, 0x011); return (z != cx.end() && (z->second & 3u) != 0) || (stencil != cx.end() && (stencil->second & 1u) != 0); } -// One face of the stencil state (the compare function, DB_STENCIL_CONTROL's three operations and -// DB_STENCILREFMASK) as Vulkan's. AMD's compare functions are in Vulkan's order. Vulkan has one -// reference per face for the test and every replacement, so a replacement by another value -// (REPLACE_OP writes STENCILOPVAL, ONES 0xff) is taken where the bits it writes are not also compared. VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint32_t refMask, bool readOnly) { VkStencilOpState face{}; face.compareOp = static_cast(compare); @@ -98,8 +93,6 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint const auto test = refMask & 0xffu; const auto opValue = refMask >> 24u; auto reference = test; - // The reference bits already spoken for: the compared ones (none for NEVER and ALWAYS), then - // the replaced ones. auto fixed = compare == 0 || compare == 7 ? 0u : face.compareMask; const auto convert = [&](std::uint32_t op) { if (face.writeMask == 0) return VK_STENCIL_OP_KEEP; @@ -123,7 +116,6 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint case 6: case 8: case 9: - // ADD/SUB add STENCILOPVAL; Vulkan's increments add 1. if (opValue != 1) { std::snprintf(detail, sizeof(detail), "AGC graphics: stencil add/subtract of 0x%02x is unsupported", opValue); throw std::runtime_error(detail); @@ -142,15 +134,10 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint return face; } -// The depth/stencil surface and tests of a draw that tests depth or stencil with a plane bound. void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) { - // DEPTH/STENCIL_COMPRESS_DISABLE (bits 5, 6) and the ordering hints from bit 13 on do not change - // what is drawn. zero(cx, 0x000, 0x00001f9fu, "depth/stencil clear, copy, resummarize or decompress draws (DB_RENDER_CONTROL)"); const auto view = read(cx, 0x002); - // Z_READ_ONLY and STENCIL_READ_ONLY (bits 24, 25) turn the writes off. zero(cx, 0x002, ~0x03000000u, "depth array slices or mips (DB_DEPTH_VIEW)"); - // NUM_SAMPLES, PARTIALLY_RESIDENT, MAXMIP; the rest is tiling and compression the host image has none of. zero(cx, 0x010, 0x000f100cu, "multisampled, partially resident or mipmapped depth (DB_Z_INFO)"); zero(cx, 0x011, 0x00001000u, "partially resident stencil (DB_STENCIL_INFO)"); const auto zFormat = read(cx, 0x010) & 3u; @@ -171,7 +158,6 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) Require(!stencil || stencilReadOnly || base(0x015, 0x01d) == depth.stencilAddress, "stencil read and written at different addresses is unsupported"); const auto size = read(cx, 0x007); depth.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u}; - // A stencil-only surface gets an unused depth plane. depth.format = zFormat == 1 ? (stencil ? VK_FORMAT_D16_UNORM_S8_UINT : VK_FORMAT_D16_UNORM) : (stencil ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT); depth.clearDepth = readFloat(cx, 0x00b); depth.clearStencil = static_cast(read(cx, 0x00a) & 0xffu); @@ -183,13 +169,10 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) if (result.stencilTest) { const auto ops = read(cx, 0x10b); result.stencilFront = stencilFace((depthControl >> 8u) & 7u, ops, read(cx, 0x10c), stencilReadOnly); - // BACKFACE_ENABLE gives back faces their own function, operations and masks. result.stencilBack = (depthControl & 0x80u) != 0 ? stencilFace((depthControl >> 20u) & 7u, ops >> 12u, read(cx, 0x10d), stencilReadOnly) : result.stencilFront; } } -// DB_DEPTH_CONTROL without a bound surface: depth and stencil tests whose function is ALWAYS and -// that write no depth cannot change color output, so they are accepted (stencil writes go nowhere). bool depthPassThrough(std::uint32_t depthControl) { const bool stencil = (depthControl & 1u) != 0; const bool depth = (depthControl & 2u) != 0; @@ -238,7 +221,6 @@ VkBlendOp blendOp(std::uint32_t value) { struct DecodedColorFormat { VkFormat format; std::uint32_t elementBytes; - // The exported component each attachment component stores, two bits each (0xe4 is identity). std::uint8_t componentMapping = 0xe4u; }; @@ -251,8 +233,6 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, throw std::runtime_error("AGC graphics: unsupported color format " + std::to_string(format) + " number type " + std::to_string(number) + " component swap " + std::to_string(swap)); }; const bool alternate = swap == 1; - // A one-component format stores the exported component COMP_SWAP names: R, G, B or A (an A8 - // target is COLOR_8 with SWAP_ALT_REV). const auto single = [&](VkFormat vkFormat, std::uint32_t bytes) { return DecodedColorFormat{vkFormat, bytes, static_cast((0xe4u & ~3u) | swap)}; }; if (swap > 1 && format != 1 && format != 2 && format != 4) return fail(); switch (format) { @@ -497,8 +477,6 @@ State DecodeState(const QueueState& queue) { for (std::uint32_t slot = 0; slot < slotCount; ++slot) { // Export formats only matter for the targets the draw writes. const auto slotExport = (exportFormat >> (4u * slot)) & 0xfu; - // FP16, UNORM16 and SNORM16 exports are unpacked to floats by the shader; 32_R, 32_GR, 32_AR and - // 32_ABGR are written as is, the components a format leaves out reading as 0 (alpha 1). if (slotExport == 0 || slotExport == 7 || slotExport == 8 || slotExport > 9) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " is unsupported"); const auto stride = slot * 0xfu; ColorTarget color{}; @@ -509,12 +487,8 @@ State DecodeState(const QueueState& queue) { const auto format = (info >> 2u) & 0x1fu; const auto decoded = DecodeColorFormat(format, number, swap); // ROUND_MODE (bit 18) only affects unorm rounding. With DCC_ENABLE (bit 28) the target is written - // uncompressed and only its fast-clear keys matter (see DccMetadata.hpp). BLEND_BYPASS (bit 16) is - // checked against enabled blending below. if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")"); Require((info & 0x8000u) != 0 || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported"); - // CB_COLOR_VIEW (GFX10: SLICE_START 0-12, SLICE_MAX 13-25, MIP_LEVEL 26-29): MIP_LEVEL selects - // the rendered mip; array slices are not modeled. const auto view = read(cx, 0x31b + stride); Require((view & ~0x3c000000u) == 0, "color array views are unsupported"); const auto viewMip = (view >> 26u) & 0xfu; @@ -608,8 +582,6 @@ State DecodeState(const QueueState& queue) { APS5_LOG_OUT_DEBUG("Blend %u control=0x%x", slot, blend); Require((blend & 0x0000e000u) == 0, "reserved blend control bits"); VkPipelineColorBlendAttachmentState state{}; - // CB_TARGET_MASK names exported components; attachment component c stores exported component - // mapping[c], so its write bit and, when it stores alpha, its blend come from that component. const auto mapping = result.colors[slot].componentMapping; const auto exportedMask = (targetMask >> (4u * slot)) & 0xfu; for (std::uint32_t component = 0; component < 4; ++component) { @@ -665,7 +637,6 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (auto reason = indexed ? nonzero(queue.userConfig, 0x24b, ~0u, "primitive restart (GE_MULTI_PRIM_IB_RESET_EN)") : std::string(); !reason.empty()) return reason; if (auto reason = nonzero(cx, 0x207, ~LayerExports, "clip distances, layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason; if (value(cx, 0x200, word)) { - // Tests against a bound surface are decoded (and their limits checked) by DecodeState. const bool surface = (word & 3u) != 0 && depthSurfaceBound(cx); if (!surface && !depthPassThrough(word) && !IgnoreDepthTest() && (word & DepthControlMask) != 0) return zeroMessage(0x200, word, "depth, stencil or conditional color writes"); if (auto reason = require((word & 0xc0000008u) == 0, "depth bounds or depth-conditional color writes are unsupported"); !reason.empty()) return reason; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index 48506c345..656239486 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -291,8 +291,6 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) { SurfaceGeometry geometry; if (descriptor.dimension == TextureDimension::k3D && (descriptor.tileMode == TextureTileMode::kD64KBX || descriptor.tileMode == TextureTileMode::kR64KBX)) { - // addrlib keeps display and rotated swizzles thin for 3D (only Z and standard ones are thick): - // each depth slice is laid out as an array slice, its index XORed in through the equation. Require(descriptor.mipCount == 1, "mipmapped 3D textures are not implemented"); const auto depth = descriptor.depthOrLastArray + 1u; geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, 1u); diff --git a/core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp b/core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp index 08c81ffb4..134e81a64 100644 --- a/core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp +++ b/core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp @@ -8,8 +8,6 @@ extern "C" { -// Tessellation runs as Vulkan tessellation stages, which keep hull-shader outputs and tessellation -// factors on the host GPU: the off-chip hull memory and the tessellation-factor ring are never read. int APS5_VABI sceAgcDriverSetHsOffchipParam(uint64_t value0, uint64_t value1, uint64_t value2) { (void)value0; (void)value1; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp index ee0209973..3af9d345d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp @@ -38,8 +38,6 @@ void RunBdaContractTests() { auto& block = program.CreateBlock(); block.AppendInstruction(&program.CreateValue(IrOpcode::Barrier, IrType::Void)); program.BlockOrder().push_back(&block); - // A program with workgroup barriers is accepted: its faulting reads return zero instead of - // ending the invocation, so every invocation still reaches the barriers. ValidateBdaTarget(program, target); AgcDriver::Graphics::Require(!BdaInvocationsMayStop(program), "a BDA program with barriers must keep its invocations running"); program.Resources().stage = IrShaderStage::TessellationControl; @@ -57,6 +55,5 @@ void RunBdaContractTests() { auto invalid = encoded; invalid[0] = invalid[0] == 'A' ? 'B' : 'A'; reject([&] { static_cast(serializer.Deserialize(invalid)); }, "request signature"); - // The signature followed by serialization version 99, in base64. reject([&] { static_cast(serializer.Deserialize("NVNQQWMAAAA=")); }, "serialization version"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp index bd5f7a609..484d60a7a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp @@ -9,7 +9,6 @@ #include #include -// Resources.cpp submits and reaps through the active recorder; these tests record nothing. namespace AgcDriver::Graphics { Recorder* Recorder::Active() { return nullptr; } void Recorder::Submit() { Require(false, "the device tests have no recorder"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index 5b4028eeb..ea44c3f7a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -35,12 +35,7 @@ ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) { return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false}; } -// Heap ranges that no import serves are mirrored. A read-only one: the build binds the mirror, the -// next build with the range unchanged copies nothing into it, and the blocks a CPU write changed are -// read again. A writable one: a descriptor's store reaches guest memory at write-back, and only the -// bytes the GPU changed (a CPU store made while the GPU works stays). void heapMirrorTests(const Context& context, const BdaTestAccess& access) { - // Heap mirrors refresh from the arena's write tracking; without it they are never made. Require(GuestArena::GuestArenaAvailable_nid_postfix() && GuestArena::GuestArenaWriteWatched_nid_postfix(), "the heap mirror tests need the write-watched guest arena"); constexpr std::size_t bytes = 2 * 65536; void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536); @@ -56,8 +51,6 @@ void heapMirrorTests(const Context& context, const BdaTestAccess& access) { else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); }; - // An address-based build that writes 32 bytes at `written` (nothing for 0), with `gpu` run - // between its upload and its write-back; returns the device address serving the range. const auto build = [&](std::uint64_t written, const std::function& gpu) { GuestBufferMemory leased(context); leased.AcquireRegistered(); @@ -71,7 +64,6 @@ void heapMirrorTests(const Context& context, const BdaTestAccess& access) { leased.WriteBack(); return device; }; - // An address-based build sweeps the mirrors whose ranges are gone (an earlier test's too). const auto sweep = [&] { GuestBufferMemory leased(context); leased.AcquireRegistered(); @@ -113,8 +105,6 @@ void heapMirrorTests(const Context& context, const BdaTestAccess& access) { Require(MirrorCounters().heapMirrors == before.heapMirrors, "the writable heap mirror outlived its range"); #ifdef _WIN32 - // A build that throws after queuing a mirror's changed blocks (another mirror's range became - // inaccessible) must not mark those blocks current: the next build copies them. { constexpr std::size_t half = 2 * 65536; void* pair = GuestArena::GuestArenaAllocate_nid_postfix(2 * half, 65536); @@ -145,7 +135,6 @@ void heapMirrorTests(const Context& context, const BdaTestAccess& access) { const auto device = firstDevice(); first[5] = 0x66; DWORD previous = 0; - // A protection change outside the registry, as a heap decommit is: the page cache is told. Require(VirtualProtect(second, 65536, PAGE_NOACCESS, &previous) != 0, "cannot protect the second heap range"); GuestAllocations::GuestAllocationsInvalidate_nid_postfix(reinterpret_cast(second), 65536); bool threw = false; @@ -166,10 +155,6 @@ void heapMirrorTests(const Context& context, const BdaTestAccess& access) { } #endif #ifdef _WIN32 - // A writable range with a read-only page (a direct memory page mapped at several addresses is - // read-only in every view): the mirror holds the page's bytes, a store next to it is written - // back, and a GPU change inside it fails the write-back instead of being dropped. A descriptor's - // copy outside an address-based build holds the page's bytes too. { constexpr std::size_t size = 2 * 65536; void* raw = GuestArena::GuestArenaAllocate_nid_postfix(size, 65536); @@ -304,7 +289,6 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { Require(range.begin == snapshots[0].address && range.end == range.begin + source.size(), "64-bit guest address was truncated"); const auto fault = access.bytes(access.descriptor(5).buffer); for (const auto byte : fault) Require(byte == std::byte{}, "fault buffer was not initialized"); - // A store into a read-only table range names the range and why stores cannot reach it. const ShaderRecompiler::BdaAbi::Fault denied{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::Permission, snapshots[0].address + 4, 4, 0, 0x88, 0}; std::memcpy(fault.data(), &denied, sizeof(denied)); reject([&] { resources.WriteBack(); }, "read-only in the BDA table"); @@ -372,7 +356,6 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { if (dropped.enabled) Require(dropped.waiterDrops == after.waiterDrops + 1 && LeaseCounters().cacheDrops == dropped.waiterDrops, "the free did not drop the cached address space"); } heapMirrorTests(context, access); - // The copies left to an address-based build: nothing within the limit, the largest first past it. Require(AddressCopyOverflow({{0x1000, 0x3000, 0x2000, "uncommitted pages"}}, 0x2000).empty(), "copies within the limit were refused"); const auto copies = AddressCopyOverflow({{0x1000, 0x2000, 0x1000, "not mirrored"}, {0x10000, 0x30000, 0x18000, "uncommitted pages"}}, 0x2000); Require(!copies.empty() && copies.find("0x10000+0x20000 (0.1 MiB committed, uncommitted pages)") < copies.find("0x1000+0x1000"), "the copy limit does not name the largest copy first"); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp index 157ec6e93..45d1d8f25 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp @@ -8,7 +8,6 @@ struct BdaTestAccess { std::function(VkBuffer)> bytes; std::function descriptor; - // The bytes of the buffer behind a device address, from that address on. std::function(VkDeviceAddress)> addressBytes; }; diff --git a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp index 5b3c912d5..f95863fdc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ColorTargetLayout.cpp @@ -59,7 +59,6 @@ void RunColorTargetLayoutTests() { } reject([&] { layout.Detile(std::span(tiled).first(4), restored); }); reject([&] { layout.Tile(std::span(linear).first(4), tiled); }); - // Aligned by hand: a PE section cannot ask for 64 KiB alignment. static std::vector storage(2 * 65536); const std::span guest(reinterpret_cast((reinterpret_cast(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}), 65536); std::fill(guest.begin(), guest.end(), std::byte{0x6b}); diff --git a/core/libs/prx/libSceAgcDriver/tests/DppWrite.cpp b/core/libs/prx/libSceAgcDriver/tests/DppWrite.cpp index 7955b83f1..0c5fccca7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DppWrite.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DppWrite.cpp @@ -1,5 +1,3 @@ -// A DPP instruction's row and bank masks gate its VGPR write, not only its source read: the rows a -// mask leaves out keep their value (a scan adding the other row's total into the odd rows only). #include "ControlFlow/GraphBuilder.hpp" #include "ControlFlow/Structurizer.hpp" #include "RdnaDecoder/RdnaInstructionDecoder.hpp" @@ -12,7 +10,6 @@ using namespace ShaderRecompiler; int main() { - // v_add_nc_u32_dpp v1, v0, v1 quad_perm:[0,1,2,3] row_mask:0xa bank_mask:0xf; s_endpgm const std::vector code{0x4a0202fau, 0xaf00e400u, 0xbf810000u}; try { const auto decoded = RdnaInstructionDecoder{}.Decode(code); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 752475279..0b172c699 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -99,7 +99,6 @@ void stateTests() { queue.context[0x91] = 0x30020; state = AgcDriver::Graphics::DecodeState(queue); Require(state.scissor.offset.x == 3 && state.scissor.offset.y == 1 && state.scissor.extent.width == 29 && state.scissor.extent.height == 2, "scissor intersection changed"); - // DCC_ENABLE: the target is written uncompressed, its keys at CB_COLOR0_DCC_BASE. queue.context[0x31c] |= 0x10000000; queue.context[0x325] = 0x1234; Require(AgcDriver::Graphics::DecodeState(queue).color.dccAddress == 0x123400, "DCC key address decode changed"); @@ -110,11 +109,9 @@ void stateTests() { queue.context[0x3b8] |= 5u << 14u; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "unsupported color tile mode"); queue = makeState(); - // A linear width off the 256-byte pitch alignment is padded (rows of 64 texels here). queue.context[0x3b0] = (62u << 14u) | 3u; Require(AgcDriver::Graphics::DecodeState(queue).color.bytes == 64u * 4u * 4u, "padded linear pitch changed"); queue = makeState(); - // Slot 1 written by the shader needs an export format. queue.context[0x8e] = 0xff; queue.context[0x8f] = 0xff; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "color export format 0"); @@ -238,8 +235,6 @@ void DisabledColorTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); } -// Depth/stencil tests against a bound surface: Scaleform's mask pass (stencil ALWAYS, add 1) and -// content pass (stencil EQUAL), as a Scaleform menu draws them. void DepthStencilTests() { auto queue = makeState(); queue.context[0x000] = 0; @@ -270,35 +265,29 @@ void DepthStencilTests() { const auto& front = state.stencilFront; Require(front.compareOp == VK_COMPARE_OP_ALWAYS && front.passOp == VK_STENCIL_OP_INCREMENT_AND_CLAMP && front.failOp == VK_STENCIL_OP_KEEP && front.reference == 1 && front.writeMask == 0xff, "stencil mask pass changed"); Require(std::memcmp(&state.stencilBack, &front, sizeof(front)) == 0, "back faces without BACKFACE_ENABLE must use the front state"); - // The content pass: EQUAL to 2, nothing written. queue.context[0x10b] = 0; queue.context[0x10c] = 0x01ffff02; queue.context[0x200] = 0x00200211; state = AgcDriver::Graphics::DecodeState(queue); Require(state.stencilFront.compareOp == VK_COMPARE_OP_EQUAL && state.stencilFront.reference == 2 && state.stencilFront.passOp == VK_STENCIL_OP_KEEP, "stencil content pass changed"); - // Clearing to 0 with REPLACE_TEST under ALWAYS. queue.context[0x10b] = 0x00030030; queue.context[0x10c] = 0x01ffff00; queue.context[0x200] = 0x00700771; state = AgcDriver::Graphics::DecodeState(queue); Require(state.stencilFront.passOp == VK_STENCIL_OP_REPLACE && state.stencilFront.reference == 0 && std::memcmp(&state.stencilBack, &state.stencilFront, sizeof(state.stencilFront)) == 0, "stencil clear pass changed"); - // BACKFACE_ENABLE: back faces get STENCILFUNC_BF, the _BF operations and DB_STENCILREFMASK_BF. queue.context[0x200] = 0x007007f1; state = AgcDriver::Graphics::DecodeState(queue); Require(state.stencilBack.compareOp == VK_COMPARE_OP_ALWAYS && state.stencilBack.passOp == VK_STENCIL_OP_KEEP && state.stencilBack.writeMask == 0 && state.stencilBack.reference == 1, "back-face stencil state changed"); - // REPLACE_OP writing a value the test compares differently has no single Vulkan reference. queue.context[0x10b] = 0x40; queue.context[0x10c] = 0x05ffff02; queue.context[0x200] = 0x00200211; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement"); - // Adding anything but 1 has no Vulkan operation. queue.context[0x10b] = 0x50; queue.context[0x200] = 0x00700711; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil add/subtract"); queue.context[0x10b] = 0; queue.context[0x000] = 1; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DB_RENDER_CONTROL"); - // CB_COLOR_VIEW's mip level is bits 26-29. queue = makeState(); queue.context[0x31b] = 1u << 26u; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "mip exceeds"); @@ -334,7 +323,6 @@ void DepthClipTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "inverted viewport depth clamp"); queue.context[0xb4] = 0; for (std::uint32_t bit = 0; bit < 32; ++bit) { - // DX_CLIP_SPACE_DEF (19) and ZCLIP_NEAR/FAR_DISABLE (26, 27, depth clamping) are decoded. if (bit == 19 || bit == 26 || bit == 27) continue; queue.context[0x204] = 1u << bit; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); @@ -666,7 +654,6 @@ void expectSingleFailure(const ShaderRecompiler::DescriptorBinding& binding, std expectResourceFailure(vertex, fragment, reason); } -// A binding the resources accept (building them throws nothing), leaving no Vulkan object behind. void expectSingleAccepted(const ShaderRecompiler::DescriptorBinding& binding, std::string_view what) { ShaderRecompiler::RecompileResult vertex; ShaderRecompiler::RecompileResult fragment; @@ -802,8 +789,6 @@ void resourceTests() { mutate(binding); return binding; }; - // Image and sampler bindings are implemented: a buffer's four-DWORD V# is no T#, and the mock - // context has no sampler slots, so a sampler binding exceeds its per-stage limit. expectSingleFailure(changed([](auto& binding) { binding.role = Role::GuestImages; binding.kind = Kind::SampledImage; }), "guest texture descriptor must contain 8 dwords"); expectSingleFailure(changed([](auto& binding) { binding.role = Role::GuestImages; binding.kind = Kind::StorageImage; }), "guest storage image descriptors must contain 8 dwords"); expectSingleFailure(changed([](auto& binding) { binding.role = Role::GuestSamplers; binding.kind = Kind::Sampler; }), "shader sampler descriptors exceed per-stage limits"); @@ -825,8 +810,6 @@ void resourceTests() { expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[3] |= 0x40000000u; }), "unsupported type"); expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[1] |= 0x3fffu << 16u; binding.guestDescriptor[2] = 0xffffffffu; }), "descriptor range limit"); expectSingleFailure(changed([](auto& binding) { binding.guestDescriptor[2] = 8192; }), "descriptor range limit"); - // A V# left pointing at unmapped memory binds its committed pages only (none here); the rest reads - // as zeros (GuestBufferMemory::addDescriptorRegion), so it is not rejected. expectSingleAccepted(changed([](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast(0x1000), 8); }), "an unmapped V#"); expectSingleFailure(changed([&](auto& binding) { binding.guestDescriptor = vsharp(reinterpret_cast(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(0x1000), 8)); }), "an unmapped V# element"); @@ -1051,8 +1034,6 @@ void rectListTests() { expectFailure([&] { static_cast(BuildRectListShaders(vertex, fragment, target)); }, "per-vertex interpolation"); fragment.fragmentParameters[0].perVertex = false; vertex.parameterExports.clear(); - // A fragment input the vertex shader does not export is built as a missing parameter (see - // RectListShaders.cpp), not rejected. auto unexported = BuildRectListShaders(vertex, fragment, target); Require(!unexported.control.spirv.empty() && !unexported.evaluation.spirv.empty(), "rect-list shaders with an unexported parameter are empty"); fragment.fragmentParameters.clear(); @@ -1234,8 +1215,6 @@ void validationTests() { } int main() { - // The guest allocation tests expect mutations of leased ranges to be refused; they need not wait - // the default minute for each refusal. Set before the registry first reads it. _putenv_s("APS5_PIN_WAIT_MS", "200"); try { { @@ -1273,7 +1252,6 @@ int main() { mock = MockVulkan{}; auto bdaContext = mockContext(); bdaContext.bufferDeviceAddress = true; - // The fault buffer (with its written-page slots) is larger than the mock's 4 KiB default. bdaContext.limits.maxStorageBufferRange = 1u << 27; RunBdaResourceTests(bdaContext, { [](VkBuffer buffer) -> std::span { return mock.memories.at(mock.bufferMemory.at(buffer)); }, @@ -1284,7 +1262,6 @@ int main() { throw std::runtime_error("missing BDA test descriptor"); }, [](VkDeviceAddress address) { - // The inverse of mockGetBufferDeviceAddress. const auto offset = address - 0x100000000000ULL; const auto buffer = reinterpret_cast(offset / 0x10000); return std::span(mock.memories.at(mock.bufferMemory.at(buffer))).subspan(offset % 0x10000); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp index 0e706600b..4af9a844c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp @@ -24,7 +24,6 @@ struct Fields { std::uint32_t filterMode = 0; bool disableDegamma = false; std::uint32_t minLodRaw = 0; - // MAX_LOD is 12 bits of unsigned 4.8 fixed point: 0xc00 is LOD 12. std::uint32_t maxLodRaw = 0xc00; std::uint32_t perfMip = 0; std::uint32_t perfZ = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index be1fe3d42..64116f4a8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -114,8 +114,6 @@ void RunGuestTextureResourceTests() { tileModes.tileModeRaw = 0x09; Require(DecodeTextureResource(pack(tileModes)).tileMode == TextureTileMode::kStandard64KB, "tile mode 0x09 must decode to standard 64KB"); tileModes.tileModeRaw = 0x1b; - // An XOR swizzle folds a pipe/bank XOR into the low base bits; only 64 KiB aligned bases are - // modeled, so the fixture's unaligned base is refused and an aligned one decodes. rejectFields(tileModes, "pipe/bank XOR base"); tileModes.base40 = 0x120000ull; Require(DecodeTextureResource(pack(tileModes)).tileMode == TextureTileMode::RenderTarget64KB, "tile mode 0x1b must decode to render target 64KB"); @@ -220,14 +218,11 @@ void RunGuestTextureResourceTests() { badMsaa.msaaDepth = true; rejectFields(badMsaa, "MSAA"); - // DCC block size overrides only matter to DCC metadata, which is not read; they decode. Fields blockSize = base; blockSize.maxUncompBlkSize = 1; blockSize.maxCompBlkSize = 1; Require(DecodeTextureResource(pack(blockSize)).baseAddress == DecodeTextureResource(pack(base)).baseAddress, "DCC block size overrides changed texture storage"); - // DCC metadata decodes: its address only with metadata compression on, and where alpha sits; the - // pipe alignment, write compression and color transform flags do not change what a read returns. Fields meta = base; meta.metaPipeAligned = true; meta.writeCompress = true; @@ -250,7 +245,6 @@ void RunGuestTextureResourceTests() { badLevels.maxMip = 1; rejectFields(badLevels, "base mip level past its last mip level"); - // A view may stop before the surface's last level: the surface keeps its mip count. Fields partialMips = base; partialMips.lastLevel = 1; partialMips.maxMip = 2; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 20c919a16..adaaafd60 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -343,8 +343,6 @@ void testMemorySynchronization() { } void testWriteChangedKeepsUntouchedBytes() { - // Write-back stores only the bytes the GPU changed: its other bytes are the snapshot, and the - // CPU may have written them since. alignas(256) static std::uint8_t guest[256]; std::memset(guest, 0, sizeof(guest)); std::vector original(sizeof(guest)), current(sizeof(guest)); diff --git a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp index ff4ec555f..197224a25 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Structurizer.cpp @@ -1,5 +1,3 @@ -// A selection in a loop with an arm that continues and another target it shares with an enclosing -// selection: its merge is where the other arm goes, so splitting the shared merge terminates. #include "ControlFlow/Structurizer.hpp" #include #include @@ -9,8 +7,6 @@ using namespace ShaderRecompiler; int main() { - // 0 -> 1 (loop header) -> 2; 2: 5 | 3; 3: 5 | 4; 4 -> 7 (continue); 5: 6 (exit) | 7; 6 returns; - // 7 -> 1. Around the back edge every path from 4 passes 5, so 5 post-dominates both selections. const std::vector> successors{{1}, {2}, {5, 3}, {5, 4}, {7}, {6, 7}, {}, {1}}; ControlFlowGraph graph; graph.entryBlock = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index 8afa74332..afda8cfc4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -88,15 +88,11 @@ void RunTextureTilingTests() { { const auto mips = ComputeMipLayout(TextureTileMode::RenderTarget64KB, 56, 257, 129, 1); Require(mips[0].blocksPerRow == 3 && mips[0].tiledSize == 393216, "render target surfaces must pad to complete 128 by 128 blocks for 32-bit pixels"); - // The detiler writes linear rows at the padded pitch (three 128-texel blocks), so the linear - // region spans whole padded rows. Require(mips[0].pitchBytes == 1536 && mips[0].linearSize == 1536u * 129u, "detiled render target rows must span the padded block width"); Require(ComputeSurfaceSize(mips, 6) == 2359296, "render target cube faces must retain the padded guest slice stride"); } for (const auto format : std::array{1, 7, 56, 71, 77}) { const auto mips = ComputeMipLayout(TextureTileMode::RenderTarget64KB, format, 1024, 513, 11); - // Linear offsets follow the tiled ones (the smallest regular level first), with the tail levels - // after the chain, so the ranges are checked for overlap in address order, not level order. std::vector> ranges; bool tailSeen = false; for (const auto& mip : mips) { @@ -113,11 +109,8 @@ void RunTextureTilingTests() { for (std::size_t index = 1; index < ranges.size(); ++index) Require(ranges[index].first >= ranges[index - 1].second, "detiled mip levels must occupy separate ranges"); Require(tailSeen && !mips.front().tail, "render target mip chains must cover both regular blocks and mip tails"); } - // Block-compressed formats are laid out in render target tiling too: a 64 by 64 texel level is one - // guest 64KB block. const auto compressed = ComputeMipLayout(TextureTileMode::RenderTarget64KB, 169, 64, 64, 1); Require(compressed.size() == 1 && compressed[0].tiledSize == 65536 && compressed[0].linearSize != 0, "block compressed render target layout is wrong"); - // Formats 128, 129 and 132 are laid out in render target tiling as well. Require(ComputeMipLayout(TextureTileMode::RenderTarget64KB, 132, 64, 64, 1).size() == 1, "format 132 render target layout is missing"); reject([] { ComputeMipLayout(TextureTileMode::RenderTarget64KB, 74, 64, 64, 1); }, "unsupported bytes per element"); diff --git a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp index 1cdb7a93e..dd73918e3 100644 --- a/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tools/ShaderReplay.cpp @@ -80,7 +80,6 @@ bool Replay(const char* path) { const auto result = ShaderRecompiler::Recompile(request.request); std::printf(" recompiled: %zu SPIR-V words\n", result.spirv.size()); if (g_spirv) { - // For spirv-val: .spv in the working directory. std::string name(path); name = name.substr(name.find_last_of("/\\") + 1) + ".spv"; std::ofstream(name, std::ios::binary).write(reinterpret_cast(result.spirv.data()), static_cast(result.spirv.size() * sizeof(result.spirv[0]))); diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 13c823926..4d6207075 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -97,7 +97,6 @@ struct Instance { std::uint64_t totalDecodedSamples = 0; SidebandGaplessDecode gapless{}; bool flagsReported = false; - // MP3: the FFmpeg decoder (it keeps the bit reservoir between frames) and the last frame's shape. AVCodecContext* mp3 = nullptr; std::uint32_t mp3Channels = 0; std::uint32_t mp3SampleRate = 0; @@ -246,7 +245,6 @@ std::vector JoinInputs(const JobHeader& job, const AjmBuffer* inpu return input; } -// The job's output buffers, filled in order. struct PcmOutputs { const AjmBuffer* buffers; std::uint32_t count; @@ -275,12 +273,10 @@ struct PcmOutputs { } }; -// Instance flags bits 7..9 select the PCM encoding: 0 16-bit, 1 32-bit integer, 2 float. std::uint32_t PcmEncoding(const Instance& instance) { return static_cast((instance.flags >> 7u) & 7u); } -// Emits one decoded frame of interleaved PCM after the instance's gapless skip and up to its sample limit. void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, std::size_t samples, std::size_t channels, std::size_t sampleBytes) { std::size_t first = 0; std::size_t count = samples; @@ -298,7 +294,6 @@ void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, instance.totalDecodedSamples += count; } -// The sidebands a run job asked for, in the SDK's order after its result. void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32_t result, std::size_t consumed, std::size_t produced, std::uint32_t frames, const SidebandFormat& format) { auto* sideband = static_cast(job.sideband); std::size_t offset = 0; @@ -321,8 +316,6 @@ void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32 } } -// Decodes AT9 frames from the concatenated inputs into the concatenated outputs in the instance's PCM -// encoding. void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { const auto input = JoinInputs(job, inputs); PcmOutputs pcmOutputs(outputs, job.outputCount); @@ -398,8 +391,6 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{static_cast(channels), ChannelMask(channels), static_cast(instance.info.samplingRate), encoding, 0, 0}); } -// An MPEG audio Layer III frame header: the frame's bytes and PCM shape; false when the bytes do not -// start one (free-format bitrates included). struct Mp3Frame { std::size_t bytes; std::uint32_t channels; @@ -437,8 +428,6 @@ void OpenMp3(Instance& instance) { if (!instance.mp3 || avcodec_open2(instance.mp3, codec, nullptr) < 0) throw std::runtime_error("AJM: cannot open the FFmpeg MP3 decoder"); } -// Decodes whole MP3 frames from the concatenated inputs. Like AT9 superframes, a frame is decoded only -// once all its bytes are present: a trailing partial frame stays unconsumed as partial input. void RunMp3(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { const auto input = JoinInputs(job, inputs); PcmOutputs pcmOutputs(outputs, job.outputCount); @@ -467,7 +456,6 @@ void RunMp3(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c if (frames == 0) result |= AJM_RESULT_NOT_ENOUGH_ROOM; break; } - // Not reference counted: the decoder copies the frame into a padded buffer of its own. packet->data = const_cast(input.data() + consumed); packet->size = static_cast(frame.bytes); if (avcodec_send_packet(instance.mp3, packet.get()) < 0) { @@ -620,7 +608,6 @@ int APS5_VABI sceAjmInstanceCreate(uint32_t context, uint32_t codec, uint64_t fl auto created = std::make_unique(); created->codec = codec; created->flags = flags; - // MP3 has no codec parameters: the stream's frame headers carry them, so it runs without an initialize job. if (codec == CODEC_MP3) { OpenMp3(*created); created->initialized = true; diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 1740dea13..3e38e5ad2 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -17,7 +17,6 @@ static constexpr char TEMPORARY_MOUNT_POINT[] = "/temp0"; static constexpr char DOWNLOAD_MOUNT_POINT[] = "/download0"; static constexpr uint32_t TEMPORARY_DATA_OPTION_FORMAT = 1; -// Temporary data lives in /temp0 on the host and survives until the title formats it. static std::filesystem::path TemporaryDirectory(const AppContentMountPoint* mount_point) { if (!mount_point || std::strncmp(mount_point->data, TEMPORARY_MOUNT_POINT, sizeof(mount_point->data)) != 0) APS5_INVALID_ARG_EX; return ResolvePath_nid_no_patch(TEMPORARY_MOUNT_POINT); @@ -62,7 +61,6 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount if (!mount_point || std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) APS5_INVALID_ARG_EX; const std::uint64_t quotaKb = GetAppDownloadDataSizeMiB_nid_postfix() * 1024u; if (quotaKb == 0) throw std::logic_error(std::string(__func__) + ": the title declares no download data"); - // The system mounts the title's download data before boot; on the host it is /download0. const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT); std::filesystem::create_directories(directory); std::uint64_t usedKb = 0; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index c1fccfd6e..04a0fc850 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -331,7 +331,6 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len if (type != PORT_TYPE_VIBRATION) { openDevice(port); } - // APS5_TRACE_AUDIOOUT=1 reports the ports a title opens. static const bool trace = std::getenv("APS5_TRACE_AUDIOOUT") != nullptr; if (trace) std::fprintf(stderr, "[audioout] port %d: type %d, %u samples at %u Hz, format %d, device %u\n", i + 1, type, len, freq, static_cast(format), static_cast(port.device)); return i + 1; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp index 86e9524cb..0ee974536 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Internal.hpp @@ -46,12 +46,8 @@ struct AudioOut2Port { std::uint32_t dataFormat = 0; std::uint32_t samplingFreq = 0; std::uint32_t flags = 0; - // Channel count and sample type decoded from dataFormat: float samples, or 16-bit integers. std::uint32_t channels = 0; bool int16 = false; - // The PCM buffer (one grain, interleaved) the title last handed over through the data attribute. - // It is guest memory the title rewrites every tick, so it is read when the context mixes, not - // when it is set. const void* data = nullptr; float volume[AUDIO_OUT2_PORT_CHANNELS_MAX] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; std::uint64_t dataSets = 0; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp index ea2f8a169..2cc479fe1 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp @@ -22,9 +22,6 @@ static constexpr std::uint64_t ATTRIBUTE_TRACE_EVERY = 2000; static constexpr std::uint32_t ATTRIBUTE_DATA = 0; static constexpr std::uint32_t ATTRIBUTE_VOLUME = 1; -// data_format bits 8..11 carry the channel count, bits 0..6 the sample type (0 float, 1 16-bit -// integer) and bit 7 the standard 8-channel order, as Kyty decodes it: the titles open 0x100 (mono -// object ports), 0x200 (stereo), 0x880 (7.1 bed) and 0x201 (a Bink movie's sound). static constexpr std::uint32_t FORMAT_CHANNELS_SHIFT = 8; static constexpr std::uint32_t FORMAT_CHANNELS_MASK = 0xFu; static constexpr std::uint32_t FORMAT_TYPE_MASK = 0x7Fu; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp index a8da0320c..9185d192e 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2SpeakerArray.cpp @@ -10,7 +10,6 @@ namespace { -// Layout of the params titles pass to sceAudioOut2SpeakerArrayCreate. struct GuestVbapParams { const AudioOut2Position* positions; std::uint32_t numSpeakers; @@ -24,7 +23,6 @@ constexpr std::uint32_t MaxSpeakers = 32; constexpr std::uint32_t AmbisonicsChannelFlag = 0x40; constexpr std::uint32_t MaxAmbisonicsOrder = 5; -// Lives in the memory the title allocates from sceAudioOut2GetSpeakerArrayMemorySize; the handle points at it. struct SpeakerArray { std::uint32_t magic; std::uint32_t numSpeakers; @@ -38,7 +36,6 @@ SpeakerArray& Array(AudioOut2SpeakerArrayHandle handle) { return *array; } -// Real SN3D spherical harmonic for ACN index `acn` (no Condon-Shortley phase), frame x front, y left, z up. float Sn3d(std::uint32_t acn, const AudioOut2Position& direction) { const int n = static_cast(std::sqrt(static_cast(acn))); const int m = static_cast(acn) - n * n - n; @@ -87,12 +84,9 @@ int APS5_VABI sceAudioOut2SpeakerArrayDestroy(AudioOut2SpeakerArrayHandle handle return 0; } -// Basic sampling decoder: speaker gain = sum over channels of Y(speaker) / numSpeakers, so a stereo pair decodes -// first order as two cardioids. Orders the layout cannot resolve (2D rule: 2N+1 speakers) get zero. int APS5_VABI sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(AudioOut2SpeakerArrayHandle handle, uint32_t ambisonics_channel, float* coefficients, uint32_t num_coefficients) { const auto& array = Array(handle); if (coefficients == nullptr || num_coefficients != array.numSpeakers) APS5_INVALID_ARG_EX; - // ponytail: only the 0x40-flagged encoding seen so far (assumed ACN/SN3D); other encodings throw until seen. const std::uint32_t acn = ambisonics_channel & ~AmbisonicsChannelFlag; if ((ambisonics_channel & AmbisonicsChannelFlag) == 0 || acn >= (MaxAmbisonicsOrder + 1) * (MaxAmbisonicsOrder + 1)) { throw std::invalid_argument("unsupported ambisonics channel " + std::to_string(ambisonics_channel)); diff --git a/core/libs/prx/libSceContentDelete/Export.cpp b/core/libs/prx/libSceContentDelete/Export.cpp index 7d77a1ffb..16dd322fb 100644 --- a/core/libs/prx/libSceContentDelete/Export.cpp +++ b/core/libs/prx/libSceContentDelete/Export.cpp @@ -7,7 +7,6 @@ #include namespace { -// Deletes exported content; the lifecycle is honest, deletion is not implemented. std::atomic g_initialized{false}; } diff --git a/core/libs/prx/libSceContentExport/Export.cpp b/core/libs/prx/libSceContentExport/Export.cpp index a7a90886b..aac09f3d1 100644 --- a/core/libs/prx/libSceContentExport/Export.cpp +++ b/core/libs/prx/libSceContentExport/Export.cpp @@ -7,7 +7,6 @@ #include namespace { -// Exports to the system gallery; the lifecycle is honest, exports themselves are not implemented. std::atomic g_initialized{false}; } diff --git a/core/libs/prx/libSceContentSearch/Export.cpp b/core/libs/prx/libSceContentSearch/Export.cpp index 0bd423b39..cfb70402a 100644 --- a/core/libs/prx/libSceContentSearch/Export.cpp +++ b/core/libs/prx/libSceContentSearch/Export.cpp @@ -7,7 +7,6 @@ #include namespace { -// Searches the system content library; the lifecycle is honest, searches are not implemented. std::atomic g_initialized{false}; } diff --git a/core/libs/prx/libSceGameLiveStreaming/Export.cpp b/core/libs/prx/libSceGameLiveStreaming/Export.cpp index a0401f1bb..be247e398 100644 --- a/core/libs/prx/libSceGameLiveStreaming/Export.cpp +++ b/core/libs/prx/libSceGameLiveStreaming/Export.cpp @@ -6,7 +6,6 @@ extern "C" { int APS5_VABI sceGameLiveStreamingInitialize(size_t heap_size) { - // Nothing is ever broadcast; the library only has to accept its lifecycle. if (heap_size == 0) APS5_INVALID_ARG_EX; return 0; } diff --git a/core/libs/prx/libSceIme/Export.cpp b/core/libs/prx/libSceIme/Export.cpp index c4303becc..b9d2bda7d 100644 --- a/core/libs/prx/libSceIme/Export.cpp +++ b/core/libs/prx/libSceIme/Export.cpp @@ -9,8 +9,6 @@ namespace { -// The host keyboard drives the virtual pad (docs/INPUT_MAPPING.md), so no USB keyboard is attached: -// opening the keyboard succeeds, as on a console with none plugged in, and it never reports events. std::mutex g_keyboardMutex; std::set g_openKeyboards; @@ -99,7 +97,6 @@ int APS5_VABI sceImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geome int APS5_VABI sceImeUpdate(EventHandler handler) { if (!handler) APS5_INVALID_ARG_EX; - // Only keyboard events could be pending (sceImeOpen is not implemented), and none are generated. return 0; } diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 208106db8..b2b70f0aa 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -6,7 +6,6 @@ #include "prx/libc/include/General.hpp" namespace { -// Common dialog status: 0 none, 1 initialized, 2 running, 3 finished. Nothing is ever opened here, so it never runs. std::atomic g_status{0}; } diff --git a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp index 8de1356bd..695705242 100644 --- a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp +++ b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp @@ -14,7 +14,6 @@ #include #endif -// The base game is the full SKU; the add-ons the account owns come from anyps5-entitlements.ini (below). static constexpr int SCE_NP_ENTITLEMENT_ACCESS_ERROR_PARAMETER = static_cast(0x80558003); static constexpr int SCE_NP_ENTITLEMENT_ACCESS_ERROR_NOT_FOUND = static_cast(0x80558007); static constexpr uint32_t SKU_FLAG_FULL = 3; @@ -33,10 +32,6 @@ std::filesystem::path EntitlementsPath() { #endif } -// The add-on entitlements the account owns: one unified entitlement label per line of -// anyps5-entitlements.ini next to the executable (ANYPS5_ENTITLEMENTS names another file), '#' and ';' -// starting comments. Without the file the account owns no add-on. They are reported as installed -// license-only add-ons (package type 3, download status 4), as Kyty reports them. const std::vector& OwnedAddons() { static const auto owned = [] { std::vector addons; diff --git a/core/libs/prx/libSceNpManager/Export.cpp b/core/libs/prx/libSceNpManager/Export.cpp index 87d0fd9f8..491eebc0c 100644 --- a/core/libs/prx/libSceNpManager/Export.cpp +++ b/core/libs/prx/libSceNpManager/Export.cpp @@ -128,28 +128,24 @@ void APS5_VABI sceNpRegisterGamePresenceCallback(void* callback, void* userdata) int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void* callback, void* userdata) { (void)userdata; - // The signed-out state never changes, so registered callbacks never fire. if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT; return 0; } int APS5_VABI sceNpRegisterPlusEventCallback(void* callback, void* userdata) { (void)userdata; - // The signed-out state never changes, so registered callbacks never fire. if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT; return 0; } int APS5_VABI sceNpRegisterPremiumEventCallback(void* callback, void* userdata) { (void)userdata; - // The signed-out state never changes, so registered callbacks never fire. if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT; return 0; } int APS5_VABI sceNpRegisterStateCallback(void* callback, void* userdata) { (void)userdata; - // The signed-out state never changes, so registered callbacks never fire. if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT; return 0; } diff --git a/core/libs/prx/libSceRemoteplay/Export.cpp b/core/libs/prx/libSceRemoteplay/Export.cpp index ca410a355..1c3e5bbad 100644 --- a/core/libs/prx/libSceRemoteplay/Export.cpp +++ b/core/libs/prx/libSceRemoteplay/Export.cpp @@ -5,7 +5,6 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -// A PC has no Remote Play: the library initializes and every user stays disconnected. constexpr int REMOTEPLAY_CONNECTION_STATUS_DISCONNECT = 0; extern "C" { diff --git a/core/libs/prx/libSceSharePlay/Export.cpp b/core/libs/prx/libSceSharePlay/Export.cpp index 4b4f66b2b..d0289f4bb 100644 --- a/core/libs/prx/libSceSharePlay/Export.cpp +++ b/core/libs/prx/libSceSharePlay/Export.cpp @@ -6,7 +6,6 @@ extern "C" { int APS5_VABI sceSharePlayInitialize(void* heap, size_t heap_size) { - // No Share Play session can ever start; the library only has to accept its lifecycle. if (heap == nullptr || heap_size == 0) APS5_INVALID_ARG_EX; return 0; } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index 45104221d..fcc4fa910 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -85,8 +85,6 @@ public: if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{}; std::lock_guard lock(cfg->mutex); checkConfig(*cfg); - // The limit was waited for by WaitForFlipRoom before the driver took its lock; concurrent - // submitters may overshoot it by their own flips. if (info.index >= 0) { request->buffer = cfg->buffers[info.index]; require(request->buffer.Occupied(), "flip buffer is not registered"); @@ -507,7 +505,6 @@ void VideoOutDriver::presentLoop(std::stop_token token) { } if (current) { current.reset(); - // processFlip released the flip's reservation: a submitter may be waiting for room. flipQueue->changed.notify_all(); } } diff --git a/core/libs/prx/libSceVideoRecordingP/Export.cpp b/core/libs/prx/libSceVideoRecordingP/Export.cpp index 599945ffb..9400810ba 100644 --- a/core/libs/prx/libSceVideoRecordingP/Export.cpp +++ b/core/libs/prx/libSceVideoRecordingP/Export.cpp @@ -10,8 +10,6 @@ #include namespace { -// Recording metadata the title sets for the system recorder (titles, permission levels, guard -// areas); kept as given. Nothing records, so recording itself is not implemented. std::mutex g_infoMutex; std::map> g_info; } diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 44e6e9967..735f0697e 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -9,8 +9,6 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -// A console with no headset and no voice peers: the library runs its lifecycle and keeps ports and -// their connections, but no voice ever arrives, so output ports stay empty. namespace { constexpr std::int32_t PortInDevice = 0; @@ -70,7 +68,6 @@ int APS5_VABI sceVoiceInit(VoiceInitParam* param, int32_t version) { } int APS5_VABI sceVoiceSetThreadsParams(void* params) { - // Thread priorities and affinities for the voice threads; this library runs none. if (params == nullptr) APS5_INVALID_ARG_EX; return 0; } @@ -176,8 +173,6 @@ int APS5_VABI sceVoiceGetPortInfo(uint32_t port_id, VoicePortInfo* info) { *info = VoicePortInfo{}; info->port_type = port.type; info->state = PortStateReady; - // Titles pace voice by frame (a title divides by the frame length it derives from this). - // ponytail: the codec's real frame layout is unknown; 20 ms frames at the port's bitrate. if (port.bitrate != 0) info->frame_size = (port.bitrate * VoiceFrameMs + 7999u) / 8000u; return 0; } @@ -196,7 +191,6 @@ int APS5_VABI sceVoiceReadFromOPort(uint32_t output_port_id, void* data, uint32_ auto& voice = State(); std::lock_guard lock(voice.mutex); if (IsInput(RequirePort(voice, output_port_id, __func__).type)) Fail(__func__, "port is not an output port"); - // Inputs are the absent microphone or WriteToIPort, which is not implemented: nothing to read. *size = 0; return 0; } diff --git a/core/libs/prx/libSceWebBrowserDialog/Export.cpp b/core/libs/prx/libSceWebBrowserDialog/Export.cpp index b26299820..416407839 100644 --- a/core/libs/prx/libSceWebBrowserDialog/Export.cpp +++ b/core/libs/prx/libSceWebBrowserDialog/Export.cpp @@ -6,7 +6,6 @@ #include "prx/libc/include/General.hpp" namespace { -// Common dialog status: 0 none, 1 initialized, 2 running, 3 finished. Nothing is ever opened here, so it never runs. std::atomic g_status{0}; } diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index d747e6f01..385b01298 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -5,7 +5,6 @@ #include // Guest virtual memory is placed inside one reserved arena below the PS5 application map limit -// (0xFC_0000_0000), in ascending first-fit order from the system-managed area like the PS5 kernel. Guest code indexes tables by // absolute address and breaks on host addresses outside that range. namespace GuestArena { diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 6e6d0b18a..19d02b612 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -202,7 +202,6 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo AppendUtf8(utf8, static_cast(args.Next())); output.Value(spec + 's', utf8.c_str()); } else if (conversion == 's' && (length.empty() || length == "h")) { - // FreeBSD's printf, the console's, prints a null string as "(null)". const char* value = args.Next(); if (!value) value = "(null)"; output.Value(spec + 's', value); diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 59d445d6c..744b07805 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -18,7 +18,6 @@ namespace { -// Guest long is 64-bit; MinGW's is 32-bit, so integer "l" conversions need "ll" on the host. std::string HostLength(const std::string& length, char conversion) { #ifdef _WIN32 if (length == "l" && std::strchr("diouxXn", conversion) != nullptr) return "ll"; @@ -28,8 +27,6 @@ std::string HostLength(const std::string& length, char conversion) { return length; } -// Guest scanf, one directive at a time through the host sscanf, so the guest arguments can come from any -// va_list flavor. secure adds the _s rules: %s, %c and %[ take a buffer capacity after the pointer. template int ScanGuest(const char* buffer, const char* format, bool secure, NextPointer nextPointer, NextCapacity nextCapacity) { if (buffer == nullptr || format == nullptr) return EOF; diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 8730c57f8..5d0dc4028 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -53,7 +53,6 @@ std::optional ResolveAlias(const std::string& guestPath) struct WorkingDirectory { std::mutex mutex; const std::filesystem::path root = std::filesystem::canonical(std::filesystem::current_path()); - // A PS5 process starts in /app0, so titles open game files by relative path. std::filesystem::path current = std::filesystem::is_directory(root / "app0") ? root / "app0" : root; }; WorkingDirectory& Directories() { static WorkingDirectory state; return state; } diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index 30a3896c4..8686e99a5 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -37,8 +37,6 @@ std::atomic generation{1}; std::atomic invalidator{nullptr}; std::atomic pinWaiter{nullptr}; -// How long a mutation of a leased range waits for the lease before it is refused: -// APS5_PIN_WAIT_MS, 60 s by default (tests that expect the refusal set it short). std::chrono::milliseconds pinWait() { static const std::chrono::milliseconds value{[] { const char* text = std::getenv("APS5_PIN_WAIT_MS"); @@ -210,9 +208,6 @@ void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* poi // this registry, so waiting with the lock held would deadlock them. The scan restarts after the // lock is retaken, since another mutation may have run meanwhile. Without a waiter the lease is // dropped by another thread (a synchronous draw or dispatch), so yielding suffices. A waiter - // round that finished nothing (the holder is not recorded GPU work) yields: the holder may be a - // driver thread inside a long Vulkan call (importing host memory takes a lease for its whole - // length), so every wait is bounded by the deadline only. auto* state = static_cast(mutation); const auto deadline = std::chrono::steady_clock::now() + pinWait(); int syncedRounds = 0; diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 19066a785..32efc3c02 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -15,10 +15,6 @@ namespace GuestArena { namespace { -// Mappings without an address go to the system-managed area from 0x2_0000_0000 first, as in Kyty and -// shadPS4; the application area from 0x10_0000_0000 is left to fixed mappings until that fills (titles -// map their virtual memory pages at a fixed window from 0x10_0000_0000). The system-reserved range -// between the two is never handed out. constexpr std::uintptr_t PreferredBase = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 0556f6e34..2339acf46 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -98,7 +98,6 @@ void APS5_VABI srand_nid_postfix(unsigned int seed) { } -// Dinkumware float classification codes returned by _FDtest and _FExp. constexpr short FloatDenorm = -2; constexpr short FloatFinite = -1; constexpr short FloatInfinite = 1; @@ -116,7 +115,6 @@ short APS5_VABI _FDtest_nid_postfix(const float* x) { } } -// Stores y * e^(*x) * 2^eoff in *x and classifies the result as 0 (zero or underflow), infinite, or finite. short APS5_VABI _FExp_nid_postfix(float* x, float y, short eoff) { *x = static_cast(std::ldexp(static_cast(y) * std::exp(static_cast(*x)), eoff)); if (*x == 0.0f) return 0; diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index 0f4fc3d5c..794e11600 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -104,7 +104,6 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() { throw std::runtime_error("_Throw_Cpp_error: C++ thread error " + std::to_string(code)); } -// The title's std::error_category object carries its own Dinkumware vtable and string ABI. const void* APS5_VABI _ZSt16generic_categoryv_nid_postfix() { NotImplemented_nid_no_patch(__func__); return nullptr; diff --git a/core/libs/prx/libc/src/StdioExtras.cpp b/core/libs/prx/libc/src/StdioExtras.cpp index a1b8d21d6..3f82b12fd 100644 --- a/core/libs/prx/libc/src/StdioExtras.cpp +++ b/core/libs/prx/libc/src/StdioExtras.cpp @@ -51,7 +51,6 @@ int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* forma std::abort(); } -// Dinkumware lconv as the title's libc lays it out: monetary strings, CHAR_MAX monetary flags, then numeric and message strings. struct GuestLconv { const char* monetary[7]; char monetaryFlags[14]; diff --git a/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp b/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp index 93a0ebdc8..dc5540138 100644 --- a/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp +++ b/core/libs/prx/libkernel/AppMetadata/include/AppMetadata.hpp @@ -22,7 +22,6 @@ AppTitle GetAppTitle_nid_postfix(); AppTitleId GetAppTitleId_nid_postfix(); bool HasAppIcon_nid_postfix(); AppIconData GetAppIconData_nid_postfix(); -// The download data quota param.json declares, in MiB (0 when none). std::uint64_t GetAppDownloadDataSizeMiB_nid_postfix(); } diff --git a/core/libs/prx/libkernel/AppMetadata/include/ParamJsonParser.hpp b/core/libs/prx/libkernel/AppMetadata/include/ParamJsonParser.hpp index 08b60e434..a52b5da50 100644 --- a/core/libs/prx/libkernel/AppMetadata/include/ParamJsonParser.hpp +++ b/core/libs/prx/libkernel/AppMetadata/include/ParamJsonParser.hpp @@ -8,7 +8,6 @@ struct ParsedParamJson { std::string title; std::string titleId; - // The download data quota in MiB; 0 when the title declares none. std::uint64_t downloadDataSizeMiB = 0; }; diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 8510e7b63..8d53f8bdd 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -187,7 +187,6 @@ int APS5_VABI sceKernelAprResolveFilepathsToIdsAndFileSizes(const char** paths, return 0; } -// The prefix is prepended to every path; titles pass "" with absolute /app0 paths. int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIdsAndFileSizes(const char* prefix, const char** paths, uint32_t count, uint32_t* ids, uint64_t* sizes, uint32_t* error_index) { if (!prefix || !paths || !ids) return _fail(GUEST_EINVAL); for (uint32_t index = 0; index < count; ++index) { @@ -223,8 +222,6 @@ int APS5_VABI sceKernelAprSubmitCommandBuffer(const Apr::CommandBufferObject* bu return 0; } -// Command buffers run synchronously, so a submission is complete (result 0) before its id is returned -// and waiting on it has nothing left to do. int APS5_VABI sceKernelAprSubmitCommandBufferAndGetId(const Apr::CommandBufferObject* buffer, uint32_t priority, uint32_t* id) { if (!id) return _fail(GUEST_EINVAL); const int result = sceKernelAprSubmitCommandBuffer(buffer, priority); diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 27546e6c4..22ebff5a3 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -219,17 +219,12 @@ void Trace(const char* format, ...) { va_end(args); } -// Direct memory keeps its contents across mappings: titles unmap physical pages and map them again at -// another address (titles move their pools that way) and read back what they wrote through the old one. -// The contents move by copying at unmap and map; a shared section with views (as in Kyty) would alias -// them for real but loses the write watching of the guest arena. struct DirectMapping { std::uintptr_t end; std::uint64_t phys; }; std::mutex g_directLock; std::map g_directMappings; -// Contents of unmapped direct memory by physical page; all-zero pages are left out. std::map> g_physPages; void ProtectOrThrow(std::uintptr_t address, std::size_t len, int nativeProt) { @@ -248,13 +243,8 @@ void SaveContents(std::uintptr_t address, std::size_t len, std::uint64_t phys) { } } -// A physical page mapped at more than one address has the same contents in every view, and every -// view lacks write access while it is shared (g_shared). -// ponytail: a write to a shared page faults; handing the page to the writing view on the fault, with -// the GPU driver's imports of the other views, is the upgrade if a title writes through an alias. std::set g_shared; -// The views of physPage in g_directMappings. std::vector Views(std::uint64_t physPage) { std::vector views; for (const auto& [base, mapping] : g_directMappings) { @@ -273,9 +263,6 @@ int SharedProtection(int nativeProt) { return nativeProt & ~PROT_WRITE; } -// Takes the shared page out of its sharing; the views left keep sharing it, or the last one gets its -// own protection back. The views must still hold the same bytes: a write that reached one of them -// (the GPU writes through its imports of guest memory) would have been lost to the others. void Unshare(std::uintptr_t page, std::uint64_t physPage) { const auto others = Views(physPage); if (others.empty()) throw std::runtime_error("shared direct memory page has no other view"); @@ -293,7 +280,6 @@ void Unshare(std::uintptr_t page, std::uint64_t physPage) { ProtectOrThrow(others.front(), PS5_PAGE_SIZE, last ? prot : SharedProtection(prot)); } -// Takes [start, end) out of the direct mappings; with save its bytes stay as the contents of its pages. void EraseMappings(std::uintptr_t start, std::uintptr_t end, bool save) { auto it = g_directMappings.lower_bound(start); if (it != g_directMappings.begin() && std::prev(it)->second.end > start) --it; @@ -317,7 +303,6 @@ void EraseMappings(std::uintptr_t start, std::uintptr_t end, bool save) { } } -// Records a fresh mapping of phys at address (zero-filled, already protected nativeProt) and brings its contents back. void AddMapping(std::uintptr_t address, std::size_t len, std::uint64_t phys, int nativeProt) { std::vector> shared; for (const auto& [base, mapping] : g_directMappings) { @@ -355,7 +340,6 @@ bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, const auto address = reinterpret_cast(addr); std::lock_guard lock(g_directLock); EraseMappings(address, address + len, true); - // The range shows the physical pages' contents now, not what was mapped there before. #ifdef _WIN32 if (physStart >= 0 && !VirtualFree(addr, len, MEM_DECOMMIT)) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualFree decommit failed"); if (!VirtualAlloc(addr, len, MEM_COMMIT, WinProtFromPosix(nativeProtection))) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualAlloc commit failed"); @@ -443,8 +427,6 @@ void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) return aligned; } -// SCE protections as the title set them: the host page protection keeps only the CPU bits, but titles -// read the GPU and AMPR bits back (a streamer reads straight into memory the APR may write). struct ProtectedRange { std::uintptr_t end; int prot; diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp index a71ffc684..73fde6bd6 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp @@ -11,9 +11,7 @@ static constexpr size_t PS5_PAGE_SIZE = 0x4000; int DirectMemoryAlloc(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physOut); void DirectMemoryFree(int64_t start, size_t len); -// Drops what the released physical range held and stops its live mappings from saving into it. void ForgetDirectMemory(int64_t start, size_t len); -// Finds the allocation containing offset, or with findNext the first one above it; false when there is none. bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* end, int* memoryType); size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit); int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, size_t alignment); @@ -21,7 +19,6 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags); int DoMprotect(const void* addr, size_t len, int prot); int DoMunmap(void* addr, size_t len); int DoReserveVirtual(void** addr, size_t len, size_t alignment); -// The SCE protection the title last mapped or protected addr with (GPU and AMPR bits included); false when unknown. bool GuestProtection(uintptr_t addr, int* prot); #endif \ No newline at end of file diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 0f7fabf6c..5a5d435f7 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -279,7 +279,6 @@ int APS5_VABI sceKernelMtypeprotect(const void* addr, size_t len, int type, int return 0; } -// The mapping that contains addr, as sceKernelVirtualQuery reports it without FIND_NEXT. int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot) { VirtualQueryInfo info{}; const int result = sceKernelVirtualQuery(addr, 0, &info, sizeof(info)); diff --git a/core/libs/prx/libkernel/DirectMemory/MemoryPool.hpp b/core/libs/prx/libkernel/DirectMemory/MemoryPool.hpp index 28ab864c9..b6e799e68 100644 --- a/core/libs/prx/libkernel/DirectMemory/MemoryPool.hpp +++ b/core/libs/prx/libkernel/DirectMemory/MemoryPool.hpp @@ -6,7 +6,6 @@ int DirectMemoryAlloc(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physOut); void DirectMemoryFree(int64_t start, size_t len); -// Finds the allocation containing offset, or with findNext the first one above it; false when there is none. bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* end, int* memoryType); #endif diff --git a/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp b/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp index 1bbb73f17..00c3b5895 100644 --- a/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp +++ b/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp @@ -4,15 +4,11 @@ #include #include -// Windows cannot open a directory as a file descriptor, so a guest open() of a directory gets a -// descriptor for NUL that is tracked here with its path and a getdents cursor. namespace File { int OpenDirectoryDescriptor(const std::filesystem::path& path); std::optional DirectoryDescriptorPath(int fd); void ForgetDirectoryDescriptor(int fd); -// Fills FreeBSD dirent records { u32 fileno; u16 reclen; u8 type; u8 namlen; char name[]; } (4-byte aligned). -// Returns the bytes written, or -1 when fd is not a directory descriptor. int ReadDirectoryDescriptor(int fd, char* buf, int nbytes); } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index e0dbf878e..4e35b2dd9 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -47,9 +47,6 @@ static int NativeFlock(int descriptor, int operation) { if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY; return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1; } -// Positioned I/O through OVERLAPPED is atomic, unlike seek + read, which races when streaming threads share a -// descriptor. ponytail: on a synchronous handle it still moves the file pointer to offset + n, which POSIX pread -// does not; a mixed read()/pread() user on one descriptor would notice. static std::int64_t NativePositioned(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write) { if (nbytes > static_cast(std::numeric_limits::max())) { throw std::runtime_error("NativePositioned: nbytes exceeds platform limit"); diff --git a/core/libs/prx/libkernel/Pthread/Posix/Xthreads.cpp b/core/libs/prx/libkernel/Pthread/Posix/Xthreads.cpp index 5dd4ab78a..ff45a2d56 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Xthreads.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Xthreads.cpp @@ -9,9 +9,6 @@ #include "../../Time/include/Time.hpp" #include "Common.hpp" -// Dinkumware C11 thread support as the title's own libc.prx implements it: each _Mtx_t/_Cnd_t is a -// pointer-sized slot holding the sce object, and results are _Thrd_* codes. - 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); @@ -37,7 +34,6 @@ int initResult(int result) { return result == 0 ? ThrdSuccess : result == SceEnomem ? ThrdNomem : ThrdError; } -// std::_Pad as laid out by the title's libc; _Go() is the first vtable slot. struct GuestPad { unsigned (APS5_VABI* const* vtable)(GuestPad*); PthreadCond cond; @@ -101,7 +97,6 @@ int APS5_VABI _Cnd_broadcast_nid_postfix(PthreadCond* cond) { return ThrdSuccess; } -// xtime has the KernelTimespec layout and is an absolute gettimeofday deadline. int APS5_VABI _Cnd_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* deadline) { KernelUseconds usec = 0; if (!PosixThread::RelativeMicroseconds(GuestRealtimeClock, deadline, &usec)) throw std::runtime_error("_Cnd_timedwait: invalid xtime"); @@ -120,7 +115,6 @@ Pthread APS5_VABI _Thrd_id_nid_postfix() { return scePthreadSelf(); } -// Microseconds since the epoch, not the 100 ns ticks of other Dinkumware ports. std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix() { KernelTimeval now{}; gettimeofday_nid_postfix(&now, nullptr); diff --git a/core/libs/prx/libkernel/Pthread/src/Attr.cpp b/core/libs/prx/libkernel/Pthread/src/Attr.cpp index 909bd5a44..5a50773e3 100644 --- a/core/libs/prx/libkernel/Pthread/src/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Attr.cpp @@ -57,7 +57,6 @@ int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksiz if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr"); if (stacksize < 16384) throw std::runtime_error(std::string(__func__) + ": too small"); #ifdef _WIN32 - // Like libthr, any size is kept as given; thread creation rounds it up to whole pages. if (stacksize > std::numeric_limits::max() - 0xffffu) throw std::runtime_error(std::string(__func__) + ": stack size exceeds the Windows limit"); #endif diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 575feb15a..be6727f14 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -228,7 +228,7 @@ int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_ int APS5_VABI shutdown_nid_postfix(int descriptor, int how) { const auto socket = Lookup(descriptor); if (!socket) return Fail(9); - if (how < 0 || how > 2) return Fail(22); // SHUT_RD/SHUT_WR/SHUT_RDWR share values with the host constants + if (how < 0 || how > 2) return Fail(22); return ::shutdown(socket->value, how) ? Fail(NativeError()) : 0; } int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 1daea6429..1efdef414 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -24,9 +24,6 @@ int APS5_VABI sceCoredumpUnregisterCoredumpHandler(void) { return 0; } -// A libc that fails to throw (no handler: the exception is uncaught) reports it here right before -// std::terminate, after its own stderr line; the console keeps it for the core dump, we print it. -// The second argument is 0x400 from the libc that calls it; its meaning is not known. int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_t unknown, const char* typeName, const char* what) { (void)unknown; std::fprintf(stderr, "[coredump] uncaught C++ exception %p of type %s%s%s\n", exception, typeName ? typeName : "(unknown)", what ? ", what(): " : "", what ? what : ""); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index c8a27e885..32cf4a341 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -62,7 +62,6 @@ static void CheckNamedAndHintedMappings() { Require(sceKernelMunmap(first, length) == 0); } -// Direct memory contents follow the physical pages: unmapped and mapped again elsewhere, they read back. static void CheckDirectMemoryFollowsPhysicalPages() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; diff --git a/core/relinker/codegen/src/InstructionScanner.cpp b/core/relinker/codegen/src/InstructionScanner.cpp index 94addfff7..ccd03f983 100644 --- a/core/relinker/codegen/src/InstructionScanner.cpp +++ b/core/relinker/codegen/src/InstructionScanner.cpp @@ -35,7 +35,6 @@ std::vector InstructionScanner::ScanCodeSection( try { length = decoder.Decode(cursor, available); } catch (const CodegenException& e) { - // Zero padding at the segment tail decodes as `add [rax], al` pairs; an odd tail leaves one truncated byte. if (std::all_of(cursor, cursor + available, [](const std::uint8_t b) { return b == 0; })) break; throw CodegenException(std::string("Cannot decode instruction: ") + e.what(), codeSectionOffset + i); } diff --git a/core/shader/recompiler/BdaAbi.hpp b/core/shader/recompiler/BdaAbi.hpp index 753a178b9..8632c87b9 100644 --- a/core/shader/recompiler/BdaAbi.hpp +++ b/core/shader/recompiler/BdaAbi.hpp @@ -45,10 +45,6 @@ static_assert(offsetof(Range, deviceAddress) == 16 && offsetof(Range, permission static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v && sizeof(Fault) == 32); static_assert(offsetof(Fault, address) == 8 && offsetof(Fault, instruction) == 24); -// After the Fault record, the fault buffer holds the pages the shader stored to through the table -// (Write permission), for the host to mark GPU-written once the work completed: an overflow word, -// then an open-addressed set of page number + 1 (0 is free), probed WrittenPageProbes slots from a -// multiplicative hash. A store that finds no slot sets the overflow word. inline constexpr std::uint32_t WrittenPageShift = 12; inline constexpr std::uint32_t WrittenPageSlots = 4096; inline constexpr std::uint32_t WrittenPageProbes = 8; diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index ee3c35b6e..0588b5da7 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -670,7 +670,6 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const writeGraphicsCompileContext(writer, *request.graphics); } writer.WriteBool(request.useCache); - // Version 3: a compute request's partial-group size. if (request.context.compute.has_value()) { for (const std::uint32_t value : request.context.compute->partialThreads) { writer.WriteU32(value); diff --git a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp index 8b7f2259f..9a138a090 100644 --- a/core/shader/recompiler/ControlFlow/src/Structurizer.cpp +++ b/core/shader/recompiler/ControlFlow/src/Structurizer.cpp @@ -288,7 +288,6 @@ bool canReachBefore(const ControlFlowGraph& graph, std::uint32_t start, std::uin return false; } -// Whether `start` reaches `target` without passing the loop's continue or merge block. bool reachesWithinIteration(const ControlFlowGraph& graph, const NaturalLoop& loop, std::uint32_t start, std::uint32_t target) { std::vector pending = {start}; std::vector visited(graph.blocks.size(), false); @@ -345,9 +344,6 @@ std::uint32_t findSelectionMerge(const ControlFlowGraph& graph, const BasicBlock if (isLoopControlGateway(graph, *loop, falseTarget) && graph.Dominates(block.id, falseTarget) && isInsideLoopConstruct(graph, *loop, trueTarget)) { return falseTarget; } - // An arm that reaches the post-dominator only in a later iteration (around the back edge) - // leaves the selection by continuing; the merge is where the other arm goes. Splitting the - // post-dominator off would leave it where it is, and the split repeats without end. if (globalMerge != InvalidControlFlowId) { const bool trueJoins = reachesWithinIteration(graph, *loop, trueTarget, globalMerge); const bool falseJoins = reachesWithinIteration(graph, *loop, falseTarget, globalMerge); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp index 227915e95..7ea1c1823 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp @@ -49,8 +49,6 @@ struct IrBindingLayout { std::uint32_t pushDataStartDword = PushData::NoStart; std::uint32_t memoryOffsetDword = 0; std::uint32_t memoryOffsetCount = 0; - // Three dwords after the memory offsets hold the partial-group dispatch size (ShaderInfo:: - // dispatchThreadLimit). bool dispatchThreadLimit = false; std::vector userDataRegisters; std::vector descriptors; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourceInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourceInfo.hpp index 5b4bed6d5..f5020e2a8 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourceInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourceInfo.hpp @@ -45,11 +45,7 @@ struct MemoryInfo { bool idxen = false; bool offen = false; bool planningOnly = false; - // A buffer load or store with GLC or DLC: it bypasses the caches another workgroup's stores - // may be stale in (a look-back polling a flag another workgroup publishes). bool coherent = false; - // A buffer access whose V# the program computes on the GPU (loaded at a runtime offset, say), - // so no binding can hold it: the shader decodes the V# and goes through the BDA page table. bool gpuDescriptor = false; bool operator==(const MemoryInfo& other) const = default; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp index 98d92896f..5d20bc6ac 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp @@ -25,10 +25,6 @@ struct BufferResource { bool atomic = false; bool formatted = false; bool scalar = false; - // Specialized: the V# has no records or is at address 0 (a null V#), so every access is out of - // bounds: loads read zero (or the format's default), stores and atomics are dropped. For no - // records that is the hardware's behavior; at address 0 an access would fault on hardware, so a - // working title never reaches such a slot. bool empty = false; bool operator==(const BufferResource& other) const = default; @@ -54,8 +50,6 @@ struct ImageResource { bool depthCompare = false; bool cube = false; bool r128 = false; - // Specialized: a 32-bit integer read of a depth plane (IsDepthBitsTexture), bound as the float - // depth view; the texel bits are the result, the swizzle's constants integers. bool depthBits = false; std::uint32_t indirectRoot = NoIndirectImage; std::uint32_t indirectMappingOffset = 0; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp index 6a53521ce..23fe56c39 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp @@ -32,10 +32,7 @@ struct ShaderInfo { bool instanceOffsetConflict = false; bool hasBitwiseXor = false; bool usesDma = false; - // Some access stores through the BDA page table (MemoryInfo::gpuDescriptor): the pages it - // writes are noted in the fault buffer (BdaAbi::WrittenPageSlots). bool bdaWrites = false; - // The entry reads the partial-group dispatch size (StageInputKind::DispatchThreadLimit). bool dispatchThreadLimit = false; bool operator==(const ShaderInfo& other) const = default; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp index bb5972d64..ec74234c2 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/StageIO.hpp @@ -33,8 +33,6 @@ enum class StageInputKind { LocalInvocationId, LocalInvocationIndex, GlobalInvocationId, - // Not an input: a partial-group dispatch's size in threads, from shader data - // (IrBindingLayout::DispatchThreadLimitDword). DispatchThreadLimit, Parameter, }; diff --git a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp index bb5d6f791..bac6a047a 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp @@ -9,7 +9,6 @@ namespace ShaderRecompiler { class DescriptorBindingBuilder { public: - // `partialThreads`: ShaderComputeStageInfo::partialThreads (zero outside compute). void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot, const std::array& partialThreads) const; void Populate(BindingAllocationResult& allocation, const ShaderInfo& info, IrShaderStage stage, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array& partialThreads) const; }; diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 97afa4e9a..44757fdc8 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -148,7 +148,6 @@ struct ShaderVertexInputInfo { struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo { bool groupId[3] = {false, false, false}; - // ShaderComputeStageInfo::PartialGroups: threads past the dispatch size start inactive. bool partialGroups = false; int threadIdsNum = 0; int workgroupRegister = 0; diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index a431d1f9a..4fb6ca805 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -507,7 +507,6 @@ void buildResourceSpecialization(const IrResourcePlan& plan, ResourceSnapshot& s entry.packedStride = packedStride; entry.descriptorFormat = buffer.formatted ? decoded.Format() : IrBufferFormat::Invalid; entry.descriptorSwizzle = buffer.formatted ? decoded.DstSelXYZW() : DstSel(4, 5, 6, 7); - // A V# at address 0 is a slot the shader does not reach (an access faults on hardware). entry.empty = decoded.GetSize() == 0u || decoded.Base48() == 0u; result.buffers.push_back(entry); } diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 23a5a76f0..c7ced39e6 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -524,10 +524,6 @@ private: source = InternSource(descriptor); } - // A raw dword load or store (1-4 dwords, or a scalar dword load) through a V# the SRT walker - // cannot compute: the program loads it at a GPU-computed offset (a descriptor heap) or builds - // it. No binding holds such a V#, so the shader decodes it and accesses memory through the BDA - // page table (MemoryInfo::gpuDescriptor). Other accesses keep failing in GetHandle. bool TakeGpuDescriptor(IrValue& inst, std::uint32_t memoryIndex) { const IrValue* handle = inst.Argument(0)->Resolve(); if (handle->Opcode() != IrOpcode::GetBufferResource || handle->ArgumentCount() != 4u) { @@ -540,7 +536,6 @@ private: return false; } const auto op = inst.Opcode(); - // A scalar buffer load (s_buffer_load) reads one dword per ReadConstBuffer. const bool load = op == IrOpcode::LoadBufferU32 || op == IrOpcode::LoadBufferU32x2 || op == IrOpcode::LoadBufferU32x3 || op == IrOpcode::LoadBufferU32x4 || op == IrOpcode::ReadConstBuffer; const bool store = op == IrOpcode::StoreBufferU32 || op == IrOpcode::StoreBufferU32x2 || op == IrOpcode::StoreBufferU32x3 || op == IrOpcode::StoreBufferU32x4; auto& memory = m_program.Resources().memoryInfo[memoryIndex]; @@ -690,7 +685,6 @@ private: void Collect(IrValue& inst) { const auto op = inst.Opcode(); if (op == IrOpcode::ImageBvhIntersectRay) { - // A ray query reads its node through the BDA page table (its T# is a run-time value). m_info.usesDma = true; return; } diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp index 12e355631..f043c6c55 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp @@ -20,9 +20,6 @@ enum class ImageType : std::uint32_t { [[nodiscard]] bool IsFmaskTextureFormat(IrBufferFormat format); [[nodiscard]] IrTextureNumericClass SampledTextureNumericClass(IrBufferFormat format); [[nodiscard]] IrBufferFormat RemapTextureFormat(IrBufferFormat format); -// A sampled T# (dwords 1 and 3) of a 32-bit integer format over a depth layout (a Z swizzle mode): -// the raw bits of a D32 depth plane. A host cannot view a depth image with an integer format, so -// the image binds its float depth view and the shader takes the texel bits (ImageResource::depthBits). [[nodiscard]] bool IsDepthBitsTexture(std::uint32_t word1, std::uint32_t word3); } diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp index eecd30628..e8d2f594c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaDescriptorFormat.cpp @@ -123,7 +123,6 @@ IrBufferFormat RemapTextureFormat(IrBufferFormat format) { bool IsDepthBitsTexture(std::uint32_t word1, std::uint32_t word3) { const auto format = static_cast((word1 >> 20u) & 0x1ffu); - // SW_4KB_Z, SW_64KB_Z, SW_64KB_Z_T, SW_4KB_Z_X, SW_64KB_Z_X. const auto swizzle = (word3 >> 20u) & 0x1fu; const bool depthLayout = swizzle == 4u || swizzle == 8u || swizzle == 16u || swizzle == 20u || swizzle == 24u; return depthLayout && (format == IrBufferFormat::Format32UInt || format == IrBufferFormat::Format32SInt); diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index c9499b82e..5d0aaf657 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -86,8 +86,6 @@ void validateFlags(std::uint32_t flags) { if ((flags & RdnaImageSampleFlagCd) != 0u) { throw std::runtime_error("unsupported image coarse derivative (_cd) address layout"); } - // The _a sample aliases (OPM set) keep the plain address layout: the adjustment only touches reserved - // bits of the sampler's dword 3, which the resource tracker canonicalizes (as in Kyty). const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { throw std::runtime_error("conflicting image LOD modes"); @@ -229,9 +227,6 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 18u) & 0x7Fu) | ((word0 & 1u) << 7u); const auto& info = lookupOpcode(opcode); - // Word0 bit 12 is UNORM, unnormalized addressing, which only a sampler reads: loads, stores and - // atomics address texels by integer anyway (the ISA wants it set on stores and atomics, and the - // RTIP ray queries require it). On a sample or gather it is not implemented. const auto reservedWord0 = info.sample || info.gather ? 0x000350C0u : 0x000340C0u; if ((word0 & reservedWord0) != 0u || (word1 & 0x3C000000u) != 0u) { char message[96]; @@ -262,8 +257,6 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span groupIdEnable; bool tgSizeEnable; std::uint32_t threadIdComponentCount; - // A dispatch sized in threads (USE_THREAD_DIMENSIONS) that is no whole number of workgroups: - // its size per axis, the last workgroup partial; zero otherwise. Variants key on whether it - // is set only; the size reaches the shader as shader data. std::array partialThreads; [[nodiscard]] bool PartialGroups() const { @@ -327,9 +324,6 @@ struct RecompileResult { SharedSpirv spirv; std::vector bindings; std::vector pushConstants; - // The shader data dword of the guest buffers' byte offsets, one byte per GuestBuffers element - // (four per dword), zero as populated: a driver binding an element below its view (the storage - // buffer offset alignment) stores the difference there, the shader adds it to every access. std::uint32_t memoryOffsetDword = 0; std::uint32_t bdaAbiVersion = 0; std::vector vertexAttributes; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 53eefef93..c28e6ce6f 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -18,7 +18,6 @@ struct SpirvRequirements { bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; - // A buffer load or store is coherent (MemoryInfo::coherent): the guest buffers are Coherent. bool coherentBuffers = false; std::vector capabilities; std::vector extensions; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp index f39119ea9..f7c278915 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp @@ -20,14 +20,10 @@ void ValidateBdaTarget(const IrProgram& program, const SpirvTargetOptions& targe bool BdaInvocationsMayStop(const IrProgram& program); void StopBdaInvocationIf(SpirvEmitterState& state, std::uint32_t condition); std::uint32_t EmitBdaRead(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bits); -// Stores a dword at a 4-aligned guest address through a range with Write permission and notes its -// page in the fault buffer's written set; a failure records its fault and stores nothing. void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value); // Reads the dwords of a 1-4 dword load at address + offset that the program extracts, with one // table lookup for the whole span; the per-byte lookups of EmitBdaRead remain the fallback (and // the only path under APS5_BDA_BYTE_READS=1). Dwords the program never extracts are neither read -// nor fault-checked, as when every dword was its own instruction; `everyDword` reads them all (the -// dwords are not the instruction's result). std::array EmitBdaDwordReads(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t offset, std::uint32_t dwords, bool everyDword = false); bool BdaByteReadsForced(); std::uint32_t AddBdaAddress(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t offset, bool subtract); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 8e555b598..497824dbf 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -48,7 +48,6 @@ struct ImageSampleLayout { std::uint32_t bias = NoImageComponent; std::uint32_t coord = 0; std::uint32_t lod = NoImageComponent; - // The _cl minimum level, after the coordinates. std::uint32_t clamp = NoImageComponent; std::uint32_t gradX = NoImageComponent; std::uint32_t gradY = NoImageComponent; @@ -67,7 +66,6 @@ struct MemoryResourceAccess { std::uint32_t indexOffset = 0; std::uint32_t byteOffset = 0; bool addIndexOffset = false; - // Memory operands of the loads and stores: Volatile for a coherent access (MemoryInfo::coherent). std::uint32_t memoryAccess = 0; }; @@ -101,8 +99,6 @@ struct SpirvEmitterState { // The lookup without fault recording that wide reads try first (see EmitBdaDwordReads); 0 when // every read takes the byte path. std::uint32_t bdaProbeFunction = 0; - // The lookup requiring Write and the written-page note of stores through the table (0 unless - // ShaderInfo::bdaWrites). std::uint32_t bdaWritePointerFunction = 0; std::uint32_t bdaNoteWriteFunction = 0; // False for programs with workgroup barriers: faulting BDA accesses then continue (see BdaInvocationsMayStop). diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index 4fd16c7db..467811218 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -31,10 +31,8 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { throw std::runtime_error("buffer operation has invalid memory metadata"); } const auto& memory = program.Resources().memoryInfo.at(memoryIndex); - // Through the BDA table a coherent access is a Volatile load or store (BdaAccessMask). requirements.coherentBuffers = requirements.coherentBuffers || (memory.coherent && !memory.gpuDescriptor); if (memory.gpuDescriptor) { - // The V#'s ADD_TID is known at run time only. if (program.Resources().stage != IrShaderStage::Compute) { throw std::runtime_error("GPU-selected buffer descriptors outside compute shaders are not implemented"); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaLookup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaLookup.cpp index 2ff55d064..62a7a4289 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaLookup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaLookup.cpp @@ -18,9 +18,6 @@ void ReturnBdaZeroIf(SpirvEmitterState& state, std::uint32_t condition) { } // (u64 address, u32 bytes, u32 instruction) -> u64 device address of the range holding all the bytes, -// or 0; the range must grant `permission`. With recordFaults the failure is published first; -// without, the caller retries byte-wise, and the fault is recorded there if the bytes are unmapped -// for real. std::uint32_t DefineBdaLookup(SpirvEmitterState& state, const char* name, bool recordFaults, std::uint32_t permission = BdaAbi::Read) { const auto u32 = TypeU32(state); const auto u64 = TypeScalarU64(state); @@ -119,8 +116,6 @@ std::uint32_t DefineBdaLookup(SpirvEmitterState& state, const char* name, bool r } -// (u64 address) -> void: notes the page of a store through the table in the fault buffer's written -// set (see BdaAbi::WrittenPageSlots). static std::uint32_t DefineBdaNoteWrite(SpirvEmitterState& state) { const auto u32 = TypeU32(state); const auto boolean = TypeBool(state); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index 93957c152..c42a895e1 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -16,8 +16,6 @@ void EmitBdaOverflowCheck(SpirvEmitterState& state, std::uint32_t address, std:: StopBdaInvocationIf(state, overflow); } -// The memory operands of a BDA load or store: Volatile for a coherent access (MemoryInfo::coherent), -// which must see what other workgroups store while the dispatch runs. std::uint32_t BdaAccessMask(const SpirvEmitterState& state, const IrValue& inst) { const auto index = inst.Flags().index; const auto& memory = state.program.Resources().memoryInfo; @@ -148,7 +146,6 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t const auto unaligned = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, 3u)), BdaConstant(state, 0u)); EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, 4u), instruction, BdaAbi::FaultReason::Unaligned); }); EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { - // A failed lookup (unmapped, or a range without Write) has recorded its fault. const auto physical = state.module.AllocateId(); state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, 4u), instruction); EmitIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), [&] { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 2158fc5c9..8162a4551 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -637,9 +637,6 @@ void EmitControlFlow(SpirvValueEmitContext& context, StructuredFunctionState& fu if (info == nullptr) { context.Fail("structured control flow block has no terminator metadata"); } - // A continue construct must reach its back edge (the back-edge block post-dominates the - // continue target), so a BDA fault in the continue target's instructions does not stop the - // invocation; the fault is recorded all the same. const bool stops = state.bdaStopsInvocations; state.bdaStopsInvocations = stops && !IsContinueTarget(program, info->id); EmitStructuredBlock(context, functionState, block); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 3792dd278..d02d65c3b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -301,8 +301,6 @@ std::uint32_t ResultVector(SpirvValueEmitContext& ctx, const ImageEmitAccess& ac component[index] = index == 0u ? F32BitsToU32(ctx, value) : ConstantU32(state, 0); continue; } - // Through a depth view read for its bits, only the depth channel (X) is a texel: the view - // gives 1.0 where the integer view gives 1 (ONE, or W of the single channel). const auto selector = (access.image.shaderSwizzle >> (index * 3u)) & 7u; if (access.image.depthBits && !gather && selector != 4u) { component[index] = ConstantU32(state, selector == 1u || selector == 7u ? 1u : 0u); @@ -789,7 +787,6 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityMinLod)) == capabilities.end()) ctx.Fail(access.inst, "clamps its LOD, which needs the device's shaderResourceMinLod"); const auto clamp = AddressF32(ctx, access, setup.layout.clamp); if ((operandMask & spv::ImageOperandsLodMask) != 0u) { - // An explicit level takes no MinLod operand: the level itself is clamped. const auto clamped = state.module.AllocateId(); state.module.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state), GLSLstd450FMax, operands.back(), clamp); operands.back() = clamped; @@ -957,11 +954,6 @@ void EmitImageAtomicXor32(SpirvValueEmitContext& ctx, const IrValue& inst) { EmitImage(ctx, inst); } -// image_bvh_intersect_ray tests one ray against one BVH node; the guest shader owns the traversal -// and its stack. This is the GFX10 (RT IP 1.1) node test as AMD's GPURT software fallback defines it -// (IntersectCommon.hlsl), with the node layouts of GPURT's gfx10 TriangleNode1_0 and BoxNode1_0, as -// SharpEmu ports it. The node is read through the BDA page table; an inactive lane, a null BVH, an -// out-of-range node or another node type returns four invalid dwords. void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; const auto u32 = TypeU32(state); @@ -999,10 +991,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto component = [&](std::uint32_t index) { return ctx.Arg(*address, index); }; const bool a16 = (ctx.Memory(inst).imageSampleFlags & RdnaImageSampleFlagA16) != 0u; - // The node pointer: its byte offset divided by eight above bits 2:0, which hold the type - // (0-1 triangle, 4 float16 box, 5 float32 box). The T#: base_address[39:0] in 256-byte - // units, box_grow_value[62:55], box_sort_en[63], size[105:64] (the last valid 64-byte - // node), triangle_return_mode[120]. const auto node = component(0); const auto type = op(spv::OpBitwiseAnd, u32, node, uint(7u)); const auto nodeIndex = op(spv::OpShiftRightLogical, u64, widen(node), BdaConstant(state, 3u)); @@ -1016,7 +1004,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto valid = both(present, op(spv::OpULessThanEqual, boolean, nodeIndex, lastNode)); const auto isTriangle = both(valid, op(spv::OpULessThanEqual, boolean, type, uint(1u))); const auto isBox16 = both(valid, op(spv::OpIEqual, boolean, type, uint(4u))); - // A float32 box node spans two 64-byte units. const auto isBox32 = both(both(present, op(spv::OpULessThan, boolean, nodeIndex, lastNode)), op(spv::OpIEqual, boolean, type, uint(5u))); const auto extent = asFloat(component(1)); @@ -1030,7 +1017,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { inverse[axis] = asFloat(component(8u + axis)); continue; } - // Six halves in three dwords: the direction, then the inverse direction. const auto half = [&](std::uint32_t index) { const auto packed = component(5u + index / 2u); return EmitF16BitsToF32(state, index % 2u == 0u ? op(spv::OpBitwiseAnd, u32, packed, uint(0xffffu)) : op(spv::OpShiftRightLogical, u32, packed, uint(16u))); @@ -1039,10 +1025,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { inverse[axis] = half(3u + axis); } - // The node's first `count` dwords (a multiple of four). A node lies in one range, so one - // probe of its span and direct loads read it; a node the table does not map as one aligned - // range records an Unmapped fault and reads as zeros. APS5_BDA_BYTE_READS=1 (no probe) - // reads it with the per-byte lookups. const auto nodeDwords = [&](std::uint32_t count) { std::vector dwords; if (state.bdaProbeFunction == 0) { @@ -1088,10 +1070,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { return op(spv::OpFAdd, f32, op(spv::OpFAdd, f32, op(spv::OpFMul, f32, a[0], b[0]), op(spv::OpFMul, f32, a[1], b[1])), op(spv::OpFMul, f32, a[2], b[2])); }; - // fast_intersect_triangle and SwizzleBarycentrics: type 0 tests (v0, v1, v2), type 1 tests - // (v1, v3, v2). A miss returns t_num = +inf over t_denom = 1. The barycentric return mode - // yields the i/j numerators the builder's rotation maps back; the other mode yields the - // triangle id and a hit flag. const auto triangle = [&] { const auto d = nodeDwords(16u); const auto second = op(spv::OpIEqual, boolean, type, uint(1u)); @@ -1131,12 +1109,8 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { return compose({asUint(tNum), asUint(tDenom), selectU(barycentrics, asUint(barycentric(0u)), triangleId), selectU(barycentrics, asUint(barycentric(2u)), selectU(hit, uint(1u), uint(0u)))}); }; - // IntersectNodeBvh4 with fast_intersect_bbox: a slab test clipped to [0, extent], a NaN - // interval misses, box_grow_value widens the exit time by that many 2^-24 steps, and the - // optional sort orders the hit children by entry time. const auto boxes = [&](bool fp16) { const auto d = nodeDwords(fp16 ? 16u : 28u); - // Bound 0-2 is the minimum, 3-5 the maximum of one child box, from dword 4. const auto bound = [&](std::uint32_t child, std::uint32_t index) { const auto position = child * 6u + index; if (!fp16) { @@ -1145,7 +1119,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto packed = d[4u + position / 2u]; return EmitF16BitsToF32(state, position % 2u == 0u ? op(spv::OpBitwiseAnd, u32, packed, uint(0xffffu)) : op(spv::OpShiftRightLogical, u32, packed, uint(16u))); }; - // HLSL max3/min3: a NaN operand propagates to the NaN check. const auto nanMax = [&](std::uint32_t a, std::uint32_t b) { return selectF(either(isNan(a), op(spv::OpFOrdGreaterThan, boolean, a, b)), a, b); }; const auto nanMin = [&](std::uint32_t a, std::uint32_t b) { return selectF(either(isNan(a), op(spv::OpFOrdLessThan, boolean, a, b)), a, b); }; const auto zero = real(0u); @@ -1171,7 +1144,6 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { children[child] = selectU(hit, d[child], invalid); keys[child] = minT; } - // The hardware sorting network; an invalid child always sinks. auto sorted = children; for (const auto [a, b] : std::array, 5>{{{0, 2}, {1, 3}, {0, 1}, {2, 3}, {1, 2}}}) { const auto bValid = op(spv::OpINotEqual, boolean, sorted[b], invalid); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index 068400061..1292d8e47 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -109,11 +109,9 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state access.objectPointer = state.module.AllocateId(); state.module.AddFunction(spv::OpAccessChain, pointerType, access.objectPointer, variable, ConstantU32(state, arrayIndex)); access.byteOffset = state.memoryByteOffsets.at(arrayIndex); - // A GLC/DLC access polls or publishes what other workgroups see: never cached or combined. access.memoryAccess = mem.coherent ? spv::MemoryAccessVolatileMask : 0u; access.length = state.module.AllocateId(); state.module.AddFunction(spv::OpArrayLength, TypeU32(state), access.length, access.objectPointer, 0u); - // A null V# binds a placeholder the shader must not touch: nothing is in bounds. if (mem.resource < state.program.Info().buffers.size() && state.program.Info().buffers[mem.resource].empty) { access.length = ConstantU32(state, 0u); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 39c27768a..110176243 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -357,8 +357,6 @@ void DefineDescriptors(SpirvEmitterState& state) { state.module.AddAnnotation(spv::OpDecorate, state.storageBufferU64Variable, spv::DecorationAliased); } if (state.requirements.coherentBuffers) { - // A coherent load polls what other workgroups store (RDNA stores reach L2 without - // GLC too): every alias of the buffers takes part in device-scope visibility. state.module.AddAnnotation(spv::OpDecorate, state.storageBufferVariable, spv::DecorationCoherent); if (state.storageBufferU64Variable != 0u) state.module.AddAnnotation(spv::OpDecorate, state.storageBufferU64Variable, spv::DecorationCoherent); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 6383809f0..1a9c0cf82 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -173,8 +173,6 @@ std::uint32_t DeviceAddressFromWords(SpirvEmitterState& state, std::uint32_t low return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low64, high64); } -// The byte offset of a buffer access from a V# known only at run time (stride and swizzle are ids), -// as BufferByteAddress computes it from a specialized one: {offset + immediate, full byte offset}. std::pair RuntimeBufferByteAddress(SpirvEmitterState& state, std::uint32_t index, std::uint32_t offset, std::uint32_t soffset, std::uint32_t immediate, std::uint32_t stride, std::uint32_t swizzle, std::uint32_t indexStride) { const auto u32 = TypeU32(state); const auto add = [&](std::uint32_t lhs, std::uint32_t rhs) { return Binary(state, spv::OpIAdd, u32, lhs, rhs); }; @@ -191,9 +189,6 @@ std::pair RuntimeBufferByteAddress(SpirvEmitterSta return {offset, add(Select(state, u32, swizzle, swizzled, linear), soffset)}; } -// Visits each dword of a raw access through a GPU-selected V# (MemoryInfo::gpuDescriptor, the four -// dwords are the handle's arguments) with its guest address and whether RDNA2 bounds checking -// (OOB_SELECT, per dword for raw vectors) lets it through; a V# of format 0 lets nothing through. template void ForEachGpuDescriptorDword(SpirvValueEmitContext& ctx, const IrValue& inst, const MemoryInfo& mem, std::uint32_t components, TFunction&& function) { auto& state = ctx.state; @@ -224,8 +219,6 @@ void ForEachGpuDescriptorDword(SpirvValueEmitContext& ctx, const IrValue& inst, const auto rawIndexInBounds = Binary(state, spv::OpULessThan, boolean, index, rawRecords); for (std::uint32_t component = 0; component < components; component++) { const auto [offset, byte] = RuntimeBufferByteAddress(state, index, ctx.Arg(inst, 2), soffset, mem.offset + component * 4u, stride, swizzle, field(word3, 21u, 2u)); - // OOB_SELECT 0: index and offset against the stride; 1: index only; 2: NUM_RECORDS nonzero; - // 3: raw, NUM_RECORDS reduced by SOFFSET first. const auto structured = AndCondition(state, indexInBounds, Binary(state, spv::OpULessThan, boolean, offset, stride)); const auto raw = AndCondition(state, scalarInBounds, Select(state, boolean, swizzle, AndCondition(state, rawIndexInBounds, hasDword(offset, stride)), hasDword(offset, rawRecords))); auto inBounds = Select(state, boolean, Binary(state, spv::OpIEqual, boolean, mode, ConstantU32(state, 0u)), structured, indexInBounds); @@ -661,7 +654,6 @@ const MemoryInfo& BufferMemory(SpirvValueEmitContext& ctx, const IrValue& inst) return mem; } -// A raw dword load or store (1-4 dwords) through a GPU-selected V#; false for a bound buffer. bool EmitGpuDescriptorAccess(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t components, bool store) { if (!ctx.Memory(inst).gpuDescriptor) { return false; @@ -1040,9 +1032,6 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; const auto address = Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1), ConstantU32(state, mem.offset)); if (mem.gpuDescriptor) { - // Through a GPU-selected V# (MemoryInfo::gpuDescriptor): the dword at the base plus the - // dword-aligned offset, zero past the buffer's STRIDE * NUM_RECORDS bytes (NUM_RECORDS for - // stride 0). const IrValue* handle = inst.Argument(0)->Resolve(); if (handle->Opcode() != IrOpcode::GetBufferResource || handle->ArgumentCount() != 4u) { ctx.Fail(inst, "has no GPU-selected V#"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index f82fedb04..f82cfba1a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -196,7 +196,6 @@ std::uint32_t ExportRawComponent(SpirvValueEmitContext& ctx, std::uint32_t vecto std::uint32_t ExportVector(SpirvValueEmitContext& ctx, std::uint32_t data, const ExportInfo& exp, bool uintOutput) { auto& state = ctx.state; if (exp.compr && !uintOutput) { - // SPI_SHADER_COL_FORMAT: 5 UNORM16_ABGR, 6 SNORM16_ABGR, otherwise FP16_ABGR. const auto mode = MrtOutputMode(state, exp); const auto unpack = mode == 5u ? GLSLstd450UnpackUnorm2x16 : mode == 6u ? GLSLstd450UnpackSnorm2x16 : GLSLstd450UnpackHalf2x16; std::array f32 {ConstantF32(state, 0u), ConstantF32(state, 0u), ConstantF32(state, 0u), ConstantF32(state, 0x3f800000u)}; @@ -226,7 +225,6 @@ std::uint32_t ExportVector(SpirvValueEmitContext& ctx, std::uint32_t data, const ConstantU32(state, 0u), ConstantU32(state, uintOutput ? 1u : 0x3f800000u), }; - // SPI_SHADER_COL_FORMAT 32_R, 32_GR and 32_AR export only those components; the rest read as 0, alpha 1. const auto mode = MrtOutputMode(state, exp); const auto exported = exp.en & (mode == 1u ? 0x1u : mode == 2u ? 0x3u : mode == 3u ? 0x9u : 0xfu); if (exp.compr) { diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 9080ff69b..c7a76848b 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -77,7 +77,6 @@ void TranslationContext::addU32(const RdnaInstruction& inst, bool vector, bool u ir.SetScc(carryOut.Value()); return; } - // A vector carry-in is the mask the third source names: VCC for VOP2, any SGPR pair for VOP3. const IrU1 carryIn = !vector ? IrU1(ir.GetScc()) : inst.sourceCount >= 3u ? readMask(sourceAt(inst, 2u)) : IrU1(ir.GetVcc()); const IrU32 carryInU32(ir.Select(carryIn.Value(), ir.Constant(1u), ir.Constant(0u))); IrValue& secondAdd = ir.Emit(IrOpcode::IAddCarry32, IrType::U32x2, {&sum.Value(), &carryInU32.Value()}); diff --git a/core/shader/recompiler/Translation/src/DispatchInstructions.cpp b/core/shader/recompiler/Translation/src/DispatchInstructions.cpp index f43a2bb10..6bfc43eca 100644 --- a/core/shader/recompiler/Translation/src/DispatchInstructions.cpp +++ b/core/shader/recompiler/Translation/src/DispatchInstructions.cpp @@ -13,9 +13,6 @@ void DispatchInstruction(IrBuilder& builder, const RdnaInstruction& instruction, } void TranslationContext::TranslateInstruction(const RdnaInstruction& decoded) { - // DPP masks the instruction's VGPR write as well as its source read (row and bank masks, lanes - // with no source lane without bound_ctrl keep their value): the destination takes the source's - // DPP fields for writeRawU32. RdnaInstruction instruction = decoded; instruction.destination = destinationOperand(decoded); currentOpcode = instruction.op; diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 4cfc32819..73813b189 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -200,8 +200,6 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO initialExec = &entryIr.ULessThan(builtin(StageInputKind::LocalInvocationIndex), entryIr.Constant(totalThreads)); } } - // A dispatch sized in threads launches a partial last workgroup; the host rounds it up to - // whole groups, so threads past the dispatch size start inactive. if (options.stage == ShaderStageKind::Compute && options.inputInfo.compute->partialGroups) { for (std::uint32_t axis = 0; axis < 3u; axis++) { initialExec = &entryIr.LogicalAnd(*initialExec, entryIr.ULessThan(builtin(StageInputKind::GlobalInvocationId, axis), builtin(StageInputKind::DispatchThreadLimit, axis))); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp index c24dc1ef6..da921ca2e 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp @@ -41,9 +41,6 @@ MemoryInfo imageMemoryInfoFromInstruction(const RdnaInstruction& inst) { } bool TranslationContext::imageBvhIntersectRay(const RdnaInstruction& inst) { - // The node test only: the guest shader owns the traversal and its stack. The T# is read at run - // time and the node through the BDA page table, so the access is address-based (kind Global); - // imageSampleFlags carries A16 (the direction and inverse direction as halves). if (inst.dataDwordCount != 4u || inst.imageD16) { throw std::runtime_error("image_bvh_intersect_ray returns four dwords"); } diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 1bda28419..135cc4281 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -36,7 +36,6 @@ RdnaOperand TranslationContext::destinationOperand(const RdnaInstruction& inst) return destination; } -// The destination read as a mac accumulator: its own value, which the instruction's DPP does not move. RdnaOperand TranslationContext::accumulatorOperand(const RdnaInstruction& inst) { RdnaOperand accumulator = inst.destination; accumulator.dpp = false;