fix(agc): import native multisample depth whose HTILE is not ITERATE_256

The initial import of a native multisampled depth target only proceeds
when every HTILE word proves the same clear value, so no compressed Z
planes are read from guest memory. ITERATE_256 only changes how the DB
and the texture unit walk compressed MSAA Z planes (Mesa
ac_descriptors.c: "TC-compatible MSAA HTILE requires ITERATE_256").
The HTILE footprint (Addrlib) and the HTILE clear words (Mesa
radv_get_htile_fast_clear_value) do not depend on it. The import no
longer requires it; the texture's ITERATE_256 must still match the
target's.

Unblocks PPSA27836 (Onimusha: Way of the Sword), which samples a
four-sample D32 target with pipe-aligned HTILE and ITERATE_256 off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
simonbronkandClaude Opus 5.5 committed 2026-10-18 23:38:21 +02:00
1 parent 0bc3003f89
commit 752cca10fe
3 files changed
+13 -10

No files matched your search

@@ -93,7 +93,7 @@ DepthSamplingState prepareDepthSampling(const Context& context, const DepthTarge
if ((target.pipeBankXor & ~0xcf00u) != 0u) return refuse("unsupported native multisample depth XOR");
if (target.address == 0u || (target.address & 65535u) != 0u) return refuse("native multisample depth base is not aligned");
if (target.stencilAddress != 0u || target.htileStencil) return refuse("native multisample stencil import is not implemented");
if (target.htileAddress == 0u || (target.htileAddress & 255u) != 0u || !target.iterate256 || !target.htilePipeAligned || target.htileVrsEncoding != 0u) return refuse("native multisample depth initial import requires pipe-aligned ITERATE_256 HTILE without VRS");
if (target.htileAddress == 0u || (target.htileAddress & 255u) != 0u || !target.htilePipeAligned || target.htileVrsEncoding != 0u) return refuse("native multisample depth initial import requires pipe-aligned HTILE without VRS");
if (target.clearDepth != 0.0f && target.clearDepth != 1.0f) return refuse("native multisample depth initial clear value is not implemented");
state.initial = {{{target.address, static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u, target.samples)), 0u}, {target.htileAddress, static_cast<std::size_t>(HtileMetadataBytes(target.extent)), 0u}}};
for (const auto& range : state.initial) {
@@ -534,7 +534,7 @@ std::array<std::uint32_t, 8> DepthSampleWords(const DepthTarget& target) {
const auto format = target.format == VK_FORMAT_D16_UNORM ? 7u : 22u;
const auto count = static_cast<std::uint32_t>(std::countr_zero(target.samples));
const auto width = target.extent.width - 1u, height = target.extent.height - 1u;
return {static_cast<std::uint32_t>(base), static_cast<std::uint32_t>((base >> 32u) & 255u) | (format << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (14u << 28u) | (24u << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, (1u << 10u) | (1u << 19u) | (1u << 21u) | (static_cast<std::uint32_t>(metadata & 255u) << 24u), static_cast<std::uint32_t>(metadata >> 8u)};
return {static_cast<std::uint32_t>(base), static_cast<std::uint32_t>((base >> 32u) & 255u) | (format << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (14u << 28u) | (24u << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, (target.iterate256 ? 1u << 10u : 0u) | (1u << 19u) | (1u << 21u) | (static_cast<std::uint32_t>(metadata & 255u) << 24u), static_cast<std::uint32_t>(metadata >> 8u)};
}
bool DepthSupported(const Context& context, VkFormat format, std::uint32_t fragments) {
@@ -649,7 +649,7 @@ void CheckClearDispatch() {
}
}
void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, bool one, bool clearing = false, std::optional<bool> depthOnlyOverride = std::nullopt, ClearFault fault = ClearFault::None) {
void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, bool one, bool clearing = false, std::optional<bool> depthOnlyOverride = std::nullopt, ClearFault fault = ClearFault::None, bool iterate256 = true) {
const VkExtent2D extent{5u, 3u};
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments));
TargetMemory memory(bytes), metadata(65536u);
@@ -661,7 +661,8 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
target.depthSwizzle = 24u;
target.pipeBankXor = one ? 0x8c00u : 0u;
target.htileDepthOnly = depthOnly;
target.iterate256 = target.htilePipeAligned = true;
target.iterate256 = iterate256;
target.htilePipeAligned = true;
Recorder recorder(context);
recorder.Activate();
struct Cleanup {
@@ -814,7 +815,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
case 8u: changed.pipeBankXor ^= 0x100u; break;
case 9u: changed.dccAddress += 256u; break;
case 10u: changed.dccPipeAligned = false; break;
case 11u: changed.iterate256 = false; break;
case 11u: changed.iterate256 = !changed.iterate256; break;
case 12u: changed.allocatedMipCount = 2u; break;
case 13u: changed.mipCount = 2u; break;
case 14u: changed.baseLevel = 1u; break;
@@ -829,7 +830,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
TargetRefuses([&] { DepthSurfaceHolds(context, resource, texture.get()); }, "guest writes");
texture.reset();
arrayTexture.reset();
std::printf("Native depth format=%u samples=%u clear=%u: imported, rasterized twice, sampled and refused conflicts\n", static_cast<std::uint32_t>(format), fragments, one);
std::printf("Native depth format=%u samples=%u clear=%u iterate256=%u: imported, rasterized twice, sampled and refused conflicts\n", static_cast<std::uint32_t>(format), fragments, one, iterate256);
}
void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments) {
@@ -895,7 +896,7 @@ void CheckEligibility(Context context) {
case 2u: invalid.lastSlice = 1u; reason = "arrays"; break;
case 3u: invalid.pipeBankXor = 0x10u; reason = "XOR"; break;
case 4u: invalid.htileAddress = 0u; reason = "HTILE"; break;
case 5u: invalid.iterate256 = false; reason = "HTILE"; break;
case 5u: invalid.htileAddress += 16u; reason = "HTILE"; break;
case 6u: invalid.htilePipeAligned = false; reason = "HTILE"; break;
case 7u: invalid.htileVrsEncoding = 1u; reason = "VRS"; break;
case 8u: invalid.clearDepth = 0.5f; reason = "clear value"; break;
@@ -1161,8 +1162,10 @@ int main(int argc, char** argv) {
std::uint32_t checked = 0u, unavailable = 0u;
for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT}) {
for (const auto samples : {2u, 4u, 8u}) {
if (!DepthSupported(context, format, samples)) { unavailable += 2u; continue; }
for (const bool one : {false, true}) { CheckDepthTarget(context, format, samples, one); ++checked; }
if (!DepthSupported(context, format, samples)) { unavailable += 4u; continue; }
for (const bool one : {false, true}) {
for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, iterate256); ++checked; }
}
CheckHtileRouting(context, format, samples);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@
- [Native multisampled depth clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accept the recognized HTILE mask kernel's direct full-range dispatch after its GPU writes complete. Every metadata byte needs tracked driver-store coverage since notification, no intervening CPU or depth write, and HTILE words proving the current clear value of zero or one. Partial, overlapping, indirect and unproven producers remain unsupported. Existing sampled views retain the native image; guest depth publication and metadata-fill transitions remain unimplemented. Windows shared imports retain write tracking through separate aliases; private host imports that lose tracking and copied or mirrored HTILE uploads remain unsupported. Clear-word encoding and console equivalence remain unmeasured on PS5 hardware.
- [Native depth allocation retirement](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) covers sampling-eligible multisampled owners only. Successful release notifications must cumulatively cover both complete depth and HTILE ranges; protection changes, failed releases and partial coverage keep the original guards. Retired owners leave lookup and metadata routing, while held views and recorded work retain their images. Retirement of single-sample or sampling-ineligible depth attachments and console equivalence remain unimplemented or unmeasured. The regression uses real allocation-registry operations with a no-op host-release callback, so it does not validate the host unmap backend.
- [Native MSAA depth attachment feedback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) is not implemented. A draw sampling a native multisampled depth view while attaching its source image throws before pipeline creation or render-pass recording, including read-only attachment feedback. Sampling that image in a separate draw or dispatch remains supported. The refusal compares the held native image views and their device and attachment identity; single-sample depth behavior is retained.
- [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, two/four/eight samples, one Z_X slice and mip, and pipe-aligned ITERATE_256 HTILE without stencil or VRS. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one, and the complete sample-aware depth and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes, and unproven HTILE clear/fill transitions. Guest publication, arbitrary guest depth import, stencil/raw-bit sampling, arrays and storage access remain unimplemented. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729).
- [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE without stencil or VRS, with ITERATE_256 on or off: the initial import reads no compressed Z planes, and Mesa's HTILE clear words (`radv_get_htile_fast_clear_value`) and Addrlib's HTILE footprint do not depend on ITERATE_256, which `ac_descriptors.c` ties to TC reads of compressed MSAA HTILE. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one, and the complete sample-aware depth and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes, and unproven HTILE clear/fill transitions. Guest publication, arbitrary guest depth import, stencil/raw-bit sampling, arrays and storage access remain unimplemented. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729).
- [sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the five validation calls return `0x8A6C1000` without reading their arguments or writing caller memory, and `sceAgcDriverGetShaderDebuggingStatus` returns 1, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L250-L291) reads them from Sony's libSceAgcDriver.sprx: each validation export is `mov eax, 0x8a6c1000; ret` in the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them
- [Indices far past every vertex fetch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of `0xffffffff` with primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice
- [Depth clear draws](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a draw with `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` is taken to write `DB_DEPTH_CLEAR` to every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured