diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 51eaf6af..9f24981c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -2784,9 +2784,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& bindingPoint = MGB_CTX->GetBufferBindingPoint(BufferTarget::Uniform, frontendBinding); const auto& bufferObject = bindingPoint.GetBoundObject(); - MOBILEGL_ASSERT(bufferObject != nullptr, - "ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'", - frontendBinding, program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + static thread_local Vector paddedUbo; + if (bufferObject == nullptr) { + // A block the program declares at a point with nothing bound - e.g. its buffer was + // deleted while bound, which unbinds it (DeletedBoundBufferScenario). GL leaves what + // the shader READS undefined and raises no error; this used to dereference the null + // object. The wire arm already answers it with a zeroed block + // (ResolveWireUniformBufferPayload), so this one does too. + const VkDeviceSize blockSize = + static_cast(program.GetUBOSizeAt(static_cast(blockIndex))); + paddedUbo.assign(static_cast(blockSize > 0 ? blockSize : 16), 0); + out.payload = paddedUbo.data(); + out.payloadSize = static_cast(paddedUbo.size()); + return true; + } bufferObject->SyncPersistentMappedRange(); MOBILEGL_ASSERT(bufferObject->MappedData() != nullptr && bufferObject->GetSize() != 0, @@ -2819,7 +2830,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { outSize = blockSize; outData = bufferObject->MappedData() + static_cast(rangeStart); if (available < blockSize) { - static thread_local Vector paddedUbo; paddedUbo.assign(static_cast(blockSize), 0); Memcpy(paddedUbo.data(), outData, static_cast(available)); outData = paddedUbo.data();