[Fix, Test] (DirectVulkan): replay Photon descriptor pressure correctly

This commit is contained in:
2026-08-15 22:35:39 -04:00
parent 72dc7aa6aa
commit d4766513e4
6 changed files with 238 additions and 32 deletions
@@ -2973,8 +2973,73 @@ namespace MobileGL::MG_Backend::DirectVulkan {
bindings.push_back(layoutBinding);
}
// UPDATE_AFTER_BIND is strictly an optional per-layout acceleration. The GL
// descriptor model still resolves every sampler uniform element independently
// (including its texture-unit sampler-object override); selecting this path
// changes neither that resolution nor the set versioning in UniformManager.
// A conservative count keeps a layout on ordinary descriptors whenever any
// relevant update-after-bind limit is not large enough, rather than asking a
// driver to reject it during vkCreateDescriptorSetLayout.
Uint32 updateAfterBindSamplers = 0;
Uint32 updateAfterBindUniformBuffers = 0;
Uint32 updateAfterBindStorageBuffers = 0;
Uint32 updateAfterBindSampledImages = 0;
Uint32 updateAfterBindStorageImages = 0;
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const Uint32 count = entry.bindingDescriptorCounts[binding];
switch (entry.bindingKinds[binding]) {
case DescriptorBindingKind::UniformBufferDynamic:
updateAfterBindUniformBuffers += count;
break;
case DescriptorBindingKind::CombinedImageSampler:
updateAfterBindSamplers += count;
updateAfterBindSampledImages += count;
break;
case DescriptorBindingKind::UniformTexelBuffer:
updateAfterBindSampledImages += count;
break;
case DescriptorBindingKind::StorageBuffer:
case DescriptorBindingKind::StorageTexelBuffer:
updateAfterBindStorageBuffers += count;
break;
case DescriptorBindingKind::StorageImage:
updateAfterBindStorageImages += count;
break;
case DescriptorBindingKind::None:
break;
}
}
const Uint32 updateAfterBindResources = updateAfterBindUniformBuffers + updateAfterBindStorageBuffers +
updateAfterBindSampledImages + updateAfterBindStorageImages;
const auto& uab = m_updateAfterBindLimits;
entry.usesUpdateAfterBind =
uab.enabled && updateAfterBindSamplers <= uab.maxPerStageSamplers &&
updateAfterBindUniformBuffers <= uab.maxPerStageUniformBuffers &&
updateAfterBindStorageBuffers <= uab.maxPerStageStorageBuffers &&
updateAfterBindSampledImages <= uab.maxPerStageSampledImages &&
updateAfterBindStorageImages <= uab.maxPerStageStorageImages &&
updateAfterBindResources <= uab.maxPerStageResources &&
updateAfterBindSamplers <= uab.maxSetSamplers &&
updateAfterBindUniformBuffers <= uab.maxSetUniformBuffers &&
updateAfterBindUniformBuffers <= uab.maxSetUniformBuffersDynamic &&
updateAfterBindStorageBuffers <= uab.maxSetStorageBuffers &&
updateAfterBindStorageBuffers <= uab.maxSetStorageBuffersDynamic &&
updateAfterBindSampledImages <= uab.maxSetSampledImages &&
updateAfterBindStorageImages <= uab.maxSetStorageImages;
Vector<VkDescriptorBindingFlags> bindingFlags;
VkDescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo{};
if (entry.usesUpdateAfterBind) {
bindingFlags.assign(bindings.size(), VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT);
bindingFlagsInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
bindingFlagsInfo.bindingCount = static_cast<Uint32>(bindingFlags.size());
bindingFlagsInfo.pBindingFlags = bindingFlags.data();
}
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.flags = entry.usesUpdateAfterBind ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT : 0;
setLayoutInfo.pNext = entry.usesUpdateAfterBind ? &bindingFlagsInfo : nullptr;
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
setLayoutInfo.pBindings = bindings.data();
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout),
@@ -3299,14 +3364,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkDescriptorSetLayout descriptorSetLayout = it->second.descriptorSetLayout;
MGLOG_D("ProgramFactory::OnFrameBoundary: evicting idle program entry hash=0x%llx",
static_cast<unsigned long long>(hash));
// erase runs ~VkProgramObject (modules/layouts destroyed); notify after
// so an observer never observes a half-destroyed entry through a lookup.
// Observers only need the handle values to purge their keyed caches.
++m_cacheStructureEpoch; // erase moves/kills entries: memoised pointers die
it = m_cache.erase(it);
// The observer destroys dependent pipelines and frees descriptor sets while
// this entry still owns its layout. Vulkan requires every descriptor set to be
// freed before its VkDescriptorSetLayout is destroyed.
if (m_evictionObserver != nullptr) {
m_evictionObserver->OnProgramEvicted(hash, descriptorSetLayout);
}
++m_cacheStructureEpoch; // erase moves/kills entries: memoised pointers die
it = m_cache.erase(it);
} else {
++it;
}
@@ -76,6 +76,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
using CompileOptionFlags = Flags<CompileOptionBit>;
using HashType = Uint64;
struct UpdateAfterBindLimits {
Bool enabled = false;
Uint32 maxPerStageSamplers = 0;
Uint32 maxPerStageUniformBuffers = 0;
Uint32 maxPerStageStorageBuffers = 0;
Uint32 maxPerStageSampledImages = 0;
Uint32 maxPerStageStorageImages = 0;
Uint32 maxPerStageResources = 0;
Uint32 maxSetSamplers = 0;
Uint32 maxSetUniformBuffers = 0;
Uint32 maxSetUniformBuffersDynamic = 0;
Uint32 maxSetStorageBuffers = 0;
Uint32 maxSetStorageBuffersDynamic = 0;
Uint32 maxSetSampledImages = 0;
Uint32 maxSetStorageImages = 0;
};
struct VkProgramObject {
static constexpr Uint32 kMaxVertexInputLocations = 32;
@@ -88,6 +105,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Layout data (previously in separate VkProgramLayout)
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
// True only when this layout passed every descriptor-indexing feature and
// update-after-bind limit gate at reflection time. It controls both the
// layout/binding flags and the pool class used by UniformManager.
Bool usesUpdateAfterBind = false;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<DescriptorBindingKind> bindingKinds;
// The bindings this program actually declares, ascending. bindingKinds is sized to the
@@ -196,6 +217,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// a pipeline failure would be reported against the wrong SPIR-V.
stageSpirvDigests = std::move(other.stageSpirvDigests);
descriptorSetLayout = other.descriptorSetLayout;
usesUpdateAfterBind = other.usesUpdateAfterBind;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
activeBindings = std::move(other.activeBindings);
@@ -230,6 +252,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
lastUsedFrame = other.lastUsedFrame;
other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.usesUpdateAfterBind = false;
other.pipelineLayout = VK_NULL_HANDLE;
other.hasStorageImages = false;
other.declinedDescriptors = false;
@@ -256,6 +279,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
modules = std::move(other.modules);
stageSpirvDigests = std::move(other.stageSpirvDigests); // travels with `modules` - see the move ctor
descriptorSetLayout = other.descriptorSetLayout;
usesUpdateAfterBind = other.usesUpdateAfterBind;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
activeBindings = std::move(other.activeBindings);
@@ -290,6 +314,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
lastUsedFrame = other.lastUsedFrame;
other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.usesUpdateAfterBind = false;
other.pipelineLayout = VK_NULL_HANDLE;
other.hasStorageImages = false;
other.declinedDescriptors = false;
@@ -347,12 +372,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
virtual void OnProgramEvicted(HashType programHash, VkDescriptorSetLayout descriptorSetLayout) = 0;
};
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
Bool shaderDrawParametersEnabled = false,
Bool unformattedFloatStorageImagesEnabled = false)
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings,
Bool shaderDrawParametersEnabled,
Bool unformattedFloatStorageImagesEnabled,
UpdateAfterBindLimits updateAfterBindLimits)
: m_device(device), m_maxBindings(maxBindings), m_config(config),
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled) {
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled),
m_updateAfterBindLimits(updateAfterBindLimits) {
VkProgramObject::s_device = device;
}
// Destroys the pass-through tessellation control modules. Runs while the device is
@@ -475,6 +502,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// True only when the logical device enabled both
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
Bool m_unformattedFloatStorageImagesEnabled = false;
// Device feature and limit gate resolved before vkCreateDevice. Keeping it in
// the factory lets each reflected layout choose ordinary descriptors when its
// own counts would exceed the update-after-bind budget.
UpdateAfterBindLimits m_updateAfterBindLimits{};
// See SetDefaultFramebufferHeight. 0 means "not known yet"; the FragCoordYFlip bit is
// never set before the swapchain exists, so no variant can be compiled against it.
Uint32 m_defaultFramebufferHeight = 0;
@@ -156,13 +156,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.descriptorPools.clear();
VkDescriptorPool initialPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) {
if (!CreateDescriptorPool(m_setsPerFrame, false, initialPool)) {
MGLOG_E_ONCE("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
frameIndex);
Shutdown();
return false;
}
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0, false});
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex,
m_setsPerFrame);
}
@@ -1390,7 +1390,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const {
Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, Bool updateAfterBind, VkDescriptorPool& outPool) const {
outPool = VK_NULL_HANDLE;
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
return false;
@@ -1433,7 +1433,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (OnDescriptorSetLayoutDestroyed) so program churn recycles pool capacity.
// The cost is on set allocation only, which happens when a layout's per-frame
// cache grows - never on the per-draw reuse path.
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 = maxSets;
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
poolInfo.pPoolSizes = poolSizes;
@@ -1447,24 +1448,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) {
Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex, Bool updateAfterBind) {
if (frame.descriptorPools.empty()) {
return false;
}
const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
const Uint32 currentMaxSets = std::max<Uint32>(1, currentBucket.maxSets);
const auto matchingBucket = std::find_if(
frame.descriptorPools.begin(), frame.descriptorPools.end(),
[updateAfterBind](const DescriptorPoolBucket& candidate) { return candidate.updateAfterBind == updateAfterBind; });
const Uint32 currentMaxSets = matchingBucket != frame.descriptorPools.end()
? std::max<Uint32>(1, matchingBucket->maxSets)
: m_setsPerFrame;
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
: currentMaxSets;
VkDescriptorPool grownPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(grownMaxSets, grownPool)) {
if (!CreateDescriptorPool(grownMaxSets, updateAfterBind, grownPool)) {
MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
currentMaxSets, grownMaxSets);
return false;
}
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0});
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0, updateAfterBind});
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
MGLOG_D(
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
@@ -1474,14 +1479,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
frame.activeDescriptorPoolIndex = 0;
}
if (frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
const Bool updateAfterBind = programObj.usesUpdateAfterBind;
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size() ||
frame.descriptorPools[frame.activeDescriptorPoolIndex].updateAfterBind != updateAfterBind ||
frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
const auto availableBucket = std::find_if(
frame.descriptorPools.begin(), frame.descriptorPools.end(),
[](const DescriptorPoolBucket& candidate) { return candidate.allocatedSets < candidate.maxSets; });
[updateAfterBind](const DescriptorPoolBucket& candidate) {
return candidate.updateAfterBind == updateAfterBind && candidate.allocatedSets < candidate.maxSets;
});
if (availableBucket == frame.descriptorPools.end()) {
outDescriptorSet = VK_NULL_HANDLE;
return VK_ERROR_OUT_OF_POOL_MEMORY;
@@ -1517,7 +1524,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} else {
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
if (!GrowFrameDescriptorPool(frame, frameIndex, programObj.usesUpdateAfterBind)) {
MGLOG_E_ONCE("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed");
return allocResult;
}
@@ -114,6 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDescriptorPool handle = VK_NULL_HANDLE;
Uint32 maxSets = 0;
Uint32 allocatedSets = 0;
Bool updateAfterBind = false;
};
// A cached descriptor set together with the pool it was allocated from, so a
@@ -223,8 +224,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void BindDescriptorSetDeduped(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint,
VkPipelineLayout pipelineLayout, VkDescriptorSet descriptorSet,
const Vector<Uint32>& dynamicOffsets);
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
Bool CreateDescriptorPool(Uint32 maxSets, Bool updateAfterBind, VkDescriptorPool& outPool) const;
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex, Bool updateAfterBind);
VkResult AllocateDescriptorSetsFromActivePool(
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
VkResult AcquireDescriptorSet(Uint32 frameIndex,
@@ -1480,7 +1480,8 @@ void main() {
const char* label = nullptr;
};
static Uint32 ComputeMaxProgramBindings(const VkPhysicalDeviceProperties& properties) {
static Uint32 ComputeMaxProgramBindings(const VkPhysicalDeviceProperties& properties,
const ProgramFactory::UpdateAfterBindLimits& updateAfterBindLimits) {
const auto& limits = properties.limits;
static constexpr Uint32 kMinProgramBindings = 16;
static constexpr Uint32 kMaxProgramBindingsCap = 256;
@@ -1495,6 +1496,21 @@ void main() {
maxBindings = std::min(maxBindings, maxCombinedImageSamplers);
maxBindings = std::min(maxBindings, maxSampledImages + maxDynamicUniformBuffers);
if (updateAfterBindLimits.enabled) {
const Uint32 updateAfterBindSamplers = std::min(updateAfterBindLimits.maxPerStageSamplers,
updateAfterBindLimits.maxSetSamplers);
const Uint32 updateAfterBindSampledImages = std::min(updateAfterBindLimits.maxPerStageSampledImages,
updateAfterBindLimits.maxSetSampledImages);
const Uint32 updateAfterBindDynamicUniformBuffers =
std::min(updateAfterBindLimits.maxPerStageUniformBuffers,
updateAfterBindLimits.maxSetUniformBuffersDynamic);
Uint32 updateAfterBindBindings = updateAfterBindLimits.maxPerStageResources;
updateAfterBindBindings = std::min(updateAfterBindBindings, updateAfterBindSamplers);
updateAfterBindBindings =
std::min(updateAfterBindBindings, updateAfterBindSampledImages + updateAfterBindDynamicUniformBuffers);
maxBindings = std::max(maxBindings, updateAfterBindBindings);
}
maxBindings = std::max(kMinProgramBindings, maxBindings);
maxBindings = std::min(kMaxProgramBindingsCap, maxBindings);
return maxBindings;
@@ -3010,7 +3026,7 @@ void main() {
succeeded = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
const Uint32 maxProgramBindings = ComputeMaxProgramBindings(m_physicalDevice.properties);
const Uint32 maxProgramBindings = ComputeMaxProgramBindings(m_physicalDevice.properties, m_updateAfterBindLimits);
MGLOG_I("DirectVulkan: using %u program descriptor bindings", maxProgramBindings);
if (IsPowerVRDevice(m_physicalDevice.properties)) {
m_config.DisablePipelineCache = true;
@@ -3044,7 +3060,8 @@ void main() {
}
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config, maxProgramBindings,
m_shaderDrawParametersFeatureEnabled,
m_unformattedFloatStorageImagesEnabled);
m_unformattedFloatStorageImagesEnabled,
m_updateAfterBindLimits);
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
// The swapchain already exists at this point (Initialize creates it first), so seed the
// height the factory could not be told about from CreateSwapchain.
@@ -11899,6 +11916,17 @@ void main() {
}
void VulkanRenderer::CreateInstance() {
#if defined(VK_USE_PLATFORM_METAL_EXT)
// MoltenVK snapshots its configuration when the loader first discovers the ICD. Set
// this before instance-extension enumeration, while preserving an explicit user value.
if (std::getenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS") == nullptr) {
if (::setenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1", 0) == 0) {
MGLOG_I("MoltenVK: enabling Metal argument buffers");
} else {
MGLOG_W("MoltenVK: could not enable Metal argument buffers before ICD discovery");
}
}
#endif
m_extensions = EnumerateInstanceExtensions();
MGLOG_I("Got %d Vulkan instance extensions: ", m_extensions.size());
for (auto& extension : m_extensions) {
@@ -12040,17 +12068,19 @@ void main() {
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
// Layers
const void* instanceCreatePNext = nullptr;
if (m_validationLayersEnabled) {
MGLOG_I("Enabling validation layer...");
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
// Chaining the messenger create-info is only legal with the extension on.
instanceInfo.pNext = debugUtilsAvailable ? &debugMessengerCreateInfo : nullptr;
instanceCreatePNext = debugUtilsAvailable ? &debugMessengerCreateInfo : nullptr;
} else {
instanceInfo.enabledLayerCount = 0;
instanceInfo.pNext = nullptr;
}
instanceInfo.pNext = instanceCreatePNext;
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
if (debugUtilsAvailable) {
@@ -12476,6 +12506,75 @@ void main() {
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR"));
}
m_updateAfterBindLimits = {};
VkPhysicalDeviceDescriptorIndexingFeatures descriptorIndexingFeatures{};
descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
VkPhysicalDeviceDescriptorIndexingProperties descriptorIndexingProperties{};
descriptorIndexingProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES;
const Bool descriptorIndexingCore = m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2;
const Bool descriptorIndexingExtension =
IsExtensionSupported(availableExtensions, VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
if (getPhysicalDeviceProperties2 == nullptr) {
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
}
if ((descriptorIndexingCore || descriptorIndexingExtension) && getPhysicalDeviceFeatures2 != nullptr &&
getPhysicalDeviceProperties2 != nullptr) {
VkPhysicalDeviceFeatures2 featureQuery{};
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
featureQuery.pNext = &descriptorIndexingFeatures;
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
VkPhysicalDeviceProperties2 propertyQuery{};
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
propertyQuery.pNext = &descriptorIndexingProperties;
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
// This renderer emits every descriptor category listed below, including
// dynamic UBOs and combined image samplers. Do not enable a partial
// descriptor-indexing contract: it would make a later reflected program
// fail in the driver instead of choosing its ordinary descriptor layout.
const Bool allUpdateAfterBindFeatures =
descriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind == VK_TRUE &&
descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind == VK_TRUE &&
descriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind == VK_TRUE &&
descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind == VK_TRUE &&
descriptorIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind == VK_TRUE &&
descriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind == VK_TRUE &&
(!deviceFeatures.robustBufferAccess || descriptorIndexingProperties.robustBufferAccessUpdateAfterBind);
if (allUpdateAfterBindFeatures) {
if (!descriptorIndexingCore && !IsExtensionAlreadyEnabled(
enabledDeviceExtensions,
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)) {
enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
}
descriptorIndexingFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
deviceCreateInfo.pNext = &descriptorIndexingFeatures;
m_updateAfterBindLimits = {
true,
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSamplers,
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindUniformBuffers,
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageBuffers,
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSampledImages,
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageImages,
descriptorIndexingProperties.maxPerStageUpdateAfterBindResources,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSamplers,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffers,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffersDynamic,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffers,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffersDynamic,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSampledImages,
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageImages};
MGLOG_I("Vulkan: update-after-bind descriptor layouts enabled");
} else {
MGLOG_I("Vulkan: descriptor indexing is present but lacks the complete update-after-bind feature set; "
"using ordinary descriptor layouts");
}
} else {
MGLOG_I("Vulkan: descriptor indexing unavailable; using ordinary descriptor layouts");
}
VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{};
indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES;
if (indexTypeUint8ExtensionName != nullptr) {
@@ -555,6 +555,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_shaderDrawParametersExtensionEnabled = false;
Bool m_shaderDrawParametersFeatureEnabled = false;
Bool m_unformattedFloatStorageImagesEnabled = false;
// Set only after descriptor-indexing feature AND property queries prove that
// update-after-bind is legal for every descriptor category this renderer emits.
ProgramFactory::UpdateAfterBindLimits m_updateAfterBindLimits{};
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
// per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and
// drive a runtime fallback when the device lacks them.