core: Fix dynamic texture resource management (#4337)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
const ImageVersionMap& versionMap);
|
||||
|
||||
void removeTextures(const std::vector<TextureHandle>& textureHandles, const DynamicTexturePtr& dynamicTexture);
|
||||
void removeUnusedDynamicTextures();
|
||||
|
||||
private:
|
||||
Context& context;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user