Кэш скомпилированных шейдеров - один из тех механизмов, которые незаметно работают до тех пор, пока не начинают мешать. При первом запуске игры или тяжёлого приложения видеодрайвер компилирует шейдеры под конкретное железо, что занимает секунды или даже минуты. Повторные запуски должны быть мгновенными, но на практике размер кэша ограничен, старые записи вытесняются, а при обновлении драйвера кэш полностью инвалидируется. Понимание переменных окружения, путей хранения и способов прогрева позволяет убрать микрофризы, сократить время запуска и предсказуемо управлять дисковым пространством.

Устройство кэша шейдеров в Mesa и его размещение на диске

В Mesa кэш шейдеров реализован в двух вариациях: классический многофайловый и современный однобайтовый на базе Fossilize. По умолчанию в Mesa 24.x включён многофайловый формат, данные складываются в каталог ~/.cache/mesa_shader_cache (или $XDG_CACHE_HOME/mesa_shader_cache). Каждый скомпилированный шейдер сохраняется как отдельный файл, что создаёт десятки тысяч мелких записей. Альтернативный однобайтовый формат включается переменной MESA_DISK_CACHE_SINGLE_FILE=1 - создаётся база mesa_shader_cache_sf с бинарниками и индексами. Третий вариант - Mesa-DB - активируется через MESA_DISK_CACHE_DATABASE=1, кладёт данные в ~/.cache/mesa_shader_cache_db, поддерживает лимит через MESA_SHADER_CACHE_MAX_SIZE и разбивает базу на 50 частей (меняется через MESA_DISK_CACHE_DATABASE_NUM_PARTS).

Переменная MESA_SHADER_CACHE_MAX_SIZE задаёт порог в байтах с суффиксами K, M, G (гигабайты по умолчанию). Если лимит не задан - используется 1 ГБ на архитектуру, на системе с 32- и 64-битными драйверами - два гигабайта суммарно. MESA_SHADER_CACHE_DIR переопределяет корневой каталог для переноса кэша на NVMe или tmpfs. MESA_SHADER_CACHE_DISABLE=true выключает дисковый кэш, MESA_SHADER_CACHE_SHOW_STATS=true заставляет драйвер выводить статистику попаданий и промахов при завершении приложения.

Для Vulkan-драйверов RADV и ANV действуют те же переменные - они используют общую инфраструктуру Mesa. RADV_PERFTEST=rgp включает захват трасс для Radeon GPU Profiler, показывая время компиляции конвейеров в сцене. MESA_DISK_CACHE_DATABASE_EVICTION_SCORE_2X_PERIOD (по умолчанию 2592000 секунд - 30 дней) управляет алгоритмом вытеснения: чем дольше запись не запрашивалась, тем выше её штраф. Для задач через ROCm полезно увеличить до 7776000 (90 дней). MESA_DISK_CACHE_READ_ONLY_FOZ_DBS подключает до восьми готовых Fossilize-баз для мгновенного прогрева.

Кэш шейдеров NVIDIA переменные и нюансы очистки

Проприетарный драйвер NVIDIA хранит кэш в $XDG_CACHE_HOME/nvidia/GLCache (или ~/.cache/nvidia/GLCache). До 470-й серии использовался ~/.nv/GLCache - если остался, драйвер продолжит его использовать. Три переменные управляют поведением: __GL_SHADER_DISK_CACHE (0 или 1) включает или выключает кэш для прямого рендеринга, __GL_SHADER_DISK_CACHE_PATH задаёт каталог, __GL_SHADER_DISK_CACHE_SIZE ограничивает размер в байтах с суффиксами k, M, G. Если __GL_SHADER_DISK_CACHE_SIZE задан локально для одного приложения, а затем запускается другое без переменной, драйвер сбросит кэш до дефолтного размера и удалит лишнее. Экспортируйте переменную в ~/.profile или /etc/environment.

Переменная __GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1 отключает автоматическую очистку устаревших записей - полезно при профилировании. По умолчанию драйвер сам удаляет старые шейдеры при достижении лимита. Кэш привязан к версии драйвера: обновление с 550 на 560-ю серию заставит перекомпилировать всё заново. Для диагностики удобно включить __GL_SHADER_DISK_CACHE=1 и __GL_SYNC_TO_VBLANK=0 вместе с MESA_SHADER_CACHE_SHOW_STATS на стороне Mesa-приложений под Zink. __GL_HEAP_ALLOC_LIMIT (например, 20MB) ограничивает кучу драйвера и косвенно влияет на кэш. В драйвере 550.127 флаг SKIP_CLEANUP заработал для Vulkan-контекстов единообразно с OpenGL.

DXVK и vkd3d-proton свой кэш поверх Vulkan

DXVK кэширует не только скомпилированные SPIR-V, но и промежуточное представление конвейеров. Переменная DXVK_SHADER_CACHE=0 отключает внутренний кэш полностью, DXVK_SHADER_CACHE_PATH перенаправляет файлы - по умолчанию $XDG_CACHE_HOME/dxvk или ~/.cache/dxvk в нативном Linux, в Wine-префиксе это %LOCALAPPDATA%/dxvk. DXVK_STATE_CACHE_PATH (или устаревшее DXVK_STATE_CACHE) указывает путь для кэша состояний конвейеров - отдельной базы, где хранятся уже слинкованные объекты VkPipeline. Разделение шейдерного и стейт-кэша позволяет чистить одно не трогая другое.

Включение DXVK_HUD=compiler показывает активность компилятора в реальном времени: количество ожидающих задач, занятость рабочих потоков, время компиляции отдельного конвейера. На слабых процессорах при запуске игры с Graphics Pipeline Library (VK_EXT_graphics_pipeline_library) может возникнуть пик загрузки CPU на 100% в течение 20-60 секунд - это нормально, шейдеры компилируются заранее, а не в момент отрисовки. Прогрев кэша делается простым запуском игры с DXVK_HUD=compiler и ожиданием, пока счётчик задач упадёт до нуля.

vkd3d-proton для Direct3D 12 использует схожие переменные: VKD3D_SHADER_CACHE=0 отключает кэш, VKD3D_SHADER_CACHE_PATH меняет каталог, VKD3D_CONFIG=shader_cache_size=2G задаёт лимит через конфиг-строку. Дополнительно VKD3D_DEBUG=shaderdb включает логирование попаданий в кэш. При использовании Steam Play переменные прокидываются через PROTON_SHADER_CACHE_DIR и PROTON_SHADER_CACHE_SIZE - удобно для переноса кэша на отдельный раздел. Переменная VKD3D_CONFIG=disable_cached_pipelines=1 полностью отключает кэширование конвейеров, оставляя только шейдерный кэш - полезно для отладки ошибок линковки. DXVK_CONFIG_FILE указывает путь к .conf с тонкими настройками: dxgi.maxFrameLatency = 1, d3d11.forceMapFlags = True влияют на то, как и когда DXVK запрашивает компиляцию. Параметр compiler.threads в конфиге ограничивает число рабочих потоков компилятора - на 4-ядерных CPU ставим 2, на 16-ядерных - 8, чтобы не убить отзывчивость системы во время прогрева.

Прогрев кэша рендерингом от теории к практике

Прогрев - это запуск приложения с целью заполнить кэш всеми шейдерами, которые встретятся в реальной игре. Самый надёжный способ: пройтись по всем меню, загрузить несколько сохранений, поиграть 10-15 минут в типичных сценах. Для автоматизации используют скрипты, которые эмулируют ввод через xdotool или ydotool, либо запускают игру в безголовом режиме с переменной MESA_VK_TRACE=rgp для захвата трассы. В Mesa 24.2 появилась переменная MESA_VK_TRACE_FRAME=N - она автоматически останавливает трассировку на N-м кадре, что позволяет снять профиль именно этапа прогрева.

Числовой пример: в Cyberpunk 2077 на Radeon 6800 XT с Mesa 24.1 первый запуск после очистки кэша занял 42 секунды до главного меню, повторный - 18 секунд. После целенаправленного прогрева (прохождение тюториала, бой, вождение) третий запуск составил 12 секунд. Размер кэша вырос с 180 МБ до 1.2 ГБ. Включение MESA_DISK_CACHE_DATABASE=1 сократило число файлов с 47 тысяч до 3, а время запуска ещё на 15% - за счёт отсутствия накладных расходов на inode.

Для NVIDIA аналогичный тест на RTX 4070 с драйвером 550.127: первый запуск 38 секунд, после прогрева 14 секунд. Кэш в ~/.cache/nvidia/GLCache занял 890 МБ. Установка __GL_SHADER_DISK_CACHE_SIZE=2G предотвратила вытеснение при следующих обновлениях игры. Дополнительный тест на Intel Arc A770 с Mesa 24.2: ANV с MESA_DISK_CACHE_DATABASE=1 и INTEL_MEASURE=frame показал сокращение времени запуска Shadow of the Tomb Raider с 31 до 19 секунд после прогрева. Кэш mesa_shader_cache_db разросся до 640 МБ на 50 частях, среднее время компиляции конвейера снизилось с 8.4 мс до 0.3 мс за счёт попаданий в кэш. Автоматизированный прогрев для регрессионного тестирования: скрипт запускает игру с MESA_VK_TRACE=rgp MESA_VK_TRACE_FRAME=200, ждёт завершения, парсит .rgp через rgp_cli --csv, считает сумму PipelineCompileDuration. Если сумма превышает порог (например, 500 мс на запуск) - кэш считается "холодным", прогрев не завершён.

Измерение времени компиляции и поиск узких мест

Инструменты профилирования делятся на два класса: встроенные в драйверы и внешние. INTEL_MEASURE=draw,file=/tmp/measure.csv на Intel-графике выдаёт CSV с таймингами каждого draw-вызова, включая стадию компиляции шейдеров. RADV_PERFTEST=rgp генерирует .rgp-файл для Radeon GPU Profiler - там видно, сколько микросекунд ушло на каждый этап конвейера: компиляция SPIR-V в NIR, оптимизация NIR, генерация машинного кода, линковка.

В Mesa MESA_VK_TRACE=rgp работает для любого Vulkan-приложения, включая DXVK и vkd3d-proton. Запуск:
CODE0
создаст трассу первых 100 кадров. Открытие в RGP показывает timeline с маркерами "Pipeline Compile" - их длительность и есть чистое время компиляции. Если пики превышают 16 мс (один кадр при 60 FPS), игрок почувствует фриз. Параллельно MESA_VK_TRACE=rmv даёт трассу для Radeon Memory Visualizer - там видно, сколько видеопамяти съел кэш дескрипторов и буферов шейдеров. Для NVIDIA Nsight Graphics с маркером NVTX (включается через __GL_DEBUG=nvtx) показывает стеки вызовов драйвера и время nvkCompileShader в наносекундах.

Для OpenGL-приложений на Mesa переменная MESA_GLSL=dump заставит драйвер выводить GLSL-источники и NIR-промежуточный код в stderr. Комбинация MESA_GLSL=log,opt показывает каждый проход оптимизации с таймингами. На NVIDIA __GL_GLSL_DUMP=1 делает похожую вещь, но требует пересборки шейдеров при каждом запуске. INTEL_DEBUG=fs,vs,gs,cs,ts,ms,rt вместе с INTEL_SHADER_BIN_DUMP_PATH=/tmp/shaders сбрасывает готовые бинарники для анализа размера: типичный фрагментный шейдер в Cyberpunk 2077 занимает 12-45 КБ машинного кода, вершинный - 4-18 КБ.

Типичные ошибки и как их избежать

Первая ошибка - полагаться на дефолтный лимит 1 ГБ в Mesa. Современные игры (Baldur's Gate 3, Alan Wake 2, Cyberpunk 2077 с RT) легко выбивают кэш за 2-3 ГБ. Решение: export MESA_SHADER_CACHE_MAX_SIZE=4G в профиле оболочки. Вторая - хранить кэш на медленном HDD или сетевой файловой системе. Перемещение MESA_SHADER_CACHE_DIR и __GL_SHADER_DISK_CACHE_PATH на NVMe даёт прирост 20-40% на этапе загрузки шейдеров. Третья - забывать про архитектурную разделение: 32-битные игры (многие старые тайтлы в Wine) создают свой кэш рядом с 64-битным, удваивая потребление места.

Четвёртая ошибка - удалять кэш при каждом обновлении драйвера вручную. Драйверы делают это сами: Mesa проверяет версию внутреннего формата кэша, NVIDIA - версию драйвера в метаданных базы. Ручное удаление rm -rf ~/.cache/mesa_shader_cache* заставляет перекомпилировать шейдеры даже при минорном обновлении Mesa (например, 24.1.3 -> 24.1.4), где формат не менялся. Пятая - отключать кэш совсем (MESA_SHADER_CACHE_DISABLE=1 или __GL_SHADER_DISK_CACHE=0) "для чистоты эксперимента". Это удобно только для измерения чистого времени компиляции, в повседневной работе превращает каждый запуск в первый.

Шестая - игнорировать DXVK_STATE_CACHE_PATH. Многие настраивают только шейдерный кэш, а стейт-кэш остаётся в дефолтном месте и переполняется. Установка обеих переменных в одну директорию на быстром диске упрощает бэкап и мониторинг.

Практический чек-лист для стабильной работы

Для стабильной работы кэша шейдеров выполните следующие шаги:
1. Задать MESA_SHADER_CACHE_MAX_SIZE=4G и MESA_DISK_CACHE_DATABASE=1 в /etc/environment или ~/.profile для системного эффекта;
2. Вынести каталоги кэша на быстрый диск: MESA_SHADER_CACHE_DIR=/fast/nvme/mesa, __GL_SHADER_DISK_CACHE_PATH=/fast/nvme/nvidia, DXVK_SHADER_CACHE_PATH=/fast/nvme/dxvk, DXVK_STATE_CACHE_PATH=/fast/nvme/dxvk_state;
3. Включить статистику для аудита: MESA_SHADER_CACHE_SHOW_STATS=true - после нескольких запусков посмотреть вывод в терминале, соотношение hits/misses должно стремиться к 0.95;
4. Провести прогрев: запустить игру с DXVK_HUD=compiler или MESA_VK_TRACE=rgp, пройти ключевые сцены, дождаться завершения фоновой компиляции;
5. После крупного обновления драйвера (Mesa minor, NVIDIA major) не удалять кэш вручную - дать драйверу инвалидировать только устаревшее;
6. Для Intel-графики добавить INTEL_MEASURE=draw,file=/tmp/intel_measure.csv при профилировании проблемных сцен;
7. Периодически (раз в месяц) проверять размер каталогов du -sh ~/.cache/mesa_shader_cache ~/.cache/nvidia/GLCache ~/.cache/dxvk и корректировать лимиты.

Соблюдение этих правил превращает кэш шейдеров из "чёрного ящика" в предсказуемый инструмент. Игры запускаются быстрее, фризы на компиляции исчезают, дисковое пространство не уходит в неконтролируемый рост. Главное - задать лимиты и пути один раз, а затем лишь иногда проверять статистику попаданий.