Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2391,9 +2391,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class ShaderResources {
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 };
Expand Down Expand Up @@ -592,6 +592,7 @@ class ShaderResources {
// 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;
Expand Down
25 changes: 23 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -3367,14 +3383,19 @@ 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);
if (!fullWalk()) {
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;
Expand Down
162 changes: 162 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
#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
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
Expand Down Expand Up @@ -275,6 +279,53 @@ class Block {
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),
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading