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
10 changes: 9 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,9 @@ if(BUILD_TESTING)
set_tests_properties(agc_shader_disk_cache_large_file PROPERTIES SKIP_RETURN_CODE 77 RUN_SERIAL TRUE WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 240)
if(WIN32)
set_tests_properties(agc_shader_disk_cache_large_file PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
add_test(NAME agc_shader_disk_cache_long_paths COMMAND agc_shader_disk_cache_tests --long-host-paths)
set_tests_properties(agc_shader_disk_cache_long_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(WIN32)
add_test(NAME agc_driver_untracked_snapshot_hint COMMAND agc_driver_partially_resident_snapshot_tests --untracked-hint)
Expand Down Expand Up @@ -2131,12 +2134,17 @@ if(BUILD_TESTING)
endif()
set_tests_properties(agc_sparse_buffer_holes PROPERTIES TIMEOUT 30)
add_test(NAME agc_pipeline_journal COMMAND agc_driver_graphics_tests --pipeline-journal)
add_test(NAME agc_pipeline_journal_long_paths COMMAND agc_driver_graphics_tests --pipeline-journal-long-paths)
set_tests_properties(agc_pipeline_journal_long_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_pipeline_library_clear_waits_for_build COMMAND agc_driver_graphics_tests --pipeline-library-clear-waits-for-build)
add_test(NAME agc_pipeline_store_lazy_buffer_pool COMMAND agc_driver_graphics_tests --pipeline-store-lazy-buffer-pool)
set_tests_properties(agc_pipeline_journal PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_pipeline_library_clear_waits_for_build PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_pipeline_store_lazy_buffer_pool PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
add_test(NAME agc_pipeline_journal_extended_paths COMMAND agc_driver_graphics_tests --pipeline-journal-extended-paths)
set_tests_properties(agc_pipeline_journal_extended_paths PROPERTIES TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_pipeline_journal_long_paths agc_pipeline_journal_extended_paths PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_pipeline_journal agc_pipeline_library_clear_waits_for_build agc_pipeline_store_lazy_buffer_pool PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30)
Expand Down Expand Up @@ -3145,6 +3153,6 @@ endif()

if(BUILD_TESTING)
get_directory_property(agcRegisteredTests TESTS)
list(FILTER agcRegisteredTests EXCLUDE REGEX "^(agc_pipeline_journal|agc_driver_pipeline_journal)$")
list(FILTER agcRegisteredTests EXCLUDE REGEX "^(agc_pipeline_journal(_long_paths|_extended_paths)?|agc_driver_pipeline_journal)$")
set_property(TEST ${agcRegisteredTests} APPEND PROPERTY ENVIRONMENT "APS5_NO_PIPELINE_JOURNAL=1")
endif()
11 changes: 9 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,15 @@ class Journal {
Journal() {
const char* off = std::getenv("APS5_NO_PIPELINE_JOURNAL");
if ((off != nullptr && std::strcmp(off, "0") != 0) || !ShaderRecompiler::ShaderDiskCache::Enabled()) return;
path = ShaderRecompiler::ShaderDiskCache::EntryDirectory() / "pipelines.journal";
lockPath = ShaderRecompiler::ShaderDiskCache::EntryDirectory() / "pipelines.journal.lock";
auto directory = ShaderRecompiler::ShaderDiskCache::EntryDirectory();
#ifdef _WIN32
std::error_code error;
const auto native = ShaderRecompiler::NativeCachePath_nid_no_patch(directory, error);
if (error) throw std::filesystem::filesystem_error("cannot resolve the pipeline journal directory", directory, error);
directory = native;
#endif
path = directory / "pipelines.journal";
lockPath = directory / "pipelines.journal.lock";
maxBytes = configured("APS5_PIPELINE_JOURNAL_MAX_MIB", 1024u) << 20u;
std::thread([this] { run(); }).detach();
}
Expand Down
68 changes: 59 additions & 9 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "ShaderDiskCache.hpp"
#include "ShaderCacheDirectory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
Expand Down Expand Up @@ -9721,28 +9722,51 @@ void pipelineStoreLazyBufferPoolTests() {
Require(mock.live == 0, "lazy buffer pool pipeline tests leaked Vulkan objects");
}

void pipelineJournalTests() {
void pipelineJournalTests(bool longPath = false, bool extended = false) {
using namespace AgcDriver::Graphics;
using Stage = ShaderRecompiler::ShaderStage;
const auto directory = std::filesystem::temp_directory_path() / ("aps5-pipeline-journal-mock-" + std::to_string(std::random_device{}()));
const auto root = std::filesystem::temp_directory_path() / ("aps5-pipeline-journal-mock-" + std::to_string(std::random_device{}()));
auto directory = root;
if (longPath) {
Require(root.native().size() < 180, "the temporary root is too long for the journal path fixture");
directory /= std::filesystem::path(u8"cache-\u00e4\u6f22");
directory /= std::string(229 - directory.native().size(), 'd');
}
std::filesystem::create_directories(directory);
struct Cleanup {
std::filesystem::path directory;
std::filesystem::path journal;
~Cleanup() {
ShaderRecompiler::ShaderDiskCache::Shutdown();
std::error_code error;
if (!journal.empty()) {
std::filesystem::remove(journal, error);
auto lock = journal;
lock += ".lock";
std::filesystem::remove(lock, error);
}
std::filesystem::remove_all(directory, error);
}
} cleanup{directory};
} cleanup{root, {}};
#ifdef _WIN32
if (extended) directory = std::filesystem::path(L"\\\\?\\" + directory.make_preferred().native());
_putenv_s("ANYPS5_NO_SHADER_CACHE", "0");
_putenv_s("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str());
_wputenv_s(L"ANYPS5_SHADER_CACHE_DIR", directory.c_str());
_putenv_s("APS5_PIPELINE_JOURNAL_THREADS", "2");
#else
setenv("ANYPS5_NO_SHADER_CACHE", "0", 1);
setenv("ANYPS5_SHADER_CACHE_DIR", directory.string().c_str(), 1);
setenv("APS5_PIPELINE_JOURNAL_THREADS", "2", 1);
#endif
Require(PipelineJournalEnabled(), "the journal is disabled with a shader cache directory set");
auto journalFile = PipelineJournalPath();
#ifdef _WIN32
std::error_code pathError;
journalFile = ShaderRecompiler::NativeCachePath_nid_no_patch(journalFile, pathError);
Require(!pathError, "cannot normalize the journal fixture path");
#endif
cleanup.journal = journalFile;
if (longPath) Require(journalFile.native().size() > 260, "the journal fixture did not cross MAX_PATH");
const auto module = [](std::uint32_t salt) {
ShaderRecompiler::RecompileResult result;
result.spirv = std::vector<std::uint32_t>{0x07230203u, 0x00010000u, 0u, 8u, 0u, 0x00020011u, 1u, 0x00020011u, 2u + salt};
Expand Down Expand Up @@ -9934,13 +9958,15 @@ void pipelineJournalTests() {
FlushPipelineJournal();
const auto repeated = PipelineJournalTotals();
Require(repeated.recordedGraphics == recorded.recordedGraphics && repeated.recordedCompute == recorded.recordedCompute && repeated.recordedModules == recorded.recordedModules, "a repeated pipeline was recorded again");
Require(std::filesystem::file_size(PipelineJournalPath()) > 0, "the journal file is empty");
Require(std::filesystem::file_size(journalFile) > 0, "the journal file is empty");
const auto storeBefore = PipelineStoreTotals();
std::vector<std::byte> receivedWords;
ShaderRecompiler::RecompileResult received;
bool receivedPush = false;
std::uint32_t computeCalls = 0;
std::mutex computeMutex;
PipelineJournalHooks hooks{[&](const ShaderRecompiler::RecompileResult& shader, bool pushConstants) {
std::lock_guard lock(computeMutex);
++computeCalls;
received = shader;
receivedPush = pushConstants;
Expand All @@ -9963,7 +9989,7 @@ void pipelineJournalTests() {
const auto storeHit = PipelineStoreTotals();
Require(pipeline != nullptr && !built && mock.pipelineCreateCount == created && storeHit.hits == storeReplayed.hits + 1 && storeHit.misses == storeReplayed.misses, "the draw's configuration did not find the replayed pipeline");
{
const auto journalPath = PipelineJournalPath();
const auto& journalPath = journalFile;
const auto whole = std::filesystem::file_size(journalPath);
Require(whole % 8 == 0 && whole > 3, "the journal file is not record aligned");
std::filesystem::resize_file(journalPath, whole - 3);
Expand All @@ -9977,9 +10003,33 @@ void pipelineJournalTests() {
torn->Wait();
const auto reloaded = PipelineJournalTotals();
Require(reloaded.droppedRecords == 0 && reloaded.loadedModules == 4 && reloaded.loadedRecipes == 2 && reloaded.replayFailed == 0, "the journal did not recover from a tail torn inside a record's padding");
const auto writtenBefore = PipelineJournalTotals().writtenBytes;
RecordComputePipeline(compute, true);
FlushPipelineJournal();
Require(PipelineJournalTotals().recordedCompute == reloaded.recordedCompute + 1 && PipelineJournalTotals().recordedModules == reloaded.recordedModules, "the pipeline whose record was torn was not recorded again, or its module was");
const auto recordBytes = PipelineJournalTotals().writtenBytes - writtenBefore;
std::vector<std::byte> bytes;
Require(ShaderRecompiler::ReadWholeFile(journalFile, bytes) && recordBytes > 0 && recordBytes <= bytes.size(), "cannot read the re-recorded journal entry");
{
std::ofstream file(journalFile, std::ios::binary | std::ios::app);
file.write(reinterpret_cast<const char*>(bytes.data() + bytes.size() - recordBytes), static_cast<std::streamsize>(recordBytes));
const std::array<char, 8> tornTail{};
file.write(tornTail.data(), tornTail.size());
file.close();
Require(file.good(), "cannot append an external complete record and torn tail");
}
auto another = module(5);
another.bindings.push_back(makeBinding(Role::GuestBuffers, 0, 1, std::vector<std::uint32_t>(4)));
const auto beforeAppend = PipelineJournalTotals();
RecordComputePipeline(another, false);
FlushPipelineJournal();
const auto appended = PipelineJournalTotals();
Require(appended.writeFailures == 0 && appended.recordedCompute == beforeAppend.recordedCompute + 1, "the append after an external torn tail failed");
Require(std::filesystem::file_size(journalFile) == bytes.size() + recordBytes + appended.writtenBytes - beforeAppend.writtenBytes, "tail recovery discarded an external complete record or retained torn bytes");
auto recovered = StartPipelineJournalReplay(context, fingerprint, hooks);
Require(recovered != nullptr, "the replay after the external append did not start");
recovered->Wait();
Require(PipelineJournalTotals().loadedModules == 5 && PipelineJournalTotals().loadedRecipes == 4 && PipelineJournalTotals().droppedRecords == 0, "the external append and torn-tail recovery did not replay cleanly");
}
{
const auto before = PipelineJournalTotals();
Expand All @@ -9992,7 +10042,7 @@ void pipelineJournalTests() {
}
{
const std::uint32_t meshWithoutOutputSize = 2u;
std::fstream file(PipelineJournalPath(), std::ios::in | std::ios::out | std::ios::binary);
std::fstream file(journalFile, std::ios::in | std::ios::out | std::ios::binary);
file.seekp(4);
file.write(reinterpret_cast<const char*>(&meshWithoutOutputSize), sizeof(meshWithoutOutputSize));
file.close();
Expand Down Expand Up @@ -10035,8 +10085,8 @@ int main(int argc, char** argv) {
commandBatchFenceFailureTests();
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--pipeline-journal") {
pipelineJournalTests();
if (argc == 2 && (std::string_view(argv[1]) == "--pipeline-journal" || std::string_view(argv[1]) == "--pipeline-journal-long-paths" || std::string_view(argv[1]) == "--pipeline-journal-extended-paths")) {
pipelineJournalTests(std::string_view(argv[1]) != "--pipeline-journal", std::string_view(argv[1]) == "--pipeline-journal-extended-paths");
std::cout << "pipeline journal recipe, record and replay tests passed\n";
return 0;
}
Expand Down
62 changes: 62 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,62 @@ void verifyPersist() {
requireSameArtifact(variant.artifact, persisted->artifact, "a persisted entry whose file was replaced and removed");
}

#ifdef _WIN32
void verifyLongHostPaths() {
using namespace ShaderRecompiler::ShaderDiskCache;
auto directory = std::filesystem::temp_directory_path() / ("aps5-cache-long-" + std::to_string(std::random_device{}()));
require(directory.native().size() < 224, "temporary path is too long for the cache control");
directory /= std::wstring(224 - directory.native().size(), L'd');
const auto extended = [](const auto& path) { return std::filesystem::path(L"\\\\?\\" + path.native()); };
std::filesystem::create_directories(extended(directory));
setEnvironment("ANYPS5_NO_SHADER_CACHE", "0");
setEnvironment("ANYPS5_SHADER_CACHE_DIR", directory.string());
std::vector<std::filesystem::path> files;
std::filesystem::path entries;
struct Cleanup {
const std::filesystem::path& directory;
const std::vector<std::filesystem::path>& files;
const std::filesystem::path& entries;
~Cleanup() {
ShaderRecompiler::ShaderDiskCache::Shutdown();
std::error_code error;
for (const auto& file : files) std::filesystem::remove(std::filesystem::path(L"\\\\?\\" + file.native()), error);
if (!entries.empty()) std::filesystem::remove(std::filesystem::path(L"\\\\?\\" + entries.native()), error);
std::filesystem::remove(std::filesystem::path(L"\\\\?\\" + directory.native()), error);
}
} cleanup{directory, files, entries};
SampleRequest sample;
const auto key = sample.Key();
const auto variant = sampleVariant();
entries = ShaderDiskCache::EntryDirectory();
files.push_back(entries / EntryName(key));
files.push_back(entries / "last-used");
require(files.front().native().size() >= MAX_PATH, "the cache persistence control did not cross MAX_PATH");
const auto persisted = ShaderDiskCache::Persist(key, variant);
std::vector<std::byte> published;
require(ReadWholeFile(files.front(), published) && published == EncodeEntry(key, variant),
"the long-path cache entry was not correctly published before mapping");
require(persisted != nullptr, "a long-path cache entry was not persisted and mapped");
MEMORY_BASIC_INFORMATION region{};
require(VirtualQuery(persisted->artifact.spirv.data(), &region, sizeof(region)) == sizeof(region) && region.Type == MEM_MAPPED,
"long-path persisted SPIR-V is not mapped");
requireSameVariant(variant, *persisted, "long-path persist round trip");
CompiledVariant loaded;
require(ShaderDiskCache::Load(key, loaded), "a long-path persisted cache entry did not load");
requireSameVariant(variant, loaded, "long-path load round trip");
const auto packPath = directory / (std::wstring(75, L'p') + L".pack");
files.push_back(packPath);
require(WriteFileAtomically(packPath, packBytes({{key, EncodeEntry(key, variant)}})), "cannot publish the long-path cache pack");
CachePack pack(packPath);
require(pack.Valid(), "a long-path cache pack was rejected: " + pack.Error());
PackLoadTimings timings;
CompiledVariant decoded;
require(pack.Load(key, decoded, timings) == LoadStatus::Loaded, "a long-path cache pack entry did not load");
requireSameVariant(variant, decoded, "long-path cache pack round trip");
std::cout << "long-path shader cache persistence and pack tests passed\n";
}
#endif

struct ComputeRequest {
std::vector<std::uint32_t> code{0xe0700000u, 0x80000000u, 0xbf810000u};
std::array<std::uint32_t, 4> userData{0x10000000u, 0x00000000u, 0x40u, 0x00027facu};
Expand Down Expand Up @@ -2321,6 +2377,12 @@ int main(int argc, char** argv) {
std::cout << "artifact codec tests passed\n";
return 0;
}
#ifdef _WIN32
if (argc == 2 && std::string_view(argv[1]) == "--long-host-paths") {
verifyLongHostPaths();
return 0;
}
#endif
if (argc == 2 && std::string_view(argv[1]) == "--large-file") return runLargeFileWrite();
if (argc == 2 && std::string_view(argv[1]) == "--load") return runLoadingProcess();
if (argc == 2 && std::string_view(argv[1]) == "--load-prebuilt") return runPrebuiltLoadingProcess();
Expand Down
1 change: 1 addition & 0 deletions core/shader/recompiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ function(add_shader_recompiler target runtimeTarget)
HashBytes
LayoutBarycentricEmulation
MeshBarycentricLayoutOf
NativeCachePath_nid_no_patch
ImageTableAbi::PoisonCode
ImageTableAbi::PoisonReasonName
ImageTableAbi::TableHeader
Expand Down
Loading
Loading