diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 4eba5fa87e..aaa3940cd0 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1592,6 +1592,11 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_driver_partially_resident_pending_stores PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_shader_disk_cache_large_file COMMAND agc_shader_disk_cache_tests --large-file) + set_tests_properties(agc_shader_disk_cache_large_file PROPERTIES SKIP_RETURN_CODE 77 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:$") + endif() if(WIN32) add_test(NAME agc_driver_untracked_snapshot_hint COMMAND agc_driver_partially_resident_snapshot_tests --untracked-hint) set_tests_properties(agc_driver_untracked_snapshot_hint PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30 diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index d508666730..6adaebcddd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -17,7 +17,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -29,6 +31,7 @@ #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -730,6 +733,64 @@ void verifyFloatModeSerialization() { require(back.request.context.floatMode == mode, "the float mode did not survive serialization"); } +int runLargeFileWrite() { + constexpr std::size_t size = (std::size_t{2} << 30u) + 4096u; + const auto directory = std::filesystem::temp_directory_path() / "aps5-shader-disk-cache-large-file-test"; + std::error_code error; + std::filesystem::remove_all(directory, error); + std::filesystem::create_directories(directory, error); + const auto space = std::filesystem::space(directory, error); + if (error || space.available < size + (std::uintmax_t{1} << 30u)) { + std::cout << "skipping: not enough free disk space for a file of more than 2 GiB\n"; + return 77; + } + std::vector bytes; + try { + bytes.resize(size); + } catch (const std::bad_alloc&) { + std::cout << "skipping: cannot allocate a buffer of more than 2 GiB\n"; + return 77; + } + const std::array samples{0u, (std::size_t{1} << 30u) - 1u, std::size_t{1} << 30u, (std::size_t{2} << 30u) - 1u, std::size_t{2} << 30u, size - 1u}; + for (std::size_t index = 0; index < samples.size(); ++index) bytes[samples[index]] = static_cast(0xa0u + index); + const auto path = directory / "pipelines.bin"; + auto written = std::make_shared>(); + auto result = written->get_future(); + std::thread([written, &bytes, path] { + try { + written->set_value(WriteFileAtomically(path, bytes)); + } catch (...) { + written->set_exception(std::current_exception()); + } + }).detach(); + if (result.wait_for(std::chrono::seconds(120)) != std::future_status::ready) { + std::cerr << "WriteFileAtomically did not return within 120 s for a file of more than 2 GiB\n"; + std::cerr.flush(); + std::_Exit(1); + } + require(result.get(), "cannot write a cache file of more than 2 GiB"); + require(std::filesystem::file_size(path) == size, "the cache file of more than 2 GiB has the wrong size"); + { + std::ifstream file(path, std::ios::binary); + require(static_cast(file), "cannot open the cache file of more than 2 GiB"); + for (std::size_t index = 0; index < samples.size(); ++index) { + file.seekg(static_cast(samples[index])); + char value = 0; + require(file.get(value) && static_cast(value) == bytes[samples[index]], "the cache file of more than 2 GiB differs at byte " + std::to_string(samples[index])); + } + } + std::size_t entries = 0; + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + static_cast(entry); + ++entries; + } + require(entries == 1u, "the cache file of more than 2 GiB left a temporary file behind"); + std::filesystem::remove_all(directory, error); + require(!error, "cannot remove the large file test directory: " + error.message()); + std::cout << "large cache file write test passed\n"; + return 0; +} + void verifyAtomicReplacement(const std::filesystem::path& directory) { struct Reader { #ifdef _WIN32 @@ -2124,6 +2185,7 @@ int main(int argc, char** argv) { std::cout << "artifact codec tests passed\n"; return 0; } + 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(); if (argc == 2 && std::string_view(argv[1]) == "--no-failure-memo") return runWithoutFailureMemo(); diff --git a/core/shader/recompiler/ShaderCacheDirectory.hpp b/core/shader/recompiler/ShaderCacheDirectory.hpp index ee9393e078..84fbd1ae36 100644 --- a/core/shader/recompiler/ShaderCacheDirectory.hpp +++ b/core/shader/recompiler/ShaderCacheDirectory.hpp @@ -1,6 +1,7 @@ #ifndef CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP #define CORE_SHADER_RECOMPILER_SHADERCACHEDIRECTORY_HPP +#include #include #include #include @@ -56,7 +57,12 @@ inline bool WriteFileAtomically(const std::filesystem::path& path, std::span(bytes.data()), static_cast(bytes.size())); + constexpr std::size_t largestWrite = std::size_t{1} << 30u; + for (std::size_t offset = 0; offset < bytes.size() && file;) { + const auto chunk = std::min(bytes.size() - offset, largestWrite); + file.write(reinterpret_cast(bytes.data() + offset), static_cast(chunk)); + offset += chunk; + } file.close(); written = !file.fail(); }