Merge pull request #1580 from Zheorgyan/feat/storage-buffers-update-after-bind

feat(agc): bind storage buffers update-after-bind above the per-stage limit
This commit is contained in:
Adrià 2026-10-10 03:12:01 +02:00 committed by GitHub
commit a9ac7bc4c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 39 deletions

View File

@ -201,6 +201,8 @@ struct VulkanDevice::State {
// VK_EXT_descriptor_indexing with non-uniform image array indexing (bindless image tables in
// graphics stages, and compute workgroups wider than a wave).
bool descriptorIndexing = false;
bool storageBufferUpdateAfterBind = false;
VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptorIndexingProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT};
bool imageInt64Atomics = false;
bool bufferInt64Atomics = false;
bool primitiveListRestart = false;
@ -1008,17 +1010,23 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &descriptorIndexingFeatures};
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
state->descriptorIndexing = descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing == VK_TRUE && descriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing == VK_TRUE;
state->storageBufferUpdateAfterBind = descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind == VK_TRUE;
if (state->storageBufferUpdateAfterBind) {
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &state->descriptorIndexingProperties};
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &properties);
}
}
descriptorIndexingFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT};
if (state->descriptorIndexing || state->storageBufferUpdateAfterBind) deviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
if (state->descriptorIndexing) {
descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
descriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
deviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
state->capabilities.push_back(spv::CapabilityShaderNonUniform);
state->capabilities.push_back(spv::CapabilitySampledImageArrayNonUniformIndexing);
state->capabilities.push_back(spv::CapabilityStorageImageArrayNonUniformIndexing);
state->spirvExtensions.push_back("SPV_EXT_descriptor_indexing");
}
descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind = state->storageBufferUpdateAfterBind ? VK_TRUE : VK_FALSE;
state->samplerAnisotropy = true;
state->textureCompressionBC = true;
deviceInfo.pEnabledFeatures = &enabled;
@ -1074,7 +1082,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
imageRobustnessFeatures.pNext = byteFeatures.pNext;
byteFeatures.pNext = &imageRobustnessFeatures;
}
if (state->descriptorIndexing) {
if (state->descriptorIndexing || state->storageBufferUpdateAfterBind) {
descriptorIndexingFeatures.pNext = byteFeatures.pNext;
byteFeatures.pNext = &descriptorIndexingFeatures;
}
@ -2558,6 +2566,7 @@ Graphics::Context VulkanDevice::buildContext() const {
context.copiedWriters = state->copiedWriters.get();
context.functions = state->functionsReady ? &state->deviceFunctions : nullptr;
context.descriptorIndexing = state->descriptorIndexing;
context.descriptorIndexingLimits = state->descriptorIndexingProperties;
context.imageInt64Atomics = state->imageInt64Atomics;
context.geometryShader = state->geometryShader;
context.sampleRateShading = state->sampleRateShading;

View File

@ -137,6 +137,7 @@ struct Context {
// VK_EXT_descriptor_indexing with non-uniform sampled/storage image array indexing enabled
// (bindless image tables in graphics stages).
bool descriptorIndexing = false;
VkPhysicalDeviceDescriptorIndexingPropertiesEXT descriptorIndexingLimits{};
bool imageInt64Atomics = false;
bool geometryShader = false;
bool sampleRateShading = false;

View File

@ -68,12 +68,12 @@ public:
// The layout for `key` (binding, type, count, stage flags per binding, as ShaderResources builds
// it from `bindings`), created on first use.
VkDescriptorSetLayout Layout(std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings);
VkDescriptorSetLayout Layout(std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings, bool updateAfterBind = false);
struct SetAllocation {
VkDescriptorSet set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
};
SetAllocation Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes);
SetAllocation Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes, bool updateAfterBind = false);
void Free(const SetAllocation& allocation) noexcept;
// APS5_PROFILE_DRAW counters: layouts served from the map / created, sets allocated, pools opened.
struct Stats {
@ -93,6 +93,7 @@ private:
mutable std::mutex mutex;
std::map<std::vector<std::uint32_t>, VkDescriptorSetLayout> layouts;
std::vector<VkDescriptorPool> pools;
std::vector<VkDescriptorPool> updateAfterBindPools;
std::vector<VkDescriptorPool> dedicated;
Stats stats;
};
@ -458,6 +459,7 @@ private:
};
std::vector<DeferredImages> deferredImages;
std::uint64_t storageBuffers = 0;
bool updateAfterBind = false;
std::uint32_t plannedSampledImages = 0;
std::uint32_t plannedStorageImages = 0;
// The compute constructor's shader and captured regions: the caller's objects, valid only until

View File

@ -965,6 +965,33 @@ void ShaderResources::build(std::span<const CompiledShader> shaders, const Color
namespace {
VkDescriptorSetLayout CreateSetLayout(const Context& context, std::span<const VkDescriptorSetLayoutBinding> bindings, bool updateAfterBind) {
std::vector<VkDescriptorBindingFlags> flags;
for (const auto& binding : bindings) flags.push_back(updateAfterBind && binding.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0);
VkDescriptorSetLayoutBindingFlagsCreateInfo bindingFlags{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO};
bindingFlags.bindingCount = static_cast<std::uint32_t>(flags.size());
bindingFlags.pBindingFlags = flags.data();
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
info.pNext = updateAfterBind ? &bindingFlags : nullptr;
info.flags = updateAfterBind ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT : 0;
info.bindingCount = static_cast<std::uint32_t>(bindings.size());
info.pBindings = bindings.data();
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &layout), "vkCreateDescriptorSetLayout");
return layout;
}
VkDescriptorPool CreateSetPool(const Context& context, std::span<const VkDescriptorPoolSize> sizes, bool updateAfterBind) {
VkDescriptorPoolCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
info.flags = updateAfterBind ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0;
info.maxSets = 1;
info.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
info.pPoolSizes = sizes.data();
VkDescriptorPool pool = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &info, nullptr, &pool), "vkCreateDescriptorPool");
return pool;
}
// APS5_PROFILE_DRAW: the [resources] phase totals. Each thread accumulates its builds' phases in
// arrays of its own and merges them into the shared totals every 1000 of its builds (and when it
// ends), so no build takes the shared mutex per phase; the totals lag by up to 999 builds per
@ -1045,7 +1072,8 @@ double ShaderResources::phase(BuildPhase which) {
namespace {
void ValidateDescriptorLayout(const Context& context, std::span<const VkDescriptorSetLayoutBinding> bindings) {
void ValidateDescriptorLayout(const Context& context, std::span<const VkDescriptorSetLayoutBinding> bindings, bool updateAfterBind) {
const auto maxStageResources = updateAfterBind ? context.descriptorIndexingLimits.maxPerStageUpdateAfterBindResources : context.limits.maxPerStageResources;
std::map<VkDescriptorType, std::uint64_t> heapTotals;
std::map<std::pair<VkShaderStageFlags, VkDescriptorType>, std::uint64_t> stageTotals;
std::map<VkShaderStageFlags, std::uint64_t> stageResources;
@ -1056,7 +1084,7 @@ void ValidateDescriptorLayout(const Context& context, std::span<const VkDescript
if ((layout.stageFlags & stage) == 0u) continue;
const auto count = stageTotals[{stage, layout.descriptorType}] += layout.descriptorCount;
const auto all = stageResources[stage] += layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ? 0u : layout.descriptorCount;
Require(all <= context.limits.maxPerStageResources, "typed heaps exceed per-stage resource capacity");
Require(all <= maxStageResources, "typed heaps exceed per-stage resource capacity");
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) Require(count <= context.limits.maxPerStageDescriptorSampledImages, "typed heaps exceed per-stage sampled image capacity");
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) Require(count <= context.limits.maxPerStageDescriptorStorageImages, "typed heaps exceed per-stage storage image capacity");
if (layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER) Require(count <= context.limits.maxPerStageDescriptorSamplers, "typed heaps exceed per-stage sampler capacity");
@ -1118,6 +1146,12 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
std::int64_t shaderData = -1;
const auto firstSampler = samplers.size();
const auto firstDeferred = deferredImages.size();
const auto requireStageLimits = [&] {
if (stageStorageBuffers <= context.limits.maxPerStageDescriptorStorageBuffers && stageResources <= context.limits.maxPerStageResources) return;
const auto& limits = context.descriptorIndexingLimits;
Require(stageStorageBuffers <= limits.maxPerStageDescriptorUpdateAfterBindStorageBuffers && stageResources <= limits.maxPerStageUpdateAfterBindResources, "shader descriptors exceed per-stage limits");
updateAfterBind = true;
};
for (const auto& binding : shader.program->bindings) {
Require(binding.descriptorSet == 0, "unexpected descriptor set: every shader resource must use descriptor set zero");
Require(occupied.insert(binding.binding).second, "duplicate shader binding");
@ -1126,7 +1160,7 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
const bool imageRole = binding.role == ShaderRecompiler::DescriptorRole::GuestImages || binding.role == ShaderRecompiler::DescriptorRole::GuestSamplers;
if (imageRole) {
if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage || binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) stageResources += binding.count;
Require(stageResources <= context.limits.maxPerStageResources, "shader descriptors exceed per-stage limits");
requireStageLimits();
addImageBinding(binding, flags);
continue;
}
@ -1137,7 +1171,7 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
stageStorageBuffers += binding.count;
stageResources += binding.count;
storageBuffers += binding.count;
Require(stageStorageBuffers <= context.limits.maxPerStageDescriptorStorageBuffers && stageResources <= context.limits.maxPerStageResources, "shader descriptors exceed per-stage limits");
requireStageLimits();
Binding item{{binding.binding, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, binding.count, flags, nullptr}, {}};
if (binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers) {
Require(binding.guestDescriptor.size() == static_cast<std::uint64_t>(binding.count) * 4, "guest buffer descriptor must contain four DWORDs per array element");
@ -1184,7 +1218,10 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
deferredImages[index].samplerCount = samplers.size() - firstSampler;
}
}
Require(storageBuffers <= context.limits.maxDescriptorSetStorageBuffers, "pipeline descriptors exceed device limits");
if (storageBuffers > context.limits.maxDescriptorSetStorageBuffers) {
Require(storageBuffers <= context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindStorageBuffers, "pipeline descriptors exceed device limits");
updateAfterBind = true;
}
timing.bindingsMs = phase(BuildPhase::Bindings);
// For every build, locked ones included: their stage B then takes the fast path too, and the
// collects cost the same wherever they run.
@ -1197,16 +1234,14 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
description.push_back(binding.layout);
layoutKey.insert(layoutKey.end(), {binding.layout.binding, static_cast<std::uint32_t>(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags});
}
if (updateAfterBind) layoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
static const bool noLayoutCache = std::getenv("APS5_NO_LAYOUT_CACHE") != nullptr;
static const bool noPoolCache = std::getenv("APS5_NO_POOL_CACHE") != nullptr;
if (context.descriptorCache != nullptr && !noLayoutCache) {
_layout = context.descriptorCache->Layout(layoutKey, description);
_layout = context.descriptorCache->Layout(layoutKey, description, updateAfterBind);
} else {
ValidateDescriptorLayout(context, description);
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
info.bindingCount = static_cast<std::uint32_t>(description.size());
info.pBindings = description.data();
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &_layout), "vkCreateDescriptorSetLayout");
ValidateDescriptorLayout(context, description, updateAfterBind);
_layout = CreateSetLayout(context, description, updateAfterBind);
ownsLayout = true;
}
if (!bindings.empty()) {
@ -1218,16 +1253,12 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
if (plannedStorageImages != 0) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, plannedStorageImages});
if (!samplers.empty()) sizes.push_back({VK_DESCRIPTOR_TYPE_SAMPLER, static_cast<std::uint32_t>(samplers.size())});
if (context.descriptorCache != nullptr && !noPoolCache) {
const auto allocated = context.descriptorCache->Allocate(_layout, sizes);
const auto allocated = context.descriptorCache->Allocate(_layout, sizes, updateAfterBind);
_set = allocated.set;
cachePool = allocated.pool;
}
if (_set == VK_NULL_HANDLE) {
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.maxSets = 1;
poolInfo.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
poolInfo.pPoolSizes = sizes.data();
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
pool = CreateSetPool(context, sizes, updateAfterBind);
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocation.descriptorPool = pool;
allocation.descriptorSetCount = 1;
@ -2356,24 +2387,21 @@ DescriptorCache::DescriptorCache(const Context& context) : context(context), des
DescriptorCache::~DescriptorCache() {
for (const auto pool : pools) destroyPool(context.device, pool, nullptr);
for (const auto pool : updateAfterBindPools) destroyPool(context.device, pool, nullptr);
for (const auto pool : dedicated) destroyPool(context.device, pool, nullptr);
for (const auto& [key, layout] : layouts) destroyLayout(context.device, layout, nullptr);
}
VkDescriptorSetLayout DescriptorCache::Layout(std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings) {
VkDescriptorSetLayout DescriptorCache::Layout(std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings, bool updateAfterBind) {
std::lock_guard lock(mutex);
std::vector<std::uint32_t> keyCopy(key.begin(), key.end());
if (const auto found = layouts.find(keyCopy); found != layouts.end()) {
++stats.layoutHits;
return found->second;
}
ValidateDescriptorLayout(context, bindings);
ValidateDescriptorLayout(context, bindings, updateAfterBind);
++stats.layoutMisses;
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
info.bindingCount = static_cast<std::uint32_t>(bindings.size());
info.pBindings = bindings.data();
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &layout), "vkCreateDescriptorSetLayout");
const auto layout = CreateSetLayout(context, bindings, updateAfterBind);
layouts.emplace(std::move(keyCopy), layout);
return layout;
}
@ -2395,10 +2423,11 @@ constexpr std::array<VkDescriptorPoolSize, 4> ChainPoolSizes{{{VK_DESCRIPTOR_TYP
constexpr std::array<VkDescriptorPoolSize, 4> ChainPoolCreateSizes{{{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4096 + 2}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_SAMPLER, 512}}};
}
DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes) {
DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout layout, std::span<const VkDescriptorPoolSize> sizes, bool updateAfterBind) {
bool fits = true;
for (const auto& size : sizes) {
Require(size.descriptorCount <= SetDescriptorLimit(context.limits, size.type), "descriptor set exceeds the device's per-set descriptor limit");
const auto limit = updateAfterBind && size.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindStorageBuffers : SetDescriptorLimit(context.limits, size.type);
Require(size.descriptorCount <= limit, "descriptor set exceeds the device's per-set descriptor limit");
const auto capacity = std::find_if(ChainPoolSizes.begin(), ChainPoolSizes.end(), [&](const auto& item) { return item.type == size.type; });
if (capacity == ChainPoolSizes.end() || size.descriptorCount > capacity->descriptorCount) fits = false;
}
@ -2409,12 +2438,7 @@ DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout l
allocation.pSetLayouts = &layout;
if (!fits) {
dedicated.reserve(dedicated.size() + 1);
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.maxSets = 1;
poolInfo.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
poolInfo.pPoolSizes = sizes.data();
VkDescriptorPool pool = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
const auto pool = CreateSetPool(context, sizes, updateAfterBind);
allocation.descriptorPool = pool;
VkDescriptorSet set = VK_NULL_HANDLE;
const auto result = allocate(context.device, &allocation, &set);
@ -2429,7 +2453,8 @@ DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout l
}
// Newest pool first: it has the most room; a full or fragmented pool is left for its sets to
// drain and tried again later.
for (auto it = pools.rbegin(); it != pools.rend(); ++it) {
auto& chain = updateAfterBind ? updateAfterBindPools : pools;
for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
allocation.descriptorPool = *it;
VkDescriptorSet set = VK_NULL_HANDLE;
const auto result = allocate(context.device, &allocation, &set);
@ -2440,13 +2465,13 @@ DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout l
if (result != VK_ERROR_OUT_OF_POOL_MEMORY && result != VK_ERROR_FRAGMENTED_POOL) Check(result, "vkAllocateDescriptorSets");
}
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT | (updateAfterBind ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0);
poolInfo.maxSets = ChainPoolSets;
poolInfo.poolSizeCount = static_cast<std::uint32_t>(ChainPoolCreateSizes.size());
poolInfo.pPoolSizes = ChainPoolCreateSizes.data();
VkDescriptorPool pool = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
pools.push_back(pool);
chain.push_back(pool);
++stats.pools;
allocation.descriptorPool = pool;
VkDescriptorSet set = VK_NULL_HANDLE;

View File

@ -1248,6 +1248,8 @@ struct MockVulkan {
std::optional<VkDeviceSize> memoryLimit;
std::uint64_t allocationAttempts = 0;
std::vector<VkDescriptorSetLayoutBinding> layoutBindings;
VkDescriptorSetLayoutCreateFlags layoutFlags = 0;
std::vector<VkDescriptorBindingFlags> layoutBindingFlags;
std::vector<VkDescriptorPoolSize> poolSizes;
std::uint32_t poolMaxSets = 0;
VkDescriptorPoolCreateFlags poolFlags = 0;
@ -1330,6 +1332,10 @@ VKAPI_ATTR void VKAPI_CALL mockFreeMemory(VkDevice, VkDeviceMemory memory, const
VKAPI_ATTR VkResult VKAPI_CALL mockCreateDescriptorSetLayout(VkDevice, const VkDescriptorSetLayoutCreateInfo* info, const VkAllocationCallbacks*, VkDescriptorSetLayout* layout) {
*layout = makeHandle<VkDescriptorSetLayout>();
mock.layoutBindings.assign(info->pBindings, info->pBindings + info->bindingCount);
mock.layoutFlags = info->flags;
const auto* bindingFlags = static_cast<const VkDescriptorSetLayoutBindingFlagsCreateInfo*>(info->pNext);
if (bindingFlags != nullptr) mock.layoutBindingFlags.assign(bindingFlags->pBindingFlags, bindingFlags->pBindingFlags + bindingFlags->bindingCount);
else mock.layoutBindingFlags.clear();
++mock.live;
return VK_SUCCESS;
}
@ -1736,6 +1742,32 @@ void resourceTests() {
expectSingleAccepted(changed([](auto& binding) { binding.count = 3; binding.guestDescriptor = join(join(vsharp(guestFirst.data(), 16), vsharp(guestSecond.data(), 32)), vsharp(reinterpret_cast<const void*>(0x1000), 8)); }), "an unmapped V# element");
expectSingleFailure(changed([&](auto& binding) { binding.count = 2; binding.guestDescriptor = join(vsharp(guestFirst.data(), 16), vsharp(reinterpret_cast<const void*>(state.color.address), 64)); }), "aliases the render target");
expectSingleFailure(changed([](auto& binding) { binding.count = 17; binding.guestDescriptor.assign(68, 0); }), "per-stage limits");
{
ShaderRecompiler::RecompileResult vertex;
ShaderRecompiler::RecompileResult fragment;
std::vector<std::uint32_t> words;
for (int index = 0; index < 17; ++index) words = join(words, vsharp(reinterpret_cast<const void*>(0x1000), 8));
vertex.bindings.push_back(makeBinding(Role::GuestBuffers, 0, 17, words));
auto updateAfterBind = mockContext();
updateAfterBind.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindStorageBuffers = 17;
updateAfterBind.descriptorIndexingLimits.maxPerStageUpdateAfterBindResources = 128;
updateAfterBind.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindStorageBuffers = 32;
mock = MockVulkan{};
{
AgcDriver::Graphics::ShaderResources resources(updateAfterBind, vertex, fragment, state.color, 0, 0);
Require(mock.layoutFlags == VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, "buffers above the per-stage limit did not get an update-after-bind layout");
Require(mock.layoutBindingFlags.size() == 1 && mock.layoutBindingFlags[0] == VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, "the guest buffers binding is not update-after-bind");
Require(mock.poolFlags == VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, "the update-after-bind set did not come from an update-after-bind pool");
}
Require(mock.live == 0, "update-after-bind shader resources leaked Vulkan objects");
updateAfterBind.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindStorageBuffers = 16;
expectFailure([&] { AgcDriver::Graphics::ShaderResources resources(updateAfterBind, vertex, fragment, state.color, 0, 0); }, "shader descriptors exceed per-stage limits");
Require(mock.live == 0, "failed update-after-bind shader resources leaked Vulkan objects");
mock = MockVulkan{};
vertex.bindings.front() = makeBinding(Role::GuestBuffers, 0, 1, vsharp(reinterpret_cast<const void*>(0x1000), 8));
{ AgcDriver::Graphics::ShaderResources resources(updateAfterBind, vertex, fragment, state.color, 0, 0); }
Require(mock.layoutFlags == 0 && mock.layoutBindingFlags.empty() && mock.poolFlags == 0, "buffers within the per-stage limit took the update-after-bind path");
}
{
ShaderRecompiler::RecompileResult vertex;
ShaderRecompiler::RecompileResult fragment;