Add async texture loading
This commit is contained in:
parent
883e3ce907
commit
dd782038e4
6 changed files with 225 additions and 4 deletions
|
|
@ -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<FTextureBuffer>(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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<void()> 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<void()> 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<std::function<void()>> 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<void()> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
#include <zvulkan/vulkanobjects.h>
|
||||
#include "vulkan/textures/vk_imagetransition.h"
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
|
||||
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<void()> task);
|
||||
void RunOnMainThread(std::function<void()> 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<VulkanImage> BrdfLutTexture;
|
||||
std::unique_ptr<VulkanImageView> BrdfLutTextureView;
|
||||
|
||||
int NextUploadID = 1;
|
||||
std::unordered_map<int, VkHardwareTexture*> PendingUploads;
|
||||
|
||||
struct
|
||||
{
|
||||
std::thread Thread;
|
||||
std::mutex Mutex;
|
||||
std::condition_variable CondVar;
|
||||
bool StopFlag = false;
|
||||
std::list<std::function<void()>> WorkerTasks;
|
||||
std::vector<std::function<void()>> MainTasks;
|
||||
} Worker;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -539,6 +539,7 @@ void VulkanRenderDevice::BeginFrame()
|
|||
hwtexturecount = mTextureManager->GetHWTextureCount();
|
||||
|
||||
GetRenderPassManager()->ProcessMainThreadTasks();
|
||||
GetTextureManager()->ProcessMainThreadTasks();
|
||||
|
||||
if (levelMeshChanged)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
#include "imagehelpers.h"
|
||||
#include "v_video.h"
|
||||
#include "v_font.h"
|
||||
#include <mutex>
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue