fix(shader): key the device's capabilities and extensions by a digest

Every recompile cache key appended the target's capability list and
each SPIR-V extension name one byte per 64-bit word, so on a device that
enables a few dozen extensions most of every key was the same device
description: built and compared on every dispatch and draw, and kept in
every in-memory key and disk cache entry. The two lists now enter the key
as a 128-bit digest over the capabilities and the length and bytes of
each extension name, eight bytes per step.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
milescovielloandClaude Opus 5.5 committed 2026-10-24 00:20:45 +00:00
1 parent 3b23d18360
commit d7d9747e7d
2 files changed
+62 -2

No files matched your search

@@ -1713,6 +1713,41 @@ std::array<std::uint32_t, 8> TableSelected(const ShaderRecompiler::ImageTableSna
return column.words.at(column.codes[0]).dwords;
}
void verifyTargetFeatureKey() {
using namespace ShaderRecompiler;
static constexpr std::array<std::uint32_t, 1> code{0xbf810000u};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x10000u, code, 0, {}};
request.context.waveSize = 64;
const std::vector<std::uint32_t> capabilities{1u, 4448u, 5347u};
const std::vector<std::string_view> extensions{"SPV_KHR_8bit_storage", "SPV_KHR_physical_storage_buffer"};
const auto build = [&](std::span<const std::uint32_t> capabilityList, std::span<const std::string_view> extensionList) {
auto keyed = request;
keyed.target.supportedCapabilities = capabilityList;
keyed.target.supportedExtensions = extensionList;
std::vector<std::uint64_t> key;
RecompileCacheKey::Build(keyed, key);
return key;
};
const auto base = build(capabilities, extensions);
const std::vector<std::uint32_t> capabilityCopy = capabilities;
const std::vector<std::string> extensionStorage{"SPV_KHR_8bit_storage", "SPV_KHR_physical_storage_buffer"};
const std::vector<std::string_view> extensionCopy{extensionStorage[0], extensionStorage[1]};
require(build(capabilityCopy, extensionCopy) == base, "target feature key: equal capability and extension lists in other storage gave another key");
auto renamed = extensionStorage;
renamed[1].back() = 'X';
const std::vector<std::string_view> renamedViews{renamed[0], renamed[1]};
require(build(capabilities, renamedViews) != base, "target feature key: an extension name that differs in its last byte gave the same key");
const std::vector<std::string_view> moved{"SPV_KHR_8bit_storageS", "PV_KHR_physical_storage_buffer"};
require(build(capabilities, moved) != base, "target feature key: a byte moved between two extension names gave the same key");
const std::vector<std::uint32_t> more{1u, 4448u, 5347u, 11u};
require(build(more, extensions) != base, "target feature key: an added capability gave the same key");
std::vector<std::string> many;
for (std::uint32_t index = 0; index < 40u; ++index) many.push_back("SPV_EXT_descriptor_indexing_" + std::to_string(index));
const std::vector<std::string_view> manyViews(many.begin(), many.end());
require(build(capabilities, manyViews).size() == base.size(), "target feature key: the key grows with the device's extension names");
}
void verifyImageTableKeys() {
using namespace ShaderRecompiler;
namespace Abi = ImageTableAbi;
@@ -8377,6 +8412,7 @@ int main(int argc, char** argv) {
verifyGuardedDescriptorSources();
verifyLaneMaskGuardedDescriptorSources();
verifySrtTape();
verifyTargetFeatureKey();
verifyImageTableKeys();
verifyImageTables();
verifyImageTableRegistration();
+26 -2
View File
@@ -4,8 +4,10 @@
#include "Recompiler.hpp"
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <algorithm>
#include <bit>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <type_traits>
@@ -275,13 +277,35 @@ private:
append(key, value.maxEvaluationOutputComponents);
}
static void appendFeatureDigest(std::vector<std::uint64_t>& key, std::span<const std::uint32_t> capabilities, std::span<const std::string_view> extensions) {
std::uint64_t low = 0xcbf29ce484222325ull;
std::uint64_t high = 0x6a09e667f3bcc909ull;
const auto mix = [&](std::uint64_t value) {
low = (low ^ value) * 0x100000001b3ull;
high = (high ^ (value * 0x9e3779b97f4a7c15ull)) * 0xff51afd7ed558ccdull;
high ^= high >> 29u;
};
mix(capabilities.size());
for (const auto capability : capabilities) mix(capability);
mix(extensions.size());
for (const auto extension : extensions) {
mix(extension.size());
for (std::size_t offset = 0; offset < extension.size(); offset += sizeof(std::uint64_t)) {
std::uint64_t chunk = 0;
std::memcpy(&chunk, extension.data() + offset, std::min(sizeof(std::uint64_t), extension.size() - offset));
mix(chunk);
}
}
key.push_back(low);
key.push_back(high);
}
static void append(std::vector<std::uint64_t>& key, const SpirvTarget& value) {
append(key, value.vulkanVersion);
append(key, value.spirvVersion);
append(key, value.subgroupSize);
append(key, value.bdaAbiVersion);
append(key, value.supportedCapabilities);
append(key, value.supportedExtensions);
appendFeatureDigest(key, value.supportedCapabilities, value.supportedExtensions);
append(key, value.fragmentShaderBarycentricEnabled);
append(key, value.maxWorkgroupSize);
append(key, value.maxWorkgroupInvocations);