Merge pull request #4049 from Mouhand-Kaddo/fix/demons-readonly-resummarize
fix(agc): handle Demon's Souls read-only depth resummarize
This commit is contained in:
commit
eb91f4e860
|
|
@ -1021,6 +1021,12 @@ target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
|
|||
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_shader_mode_register_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_driver_readonly_resummarize COMMAND agc_driver_tests --readonly-resummarize)
|
||||
set_tests_properties(agc_driver_readonly_resummarize PROPERTIES TIMEOUT 10)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_readonly_resummarize PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_shader_mode_register COMMAND agc_shader_mode_register_tests)
|
||||
set_tests_properties(agc_shader_mode_register PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 20)
|
||||
if(WIN32)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ std::optional<DrawVerdict> Driver::precheckDraw(const QueueState& queue, const S
|
|||
localDevice->ColorMetadataPass(*pass);
|
||||
return DrawVerdict::Drawn;
|
||||
}
|
||||
if (Graphics::DepthResummarizeDrawsNothing(queue)) return DrawVerdict::Nothing;
|
||||
rejected = Graphics::DepthMaintenanceRejection(queue);
|
||||
if (!rejected.empty()) return DrawVerdict::Rejected;
|
||||
static const bool traceIndirectEnabled = std::getenv("APS5_TRACE_INDIRECT_DRAWS") != nullptr;
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ struct ColorMetadataPass {
|
|||
};
|
||||
std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue);
|
||||
std::string DepthMaintenanceRejection(const QueueState& queue);
|
||||
bool DepthResummarizeDrawsNothing(const QueueState& queue);
|
||||
// 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
|
||||
// pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict.
|
||||
|
|
|
|||
|
|
@ -576,7 +576,22 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool DepthResummarizeDrawsNothing(const QueueState& queue) {
|
||||
const auto& cx = queue.context;
|
||||
const auto control = find(cx, 0x000);
|
||||
const auto depthControl = find(cx, 0x200);
|
||||
const auto zInfo = find(cx, 0x010);
|
||||
const auto stencilInfo = find(cx, 0x011);
|
||||
const auto targetMask = find(cx, 0x8e);
|
||||
const auto shaderMask = find(cx, 0x8f);
|
||||
if (control == cx.end() || control->second != 0x10u || depthControl == cx.end() || zInfo == cx.end() || stencilInfo == cx.end() || targetMask == cx.end() || shaderMask == cx.end()) return false;
|
||||
const auto depth = depthControl->second;
|
||||
const auto format = zInfo->second & 3u;
|
||||
return (depth & 2u) != 0 && (depth & 0xc000000cu) == 0 && depthPassThrough(depth) && (format == 1u || format == 3u) && (stencilInfo->second & 1u) == 0 && ColorWriteMask(cx) == 0 && PixelProgramSkipped(queue);
|
||||
}
|
||||
|
||||
std::string DepthMaintenanceRejection(const QueueState& queue) {
|
||||
if (DepthResummarizeDrawsNothing(queue)) return {};
|
||||
const auto control = find(queue.context, 0x000);
|
||||
if (control == queue.context.end() || (control->second & ~0x2063u) == 0) return {};
|
||||
const auto depthControl = find(queue.context, 0x200);
|
||||
|
|
|
|||
|
|
@ -1299,6 +1299,32 @@ void depthMaintenanceTests() {
|
|||
auto absent = makeState();
|
||||
absent.context.erase(0x000);
|
||||
Require(AgcDriver::Graphics::DepthMaintenanceRejection(absent).empty(), "an absent depth control produced a maintenance verdict");
|
||||
auto readOnly = makeState();
|
||||
readOnly.context[0x000] = 0x10;
|
||||
readOnly.context[0x200] = 0x007007f3;
|
||||
readOnly.context[0x010] = 0xa0000183;
|
||||
readOnly.context[0x011] = 0x20000180;
|
||||
readOnly.context[0x8e] = 0xffffffff;
|
||||
readOnly.context[0x8f] = 0;
|
||||
readOnly.shader.erase(0x8);
|
||||
readOnly.shader.erase(0x9);
|
||||
Require(AgcDriver::Graphics::DepthMaintenanceRejection(readOnly).empty(), "a resummarize draw with pass-through read-only depth and no stencil plane was rejected");
|
||||
for (const auto [offset, value] : {std::pair{0x200u, 0x007007f7u}, {0x200u, 0x007007fbu}, {0x200u, 0x007007e3u}, {0x011u, 0x20000181u}, {0x8fu, 0xfu}, {0x000u, 0x11u}, {0x010u, 0xa0000182u}}) {
|
||||
auto changed = readOnly;
|
||||
changed.context[offset] = value;
|
||||
Require(!AgcDriver::Graphics::DepthMaintenanceRejection(changed).empty(), "a resummarize draw with surface work or unsupported depth passed");
|
||||
}
|
||||
auto pixel = readOnly;
|
||||
pixel.shader[0x8] = 1;
|
||||
pixel.shader[0x9] = 0;
|
||||
pixel.context[0x1c4] = 0;
|
||||
pixel.context[0x203] = 0x40;
|
||||
Require(!AgcDriver::Graphics::DepthMaintenanceRejection(pixel).empty(), "a resummarize draw with a running pixel stage passed");
|
||||
for (const auto offset : {0x200u, 0x010u, 0x011u, 0x8eu, 0x8fu}) {
|
||||
auto missing = readOnly;
|
||||
missing.context.erase(offset);
|
||||
Require(!AgcDriver::Graphics::DepthMaintenanceRejection(missing).empty(), "a resummarize draw without a required register passed");
|
||||
}
|
||||
}
|
||||
|
||||
// SPI_SHADER_Z_FORMAT (0x1c4) and the export enables of DB_SHADER_CONTROL (0x203): Z export needs a
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
|
|
@ -111,6 +112,17 @@ void testValidation() {
|
|||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
void testReadOnlyResummarize() {
|
||||
std::vector<std::uint32_t> commands;
|
||||
for (const auto [offset, value] : {std::pair{0x000u, 0x10u}, {0x200u, 0x007007f3u}, {0x010u, 0xa0000183u}, {0x011u, 0x20000180u}, {0x8eu, 0xffffffffu}, {0x8fu, 0u}, {0x1c4u, 0u}, {0x203u, 0u}}) {
|
||||
commands.insert(commands.end(), {0xc0016900u, offset, value});
|
||||
}
|
||||
commands.insert(commands.end(), {0xc0012d00u, 3u, 2u});
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
check(sceAgcDriverSubmitDcb(&packet) == 0, "read-only resummarize submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
void testClearState() {
|
||||
AgcDriver::QueueState graphics{{{0x20c, 1}}, {{0x10, 17}, {0x11, 23}}, {{0x242, 5}}};
|
||||
const auto shader = graphics.shader;
|
||||
|
|
@ -671,8 +683,14 @@ void testWorkerFailure() {
|
|||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--readonly-resummarize") {
|
||||
testReadOnlyResummarize();
|
||||
LibcRunShutdown_nid_postfix();
|
||||
std::puts("Read-only resummarize submission passed");
|
||||
return 0;
|
||||
}
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
alignas(256) std::array<std::uint32_t, 64> rawCode{};
|
||||
|
|
@ -766,6 +784,7 @@ int main() {
|
|||
testHeaderWithoutProgramAddress();
|
||||
testCopyIntoHostMemory();
|
||||
testRegisteredFloatMode();
|
||||
testReadOnlyResummarize();
|
||||
testWorkerFailure();
|
||||
check(expectFailure([] { LibcRunShutdown_nid_postfix(); }).find("required shader register") != std::string::npos, "shutdown lost worker failure");
|
||||
std::puts("AGC driver submit tests passed");
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require an ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and a later `s_setpc_b64` return reading that exact pair. A pair serves one call, or several calls whose regions are disjoint, each closed by the first return reading the pair after its target. Outside every region of its calls the pair may only be overwritten whole, by `s_mov_b32`, `s_mov_b64`, an SOP2 result or a scalar load, as the caller of a user-data callee does before each call. Regions must nest or be disjoint; recursion, other shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented.
|
||||
- [Relative vector addressing](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to the last register the program names in a zero-initialised function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window; the host compiler decides whether that array stays in registers or spills to scratch, which is not measured. A register in the window the program never wrote reads 0, as an unwritten SSA register does; the console's value is undefined. M0 is unsigned: an index at or past the window reads `v0` and drops its write, and `v_swaprel_b32` writes neither side when either is out of range, as measured on RDNA2 (gfx1035, `agc_driver_relative_indexing`). The window ends at the last register the program names rather than at the shader's VGPR allocation, which the recompiler does not receive, so a relative write past the named registers but inside the allocation is dropped where the hardware keeps it for a later relative read. Killzone: Liberation's PSP vertex program (PPSA11142, 3176 ISA words, 104 relative accesses over 154 registers) went from 649,515 to 392,333 SPIR-V words with this.
|
||||
- [User-data callees](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is a user-data pair, or a copy of one made with `s_mov_b32` or `s_mov_b64` in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to its `s_setpc_b64` on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten to `s_call_b64` to that copy. The words after the caller's last instruction are dropped; in the shaders seen they are metadata, and a caller that reads its program counter, which could address them, throws. A copy runs at another program counter than the callee, so a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, spans more than 64 KiB or is not readable throws. Callees are read again whenever a dispatch misses the dispatch cache; a dispatch that hits it reuses the linked program, so a title that rewrote a callee in place at the same address would keep running the old code.
|
||||
- [DB_RENDER_CONTROL resummarize draws](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing; any other resummarize, copy or decompress draw throws
|
||||
- [DB_RENDER_CONTROL resummarize draws](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing. A pure resummarize with pass-through D16/D32 depth, no depth writes or bounds test, no stencil plane, no color writes and no running pixel stage also draws nothing; other resummarize, copy or decompress draws throw.
|
||||
- [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives `PC + 4`, the branch goes to `PC + 4 + signext(SIMM16) * 4`, and `s_setpc_b64` of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation.
|
||||
- [sceVideodec2CreateDecoder](../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) decodes HEVC (codec type 974921) and VP9 (2382845) through FFmpeg, besides H.264. The tags come from SharpProspero's `Videodec2CodecType` and EVO-PLAYER-PS5's `sce_videodec2.h`, which records them from hardware runs. HEVC input is converted from length-prefixed NAL units to Annex B as for H.264, and VP9 frames are passed through. Output is the same 8-bit NV12 for every codec; a decoder for HEVC Main10 or VP9 Profile 2 is created, but a frame deeper than 8 bits throws, since the console's 10-bit output layout is unknown, and `QueryDecoderMemoryInfo` reports the 8-bit frame size for it. `sceVideodec2GetPictureInfo` and `sceVideodec2GetAvcPictureInfo` throw for HEVC and VP9 pictures, which use other layouts (`SceVideodec2HevcPictureInfo`, `SceVideodec2Vp9PictureInfo`), and `sceVideodec2GetHevcPictureInfo` and `sceVideodec2GetVp9PictureInfo` are not exported
|
||||
- [syslog](../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:<priority>] <message>`, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER`, and errno is preserved. `setlogmask` follows [FreeBSD 11](https://github.com/freebsd/freebsd-src/blob/8d2708de5d6112a492744d367cfdfb8fd219227a/lib/libc/gen/syslog.c): the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified. `openlog` and `closelog` are not implemented, so their identity and options are not applied
|
||||
|
|
|
|||
Loading…
Reference in New Issue