Skip to content

Windows shader and pipeline caches cannot persist under long host paths (PPSA99902) #7452

Open

Description

@Amoenus

Current coverage, 2026-10-21 UTC

#7933 merged as d14a186f46, carrying #7462's shared helper with its original authorship and our credited mapped-reader corrections. #7462 was closed as covered. Journal and mapped entry/pack fixes are upstream.

#8580 has also merged, as 01fbf1060d0c66883a800d75678e61ac7675083c, present in checked main c8301bbca5. It handles native Windows paths while discovering prebuilt packs/loose cache directories. This updates the source/status coverage; we have not performed a fresh runtime test of that merge.

#8554 and #7945 have now both merged, as 042504ea2820a97e6add8c89829ea65e4244e7a1 and 854a514bbe070bfa4322aec040e796d46813895e, respectively. #8554 covers PipelineCache::readHeader and the failed-read existence check; #7945 avoids unsafe namespace-root directory traversal. Thus the previous reason to hold this issue for an unmerged #7945 is obsolete.

All tracked production corrections are now upstream in checked main 27c88a8d75caa49e1f143594cd657d6368ab5563. Remaining: focused current-main verification of these paths together before closing this report, not another duplicate implementation. #7945 intentionally skips namespace-root eviction; its long device-alias stamp limitation remains documented debt. Our header-regression offer and historical evidence below retain their original pins; we have not freshly runtime-tested these merges. No new title, SMB end-to-end or performance result, and no claim that cache persistence causes level-loading behavior.

AI-assisted report maintenance: yes (Codex).

Original reproductions, proposed corrections and validation at their recorded pins

Failure

Sonic Unleashed Recompiled (PPSA99902, #3036) logs shader-cache write failures and cannot persist its Vulkan pipeline cache when the default cache is beside the converted executable under a long Windows host path:

[pipeline-cache] cannot write <run>/shader_cache/vk-pipelines-10de-2c02-988f8000-3d19fb82df5fbc0228ca3b3f3908d34c.bin
[shader-disk-cache] (10 s): 0 hits, 3 misses, 0 writes (0.0 MiB), 0 load failures, 6 write failures

The first path is abbreviated above; its actual length is 269 characters, and its parent directory exists. The title uses integration 8d4b244dac on main1c523, the standard a515 converted executable, unchanged original configuration, and the default cache settings. This is not an unchanged-main whole-title result. Menu/cinematic rendering continues; this report establishes failed cache persistence, not the cause of level loading or an FPS change.

Isolated current-main reproduction

ShaderCacheDirectory.hpp on main ed01d79d3c is byte-identical to the tested integration header (Git blob 84fbd1ae361a7243165fe3c638eeb5e87b967d5c). A standalone Windows program calls the real inline WriteFileAtomically and ReadWholeFile helpers with four-byte synthetic data. It makes an existing 150-character absolute parent directory, then uses filenames giving these total path lengths. Each row reproduced in 3 fresh processes:

Path characters Atomic write, ordinary path Direct stream write, extended path Helper read, ordinary path Helper read, extended path
180 succeeds succeeds succeeds succeeds
235 fails succeeds succeeds succeeds
269 fails succeeds fails succeeds
320 fails succeeds fails succeeds

All direct extended-path writes/readbacks match the original four bytes. The 235-character destination is below MAX_PATH, but the helper appends a unique .tmp.* suffix before opening it. Merely passing an extended path into the existing atomic writer also fails, including the short control, so adding a prefix at the caller is not a validated correction.

Minimal shape of the probe, compiled with g++ -std=c++23 -O0 -I core/shader/recompiler probe.cpp:

#include "ShaderCacheDirectory.hpp"
#include <array>
#include <iostream>

int main(int argc, char** argv) {
    if (argc != 2) return 2;
    const auto root = std::filesystem::absolute(std::filesystem::path(argv[1]));
    if (root.native().size() >= 145) return 3;
    const auto directory = root / std::wstring(149 - root.native().size(), L'd');
    const std::array<std::byte, 4> data{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}};
    for (const auto length : {180u, 235u, 269u, 320u}) {
        const auto path = directory / (std::wstring(length - directory.native().size() - 5, L'x') + L".bin");
        const auto extended = std::filesystem::path(L"\\\\?\\" + path.native());
        const bool written = ShaderRecompiler::WriteFileAtomically(path, data);
        std::ofstream reference(extended, std::ios::binary | std::ios::trunc);
        reference.write(reinterpret_cast<const char*>(data.data()), data.size());
        reference.close();
        std::vector<std::byte> ordinary, prefixed;
        const bool read = ShaderRecompiler::ReadWholeFile(path, ordinary);
        const bool extendedRead = ShaderRecompiler::ReadWholeFile(extended, prefixed);
        std::cout << length << " " << written << " " << !reference.fail()
                  << " " << read << " " << extendedRead << " "
                  << (prefixed == std::vector<std::byte>(data.begin(), data.end())) << '\n';
    }
}

Pass a fresh short absolute directory as argv1. Windows11 Enterprise26200, WinLibs GCC15.2.0 r7/UCRT. LongPathsEnabled=1; the standalone probe has no long-path-aware manifest. Microsoft's long-path documentation describes the separate application opt-in and extended path form. #4618 opted in relinker.exe, not converted executables; the runtime library should not assume that host opt-in.

Scope and follow-up

This concerns host shader/pipeline cache I/O, separate from merged guest fopen #7187 and guest stat #7196. #6771 moves the pipeline-cache file into a source-version directory and does not correct these file opens. At issue creation, searches for cache long paths/MAX_PATH/WriteFileAtomically found no matching open or merged fix.

AlexanderCalli's #7462 now proposes the shared writer/reader correction. Its existing tests and our additional host-path checks pass on Windows. Independent validation and an additional mapped-entry/pack gap record the exact source pins, successful-write checks, repeated failures and correction.

The focused fork proposal 0a3621bbbf, based on that exact PR and current maincb1, reuses its helper for the two remaining mapped opens. The regression verifies correct published bytes, mapped SPIR-V and prebuilt-pack round trips. Full Windows/Linux builds of 0a3621bbbf pass (SPIRV-Tools ON), with 51 selected test passes across RTX5080, AMD integrated graphics, Windows CPU and Linux/lavapipe, plus one explicit Linux pipeline-cache skip. The linked validation comment gives the exact matrix and analysis limits. The parent PR also needs its independently reproduced cross-drive test correction, proposed on the fork. The integration has now been refreshed to main 9caecfd with these changes at e4c6c8f970; its runtime libraries and12 repeated cache checks pass. Ordinary180s/90s RTX5080 replays now confirm 60 shader writes followed by56 shader hits with zero misses/load failures, plus successful reload of5461.2KiB from the original269-character pipeline-cache file. Each replay hash-restores145 prior files, including3 cache files, and verifies all127 runtime-library hashes and unchanged original inputs/configs. The short replays reach Continue/opening cinematic and establish persistence/readback; they do not revalidate sustained gameplay or establish an FPS/speed change. Full current evidence. This deeper-directory case is additional coverage; it is not established as the cause of Sonic's original startup time. No duplicate shared-helper PR or performance claim.

Remaining header reader: confirmed and proposed to #7462

The lightweight PipelineCache::readHeader still uses a plain path after #7462's shared read/write correction. An AMD diagnostic counted1→4 Vulkan cache creations across three unchanged idle saves. The focused regression fails3/3 per physical GPU; reverting only the reader on the final fixture also fails on each GPU. Focused correction 8d4b4914f0, on main0d078f75d3 plus exact #7462/3df970aeb7, passes full Windows/Linux builds,18 selected Windows GPU runs and6 lavapipe runs (one explicit neighboring file-merge skip). It uses AlexanderCalli's helper and provides an idle-save regression; the fixture keeps directory listings belowMAX_PATH. Maintainer-directed proposal and exact validation. This confirms redundant cache reloads, not a measured title speedup. Other single-file and directory-housekeeping review points remain tracked in #7462.

AI-assisted: yes, Codex; author responsible for the report and validation.

Activity

  1. pushkarreddyy commented on Oct 18, 2026

    @pushkarreddyy
    Contributor

    I am looking into this Windows path length limitation in ShaderDiskCache and pipeline cache persistence, and will open a PR shortly with extended-path (\\?\) handling and regression tests.

  2. Amoenus commented on Oct 18, 2026

    @Amoenus
    ContributorAuthor

    For coordination, @pushkarreddyy: #7462 already provides the shared extended-path helper. Its latest reviews list the remaining file-I/O and directory-housekeeping cases. We have supplied tested mapped-cache and relative-path fixture corrections there, and now a verified header-reader follow-up. That thread has the current implementation and evidence for coordinating the remaining work.

  3. pushkarreddyy commented on Oct 18, 2026

    @pushkarreddyy
    Contributor

    Thanks @Amoenus for the heads-up and coordination note! Understood—I will stand down on this path so as not to duplicate effort, and leave the extended-path handling and cache persistence follow-ups to #7462.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions