feat: make image loading async

This commit is contained in:
2026-10-24 02:03:10 +02:00
parent aa3eef259c
commit 355dfa7851
11 changed files with 696 additions and 361 deletions
@@ -4,7 +4,9 @@
#pragma once
#include <filesystem>
#include <future>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
@@ -12,6 +14,7 @@
#include <destrum/Graphics/GPUImage.h>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/TextureIntent.h>
#include <destrum/Graphics/imageLoader.h>
class GfxDevice;
@@ -33,6 +36,14 @@ public:
VkImageUsageFlags usage,
bool mipMap, TextureIntent intent = TextureIntent::ColorSrgb);
// Reserves the ID now and decodes on a job worker, the slot is filled in by finishPendingLoads
ImageID requestImage(
const std::filesystem::path& path,
VkImageUsageFlags usage,
bool mipMap, TextureIntent intent = TextureIntent::ColorSrgb);
void finishPendingLoads();
[[nodiscard]] std::size_t getPendingLoadCount() const { return pendingImages.size(); }
ImageID addImage(GPUImage image);
ImageID addImage(ImageID id, GPUImage image);
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
@@ -61,7 +72,22 @@ public:
}
private:
struct PendingImage {
ImageID id{NULL_IMAGE_ID};
std::future<ImageData> data;
};
[[nodiscard]] std::optional<ImageID> findLoadedImage(
const std::filesystem::path& path,
VkImageUsageFlags usage,
bool mipMap,
TextureIntent intent) const;
ImageID reserveImageId();
void finishPendingLoad(ImageID id);
void completePendingLoad(PendingImage pending);
std::vector<GPUImage> images;
std::vector<PendingImage> pendingImages;
std::shared_ptr<std::vector<ImageID>> freeIds = std::make_shared<std::vector<ImageID>>();
GfxDevice& gfxDevice;
@@ -141,6 +141,15 @@ public:
return imageManager.loadImageFromFile(path, usage, mipMap, intent);
}
[[nodiscard]] std::optional<GPUImage> createImageFromData(
const ImageData& data,
VkImageUsageFlags usage,
bool mipMap,
const std::string& debugName) const
{
return imageManager.createImageFromData(data, usage, mipMap, debugName);
}
void uploadImageDataSized(
const GPUImage& image,
const void* pixelData,
@@ -14,6 +14,7 @@
class VulkanImmediateExecutor;
class MemoryManager;
struct ImageData;
class ImageManager {
public:
@@ -41,6 +42,12 @@ public:
bool mipMap,
TextureIntent intent) const;
[[nodiscard]] std::optional<GPUImage> createImageFromData(
const ImageData& data,
VkImageUsageFlags usage,
bool mipMap,
const std::string& debugName) const;
void uploadImageData(
const GPUImage& image,
const void* pixelData,
@@ -48,6 +48,10 @@ public:
ImageID loadMaterialTexture(GfxDevice& gfxDevice, const std::filesystem::path& path, TextureIntent intent);
// While a scope is open material textures decode on job workers, closing the last one uploads them
void beginDeferredTextureLoads();
void endDeferredTextureLoads();
struct ReleaseStats {
std::size_t meshes{0};
std::size_t materials{0};
@@ -105,6 +109,20 @@ private:
ImageID whiteImageId{NULL_IMAGE_ID};
ImageID errorImageId{NULL_IMAGE_ID};
ImageID defaultNormalImageId{NULL_IMAGE_ID};
int deferredTextureLoads{0};
};
class DeferredTextureLoadScope {
public:
explicit DeferredTextureLoadScope(RenderResources* resources);
~DeferredTextureLoadScope();
DeferredTextureLoadScope(const DeferredTextureLoadScope&) = delete;
DeferredTextureLoadScope& operator=(const DeferredTextureLoadScope&) = delete;
private:
RenderResources* resources;
};
#endif
@@ -58,12 +58,12 @@ public:
void Wait(JobCounter& counter);
template <typename F>
auto Async(F&& fn) -> std::future<std::invoke_result_t<std::decay_t<F>>>
auto Async(F&& fn, JobPriority priority = JobPriority::Low) -> std::future<std::invoke_result_t<std::decay_t<F>>>
{
using Result = std::invoke_result_t<std::decay_t<F>>;
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<F>(fn));
auto future = task->get_future();
Submit([task] { (*task)(); }, JobPriority::Low);
Submit([task] { (*task)(); }, priority);
return future;
}