Merge pull request #5524 from Amoenus/codex/native-depth-allocation-retirement

fix(agc): retire fully released native depth owners
This commit is contained in:
Adrià authored and GitHub committed 2026-10-15 13:50:13 +02:00
commit fdea36b836
7 files changed
+213 -18

No files matched your search

@@ -1525,6 +1525,11 @@ if(BUILD_TESTING)
endif()
add_test(NAME agc_sparse_buffer_holes COMMAND agc_driver_graphics_tests --sparse-buffer-holes)
set_tests_properties(agc_driver_native_multisample_depth PROPERTIES TIMEOUT 30)
add_test(NAME agc_driver_native_depth_retirement COMMAND agc_driver_native_multisample_depth_tests --retirement)
set_tests_properties(agc_driver_native_depth_retirement PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_native_depth_retirement PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_sparse_buffer_holes PROPERTIES TIMEOUT 30)
set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30)
if(WIN32)
@@ -20,6 +20,8 @@ std::uint64_t DepthSurfacesSerial();
std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel);
std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32_t bytesPerTexel, std::uint32_t samples);
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes);
bool HasResidentDepthGuards_nid_no_patch();
void RetireUnmappedDepth_nid_no_patch(std::uintptr_t address, std::size_t bytes);
void RequireNoNativeDepthFeedback_nid_no_patch(const Context& context, const DepthTarget& target, std::span<const std::shared_ptr<Texture>> sampled);
void ClearDepthSurfaces(VkDevice device);
void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t bytes);
@@ -59,6 +59,7 @@ bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std:
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
void ClearMultisampleTargets(VkDevice device);
void InstallResidentImageReadGuard_nid_no_patch();
void UpdateResidentImageUnmapObserver_nid_no_patch();
void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access);
}
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
@@ -46,6 +47,8 @@ struct DepthGuard {
VkDevice device;
std::uint64_t address;
std::size_t bytes;
std::shared_ptr<std::atomic<bool>> active;
std::vector<GuestMemory::UnchangedQuery> live;
};
std::shared_mutex& depthGuardMutex() {
@@ -196,6 +199,7 @@ public:
void CheckCurrent() const {
if (target.samples == 1u) return;
Require(active->load(std::memory_order_acquire), "the native multisample depth owner was released");
Require(sampling.refusal.empty(), sampling.refusal);
for (const auto& range : sampling.initial) {
Require(GuestMemory::Accessible(reinterpret_cast<const void*>(range.address), range.bytes) && GuestMemory::Watched(range.address, range.bytes), "native multisample depth requires watched accessible guest memory");
@@ -436,6 +440,7 @@ public:
VkImage image = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
std::shared_ptr<std::atomic<bool>> active = std::make_shared<std::atomic<bool>>(true);
bool sampleable = true;
bool samplingActive = false;
DepthSamplingState sampling;
@@ -822,7 +827,7 @@ PlaneOwners& stencilOwners() {
void takePlane(DepthSurface& surface, PlaneOwners& owners, std::uint64_t address, VkImageAspectFlagBits aspect) {
auto& entry = owners[{surface.context.device, address}];
auto* owner = entry.surface;
if (owner != nullptr && owner != &surface && owner->target.format == surface.target.format && owner->target.samples == surface.target.samples && owner->target.extent.width == surface.target.extent.width && owner->target.extent.height == surface.target.extent.height) {
if (owner != nullptr && owner->active->load(std::memory_order_acquire) && owner != &surface && owner->target.format == surface.target.format && owner->target.samples == surface.target.samples && owner->target.extent.width == surface.target.extent.width && owner->target.extent.height == surface.target.extent.height) {
if ((owner->pendingClear & aspect) != 0) surface.pendingClear |= aspect;
else surface.CopyAspectFrom(*owner, aspect);
}
@@ -837,23 +842,23 @@ void takePlanes(DepthSurface& surface) {
DepthSurface* depthSlice(VkDevice device, std::uint64_t address, const DepthTarget& base) {
if (const auto found = depthOwners().find({device, address}); found != depthOwners().end()) {
const auto& target = found->second.surface->target;
if (target.extent.width == base.extent.width && target.extent.height == base.extent.height && target.format == base.format) return found->second.surface;
if (found->second.surface->active->load(std::memory_order_acquire) && target.extent.width == base.extent.width && target.extent.height == base.extent.height && target.format == base.format) return found->second.surface;
}
const auto& list = surfaces();
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == device && !surface->retired && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format; });
const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->active->load(std::memory_order_acquire) && surface->context.device == device && !surface->retired && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format; });
return slice == list.rend() ? nullptr : slice->get();
}
DepthSurface* planeSurface(VkDevice device, std::uint64_t address) {
const auto depth = depthOwners().find({device, address});
const auto stencil = stencilOwners().find({device, address});
const bool hasDepth = depth != depthOwners().end();
const bool hasStencil = stencil != stencilOwners().end();
const bool hasDepth = depth != depthOwners().end() && depth->second.surface->active->load(std::memory_order_acquire);
const bool hasStencil = stencil != stencilOwners().end() && stencil->second.surface->active->load(std::memory_order_acquire);
if (hasDepth && (!hasStencil || depth->second.serial > stencil->second.serial)) return depth->second.surface;
if (hasStencil) return stencil->second.surface;
const auto& list = surfaces();
const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) {
return surface->context.device == device && !surface->retired && (surface->target.address == address || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == address));
return surface->active->load(std::memory_order_acquire) && surface->context.device == device && !surface->retired && (surface->target.address == address || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == address));
});
return found == list.rend() ? nullptr : found->get();
}
@@ -917,6 +922,37 @@ void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t
for (const auto& range : depthGuards()) Require(address >= range.address + range.bytes || range.address >= address + bytes, "sample-preserving guest publication of native multisample depth is not implemented");
}
bool HasResidentDepthGuards_nid_no_patch() {
return anyDepthGuard().load(std::memory_order_acquire);
}
void RetireUnmappedDepth_nid_no_patch(std::uintptr_t address, std::size_t bytes) {
if (bytes == 0u || !HasResidentDepthGuards_nid_no_patch()) return;
Require(address <= std::numeric_limits<std::uint64_t>::max() - bytes, "native depth release range overflows");
const auto end = address + bytes;
std::unique_lock lock(depthGuardMutex());
for (auto& range : depthGuards()) {
std::vector<GuestMemory::UnchangedQuery> remaining;
remaining.reserve(range.live.size() + 1u);
for (const auto& part : range.live) {
const auto finish = part.address + part.bytes;
const auto first = std::max<std::uint64_t>(address, part.address);
const auto last = std::min<std::uint64_t>(end, finish);
if (first >= last) remaining.push_back(part);
else {
if (part.address < first) remaining.push_back({part.address, static_cast<std::size_t>(first - part.address), 0u});
if (last < finish) remaining.push_back({last, static_cast<std::size_t>(finish - last), 0u});
}
}
range.live = std::move(remaining);
}
for (const auto& range : depthGuards()) {
if (std::ranges::all_of(depthGuards(), [&](const auto& part) { return part.active != range.active || part.live.empty(); })) range.active->store(false, std::memory_order_release);
}
std::erase_if(depthGuards(), [](const auto& range) { return !range.active->load(std::memory_order_acquire); });
anyDepthGuard().store(!depthGuards().empty(), std::memory_order_release);
}
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples) {
if (samples <= 1 || (context.sampleLocationSampleCounts & samples) == 0) return false;
VkImageFormatProperties properties{};
@@ -959,15 +995,16 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
const bool cleared = takeClearedHtile(target);
DepthSurface* surface = nullptr;
for (const auto& candidate : surfaces()) {
if (candidate->context.device == context.device && sameSurface(candidate->target, target)) {
if (candidate->active->load(std::memory_order_acquire) && candidate->context.device == context.device && sameSurface(candidate->target, target)) {
surface = candidate.get();
break;
}
}
const bool created = surface == nullptr;
GuestAllocations::Lease creationLease;
if (created) {
for (const auto& surface : surfaces()) {
if (!surface->samplingActive || surface->context.device != context.device) continue;
if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->context.device != context.device) continue;
const auto bytes = target.samples == 1u ? DepthSliceBytes(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u) : MultisampledDepthBytes_nid_no_patch(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u, target.samples);
Require(target.address <= std::numeric_limits<std::uint64_t>::max() - bytes && target.htileAddress <= std::numeric_limits<std::uint64_t>::max() - HtileMetadataBytes(target.extent), "native depth attachment range overflows");
for (const auto& range : surface->sampling.initial) {
@@ -981,13 +1018,14 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
}
lock.unlock();
auto sampling = target.samples == 1u ? DepthSamplingState{} : prepareDepthSampling(context, target);
creationLease = target.samples == 1u || !sampling.refusal.empty() ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix();
lock.lock();
surfacesSerial.fetch_add(1, std::memory_order_release);
surfaces().push_back(std::make_unique<DepthSurface>(context, target, std::move(sampling)));
auto& fresh = *surfaces().back();
if (target.samples != 1u && fresh.sampleable && fresh.sampling.refusal.empty()) {
std::unique_lock guardLock(depthGuardMutex());
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes});
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}});
anyDepthGuard().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
fresh.samplingActive = true;
@@ -995,7 +1033,8 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
surface = surfaces().back().get();
}
surface->retired = false;
const bool inherits = depthOwners().contains({context.device, target.address});
const auto owner = depthOwners().find({context.device, target.address});
const bool inherits = owner != depthOwners().end() && owner->second.surface->active->load(std::memory_order_acquire) && !owner->second.surface->retired;
surface->clearDepth = target.clearDepth;
surface->clearStencil = target.clearStencil;
takePlanes(*surface);
@@ -1048,6 +1087,7 @@ void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
std::lock_guard lock(surfacesMutex());
bool existing = false;
for (const auto& surface : surfaces()) {
if (!surface->active->load(std::memory_order_acquire)) continue;
if (!HtileFillCovers(surface->target.htileAddress, surface->target.extent, htileAddress, bytes)) continue;
if (surface->target.samples != 1u) surface->sampling.refusal = "native multisample depth fast-clear transition is not implemented";
surface->pendingClear |= VK_IMAGE_ASPECT_DEPTH_BIT;
@@ -1069,11 +1109,12 @@ void ClearDepthSurfaces(VkDevice device) {
std::unique_lock guardLock(depthGuardMutex());
std::erase_if(depthGuards(), [&](const auto& range) { return range.device == device; });
anyDepthGuard().store(!depthGuards().empty(), std::memory_order_release);
UpdateResidentImageUnmapObserver_nid_no_patch();
}
std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
const auto held = [&](const auto& surface) {
return surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
return surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
};
{
std::lock_guard lock(surfacesMutex());
@@ -1155,6 +1196,7 @@ void RequireNoNativeDepthFeedback_nid_no_patch(const Context& context, const Dep
std::lock_guard gpu(GuestMemory::GpuMutex());
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (!surface->active->load(std::memory_order_acquire)) continue;
if (surface->context.device != context.device || !sameSurface(surface->target, target)) continue;
Require(std::none_of(sampled.begin(), sampled.end(), [&](const auto& texture) { return texture != nullptr && surface->Holds(texture.get()); }), "sampling a native multisampled depth target the draw attaches is not implemented");
}
@@ -1179,6 +1221,7 @@ bool HtileFillCovers(std::uint64_t htile, VkExtent2D extent, std::uint64_t addre
void NoteDepthMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern) {
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (!surface->active->load(std::memory_order_acquire)) continue;
const auto& target = surface->target;
if (target.samples != 1u && target.htileAddress != 0u && address < target.htileAddress + HtileMetadataBytes(target.extent) && target.htileAddress < address + bytes) surface->sampling.refusal = "native multisample depth metadata fill transition is not implemented";
if (!HtileFillCovers(target.htileAddress, target.extent, address, bytes)) continue;
@@ -1241,7 +1284,7 @@ std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std:
std::lock_guard lock(surfacesMutex());
const auto& list = surfaces();
const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) {
return surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
return surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && !surface->retired && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
});
if (found == list.rend()) return nullptr;
auto& surface = **found;
@@ -1261,7 +1304,7 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage
return true;
}
for (const auto& surface : surfaces()) {
if (surface->context.device == context.device && surface->StoreBack(storage)) return true;
if (surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && surface->StoreBack(storage)) return true;
}
for (auto surface = surfaces().rbegin(); surface != surfaces().rend(); ++surface) {
if ((*surface)->context.device == context.device && (*surface)->StoreBackCovering(storage)) return true;
@@ -1271,7 +1314,7 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage
bool DepthSurfaceAt(std::uint64_t address) {
std::lock_guard lock(surfacesMutex());
return std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& surface) { return !surface->retired && (surface->target.address == address || surface->target.stencilAddress == address); });
return std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& surface) { return surface->active->load(std::memory_order_acquire) && !surface->retired && (surface->target.address == address || surface->target.stencilAddress == address); });
}
void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t bytes) {
@@ -235,6 +235,7 @@ void guardReads(std::uint64_t address, std::size_t bytes) {
}
void retireUnmapped(std::uintptr_t address, std::size_t bytes) {
RetireUnmappedDepth_nid_no_patch(address, bytes);
std::unique_lock lock(guardedMutex());
std::erase_if(guarded(), [&](const auto& range) {
if (range.address < address || range.address - address > bytes || range.bytes > bytes - (range.address - address)) return false;
@@ -259,7 +260,6 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_
guarded().push_back({context.device, target.address, bytes, target.samples, active});
anyGuarded().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(&retireUnmapped);
}
std::mutex& targetsMutex() {
@@ -342,6 +342,13 @@ MultisampleTarget& createFilled(const Context& context, const GuestTextureResour
void InstallResidentImageReadGuard_nid_no_patch() {
static std::once_flag installed;
std::call_once(installed, [] { GuestMemory::SetReadGuard(&guardReads); });
UpdateResidentImageUnmapObserver_nid_no_patch();
}
void UpdateResidentImageUnmapObserver_nid_no_patch() {
static std::mutex mutex;
std::lock_guard lock(mutex);
GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(anyGuarded().load(std::memory_order_acquire) || HasResidentDepthGuards_nid_no_patch() ? &retireUnmapped : nullptr);
}
void MultisampledCmask::NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern) {
@@ -533,7 +540,7 @@ void ClearMultisampleTargets(VkDevice device) {
std::unique_lock guardLock(guardedMutex());
std::erase_if(guarded(), [&](const auto& range) { return range.device == device; });
anyGuarded().store(!guarded().empty(), std::memory_order_release);
if (guarded().empty()) GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(nullptr);
UpdateResidentImageUnmapObserver_nid_no_patch();
}
void RequireNoMultisampleTarget(std::uint64_t address, std::size_t bytes, const char* access) {
@@ -8,6 +8,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "SpirvBackend/SpirvModule.hpp"
#include "VulkanTestDevice.hpp"
@@ -846,9 +847,132 @@ void CheckEligibility(Context context) {
std::puts("Native depth eligibility, guest writes, queued writes, metadata transitions and held views checked");
}
void CheckRetirement(Context context, VkFormat format, std::uint32_t samples, std::uint32_t mode) {
constexpr VkExtent2D extent{5u, 3u};
const auto depthBytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, samples));
const auto metadataBytes = static_cast<std::size_t>(HtileMetadataBytes(extent));
const auto allocationBytes = depthBytes + 65536u;
TargetMemory memory(allocationBytes), colorMemory(65536u);
const auto release = [](const void*, std::size_t, const void*, bool) {};
const auto add = [&](TargetMemory& allocation, std::size_t bytes) {
GuestAllocations::Mutation mutation;
mutation.Add(allocation.Data(), bytes, true, true, true);
};
const auto remove = [&](TargetMemory& allocation, std::size_t bytes) {
GuestAllocations::Mutation mutation;
if (mutation.Overlaps(allocation.Data(), bytes)) mutation.Unmap(allocation.Data(), bytes, release);
};
Recorder recorder(context);
recorder.Activate();
const auto cleanup = [&] {
recorder.Sync();
ClearCachedTextures(context.device);
ClearDepthSurfaces(context.device);
ClearMultisampleTargets(context.device);
remove(memory, allocationBytes);
remove(colorMemory, 65536u);
};
struct Cleanup {
decltype(cleanup)& action;
~Cleanup() { action(); }
} cleanupScope{cleanup};
add(memory, allocationBytes);
add(colorMemory, 65536u);
std::memset(memory.Data() + depthBytes, 0, metadataBytes);
DepthTarget target{memory.Address(), 0u, extent, format, 0.0f, 0u, memory.Address() + depthBytes, false, samples};
target.depthSwizzle = 24u;
target.iterate256 = target.htilePipeAligned = target.htileDepthOnly = true;
const auto words = DepthSampleWords(target);
const auto resource = DecodeTextureResource(words);
const auto firstView = DepthSurfaceView(context, target);
auto firstTexture = DepthSurfaceTexture(context, words, resource, VkComponentMapping{});
Require(firstTexture != nullptr, "retirement fixture has no native sampled view");
ColorTarget color{colorMemory.Address(), extent, VK_FORMAT_R8G8B8A8_UNORM, extent.width * extent.height * 4u, 0u};
color.tileMode = ColorTileMode::RenderTarget;
color.samples = 4u;
color.surfaceAddress = color.address;
Require(MultisampleTargetView(context, color) != VK_NULL_HANDLE, "retirement fixture has no color attachment");
recorder.Sync();
const auto guarded = [&] {
TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.address, 1u); }, "sample-preserving");
};
{
GuestAllocations::Mutation mutation;
mutation.Protect(memory.Data(), allocationBytes, true, true, true, [] {});
}
guarded();
{
GuestAllocations::Mutation mutation;
TargetRefuses([&] { mutation.Unmap(memory.Data(), allocationBytes, [](const void*, std::size_t, const void*, bool) { throw std::runtime_error("retirement fixture failed host release"); }); }, "failed host release");
Require(mutation.Covers(memory.Data(), allocationBytes), "failed host release changed registry coverage");
}
guarded();
if ((mode & 1u) == 0u) ClearMultisampleTargets(context.device);
const auto unmap = [&](std::size_t offset, std::size_t bytes) {
GuestAllocations::Mutation mutation;
mutation.Unmap(memory.Data() + offset, bytes, release);
};
if (mode == 0u) {
GuestAllocations::Mutation mutation;
mutation.Remove(memory.Data());
} else if (mode == 1u) unmap(0u, allocationBytes);
else if (mode == 2u) {
unmap(0u, depthBytes / 2u);
guarded();
unmap(depthBytes / 2u, depthBytes / 2u);
guarded();
unmap(depthBytes, metadataBytes / 2u);
guarded();
unmap(depthBytes + metadataBytes / 2u, metadataBytes / 2u);
} else {
unmap(depthBytes / 2u, depthBytes / 2u + metadataBytes / 2u);
guarded();
unmap(0u, depthBytes / 2u);
guarded();
unmap(depthBytes + metadataBytes / 2u, metadataBytes / 2u);
}
RequireNoResidentDepthRead_nid_no_patch(target.address, 1u);
RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes);
Require(!DepthSurfaceHolds(context, resource, firstTexture.get()) && !DepthSurfaceAt(target.address), "a fully released depth owner remains discoverable");
if ((mode & 1u) != 0u) {
ClearDepthSurfaces(context.device);
TargetRefuses([&] { RequireNoMultisampleTarget(color.address, 1u, "a CPU read"); }, "multisampled");
remove(colorMemory, 65536u);
RequireNoMultisampleTarget(color.address, 1u, "a CPU read");
}
remove(memory, allocationBytes);
add(memory, allocationBytes);
target.clearDepth = 1.0f;
const std::uint32_t one = 0xfffffff0u;
for (std::size_t offset = 0u; offset < metadataBytes; offset += 4u) std::memcpy(memory.Data() + depthBytes + offset, &one, 4u);
const auto remappedView = DepthSurfaceView(context, target);
Require(remappedView != VK_NULL_HANDLE && ((mode & 1u) != 0u || remappedView != firstView), "a remapped allocation reused its old native depth attachment");
auto fresh = DepthSurfaceTexture(context, words, resource, VkComponentMapping{});
Require(fresh != nullptr && fresh->View() != firstTexture->View(), "a remapped allocation reused its old sampled view");
recorder.Sync();
const auto count = extent.width * extent.height * samples;
Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
for (const auto& [texture, expected] : {std::pair{firstTexture, 0.0f}, std::pair{fresh, 1.0f}}) {
std::memset(output.Bytes().data(), 0xa5, output.Bytes().size());
const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, samples, false, true));
CommandBatch batch(context);
fetch.Run(batch.Handle(), count);
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
batch.SubmitAndWait();
output.Invalidate();
for (std::uint32_t i = 0u; i < count; ++i) {
std::array<float, 4> value{};
std::memcpy(value.data(), output.Bytes().data() + (i + 1u) * 16u, 16u);
if (value != std::array<float, 4>{expected, 0.0f, 0.0f, 1.0f}) std::printf("Retirement sample format=%d samples=%u mode=%u index=%u expected=%g actual=(%g,%g,%g,%g)\n", format, samples, mode, i, expected, value[0], value[1], value[2], value[3]);
Require(value == std::array<float, 4>{expected, 0.0f, 0.0f, 1.0f}, "retired or remapped depth image lost its exact contents");
}
Require(std::all_of(output.Bytes().begin(), output.Bytes().begin() + 16u, [](std::byte value) { return value == std::byte{0xa5}; }) && std::all_of(output.Bytes().end() - 16u, output.Bytes().end(), [](std::byte value) { return value == std::byte{0xa5}; }), "retirement fetch wrote beyond its output");
}
}
int main() {
}
int main(int argc, char** argv) {
try {
std::unique_ptr<Device> device;
try { device = std::make_unique<Device>(); }
@@ -861,6 +985,18 @@ int main() {
const auto context = device->GetContext();
const std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
Require(DepthSupported(context, VK_FORMAT_D32_SFLOAT, 4u), "the native depth regression requires sampled D32 with four samples");
Require(argc == 1 || (argc == 2 && std::string_view(argv[1]) == "--retirement"), "invalid native depth fixture arguments");
if (argc == 2) {
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 += 4u; continue; }
for (std::uint32_t mode = 0u; mode < 4u; ++mode) { CheckRetirement(context, format, samples, mode); ++checked; }
}
}
std::printf("Native depth retirement cases checked=%u unavailable=%u\n", checked, unavailable);
return 0;
}
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}) {
+2 -1
View File
@@ -3,8 +3,9 @@
### Unknown function info
- [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw.
- [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 HTILE clear/fill transitions. Guest publication, arbitrary guest depth import, stencil/raw-bit sampling, arrays, storage access and allocation retirement 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); the subsequent completed HTILE transition needs separate support.
- [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 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); the subsequent completed HTILE transition needs separate support.
- [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