ADD: async glTF loading
This commit is contained in:
298
src/core/assets/async_loader.cpp
Normal file
298
src/core/assets/async_loader.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "async_loader.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "manager.h"
|
||||
#include "core/engine.h"
|
||||
#include "scene/vk_scene.h"
|
||||
|
||||
AsyncAssetLoader::AsyncAssetLoader() = default;
|
||||
|
||||
AsyncAssetLoader::~AsyncAssetLoader()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::init(VulkanEngine *engine, AssetManager *assets, TextureCache *textures, uint32_t worker_count)
|
||||
{
|
||||
_engine = engine;
|
||||
_assets = assets;
|
||||
_textures = textures;
|
||||
|
||||
if (worker_count == 0)
|
||||
{
|
||||
worker_count = 1;
|
||||
}
|
||||
start_workers(worker_count);
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::shutdown()
|
||||
{
|
||||
stop_workers();
|
||||
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
_jobs.clear();
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::start_workers(uint32_t count)
|
||||
{
|
||||
if (_running.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_running.store(true, std::memory_order_release);
|
||||
_workers.reserve(count);
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
_workers.emplace_back([this]() { worker_loop(); });
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::stop_workers()
|
||||
{
|
||||
if (!_running.exchange(false, std::memory_order_acq_rel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_jobs_cv.notify_all();
|
||||
for (auto &t : _workers)
|
||||
{
|
||||
if (t.joinable())
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
_workers.clear();
|
||||
}
|
||||
|
||||
AsyncAssetLoader::JobID AsyncAssetLoader::load_gltf_async(const std::string &scene_name,
|
||||
const std::string &model_relative_path,
|
||||
const glm::mat4 &transform)
|
||||
{
|
||||
if (!_assets)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
JobID id = _next_id.fetch_add(1, std::memory_order_relaxed);
|
||||
std::unique_ptr<Job> job = std::make_unique<Job>(/*args...*/);
|
||||
job->id = id;
|
||||
job->scene_name = scene_name;
|
||||
job->model_relative_path = model_relative_path;
|
||||
job->transform = transform;
|
||||
job->progress.store(0.0f, std::memory_order_relaxed);
|
||||
job->state.store(JobState::Pending, std::memory_order_relaxed);
|
||||
|
||||
// Prefetch textures on the main thread and remember handles for progress.
|
||||
if (_textures)
|
||||
{
|
||||
AssetManager::GLTFTexturePrefetchResult pref = _assets->prefetchGLTFTexturesWithHandles(model_relative_path);
|
||||
job->texture_handles = std::move(pref.handles);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
_jobs.emplace(id, std::move(job));
|
||||
_queue.push_back(id);
|
||||
}
|
||||
_jobs_cv.notify_one();
|
||||
return id;
|
||||
}
|
||||
|
||||
bool AsyncAssetLoader::get_job_status(JobID id, JobState &out_state, float &out_progress, std::string *out_error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
auto it = _jobs.find(id);
|
||||
if (it == _jobs.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Job &job = *it->second;
|
||||
JobState state = job.state.load(std::memory_order_acquire);
|
||||
float gltf_progress = job.progress.load(std::memory_order_relaxed);
|
||||
|
||||
float tex_fraction = 0.0f;
|
||||
if (_textures && !job.texture_handles.empty())
|
||||
{
|
||||
size_t total = job.texture_handles.size();
|
||||
size_t resident = 0;
|
||||
for (TextureCache::TextureHandle h : job.texture_handles)
|
||||
{
|
||||
if (h == TextureCache::InvalidHandle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto st = _textures->state(h);
|
||||
if (st == TextureCache::EntryState::Resident)
|
||||
{
|
||||
resident++;
|
||||
}
|
||||
}
|
||||
if (total > 0)
|
||||
{
|
||||
tex_fraction = static_cast<float>(resident) / static_cast<float>(total);
|
||||
}
|
||||
}
|
||||
|
||||
float combined = gltf_progress;
|
||||
if (tex_fraction > 0.0f)
|
||||
{
|
||||
combined = 0.7f * gltf_progress + 0.3f * tex_fraction;
|
||||
}
|
||||
if (state == JobState::Completed || state == JobState::Failed)
|
||||
{
|
||||
combined = 1.0f;
|
||||
}
|
||||
|
||||
out_state = state;
|
||||
out_progress = combined;
|
||||
if (out_error)
|
||||
{
|
||||
*out_error = job.error;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::debug_snapshot(std::vector<DebugJob> &out_jobs)
|
||||
{
|
||||
out_jobs.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
out_jobs.reserve(_jobs.size());
|
||||
|
||||
for (auto &[id, jobPtr] : _jobs)
|
||||
{
|
||||
const Job &job = *jobPtr;
|
||||
|
||||
float gltf_progress = job.progress.load(std::memory_order_relaxed);
|
||||
JobState state = job.state.load(std::memory_order_acquire);
|
||||
|
||||
float tex_fraction = 0.0f;
|
||||
size_t tex_total = job.texture_handles.size();
|
||||
size_t tex_resident = 0;
|
||||
if (_textures && tex_total > 0)
|
||||
{
|
||||
for (TextureCache::TextureHandle h : job.texture_handles)
|
||||
{
|
||||
if (h == TextureCache::InvalidHandle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_textures->state(h) == TextureCache::EntryState::Resident)
|
||||
{
|
||||
tex_resident++;
|
||||
}
|
||||
}
|
||||
if (tex_total > 0)
|
||||
{
|
||||
tex_fraction = static_cast<float>(tex_resident) / static_cast<float>(tex_total);
|
||||
}
|
||||
}
|
||||
|
||||
float combined = gltf_progress;
|
||||
if (tex_fraction > 0.0f)
|
||||
{
|
||||
combined = 0.7f * gltf_progress + 0.3f * tex_fraction;
|
||||
}
|
||||
if (state == JobState::Completed || state == JobState::Failed)
|
||||
{
|
||||
combined = 1.0f;
|
||||
}
|
||||
|
||||
DebugJob dbg{};
|
||||
dbg.id = job.id;
|
||||
dbg.state = state;
|
||||
dbg.progress = combined;
|
||||
dbg.scene_name = job.scene_name;
|
||||
dbg.model_relative_path = job.model_relative_path;
|
||||
dbg.texture_count = tex_total;
|
||||
dbg.textures_resident = tex_resident;
|
||||
|
||||
out_jobs.push_back(std::move(dbg));
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::pump_main_thread(SceneManager &scene)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
|
||||
for (auto &[id, job] : _jobs)
|
||||
{
|
||||
JobState state = job->state.load(std::memory_order_acquire);
|
||||
if (state == JobState::Completed && !job->committed_to_scene)
|
||||
{
|
||||
if (job->scene)
|
||||
{
|
||||
if (job->scene->debugName.empty())
|
||||
{
|
||||
job->scene->debugName = job->model_relative_path;
|
||||
}
|
||||
scene.addGLTFInstance(job->scene_name, job->scene, job->transform);
|
||||
}
|
||||
job->committed_to_scene = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncAssetLoader::worker_loop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
JobID id = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_jobs_mutex);
|
||||
_jobs_cv.wait(lock, [this]() { return !_queue.empty() || !_running.load(std::memory_order_acquire); });
|
||||
if (!_running.load(std::memory_order_acquire) && _queue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_queue.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
id = _queue.front();
|
||||
_queue.pop_front();
|
||||
}
|
||||
|
||||
Job *job = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_jobs_mutex);
|
||||
auto it = _jobs.find(id);
|
||||
if (it == _jobs.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
job = it->second.get();
|
||||
}
|
||||
|
||||
job->state.store(JobState::Running, std::memory_order_release);
|
||||
job->progress.store(0.01f, std::memory_order_relaxed);
|
||||
|
||||
GLTFLoadCallbacks cb{};
|
||||
cb.on_progress = [job](float v)
|
||||
{
|
||||
job->progress.store(v, std::memory_order_relaxed);
|
||||
};
|
||||
cb.is_cancelled = [job]() -> bool
|
||||
{
|
||||
return job->state.load(std::memory_order_acquire) == JobState::Cancelled;
|
||||
};
|
||||
|
||||
std::optional<std::shared_ptr<LoadedGLTF> > loaded = _assets->loadGLTF(job->model_relative_path, &cb);
|
||||
if (!loaded.has_value() || !loaded.value())
|
||||
{
|
||||
job->error = "loadGLTF failed or returned empty scene";
|
||||
job->state.store(JobState::Failed, std::memory_order_release);
|
||||
job->progress.store(1.0f, std::memory_order_relaxed);
|
||||
continue;
|
||||
}
|
||||
|
||||
job->scene = loaded.value();
|
||||
job->progress.store(1.0f, std::memory_order_relaxed);
|
||||
job->state.store(JobState::Completed, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
97
src/core/assets/async_loader.h
Normal file
97
src/core/assets/async_loader.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
#include "scene/vk_loader.h"
|
||||
#include "core/assets/texture_cache.h"
|
||||
|
||||
class VulkanEngine;
|
||||
class AssetManager;
|
||||
class SceneManager;
|
||||
|
||||
// Small orchestrator for asynchronous glTF asset jobs.
|
||||
// - CPU work (file I/O, fastgltf parsing, mesh/BVH build) runs on worker threads.
|
||||
// - GPU uploads are still deferred through ResourceManager and the Render Graph.
|
||||
// - Texture streaming and residency are tracked via TextureCache for progress.
|
||||
class AsyncAssetLoader
|
||||
{
|
||||
public:
|
||||
using JobID = uint32_t;
|
||||
|
||||
enum class JobState { Pending, Running, Completed, Failed, Cancelled };
|
||||
|
||||
AsyncAssetLoader();
|
||||
~AsyncAssetLoader();
|
||||
|
||||
void init(VulkanEngine *engine, AssetManager *assets, TextureCache *textures, uint32_t worker_count = 1);
|
||||
void shutdown();
|
||||
|
||||
JobID load_gltf_async(const std::string &scene_name,
|
||||
const std::string &model_relative_path,
|
||||
const glm::mat4 &transform);
|
||||
|
||||
bool get_job_status(JobID id, JobState &out_state, float &out_progress, std::string *out_error = nullptr);
|
||||
|
||||
// Main-thread integration: commit completed jobs into the SceneManager.
|
||||
void pump_main_thread(SceneManager &scene);
|
||||
|
||||
struct DebugJob
|
||||
{
|
||||
JobID id{0};
|
||||
JobState state{JobState::Pending};
|
||||
float progress{0.0f};
|
||||
std::string scene_name;
|
||||
std::string model_relative_path;
|
||||
size_t texture_count{0};
|
||||
size_t textures_resident{0};
|
||||
};
|
||||
// Debug-only snapshot of current jobs for UI/tools (main-thread only).
|
||||
void debug_snapshot(std::vector<DebugJob> &out_jobs);
|
||||
|
||||
private:
|
||||
struct Job
|
||||
{
|
||||
JobID id{0};
|
||||
std::string scene_name;
|
||||
std::string model_relative_path;
|
||||
glm::mat4 transform{1.0f};
|
||||
|
||||
std::shared_ptr<LoadedGLTF> scene;
|
||||
|
||||
std::atomic<float> progress{0.0f};
|
||||
std::atomic<JobState> state{JobState::Pending};
|
||||
|
||||
std::string error;
|
||||
bool committed_to_scene{false};
|
||||
|
||||
// Texture handles associated with this glTF (prefetched via TextureCache).
|
||||
std::vector<TextureCache::TextureHandle> texture_handles;
|
||||
};
|
||||
|
||||
void start_workers(uint32_t count);
|
||||
void stop_workers();
|
||||
void worker_loop();
|
||||
|
||||
VulkanEngine *_engine{nullptr};
|
||||
AssetManager *_assets{nullptr};
|
||||
TextureCache *_textures{nullptr};
|
||||
|
||||
std::atomic<bool> _running{false};
|
||||
std::vector<std::thread> _workers;
|
||||
|
||||
std::mutex _jobs_mutex;
|
||||
std::condition_variable _jobs_cv;
|
||||
std::unordered_map<JobID, std::unique_ptr<Job>> _jobs;
|
||||
std::deque<JobID> _queue;
|
||||
std::atomic<JobID> _next_id{1};
|
||||
};
|
||||
@@ -52,7 +52,10 @@ void AssetManager::cleanup()
|
||||
_meshCache.clear();
|
||||
_meshMaterialBuffers.clear();
|
||||
_meshOwnedImages.clear();
|
||||
_gltfCacheByPath.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||
_gltfCacheByPath.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AssetManager::shaderPath(std::string_view name) const
|
||||
@@ -71,6 +74,12 @@ std::string AssetManager::modelPath(std::string_view name) const
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_view nameOrPath)
|
||||
{
|
||||
return loadGLTF(nameOrPath, nullptr);
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_view nameOrPath,
|
||||
const GLTFLoadCallbacks *cb)
|
||||
{
|
||||
if (!_engine) return {};
|
||||
if (nameOrPath.empty()) return {};
|
||||
@@ -82,18 +91,22 @@ std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_v
|
||||
keyPath = std::filesystem::weakly_canonical(keyPath, ec);
|
||||
std::string key = (ec ? resolved : keyPath.string());
|
||||
|
||||
if (auto it = _gltfCacheByPath.find(key); it != _gltfCacheByPath.end())
|
||||
{
|
||||
if (auto sp = it->second.lock())
|
||||
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||
if (auto it = _gltfCacheByPath.find(key); it != _gltfCacheByPath.end())
|
||||
{
|
||||
fmt::println("[AssetManager] loadGLTF cache hit key='{}' path='{}' ptr={}", key, resolved,
|
||||
static_cast<const void *>(sp.get()));
|
||||
return sp;
|
||||
if (auto sp = it->second.lock())
|
||||
{
|
||||
fmt::println("[AssetManager] loadGLTF cache hit key='{}' path='{}' ptr={}", key, resolved,
|
||||
static_cast<const void *>(sp.get()));
|
||||
return sp;
|
||||
}
|
||||
fmt::println("[AssetManager] loadGLTF cache expired key='{}' path='{}' (reloading)", key, resolved);
|
||||
_gltfCacheByPath.erase(it);
|
||||
}
|
||||
fmt::println("[AssetManager] loadGLTF cache expired key='{}' path='{}' (reloading)", key, resolved);
|
||||
}
|
||||
|
||||
auto loaded = loadGltf(_engine, resolved);
|
||||
auto loaded = loadGltf(_engine, resolved, cb);
|
||||
if (!loaded.has_value()) return {};
|
||||
|
||||
if (loaded.value())
|
||||
@@ -106,7 +119,10 @@ std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_v
|
||||
fmt::println("[AssetManager] loadGLTF got empty scene for key='{}' path='{}'", key, resolved);
|
||||
}
|
||||
|
||||
_gltfCacheByPath[key] = loaded.value();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||
_gltfCacheByPath[key] = loaded.value();
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
@@ -336,10 +352,11 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
|
||||
return mesh;
|
||||
}
|
||||
|
||||
size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
AssetManager::GLTFTexturePrefetchResult AssetManager::prefetchGLTFTexturesWithHandles(std::string_view nameOrPath)
|
||||
{
|
||||
if (!_engine || !_engine->_context || !_engine->_context->textures) return 0;
|
||||
if (nameOrPath.empty()) return 0;
|
||||
GLTFTexturePrefetchResult result{};
|
||||
if (!_engine || !_engine->_context || !_engine->_context->textures) return result;
|
||||
if (nameOrPath.empty()) return result;
|
||||
|
||||
std::string resolved = assetPath(nameOrPath);
|
||||
std::filesystem::path path = resolved;
|
||||
@@ -348,28 +365,28 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble |
|
||||
fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
|
||||
fastgltf::GltfDataBuffer data;
|
||||
if (!data.loadFromFile(path)) return 0;
|
||||
if (!data.loadFromFile(path)) return result;
|
||||
|
||||
fastgltf::Asset gltf;
|
||||
size_t scheduled = 0;
|
||||
|
||||
auto type = fastgltf::determineGltfFileType(&data);
|
||||
if (type == fastgltf::GltfType::glTF)
|
||||
{
|
||||
auto load = parser.loadGLTF(&data, path.parent_path(), gltfOptions);
|
||||
if (load) gltf = std::move(load.get()); else return 0;
|
||||
if (load) gltf = std::move(load.get()); else return result;
|
||||
}
|
||||
else if (type == fastgltf::GltfType::GLB)
|
||||
{
|
||||
auto load = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions);
|
||||
if (load) gltf = std::move(load.get()); else return 0;
|
||||
if (load) gltf = std::move(load.get()); else return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
TextureCache *cache = _engine->_context->textures;
|
||||
const std::filesystem::path baseDir = path.parent_path();
|
||||
|
||||
auto enqueueTex = [&](size_t imgIndex, bool srgb)
|
||||
{
|
||||
@@ -382,10 +399,15 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
std::visit(fastgltf::visitor{
|
||||
[&](fastgltf::sources::URI &filePath)
|
||||
{
|
||||
const std::string p(filePath.uri.path().begin(), filePath.uri.path().end());
|
||||
const std::string rel(filePath.uri.path().begin(), filePath.uri.path().end());
|
||||
std::filesystem::path resolvedImg = std::filesystem::path(rel);
|
||||
if (resolvedImg.is_relative())
|
||||
{
|
||||
resolvedImg = baseDir / resolvedImg;
|
||||
}
|
||||
key.kind = TextureCache::TextureKey::SourceKind::FilePath;
|
||||
key.path = p;
|
||||
std::string id = std::string("GLTF-PREF:") + p + (srgb ? "#sRGB" : "#UNORM");
|
||||
key.path = resolvedImg.string();
|
||||
std::string id = std::string("GLTF:") + key.path + (srgb ? "#sRGB" : "#UNORM");
|
||||
key.hash = texcache::fnv1a64(id);
|
||||
},
|
||||
[&](fastgltf::sources::Vector &vector)
|
||||
@@ -418,8 +440,9 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
if (key.hash != 0)
|
||||
{
|
||||
VkSampler samp = _engine->_samplerManager->defaultLinear();
|
||||
cache->request(key, samp);
|
||||
scheduled++;
|
||||
TextureCache::TextureHandle handle = cache->request(key, samp);
|
||||
result.handles.push_back(handle);
|
||||
result.scheduled++;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -443,7 +466,12 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
}, buf.data);
|
||||
}
|
||||
|
||||
return scheduled;
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
||||
{
|
||||
return prefetchGLTFTexturesWithHandles(nameOrPath).scheduled;
|
||||
}
|
||||
|
||||
static Bounds compute_bounds(std::span<Vertex> vertices)
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
|
||||
#include <scene/vk_loader.h>
|
||||
#include <core/types.h>
|
||||
#include <core/assets/texture_cache.h>
|
||||
|
||||
#include "render/materials.h"
|
||||
#include "locator.h"
|
||||
@@ -82,6 +84,8 @@ public:
|
||||
std::string assetPath(std::string_view name) const;
|
||||
|
||||
std::optional<std::shared_ptr<LoadedGLTF> > loadGLTF(std::string_view nameOrPath);
|
||||
std::optional<std::shared_ptr<LoadedGLTF> > loadGLTF(std::string_view nameOrPath,
|
||||
const GLTFLoadCallbacks *cb);
|
||||
|
||||
// Queue texture loads for a glTF file ahead of time. This parses the glTF,
|
||||
// builds TextureCache keys for referenced images (both external URIs and
|
||||
@@ -89,6 +93,12 @@ public:
|
||||
// Actual uploads happen via the normal per-frame pump.
|
||||
// Returns number of textures scheduled.
|
||||
size_t prefetchGLTFTextures(std::string_view nameOrPath);
|
||||
struct GLTFTexturePrefetchResult
|
||||
{
|
||||
size_t scheduled = 0;
|
||||
std::vector<TextureCache::TextureHandle> handles;
|
||||
};
|
||||
GLTFTexturePrefetchResult prefetchGLTFTexturesWithHandles(std::string_view nameOrPath);
|
||||
|
||||
std::shared_ptr<MeshAsset> createMesh(const MeshCreateInfo &info);
|
||||
|
||||
@@ -116,6 +126,7 @@ private:
|
||||
AssetLocator _locator;
|
||||
|
||||
std::unordered_map<std::string, std::weak_ptr<LoadedGLTF> > _gltfCacheByPath;
|
||||
mutable std::mutex _gltfMutex;
|
||||
std::unordered_map<std::string, std::shared_ptr<MeshAsset> > _meshCache;
|
||||
std::unordered_map<std::string, AllocatedBuffer> _meshMaterialBuffers;
|
||||
std::unordered_map<std::string, std::vector<AllocatedImage> > _meshOwnedImages;
|
||||
|
||||
@@ -941,3 +941,10 @@ void TextureCache::debug_snapshot(std::vector<DebugRow> &outRows, DebugStats &ou
|
||||
return a.bytes > b.bytes;
|
||||
});
|
||||
}
|
||||
|
||||
TextureCache::EntryState TextureCache::state(TextureHandle handle) const
|
||||
{
|
||||
if (handle == InvalidHandle) return EntryState::Unloaded;
|
||||
if (handle >= _entries.size()) return EntryState::Unloaded;
|
||||
return _entries[handle].state;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
using TextureHandle = uint32_t;
|
||||
static constexpr TextureHandle InvalidHandle = 0xFFFFFFFFu;
|
||||
|
||||
enum class EntryState : uint8_t { Unloaded = 0, Loading = 1, Resident = 2, Evicted = 3 };
|
||||
|
||||
void init(EngineContext *ctx);
|
||||
void cleanup();
|
||||
|
||||
@@ -83,6 +85,8 @@ public:
|
||||
size_t countUnloaded{0};
|
||||
};
|
||||
void debug_snapshot(std::vector<DebugRow>& outRows, DebugStats& outStats) const;
|
||||
// Read-only per-handle state query (main-thread only).
|
||||
EntryState state(TextureHandle handle) const;
|
||||
size_t resident_bytes() const { return _residentBytes; }
|
||||
// CPU-side source bytes currently retained (compressed image payloads kept
|
||||
// for potential re-decode). Only applies to entries created with Bytes keys.
|
||||
@@ -126,8 +130,6 @@ private:
|
||||
VkImageView fallbackView{VK_NULL_HANDLE};
|
||||
};
|
||||
|
||||
enum class EntryState : uint8_t { Unloaded, Loading, Resident, Evicted };
|
||||
|
||||
struct Entry
|
||||
{
|
||||
TextureKey key{};
|
||||
|
||||
Reference in New Issue
Block a user