diff --git a/src/common/rendering/vulkan/textures/vk_hwtexture.cpp b/src/common/rendering/vulkan/textures/vk_hwtexture.cpp index 7a55e9918..40b251129 100644 --- a/src/common/rendering/vulkan/textures/vk_hwtexture.cpp +++ b/src/common/rendering/vulkan/textures/vk_hwtexture.cpp @@ -37,6 +37,8 @@ #include "vulkan/shaders/vk_shader.h" #include "vk_hwtexture.h" +CVAR(Bool, gl_async_textures, false, 0); + VkHardwareTexture::VkHardwareTexture(VulkanRenderDevice* fb, int numchannels) : fb(fb) { mTexelsize = numchannels; @@ -107,9 +109,39 @@ void VkHardwareTexture::CreateImage(FTexture *tex, int translation, int flags) { if (!tex->isHardwareCanvas()) { - FTextureBuffer texbuffer = tex->CreateTexBuffer(translation, flags | CTF_ProcessData); - bool indexed = flags & CTF_Indexed; - CreateTexture(texbuffer.mWidth, texbuffer.mHeight,indexed? 1 : 4, indexed? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM, texbuffer.mBuffer, !indexed); + if (gl_async_textures && tex->GetImage()) + { + // Create the texture now as that's easier to deal with elsewhere. + + FTextureBuffer texbuffer = tex->CreateTexBuffer(translation, flags | CTF_CheckOnly); + bool indexed = flags & CTF_Indexed; + CreateTexture(texbuffer.mWidth, texbuffer.mHeight, indexed ? 1 : 4, indexed ? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM, texbuffer.mBuffer, !indexed); + + auto textureManager = fb->GetTextureManager(); + + int uploadID = textureManager->CreateUploadID(this); + textureManager->RunOnWorkerThread([=]() { + + // Load the texture on the worker thread + auto imagedata = std::make_shared(tex->CreateTexBuffer(translation, flags | CTF_ProcessData)); + + textureManager->RunOnMainThread([=]() { + + // Upload the texture on the main thread, as long as the hwrenderer didn't destroy this hwtexture already. + if (textureManager->CheckUploadID(uploadID)) + { + UploadTexture(imagedata->mWidth, imagedata->mHeight, indexed ? 1 : 4, indexed ? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM, imagedata->mBuffer, !indexed); + } + + }); + }); + } + else + { + FTextureBuffer texbuffer = tex->CreateTexBuffer(translation, flags | CTF_ProcessData); + bool indexed = flags & CTF_Indexed; + CreateTexture(texbuffer.mWidth, texbuffer.mHeight, indexed ? 1 : 4, indexed ? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM, texbuffer.mBuffer, !indexed); + } } else { @@ -149,7 +181,12 @@ void VkHardwareTexture::CreateTexture(int w, int h, int pixelsize, VkFormat form .Create(fb->GetDevice()); uint8_t *data = (uint8_t*)stagingBuffer->Map(0, totalSize); - memcpy(data, pixels, totalSize); + + if (pixels) + memcpy(data, pixels, totalSize); + else + memset(data, 0, totalSize); + stagingBuffer->Unmap(); mImage.Image = ImageBuilder() @@ -186,6 +223,45 @@ void VkHardwareTexture::CreateTexture(int w, int h, int pixelsize, VkFormat form fb->GetCommands()->WaitForCommands(false, true); } +void VkHardwareTexture::UploadTexture(int w, int h, int pixelsize, VkFormat format, const void* pixels, bool mipmap) +{ + if (w <= 0 || h <= 0) + throw CVulkanError("Trying to create zero size texture"); + + int totalSize = w * h * pixelsize; + + auto stagingBuffer = BufferBuilder() + .Size(totalSize) + .Usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY) + .DebugName("VkHardwareTexture.mStagingBuffer") + .Create(fb->GetDevice()); + + uint8_t* data = (uint8_t*)stagingBuffer->Map(0, totalSize); + memcpy(data, pixels, totalSize); + stagingBuffer->Unmap(); + + auto cmdbuffer = fb->GetCommands()->GetTransferCommands(); + + VkImageTransition() + .AddImage(&mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true) + .Execute(cmdbuffer); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.depth = 1; + region.imageExtent.width = w; + region.imageExtent.height = h; + cmdbuffer->copyBufferToImage(stagingBuffer->buffer, mImage.Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + if (mipmap) mImage.GenerateMipmaps(cmdbuffer); + + // If we queued more than 64 MB of data already: wait until the uploads finish before continuing + fb->GetCommands()->TransferDeleteList->Add(std::move(stagingBuffer)); + if (fb->GetCommands()->TransferDeleteList->TotalSize > 64 * 1024 * 1024) + fb->GetCommands()->WaitForCommands(false, true); +} + int VkHardwareTexture::GetMipLevels(int w, int h) { int levels = 1; diff --git a/src/common/rendering/vulkan/textures/vk_hwtexture.h b/src/common/rendering/vulkan/textures/vk_hwtexture.h index b11c031bb..4dd924bf8 100644 --- a/src/common/rendering/vulkan/textures/vk_hwtexture.h +++ b/src/common/rendering/vulkan/textures/vk_hwtexture.h @@ -49,6 +49,7 @@ private: void CreateImage(FTexture *tex, int translation, int flags); void CreateTexture(int w, int h, int pixelsize, VkFormat format, const void *pixels, bool mipmap); + void UploadTexture(int w, int h, int pixelsize, VkFormat format, const void* pixels, bool mipmap); static int GetMipLevels(int w, int h); VkTextureImage mImage; diff --git a/src/common/rendering/vulkan/textures/vk_texture.cpp b/src/common/rendering/vulkan/textures/vk_texture.cpp index a65ba5f07..d06770855 100644 --- a/src/common/rendering/vulkan/textures/vk_texture.cpp +++ b/src/common/rendering/vulkan/textures/vk_texture.cpp @@ -35,10 +35,12 @@ VkTextureManager::VkTextureManager(VulkanRenderDevice* fb) : fb(fb) CreateLightmap(); CreateIrradiancemap(); CreatePrefiltermap(); + StartWorkerThread(); } VkTextureManager::~VkTextureManager() { + StopWorkerThread(); while (!Textures.empty()) RemoveTexture(Textures.back()); while (!PPTextures.empty()) @@ -72,6 +74,16 @@ void VkTextureManager::RemoveTexture(VkHardwareTexture* texture) texture->Reset(); texture->fb = nullptr; Textures.erase(texture->it); + + // Make sure no pending uploads access the texture after it has been destroyed by the hwrenderer + for (auto it = PendingUploads.begin(); it != PendingUploads.end(); ++it) + { + if (it->second == texture) + { + PendingUploads.erase(it); + break; + } + } } void VkTextureManager::AddPPTexture(VkPPTexture* texture) @@ -567,3 +579,100 @@ void VkTextureManager::DownloadLightmap(int arrayIndex, uint16_t* buffer) memcpy(buffer, srcdata, totalSize * sizeof(uint16_t)); stagingBuffer->Unmap(); } + +int VkTextureManager::CreateUploadID(VkHardwareTexture* tex) +{ + int id = NextUploadID++; + PendingUploads[id] = tex; + return id; +} + +bool VkTextureManager::CheckUploadID(int id) +{ + auto it = PendingUploads.find(id); + if (it == PendingUploads.end()) + return false; + PendingUploads.erase(it); + return true; +} + +void VkTextureManager::RunOnWorkerThread(std::function task) +{ + std::unique_lock lock(Worker.Mutex); + Worker.WorkerTasks.push_back(std::move(task)); + lock.unlock(); + Worker.CondVar.notify_one(); +} + +void VkTextureManager::RunOnMainThread(std::function task) +{ + std::unique_lock lock(Worker.Mutex); + Worker.MainTasks.push_back(std::move(task)); +} + +void VkTextureManager::StartWorkerThread() +{ + Worker.Thread = std::thread([this]() { WorkerThreadMain(); }); +} + +void VkTextureManager::StopWorkerThread() +{ + std::unique_lock lock(Worker.Mutex); + Worker.StopFlag = true; + lock.unlock(); + Worker.CondVar.notify_all(); + Worker.Thread.join(); + lock.lock(); + Worker.WorkerTasks.clear(); + Worker.MainTasks.clear(); + Worker.StopFlag = false; +} + +void VkTextureManager::ProcessMainThreadTasks() +{ + std::unique_lock lock(Worker.Mutex); + std::vector> tasks; + tasks.swap(Worker.MainTasks); + lock.unlock(); + + for (auto& task : tasks) + { + task(); + } +} + +void VkTextureManager::WorkerThreadMain() +{ + std::unique_lock lock(Worker.Mutex); + while (true) + { + Worker.CondVar.wait(lock, [&] { return Worker.StopFlag || !Worker.WorkerTasks.empty(); }); + if (Worker.StopFlag) + break; + + std::function task; + + if (!Worker.WorkerTasks.empty()) + { + task = std::move(Worker.WorkerTasks.front()); + Worker.WorkerTasks.pop_front(); + } + + if (task) + { + lock.unlock(); + + try + { + task(); + } + catch (...) + { + auto exception = std::current_exception(); + RunOnMainThread([=]() { std::rethrow_exception(exception); }); + } + + lock.lock(); + } + } +} diff --git a/src/common/rendering/vulkan/textures/vk_texture.h b/src/common/rendering/vulkan/textures/vk_texture.h index 443a73201..f15f228a3 100644 --- a/src/common/rendering/vulkan/textures/vk_texture.h +++ b/src/common/rendering/vulkan/textures/vk_texture.h @@ -4,6 +4,10 @@ #include #include "vulkan/textures/vk_imagetransition.h" #include +#include +#include +#include +#include class VulkanRenderDevice; class VkHardwareTexture; @@ -56,6 +60,13 @@ public: static const int MAX_REFLECTION_LOD = 4; // Note: must match what lightmodel_pbr.glsl expects + void ProcessMainThreadTasks(); + void RunOnWorkerThread(std::function task); + void RunOnMainThread(std::function task); + + int CreateUploadID(VkHardwareTexture* tex); + bool CheckUploadID(int id); + private: void CreateNullTexture(); void CreateBrdfLutTexture(); @@ -64,6 +75,10 @@ private: void CreateIrradiancemap(); void CreatePrefiltermap(); + void StartWorkerThread(); + void StopWorkerThread(); + void WorkerThreadMain(); + VkPPTexture* GetVkTexture(PPTexture* texture); VulkanRenderDevice* fb = nullptr; @@ -76,4 +91,17 @@ private: std::unique_ptr BrdfLutTexture; std::unique_ptr BrdfLutTextureView; + + int NextUploadID = 1; + std::unordered_map PendingUploads; + + struct + { + std::thread Thread; + std::mutex Mutex; + std::condition_variable CondVar; + bool StopFlag = false; + std::list> WorkerTasks; + std::vector> MainTasks; + } Worker; }; diff --git a/src/common/rendering/vulkan/vk_renderdevice.cpp b/src/common/rendering/vulkan/vk_renderdevice.cpp index 9123e0db2..3ae62996c 100644 --- a/src/common/rendering/vulkan/vk_renderdevice.cpp +++ b/src/common/rendering/vulkan/vk_renderdevice.cpp @@ -539,6 +539,7 @@ void VulkanRenderDevice::BeginFrame() hwtexturecount = mTextureManager->GetHWTextureCount(); GetRenderPassManager()->ProcessMainThreadTasks(); + GetTextureManager()->ProcessMainThreadTasks(); if (levelMeshChanged) { diff --git a/src/common/textures/texture.cpp b/src/common/textures/texture.cpp index 38e0a561d..1155c64dc 100644 --- a/src/common/textures/texture.cpp +++ b/src/common/textures/texture.cpp @@ -51,6 +51,7 @@ #include "imagehelpers.h" #include "v_video.h" #include "v_font.h" +#include // Wrappers to keep the definitions of these classes out of here. IHardwareTexture* CreateHardwareTexture(int numchannels); @@ -325,6 +326,11 @@ bool FTexture::ProcessData(unsigned char* buffer, int w, int h, bool ispatch) FTextureBuffer FTexture::CreateTexBuffer(int translation, int flags) { + // Textures may be loaded on the main thread or on a worker thread. + // To keep things simple, make sure only one of the threads is doing this at any given time. + static std::mutex mutex; + std::unique_lock lock(mutex); + FTextureBuffer result; if (flags & CTF_Indexed) {