Repository navigation
Conversation
Add VulkanPipelineCache, an RAII VkPipelineCache that restores a cache file only after validating its header against the physical device and saves through a temporary file and rename. VulkanBase::SetPipelineCache shares one cache across compute and graphics pipelines; VulkanRender now passes it to vkCreateGraphicsPipelines and VulkanCompute keeps its legacy per-pipeline path on top of the validated loader. The FPS shooter, car control and physics demos persist one shared cache each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every Vulkan pipeline in CORE is compiled from scratch on every launch.
VulkanRender::CreatePipelinepassesVK_NULL_HANDLEas the pipeline cache, so graphics pipelines never use a cache.VulkanComputecreates a private cache for each pipeline. It is persisted only if a subclass overridesGetPipelineCache()and callsSavePipelineCache(); today only the Gaussian blur test does this. Three problems with that path:vkCreatePipelineCacheunchecked: no header, vendor, device or UUID check, and no size bound.CreatePipelinetwice overwrites and leaks the previousVkPipelineCache.Measured cost of a cold start for 5 real project compute pipelines: about 0.78 ms on macOS (MoltenVK) and about 21 ms on a Snapdragon (Adreno) Android phone. See Validation.
Solution
New
core::vulkan::VulkanPipelineCache(vulkan/include/VulkanPipelineCache.h,vulkan/src/VulkanPipelineCache.cpp), an RAII wrapper around oneVkPipelineCache.Loading a cache file:
kMaxDataSize(64 MiB).VkPipelineCacheHeaderVersionOnefirst. It decodes the fields as little-endian values at fixed offsets and checks each againstVkPhysicalDeviceProperties:headerSize == 32,headerVersion == ONE,vendorID,deviceIDandpipelineCacheUUID.restored()reports which case happened.Saving (
Save()/ staticWrite()):kMaxDataSizebefore allocating.VK_INCOMPLETEif the cache grows between the size query and the data read.falseon any failure instead of throwing, so persistence stays best-effort.VulkanBase::SetPipelineCache(const VulkanPipelineCache*)lets the caller share one cache across any number of pipelines.VulkanRendernow passes the shared cache tovkCreateGraphicsPipelines.VulkanComputeuses the shared cache when one is set. Otherwise it keeps the existingGetPipelineCache()/SavePipelineCache()behaviour, now backed by the validated loader through an ownedstd::unique_ptr. This also fixes the handle overwrite and leak.Callers:
FPSShooterDemo,CarControlDemoandPhysicsDemoeach create one cache atPIPELINE_CACHE_DIR/<demo>.cache, attach it to every pipeline, andSave()it once all pipelines are initialized.Usage:
The cache only has to outlive the
Init()calls that create pipelines. Pipelines do not reference it afterwards. As the Vulkan spec allows, aVkPipelineCachecan be used by concurrent pipeline creation without external synchronization.Alternatives considered:
VulkanContextown a global cache. This is less code at call sites, but it couples device setup to file I/O and takes away the caller's choice of which pipelines share a cache. Rejected.Validation
All results come from the final commit
e9783d0(tree58283bc). The review gate below closed before the final runs, and no source changed afterwards.macOS (local):
./scripts/run.sh -t macos -r tests, exit 0.-Werror), no compiler warnings.Android (physical device):
./scripts/run.sh -t arm64-v8a -r tests, exit 0.kalama, Android 13 / API 33.adb devices -lshowed exactly one authorized device, pinned withANDROID_SERIAL.New tests (
tests/vulkan/VulkanPipelineCacheTest.cpp, 6 tests), passing on both platforms:VkDevice, and the restored pipelines run; the compute dispatch output is checked. A shared cache takes precedence over the legacy path, which is never queried.headerSize. For each case the pipeline still builds and runs, andSave()replaces the file with valid data. Sparse files ofkMaxDataSize + 1bytes are rejected with both a valid and an invalid header.vkCreateGraphicsPipelines, records the cache argument, and forwards tovkGetDeviceProcAddr. The test assertsVK_NULL_HANDLEwhen no cache is set and the exact shared handle when one is. Call counts confirm the wrapper is in the call path on both platforms. A build whoseVulkanRenderpassedVK_NULL_HANDLEmade this test fail.SavePipelineCachethen writes a compatible file that restores.falseand leave no files behind.Benchmark:
vulkan_pipeline_cache_benchmark <cache-path> 21.VulkanContext/VkDeviceand builds 5 real project compute pipelines: Gaussian blur, sum, copy, FMA, barycentric rasterizer.vkCreatePipelineCache) is included in warm.Per pipeline on Android, Gaussian blur went from 6.37 ms to 0.15 ms. An exploratory run before the change also measured one Gaussian blur pipeline per process (12 processes per mode) with the raw Vulkan API: macOS about 347 µs to 53 µs, Android about 4.8 ms to 0.13 ms.
Demo smoke test:
vk_physics_demoran twice from the repo root for 5 s each. The first run wrotephysics_demo.cache(10,994 B, gitignored via*.cache); the second ran with it. No errors or validation messages.Review gate: independent GPT reviews (gpt-6.1-sol, high effort, read-only), 4 rounds. Findings and fixes:
The final round reported all findings resolved, no new findings, and
GATE: PASS.Limits:
Changes
git diff --numstat origin/master: 15 files, +872 / −59 = 931 lines.Implementation, 277 lines:
vulkan/include/VulkanPipelineCache.h+43vulkan/src/VulkanPipelineCache.cpp+151vulkan/include/VulkanBase.h+5vulkan/src/VulkanBase.cpp+4vulkan/include/VulkanCompute.h+4/−3vulkan/src/VulkanCompute.cpp+9/−54vulkan/src/VulkanRender.cpp+2/−2Demo callers, 24 lines:
FPSShooterDemo/main.cpp+9,CarControlDemo/main.cpp+8,PhysicsDemo/main.cpp+7.Tests, 396 lines:
tests/vulkan/VulkanPipelineCacheTest.cpp+379,PipelineCacheProbe.vert+9,PipelineCacheProbe.frag+8.Benchmark, 234 lines:
tests/vulkan/benchmarks/PipelineCacheBenchmark.cpp+218,tests/vulkan/CMakeLists.txt+16 (benchmark target).Docs: 0.
Risk
SetPipelineCacheand the new class are additions.VulkanCompute::CreatePipelineCacheandLoadPipelineCachehelpers are removed.VulkanComputeobject layout changes (one newunique_ptr), so dependent code must be rebuilt.SavePipelineCachenow writes through temp-and-rename.<demo>.cacheinPIPELINE_CACHE_DIR(the repo root when built withrun.shon macOS); these files are covered by.gitignore. A stale or foreign cache is detected by the header check, and pipelines are rebuilt.vkCreateGraphicsPipelineswrapper invulkan_testsforwards transparently through the loader. It relies on the executable's definition taking precedence over the loader's, which the call-count assertions confirm on macOS and Android.VulkanBase.h/.cpp(an added member and method) is possible. There is no functional overlap.🤖 Generated with Claude Code