From d4766513e466ae0c256b0520af827da5286910b6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 15 Aug 2026 22:35:39 -0400 Subject: [PATCH] [Fix, Test] (DirectVulkan): replay Photon descriptor pressure correctly --- .../DirectVulkan/Renderer/ProgramFactory.cpp | 75 +++++++++++- .../DirectVulkan/Renderer/ProgramFactory.h | 39 ++++++- .../DirectVulkan/Renderer/UniformManager.cpp | 39 ++++--- .../DirectVulkan/Renderer/UniformManager.h | 5 +- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 109 +++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 3 + 6 files changed, 238 insertions(+), 32 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 531ef6da..d313f94c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -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 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(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(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(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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index aa138b09..d667d9fb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -76,6 +76,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { using CompileOptionFlags = Flags; 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 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 5241558f..e6d4bd5d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -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(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(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(1, matchingBucket->maxSets) + : m_setsPerFrame; const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits::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(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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index ead1edaa..3ac6b87c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -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& 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, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index fb6b8aed..359b0b65 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -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(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(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( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2")); + if (getPhysicalDeviceProperties2 == nullptr) { + getPhysicalDeviceProperties2 = reinterpret_cast( + 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(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) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index c2593185..5aac2886 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -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.