mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #8047 from fisherjoey/fix/template-texture-audit
fix(agc): fail a template's proof on a released texture and audit it with the full walk
This commit is contained in:
5 files changed
+192
-5
No files matched your search
@@ -2393,9 +2393,11 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_resource_cache_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-admission)
|
||||
add_test(NAME agc_driver_resource_cache_oversized_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-oversized)
|
||||
add_test(NAME agc_driver_resource_cache_storage_clear COMMAND agc_driver_sampled_cache_budget_tests resource-clear)
|
||||
set_tests_properties(agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
|
||||
add_test(NAME agc_driver_resource_cache_replaced_texture COMMAND agc_driver_sampled_cache_budget_tests resource-replaced)
|
||||
add_test(NAME agc_driver_resource_cache_lookup_changed COMMAND agc_driver_sampled_cache_budget_tests resource-lookup-changed)
|
||||
set_tests_properties(agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear agc_driver_resource_cache_replaced_texture agc_driver_resource_cache_lookup_changed PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_sampled_cache_budget_without_storage_image_multisample agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_sampled_cache_budget_without_storage_image_multisample agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear agc_driver_resource_cache_replaced_texture agc_driver_resource_cache_lookup_changed PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
# The driver draw test again with the draw key off: the draw cache then keys on the decoded vertex
|
||||
|
||||
@@ -307,7 +307,7 @@ public:
|
||||
bool DataWordsDiffer(const CompiledShader& shader) const;
|
||||
// Why the fast proof of a Revalidate left the object to the full walk (the [rescache]
|
||||
// revalidate line's reasons); Count: it did not.
|
||||
enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Count };
|
||||
enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Replaced, Audit, Count };
|
||||
// Why a Pending failure was left to the full walk instead of the own-object refresh (T1, see
|
||||
// refreshOwnObjects); Count: it was not.
|
||||
enum class OwnRefreshFallback : std::size_t { Disabled, Snapshot, Keys, ForeignView, SurfaceKey, NotImported, Uncached, Rerun, Count };
|
||||
@@ -592,6 +592,7 @@ private:
|
||||
// Guest buffer elements bound read-only: each use of this object skips that many pending-write
|
||||
// notes (counted in MarkGpuWrites for the [buffers] line).
|
||||
std::size_t readOnlyBuffers = 0;
|
||||
std::chrono::steady_clock::time_point lastFullWalk = std::chrono::steady_clock::now();
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> writeOnlyRanges;
|
||||
bool drawBuild = false;
|
||||
std::uint64_t dispatchThreads = 0;
|
||||
|
||||
@@ -2720,7 +2720,7 @@ namespace {
|
||||
// of the reused ones, fast = every image proved current from stamps, full = the lookups were repeated.
|
||||
// Why the fast path left an object to the full walk (the "fast-fail by reason" counts).
|
||||
using FastFail = ShaderResources::FastFail;
|
||||
constexpr std::array<const char*, static_cast<std::size_t>(FastFail::Count)> FastFailNames{"no record", "collect", "pending image", "evicted image", "memory changed", "keys", "cleared view", "storage keys", "depth surface"};
|
||||
constexpr std::array<const char*, static_cast<std::size_t>(FastFail::Count)> FastFailNames{"no record", "collect", "pending image", "evicted image", "memory changed", "keys", "cleared view", "storage keys", "depth surface", "replaced texture", "audit"};
|
||||
using OwnRefreshFallback = ShaderResources::OwnRefreshFallback;
|
||||
constexpr std::array<const char*, static_cast<std::size_t>(OwnRefreshFallback::Count)> OwnRefreshFallbackNames{"disabled", "snapshot texture", "cleared view", "foreign view", "surface key", "not imported", "uncached", "re-run failed"};
|
||||
|
||||
@@ -2858,6 +2858,19 @@ bool EpochRevalidate() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration TemplateAuditInterval() {
|
||||
static const auto interval = [] {
|
||||
const char* text = std::getenv("APS5_TEMPLATE_AUDIT_MS");
|
||||
return std::chrono::milliseconds(text != nullptr ? std::strtoull(text, nullptr, 10) : 1000ull);
|
||||
}();
|
||||
return interval;
|
||||
}
|
||||
|
||||
bool TemplateAuditDue(std::chrono::steady_clock::time_point last) {
|
||||
const auto interval = TemplateAuditInterval();
|
||||
return interval.count() != 0 && std::chrono::steady_clock::now() - last >= interval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Turns the lookups' records into this object's per-texture validation records (see
|
||||
@@ -2936,6 +2949,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
||||
if (!DepthSurfaceHolds(context, surface.resource, textures[i].get())) return fail(FastFail::Depth);
|
||||
continue;
|
||||
}
|
||||
if (textures[i]->Released()) return fail(FastFail::Replaced);
|
||||
const auto address = surface.resource.baseAddress;
|
||||
const auto bytes = static_cast<std::size_t>(surface.bytes);
|
||||
surface.collected = GuestMemory::CollectWrites(address, bytes);
|
||||
@@ -3062,6 +3076,7 @@ bool ShaderResources::fastRevalidateEach() {
|
||||
if (!DepthSurfaceHolds(context, surface.resource, textures[i].get())) return false;
|
||||
continue;
|
||||
}
|
||||
if (textures[i]->Released()) return false;
|
||||
const auto address = surface.resource.baseAddress;
|
||||
const auto bytes = static_cast<std::size_t>(surface.bytes);
|
||||
surface.collected = GuestMemory::CollectWrites(address, bytes);
|
||||
@@ -3262,6 +3277,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
||||
// they must hand back the very objects the set's views belong to. The build appended them stage
|
||||
// by stage, binding by binding, so the walk repeats that order.
|
||||
const auto fullWalk = [&] {
|
||||
lastFullWalk = std::chrono::steady_clock::now();
|
||||
lookupLog().clear();
|
||||
BuildLookups lookups;
|
||||
std::size_t textureIndex = 0;
|
||||
@@ -3367,6 +3383,11 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
||||
}
|
||||
if (fallback != OwnRefreshFallback::Count) countOwnRefreshFallback(fallback);
|
||||
}
|
||||
if (fast && TemplateAuditDue(lastFullWalk)) {
|
||||
fast = false;
|
||||
ownRefreshed = false;
|
||||
reason = FastFail::Audit;
|
||||
}
|
||||
if (report != nullptr) report->path = !fast ? ProofPath::Full : ownRefreshed ? ProofPath::OwnRefreshed : ProofPath::Fast;
|
||||
if (!fast) {
|
||||
countFullWalk(reason);
|
||||
@@ -3374,7 +3395,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
||||
const auto failure = [&] {
|
||||
switch (reason) {
|
||||
case FastFail::Pending: return ProofFailure::Pending;
|
||||
case FastFail::Evicted: return ProofFailure::Evicted;
|
||||
case FastFail::Evicted: case FastFail::Replaced: return ProofFailure::Evicted;
|
||||
case FastFail::Changed: return ProofFailure::Changed;
|
||||
case FastFail::Keys: case FastFail::ClearedView: case FastFail::StorageKeys: return ProofFailure::Keys;
|
||||
default: return ProofFailure::Other;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <SDL_loadso.h>
|
||||
#ifdef _WIN32
|
||||
@@ -16,6 +18,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -275,6 +279,53 @@ private:
|
||||
void* memory = nullptr;
|
||||
};
|
||||
|
||||
class WatchedSurface {
|
||||
public:
|
||||
explicit WatchedSurface(const Context& context) : context(context) {
|
||||
#ifdef _WIN32
|
||||
memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, 65536));
|
||||
if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, SurfaceBytes, PAGE_READWRITE, SurfaceBytes);
|
||||
#else
|
||||
constexpr std::uintptr_t alignment = 65536;
|
||||
void* mapped = mmap(nullptr, SurfaceBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (mapped != MAP_FAILED) {
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
|
||||
const auto aligned = (begin + alignment - 1) & ~(alignment - 1);
|
||||
if (aligned != begin) munmap(mapped, aligned - begin);
|
||||
if (aligned + SurfaceBytes != begin + SurfaceBytes + alignment) munmap(reinterpret_cast<void*>(aligned + SurfaceBytes), begin + alignment - aligned);
|
||||
memory = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, SurfaceBytes);
|
||||
}
|
||||
#endif
|
||||
Require(memory != nullptr, "cannot allocate the watched surface");
|
||||
Require(AgcDriver::GuestMemory::Watched(Address(), SurfaceBytes), "the watched surface is not write-watched");
|
||||
std::memset(memory, 0x5a, SurfaceBytes);
|
||||
GuestAllocations::Mutation().Add(memory, SurfaceBytes, true, true, true);
|
||||
AgcDriver::GuestMemory::CollectWritesUncached(Address(), SurfaceBytes);
|
||||
}
|
||||
|
||||
~WatchedSurface() {
|
||||
ClearCachedTextures(context.device);
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaReset_nid_postfix(memory, SurfaceBytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(memory, SurfaceBytes);
|
||||
#else
|
||||
munmap(memory, SurfaceBytes);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, SurfaceBytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
WatchedSurface(const WatchedSurface&) = delete;
|
||||
WatchedSurface& operator=(const WatchedSurface&) = delete;
|
||||
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uint64_t>(memory); }
|
||||
|
||||
private:
|
||||
const Context& context;
|
||||
std::uint8_t* memory = nullptr;
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address, std::uint32_t side = Side) {
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
@@ -480,6 +531,103 @@ void viewTests(const Context& context) {
|
||||
Require(heldView.expired() && heldStorage.expired(), "the evicted image outlived its last caller without a sampled lookup");
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult SampledProgram(std::uint64_t address) {
|
||||
ShaderRecompiler::DescriptorBinding binding{};
|
||||
binding.kind = ShaderRecompiler::DescriptorKind::SampledImage;
|
||||
binding.role = ShaderRecompiler::DescriptorRole::GuestImages;
|
||||
binding.descriptorSet = 0;
|
||||
binding.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding + PlaceholderDimensionsPerClass + 2u;
|
||||
binding.count = 1;
|
||||
binding.imageShape = ShaderRecompiler::DescriptorImageShape::Image2D;
|
||||
binding.imageWritten.assign(1, false);
|
||||
binding.imageSamplers.assign(1, 0u);
|
||||
const auto words = Descriptor(address);
|
||||
binding.guestDescriptor.assign(words.begin(), words.end());
|
||||
ShaderRecompiler::RecompileResult program;
|
||||
program.bindings.push_back(std::move(binding));
|
||||
return program;
|
||||
}
|
||||
|
||||
void replacedEntryTests(Context context) {
|
||||
PlaceholderImages placeholders(context);
|
||||
context.placeholderImages = &placeholders;
|
||||
TextureCache textures(context);
|
||||
context.textureCache = &textures;
|
||||
WatchedSurface surface(context);
|
||||
ClearCachedTextures(context.device);
|
||||
const auto program = SampledProgram(surface.Address());
|
||||
const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0};
|
||||
auto first = std::make_shared<ShaderResources>(context, shader);
|
||||
Require(first->Reusable(), "the sampled snapshot's descriptor set is not eligible for resource caching");
|
||||
Require(first->SampledTextures().size() == 1, "the sampled descriptor set binds no texture");
|
||||
const auto bound = first->SampledTextures().front();
|
||||
Require(!bound->ViewsStorageImage(), "the replaced-entry fixture is not a snapshot");
|
||||
Require(Sampled(context, surface.Address()) == bound, "the descriptor set does not bind the cache's texture");
|
||||
ShaderResources::ProofReport report{};
|
||||
Require(first->Revalidate(shader), "a template over an unchanged, still cached texture did not prove current");
|
||||
Require(first->Revalidate(shader, &report), "a template over an unchanged, still cached texture did not prove current twice");
|
||||
Require(report.path == ShaderResources::ProofPath::Fast, "the fixture's template did not take the fast proof");
|
||||
Require(first->SampledTextures().front() == bound, "the proof changed the bound texture");
|
||||
ClearCachedTextures(context.device);
|
||||
Require(TextureCacheUsage().sampledEntries == 0, "clearing the sampled cache left entries");
|
||||
Require(!first->Revalidate(shader, &report), "a template binding a texture whose cache entry left still proved current");
|
||||
const auto replacement = Sampled(context, surface.Address());
|
||||
Require(replacement != bound, "the lookup after the clear handed back the old texture");
|
||||
Require(!first->Revalidate(shader), "a template binding the replaced texture proved current against the new entry");
|
||||
auto rebuilt = std::make_shared<ShaderResources>(context, shader);
|
||||
Require(rebuilt->SampledTextures().front() == replacement, "the rebuilt descriptor set does not bind the cache's new texture");
|
||||
Require(rebuilt->Revalidate(shader), "the rebuilt template did not prove current over its cached texture");
|
||||
first.reset();
|
||||
rebuilt.reset();
|
||||
ClearCachedTextures(context.device);
|
||||
std::puts("template over a replaced sampled cache entry passed");
|
||||
}
|
||||
|
||||
bool lookupChangedTests(Context context) {
|
||||
PlaceholderImages placeholders(context);
|
||||
context.placeholderImages = &placeholders;
|
||||
TextureCache textures(context);
|
||||
context.textureCache = &textures;
|
||||
if (context.hostImportAlignment == 0) {
|
||||
Require(std::getenv("ANYPS5_REQUIRE_VULKAN") == nullptr, "the lookup-changed test requires host imports");
|
||||
std::puts("skipped, host imports are unavailable");
|
||||
return false;
|
||||
}
|
||||
WatchedSurface surface(context);
|
||||
ClearCachedTextures(context.device);
|
||||
const auto program = SampledProgram(surface.Address());
|
||||
const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0};
|
||||
auto first = std::make_shared<ShaderResources>(context, shader);
|
||||
Require(first->Reusable() && first->SampledTextures().size() == 1, "the lookup-changed template is not reusable");
|
||||
const auto bound = first->SampledTextures().front();
|
||||
Require(!bound->ViewsStorageImage(), "the lookup-changed fixture is not a snapshot before the import");
|
||||
ShaderResources::ProofReport report{};
|
||||
Require(first->Revalidate(shader), "the lookup-changed template did not prove current");
|
||||
Require(first->Revalidate(shader, &report) && report.path == ShaderResources::ProofPath::Fast, "the lookup-changed template did not take the fast proof");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
Require(first->Revalidate(shader, &report), "the audit refused an unchanged template");
|
||||
Require(report.path == ShaderResources::ProofPath::Full, "a proof past the audit interval did not take the full walk");
|
||||
Require(first->SampledTextures().front() == bound, "the audit changed the bound texture");
|
||||
Require(first->Revalidate(shader, &report) && report.path == ShaderResources::ProofPath::Fast, "the proof right after an audit did not take the fast proof");
|
||||
if (HostImportFor(context, surface.Address(), SurfaceBytes) == nullptr) {
|
||||
Require(std::getenv("ANYPS5_REQUIRE_VULKAN") == nullptr, "the host import of the watched surface was refused");
|
||||
std::puts("skipped, host imports are unavailable");
|
||||
return false;
|
||||
}
|
||||
Require(!bound->Released(), "the snapshot left the cache without a lookup");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
Require(!first->Revalidate(shader, &report), "a template binding a snapshot of a surface its lookup now views through a storage image still proved current past the audit interval");
|
||||
const auto view = Sampled(context, surface.Address());
|
||||
Require(view != bound && view->ViewsStorageImage(), "the lookup of the imported surface does not view its storage image");
|
||||
auto rebuilt = std::make_shared<ShaderResources>(context, shader);
|
||||
Require(rebuilt->SampledTextures().front() == view, "the rebuilt template does not bind the storage view");
|
||||
first.reset();
|
||||
rebuilt.reset();
|
||||
ClearCachedTextures(context.device);
|
||||
std::puts("template over a changed sampled lookup passed");
|
||||
return true;
|
||||
}
|
||||
|
||||
void reportedBudgetTests(const Context& context) {
|
||||
if (context.memoryProperties2 == nullptr) {
|
||||
std::puts("VK_EXT_memory_budget unavailable: the sampled texture cache keeps a quarter of the device-local heap");
|
||||
@@ -872,8 +1020,10 @@ int main(int argc, char** argv) {
|
||||
const bool disableStorageImageMultisample = mode == "without-storage-image-multisample";
|
||||
#ifdef _WIN32
|
||||
if (!memory) _putenv_s("APS5_TEXTURE_CACHE_MIB", "1");
|
||||
if (std::getenv("APS5_TEMPLATE_AUDIT_MS") == nullptr) _putenv_s("APS5_TEMPLATE_AUDIT_MS", "250");
|
||||
#else
|
||||
if (!memory) setenv("APS5_TEXTURE_CACHE_MIB", "1", 1);
|
||||
setenv("APS5_TEMPLATE_AUDIT_MS", "250", 0);
|
||||
#endif
|
||||
std::unique_ptr<Device> device;
|
||||
try {
|
||||
@@ -912,6 +1062,18 @@ int main(int argc, char** argv) {
|
||||
std::puts("resource cache tests passed");
|
||||
return 0;
|
||||
}
|
||||
if (mode == "resource-replaced") {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
replacedEntryTests(context);
|
||||
std::puts("resource cache replaced entry tests passed");
|
||||
return 0;
|
||||
}
|
||||
if (mode == "resource-lookup-changed") {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
if (!lookupChangedTests(context)) return VulkanTestSkipped;
|
||||
std::puts("resource cache lookup-changed tests passed");
|
||||
return 0;
|
||||
}
|
||||
if (memory) {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
memoryTests(context);
|
||||
|
||||
@@ -377,6 +377,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti
|
||||
| `APS5_SYNC_SAMPLE_DUMPS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | takes the CPU path for occlusion-counter sample dumps, as `APS5_NO_GPU_SAMPLE_DUMPS` does |
|
||||
| `APS5_SYNC_SHADER_PREPARE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp` | | prepares registered shaders on the registering thread instead of in the background |
|
||||
| `APS5_SYNC_TEXTURE_UPLOAD` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | restores the waiting batch for every texture upload |
|
||||
| `APS5_TEMPLATE_AUDIT_MS` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 1000 | milliseconds after which a cached shader-resource object takes the full walk instead of the fast proof; `0` turns the audit off |
|
||||
| `APS5_TEXTURE_CACHE_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | 0 | forced byte budget in MiB for the storage and sampled texture caches; `0` selects the budget automatically |
|
||||
| `APS5_THREAD_NICE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | -10 | nice value the raised queue workers and presenter ask rtkit for (clamped to -20..19) |
|
||||
| `APS5_THREAD_PRIORITY` | string | `core/libs/prx/libSceAgcDriver/Execution/src/ThreadPriority.cpp` | off | raises the queue workers and presenter above the title's threads: `off`, `high` or `rt` |
|
||||
|
||||
Reference in new issue
Block a user