1
1
#include < backends/imgui_impl_glfw.h>
2
2
#include < backends/imgui_impl_vulkan.h>
3
3
#include < imgui.h>
4
+ #include < djson/json.hpp>
4
5
#include < facade/defines.hpp>
5
6
#include < facade/engine/engine.hpp>
6
7
#include < facade/engine/scene_renderer.hpp>
7
8
#include < facade/glfw/glfw_wsi.hpp>
8
9
#include < facade/render/renderer.hpp>
10
+ #include < facade/util/data_provider.hpp>
9
11
#include < facade/util/error.hpp>
12
+ #include < facade/util/logger.hpp>
10
13
#include < facade/vk/cmd.hpp>
11
14
#include < facade/vk/vk.hpp>
12
15
#include < glm/gtc/color_space.hpp>
13
16
#include < glm/mat4x4.hpp>
17
+ #include < filesystem>
18
+ #include < future>
14
19
15
20
namespace facade {
21
+ namespace fs = std::filesystem;
22
+
16
23
namespace {
17
24
static constexpr std::size_t command_buffers_v{1 };
18
25
@@ -150,6 +157,34 @@ struct RenderWindow {
150
157
: window(std::move(window)), vulkan(GlfwWsi{this ->window }, validation), gfx(vulkan.gfx()),
151
158
renderer (gfx, this ->window, gui.get(), Renderer::CreateInfo{command_buffers_v, msaa}), gui(std::move(gui)) {}
152
159
};
160
+
161
+ bool load_gltf (Scene& out_scene, char const * path, std::atomic<LoadStatus>* out_status) {
162
+ auto const provider = FileDataProvider::mount_parent_dir (path);
163
+ auto json = dj::Json::from_file (path);
164
+ return out_scene.load_gltf (json, provider, out_status);
165
+ }
166
+
167
+ template <typename T>
168
+ bool ready (std::future<T> const & future) {
169
+ return future.valid () && future.wait_for (std::chrono::seconds{}) == std::future_status::ready;
170
+ }
171
+
172
+ template <typename T>
173
+ bool timeout (std::future<T> const & future) {
174
+ return future.valid () && future.wait_for (std::chrono::seconds{}) == std::future_status::timeout;
175
+ }
176
+
177
+ template <typename T>
178
+ bool busy (std::future<T> const & future) {
179
+ return future.valid () && future.wait_for (std::chrono::seconds{}) == std::future_status::deferred;
180
+ }
181
+
182
+ struct LoadRequest {
183
+ std::string path{};
184
+ std::future<Scene> future{};
185
+ std::atomic<LoadStatus> status{};
186
+ float start_time{};
187
+ };
153
188
} // namespace
154
189
155
190
struct Engine ::Impl {
@@ -158,14 +193,21 @@ struct Engine::Impl {
158
193
Scene scene;
159
194
160
195
std::uint8_t msaa;
161
- DeltaTime dt{};
196
+
197
+ std::mutex mutex{};
198
+
199
+ struct {
200
+ LoadRequest request{};
201
+ UniqueTask<void ()> callback{};
202
+ } load{};
162
203
163
204
Impl (UniqueWin window, std::uint8_t msaa, bool validation)
164
205
: window(std::move(window), std::make_unique<DearImGui>(), msaa, validation), renderer(this ->window.gfx), scene(this ->window.gfx), msaa(msaa) {
165
206
s_instance = this ;
166
207
}
167
208
168
209
~Impl () {
210
+ load.request .future = {};
169
211
window.gfx .device .waitIdle ();
170
212
s_instance = {};
171
213
}
@@ -189,32 +231,96 @@ void Engine::add_shader(Shader shader) { m_impl->window.renderer.add_shader(std:
189
231
190
232
void Engine::show (bool reset_dt) {
191
233
glfwShowWindow (window ());
192
- if (reset_dt) { m_impl->dt = {} ; }
234
+ if (reset_dt) { m_impl->window . window . get (). glfw -> reset_dt () ; }
193
235
}
194
236
195
237
void Engine::hide () { glfwHideWindow (window ()); }
196
238
197
239
bool Engine::running () const { return !glfwWindowShouldClose (window ()); }
198
240
199
- float Engine::poll () {
200
- window ().glfw ->poll_events ();
241
+ auto Engine::poll () -> State const & {
242
+ // the code in this call locks the mutex, so it's not inlined here
243
+ update_load_request ();
244
+ // ImGui wants all widget calls within BeginFrame() / EndFrame(), so begin here
201
245
m_impl->window .gui ->new_frame ();
202
- return m_impl->dt ();
246
+ m_impl->window .window .get ().glfw ->poll_events ();
247
+ return m_impl.get ()->window .window .get ().state ();
203
248
}
204
249
205
250
void Engine::render () {
206
251
auto cb = vk::CommandBuffer{};
252
+ // we skip rendering the scene if acquiring a swapchain image fails (unlikely)
207
253
if (m_impl->window .renderer .next_frame ({&cb, 1 })) { m_impl->renderer .render (scene (), renderer (), cb); }
208
254
m_impl->window .gui ->end_frame ();
209
255
m_impl->window .renderer .render ();
210
256
}
211
257
212
258
void Engine::request_stop () { glfwSetWindowShouldClose (window (), GLFW_TRUE); }
213
259
260
+ glm::uvec2 Engine::window_extent () const { return m_impl->window .window .get ().window_extent (); }
261
+ glm::uvec2 Engine::framebuffer_extent () const { return m_impl->window .window .get ().framebuffer_extent (); }
262
+
263
+ bool Engine::load_async (std::string gltf_json_path, UniqueTask<void ()> on_loaded) {
264
+ if (!fs::is_regular_file (gltf_json_path)) {
265
+ // early return if file will fail to load anyway
266
+ logger::error (" [Engine] Invalid GLTF JSON path: [{}]" , gltf_json_path);
267
+ return false ;
268
+ }
269
+ // shared state will need to be accessed, lock the mutex
270
+ auto lock = std::scoped_lock{m_impl->mutex };
271
+ if (m_impl->load .request .future .valid ()) {
272
+ // we don't support discarding in-flight requests
273
+ logger::warn (" [Engine] Denied attempt to load_async when a load request is already in flight" );
274
+ return false ;
275
+ }
276
+
277
+ // ready to start loading
278
+ logger::info (" [Engine] Loading GLTF [{}]..." , State::to_filename (gltf_json_path));
279
+ // populate load request
280
+ m_impl->load .callback = std::move (on_loaded);
281
+ m_impl->load .request .path = std::move (gltf_json_path);
282
+ m_impl->load .request .status .store (LoadStatus::eStartingThread);
283
+ m_impl->load .request .start_time = time::since_start ();
284
+ auto func = [path = m_impl->load .request .path , gfx = m_impl->window .gfx , status = &m_impl->load .request .status ] {
285
+ auto scene = Scene{gfx};
286
+ if (!load_gltf (scene, path.c_str (), status)) { logger::error (" [Engine] Failed to load GLTF: [{}]" , path); }
287
+ // return the scene even on failure, it will be empty but valid
288
+ return scene;
289
+ };
290
+ // store future
291
+ m_impl->load .request .future = std::async (std::launch::async, func);
292
+ return true ;
293
+ }
294
+
295
+ LoadStatus Engine::load_status () const {
296
+ auto lock = std::scoped_lock{m_impl->mutex };
297
+ return m_impl->load .request .status .load ();
298
+ }
299
+
214
300
Scene& Engine::scene () const { return m_impl->scene ; }
215
- Gfx const & Engine::gfx () const { return m_impl->window .gfx ; }
216
- Glfw::Window const & Engine::window () const { return m_impl->window .window ; }
217
- Glfw::State const & Engine::state () const { return window ().state (); }
301
+ GLFWwindow* Engine::window () const { return m_impl->window .window .get (); }
302
+ Glfw::State const & Engine::state () const { return m_impl->window .window .get ().state (); }
218
303
Input const & Engine::input () const { return state ().input ; }
219
304
Renderer& Engine::renderer () const { return m_impl->window .renderer ; }
305
+
306
+ void Engine::update_load_request () {
307
+ auto lock = std::unique_lock{m_impl->mutex };
308
+ // early return if future isn't valid or is still busy
309
+ if (!ready (m_impl->load .request .future )) { return ; }
310
+
311
+ // transfer scene (under mutex lock)
312
+ m_impl->scene = m_impl->load .request .future .get ();
313
+ // reset load status
314
+ m_impl->load .request .status .store (LoadStatus::eNone);
315
+ // move out the path
316
+ auto path = std::move (m_impl->load .request .path );
317
+ // move out the callback
318
+ auto callback = std::move (m_impl->load .callback );
319
+ auto const duration = time::since_start () - m_impl->load .request .start_time ;
320
+ // unlock mutex to prevent possible deadlock (eg callback calls load_gltf again)
321
+ lock.unlock ();
322
+ logger::info (" ...GLTF [{}] loaded in [{:.2f}s]" , State::to_filename (path), duration);
323
+ // invoke callback
324
+ if (callback) { callback (); }
325
+ }
220
326
} // namespace facade
0 commit comments