Skip to content

Commit d5110e9

Browse files
committed
Integrate Skybox and enable async loads
- Remove increment logic from `MaybeFuture` and expose class in thread_pool.hpp. - Add `LoadFuture` with increment logic to gltf_loader.hpp.
1 parent a28bbf1 commit d5110e9

File tree

8 files changed

+182
-99
lines changed

8 files changed

+182
-99
lines changed

lib/engine/include/facade/engine/engine.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ class Engine {
8383
void request_stop();
8484

8585
///
86-
/// \brief Load a GLTF scene asynchronously.
86+
/// \brief Load a GLTF scene / Skybox asynchronously.
8787
///
8888
/// Subsequent requests will be rejected if one is in flight
8989
///
90-
bool load_async(std::string gltf_json_path, UniqueTask<void()> on_loaded = {});
90+
bool load_async(std::string json_path, UniqueTask<void()> on_loaded = {});
9191
///
9292
/// \brief Obtain status of in-flight async load request (if active).
9393
///

lib/engine/src/engine.cpp

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
#include <facade/render/renderer.hpp>
1010
#include <facade/scene/gltf_loader.hpp>
1111
#include <facade/util/data_provider.hpp>
12+
#include <facade/util/enumerate.hpp>
1213
#include <facade/util/env.hpp>
1314
#include <facade/util/error.hpp>
1415
#include <facade/util/logger.hpp>
1516
#include <facade/util/thread_pool.hpp>
1617
#include <facade/vk/cmd.hpp>
18+
#include <facade/vk/skybox.hpp>
1719
#include <facade/vk/vk.hpp>
1820
#include <glm/gtc/color_space.hpp>
1921
#include <glm/mat4x4.hpp>
@@ -166,6 +168,42 @@ bool load_gltf(Scene& out_scene, char const* path, AtomicLoadStatus& out_status,
166168
return Scene::GltfLoader{out_scene, out_status}(json, provider, thread_pool);
167169
}
168170

171+
std::optional<Skybox::Data> load_skybox_data(char const* path, AtomicLoadStatus& out_status, ThreadPool* thread_pool) {
172+
LoadFuture<Image> images[6]{};
173+
out_status.total = 6;
174+
out_status.done = 0;
175+
out_status.stage = LoadStage::eUploadingResources;
176+
177+
auto const provider = FileDataProvider::mount_parent_dir(path);
178+
auto load_image = [&provider, &out_status, thread_pool](std::string_view uri, LoadFuture<Image>& out_image) {
179+
auto load = [&provider, uri] { return Image{provider.load(uri).span(), std::string{uri}}; };
180+
if (thread_pool) {
181+
out_image = {*thread_pool, out_status.done, load};
182+
} else {
183+
out_image = {out_status.done, load};
184+
}
185+
};
186+
auto json = dj::Json::from_file(path);
187+
if (!json) {
188+
// TODO: error
189+
return {};
190+
}
191+
load_image(json["x+"].as_string(), images[0]);
192+
load_image(json["x-"].as_string(), images[1]);
193+
load_image(json["y+"].as_string(), images[2]);
194+
load_image(json["y-"].as_string(), images[3]);
195+
load_image(json["z+"].as_string(), images[4]);
196+
load_image(json["z-"].as_string(), images[5]);
197+
if (!std::all_of(std::begin(images), std::end(images), [](MaybeFuture<Image> const& i) { return i.active(); })) {
198+
// TODO: error
199+
return {};
200+
}
201+
202+
auto ret = Skybox::Data{};
203+
for (auto [future, index] : enumerate(images)) { ret.images[index] = future.get(); }
204+
return ret;
205+
}
206+
169207
template <typename T>
170208
bool ready(std::future<T> const& future) {
171209
return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::ready;
@@ -183,7 +221,8 @@ bool busy(std::future<T> const& future) {
183221

184222
struct LoadRequest {
185223
std::string path{};
186-
std::future<Scene> future{};
224+
std::future<Scene> scene{};
225+
std::future<std::optional<Skybox::Data>> skybox_data{};
187226
AtomicLoadStatus status{};
188227
float start_time{};
189228
};
@@ -193,6 +232,7 @@ struct Engine::Impl {
193232
RenderWindow window;
194233
SceneRenderer renderer;
195234
Scene scene;
235+
Skybox skybox;
196236

197237
std::uint8_t msaa;
198238

@@ -202,17 +242,19 @@ struct Engine::Impl {
202242
struct {
203243
LoadRequest request{};
204244
UniqueTask<void()> callback{};
245+
246+
bool active() const { return request.scene.valid() || request.skybox_data.valid(); }
205247
} load{};
206248

207249
Impl(UniqueWin window, std::uint8_t msaa, bool validation, std::optional<std::uint32_t> thread_count)
208-
: window(std::move(window), std::make_unique<DearImGui>(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx), msaa(msaa),
209-
thread_pool(thread_count) {
250+
: window(std::move(window), std::make_unique<DearImGui>(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx),
251+
skybox(this->window.gfx), msaa(msaa), thread_pool(thread_count) {
210252
s_instance = this;
211253
load.request.status.reset();
212254
}
213255

214256
~Impl() {
215-
load.request.future = {};
257+
load.request.scene = {};
216258
window.gfx.device.waitIdle();
217259
s_instance = {};
218260
}
@@ -256,7 +298,7 @@ auto Engine::poll() -> State const& {
256298
void Engine::render() {
257299
auto cb = vk::CommandBuffer{};
258300
// we skip rendering the scene if acquiring a swapchain image fails (unlikely)
259-
if (m_impl->window.renderer.next_frame({&cb, 1})) { m_impl->renderer.render(scene(), {}, renderer(), cb); }
301+
if (m_impl->window.renderer.next_frame({&cb, 1})) { m_impl->renderer.render(scene(), &m_impl->skybox, renderer(), cb); }
260302
m_impl->window.gui->end_frame();
261303
m_impl->window.renderer.render();
262304
}
@@ -267,49 +309,60 @@ glm::ivec2 Engine::window_position() const { return m_impl->window.window.get().
267309
glm::uvec2 Engine::window_extent() const { return m_impl->window.window.get().window_extent(); }
268310
glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get().framebuffer_extent(); }
269311

270-
bool Engine::load_async(std::string gltf_json_path, UniqueTask<void()> on_loaded) {
271-
if (!fs::is_regular_file(gltf_json_path)) {
312+
bool Engine::load_async(std::string json_path, UniqueTask<void()> on_loaded) {
313+
if (!fs::is_regular_file(json_path)) {
272314
// early return if file will fail to load anyway
273-
logger::error("[Engine] Invalid GLTF JSON path: [{}]", gltf_json_path);
315+
logger::error("[Engine] Invalid GLTF JSON path: [{}]", json_path);
274316
return false;
275317
}
276318

277319
// ensure thread pool queue has at least one worker thread, else load on this thread
278320
if (m_impl->thread_pool.thread_count() == 0) {
279321
auto const start = time::since_start();
280-
if (!load_gltf(m_impl->scene, gltf_json_path.c_str(), m_impl->load.request.status, nullptr)) {
281-
logger::error("[Engine] Failed to load GLTF: [{}]", gltf_json_path);
282-
return false;
322+
if (load_gltf(m_impl->scene, json_path.c_str(), m_impl->load.request.status, nullptr)) {
323+
logger::info("...GLTF [{}] loaded in [{:.2f}s]", env::to_filename(json_path), time::since_start() - start);
324+
return true;
325+
}
326+
if (auto sd = load_skybox_data(json_path.c_str(), m_impl->load.request.status, nullptr)) {
327+
m_impl->skybox.set(sd->views());
328+
logger::info("...Skybox [{}] loaded in [{:.2f}s]", env::to_filename(json_path), time::since_start() - start);
283329
}
284-
logger::info("...GLTF [{}] loaded in [{:.2f}s]", env::to_filename(gltf_json_path), time::since_start() - start);
285-
return true;
330+
logger::error("[Engine] Failed to load file: [{}]", json_path);
331+
return false;
286332
}
287333

288334
// shared state will need to be accessed, lock the mutex
289335
auto lock = std::scoped_lock{m_impl->mutex};
290-
if (m_impl->load.request.future.valid()) {
336+
if (m_impl->load.active()) {
291337
// we don't support discarding in-flight requests
292338
logger::warn("[Engine] Denied attempt to load_async when a load request is already in flight");
293339
return false;
294340
}
295341

296342
// ready to start loading
297-
logger::info("[Engine] Loading GLTF [{}]...", env::to_filename(gltf_json_path));
343+
std::string_view const type = fs::path{json_path}.extension() == ".skybox" ? "Skybox" : "GLTF";
344+
logger::info("[Engine] Loading {} [{}]...", type, env::to_filename(json_path));
298345
// populate load request
299346
m_impl->load.callback = std::move(on_loaded);
300-
m_impl->load.request.path = std::move(gltf_json_path);
347+
m_impl->load.request.path = std::move(json_path);
301348
m_impl->load.request.status.reset();
302349
m_impl->load.request.start_time = time::since_start();
303350
// if thread pool queue has only one worker thread, can't dispatch tasks from within a task and then wait for them (deadlock)
304351
auto* tp = m_impl->thread_pool.thread_count() > 1 ? &m_impl->thread_pool : nullptr;
305-
auto func = [path = m_impl->load.request.path, gfx = m_impl->window.gfx, status = &m_impl->load.request.status, tp] {
306-
auto scene = Scene{gfx};
307-
if (!load_gltf(scene, path.c_str(), *status, tp)) { logger::error("[Engine] Failed to load GLTF: [{}]", path); }
308-
// return the scene even on failure, it will be empty but valid
309-
return scene;
310-
};
311-
// store future
312-
m_impl->load.request.future = m_impl->thread_pool.enqueue(func);
352+
if (type == "Skybox") {
353+
auto func = [path = m_impl->load.request.path, status = &m_impl->load.request.status, tp] { return load_skybox_data(path.c_str(), *status, tp); };
354+
// store future
355+
m_impl->load.request.skybox_data = m_impl->thread_pool.enqueue(func);
356+
} else {
357+
auto func = [path = m_impl->load.request.path, gfx = m_impl->window.gfx, status = &m_impl->load.request.status, tp] {
358+
auto scene = Scene{gfx};
359+
if (!load_gltf(scene, path.c_str(), *status, tp)) { logger::error("[Engine] Failed to load GLTF: [{}]", path); }
360+
// return the scene even on failure, it will be empty but valid
361+
return scene;
362+
};
363+
// store future
364+
m_impl->load.request.scene = m_impl->thread_pool.enqueue(func);
365+
}
313366
return true;
314367
}
315368

@@ -330,21 +383,37 @@ Renderer& Engine::renderer() const { return m_impl->window.renderer; }
330383
void Engine::update_load_request() {
331384
auto lock = std::unique_lock{m_impl->mutex};
332385
// early return if future isn't valid or is still busy
333-
if (!ready(m_impl->load.request.future)) { return; }
386+
if (ready(m_impl->load.request.scene)) {
387+
// transfer scene (under mutex lock)
388+
m_impl->scene = m_impl->load.request.scene.get();
389+
// reset load status
390+
m_impl->load.request.status.reset();
391+
// move out the path
392+
auto path = std::move(m_impl->load.request.path);
393+
// move out the callback
394+
auto callback = std::move(m_impl->load.callback);
395+
auto const duration = time::since_start() - m_impl->load.request.start_time;
396+
// unlock mutex to prevent possible deadlock (eg callback calls load_gltf again)
397+
lock.unlock();
398+
logger::info("...GLTF [{}] loaded in [{:.2f}s]", env::to_filename(path), duration);
399+
// invoke callback
400+
if (callback) { callback(); }
401+
}
334402

335-
// transfer scene (under mutex lock)
336-
m_impl->scene = m_impl->load.request.future.get();
337-
// reset load status
338-
m_impl->load.request.status.reset();
339-
// move out the path
340-
auto path = std::move(m_impl->load.request.path);
341-
// move out the callback
342-
auto callback = std::move(m_impl->load.callback);
343-
auto const duration = time::since_start() - m_impl->load.request.start_time;
344-
// unlock mutex to prevent possible deadlock (eg callback calls load_gltf again)
345-
lock.unlock();
346-
logger::info("...GLTF [{}] loaded in [{:.2f}s]", env::to_filename(path), duration);
347-
// invoke callback
348-
if (callback) { callback(); }
403+
if (ready(m_impl->load.request.skybox_data)) {
404+
auto skybox_data = m_impl->load.request.skybox_data.get();
405+
m_impl->load.request.status.reset();
406+
auto path = std::move(m_impl->load.request.path);
407+
if (skybox_data) {
408+
m_impl->skybox.set(skybox_data->views());
409+
auto const duration = time::since_start() - m_impl->load.request.start_time;
410+
lock.unlock();
411+
auto callback = std::move(m_impl->load.callback);
412+
logger::info("...Skybox [{}] loaded in [{:.2f}s]", env::to_filename(path), duration);
413+
if (callback) { callback(); }
414+
} else {
415+
logger::warn("...Skybox [{}] failed to load", env::to_filename(path));
416+
}
417+
}
349418
}
350419
} // namespace facade

lib/scene/include/facade/scene/gltf_loader.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
#include <djson/json.hpp>
22
#include <facade/scene/load_status.hpp>
33
#include <facade/scene/scene.hpp>
4+
#include <facade/util/thread_pool.hpp>
45
#include <atomic>
56

67
namespace facade {
7-
class ThreadPool;
8-
98
///
109
/// \brief Concurrent status for multi-threaded loading
1110
///
@@ -21,6 +20,24 @@ struct AtomicLoadStatus {
2120
}
2221
};
2322

23+
template <typename T>
24+
struct LoadFuture : MaybeFuture<T> {
25+
LoadFuture() = default;
26+
27+
template <typename F>
28+
LoadFuture(ThreadPool& pool, std::atomic<std::size_t>& post_increment, F func)
29+
: MaybeFuture<T>(pool, [&post_increment, f = std::move(func)] {
30+
auto ret = f();
31+
++post_increment;
32+
return ret;
33+
}) {}
34+
35+
template <typename F>
36+
LoadFuture(std::atomic<std::size_t>& post_increment, F func) : MaybeFuture<T>(std::move(func)) {
37+
++post_increment;
38+
}
39+
};
40+
2441
class Scene::GltfLoader {
2542
public:
2643
///

lib/scene/src/gltf_loader.cpp

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,8 @@ Mesh to_mesh(gltf::Mesh mesh) {
7373
return ret;
7474
}
7575

76-
template <typename T>
77-
struct MaybeFuture {
78-
std::future<T> future{};
79-
std::optional<T> t{};
80-
81-
template <typename F>
82-
requires(std::same_as<std::invoke_result_t<F>, T>)
83-
MaybeFuture(ThreadPool& pool, std::atomic<std::size_t>& done, F func)
84-
: future(pool.enqueue([&done, f = std::move(func)] {
85-
auto ret = f();
86-
++done;
87-
return ret;
88-
})) {}
89-
90-
template <typename F>
91-
requires(std::same_as<std::invoke_result_t<F>, T>)
92-
MaybeFuture(std::atomic<std::size_t>& done, F func) : t(func()) {
93-
++done;
94-
}
95-
96-
T get() {
97-
assert(future.valid() || t.has_value());
98-
if (future.valid()) { return future.get(); }
99-
return std::move(*t);
100-
}
101-
};
102-
10376
template <typename F>
104-
static auto make_maybe_future(ThreadPool* pool, std::atomic<std::size_t>& out_done, F func) -> MaybeFuture<std::invoke_result_t<F>> {
77+
auto make_load_future(ThreadPool* pool, std::atomic<std::size_t>& out_done, F func) -> LoadFuture<std::invoke_result_t<F>> {
10578
if (pool) { return {*pool, out_done, std::move(func)}; }
10679
return {out_done, std::move(func)};
10780
}
@@ -132,7 +105,7 @@ bool Scene::GltfLoader::operator()(dj::Json const& root, DataProvider const& pro
132105
auto images = std::vector<MaybeFuture<Image>>{};
133106
images.reserve(asset.images.size());
134107
for (auto& image : asset.images) {
135-
images.push_back(make_maybe_future(thread_pool, m_status.done, [i = std::move(image)] { return Image{i.bytes.span(), std::move(i.name)}; }));
108+
images.push_back(make_load_future(thread_pool, m_status.done, [i = std::move(image)] { return Image{i.bytes.span(), std::move(i.name)}; }));
136109
}
137110

138111
for (auto const& sampler : asset.samplers) { m_scene.add(to_sampler_info(sampler)); }
@@ -144,7 +117,7 @@ bool Scene::GltfLoader::operator()(dj::Json const& root, DataProvider const& pro
144117
auto textures = std::vector<MaybeFuture<Texture>>{};
145118
textures.reserve(asset.textures.size());
146119
for (auto& texture : asset.textures) {
147-
textures.push_back(make_maybe_future(thread_pool, m_status.done, [texture = std::move(texture), &images, &get_sampler, this] {
120+
textures.push_back(make_load_future(thread_pool, m_status.done, [texture = std::move(texture), &images, &get_sampler, this] {
148121
bool const mip_mapped = texture.colour_space == ColourSpace::eSrgb;
149122
auto const tci = Texture::CreateInfo{.name = std::move(texture.name), .mip_mapped = mip_mapped, .colour_space = texture.colour_space};
150123
return Texture{m_scene.m_gfx, get_sampler(texture.sampler), images[texture.source].get(), tci};
@@ -154,7 +127,7 @@ bool Scene::GltfLoader::operator()(dj::Json const& root, DataProvider const& pro
154127
auto static_meshes = std::vector<MaybeFuture<StaticMesh>>{};
155128
static_meshes.reserve(asset.geometries.size());
156129
for (auto& geometry : asset.geometries) {
157-
static_meshes.push_back(make_maybe_future(thread_pool, m_status.done, [g = std::move(geometry), this] { return StaticMesh{m_scene.m_gfx, g}; }));
130+
static_meshes.push_back(make_load_future(thread_pool, m_status.done, [g = std::move(geometry), this] { return StaticMesh{m_scene.m_gfx, g}; }));
158131
}
159132

160133
m_scene.replace(from_maybe_futures(std::move(textures)));

lib/util/include/facade/util/thread_pool.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,28 @@ class ThreadPool {
6161
AsyncQueue<UniqueTask<void()>> m_queue{};
6262
std::vector<std::jthread> m_threads{};
6363
};
64+
65+
template <typename T>
66+
struct MaybeFuture {
67+
std::future<T> future{};
68+
std::optional<T> t{};
69+
70+
MaybeFuture() = default;
71+
72+
template <typename F>
73+
requires(std::same_as<std::invoke_result_t<F>, T>)
74+
MaybeFuture(ThreadPool& pool, F func) : future(pool.enqueue([f = std::move(func)] { return f(); })) {}
75+
76+
template <typename F>
77+
requires(std::same_as<std::invoke_result_t<F>, T>)
78+
explicit MaybeFuture(F func) : t(func()) {}
79+
80+
bool active() const { return future.valid() || t.has_value(); }
81+
82+
T get() {
83+
assert(active());
84+
if (future.valid()) { return future.get(); }
85+
return std::move(*t);
86+
}
87+
};
6488
} // namespace facade

0 commit comments

Comments
 (0)