diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index f6f206c78..96394c5d3 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -1285,16 +1285,19 @@ KYTY_CP_OP_PARSER(CpOpGetLodStats) { EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0038e00); const auto buffer_size = buffer[0]; - auto* dst = reinterpret_cast((buffer[1] & 0xffffffc0u) | + auto* dst = reinterpret_cast((buffer[1] & 0xffffffc0u) | (static_cast(buffer[2]) << 32u)); - if (dst != nullptr && buffer_size != 0) { - memset(dst, 0, buffer_size); - // Hack? - if (buffer_size >= sizeof(uint32_t)) { - auto* label = static_cast(dst); - *label = 1; + // Mip statistics are not collected: each of up to 256 2-dword slots after the 16-dword header + // reports its id (dword 0 bits 24-31) and finest sampled mip 15 (dword 1 bits 24-27), i.e. no + // samples. A non-zero dword 0 then marks the report complete. + if (dst != nullptr && buffer_size >= 64) { + const auto slots = std::min((buffer_size - 64) / 8, 256u); + for (uint32_t slot = 0; slot < slots; slot++) { + dst[16 + slot * 2] = slot << 24u; + dst[16 + slot * 2 + 1] = 15u << 24u; } + dst[0] = 1; } return 4; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ab2017ed8..2c6f62b00 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -52915,6 +52915,43 @@ void CheckPm4WaitResume(RenderContext &renderer) { std::printf("[host] %-32s ok\n", "Pm4WaitResume"); } +void CheckPm4LodStats(RenderContext &renderer) { + constexpr const char *name = "Pm4LodStats"; + GraphicsInitJmpTables(); + CommandProcessor processor(renderer, 0); + + // A 16-dword header, 256 two-dword slots and a guard the CP must leave unwritten. + constexpr uint32_t poison = 0xa5a5a5a5u; + alignas(64) std::array report{}; + std::array packet{}; + const auto run = [&](uint32_t size) { + report.fill(poison); + report[0] = 0; + CommandBufferLayout dcb{packet.data(), packet.data() + packet.size(), packet.data(), + packet.data() + packet.size(), nullptr, nullptr, 0}; + Pm4Execution execution; + return Gen5::AgcDcbGetLodStats(reinterpret_cast(&dcb), 0, + report.data(), size, 0, 0, 0, 0) == packet.data() && + processor.Process(execution, packet) == Pm4ProcessResult::Complete && report[0] != 0; + }; + // Slots below the count report no samples (finest mip 15); everything after them is untouched. + const auto empty_slots = [&](uint32_t slots) { + for (uint32_t slot = 0; slot < slots; slot++) { + if (report[16 + slot * 2] != slot << 24u || report[17 + slot * 2] != 15u << 24u) { + return false; + } + } + return std::all_of(report.begin() + 16 + slots * 2, report.end(), + [](uint32_t dw) { return dw == poison; }); + }; + + Require(name, "full report", run(64 + 256 * 8 + 16) && empty_slots(256), + "report did not mark exactly 256 slots unsampled"); + Require(name, "partial report", run(64 + 2 * 8 + 4) && empty_slots(2), + "report did not stop at the last whole slot in the buffer"); + std::printf("[host] %-32s ok\n", name); +} + void CheckPm4MemSemaphore(RenderContext &renderer) { constexpr const char *name = "Pm4MemSemaphore"; Loader::SymbolDatabase symbols; @@ -54030,6 +54067,7 @@ int main(int argc, char **argv) { CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer()); CheckPm4WaitPackets(vulkan.RuntimeRenderer()); CheckPm4WaitResume(vulkan.RuntimeRenderer()); + CheckPm4LodStats(vulkan.RuntimeRenderer()); CheckPm4MemSemaphore(vulkan.RuntimeRenderer()); return 0; } @@ -54341,6 +54379,7 @@ int main(int argc, char **argv) { CheckPm4ContextStateOperations(vulkan.RuntimeRenderer()); CheckPm4IndirectControlFlow(vulkan.RuntimeRenderer()); CheckPm4WaitResume(vulkan.RuntimeRenderer()); + CheckPm4LodStats(vulkan.RuntimeRenderer()); CheckPm4MemSemaphore(vulkan.RuntimeRenderer()); CheckPm4RewindResume(vulkan.RuntimeRenderer()); CheckEmbeddedFetchVertexOffset();