diff --git a/destrum/include/destrum/Graphics/Caches/ImageCache.h b/destrum/include/destrum/Graphics/Caches/ImageCache.h index f150fef..7be9a23 100644 --- a/destrum/include/destrum/Graphics/Caches/ImageCache.h +++ b/destrum/include/destrum/Graphics/Caches/ImageCache.h @@ -4,7 +4,9 @@ #pragma once #include +#include #include +#include #include #include @@ -12,6 +14,7 @@ #include #include #include +#include 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 data; + }; + + [[nodiscard]] std::optional 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 images; + std::vector pendingImages; std::shared_ptr> freeIds = std::make_shared>(); GfxDevice& gfxDevice; diff --git a/destrum/include/destrum/Graphics/GfxDevice.h b/destrum/include/destrum/Graphics/GfxDevice.h index cef6ec8..f3001f2 100644 --- a/destrum/include/destrum/Graphics/GfxDevice.h +++ b/destrum/include/destrum/Graphics/GfxDevice.h @@ -141,6 +141,15 @@ public: return imageManager.loadImageFromFile(path, usage, mipMap, intent); } + [[nodiscard]] std::optional 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, diff --git a/destrum/include/destrum/Graphics/Managers/ImageManager.h b/destrum/include/destrum/Graphics/Managers/ImageManager.h index 7acae63..dd9eb16 100644 --- a/destrum/include/destrum/Graphics/Managers/ImageManager.h +++ b/destrum/include/destrum/Graphics/Managers/ImageManager.h @@ -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 createImageFromData( + const ImageData& data, + VkImageUsageFlags usage, + bool mipMap, + const std::string& debugName) const; + void uploadImageData( const GPUImage& image, const void* pixelData, diff --git a/destrum/include/destrum/Graphics/RenderResources.h b/destrum/include/destrum/Graphics/RenderResources.h index 7d312bb..5602163 100644 --- a/destrum/include/destrum/Graphics/RenderResources.h +++ b/destrum/include/destrum/Graphics/RenderResources.h @@ -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 diff --git a/destrum/include/destrum/Threading/JobSystem.h b/destrum/include/destrum/Threading/JobSystem.h index 688db7a..9fd84e8 100644 --- a/destrum/include/destrum/Threading/JobSystem.h +++ b/destrum/include/destrum/Threading/JobSystem.h @@ -58,12 +58,12 @@ public: void Wait(JobCounter& counter); template - auto Async(F&& fn) -> std::future>> + auto Async(F&& fn, JobPriority priority = JobPriority::Low) -> std::future>> { using Result = std::invoke_result_t>; auto task = std::make_shared>(std::forward(fn)); auto future = task->get_future(); - Submit([task] { (*task)(); }, JobPriority::Low); + Submit([task] { (*task)(); }, priority); return future; } diff --git a/destrum/src/Graphics/Caches/ImageCache.cpp b/destrum/src/Graphics/Caches/ImageCache.cpp index 287d278..cb82052 100644 --- a/destrum/src/Graphics/Caches/ImageCache.cpp +++ b/destrum/src/Graphics/Caches/ImageCache.cpp @@ -1,9 +1,12 @@ #include #include +#include #include "spdlog/spdlog.h" +#include #include +#include #include #include @@ -16,14 +19,9 @@ ImageID ImageCache::loadImageFromFile( bool mipMap, TextureIntent intent) { - for (const auto& [id, info] : loadedImagesInfo) { - if (info.path == path && - info.intent == intent && - info.usage == usage && - info.mipMap == mipMap) - { - return id; - } + if (const auto existing = findLoadedImage(path, usage, mipMap, intent)) { + finishPendingLoad(*existing); + return *existing; } 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 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 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) { try { return addImage(getFreeImageId(), std::move(image)); @@ -113,11 +225,16 @@ const ImageCache::LoadedImageInfo* ImageCache::findLoadedImageInfo(ImageID id) c } 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; } - loadedImagesInfo.erase(id); gfxDevice.destroyImage(images[id]); // In-flight frames may still sample this bindless slot, so it only becomes @@ -146,6 +263,7 @@ ImageID ImageCache::getFreeImageId() const { } void ImageCache::destroyImages() { + pendingImages.clear(); for (auto& image: images) { gfxDevice.destroyImage(image); } diff --git a/destrum/src/Graphics/Managers/ImageManager.cpp b/destrum/src/Graphics/Managers/ImageManager.cpp index 4987e61..e78c757 100644 --- a/destrum/src/Graphics/Managers/ImageManager.cpp +++ b/destrum/src/Graphics/Managers/ImageManager.cpp @@ -130,13 +130,20 @@ std::optional ImageManager::loadImageFromFile( bool mipMap, TextureIntent intent) const { - const auto data = util::loadImage(path, intent); + return createImageFromData(util::loadImage(path, intent), usage, mipMap, path.string()); +} +std::optional ImageManager::createImageFromData( + const ImageData& data, + VkImageUsageFlags usage, + bool mipMap, + const std::string& debugName) const +{ if (data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0 || (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; } @@ -151,7 +158,7 @@ std::optional ImageManager::loadImageFromFile( spdlog::warn( "Format {} does not support linear mip generation, loading '{}' without mips", static_cast(data.vkFormat), - path.string()); + debugName); mipMap = false; } } @@ -178,7 +185,7 @@ std::optional ImageManager::loadImageFromFile( throw; } - image.debugName = path.string(); + image.debugName = debugName; vkutil::addDebugLabel(device, image.image, image.debugName.c_str()); const std::string viewName = image.debugName + " (view)"; vkutil::addDebugLabel(device, image.imageView, viewName.c_str()); diff --git a/destrum/src/Graphics/RenderResources.cpp b/destrum/src/Graphics/RenderResources.cpp index 6c85adb..60046b2 100644 --- a/destrum/src/Graphics/RenderResources.cpp +++ b/destrum/src/Graphics/RenderResources.cpp @@ -204,13 +204,49 @@ ImageID RenderResources::loadMaterialTexture( TextureIntent intent) { (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) { materialTextures.insert(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( GfxDevice& gfxDevice, const std::unordered_set& usedMeshes, diff --git a/destrum/src/Serialization/SceneSerializer.cpp b/destrum/src/Serialization/SceneSerializer.cpp index 03db435..211adee 100644 --- a/destrum/src/Serialization/SceneSerializer.cpp +++ b/destrum/src/Serialization/SceneSerializer.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include using json = nlohmann::json; @@ -207,6 +209,10 @@ bool SceneSerializer::ConstructScene(Scene& scene, LoadResult& result, std::vector registeredObjects; std::string context = "scene"; + auto& gameState = GameState::GetInstance(); + const DeferredTextureLoadScope textureLoads{ + gameState.HasRenderer() && gameState.HasGfxDevice() ? gameState.Renderer().getResources() : nullptr}; + try { json objects = root.at("objects"); if (root.contains("materials")) { diff --git a/lightkeeper/assets_src/scenes/main.scene b/lightkeeper/assets_src/scenes/main.scene index db8919f..651e943 100644 --- a/lightkeeper/assets_src/scenes/main.scene +++ b/lightkeeper/assets_src/scenes/main.scene @@ -72,87 +72,7 @@ } } }, - "2": { - "baseColor": [ - 0.8999999761581421, - 0.10000000149011612, - 0.10000000149011612 - ], - "blend": "opaque", - "emissiveColor": [ - 0.35999998450279236, - 0.04000000283122063, - 0.04000000283122063 - ], - "filtering": "linear", - "metallic": 0.0, - "name": "ButtonMaterial", - "normalScale": 1.0, - "occlusionStrength": 1.0, - "opacity": 1.0, - "roughness": 0.4000000059604645 - }, - "3": { - "baseColor": [ - 1.0, - 0.949999988079071, - 0.8500000238418579 - ], - "blend": "opaque", - "emissiveColor": [ - 1.0, - 0.8199999928474426, - 0.550000011920929 - ], - "filtering": "linear", - "metallic": 0.0, - "name": "BulbMaterial", - "normalScale": 1.0, - "occlusionStrength": 1.0, - "opacity": 1.0, - "roughness": 1.0 - }, - "4": { - "baseColor": [ - 0.8999999761581421, - 0.8999999761581421, - 0.8999999761581421 - ], - "blend": "opaque", - "emissiveColor": [ - 0.0, - 0.0, - 0.0 - ], - "filtering": "linear", - "metallic": 0.0, - "name": "LightSwitchMaterial", - "normalScale": 1.0, - "occlusionStrength": 1.0, - "opacity": 1.0, - "roughness": 0.5 - }, - "5": { - "baseColor": [ - 0.8999999761581421, - 0.10000000149011612, - 0.10000000149011612 - ], - "blend": "opaque", - "emissiveColor": [ - 0.35999998450279236, - 0.04000000283122063, - 0.04000000283122063 - ], - "filtering": "linear", - "metallic": 0.0, - "name": "WinButtonMaterial", - "normalScale": 1.0, - "occlusionStrength": 1.0, - "opacity": 1.0, - "roughness": 0.4000000059604645 - }, - "6": { + "11": { "baseColor": [ 1.0, 1.0, @@ -179,6 +99,113 @@ } } }, + "2": { + "baseColor": [ + 1.0, + 1.0, + 1.0 + ], + "blend": "transparent", + "emissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "filtering": "linear", + "metallic": 0.0, + "name": "Material", + "normalScale": 1.0, + "occlusionStrength": 1.0, + "opacity": 1.0, + "roughness": 1.0, + "textures": { + "diffuse": { + "guid": "a6c64648ac15e99110de13faea6b80e1", + "path": "game://kobe.png", + "srgb": true + } + } + }, + "3": { + "baseColor": [ + 0.8999999761581421, + 0.10000000149011612, + 0.10000000149011612 + ], + "blend": "opaque", + "emissiveColor": [ + 0.35999998450279236, + 0.04000000283122063, + 0.04000000283122063 + ], + "filtering": "linear", + "metallic": 0.0, + "name": "ButtonMaterial", + "normalScale": 1.0, + "occlusionStrength": 1.0, + "opacity": 1.0, + "roughness": 0.4000000059604645 + }, + "4": { + "baseColor": [ + 1.0, + 0.949999988079071, + 0.8500000238418579 + ], + "blend": "opaque", + "emissiveColor": [ + 1.0, + 0.8199999928474426, + 0.550000011920929 + ], + "filtering": "linear", + "metallic": 0.0, + "name": "BulbMaterial", + "normalScale": 1.0, + "occlusionStrength": 1.0, + "opacity": 1.0, + "roughness": 1.0 + }, + "5": { + "baseColor": [ + 0.8999999761581421, + 0.8999999761581421, + 0.8999999761581421 + ], + "blend": "opaque", + "emissiveColor": [ + 0.0, + 0.0, + 0.0 + ], + "filtering": "linear", + "metallic": 0.0, + "name": "LightSwitchMaterial", + "normalScale": 1.0, + "occlusionStrength": 1.0, + "opacity": 1.0, + "roughness": 0.5 + }, + "6": { + "baseColor": [ + 0.8999999761581421, + 0.10000000149011612, + 0.10000000149011612 + ], + "blend": "opaque", + "emissiveColor": [ + 0.35999998450279236, + 0.04000000283122063, + 0.04000000283122063 + ], + "filtering": "linear", + "metallic": 0.0, + "name": "WinButtonMaterial", + "normalScale": 1.0, + "occlusionStrength": 1.0, + "opacity": 1.0, + "roughness": 0.4000000059604645 + }, "7": { "baseColor": [ 1.0, @@ -284,7 +311,7 @@ "type": "LightComponent" } ], - "id": 1, + "id": 880, "name": "Sun", "parent": 0, "transform": { @@ -324,7 +351,7 @@ "type": "MeshRendererComponent" } ], - "id": 2, + "id": 881, "name": "GroundPlane", "parent": 0, "transform": { @@ -380,7 +407,7 @@ "type": "Rigidbody" } ], - "id": 3, + "id": 882, "name": "GroundCollider", "parent": 0, "transform": { @@ -447,7 +474,7 @@ "type": "Rigidbody" } ], - "id": 4, + "id": 883, "name": "Parkour_Step", "parent": 0, "transform": { @@ -514,7 +541,7 @@ "type": "Rigidbody" } ], - "id": 5, + "id": 884, "name": "Parkour_Block", "parent": 0, "transform": { @@ -581,7 +608,7 @@ "type": "Rigidbody" } ], - "id": 6, + "id": 885, "name": "Parkour_Pillar1", "parent": 0, "transform": { @@ -648,7 +675,7 @@ "type": "Rigidbody" } ], - "id": 7, + "id": 886, "name": "Parkour_Pillar2", "parent": 0, "transform": { @@ -715,7 +742,7 @@ "type": "Rigidbody" } ], - "id": 8, + "id": 887, "name": "Parkour_Beam", "parent": 0, "transform": { @@ -782,7 +809,7 @@ "type": "Rigidbody" } ], - "id": 9, + "id": 888, "name": "Parkour_Goal", "parent": 0, "transform": { @@ -849,7 +876,7 @@ "type": "Rigidbody" } ], - "id": 10, + "id": 889, "name": "Wall", "parent": 0, "transform": { @@ -916,7 +943,7 @@ "type": "Rigidbody" } ], - "id": 11, + "id": 890, "name": "LowWall", "parent": 0, "transform": { @@ -983,7 +1010,7 @@ "type": "Rigidbody" } ], - "id": 12, + "id": 891, "name": "Crate0", "parent": 0, "transform": { @@ -1050,7 +1077,7 @@ "type": "Rigidbody" } ], - "id": 13, + "id": 892, "name": "Crate1", "parent": 0, "transform": { @@ -1072,6 +1099,42 @@ ] } }, + { + "active": true, + "components": [ + { + "data": { + "material": { + "localMaterial": "2" + }, + "mesh": "prim://plane?size=1,1" + }, + "enabled": true, + "type": "MeshRendererComponent" + } + ], + "id": 893, + "name": "Decal", + "parent": 892, + "transform": { + "position": [ + -0.00057220458984375, + 0.03354451060295105, + -0.4096747636795044 + ], + "rotation": [ + -3.09084171590257e-08, + -0.7071043252944946, + 0.7071092128753662, + 3.0908708481547364e-08 + ], + "scale": [ + 0.7482033967971802, + 0.748164176940918, + 0.7481649518013 + ] + } + }, { "active": true, "components": [ @@ -1117,7 +1180,7 @@ "type": "Rigidbody" } ], - "id": 14, + "id": 894, "name": "Crate2", "parent": 0, "transform": { @@ -1184,13 +1247,13 @@ "type": "Rigidbody" } ], - "id": 15, + "id": 895, "name": "Crate3", "parent": 0, "transform": { "position": [ -2.5, - 1.8300000429153442, + 2.52571439743042, 2.0 ], "rotation": [ @@ -1209,7 +1272,7 @@ { "active": true, "components": [], - "id": 16, + "id": 896, "name": "FlightHelmet", "parent": 0, "transform": { @@ -1251,9 +1314,9 @@ "type": "MeshRendererComponent" } ], - "id": 17, + "id": 897, "name": "Lenses_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1293,9 +1356,9 @@ "type": "MeshRendererComponent" } ], - "id": 18, + "id": 898, "name": "LeatherParts_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1335,9 +1398,9 @@ "type": "MeshRendererComponent" } ], - "id": 19, + "id": 899, "name": "MetalParts_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1377,9 +1440,9 @@ "type": "MeshRendererComponent" } ], - "id": 20, + "id": 900, "name": "GlassPlastic_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1419,9 +1482,9 @@ "type": "MeshRendererComponent" } ], - "id": 21, + "id": 901, "name": "RubberWood_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1461,9 +1524,9 @@ "type": "MeshRendererComponent" } ], - "id": 22, + "id": 902, "name": "Hose_low", - "parent": 16, + "parent": 896, "transform": { "position": [ 0.0, @@ -1504,7 +1567,7 @@ "type": "LightComponent" } ], - "id": 23, + "id": 903, "name": "HelmetSpotlight", "parent": 0, "transform": { @@ -1557,7 +1620,7 @@ "type": "Rigidbody" } ], - "id": 24, + "id": 904, "name": "Tree", "parent": 0, "transform": { @@ -1599,9 +1662,9 @@ "type": "MeshRendererComponent" } ], - "id": 25, + "id": 905, "name": "model", - "parent": 24, + "parent": 904, "transform": { "position": [ 0.0, @@ -1663,7 +1726,7 @@ "type": "TriggerLoggerComponent" } ], - "id": 26, + "id": 906, "name": "TriggerBox", "parent": 0, "transform": { @@ -1730,7 +1793,7 @@ "type": "Rigidbody" } ], - "id": 27, + "id": 907, "name": "DoorFrame_Left", "parent": 0, "transform": { @@ -1797,7 +1860,7 @@ "type": "Rigidbody" } ], - "id": 28, + "id": 908, "name": "DoorFrame_Right", "parent": 0, "transform": { @@ -1864,7 +1927,7 @@ "type": "Rigidbody" } ], - "id": 29, + "id": 909, "name": "DoorFrame_Top", "parent": 0, "transform": { @@ -1946,7 +2009,7 @@ "type": "Door" } ], - "id": 30, + "id": 910, "name": "Door", "parent": 0, "transform": { @@ -2013,7 +2076,7 @@ "type": "Rigidbody" } ], - "id": 31, + "id": 911, "name": "ButtonPedestal", "parent": 0, "transform": { @@ -2041,7 +2104,7 @@ { "data": { "material": { - "localMaterial": "2" + "localMaterial": "3" }, "mesh": "prim://box?size=0.25,0.1,0.25" }, @@ -2084,14 +2147,14 @@ "cycleColors": true, "pressCount": 0, "targets": [ - 30 + 910 ] }, "enabled": true, "type": "InteractableButton" } ], - "id": 32, + "id": 912, "name": "Button", "parent": 0, "transform": { @@ -2116,7 +2179,7 @@ { "active": true, "components": [], - "id": 33, + "id": 913, "name": "House", "parent": 0, "transform": { @@ -2183,9 +2246,9 @@ "type": "Rigidbody" } ], - "id": 34, + "id": 914, "name": "House_Floor", - "parent": 33, + "parent": 913, "transform": { "position": [ 0.0, @@ -2250,9 +2313,9 @@ "type": "Rigidbody" } ], - "id": 35, + "id": 915, "name": "House_FrontWall_Left", - "parent": 33, + "parent": 913, "transform": { "position": [ -3.125, @@ -2317,9 +2380,9 @@ "type": "Rigidbody" } ], - "id": 36, + "id": 916, "name": "House_FrontWall_Right", - "parent": 33, + "parent": 913, "transform": { "position": [ 1.625, @@ -2384,9 +2447,9 @@ "type": "Rigidbody" } ], - "id": 37, + "id": 917, "name": "House_FrontWall_Above", - "parent": 33, + "parent": 913, "transform": { "position": [ -1.5, @@ -2451,9 +2514,9 @@ "type": "Rigidbody" } ], - "id": 38, + "id": 918, "name": "House_BackWall_Left", - "parent": 33, + "parent": 913, "transform": { "position": [ -1.6750000715255737, @@ -2518,9 +2581,9 @@ "type": "Rigidbody" } ], - "id": 39, + "id": 919, "name": "House_BackWall_Right", - "parent": 33, + "parent": 913, "transform": { "position": [ 3.1750001907348633, @@ -2585,9 +2648,9 @@ "type": "Rigidbody" } ], - "id": 40, + "id": 920, "name": "House_BackWall_Below", - "parent": 33, + "parent": 913, "transform": { "position": [ 1.5, @@ -2652,9 +2715,9 @@ "type": "Rigidbody" } ], - "id": 41, + "id": 921, "name": "House_BackWall_Above", - "parent": 33, + "parent": 913, "transform": { "position": [ 1.5, @@ -2719,9 +2782,9 @@ "type": "Rigidbody" } ], - "id": 42, + "id": 922, "name": "House_LeftWall", - "parent": 33, + "parent": 913, "transform": { "position": [ -4.0, @@ -2786,9 +2849,9 @@ "type": "Rigidbody" } ], - "id": 43, + "id": 923, "name": "House_RightWall_Left", - "parent": 33, + "parent": 913, "transform": { "position": [ 4.0, @@ -2853,9 +2916,9 @@ "type": "Rigidbody" } ], - "id": 44, + "id": 924, "name": "House_RightWall_Right", - "parent": 33, + "parent": 913, "transform": { "position": [ 4.0, @@ -2920,9 +2983,9 @@ "type": "Rigidbody" } ], - "id": 45, + "id": 925, "name": "House_RightWall_Below", - "parent": 33, + "parent": 913, "transform": { "position": [ 4.0, @@ -2987,9 +3050,9 @@ "type": "Rigidbody" } ], - "id": 46, + "id": 926, "name": "House_RightWall_Above", - "parent": 33, + "parent": 913, "transform": { "position": [ 4.0, @@ -3054,9 +3117,9 @@ "type": "Rigidbody" } ], - "id": 47, + "id": 927, "name": "House_Roof", - "parent": 33, + "parent": 913, "transform": { "position": [ 0.0, @@ -3082,7 +3145,7 @@ { "data": { "material": { - "localMaterial": "3" + "localMaterial": "4" }, "mesh": "prim://box?size=0.35,0.12,0.35" }, @@ -3114,9 +3177,9 @@ "type": "LightToggle" } ], - "id": 48, + "id": 928, "name": "CeilingLight", - "parent": 33, + "parent": 913, "transform": { "position": [ 0.0, @@ -3142,7 +3205,7 @@ { "data": { "material": { - "localMaterial": "4" + "localMaterial": "5" }, "mesh": "prim://box?size=0.12,0.2,0.06" }, @@ -3185,16 +3248,16 @@ "cycleColors": false, "pressCount": 0, "targets": [ - 48 + 928 ] }, "enabled": true, "type": "InteractableButton" } ], - "id": 49, + "id": 929, "name": "LightSwitch", - "parent": 33, + "parent": 913, "transform": { "position": [ -0.4999999701976776, @@ -3217,9 +3280,9 @@ { "active": true, "components": [], - "id": 50, + "id": 930, "name": "Sofa", - "parent": 33, + "parent": 913, "transform": { "position": [ -3.700000047683716, @@ -3259,9 +3322,9 @@ "type": "MeshRendererComponent" } ], - "id": 51, + "id": 931, "name": "loungeSofa_1", - "parent": 50, + "parent": 930, "transform": { "position": [ 0.0, @@ -3301,9 +3364,9 @@ "type": "MeshRendererComponent" } ], - "id": 52, + "id": 932, "name": "loungeSofa_1", - "parent": 50, + "parent": 930, "transform": { "position": [ 0.0, @@ -3343,9 +3406,9 @@ "type": "MeshRendererComponent" } ], - "id": 53, + "id": 933, "name": "loungeSofa", - "parent": 50, + "parent": 930, "transform": { "position": [ 0.0, @@ -3385,9 +3448,9 @@ "type": "MeshRendererComponent" } ], - "id": 54, + "id": 934, "name": "loungeSofa", - "parent": 50, + "parent": 930, "transform": { "position": [ 0.0, @@ -3452,7 +3515,7 @@ "type": "Rigidbody" } ], - "id": 55, + "id": 935, "name": "PlatformRamp", "parent": 0, "transform": { @@ -3533,7 +3596,7 @@ "type": "MovingPlatform" } ], - "id": 56, + "id": 936, "name": "MovingPlatform", "parent": 0, "transform": { @@ -3600,7 +3663,7 @@ "type": "Rigidbody" } ], - "id": 57, + "id": 937, "name": "PlatformLanding", "parent": 0, "transform": { @@ -3625,7 +3688,7 @@ { "active": true, "components": [], - "id": 58, + "id": 938, "name": "WinCourse", "parent": 0, "transform": { @@ -3692,9 +3755,9 @@ "type": "Rigidbody" } ], - "id": 59, + "id": 939, "name": "WinCourse_Start", - "parent": 58, + "parent": 938, "transform": { "position": [ -2.5, @@ -3759,9 +3822,9 @@ "type": "Checkpoint" } ], - "id": 60, + "id": 940, "name": "WinCourse_Checkpoint1", - "parent": 58, + "parent": 938, "transform": { "position": [ -2.5, @@ -3826,9 +3889,9 @@ "type": "Rigidbody" } ], - "id": 61, + "id": 941, "name": "WinCourse_Platform1", - "parent": 58, + "parent": 938, "transform": { "position": [ -8.0, @@ -3893,9 +3956,9 @@ "type": "Rigidbody" } ], - "id": 62, + "id": 942, "name": "WinCourse_Platform2", - "parent": 58, + "parent": 938, "transform": { "position": [ -12.150001525878906, @@ -3960,9 +4023,9 @@ "type": "Rigidbody" } ], - "id": 63, + "id": 943, "name": "WinCourse_Platform3", - "parent": 58, + "parent": 938, "transform": { "position": [ -16.25, @@ -4027,9 +4090,9 @@ "type": "Rigidbody" } ], - "id": 64, + "id": 944, "name": "WinCourse_Platform4", - "parent": 58, + "parent": 938, "transform": { "position": [ -20.39999771118164, @@ -4094,9 +4157,9 @@ "type": "Rigidbody" } ], - "id": 65, + "id": 945, "name": "WinCourse_Platform5", - "parent": 58, + "parent": 938, "transform": { "position": [ -24.699996948242188, @@ -4161,9 +4224,9 @@ "type": "Checkpoint" } ], - "id": 66, + "id": 946, "name": "WinCourse_Checkpoint2", - "parent": 58, + "parent": 938, "transform": { "position": [ -24.699996948242188, @@ -4228,9 +4291,9 @@ "type": "Rigidbody" } ], - "id": 67, + "id": 947, "name": "WinCourse_Platform6", - "parent": 58, + "parent": 938, "transform": { "position": [ -29.29999542236328, @@ -4295,9 +4358,9 @@ "type": "Rigidbody" } ], - "id": 68, + "id": 948, "name": "WinCourse_Platform7", - "parent": 58, + "parent": 938, "transform": { "position": [ -32.999996185302734, @@ -4362,9 +4425,9 @@ "type": "Rigidbody" } ], - "id": 69, + "id": 949, "name": "WinCourse_Platform8", - "parent": 58, + "parent": 938, "transform": { "position": [ -36.54999542236328, @@ -4429,9 +4492,9 @@ "type": "Rigidbody" } ], - "id": 70, + "id": 950, "name": "WinCourse_Platform9", - "parent": 58, + "parent": 938, "transform": { "position": [ -40.24999237060547, @@ -4496,9 +4559,9 @@ "type": "Rigidbody" } ], - "id": 71, + "id": 951, "name": "WinCourse_Platform10", - "parent": 58, + "parent": 938, "transform": { "position": [ -44.149986267089844, @@ -4563,9 +4626,9 @@ "type": "Rigidbody" } ], - "id": 72, + "id": 952, "name": "WinCourse_Platform11", - "parent": 58, + "parent": 938, "transform": { "position": [ -48.49998474121094, @@ -4630,9 +4693,9 @@ "type": "Checkpoint" } ], - "id": 73, + "id": 953, "name": "WinCourse_Checkpoint3", - "parent": 58, + "parent": 938, "transform": { "position": [ -48.49998474121094, @@ -4697,9 +4760,9 @@ "type": "Rigidbody" } ], - "id": 74, + "id": 954, "name": "WinCourse_Platform12", - "parent": 58, + "parent": 938, "transform": { "position": [ -54.199981689453125, @@ -4764,9 +4827,9 @@ "type": "Rigidbody" } ], - "id": 75, + "id": 955, "name": "WinCourse_Platform13", - "parent": 58, + "parent": 938, "transform": { "position": [ -61.09998321533203, @@ -4831,9 +4894,9 @@ "type": "Rigidbody" } ], - "id": 76, + "id": 956, "name": "WinCourse_Platform14", - "parent": 58, + "parent": 938, "transform": { "position": [ -66.99998474121094, @@ -4898,9 +4961,9 @@ "type": "Rigidbody" } ], - "id": 77, + "id": 957, "name": "WinCourse_Platform15", - "parent": 58, + "parent": 938, "transform": { "position": [ -73.34998321533203, @@ -4965,9 +5028,9 @@ "type": "Rigidbody" } ], - "id": 78, + "id": 958, "name": "WinCourse_Platform16", - "parent": 58, + "parent": 938, "transform": { "position": [ -78.84998321533203, @@ -5032,9 +5095,9 @@ "type": "Rigidbody" } ], - "id": 79, + "id": 959, "name": "WinCourse_Platform17", - "parent": 58, + "parent": 938, "transform": { "position": [ -83.99998474121094, @@ -5099,9 +5162,9 @@ "type": "Rigidbody" } ], - "id": 80, + "id": 960, "name": "WinCourse_Platform18", - "parent": 58, + "parent": 938, "transform": { "position": [ -89.09998321533203, @@ -5166,9 +5229,9 @@ "type": "Checkpoint" } ], - "id": 81, + "id": 961, "name": "WinCourse_Checkpoint4", - "parent": 58, + "parent": 938, "transform": { "position": [ -89.09998321533203, @@ -5247,9 +5310,9 @@ "type": "MovingPlatform" } ], - "id": 82, + "id": 962, "name": "WinCourse_Platform19", - "parent": 58, + "parent": 938, "transform": { "position": [ -92.44998168945313, @@ -5314,9 +5377,9 @@ "type": "Rigidbody" } ], - "id": 83, + "id": 963, "name": "WinCourse_Platform20", - "parent": 58, + "parent": 938, "transform": { "position": [ -104.29998779296875, @@ -5395,9 +5458,9 @@ "type": "MovingPlatform" } ], - "id": 84, + "id": 964, "name": "WinCourse_Platform21", - "parent": 58, + "parent": 938, "transform": { "position": [ -108.24998474121094, @@ -5476,9 +5539,9 @@ "type": "MovingPlatform" } ], - "id": 85, + "id": 965, "name": "WinCourse_Platform22", - "parent": 58, + "parent": 938, "transform": { "position": [ -111.94998168945313, @@ -5557,9 +5620,9 @@ "type": "MovingPlatform" } ], - "id": 86, + "id": 966, "name": "WinCourse_Platform23", - "parent": 58, + "parent": 938, "transform": { "position": [ -115.64997863769531, @@ -5624,9 +5687,9 @@ "type": "Rigidbody" } ], - "id": 87, + "id": 967, "name": "WinCourse_Platform24", - "parent": 58, + "parent": 938, "transform": { "position": [ -120.0999755859375, @@ -5691,9 +5754,9 @@ "type": "Checkpoint" } ], - "id": 88, + "id": 968, "name": "WinCourse_Checkpoint5", - "parent": 58, + "parent": 938, "transform": { "position": [ -120.0999755859375, @@ -5772,9 +5835,9 @@ "type": "MovingPlatform" } ], - "id": 89, + "id": 969, "name": "WinCourse_Platform25", - "parent": 58, + "parent": 938, "transform": { "position": [ -123.19998168945313, @@ -5839,9 +5902,9 @@ "type": "Rigidbody" } ], - "id": 90, + "id": 970, "name": "WinCourse_Platform26", - "parent": 58, + "parent": 938, "transform": { "position": [ -126.14997863769531, @@ -5906,9 +5969,9 @@ "type": "Rigidbody" } ], - "id": 91, + "id": 971, "name": "WinCourse_Platform27", - "parent": 58, + "parent": 938, "transform": { "position": [ -130.0999755859375, @@ -5973,9 +6036,9 @@ "type": "Rigidbody" } ], - "id": 92, + "id": 972, "name": "WinCourse_Platform28", - "parent": 58, + "parent": 938, "transform": { "position": [ -133.8499755859375, @@ -6040,9 +6103,9 @@ "type": "Rigidbody" } ], - "id": 93, + "id": 973, "name": "WinCourse_Platform29", - "parent": 58, + "parent": 938, "transform": { "position": [ -137.69998168945313, @@ -6107,9 +6170,9 @@ "type": "Rigidbody" } ], - "id": 94, + "id": 974, "name": "WinCourse_Platform30", - "parent": 58, + "parent": 938, "transform": { "position": [ -141.69998168945313, @@ -6174,9 +6237,9 @@ "type": "Rigidbody" } ], - "id": 95, + "id": 975, "name": "WinCourse_Platform31", - "parent": 58, + "parent": 938, "transform": { "position": [ -145.99998474121094, @@ -6241,9 +6304,9 @@ "type": "Checkpoint" } ], - "id": 96, + "id": 976, "name": "WinCourse_Checkpoint6", - "parent": 58, + "parent": 938, "transform": { "position": [ -145.99998474121094, @@ -6322,9 +6385,9 @@ "type": "MovingPlatform" } ], - "id": 97, + "id": 977, "name": "WinCourse_Platform32", - "parent": 58, + "parent": 938, "transform": { "position": [ -150.49998474121094, @@ -6389,9 +6452,9 @@ "type": "Rigidbody" } ], - "id": 98, + "id": 978, "name": "WinCourse_Platform33", - "parent": 58, + "parent": 938, "transform": { "position": [ -154.1999969482422, @@ -6456,9 +6519,9 @@ "type": "Rigidbody" } ], - "id": 99, + "id": 979, "name": "WinCourse_Platform34", - "parent": 58, + "parent": 938, "transform": { "position": [ -158.49998474121094, @@ -6523,9 +6586,9 @@ "type": "Rigidbody" } ], - "id": 100, + "id": 980, "name": "WinCourse_Platform35", - "parent": 58, + "parent": 938, "transform": { "position": [ -162.8999786376953, @@ -6597,9 +6660,9 @@ "type": "WinGoal" } ], - "id": 101, + "id": 981, "name": "WinCourse_Goal", - "parent": 58, + "parent": 938, "transform": { "position": [ -167.7999725341797, @@ -6664,9 +6727,9 @@ "type": "Rigidbody" } ], - "id": 102, + "id": 982, "name": "WinCourse_ButtonPedestal", - "parent": 58, + "parent": 938, "transform": { "position": [ -168.7999725341797, @@ -6692,7 +6755,7 @@ { "data": { "material": { - "localMaterial": "5" + "localMaterial": "6" }, "mesh": "prim://box?size=0.25,0.1,0.25" }, @@ -6735,16 +6798,16 @@ "cycleColors": true, "pressCount": 0, "targets": [ - 101 + 981 ] }, "enabled": true, "type": "InteractableButton" } ], - "id": 103, + "id": 983, "name": "WinCourse_Button", - "parent": 58, + "parent": 938, "transform": { "position": [ -168.7999725341797, @@ -6785,9 +6848,9 @@ "type": "LightComponent" } ], - "id": 104, + "id": 984, "name": "WinCourse_GoalLight", - "parent": 58, + "parent": 938, "transform": { "position": [ -167.7999725341797, @@ -6872,7 +6935,7 @@ "type": "PlayerRespawn" } ], - "id": 105, + "id": 985, "name": "Player", "parent": 0, "transform": { @@ -6915,8 +6978,8 @@ 1.0, 0.800000011920929 ], - "crosshair": 107, - "focusIndicator": 110, + "crosshair": 987, + "focusIndicator": 990, "focusedColor": [ 1.0, 0.800000011920929, @@ -6924,7 +6987,7 @@ 1.0 ], "focusedSize": 16.0, - "player": 105, + "player": 985, "size": 10.0 }, "enabled": true, @@ -6932,14 +6995,14 @@ }, { "data": { - "goal": 101, - "screen": 113 + "goal": 981, + "screen": 993 }, "enabled": true, "type": "WinScreenUI" } ], - "id": 106, + "id": 986, "name": "HUD", "parent": 0, "transform": { @@ -6991,9 +7054,9 @@ "type": "RectTransform" } ], - "id": 107, + "id": 987, "name": "Crosshair", - "parent": 106, + "parent": 986, "transform": { "position": [ 0.0, @@ -7060,9 +7123,9 @@ "type": "UIImage" } ], - "id": 108, + "id": 988, "name": "Crosshair_Horizontal", - "parent": 107, + "parent": 987, "transform": { "position": [ 0.0, @@ -7129,9 +7192,9 @@ "type": "UIImage" } ], - "id": 109, + "id": 989, "name": "Crosshair_Vertical", - "parent": 107, + "parent": 987, "transform": { "position": [ 0.0, @@ -7181,9 +7244,9 @@ "type": "RectTransform" } ], - "id": 110, + "id": 990, "name": "FocusIndicator", - "parent": 107, + "parent": 987, "transform": { "position": [ 0.0, @@ -7250,9 +7313,9 @@ "type": "UIImage" } ], - "id": 111, + "id": 991, "name": "FocusRing", - "parent": 110, + "parent": 990, "transform": { "position": [ 0.0, @@ -7326,9 +7389,9 @@ "type": "UIText" } ], - "id": 112, + "id": 992, "name": "InteractPrompt", - "parent": 110, + "parent": 990, "transform": { "position": [ 0.0, @@ -7395,9 +7458,9 @@ "type": "UIImage" } ], - "id": 113, + "id": 993, "name": "WinScreen", - "parent": 106, + "parent": 986, "transform": { "position": [ 0.0, @@ -7471,9 +7534,9 @@ "type": "UIText" } ], - "id": 114, + "id": 994, "name": "WinScreen_Title", - "parent": 113, + "parent": 993, "transform": { "position": [ 0.0, @@ -7512,7 +7575,7 @@ "type": "MeshRendererComponent" } ], - "id": 115, + "id": 995, "name": "GameObject", "parent": 0, "transform": { @@ -7579,7 +7642,7 @@ "type": "Rigidbody" } ], - "id": 116, + "id": 996, "name": "Parkour_Goal", "parent": 0, "transform": { @@ -7604,7 +7667,7 @@ { "active": true, "components": [], - "id": 117, + "id": 997, "name": "capybara", "parent": 0, "transform": { @@ -7646,9 +7709,9 @@ "type": "MeshRendererComponent" } ], - "id": 118, + "id": 998, "name": "Capybara", - "parent": 117, + "parent": 997, "transform": { "position": [ 0.0, @@ -7688,9 +7751,9 @@ "type": "MeshRendererComponent" } ], - "id": 119, + "id": 999, "name": "Capybara", - "parent": 117, + "parent": 997, "transform": { "position": [ 0.0, @@ -7730,7 +7793,7 @@ "type": "AudioSource" } ], - "id": 120, + "id": 1000, "name": "Radio", "parent": 0, "transform": { @@ -7752,42 +7815,6 @@ ] } }, - { - "active": true, - "components": [ - { - "data": { - "material": { - "localMaterial": "6" - }, - "mesh": "prim://plane?size=1,1" - }, - "enabled": true, - "type": "MeshRendererComponent" - } - ], - "id": 121, - "name": "Decal", - "parent": 0, - "transform": { - "position": [ - -4.743542194366455, - 0.3649294674396515, - 0.07782143354415894 - ], - "rotation": [ - 0.5, - 0.5, - -0.5, - 0.5 - ], - "scale": [ - 1.0, - 0.9999995231628418, - 0.9999995827674866 - ] - } - }, { "active": true, "components": [ @@ -7802,14 +7829,14 @@ "type": "MeshRendererComponent" } ], - "id": 122, + "id": 1001, "name": "Decal", "parent": 0, "transform": { "position": [ -4.743542194366455, - 1.1351869106292725, - 2.188525676727295 + 0.3649294674396515, + 0.07782143354415894 ], "rotation": [ 0.5, @@ -7838,14 +7865,14 @@ "type": "MeshRendererComponent" } ], - "id": 123, + "id": 1002, "name": "Decal", "parent": 0, "transform": { "position": [ -4.743542194366455, - 1.5165855884552002, - 0.601291835308075 + 1.1351869106292725, + 2.188525676727295 ], "rotation": [ 0.5, @@ -7874,14 +7901,14 @@ "type": "MeshRendererComponent" } ], - "id": 124, + "id": 1003, "name": "Decal", "parent": 0, "transform": { "position": [ -4.743542194366455, - 0.5876348614692688, - -1.545495867729187 + 1.5165855884552002, + 0.601291835308075 ], "rotation": [ 0.5, @@ -7910,7 +7937,43 @@ "type": "MeshRendererComponent" } ], - "id": 125, + "id": 1004, + "name": "Decal", + "parent": 0, + "transform": { + "position": [ + -4.743542194366455, + 0.5876348614692688, + -1.545495867729187 + ], + "rotation": [ + 0.5, + 0.5, + -0.5, + 0.5 + ], + "scale": [ + 1.0, + 0.9999995231628418, + 0.9999995827674866 + ] + } + }, + { + "active": true, + "components": [ + { + "data": { + "material": { + "localMaterial": "11" + }, + "mesh": "prim://plane?size=1,1" + }, + "enabled": true, + "type": "MeshRendererComponent" + } + ], + "id": 1005, "name": "Decal", "parent": 0, "transform": { diff --git a/tests/jobs_tests.cpp b/tests/jobs_tests.cpp index 91256e7..d941932 100644 --- a/tests/jobs_tests.cpp +++ b/tests/jobs_tests.cpp @@ -5,7 +5,9 @@ #include #include #include +#include +#include #include #include @@ -161,6 +163,47 @@ namespace { 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 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> 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() { check(!Jobs().IsRunning(), "job system should start stopped"); @@ -183,4 +226,6 @@ void RunJobsTests() testMainThreadQueue(); testShutdownDrainsPendingJobs(); testLoadSceneFileAsync(); + testAsyncHighPriority(); + testImageDecodeOnWorkersMatchesSerial(); }