ADD: async glTF loading
This commit is contained in:
@@ -36,7 +36,15 @@ layout(push_constant) uniform constants
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Apply baseColor texture and baseColorFactor once
|
// Apply baseColor texture and baseColorFactor once
|
||||||
vec3 albedo = inColor * texture(colorTex, inUV).rgb * materialData.colorFactors.rgb;
|
vec4 baseTex = texture(colorTex, inUV);
|
||||||
|
// Alpha from baseColor texture and factor, used for cutouts on MASK materials.
|
||||||
|
float alpha = clamp(baseTex.a * materialData.colorFactors.a, 0.0, 1.0);
|
||||||
|
float alphaCutoff = materialData.extra[2].x;
|
||||||
|
if (alphaCutoff > 0.0 && alpha < alphaCutoff)
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
vec3 albedo = inColor * baseTex.rgb * materialData.colorFactors.rgb;
|
||||||
|
|
||||||
// glTF metallic-roughness in G (roughness) and B (metallic)
|
// glTF metallic-roughness in G (roughness) and B (metallic)
|
||||||
vec2 mrTex = texture(metalRoughTex, inUV).gb;
|
vec2 mrTex = texture(metalRoughTex, inUV).gb;
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ void main()
|
|||||||
{
|
{
|
||||||
// Base color with material factor and texture
|
// Base color with material factor and texture
|
||||||
vec4 baseTex = texture(colorTex, inUV);
|
vec4 baseTex = texture(colorTex, inUV);
|
||||||
|
// Alpha from baseColor texture and factor (glTF spec)
|
||||||
|
float alpha = clamp(baseTex.a * materialData.colorFactors.a, 0.0, 1.0);
|
||||||
|
// Optional alpha-cutout support for MASK materials (alphaCutoff > 0)
|
||||||
|
float alphaCutoff = materialData.extra[2].x;
|
||||||
|
if (alphaCutoff > 0.0 && alpha < alphaCutoff)
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
vec3 albedo = inColor * baseTex.rgb * materialData.colorFactors.rgb;
|
vec3 albedo = inColor * baseTex.rgb * materialData.colorFactors.rgb;
|
||||||
// glTF: metallicRoughnessTexture uses G=roughness, B=metallic
|
// glTF: metallicRoughnessTexture uses G=roughness, B=metallic
|
||||||
vec2 mrTex = texture(metalRoughTex, inUV).gb;
|
vec2 mrTex = texture(metalRoughTex, inUV).gb;
|
||||||
@@ -74,7 +82,5 @@ void main()
|
|||||||
vec3 indirect = diffIBL + specIBL;
|
vec3 indirect = diffIBL + specIBL;
|
||||||
vec3 color = direct + indirect * ao + emissive;
|
vec3 color = direct + indirect * ao + emissive;
|
||||||
|
|
||||||
// Alpha from baseColor texture and factor (glTF spec)
|
|
||||||
float alpha = clamp(baseTex.a * materialData.colorFactors.a, 0.0, 1.0);
|
|
||||||
outFragColor = vec4(color, alpha);
|
outFragColor = vec4(color, alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ add_executable (vulkan_engine
|
|||||||
core/assets/locator.cpp
|
core/assets/locator.cpp
|
||||||
core/assets/manager.h
|
core/assets/manager.h
|
||||||
core/assets/manager.cpp
|
core/assets/manager.cpp
|
||||||
|
core/assets/async_loader.h
|
||||||
|
core/assets/async_loader.cpp
|
||||||
core/assets/texture_cache.h
|
core/assets/texture_cache.h
|
||||||
core/assets/texture_cache.cpp
|
core/assets/texture_cache.cpp
|
||||||
core/assets/ktx_loader.h
|
core/assets/ktx_loader.h
|
||||||
|
|||||||
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,8 +52,11 @@ void AssetManager::cleanup()
|
|||||||
_meshCache.clear();
|
_meshCache.clear();
|
||||||
_meshMaterialBuffers.clear();
|
_meshMaterialBuffers.clear();
|
||||||
_meshOwnedImages.clear();
|
_meshOwnedImages.clear();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||||
_gltfCacheByPath.clear();
|
_gltfCacheByPath.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string AssetManager::shaderPath(std::string_view name) const
|
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)
|
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 (!_engine) return {};
|
||||||
if (nameOrPath.empty()) return {};
|
if (nameOrPath.empty()) return {};
|
||||||
@@ -82,6 +91,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_v
|
|||||||
keyPath = std::filesystem::weakly_canonical(keyPath, ec);
|
keyPath = std::filesystem::weakly_canonical(keyPath, ec);
|
||||||
std::string key = (ec ? resolved : keyPath.string());
|
std::string key = (ec ? resolved : keyPath.string());
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||||
if (auto it = _gltfCacheByPath.find(key); it != _gltfCacheByPath.end())
|
if (auto it = _gltfCacheByPath.find(key); it != _gltfCacheByPath.end())
|
||||||
{
|
{
|
||||||
if (auto sp = it->second.lock())
|
if (auto sp = it->second.lock())
|
||||||
@@ -91,9 +102,11 @@ std::optional<std::shared_ptr<LoadedGLTF> > AssetManager::loadGLTF(std::string_v
|
|||||||
return sp;
|
return sp;
|
||||||
}
|
}
|
||||||
fmt::println("[AssetManager] loadGLTF cache expired key='{}' path='{}' (reloading)", key, resolved);
|
fmt::println("[AssetManager] loadGLTF cache expired key='{}' path='{}' (reloading)", key, resolved);
|
||||||
|
_gltfCacheByPath.erase(it);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto loaded = loadGltf(_engine, resolved);
|
auto loaded = loadGltf(_engine, resolved, cb);
|
||||||
if (!loaded.has_value()) return {};
|
if (!loaded.has_value()) return {};
|
||||||
|
|
||||||
if (loaded.value())
|
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);
|
fmt::println("[AssetManager] loadGLTF got empty scene for key='{}' path='{}'", key, resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_gltfMutex);
|
||||||
_gltfCacheByPath[key] = loaded.value();
|
_gltfCacheByPath[key] = loaded.value();
|
||||||
|
}
|
||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,10 +352,11 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
|
|||||||
return mesh;
|
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;
|
GLTFTexturePrefetchResult result{};
|
||||||
if (nameOrPath.empty()) return 0;
|
if (!_engine || !_engine->_context || !_engine->_context->textures) return result;
|
||||||
|
if (nameOrPath.empty()) return result;
|
||||||
|
|
||||||
std::string resolved = assetPath(nameOrPath);
|
std::string resolved = assetPath(nameOrPath);
|
||||||
std::filesystem::path path = resolved;
|
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 |
|
constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble |
|
||||||
fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
|
fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
|
||||||
fastgltf::GltfDataBuffer data;
|
fastgltf::GltfDataBuffer data;
|
||||||
if (!data.loadFromFile(path)) return 0;
|
if (!data.loadFromFile(path)) return result;
|
||||||
|
|
||||||
fastgltf::Asset gltf;
|
fastgltf::Asset gltf;
|
||||||
size_t scheduled = 0;
|
|
||||||
|
|
||||||
auto type = fastgltf::determineGltfFileType(&data);
|
auto type = fastgltf::determineGltfFileType(&data);
|
||||||
if (type == fastgltf::GltfType::glTF)
|
if (type == fastgltf::GltfType::glTF)
|
||||||
{
|
{
|
||||||
auto load = parser.loadGLTF(&data, path.parent_path(), gltfOptions);
|
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)
|
else if (type == fastgltf::GltfType::GLB)
|
||||||
{
|
{
|
||||||
auto load = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions);
|
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
|
else
|
||||||
{
|
{
|
||||||
return 0;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache *cache = _engine->_context->textures;
|
TextureCache *cache = _engine->_context->textures;
|
||||||
|
const std::filesystem::path baseDir = path.parent_path();
|
||||||
|
|
||||||
auto enqueueTex = [&](size_t imgIndex, bool srgb)
|
auto enqueueTex = [&](size_t imgIndex, bool srgb)
|
||||||
{
|
{
|
||||||
@@ -382,10 +399,15 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
|||||||
std::visit(fastgltf::visitor{
|
std::visit(fastgltf::visitor{
|
||||||
[&](fastgltf::sources::URI &filePath)
|
[&](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.kind = TextureCache::TextureKey::SourceKind::FilePath;
|
||||||
key.path = p;
|
key.path = resolvedImg.string();
|
||||||
std::string id = std::string("GLTF-PREF:") + p + (srgb ? "#sRGB" : "#UNORM");
|
std::string id = std::string("GLTF:") + key.path + (srgb ? "#sRGB" : "#UNORM");
|
||||||
key.hash = texcache::fnv1a64(id);
|
key.hash = texcache::fnv1a64(id);
|
||||||
},
|
},
|
||||||
[&](fastgltf::sources::Vector &vector)
|
[&](fastgltf::sources::Vector &vector)
|
||||||
@@ -418,8 +440,9 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
|||||||
if (key.hash != 0)
|
if (key.hash != 0)
|
||||||
{
|
{
|
||||||
VkSampler samp = _engine->_samplerManager->defaultLinear();
|
VkSampler samp = _engine->_samplerManager->defaultLinear();
|
||||||
cache->request(key, samp);
|
TextureCache::TextureHandle handle = cache->request(key, samp);
|
||||||
scheduled++;
|
result.handles.push_back(handle);
|
||||||
|
result.scheduled++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -443,7 +466,12 @@ size_t AssetManager::prefetchGLTFTextures(std::string_view nameOrPath)
|
|||||||
}, buf.data);
|
}, 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)
|
static Bounds compute_bounds(std::span<Vertex> vertices)
|
||||||
|
|||||||
@@ -8,9 +8,11 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <scene/vk_loader.h>
|
#include <scene/vk_loader.h>
|
||||||
#include <core/types.h>
|
#include <core/types.h>
|
||||||
|
#include <core/assets/texture_cache.h>
|
||||||
|
|
||||||
#include "render/materials.h"
|
#include "render/materials.h"
|
||||||
#include "locator.h"
|
#include "locator.h"
|
||||||
@@ -82,6 +84,8 @@ public:
|
|||||||
std::string assetPath(std::string_view name) const;
|
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);
|
||||||
|
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,
|
// Queue texture loads for a glTF file ahead of time. This parses the glTF,
|
||||||
// builds TextureCache keys for referenced images (both external URIs and
|
// builds TextureCache keys for referenced images (both external URIs and
|
||||||
@@ -89,6 +93,12 @@ public:
|
|||||||
// Actual uploads happen via the normal per-frame pump.
|
// Actual uploads happen via the normal per-frame pump.
|
||||||
// Returns number of textures scheduled.
|
// Returns number of textures scheduled.
|
||||||
size_t prefetchGLTFTextures(std::string_view nameOrPath);
|
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);
|
std::shared_ptr<MeshAsset> createMesh(const MeshCreateInfo &info);
|
||||||
|
|
||||||
@@ -116,6 +126,7 @@ private:
|
|||||||
AssetLocator _locator;
|
AssetLocator _locator;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::weak_ptr<LoadedGLTF> > _gltfCacheByPath;
|
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, std::shared_ptr<MeshAsset> > _meshCache;
|
||||||
std::unordered_map<std::string, AllocatedBuffer> _meshMaterialBuffers;
|
std::unordered_map<std::string, AllocatedBuffer> _meshMaterialBuffers;
|
||||||
std::unordered_map<std::string, std::vector<AllocatedImage> > _meshOwnedImages;
|
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;
|
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;
|
using TextureHandle = uint32_t;
|
||||||
static constexpr TextureHandle InvalidHandle = 0xFFFFFFFFu;
|
static constexpr TextureHandle InvalidHandle = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
enum class EntryState : uint8_t { Unloaded = 0, Loading = 1, Resident = 2, Evicted = 3 };
|
||||||
|
|
||||||
void init(EngineContext *ctx);
|
void init(EngineContext *ctx);
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
@@ -83,6 +85,8 @@ public:
|
|||||||
size_t countUnloaded{0};
|
size_t countUnloaded{0};
|
||||||
};
|
};
|
||||||
void debug_snapshot(std::vector<DebugRow>& outRows, DebugStats& outStats) const;
|
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; }
|
size_t resident_bytes() const { return _residentBytes; }
|
||||||
// CPU-side source bytes currently retained (compressed image payloads kept
|
// CPU-side source bytes currently retained (compressed image payloads kept
|
||||||
// for potential re-decode). Only applies to entries created with Bytes keys.
|
// for potential re-decode). Only applies to entries created with Bytes keys.
|
||||||
@@ -126,8 +130,6 @@ private:
|
|||||||
VkImageView fallbackView{VK_NULL_HANDLE};
|
VkImageView fallbackView{VK_NULL_HANDLE};
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class EntryState : uint8_t { Unloaded, Loading, Resident, Evicted };
|
|
||||||
|
|
||||||
struct Entry
|
struct Entry
|
||||||
{
|
{
|
||||||
TextureKey key{};
|
TextureKey key{};
|
||||||
|
|||||||
@@ -221,7 +221,10 @@ AllocatedImage ResourceManager::create_image(const void *data, VkExtent3D size,
|
|||||||
? static_cast<uint32_t>(std::floor(std::log2(std::max(size.width, size.height)))) + 1
|
? static_cast<uint32_t>(std::floor(std::log2(std::max(size.width, size.height)))) + 1
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
_pendingImageUploads.push_back(std::move(pending));
|
_pendingImageUploads.push_back(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
if (!_deferUploads)
|
if (!_deferUploads)
|
||||||
{
|
{
|
||||||
@@ -258,7 +261,10 @@ AllocatedImage ResourceManager::create_image(const void *data, VkExtent3D size,
|
|||||||
pending.mipLevels = (mipmapped && mipLevelsOverride > 0) ? mipLevelsOverride
|
pending.mipLevels = (mipmapped && mipLevelsOverride > 0) ? mipLevelsOverride
|
||||||
: (mipmapped ? static_cast<uint32_t>(std::floor(std::log2(std::max(size.width, size.height)))) + 1 : 1);
|
: (mipmapped ? static_cast<uint32_t>(std::floor(std::log2(std::max(size.width, size.height)))) + 1 : 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
_pendingImageUploads.push_back(std::move(pending));
|
_pendingImageUploads.push_back(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
if (!_deferUploads)
|
if (!_deferUploads)
|
||||||
{
|
{
|
||||||
@@ -340,7 +346,10 @@ GPUMeshBuffers ResourceManager::uploadMesh(std::span<uint32_t> indices, std::spa
|
|||||||
.stagingOffset = vertexBufferSize,
|
.stagingOffset = vertexBufferSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
_pendingBufferUploads.push_back(std::move(pending));
|
_pendingBufferUploads.push_back(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
if (!_deferUploads)
|
if (!_deferUploads)
|
||||||
{
|
{
|
||||||
@@ -352,29 +361,43 @@ GPUMeshBuffers ResourceManager::uploadMesh(std::span<uint32_t> indices, std::spa
|
|||||||
|
|
||||||
bool ResourceManager::has_pending_uploads() const
|
bool ResourceManager::has_pending_uploads() const
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
return !_pendingBufferUploads.empty() || !_pendingImageUploads.empty();
|
return !_pendingBufferUploads.empty() || !_pendingImageUploads.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceManager::clear_pending_uploads()
|
void ResourceManager::clear_pending_uploads()
|
||||||
{
|
{
|
||||||
for (auto &upload : _pendingBufferUploads)
|
std::vector<PendingBufferUpload> buffers;
|
||||||
|
std::vector<PendingImageUpload> images;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
|
buffers.swap(_pendingBufferUploads);
|
||||||
|
images.swap(_pendingImageUploads);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &upload : buffers)
|
||||||
{
|
{
|
||||||
destroy_buffer(upload.staging);
|
destroy_buffer(upload.staging);
|
||||||
}
|
}
|
||||||
for (auto &upload : _pendingImageUploads)
|
for (auto &upload : images)
|
||||||
{
|
{
|
||||||
destroy_buffer(upload.staging);
|
destroy_buffer(upload.staging);
|
||||||
}
|
}
|
||||||
_pendingBufferUploads.clear();
|
|
||||||
_pendingImageUploads.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceManager::process_queued_uploads_immediate()
|
void ResourceManager::process_queued_uploads_immediate()
|
||||||
{
|
{
|
||||||
if (!has_pending_uploads()) return;
|
std::vector<PendingBufferUpload> buffers;
|
||||||
|
std::vector<PendingImageUpload> images;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
|
if (_pendingBufferUploads.empty() && _pendingImageUploads.empty()) return;
|
||||||
|
buffers.swap(_pendingBufferUploads);
|
||||||
|
images.swap(_pendingImageUploads);
|
||||||
|
}
|
||||||
|
|
||||||
immediate_submit([&](VkCommandBuffer cmd) {
|
immediate_submit([&](VkCommandBuffer cmd) {
|
||||||
for (auto &bufferUpload : _pendingBufferUploads)
|
for (auto &bufferUpload : buffers)
|
||||||
{
|
{
|
||||||
for (const auto © : bufferUpload.copies)
|
for (const auto © : bufferUpload.copies)
|
||||||
{
|
{
|
||||||
@@ -386,7 +409,7 @@ void ResourceManager::process_queued_uploads_immediate()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &imageUpload : _pendingImageUploads)
|
for (auto &imageUpload : images)
|
||||||
{
|
{
|
||||||
vkutil::transition_image(cmd, imageUpload.image, imageUpload.initialLayout,
|
vkutil::transition_image(cmd, imageUpload.image, imageUpload.initialLayout,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||||
@@ -434,15 +457,26 @@ void ResourceManager::process_queued_uploads_immediate()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clear_pending_uploads();
|
for (auto &upload : buffers)
|
||||||
|
{
|
||||||
|
destroy_buffer(upload.staging);
|
||||||
|
}
|
||||||
|
for (auto &upload : images)
|
||||||
|
{
|
||||||
|
destroy_buffer(upload.staging);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceManager::register_upload_pass(RenderGraph &graph, FrameResources &frame)
|
void ResourceManager::register_upload_pass(RenderGraph &graph, FrameResources &frame)
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<std::vector<PendingBufferUpload>> bufferUploads;
|
||||||
|
std::shared_ptr<std::vector<PendingImageUpload>> imageUploads;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
if (_pendingBufferUploads.empty() && _pendingImageUploads.empty()) return;
|
if (_pendingBufferUploads.empty() && _pendingImageUploads.empty()) return;
|
||||||
|
bufferUploads = std::make_shared<std::vector<PendingBufferUpload>>(std::move(_pendingBufferUploads));
|
||||||
auto bufferUploads = std::make_shared<std::vector<PendingBufferUpload>>(std::move(_pendingBufferUploads));
|
imageUploads = std::make_shared<std::vector<PendingImageUpload>>(std::move(_pendingImageUploads));
|
||||||
auto imageUploads = std::make_shared<std::vector<PendingImageUpload>>(std::move(_pendingImageUploads));
|
}
|
||||||
|
|
||||||
struct BufferBinding
|
struct BufferBinding
|
||||||
{
|
{
|
||||||
@@ -680,7 +714,10 @@ AllocatedImage ResourceManager::create_image_compressed(const void* bytes, size_
|
|||||||
pending.copies.push_back(region);
|
pending.copies.push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
_pendingImageUploads.push_back(std::move(pending));
|
_pendingImageUploads.push_back(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
if (!_deferUploads)
|
if (!_deferUploads)
|
||||||
{
|
{
|
||||||
@@ -756,7 +793,10 @@ AllocatedImage ResourceManager::create_image_compressed_layers(const void* bytes
|
|||||||
pending.mipLevels = mipLevels;
|
pending.mipLevels = mipLevels;
|
||||||
pending.copies.assign(regions.begin(), regions.end());
|
pending.copies.assign(regions.begin(), regions.end());
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(_pendingMutex);
|
||||||
_pendingImageUploads.push_back(std::move(pending));
|
_pendingImageUploads.push_back(std::move(pending));
|
||||||
|
}
|
||||||
|
|
||||||
if (!_deferUploads)
|
if (!_deferUploads)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <core/types.h>
|
#include <core/types.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
class RenderGraph;
|
class RenderGraph;
|
||||||
@@ -96,8 +97,6 @@ public:
|
|||||||
void immediate_submit(std::function<void(VkCommandBuffer)> &&function) const;
|
void immediate_submit(std::function<void(VkCommandBuffer)> &&function) const;
|
||||||
|
|
||||||
bool has_pending_uploads() const;
|
bool has_pending_uploads() const;
|
||||||
const std::vector<PendingBufferUpload> &pending_buffer_uploads() const { return _pendingBufferUploads; }
|
|
||||||
const std::vector<PendingImageUpload> &pending_image_uploads() const { return _pendingImageUploads; }
|
|
||||||
void clear_pending_uploads();
|
void clear_pending_uploads();
|
||||||
void process_queued_uploads_immediate();
|
void process_queued_uploads_immediate();
|
||||||
|
|
||||||
@@ -119,4 +118,6 @@ private:
|
|||||||
bool _deferUploads = false;
|
bool _deferUploads = false;
|
||||||
|
|
||||||
DeletionQueue _deletionQueue;
|
DeletionQueue _deletionQueue;
|
||||||
|
|
||||||
|
mutable std::mutex _pendingMutex;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ void VulkanEngine::init()
|
|||||||
_textureCache->set_max_bytes_per_pump(128ull * 1024ull * 1024ull); // 128 MiB/frame
|
_textureCache->set_max_bytes_per_pump(128ull * 1024ull * 1024ull); // 128 MiB/frame
|
||||||
_textureCache->set_max_upload_dimension(4096);
|
_textureCache->set_max_upload_dimension(4096);
|
||||||
|
|
||||||
|
// Async asset loader for background glTF + texture jobs
|
||||||
|
_asyncLoader = std::make_unique<AsyncAssetLoader>();
|
||||||
|
_asyncLoader->init(this, _assetManager.get(), _textureCache.get(), 1);
|
||||||
|
|
||||||
// Optional ray tracing manager if supported and extensions enabled
|
// Optional ray tracing manager if supported and extensions enabled
|
||||||
if (_deviceManager->supportsRayQuery() && _deviceManager->supportsAccelerationStructure())
|
if (_deviceManager->supportsRayQuery() && _deviceManager->supportsAccelerationStructure())
|
||||||
{
|
{
|
||||||
@@ -375,8 +379,26 @@ bool VulkanEngine::addGLTFInstance(const std::string &instanceName,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t VulkanEngine::loadGLTFAsync(const std::string &sceneName,
|
||||||
|
const std::string &modelRelativePath,
|
||||||
|
const glm::mat4 &transform)
|
||||||
|
{
|
||||||
|
if (!_asyncLoader || !_assetManager || !_sceneManager)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _asyncLoader->load_gltf_async(sceneName, modelRelativePath, transform);
|
||||||
|
}
|
||||||
|
|
||||||
void VulkanEngine::cleanup()
|
void VulkanEngine::cleanup()
|
||||||
{
|
{
|
||||||
|
if (_asyncLoader)
|
||||||
|
{
|
||||||
|
_asyncLoader->shutdown();
|
||||||
|
_asyncLoader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
vkDeviceWaitIdle(_deviceManager->device());
|
vkDeviceWaitIdle(_deviceManager->device());
|
||||||
|
|
||||||
print_vma_stats(_deviceManager.get(), "begin");
|
print_vma_stats(_deviceManager.get(), "begin");
|
||||||
@@ -481,6 +503,12 @@ void VulkanEngine::cleanup()
|
|||||||
|
|
||||||
void VulkanEngine::draw()
|
void VulkanEngine::draw()
|
||||||
{
|
{
|
||||||
|
// Integrate any completed async asset jobs into the scene before updating.
|
||||||
|
if (_asyncLoader && _sceneManager)
|
||||||
|
{
|
||||||
|
_asyncLoader->pump_main_thread(*_sceneManager);
|
||||||
|
}
|
||||||
|
|
||||||
_sceneManager->update_scene();
|
_sceneManager->update_scene();
|
||||||
|
|
||||||
// Update IBL based on camera position and user-defined reflection volumes.
|
// Update IBL based on camera position and user-defined reflection volumes.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "core/context.h"
|
#include "core/context.h"
|
||||||
#include "core/pipeline/manager.h"
|
#include "core/pipeline/manager.h"
|
||||||
#include "core/assets/manager.h"
|
#include "core/assets/manager.h"
|
||||||
|
#include "core/assets/async_loader.h"
|
||||||
#include "render/graph/graph.h"
|
#include "render/graph/graph.h"
|
||||||
#include "core/raytracing/raytracing.h"
|
#include "core/raytracing/raytracing.h"
|
||||||
#include "core/assets/texture_cache.h"
|
#include "core/assets/texture_cache.h"
|
||||||
@@ -70,6 +71,7 @@ public:
|
|||||||
std::unique_ptr<SceneManager> _sceneManager;
|
std::unique_ptr<SceneManager> _sceneManager;
|
||||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||||
std::unique_ptr<AssetManager> _assetManager;
|
std::unique_ptr<AssetManager> _assetManager;
|
||||||
|
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
||||||
std::unique_ptr<RenderGraph> _renderGraph;
|
std::unique_ptr<RenderGraph> _renderGraph;
|
||||||
std::unique_ptr<RayTracingManager> _rayManager;
|
std::unique_ptr<RayTracingManager> _rayManager;
|
||||||
std::unique_ptr<TextureCache> _textureCache;
|
std::unique_ptr<TextureCache> _textureCache;
|
||||||
@@ -202,6 +204,12 @@ public:
|
|||||||
const std::string &modelRelativePath,
|
const std::string &modelRelativePath,
|
||||||
const glm::mat4 &transform = glm::mat4(1.f));
|
const glm::mat4 &transform = glm::mat4(1.f));
|
||||||
|
|
||||||
|
// Asynchronous glTF load that reports progress via AsyncAssetLoader.
|
||||||
|
// Returns a JobID that can be queried via AsyncAssetLoader.
|
||||||
|
uint32_t loadGLTFAsync(const std::string &sceneName,
|
||||||
|
const std::string &modelRelativePath,
|
||||||
|
const glm::mat4 &transform = glm::mat4(1.f));
|
||||||
|
|
||||||
bool resize_requested{false};
|
bool resize_requested{false};
|
||||||
bool freeze_rendering{false};
|
bool freeze_rendering{false};
|
||||||
|
|
||||||
|
|||||||
@@ -379,6 +379,97 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *job_state_name(AsyncAssetLoader::JobState s)
|
||||||
|
{
|
||||||
|
using JS = AsyncAssetLoader::JobState;
|
||||||
|
switch (s)
|
||||||
|
{
|
||||||
|
case JS::Pending: return "Pending";
|
||||||
|
case JS::Running: return "Running";
|
||||||
|
case JS::Completed: return "Completed";
|
||||||
|
case JS::Failed: return "Failed";
|
||||||
|
case JS::Cancelled: return "Cancelled";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_async_assets(VulkanEngine *eng)
|
||||||
|
{
|
||||||
|
if (!eng || !eng->_asyncLoader)
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("AsyncAssetLoader not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AsyncAssetLoader::DebugJob> jobs;
|
||||||
|
eng->_asyncLoader->debug_snapshot(jobs);
|
||||||
|
|
||||||
|
ImGui::Text("Active jobs: %zu", jobs.size());
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (!jobs.empty())
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("async_jobs", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp))
|
||||||
|
{
|
||||||
|
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||||
|
ImGui::TableSetupColumn("Scene");
|
||||||
|
ImGui::TableSetupColumn("Model");
|
||||||
|
ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("Progress", ImGuiTableColumnFlags_WidthFixed, 180);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto &j : jobs)
|
||||||
|
{
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::Text("%u", j.id);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::TextUnformatted(j.scene_name.c_str());
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
ImGui::TextUnformatted(j.model_relative_path.c_str());
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
ImGui::TextUnformatted(job_state_name(j.state));
|
||||||
|
ImGui::TableSetColumnIndex(4);
|
||||||
|
float p = j.progress;
|
||||||
|
ImGui::ProgressBar(p, ImVec2(-FLT_MIN, 0.0f));
|
||||||
|
if (j.texture_count > 0)
|
||||||
|
{
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("(%zu/%zu tex)", j.textures_resident, j.texture_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("No async asset jobs currently running.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted("Spawn async glTF instance");
|
||||||
|
static char gltfPath[256] = "mirage2000/scene.gltf";
|
||||||
|
static char gltfName[128] = "async_gltf_01";
|
||||||
|
static float gltfPos[3] = {0.0f, 0.0f, 0.0f};
|
||||||
|
static float gltfRot[3] = {0.0f, 0.0f, 0.0f};
|
||||||
|
static float gltfScale[3] = {1.0f, 1.0f, 1.0f};
|
||||||
|
ImGui::InputText("Model path (assets/models/...)", gltfPath, IM_ARRAYSIZE(gltfPath));
|
||||||
|
ImGui::InputText("Instance name", gltfName, IM_ARRAYSIZE(gltfName));
|
||||||
|
ImGui::InputFloat3("Position", gltfPos);
|
||||||
|
ImGui::InputFloat3("Rotation (deg XYZ)", gltfRot);
|
||||||
|
ImGui::InputFloat3("Scale", gltfScale);
|
||||||
|
if (ImGui::Button("Load glTF async"))
|
||||||
|
{
|
||||||
|
glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(gltfPos[0], gltfPos[1], gltfPos[2]));
|
||||||
|
glm::mat4 R = glm::eulerAngleXYZ(glm::radians(gltfRot[0]),
|
||||||
|
glm::radians(gltfRot[1]),
|
||||||
|
glm::radians(gltfRot[2]));
|
||||||
|
glm::mat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(gltfScale[0], gltfScale[1], gltfScale[2]));
|
||||||
|
glm::mat4 M = T * R * S;
|
||||||
|
eng->loadGLTFAsync(gltfName, gltfPath, M);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shadows / Ray Query controls
|
// Shadows / Ray Query controls
|
||||||
static void ui_shadows(VulkanEngine *eng)
|
static void ui_shadows(VulkanEngine *eng)
|
||||||
{
|
{
|
||||||
@@ -1164,6 +1255,11 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng)
|
|||||||
ui_scene(eng);
|
ui_scene(eng);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
if (ImGui::BeginTabItem("Async Assets"))
|
||||||
|
{
|
||||||
|
ui_async_assets(eng);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
if (ImGui::BeginTabItem("Textures"))
|
if (ImGui::BeginTabItem("Textures"))
|
||||||
{
|
{
|
||||||
ui_textures(eng);
|
ui_textures(eng);
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter)
|
|||||||
|
|
||||||
//< filters
|
//< filters
|
||||||
|
|
||||||
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::string_view filePath)
|
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine,
|
||||||
|
std::string_view filePath,
|
||||||
|
const GLTFLoadCallbacks *cb)
|
||||||
{
|
{
|
||||||
//> load_1
|
//> load_1
|
||||||
fmt::println("[GLTF] loadGltf begin: '{}'", filePath);
|
fmt::println("[GLTF] loadGltf begin: '{}'", filePath);
|
||||||
@@ -216,6 +218,24 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
//< load_1
|
//< load_1
|
||||||
|
// Simple helpers for progress/cancellation callbacks (if provided)
|
||||||
|
auto report_progress = [&](float v)
|
||||||
|
{
|
||||||
|
if (cb && cb->on_progress)
|
||||||
|
{
|
||||||
|
float clamped = std::clamp(v, 0.0f, 1.0f);
|
||||||
|
cb->on_progress(clamped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto is_cancelled = [&]() -> bool
|
||||||
|
{
|
||||||
|
if (cb && cb->is_cancelled)
|
||||||
|
{
|
||||||
|
return cb->is_cancelled();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
//> load_2
|
//> load_2
|
||||||
// we can stimate the descriptors we will need accurately
|
// we can stimate the descriptors we will need accurately
|
||||||
fmt::println("[GLTF] loadGltf: materials={} meshes={} images={} samplers={} (creating descriptor pool)",
|
fmt::println("[GLTF] loadGltf: materials={} meshes={} images={} samplers={} (creating descriptor pool)",
|
||||||
@@ -235,6 +255,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
fmt::println("[GLTF] loadGltf: descriptor pool initialized for '{}' (materials={})",
|
fmt::println("[GLTF] loadGltf: descriptor pool initialized for '{}' (materials={})",
|
||||||
filePath,
|
filePath,
|
||||||
gltf.materials.size());
|
gltf.materials.size());
|
||||||
|
|
||||||
|
report_progress(0.1f);
|
||||||
//< load_2
|
//< load_2
|
||||||
//> load_samplers
|
//> load_samplers
|
||||||
|
|
||||||
@@ -270,6 +292,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
file.samplers.push_back(newSampler);
|
file.samplers.push_back(newSampler);
|
||||||
}
|
}
|
||||||
//< load_samplers
|
//< load_samplers
|
||||||
|
|
||||||
|
report_progress(0.2f);
|
||||||
//> load_arrays
|
//> load_arrays
|
||||||
// temporal arrays for all the objects to use while creating the GLTF data
|
// temporal arrays for all the objects to use while creating the GLTF data
|
||||||
std::vector<std::shared_ptr<MeshAsset> > meshes;
|
std::vector<std::shared_ptr<MeshAsset> > meshes;
|
||||||
@@ -349,6 +373,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
//> load_material
|
//> load_material
|
||||||
for (fastgltf::Material &mat: gltf.materials)
|
for (fastgltf::Material &mat: gltf.materials)
|
||||||
{
|
{
|
||||||
|
if (is_cancelled()) return {};
|
||||||
|
|
||||||
std::shared_ptr<GLTFMaterial> newMat = std::make_shared<GLTFMaterial>();
|
std::shared_ptr<GLTFMaterial> newMat = std::make_shared<GLTFMaterial>();
|
||||||
materials.push_back(newMat);
|
materials.push_back(newMat);
|
||||||
file.materials[mat.name.c_str()] = newMat;
|
file.materials[mat.name.c_str()] = newMat;
|
||||||
@@ -361,11 +387,20 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
|
|
||||||
constants.metal_rough_factors.x = mat.pbrData.metallicFactor;
|
constants.metal_rough_factors.x = mat.pbrData.metallicFactor;
|
||||||
constants.metal_rough_factors.y = mat.pbrData.roughnessFactor;
|
constants.metal_rough_factors.y = mat.pbrData.roughnessFactor;
|
||||||
|
// extra[0].x: normalScale (default 1.0)
|
||||||
constants.extra[0].x = 1.0f;
|
constants.extra[0].x = 1.0f;
|
||||||
|
// extra[0].y: occlusionStrength (0..1, default 1.0)
|
||||||
constants.extra[0].y = mat.occlusionTexture.has_value() ? mat.occlusionTexture->strength : 1.0f;
|
constants.extra[0].y = mat.occlusionTexture.has_value() ? mat.occlusionTexture->strength : 1.0f;
|
||||||
|
// extra[1].rgb: emissiveFactor
|
||||||
constants.extra[1].x = mat.emissiveFactor[0];
|
constants.extra[1].x = mat.emissiveFactor[0];
|
||||||
constants.extra[1].y = mat.emissiveFactor[1];
|
constants.extra[1].y = mat.emissiveFactor[1];
|
||||||
constants.extra[1].z = mat.emissiveFactor[2];
|
constants.extra[1].z = mat.emissiveFactor[2];
|
||||||
|
// extra[2].x: alphaCutoff for MASK materials (>0 enables alpha test)
|
||||||
|
constants.extra[2].x = 0.0f;
|
||||||
|
if (mat.alphaMode == fastgltf::AlphaMode::Mask)
|
||||||
|
{
|
||||||
|
constants.extra[2].x = static_cast<float>(mat.alphaCutoff);
|
||||||
|
}
|
||||||
// write material parameters to buffer
|
// write material parameters to buffer
|
||||||
sceneMaterialConstants[data_index] = constants;
|
sceneMaterialConstants[data_index] = constants;
|
||||||
|
|
||||||
@@ -510,6 +545,12 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
}
|
}
|
||||||
//< load_material
|
//< load_material
|
||||||
|
|
||||||
|
// Rough progress after materials and texture requests
|
||||||
|
if (!gltf.meshes.empty())
|
||||||
|
{
|
||||||
|
report_progress(0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
// Flush material constants buffer so GPU sees updated data on non-coherent memory
|
// Flush material constants buffer so GPU sees updated data on non-coherent memory
|
||||||
if (!gltf.materials.empty())
|
if (!gltf.materials.empty())
|
||||||
{
|
{
|
||||||
@@ -522,8 +563,11 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
std::vector<uint32_t> indices;
|
std::vector<uint32_t> indices;
|
||||||
std::vector<Vertex> vertices;
|
std::vector<Vertex> vertices;
|
||||||
|
|
||||||
for (fastgltf::Mesh &mesh: gltf.meshes)
|
for (size_t meshIndex = 0; meshIndex < gltf.meshes.size(); ++meshIndex)
|
||||||
{
|
{
|
||||||
|
if (is_cancelled()) return {};
|
||||||
|
|
||||||
|
fastgltf::Mesh &mesh = gltf.meshes[meshIndex];
|
||||||
std::shared_ptr<MeshAsset> newmesh = std::make_shared<MeshAsset>();
|
std::shared_ptr<MeshAsset> newmesh = std::make_shared<MeshAsset>();
|
||||||
meshes.push_back(newmesh);
|
meshes.push_back(newmesh);
|
||||||
file.meshes[mesh.name.c_str()] = newmesh;
|
file.meshes[mesh.name.c_str()] = newmesh;
|
||||||
@@ -704,11 +748,18 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
};
|
};
|
||||||
shrink_if_huge(indices, sizeof(uint32_t));
|
shrink_if_huge(indices, sizeof(uint32_t));
|
||||||
shrink_if_huge(vertices, sizeof(Vertex));
|
shrink_if_huge(vertices, sizeof(Vertex));
|
||||||
|
|
||||||
|
// Update progress based on meshes built so far; meshes/BVH/uploads get 0.6 of the range.
|
||||||
|
float meshFrac = static_cast<float>(meshIndex + 1) / static_cast<float>(gltf.meshes.size());
|
||||||
|
report_progress(0.2f + meshFrac * 0.6f);
|
||||||
}
|
}
|
||||||
//> load_nodes
|
//> load_nodes
|
||||||
// load all nodes and their meshes
|
// load all nodes and their meshes
|
||||||
for (fastgltf::Node &node: gltf.nodes)
|
for (size_t nodeIndex = 0; nodeIndex < gltf.nodes.size(); ++nodeIndex)
|
||||||
{
|
{
|
||||||
|
if (is_cancelled()) return {};
|
||||||
|
|
||||||
|
fastgltf::Node &node = gltf.nodes[nodeIndex];
|
||||||
std::shared_ptr<Node> newNode;
|
std::shared_ptr<Node> newNode;
|
||||||
|
|
||||||
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
|
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
|
||||||
@@ -752,6 +803,14 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
node.transform);
|
node.transform);
|
||||||
|
|
||||||
|
// Node building and hierarchy wiring shares a small slice of progress.
|
||||||
|
if (!gltf.nodes.empty())
|
||||||
|
{
|
||||||
|
float nodeFrac = static_cast<float>(nodeIndex + 1) / static_cast<float>(gltf.nodes.size());
|
||||||
|
// Reserve 0.1 of the total range for nodes/animations/transforms.
|
||||||
|
report_progress(0.8f + nodeFrac * 0.1f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//< load_nodes
|
//< load_nodes
|
||||||
//> load_graph
|
//> load_graph
|
||||||
@@ -892,6 +951,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
// LoadedGLTF only stores shared animation clips.
|
// LoadedGLTF only stores shared animation clips.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
report_progress(0.95f);
|
||||||
|
|
||||||
// We no longer need glTF-owned buffer payloads; free any large vectors
|
// We no longer need glTF-owned buffer payloads; free any large vectors
|
||||||
for (auto &buf : gltf.buffers)
|
for (auto &buf : gltf.buffers)
|
||||||
{
|
{
|
||||||
@@ -918,6 +979,8 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
file.samplers.size(),
|
file.samplers.size(),
|
||||||
file.animations.size(),
|
file.animations.size(),
|
||||||
file.debugName.empty() ? "<none>" : file.debugName);
|
file.debugName.empty() ? "<none>" : file.debugName);
|
||||||
|
|
||||||
|
report_progress(1.0f);
|
||||||
return scene;
|
return scene;
|
||||||
//< load_graph
|
//< load_graph
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "core/descriptor/descriptors.h"
|
#include "core/descriptor/descriptors.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class VulkanEngine;
|
class VulkanEngine;
|
||||||
|
|
||||||
@@ -59,6 +60,12 @@ struct MeshAsset
|
|||||||
std::shared_ptr<MeshBVH> bvh;
|
std::shared_ptr<MeshBVH> bvh;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GLTFLoadCallbacks
|
||||||
|
{
|
||||||
|
std::function<void(float)> on_progress; // range 0..1
|
||||||
|
std::function<bool()> is_cancelled; // optional, may be null
|
||||||
|
};
|
||||||
|
|
||||||
struct LoadedGLTF : public IRenderable
|
struct LoadedGLTF : public IRenderable
|
||||||
{
|
{
|
||||||
// storage for all the data on a given gltf file
|
// storage for all the data on a given gltf file
|
||||||
@@ -133,4 +140,6 @@ private:
|
|||||||
void clearAll();
|
void clearAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::string_view filePath);
|
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine,
|
||||||
|
std::string_view filePath,
|
||||||
|
const GLTFLoadCallbacks *cb = nullptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user