core: Fix Vulkan dynamic texture threading (#4102)

This commit is contained in:
Adrian Cojocaru
2026-02-17 20:23:53 +02:00
committed by GitHub
parent 9737694916
commit 3fc501dfdf
8 changed files with 147 additions and 83 deletions

View File

@@ -47,6 +47,7 @@ public:
BufferResource& operator=(BufferResource&&) noexcept;
BufferResource clone() const;
void destroy(bool deferred = true);
void update(const void* data, std::size_t size, std::size_t offset) noexcept;

View File

@@ -13,6 +13,7 @@
#include <mbgl/vulkan/renderer_backend.hpp>
#include <mbgl/vulkan/pipeline.hpp>
#include <mbgl/vulkan/descriptor_set.hpp>
#include <mbgl/util/util.hpp>
#include <memory>
#include <optional>
@@ -150,7 +151,8 @@ public:
uint8_t getCurrentFrameResourceIndex() const { return frameResourceIndex; }
void enqueueDeletion(DeletionTask&& function);
void submitOneTimeCommand(const std::function<void(const vk::UniqueCommandBuffer&)>& function);
void submitOneTimeCommand(const vk::UniqueCommandPool& commandPool,
void submitOneTimeCommand(const vk::UniqueCommandBuffer& buffer);
void submitOneTimeCommand(const vk::UniqueCommandPool& pool,
const std::function<void(const vk::UniqueCommandBuffer&)>& function);
void requestSurfaceUpdate(bool useDelay = true);
@@ -212,6 +214,8 @@ private:
#if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD
std::mutex graphicsQueueSubmitMutex;
#endif
MBGL_STORE_THREAD(tid);
};
} // namespace vulkan

View File

@@ -3,7 +3,7 @@
#include <mbgl/gfx/dynamic_texture.hpp>
#include <mbgl/vulkan/texture2d.hpp>
#define DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD 1
#define DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD 0
namespace mbgl {
namespace vulkan {
@@ -15,20 +15,22 @@ using UniqueBufferAllocation = std::unique_ptr<BufferAllocation>;
class DynamicTexture : public gfx::DynamicTexture {
public:
DynamicTexture(Context& context, Size size, gfx::TexturePixelType pixelType);
~DynamicTexture() override;
void uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) override;
void uploadDeferredImages() override;
bool removeTexture(const gfx::TextureHandle& texHandle) override;
#if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD
using TexturesToBlit = std::unordered_map<gfx::TextureHandle, gfx::Texture2DPtr, gfx::TextureHandle::Hasher>;
using TexturesToBlit =
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>;
#endif
private:
Context& context;
vulkan::Context& context;
#if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD
TexturesToBlit texturesToBlit;

View File

@@ -65,6 +65,7 @@ public:
bool isModifiedAfter(const std::chrono::duration<double>& t) const { return t < lastModified; }
void create() noexcept override;
void destroy(bool deferred = true) noexcept;
void upload() noexcept override;
void upload(const void* pixelData, const Size& size_) noexcept override;
@@ -74,7 +75,7 @@ public:
uint16_t xOffset,
uint16_t yOffset,
const vk::UniqueCommandBuffer& buffer,
std::vector<std::function<void(Context&)>>* deletionQueue = nullptr) noexcept;
bool submit) noexcept;
bool needsUpload() const noexcept override { return !!imageData; };
@@ -96,8 +97,8 @@ private:
void createTexture();
void createSampler();
void destroyTexture();
void destroySampler();
void destroyTexture(bool deferred = true);
void destroySampler(bool deferred = true);
void transitionToTransferWriteLayout(const vk::UniqueCommandBuffer&);
void transitionToTransferReadLayout(const vk::UniqueCommandBuffer&);

View File

@@ -128,6 +128,10 @@ BufferResource::BufferResource(BufferResource&& other) noexcept
}
BufferResource::~BufferResource() noexcept {
destroy(true);
}
void BufferResource::destroy(bool deferred) {
if (isValid()) {
context.threadSafeAccessRenderingStats([&](gfx::RenderingStats& stats) {
stats.numBuffers--;
@@ -140,9 +144,15 @@ BufferResource::~BufferResource() noexcept {
});
}
if (!bufferAllocation) return;
if (!bufferAllocation) {
return;
}
context.enqueueDeletion([allocation = std::move(bufferAllocation)](auto&) mutable { allocation.reset(); });
if (deferred) {
context.enqueueDeletion([allocation = std::move(bufferAllocation)](auto&) mutable { allocation.reset(); });
} else {
bufferAllocation.reset();
}
}
BufferResource BufferResource::clone() const {

View File

@@ -66,6 +66,8 @@ Context::Context(RendererBackend& backend_)
}
Context::~Context() noexcept {
MBGL_VERIFY_THREAD(tid);
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
destroyResources();
@@ -143,6 +145,8 @@ void Context::initFrameResources() {
}
void Context::destroyResources() {
MBGL_VERIFY_THREAD(tid);
backend.getDevice()->waitIdle(backend.getDispatcher());
for (auto& frame : frameResources) {
@@ -166,6 +170,8 @@ void Context::destroyResources() {
}
void Context::enqueueDeletion(DeletionTask&& function) {
MBGL_VERIFY_THREAD(tid);
if (frameResources.empty()) {
function(*this);
return;
@@ -175,32 +181,19 @@ void Context::enqueueDeletion(DeletionTask&& function) {
}
void Context::submitOneTimeCommand(const std::function<void(const vk::UniqueCommandBuffer&)>& function) {
submitOneTimeCommand({}, function);
submitOneTimeCommand(backend.getCommandPool(), function);
}
void Context::submitOneTimeCommand(const vk::UniqueCommandPool& commandPool,
const std::function<void(const vk::UniqueCommandBuffer&)>& function) {
void Context::submitOneTimeCommand(const vk::UniqueCommandBuffer& commandBuffer) {
MLN_TRACE_FUNC();
#if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD
std::scoped_lock lock(graphicsQueueSubmitMutex);
#endif
const vk::CommandBufferAllocateInfo allocateInfo(
commandPool ? commandPool.get() : backend.getCommandPool().get(), vk::CommandBufferLevel::ePrimary, 1);
const auto& device = backend.getDevice();
const auto& dispatcher = backend.getDispatcher();
const auto& commandBuffers = device->allocateCommandBuffersUnique(allocateInfo, dispatcher);
auto& commandBuffer = commandBuffers.front();
backend.setDebugName(commandBuffer.get(), "OneTimeSubmitCommandBuffer");
commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit), dispatcher);
function(commandBuffer);
commandBuffer->end(dispatcher);
const auto submitInfo = vk::SubmitInfo().setCommandBuffers(commandBuffer.get());
const auto& fence = device->createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlags()), nullptr, dispatcher);
backend.getGraphicsQueue().submit(submitInfo, fence.get(), dispatcher);
@@ -211,6 +204,22 @@ void Context::submitOneTimeCommand(const vk::UniqueCommandPool& commandPool,
}
}
void Context::submitOneTimeCommand(const vk::UniqueCommandPool& pool,
const std::function<void(const vk::UniqueCommandBuffer&)>& function) {
const auto& device = backend.getDevice();
const auto& dispatcher = backend.getDispatcher();
const vk::CommandBufferAllocateInfo allocateInfo(pool.get(), vk::CommandBufferLevel::ePrimary, 1);
const auto& commandBuffers = device->allocateCommandBuffersUnique(allocateInfo, dispatcher);
const auto& commandBuffer = commandBuffers.front();
commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit), dispatcher);
function(commandBuffer);
commandBuffer->end(dispatcher);
submitOneTimeCommand(commandBuffer);
}
void Context::requestSurfaceUpdate(bool useDelay) {
if (surfaceUpdateRequested) {
if (!useDelay) {
@@ -226,6 +235,8 @@ void Context::requestSurfaceUpdate(bool useDelay) {
void Context::waitFrame() const {
MLN_TRACE_FUNC();
MBGL_VERIFY_THREAD(tid);
const auto& device = backend.getDevice();
const auto& dispatcher = backend.getDispatcher();
auto& frame = frameResources[frameResourceIndex];
@@ -240,6 +251,7 @@ void Context::waitFrame() const {
void Context::beginFrame() {
MLN_TRACE_FUNC();
MBGL_VERIFY_THREAD(tid);
frameResourceIndex = (frameResourceIndex + 1) % frameResources.size();
@@ -330,6 +342,7 @@ void Context::endFrame() {}
void Context::submitFrame() {
MLN_TRACE_FUNC();
#if DYNAMIC_TEXTURE_VULKAN_MULTITHREADED_UPLOAD
std::scoped_lock lock(graphicsQueueSubmitMutex);
#endif
@@ -640,7 +653,7 @@ const std::unique_ptr<Texture2D>& Context::getDummyTexture() {
dummyTexture2D->setSize(size);
submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) {
dummyTexture2D->uploadSubRegion(data.data(), size, 0, 0, commandBuffer);
dummyTexture2D->uploadSubRegion(data.data(), size, 0, 0, commandBuffer, false);
});
}

View File

@@ -17,30 +17,38 @@ DynamicTexture::DynamicTexture(Context& context_, Size size, gfx::TexturePixelTy
createInfo, nullptr, context.getBackend().getDispatcher());
}
DynamicTexture::~DynamicTexture() {
for (auto& texture : texturesToBlit) {
texture.second->destroy(false);
}
if (texture) {
static_cast<Texture2D&>(*texture).destroy(false);
}
}
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);
gfx::Texture2DPtr textureToBlit = context.createTexture2D();
auto textureToBlit = std::static_pointer_cast<Texture2D>(context.createTexture2D());
texturesToBlit.emplace(texHandle, textureToBlit);
const auto& textureToBlitVK = static_cast<Texture2D*>(textureToBlit.get());
textureToBlitVK->setSize(imageSize);
textureToBlitVK->setFormat(texture.get()->getFormat(), gfx::TextureChannelDataType::UnsignedByte);
textureToBlitVK->setUsage(Texture2DUsage::Blit);
textureToBlitVK->create();
textureToBlit->setSize(imageSize);
textureToBlit->setFormat(texture.get()->getFormat(), gfx::TextureChannelDataType::UnsignedByte);
textureToBlit->setUsage(Texture2DUsage::Blit);
textureToBlit->create();
DeletionQueue deletionQueue;
context.submitOneTimeCommand(commandPool, [&](const vk::UniqueCommandBuffer& commandBuffer) {
textureToBlitVK->uploadSubRegion(pixelData, imageSize, 0, 0, commandBuffer, &deletionQueue);
});
const auto& device = context.getBackend().getDevice();
const auto& dispatcher = context.getBackend().getDispatcher();
for (const auto& function : deletionQueue) {
function(context);
}
const vk::CommandBufferAllocateInfo allocateInfo(commandPool.get(), vk::CommandBufferLevel::ePrimary, 1);
const auto& commandBuffers = device->allocateCommandBuffersUnique(allocateInfo, dispatcher);
const auto& commandBuffer = commandBuffers.front();
deletionQueue.clear();
commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit), dispatcher);
textureToBlit->uploadSubRegion(pixelData, imageSize, 0, 0, commandBuffer, true);
gfx::DynamicTexture::uploadImage(pixelData, texHandle);
}
@@ -49,7 +57,7 @@ void DynamicTexture::uploadDeferredImages() {
std::scoped_lock lock(mutex);
const auto& textureVK = static_cast<Texture2D*>(texture.get());
context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) {
context.submitOneTimeCommand(commandPool, [&](const vk::UniqueCommandBuffer& commandBuffer) {
textureVK->transitionToTransferWriteLayout(commandBuffer);
for (const auto& pair : texturesToBlit) {
const auto& rect = pair.first.getRectangle();
@@ -69,13 +77,23 @@ void DynamicTexture::uploadDeferredImages() {
}
textureVK->transitionToShaderReadLayout(commandBuffer);
});
for (auto& texture : texturesToBlit) {
texture.second->destroy(false);
}
texturesToBlit.clear();
}
bool DynamicTexture::removeTexture(const gfx::TextureHandle& texHandle) {
if (gfx::DynamicTexture::removeTexture(texHandle)) {
std::scoped_lock lock(mutex);
texturesToBlit.erase(texHandle);
const auto& tex = texturesToBlit.find(texHandle);
if (tex != texturesToBlit.end()) {
tex->second->destroy(false);
texturesToBlit.erase(tex);
}
return true;
}
return false;
@@ -88,6 +106,12 @@ DynamicTexture::DynamicTexture(Context& context_, Size size, gfx::TexturePixelTy
texture->create();
}
DynamicTexture::~DynamicTexture() {
if (texture) {
static_cast<Texture2D&>(*texture).destroy(false);
}
}
void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) {
std::scoped_lock lock(mutex);
const auto& rect = texHandle.getRectangle();
@@ -142,11 +166,14 @@ void DynamicTexture::uploadDeferredImages() {
region,
context.getBackend().getDispatcher());
context.renderingStats().numTextureUpdates++;
context.renderingStats().textureUpdateBytes += rect.w * rect.h * texture->getPixelStride();
context.threadSafeAccessRenderingStats([&](gfx::RenderingStats& stats) {
stats.numTextureUpdates++;
stats.textureUpdateBytes += rect.w * rect.h * texture->getPixelStride();
});
}
textureVK->transitionToShaderReadLayout(commandBuffer);
});
textureBuffersToUpload.clear();
}

View File

@@ -42,8 +42,7 @@ Texture2D::Texture2D(Context& context_)
: context(context_) {}
Texture2D::~Texture2D() {
destroyTexture();
destroySampler();
destroy(true);
}
gfx::Texture2D& Texture2D::setSamplerConfiguration(const SamplerState& samplerState_) noexcept {
@@ -132,6 +131,11 @@ void Texture2D::create() noexcept {
}
}
void Texture2D::destroy(bool deferred) noexcept {
destroyTexture(deferred);
destroySampler(deferred);
}
void Texture2D::upload() noexcept {
if (!imageData) return;
@@ -151,7 +155,7 @@ void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16
const auto& encoder = context.createCommandEncoder();
const auto& encoderImpl = static_cast<const CommandEncoder&>(*encoder);
uploadSubRegion(pixelData, size_, xOffset, yOffset, encoderImpl.getCommandBuffer());
uploadSubRegion(pixelData, size_, xOffset, yOffset, encoderImpl.getCommandBuffer(), false);
}
void Texture2D::uploadSubRegion(const void* pixelData,
@@ -159,7 +163,7 @@ void Texture2D::uploadSubRegion(const void* pixelData,
uint16_t xOffset,
uint16_t yOffset,
const vk::UniqueCommandBuffer& commandBuffer,
DeletionQueue* deletionQueue) noexcept {
bool submit) noexcept {
if (!pixelData || size_.width == 0 || size_.height == 0) return;
create();
@@ -189,39 +193,32 @@ void Texture2D::uploadSubRegion(const void* pixelData,
vmaMapMemory(allocator, bufferAllocation->allocation, &bufferAllocation->mappedBuffer);
memcpy(bufferAllocation->mappedBuffer, pixelData, bufferInfo.size);
const auto enqueueCommands = [&](const auto& buffer) {
transitionToTransferWriteLayout(buffer);
transitionToTransferWriteLayout(commandBuffer);
const auto region = vk::BufferImageCopy()
.setBufferOffset(0)
.setBufferRowLength(size_.width)
.setImageSubresource(
vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1))
.setImageOffset(vk::Offset3D(xOffset, yOffset))
.setImageExtent(vk::Extent3D(size_.width, size_.height, 1));
const auto region = vk::BufferImageCopy()
.setBufferOffset(0)
.setBufferRowLength(size_.width)
.setImageSubresource(vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1))
.setImageOffset(vk::Offset3D(xOffset, yOffset))
.setImageExtent(vk::Extent3D(size_.width, size_.height, 1));
buffer->copyBufferToImage(
bufferAllocation->buffer, imageAllocation->image, imageLayout, region, backend.getDispatcher());
commandBuffer->copyBufferToImage(
bufferAllocation->buffer, imageAllocation->image, imageLayout, region, backend.getDispatcher());
if (samplerState.mipmapped && textureUsage == Texture2DUsage::ShaderInput) {
generateMips(buffer);
} else if (textureUsage == Texture2DUsage::Blit) {
transitionToTransferReadLayout(buffer);
} else {
transitionToShaderReadLayout(buffer);
}
};
enqueueCommands(commandBuffer);
const auto function = [buffAlloc = std::move(bufferAllocation)](auto&) mutable {
buffAlloc.reset();
};
if (deletionQueue) {
deletionQueue->push_back(std::move(function));
if (samplerState.mipmapped && textureUsage == Texture2DUsage::ShaderInput) {
generateMips(commandBuffer);
} else if (textureUsage == Texture2DUsage::Blit) {
transitionToTransferReadLayout(commandBuffer);
} else {
context.enqueueDeletion(std::move(function));
transitionToShaderReadLayout(commandBuffer);
}
if (submit) {
commandBuffer->end(context.getBackend().getDispatcher());
context.submitOneTimeCommand(commandBuffer);
bufferAllocation.reset();
} else {
context.enqueueDeletion([buffAlloc = std::move(bufferAllocation)](auto&) mutable { buffAlloc.reset(); });
}
context.threadSafeAccessRenderingStats([&](gfx::RenderingStats& stats) {
@@ -429,9 +426,13 @@ void Texture2D::createSampler() {
lastModified = util::MonotonicTimer::now();
}
void Texture2D::destroyTexture() {
void Texture2D::destroyTexture(bool deferred) {
if (imageAllocation) {
context.enqueueDeletion([allocation = std::move(imageAllocation)](auto&) mutable { allocation.reset(); });
if (deferred) {
context.enqueueDeletion([allocation = std::move(imageAllocation)](auto&) mutable { allocation.reset(); });
} else {
imageAllocation.reset();
}
imageLayout = vk::ImageLayout::eUndefined;
@@ -442,11 +443,16 @@ void Texture2D::destroyTexture() {
}
}
void Texture2D::destroySampler() {
void Texture2D::destroySampler(bool deferred) {
if (sampler) {
context.enqueueDeletion([sampler_ = std::move(sampler)](auto& context_) mutable {
context_.getBackend().getDevice()->destroySampler(sampler_, nullptr, context_.getBackend().getDispatcher());
});
if (deferred) {
context.enqueueDeletion([sampler_ = std::move(sampler)](auto& context_) mutable {
context_.getBackend().getDevice()->destroySampler(
sampler_, nullptr, context_.getBackend().getDispatcher());
});
} else {
context.getBackend().getDevice()->destroySampler(sampler, nullptr, context.getBackend().getDispatcher());
}
sampler = nullptr;
}