From e7a373b013cd8acce4cfe462a2876829c1fe56ee Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Tue, 23 Jun 2026 18:39:50 +0300 Subject: [PATCH] core: Fix dynamic texture resource management (#4337) --- include/mbgl/gfx/dynamic_texture.hpp | 3 +- include/mbgl/gfx/dynamic_texture_atlas.hpp | 1 + include/mbgl/gl/dynamic_texture.hpp | 2 +- include/mbgl/vulkan/dynamic_texture.hpp | 6 +- include/mbgl/vulkan/upload_pass.hpp | 3 + include/mbgl/webgpu/dynamic_texture.hpp | 2 +- src/mbgl/gfx/dynamic_texture_atlas.cpp | 15 ++--- src/mbgl/gl/dynamic_texture.cpp | 2 +- src/mbgl/mtl/dynamic_texture.cpp | 5 +- src/mbgl/tile/geometry_tile.cpp | 10 ++-- src/mbgl/tile/geometry_tile.hpp | 10 ++-- src/mbgl/vulkan/context.cpp | 23 +++++++- src/mbgl/vulkan/dynamic_texture.cpp | 64 +++++++++++----------- src/mbgl/vulkan/renderable_resource.cpp | 16 ++++-- src/mbgl/vulkan/renderer_backend.cpp | 25 +++++++-- src/mbgl/webgpu/dynamic_texture.cpp | 2 +- 16 files changed, 120 insertions(+), 69 deletions(-) diff --git a/include/mbgl/gfx/dynamic_texture.hpp b/include/mbgl/gfx/dynamic_texture.hpp index 06faf0335..4414507ea 100644 --- a/include/mbgl/gfx/dynamic_texture.hpp +++ b/include/mbgl/gfx/dynamic_texture.hpp @@ -14,6 +14,7 @@ namespace gfx { class Context; class Texture2D; +class UploadPass; using Texture2DPtr = std::shared_ptr; class TextureHandle { @@ -60,7 +61,7 @@ public: std::optional addImage(const uint8_t* pixelData, const Size& imageSize, int32_t uniqueId = -1); virtual void uploadImage(const uint8_t* pixelData, TextureHandle& texHandle); - virtual void uploadDeferredImages() {}; + virtual void uploadDeferredImages(UploadPass&) {}; virtual bool removeTexture(const TextureHandle& texHandle); protected: diff --git a/include/mbgl/gfx/dynamic_texture_atlas.hpp b/include/mbgl/gfx/dynamic_texture_atlas.hpp index 78c241539..45cb1f760 100644 --- a/include/mbgl/gfx/dynamic_texture_atlas.hpp +++ b/include/mbgl/gfx/dynamic_texture_atlas.hpp @@ -37,6 +37,7 @@ public: const ImageVersionMap& versionMap); void removeTextures(const std::vector& textureHandles, const DynamicTexturePtr& dynamicTexture); + void removeUnusedDynamicTextures(); private: Context& context; diff --git a/include/mbgl/gl/dynamic_texture.hpp b/include/mbgl/gl/dynamic_texture.hpp index aa6ad2329..78479187d 100644 --- a/include/mbgl/gl/dynamic_texture.hpp +++ b/include/mbgl/gl/dynamic_texture.hpp @@ -12,7 +12,7 @@ public: DynamicTexture(Context& context, Size size, gfx::TexturePixelType pixelType); void uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) override; - void uploadDeferredImages() override; + void uploadDeferredImages(gfx::UploadPass&) override; bool removeTexture(const gfx::TextureHandle& texHandle) override; using ImagesToUpload = diff --git a/include/mbgl/vulkan/dynamic_texture.hpp b/include/mbgl/vulkan/dynamic_texture.hpp index 061c2fecd..988a25fe2 100644 --- a/include/mbgl/vulkan/dynamic_texture.hpp +++ b/include/mbgl/vulkan/dynamic_texture.hpp @@ -10,7 +10,7 @@ namespace vulkan { class Context; struct BufferAllocation; -using UniqueBufferAllocation = std::unique_ptr; +using SharedBufferAllocation = std::shared_ptr; class DynamicTexture : public gfx::DynamicTexture { public: @@ -18,7 +18,7 @@ public: ~DynamicTexture() override; void uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) override; - void uploadDeferredImages() override; + void uploadDeferredImages(gfx::UploadPass&) override; bool removeTexture(const gfx::TextureHandle& texHandle) override; #if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD @@ -26,7 +26,7 @@ public: std::unordered_map, gfx::TextureHandle::Hasher>; #else using TextureBuffersToUpload = - std::unordered_map; + std::unordered_map; #endif private: diff --git a/include/mbgl/vulkan/upload_pass.hpp b/include/mbgl/vulkan/upload_pass.hpp index 18f52b400..99ed57209 100644 --- a/include/mbgl/vulkan/upload_pass.hpp +++ b/include/mbgl/vulkan/upload_pass.hpp @@ -38,6 +38,9 @@ public: gfx::Context& getContext() override; const gfx::Context& getContext() const override; + CommandEncoder& getCommandEncoder() { return commandEncoder; } + const CommandEncoder& getCommandEncoder() const { return commandEncoder; } + std::unique_ptr createVertexBufferResource(const void* data, std::size_t size, gfx::BufferUsageType, diff --git a/include/mbgl/webgpu/dynamic_texture.hpp b/include/mbgl/webgpu/dynamic_texture.hpp index 766087be8..57aa42708 100644 --- a/include/mbgl/webgpu/dynamic_texture.hpp +++ b/include/mbgl/webgpu/dynamic_texture.hpp @@ -12,7 +12,7 @@ public: DynamicTexture(Context& context, Size size, gfx::TexturePixelType pixelType); void uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) override; - void uploadDeferredImages() override; + void uploadDeferredImages(gfx::UploadPass&) override; bool removeTexture(const gfx::TextureHandle& texHandle) override; using ImagesToUpload = diff --git a/src/mbgl/gfx/dynamic_texture_atlas.cpp b/src/mbgl/gfx/dynamic_texture_atlas.cpp index 42966f6b2..6ddf720ce 100644 --- a/src/mbgl/gfx/dynamic_texture_atlas.cpp +++ b/src/mbgl/gfx/dynamic_texture_atlas.cpp @@ -242,20 +242,21 @@ ImageAtlas DynamicTextureAtlas::uploadIconsAndPatterns(const ImageMap& icons, void DynamicTextureAtlas::removeTextures(const std::vector& textureHandles, const DynamicTexturePtr& dynamicTexture) { - std::scoped_lock lock(mutex); if (!dynamicTexture) { return; } + std::scoped_lock lock(mutex); + for (const auto& texHandle : textureHandles) { dynamicTexture->removeTexture(texHandle); } - if (dynamicTexture->isEmpty()) { - auto iterator = std::ranges::find(dynamicTextures, dynamicTexture); - if (iterator != dynamicTextures.end()) { - dynamicTextures.erase(iterator); - } - } +} + +void DynamicTextureAtlas::removeUnusedDynamicTextures() { + std::scoped_lock lock(mutex); + + std::erase_if(dynamicTextures, [](auto& dynamicTexture) { return dynamicTexture->isEmpty(); }); } } // namespace gfx diff --git a/src/mbgl/gl/dynamic_texture.cpp b/src/mbgl/gl/dynamic_texture.cpp index daebcc3b8..097f1d180 100644 --- a/src/mbgl/gl/dynamic_texture.cpp +++ b/src/mbgl/gl/dynamic_texture.cpp @@ -23,7 +23,7 @@ void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& t gfx::DynamicTexture::uploadImage(pixelData, texHandle); } -void DynamicTexture::uploadDeferredImages() { +void DynamicTexture::uploadDeferredImages(gfx::UploadPass&) { std::scoped_lock lock(mutex); if (deferredCreation) { texture->create(); diff --git a/src/mbgl/mtl/dynamic_texture.cpp b/src/mbgl/mtl/dynamic_texture.cpp index 88cbc621a..9ac0eee43 100644 --- a/src/mbgl/mtl/dynamic_texture.cpp +++ b/src/mbgl/mtl/dynamic_texture.cpp @@ -6,15 +6,14 @@ namespace mbgl { namespace mtl { DynamicTexture::DynamicTexture(Context& context, Size size, gfx::TexturePixelType pixelType) - : gfx::DynamicTexture(context, size, pixelType) { - texture->create(); -} + : gfx::DynamicTexture(context, size, pixelType) {} void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) { std::scoped_lock lock(mutex); const auto& rect = texHandle.getRectangle(); const auto imageSize = Size(rect.w, rect.h); + texture->create(); texture->uploadSubRegion(pixelData, imageSize, rect.x, rect.y); gfx::DynamicTexture::uploadImage(pixelData, texHandle); diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index d51724fcb..620c16b7c 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -126,11 +126,11 @@ void GeometryTileRenderData::upload(gfx::UploadPass& uploadPass) { assert(atlasTextures); if (const auto& glyphDynamicTexture = layoutResult->glyphAtlas.dynamicTexture) { - glyphDynamicTexture->uploadDeferredImages(); + glyphDynamicTexture->uploadDeferredImages(uploadPass); atlasTextures->glyph = glyphDynamicTexture->getTexture(); } if (const auto& imageDynamicTexture = layoutResult->imageAtlas.dynamicTexture) { - imageDynamicTexture->uploadDeferredImages(); + imageDynamicTexture->uploadDeferredImages(uploadPass); atlasTextures->icon = imageDynamicTexture->getTexture(); } @@ -226,7 +226,9 @@ GeometryTile::~GeometryTile() { if (layoutResult) { threadPool.runOnRenderThread( - [layoutResult_{std::move(layoutResult)}, atlasTextures_{std::move(atlasTextures)}]() {}); + [layoutResult_{std::move(layoutResult)}, atlasTextures_{std::move(atlasTextures)}]() { + layoutResult_->dynamicTextureAtlas->removeUnusedDynamicTextures(); + }); } } @@ -332,7 +334,7 @@ void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) { } } -void GeometryTile::onLayout(std::shared_ptr result, const uint64_t resultCorrelationID) { +void GeometryTile::onLayout(std::shared_ptr&& result, const uint64_t resultCorrelationID) { MLN_TRACE_FUNC(); loaded = true; diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 3b7a2e003..1c55f84f5 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -76,10 +76,10 @@ public: LayerRenderData* getLayerRenderData(const style::Layer::Impl&); - LayoutResult(mbgl::unordered_map renderData_, - std::unique_ptr featureIndex_, - gfx::GlyphAtlas glyphAtlas_, - gfx::ImageAtlas imageAtlas_, + LayoutResult(mbgl::unordered_map&& renderData_, + std::unique_ptr&& featureIndex_, + gfx::GlyphAtlas&& glyphAtlas_, + gfx::ImageAtlas&& imageAtlas_, gfx::DynamicTextureAtlasPtr dynamicTextureAtlas_) : layerRenderData(std::move(renderData_)), featureIndex(std::move(featureIndex_)), @@ -89,7 +89,7 @@ public: ~LayoutResult(); }; - void onLayout(std::shared_ptr, uint64_t correlationID); + void onLayout(std::shared_ptr&&, uint64_t correlationID); void onError(std::exception_ptr, uint64_t correlationID); diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp index 53425fa64..dde1dabf4 100644 --- a/src/mbgl/vulkan/context.cpp +++ b/src/mbgl/vulkan/context.cpp @@ -146,7 +146,11 @@ void Context::initFrameResources() { void Context::destroyResources() { MBGL_VERIFY_THREAD(tid); - backend.getDevice()->waitIdle(backend.getDispatcher()); + try { + backend.getDevice()->waitIdle(backend.getDispatcher()); + } catch (const vk::DeviceLostError& error) { + Log::Error(mbgl::Event::Render, "Vulkan device lost during context shutdown"); + } for (auto& frame : frameResources) { frame.runDeletionQueue(*this); @@ -767,8 +771,23 @@ const vk::UniquePipelineLayout& Context::getPushConstantPipelineLayout() { const auto stages = vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment; const auto pushConstant = vk::PushConstantRange().setSize(sizeof(matf4)).setStageFlags(stages); + auto layoutInfo = vk::PipelineLayoutCreateInfo().setPushConstantRanges(pushConstant); + +#ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION + // GPU assisted validation crashes when using a pipeline without descriptors. + // Use a compatible layout with the general pipeline when enabled + const std::vector layouts = { + globalUniformDescriptorSetLayout.get(), + layerUniformDescriptorSetLayout.get(), + drawableUniformDescriptorSetLayout.get(), + drawableImageDescriptorSetLayout.get(), + }; + + layoutInfo.setSetLayouts(layouts); +#endif + pushConstantPipelineLayout = backend.getDevice()->createPipelineLayoutUnique( - vk::PipelineLayoutCreateInfo().setPushConstantRanges(pushConstant), nullptr, backend.getDispatcher()); + layoutInfo, nullptr, backend.getDispatcher()); backend.setDebugName(pushConstantPipelineLayout.get(), "PipelineLayout_pushConstants"); diff --git a/src/mbgl/vulkan/dynamic_texture.cpp b/src/mbgl/vulkan/dynamic_texture.cpp index 025125ef7..8ce6936b8 100644 --- a/src/mbgl/vulkan/dynamic_texture.cpp +++ b/src/mbgl/vulkan/dynamic_texture.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include namespace mbgl { @@ -53,7 +55,7 @@ void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& t gfx::DynamicTexture::uploadImage(pixelData, texHandle); } -void DynamicTexture::uploadDeferredImages() { +void DynamicTexture::uploadDeferredImages(gfx::UploadPass&) { std::scoped_lock lock(mutex); if (texturesToBlit.empty()) { @@ -106,13 +108,11 @@ bool DynamicTexture::removeTexture(const gfx::TextureHandle& texHandle) { DynamicTexture::DynamicTexture(Context& context_, Size size, gfx::TexturePixelType pixelType) : gfx::DynamicTexture(context_, size, pixelType), - context(context_) { - texture->create(); -} + context(context_) {} DynamicTexture::~DynamicTexture() { if (texture) { - static_cast(*texture).destroy(false); + static_cast(*texture).destroy(true); } } @@ -134,7 +134,7 @@ void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& t allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; allocationInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; - UniqueBufferAllocation bufferAllocation = std::make_unique(allocator); + SharedBufferAllocation bufferAllocation = std::make_shared(allocator); if (!bufferAllocation->create(allocationInfo, bufferInfo)) { mbgl::Log::Error(mbgl::Event::Render, "Vulkan texture buffer allocation failed"); throw std::bad_alloc(); @@ -145,10 +145,12 @@ void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& t textureBuffersToUpload.emplace(texHandle, std::move(bufferAllocation)); + texture->create(); + gfx::DynamicTexture::uploadImage(pixelData, texHandle); } -void DynamicTexture::uploadDeferredImages() { +void DynamicTexture::uploadDeferredImages(gfx::UploadPass& uploadPass) { std::scoped_lock lock(mutex); if (textureBuffersToUpload.empty()) { @@ -156,33 +158,33 @@ void DynamicTexture::uploadDeferredImages() { } const auto& textureVK = static_cast(texture.get()); - context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) { - textureVK->transitionToTransferWriteLayout(commandBuffer); - for (const auto& pair : textureBuffersToUpload) { - const auto& rect = pair.first.getRectangle(); - const auto region = vk::BufferImageCopy() - .setBufferOffset(0) - .setBufferRowLength(rect.w) - .setImageSubresource( - vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1)) - .setImageOffset(vk::Offset3D(rect.x, rect.y)) - .setImageExtent(vk::Extent3D(rect.w, rect.h, 1)); + const auto& commandBuffer = static_cast(uploadPass).getCommandEncoder().getCommandBuffer(); - commandBuffer->copyBufferToImage(pair.second->buffer, - textureVK->getVulkanImage(), - textureVK->getVulkanImageLayout(), - region, - context.getBackend().getDispatcher()); + textureVK->transitionToTransferWriteLayout(commandBuffer); + for (auto& pair : textureBuffersToUpload) { + const auto& rect = pair.first.getRectangle(); + const auto region = vk::BufferImageCopy() + .setBufferOffset(0) + .setBufferRowLength(rect.w) + .setImageSubresource( + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1)) + .setImageOffset(vk::Offset3D(rect.x, rect.y)) + .setImageExtent(vk::Extent3D(rect.w, rect.h, 1)); - context.threadSafeAccessRenderingStats([&](gfx::RenderingStats& stats) { - stats.numTextureUpdates++; - stats.textureUpdateBytes += rect.w * rect.h * texture->getPixelStride(); - }); - } - textureVK->transitionToShaderReadLayout(commandBuffer); - }); + commandBuffer->copyBufferToImage(pair.second->buffer, + textureVK->getVulkanImage(), + textureVK->getVulkanImageLayout(), + region, + context.getBackend().getDispatcher()); - textureBuffersToUpload.clear(); + context.threadSafeAccessRenderingStats([&](gfx::RenderingStats& stats) { + stats.numTextureUpdates++; + stats.textureUpdateBytes += rect.w * rect.h * texture->getPixelStride(); + }); + } + textureVK->transitionToShaderReadLayout(commandBuffer); + + context.enqueueDeletion([buffers = std::move(textureBuffersToUpload)](Context&) {}); } bool DynamicTexture::removeTexture(const gfx::TextureHandle& texHandle) { diff --git a/src/mbgl/vulkan/renderable_resource.cpp b/src/mbgl/vulkan/renderable_resource.cpp index 07f1f66ec..eac09b0ad 100644 --- a/src/mbgl/vulkan/renderable_resource.cpp +++ b/src/mbgl/vulkan/renderable_resource.cpp @@ -11,7 +11,11 @@ SurfaceRenderableResource::~SurfaceRenderableResource() { return; } - backend.getDevice()->waitIdle(backend.getDispatcher()); + try { + backend.getDevice()->waitIdle(backend.getDispatcher()); + } catch (const vk::DeviceLostError& error) { + Log::Error(mbgl::Event::Render, "Vulkan device lost during surface shutdown"); + } // specific order swapchainFramebuffers.clear(); @@ -300,8 +304,9 @@ void SurfaceRenderableResource::initRenderPass() { .setDstSubpass(0) .setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput) - .setSrcAccessMask({}) - .setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite), + .setSrcAccessMask(vk::AccessFlagBits::eColorAttachmentWrite) + .setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite) + .setDependencyFlags(vk::DependencyFlagBits::eByRegion), vk::SubpassDependency() .setSrcSubpass(VK_SUBPASS_EXTERNAL) @@ -310,8 +315,9 @@ void SurfaceRenderableResource::initRenderPass() { vk::PipelineStageFlagBits::eLateFragmentTests) .setDstStageMask(vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests) - .setSrcAccessMask({}) - .setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite), + .setSrcAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite) + .setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentWrite) + .setDependencyFlags(vk::DependencyFlagBits::eByRegion), }; const auto renderPassCreateInfo = diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp index 7382d2f9c..05ed35d4d 100644 --- a/src/mbgl/vulkan/renderer_backend.cpp +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -438,10 +438,13 @@ void RendererBackend::initInstance() { extensions.insert(extensions.end(), debugExtensions.begin(), debugExtensions.end()); #ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION - appInfo.setApiVersion(VK_API_VERSION_1_1); + appInfo.setApiVersion(VK_API_VERSION_1_2); - const std::array validationFeatures = { - vk::ValidationFeatureEnableEXT::eGpuAssisted, vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot}; + const std::vector validationFeatures = { + vk::ValidationFeatureEnableEXT::eGpuAssisted, + vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot, + vk::ValidationFeatureEnableEXT::eSynchronizationValidation, + }; const vk::ValidationFeaturesEXT validationFeatureInfo(validationFeatures); createInfo.setPNext(&validationFeatureInfo); @@ -600,6 +603,14 @@ void RendererBackend::initDevice() { .setPEnabledExtensionNames(extensions) .setPEnabledFeatures(&physicalDeviceFeatures); +#ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION + vk::PhysicalDeviceVulkan12Features extraFeatures; + extraFeatures.setBufferDeviceAddress(true); + extraFeatures.setTimelineSemaphore(true); + + createInfo.setPNext(&extraFeatures); +#endif + device = physicalDevice.createDeviceUnique(createInfo, nullptr, dispatcher); } @@ -628,7 +639,13 @@ void RendererBackend::initCommandPool() { } void RendererBackend::destroyResources() { - if (device) device->waitIdle(dispatcher); + if (device) { + try { + device->waitIdle(dispatcher); + } catch (const vk::DeviceLostError& error) { + Log::Error(mbgl::Event::Render, "Vulkan device lost during backend shutdown"); + } + } gfx::BackendScope scope(*this, gfx::BackendScope::ScopeType::Implicit); getThreadPool().runRenderJobs(true /* closeQueue */); diff --git a/src/mbgl/webgpu/dynamic_texture.cpp b/src/mbgl/webgpu/dynamic_texture.cpp index 4f9425cf9..545f9b785 100644 --- a/src/mbgl/webgpu/dynamic_texture.cpp +++ b/src/mbgl/webgpu/dynamic_texture.cpp @@ -23,7 +23,7 @@ void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& t gfx::DynamicTexture::uploadImage(pixelData, texHandle); } -void DynamicTexture::uploadDeferredImages() { +void DynamicTexture::uploadDeferredImages(gfx::UploadPass&) { std::scoped_lock lock(mutex); if (deferredCreation) { texture->create();