feat: make image loading async
This commit is contained in:
@@ -4,7 +4,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
#include <destrum/Graphics/GPUImage.h>
|
#include <destrum/Graphics/GPUImage.h>
|
||||||
#include <destrum/Graphics/ids.h>
|
#include <destrum/Graphics/ids.h>
|
||||||
#include <destrum/Graphics/TextureIntent.h>
|
#include <destrum/Graphics/TextureIntent.h>
|
||||||
|
#include <destrum/Graphics/imageLoader.h>
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
|
|
||||||
@@ -33,6 +36,14 @@ public:
|
|||||||
VkImageUsageFlags usage,
|
VkImageUsageFlags usage,
|
||||||
bool mipMap, TextureIntent intent = TextureIntent::ColorSrgb);
|
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(GPUImage image);
|
||||||
ImageID addImage(ImageID id, GPUImage image);
|
ImageID addImage(ImageID id, GPUImage image);
|
||||||
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
|
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
|
||||||
@@ -61,7 +72,22 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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<GPUImage> images;
|
||||||
|
std::vector<PendingImage> pendingImages;
|
||||||
std::shared_ptr<std::vector<ImageID>> freeIds = std::make_shared<std::vector<ImageID>>();
|
std::shared_ptr<std::vector<ImageID>> freeIds = std::make_shared<std::vector<ImageID>>();
|
||||||
GfxDevice& gfxDevice;
|
GfxDevice& gfxDevice;
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,15 @@ public:
|
|||||||
return imageManager.loadImageFromFile(path, usage, mipMap, intent);
|
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(
|
void uploadImageDataSized(
|
||||||
const GPUImage& image,
|
const GPUImage& image,
|
||||||
const void* pixelData,
|
const void* pixelData,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
class VulkanImmediateExecutor;
|
class VulkanImmediateExecutor;
|
||||||
class MemoryManager;
|
class MemoryManager;
|
||||||
|
struct ImageData;
|
||||||
|
|
||||||
class ImageManager {
|
class ImageManager {
|
||||||
public:
|
public:
|
||||||
@@ -41,6 +42,12 @@ public:
|
|||||||
bool mipMap,
|
bool mipMap,
|
||||||
TextureIntent intent) const;
|
TextureIntent intent) const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<GPUImage> createImageFromData(
|
||||||
|
const ImageData& data,
|
||||||
|
VkImageUsageFlags usage,
|
||||||
|
bool mipMap,
|
||||||
|
const std::string& debugName) const;
|
||||||
|
|
||||||
void uploadImageData(
|
void uploadImageData(
|
||||||
const GPUImage& image,
|
const GPUImage& image,
|
||||||
const void* pixelData,
|
const void* pixelData,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ public:
|
|||||||
|
|
||||||
ImageID loadMaterialTexture(GfxDevice& gfxDevice, const std::filesystem::path& path, TextureIntent intent);
|
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 {
|
struct ReleaseStats {
|
||||||
std::size_t meshes{0};
|
std::size_t meshes{0};
|
||||||
std::size_t materials{0};
|
std::size_t materials{0};
|
||||||
@@ -105,6 +109,20 @@ private:
|
|||||||
ImageID whiteImageId{NULL_IMAGE_ID};
|
ImageID whiteImageId{NULL_IMAGE_ID};
|
||||||
ImageID errorImageId{NULL_IMAGE_ID};
|
ImageID errorImageId{NULL_IMAGE_ID};
|
||||||
ImageID defaultNormalImageId{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
|
#endif
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ public:
|
|||||||
void Wait(JobCounter& counter);
|
void Wait(JobCounter& counter);
|
||||||
|
|
||||||
template <typename F>
|
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>>;
|
using Result = std::invoke_result_t<std::decay_t<F>>;
|
||||||
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<F>(fn));
|
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<F>(fn));
|
||||||
auto future = task->get_future();
|
auto future = task->get_future();
|
||||||
Submit([task] { (*task)(); }, JobPriority::Low);
|
Submit([task] { (*task)(); }, priority);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#include <destrum/Graphics/Caches/ImageCache.h>
|
#include <destrum/Graphics/Caches/ImageCache.h>
|
||||||
|
|
||||||
#include <destrum/Graphics/GfxDevice.h>
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
#include <destrum/Threading/JobSystem.h>
|
||||||
|
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -16,14 +19,9 @@ ImageID ImageCache::loadImageFromFile(
|
|||||||
bool mipMap,
|
bool mipMap,
|
||||||
TextureIntent intent)
|
TextureIntent intent)
|
||||||
{
|
{
|
||||||
for (const auto& [id, info] : loadedImagesInfo) {
|
if (const auto existing = findLoadedImage(path, usage, mipMap, intent)) {
|
||||||
if (info.path == path &&
|
finishPendingLoad(*existing);
|
||||||
info.intent == intent &&
|
return *existing;
|
||||||
info.usage == usage &&
|
|
||||||
info.mipMap == mipMap)
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto imageOpt = gfxDevice.loadImageFromFileRaw(path, usage, mipMap, intent);
|
auto imageOpt = gfxDevice.loadImageFromFileRaw(path, usage, mipMap, intent);
|
||||||
@@ -63,6 +61,120 @@ ImageID ImageCache::loadImageFromFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageID ImageCache::requestImage(
|
||||||
|
const std::filesystem::path& path,
|
||||||
|
VkImageUsageFlags usage,
|
||||||
|
bool mipMap,
|
||||||
|
TextureIntent intent)
|
||||||
|
{
|
||||||
|
if (const auto existing = findLoadedImage(path, usage, mipMap, intent)) {
|
||||||
|
return *existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageID id = reserveImageId();
|
||||||
|
try {
|
||||||
|
loadedImagesInfo.emplace(
|
||||||
|
id,
|
||||||
|
LoadedImageInfo{
|
||||||
|
.path = path,
|
||||||
|
.intent = intent,
|
||||||
|
.usage = usage,
|
||||||
|
.mipMap = mipMap,
|
||||||
|
});
|
||||||
|
pendingImages.push_back(PendingImage{
|
||||||
|
.id = id,
|
||||||
|
.data = JobSystem::GetInstance().Async(
|
||||||
|
[path, intent] { return util::loadImage(path, intent); },
|
||||||
|
JobPriority::High),
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
} catch (...) {
|
||||||
|
loadedImagesInfo.erase(id);
|
||||||
|
freeIds->push_back(id);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageCache::finishPendingLoads()
|
||||||
|
{
|
||||||
|
ZoneScopedN("ImageCache::finishPendingLoads");
|
||||||
|
// Upload in completion order so decoded pixels don't pile up in memory
|
||||||
|
while (!pendingImages.empty()) {
|
||||||
|
const auto ready = std::ranges::find_if(pendingImages, [](const PendingImage& pending) {
|
||||||
|
return pending.data.wait_for(std::chrono::seconds{0}) == std::future_status::ready;
|
||||||
|
});
|
||||||
|
if (ready == pendingImages.end()) {
|
||||||
|
pendingImages.front().data.wait_for(std::chrono::milliseconds{1});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PendingImage pending = std::move(*ready);
|
||||||
|
pendingImages.erase(ready);
|
||||||
|
completePendingLoad(std::move(pending));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ImageID> ImageCache::findLoadedImage(
|
||||||
|
const std::filesystem::path& path,
|
||||||
|
VkImageUsageFlags usage,
|
||||||
|
bool mipMap,
|
||||||
|
TextureIntent intent) const
|
||||||
|
{
|
||||||
|
for (const auto& [id, info] : loadedImagesInfo) {
|
||||||
|
if (info.path == path &&
|
||||||
|
info.intent == intent &&
|
||||||
|
info.usage == usage &&
|
||||||
|
info.mipMap == mipMap)
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageID ImageCache::reserveImageId()
|
||||||
|
{
|
||||||
|
const ImageID id = getFreeImageId();
|
||||||
|
if (const auto it = std::find(freeIds->begin(), freeIds->end(), id); it != freeIds->end()) {
|
||||||
|
freeIds->erase(it);
|
||||||
|
}
|
||||||
|
if (id == images.size()) {
|
||||||
|
images.emplace_back();
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageCache::finishPendingLoad(ImageID id)
|
||||||
|
{
|
||||||
|
const auto it = std::ranges::find_if(pendingImages, [id](const PendingImage& pending) { return pending.id == id; });
|
||||||
|
if (it == pendingImages.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PendingImage pending = std::move(*it);
|
||||||
|
pendingImages.erase(it);
|
||||||
|
completePendingLoad(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageCache::completePendingLoad(PendingImage pending)
|
||||||
|
{
|
||||||
|
const LoadedImageInfo& info = loadedImagesInfo.at(pending.id);
|
||||||
|
std::optional<GPUImage> image;
|
||||||
|
try {
|
||||||
|
image = gfxDevice.createImageFromData(pending.data.get(), info.usage, info.mipMap, info.path.string());
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
spdlog::error("Failed to load image '{}': {}", info.path.string(), exception.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
// Materials already hold this ID, so the slot shows the error texture instead
|
||||||
|
spdlog::warn("Using error texture for failed image load: '{}'", info.path.string());
|
||||||
|
if (errorImageId != NULL_IMAGE_ID) {
|
||||||
|
bindlessSetManager.addImage(gfxDevice.getDevice(), pending.id, images.at(errorImageId).imageView);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addImage(pending.id, std::move(*image));
|
||||||
|
}
|
||||||
|
|
||||||
ImageID ImageCache::addImage(GPUImage image) {
|
ImageID ImageCache::addImage(GPUImage image) {
|
||||||
try {
|
try {
|
||||||
return addImage(getFreeImageId(), std::move(image));
|
return addImage(getFreeImageId(), std::move(image));
|
||||||
@@ -113,11 +225,16 @@ const ImageCache::LoadedImageInfo* ImageCache::findLoadedImageInfo(ImageID id) c
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImageCache::removeImage(ImageID id) {
|
void ImageCache::removeImage(ImageID id) {
|
||||||
if (id >= images.size() || images[id].image == VK_NULL_HANDLE) {
|
if (id >= images.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Pending and failed loads own a slot without an image
|
||||||
|
const bool tracked = loadedImagesInfo.erase(id) > 0;
|
||||||
|
std::erase_if(pendingImages, [id](const PendingImage& pending) { return pending.id == id; });
|
||||||
|
if (images[id].image == VK_NULL_HANDLE && !tracked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedImagesInfo.erase(id);
|
|
||||||
gfxDevice.destroyImage(images[id]);
|
gfxDevice.destroyImage(images[id]);
|
||||||
|
|
||||||
// In-flight frames may still sample this bindless slot, so it only becomes
|
// In-flight frames may still sample this bindless slot, so it only becomes
|
||||||
@@ -146,6 +263,7 @@ ImageID ImageCache::getFreeImageId() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImageCache::destroyImages() {
|
void ImageCache::destroyImages() {
|
||||||
|
pendingImages.clear();
|
||||||
for (auto& image: images) {
|
for (auto& image: images) {
|
||||||
gfxDevice.destroyImage(image);
|
gfxDevice.destroyImage(image);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,13 +130,20 @@ std::optional<GPUImage> ImageManager::loadImageFromFile(
|
|||||||
bool mipMap,
|
bool mipMap,
|
||||||
TextureIntent intent) const
|
TextureIntent intent) const
|
||||||
{
|
{
|
||||||
const auto data = util::loadImage(path, intent);
|
return createImageFromData(util::loadImage(path, intent), usage, mipMap, path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GPUImage> ImageManager::createImageFromData(
|
||||||
|
const ImageData& data,
|
||||||
|
VkImageUsageFlags usage,
|
||||||
|
bool mipMap,
|
||||||
|
const std::string& debugName) const
|
||||||
|
{
|
||||||
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
if (data.vkFormat == VK_FORMAT_UNDEFINED ||
|
||||||
data.byteSize == 0 ||
|
data.byteSize == 0 ||
|
||||||
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
(data.hdr ? data.hdrPixels == nullptr : data.pixels == nullptr))
|
||||||
{
|
{
|
||||||
spdlog::error("Failed to load image '{}'", path.string());
|
spdlog::error("Failed to load image '{}'", debugName);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +158,7 @@ std::optional<GPUImage> ImageManager::loadImageFromFile(
|
|||||||
spdlog::warn(
|
spdlog::warn(
|
||||||
"Format {} does not support linear mip generation, loading '{}' without mips",
|
"Format {} does not support linear mip generation, loading '{}' without mips",
|
||||||
static_cast<int>(data.vkFormat),
|
static_cast<int>(data.vkFormat),
|
||||||
path.string());
|
debugName);
|
||||||
mipMap = false;
|
mipMap = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +185,7 @@ std::optional<GPUImage> ImageManager::loadImageFromFile(
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
image.debugName = path.string();
|
image.debugName = debugName;
|
||||||
vkutil::addDebugLabel(device, image.image, image.debugName.c_str());
|
vkutil::addDebugLabel(device, image.image, image.debugName.c_str());
|
||||||
const std::string viewName = image.debugName + " (view)";
|
const std::string viewName = image.debugName + " (view)";
|
||||||
vkutil::addDebugLabel(device, image.imageView, viewName.c_str());
|
vkutil::addDebugLabel(device, image.imageView, viewName.c_str());
|
||||||
|
|||||||
@@ -204,13 +204,49 @@ ImageID RenderResources::loadMaterialTexture(
|
|||||||
TextureIntent intent)
|
TextureIntent intent)
|
||||||
{
|
{
|
||||||
(void)gfxDevice;
|
(void)gfxDevice;
|
||||||
const ImageID id = imageCache->loadImageFromFile(path, VK_IMAGE_USAGE_SAMPLED_BIT, true, intent);
|
const ImageID id = deferredTextureLoads > 0
|
||||||
|
? imageCache->requestImage(path, VK_IMAGE_USAGE_SAMPLED_BIT, true, intent)
|
||||||
|
: imageCache->loadImageFromFile(path, VK_IMAGE_USAGE_SAMPLED_BIT, true, intent);
|
||||||
if (id != errorImageId) {
|
if (id != errorImageId) {
|
||||||
materialTextures.insert(id);
|
materialTextures.insert(id);
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderResources::beginDeferredTextureLoads()
|
||||||
|
{
|
||||||
|
++deferredTextureLoads;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderResources::endDeferredTextureLoads()
|
||||||
|
{
|
||||||
|
if (deferredTextureLoads > 0 && --deferredTextureLoads == 0 && imageCache) {
|
||||||
|
imageCache->finishPendingLoads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredTextureLoadScope::DeferredTextureLoadScope(RenderResources* resources)
|
||||||
|
: resources(resources)
|
||||||
|
{
|
||||||
|
if (resources != nullptr) {
|
||||||
|
resources->beginDeferredTextureLoads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredTextureLoadScope::~DeferredTextureLoadScope()
|
||||||
|
{
|
||||||
|
if (resources == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
resources->endDeferredTextureLoads();
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
spdlog::error("Finishing deferred texture loads failed: {}", exception.what());
|
||||||
|
} catch (...) {
|
||||||
|
spdlog::error("Finishing deferred texture loads failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderResources::ReleaseStats RenderResources::releaseUnused(
|
RenderResources::ReleaseStats RenderResources::releaseUnused(
|
||||||
GfxDevice& gfxDevice,
|
GfxDevice& gfxDevice,
|
||||||
const std::unordered_set<MeshID>& usedMeshes,
|
const std::unordered_set<MeshID>& usedMeshes,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include <destrum/Serialization/MaterialSerialization.h>
|
#include <destrum/Serialization/MaterialSerialization.h>
|
||||||
#include <destrum/Serialization/ObjectSerialization.h>
|
#include <destrum/Serialization/ObjectSerialization.h>
|
||||||
#include <destrum/Threading/JobSystem.h>
|
#include <destrum/Threading/JobSystem.h>
|
||||||
|
#include <destrum/Graphics/Renderer.h>
|
||||||
|
#include <destrum/Util/GameState.h>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@@ -207,6 +209,10 @@ bool SceneSerializer::ConstructScene(Scene& scene, LoadResult& result,
|
|||||||
std::vector<GameObject*> registeredObjects;
|
std::vector<GameObject*> registeredObjects;
|
||||||
std::string context = "scene";
|
std::string context = "scene";
|
||||||
|
|
||||||
|
auto& gameState = GameState::GetInstance();
|
||||||
|
const DeferredTextureLoadScope textureLoads{
|
||||||
|
gameState.HasRenderer() && gameState.HasGfxDevice() ? gameState.Renderer().getResources() : nullptr};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json objects = root.at("objects");
|
json objects = root.at("objects");
|
||||||
if (root.contains("materials")) {
|
if (root.contains("materials")) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,9 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/imageLoader.h>
|
||||||
#include <destrum/Serialization/SceneSerializer.h>
|
#include <destrum/Serialization/SceneSerializer.h>
|
||||||
#include <destrum/Threading/JobSystem.h>
|
#include <destrum/Threading/JobSystem.h>
|
||||||
|
|
||||||
@@ -161,6 +163,47 @@ namespace {
|
|||||||
Jobs().Shutdown();
|
Jobs().Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testAsyncHighPriority()
|
||||||
|
{
|
||||||
|
Jobs().Init(2);
|
||||||
|
auto future = Jobs().Async([] { return std::this_thread::get_id(); }, JobPriority::High);
|
||||||
|
check(future.get() != std::this_thread::get_id(), "high priority Async should run on a worker");
|
||||||
|
Jobs().Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void testImageDecodeOnWorkersMatchesSerial()
|
||||||
|
{
|
||||||
|
const std::filesystem::path root = DESTRUM_ENGINE_ASSETS_SRC_DIR;
|
||||||
|
const std::vector<std::filesystem::path> paths{
|
||||||
|
root / "238882.png",
|
||||||
|
root / "char.jpg",
|
||||||
|
root / "flight-helmet" / "FlightHelmet_Materials_GlassPlasticMat_Normal.png",
|
||||||
|
root / "does_not_exist.png",
|
||||||
|
};
|
||||||
|
|
||||||
|
Jobs().Init(4);
|
||||||
|
std::vector<std::future<ImageData>> decoded;
|
||||||
|
for (const auto& path : paths) {
|
||||||
|
decoded.push_back(Jobs().Async([path] { return util::loadImage(path, TextureIntent::ColorSrgb); },
|
||||||
|
JobPriority::High));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < paths.size(); ++i) {
|
||||||
|
const ImageData parallel = decoded[i].get();
|
||||||
|
const ImageData serial = util::loadImage(paths[i], TextureIntent::ColorSrgb);
|
||||||
|
check(parallel.vkFormat == serial.vkFormat && parallel.width == serial.width &&
|
||||||
|
parallel.height == serial.height && parallel.byteSize == serial.byteSize,
|
||||||
|
"decoding on a worker should give the same image layout");
|
||||||
|
if (serial.pixels != nullptr) {
|
||||||
|
check(parallel.pixels != nullptr &&
|
||||||
|
std::memcmp(parallel.pixels, serial.pixels, serial.byteSize) == 0,
|
||||||
|
"decoding on a worker should give the same pixels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(decoded.size() == 4, "every image should be decoded");
|
||||||
|
Jobs().Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
void testRunsInlineWhenStopped()
|
void testRunsInlineWhenStopped()
|
||||||
{
|
{
|
||||||
check(!Jobs().IsRunning(), "job system should start stopped");
|
check(!Jobs().IsRunning(), "job system should start stopped");
|
||||||
@@ -183,4 +226,6 @@ void RunJobsTests()
|
|||||||
testMainThreadQueue();
|
testMainThreadQueue();
|
||||||
testShutdownDrainsPendingJobs();
|
testShutdownDrainsPendingJobs();
|
||||||
testLoadSceneFileAsync();
|
testLoadSceneFileAsync();
|
||||||
|
testAsyncHighPriority();
|
||||||
|
testImageDecodeOnWorkersMatchesSerial();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user