core: Fix dynamic texture resource management (#4337)

This commit is contained in:
Adrian Cojocaru
2026-06-23 18:39:50 +03:00
committed by GitHub
parent 0724dcf58d
commit e7a373b013
16 changed files with 120 additions and 69 deletions

View File

@@ -14,6 +14,7 @@ namespace gfx {
class Context;
class Texture2D;
class UploadPass;
using Texture2DPtr = std::shared_ptr<Texture2D>;
class TextureHandle {
@@ -60,7 +61,7 @@ public:
std::optional<TextureHandle> 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:

View File

@@ -37,6 +37,7 @@ public:
const ImageVersionMap& versionMap);
void removeTextures(const std::vector<TextureHandle>& textureHandles, const DynamicTexturePtr& dynamicTexture);
void removeUnusedDynamicTextures();
private:
Context& context;

View File

@@ -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 =

View File

@@ -10,7 +10,7 @@ namespace vulkan {
class Context;
struct BufferAllocation;
using UniqueBufferAllocation = std::unique_ptr<BufferAllocation>;
using SharedBufferAllocation = std::shared_ptr<BufferAllocation>;
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, std::shared_ptr<vulkan::Texture2D>, gfx::TextureHandle::Hasher>;
#else
using TextureBuffersToUpload =
std::unordered_map<gfx::TextureHandle, UniqueBufferAllocation, gfx::TextureHandle::Hasher>;
std::unordered_map<gfx::TextureHandle, SharedBufferAllocation, gfx::TextureHandle::Hasher>;
#endif
private:

View File

@@ -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<gfx::VertexBufferResource> createVertexBufferResource(const void* data,
std::size_t size,
gfx::BufferUsageType,

View File

@@ -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 =

View File

@@ -242,20 +242,21 @@ ImageAtlas DynamicTextureAtlas::uploadIconsAndPatterns(const ImageMap& icons,
void DynamicTextureAtlas::removeTextures(const std::vector<TextureHandle>& 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

View File

@@ -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();

View File

@@ -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);

View File

@@ -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<LayoutResult> result, const uint64_t resultCorrelationID) {
void GeometryTile::onLayout(std::shared_ptr<LayoutResult>&& result, const uint64_t resultCorrelationID) {
MLN_TRACE_FUNC();
loaded = true;

View File

@@ -76,10 +76,10 @@ public:
LayerRenderData* getLayerRenderData(const style::Layer::Impl&);
LayoutResult(mbgl::unordered_map<std::string, LayerRenderData> renderData_,
std::unique_ptr<FeatureIndex> featureIndex_,
gfx::GlyphAtlas glyphAtlas_,
gfx::ImageAtlas imageAtlas_,
LayoutResult(mbgl::unordered_map<std::string, LayerRenderData>&& renderData_,
std::unique_ptr<FeatureIndex>&& 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<LayoutResult>, uint64_t correlationID);
void onLayout(std::shared_ptr<LayoutResult>&&, uint64_t correlationID);
void onError(std::exception_ptr, uint64_t correlationID);

View File

@@ -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<vk::DescriptorSetLayout> 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");

View File

@@ -1,5 +1,7 @@
#include <mbgl/vulkan/dynamic_texture.hpp>
#include <mbgl/vulkan/context.hpp>
#include <mbgl/vulkan/upload_pass.hpp>
#include <mbgl/vulkan/command_encoder.hpp>
#include <mbgl/util/logging.hpp>
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<Texture2D&>(*texture).destroy(false);
static_cast<Texture2D&>(*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<BufferAllocation>(allocator);
SharedBufferAllocation bufferAllocation = std::make_shared<BufferAllocation>(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<Texture2D*>(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&>(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) {

View File

@@ -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 =

View File

@@ -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<vk::ValidationFeatureEnableEXT, 2> validationFeatures = {
vk::ValidationFeatureEnableEXT::eGpuAssisted, vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot};
const std::vector<vk::ValidationFeatureEnableEXT> 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 */);

View File

@@ -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();