9
9
#include < facade/render/renderer.hpp>
10
10
#include < facade/scene/gltf_loader.hpp>
11
11
#include < facade/util/data_provider.hpp>
12
+ #include < facade/util/enumerate.hpp>
12
13
#include < facade/util/env.hpp>
13
14
#include < facade/util/error.hpp>
14
15
#include < facade/util/logger.hpp>
15
16
#include < facade/util/thread_pool.hpp>
16
17
#include < facade/vk/cmd.hpp>
18
+ #include < facade/vk/skybox.hpp>
17
19
#include < facade/vk/vk.hpp>
18
20
#include < glm/gtc/color_space.hpp>
19
21
#include < glm/mat4x4.hpp>
@@ -166,6 +168,42 @@ bool load_gltf(Scene& out_scene, char const* path, AtomicLoadStatus& out_status,
166
168
return Scene::GltfLoader{out_scene, out_status}(json, provider, thread_pool);
167
169
}
168
170
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
+
169
207
template <typename T>
170
208
bool ready (std::future<T> const & future) {
171
209
return future.valid () && future.wait_for (std::chrono::seconds{}) == std::future_status::ready;
@@ -183,7 +221,8 @@ bool busy(std::future<T> const& future) {
183
221
184
222
struct LoadRequest {
185
223
std::string path{};
186
- std::future<Scene> future{};
224
+ std::future<Scene> scene{};
225
+ std::future<std::optional<Skybox::Data>> skybox_data{};
187
226
AtomicLoadStatus status{};
188
227
float start_time{};
189
228
};
@@ -193,6 +232,7 @@ struct Engine::Impl {
193
232
RenderWindow window;
194
233
SceneRenderer renderer;
195
234
Scene scene;
235
+ Skybox skybox;
196
236
197
237
std::uint8_t msaa;
198
238
@@ -202,17 +242,19 @@ struct Engine::Impl {
202
242
struct {
203
243
LoadRequest request{};
204
244
UniqueTask<void ()> callback{};
245
+
246
+ bool active () const { return request.scene .valid () || request.skybox_data .valid (); }
205
247
} load{};
206
248
207
249
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) {
210
252
s_instance = this ;
211
253
load.request .status .reset ();
212
254
}
213
255
214
256
~Impl () {
215
- load.request .future = {};
257
+ load.request .scene = {};
216
258
window.gfx .device .waitIdle ();
217
259
s_instance = {};
218
260
}
@@ -256,7 +298,7 @@ auto Engine::poll() -> State const& {
256
298
void Engine::render () {
257
299
auto cb = vk::CommandBuffer{};
258
300
// 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); }
260
302
m_impl->window .gui ->end_frame ();
261
303
m_impl->window .renderer .render ();
262
304
}
@@ -267,49 +309,60 @@ glm::ivec2 Engine::window_position() const { return m_impl->window.window.get().
267
309
glm::uvec2 Engine::window_extent () const { return m_impl->window .window .get ().window_extent (); }
268
310
glm::uvec2 Engine::framebuffer_extent () const { return m_impl->window .window .get ().framebuffer_extent (); }
269
311
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 )) {
272
314
// 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 );
274
316
return false ;
275
317
}
276
318
277
319
// ensure thread pool queue has at least one worker thread, else load on this thread
278
320
if (m_impl->thread_pool .thread_count () == 0 ) {
279
321
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);
283
329
}
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 ;
286
332
}
287
333
288
334
// shared state will need to be accessed, lock the mutex
289
335
auto lock = std::scoped_lock{m_impl->mutex };
290
- if (m_impl->load .request . future . valid ()) {
336
+ if (m_impl->load .active ()) {
291
337
// we don't support discarding in-flight requests
292
338
logger::warn (" [Engine] Denied attempt to load_async when a load request is already in flight" );
293
339
return false ;
294
340
}
295
341
296
342
// 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));
298
345
// populate load request
299
346
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 );
301
348
m_impl->load .request .status .reset ();
302
349
m_impl->load .request .start_time = time::since_start ();
303
350
// if thread pool queue has only one worker thread, can't dispatch tasks from within a task and then wait for them (deadlock)
304
351
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
+ }
313
366
return true ;
314
367
}
315
368
@@ -330,21 +383,37 @@ Renderer& Engine::renderer() const { return m_impl->window.renderer; }
330
383
void Engine::update_load_request () {
331
384
auto lock = std::unique_lock{m_impl->mutex };
332
385
// 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
+ }
334
402
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
+ }
349
418
}
350
419
} // namespace facade
0 commit comments