11#include < backends/imgui_impl_glfw.h>
22#include < backends/imgui_impl_vulkan.h>
33#include < imgui.h>
4+ #include < djson/json.hpp>
45#include < facade/defines.hpp>
56#include < facade/engine/engine.hpp>
67#include < facade/engine/scene_renderer.hpp>
78#include < facade/glfw/glfw_wsi.hpp>
89#include < facade/render/renderer.hpp>
10+ #include < facade/util/data_provider.hpp>
911#include < facade/util/error.hpp>
12+ #include < facade/util/logger.hpp>
1013#include < facade/vk/cmd.hpp>
1114#include < facade/vk/vk.hpp>
1215#include < glm/gtc/color_space.hpp>
1316#include < glm/mat4x4.hpp>
17+ #include < filesystem>
18+ #include < future>
1419
1520namespace facade {
21+ namespace fs = std::filesystem;
22+
1623namespace {
1724static constexpr std::size_t command_buffers_v{1 };
1825
@@ -150,6 +157,34 @@ struct RenderWindow {
150157 : window(std::move(window)), vulkan(GlfwWsi{this ->window }, validation), gfx(vulkan.gfx()),
151158 renderer (gfx, this ->window, gui.get(), Renderer::CreateInfo{command_buffers_v, msaa}), gui(std::move(gui)) {}
152159};
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+ };
153188} // namespace
154189
155190struct Engine ::Impl {
@@ -158,14 +193,21 @@ struct Engine::Impl {
158193 Scene scene;
159194
160195 std::uint8_t msaa;
161- DeltaTime dt{};
196+
197+ std::mutex mutex{};
198+
199+ struct {
200+ LoadRequest request{};
201+ UniqueTask<void ()> callback{};
202+ } load{};
162203
163204 Impl (UniqueWin window, std::uint8_t msaa, bool validation)
164205 : window(std::move(window), std::make_unique<DearImGui>(), msaa, validation), renderer(this ->window.gfx), scene(this ->window.gfx), msaa(msaa) {
165206 s_instance = this ;
166207 }
167208
168209 ~Impl () {
210+ load.request .future = {};
169211 window.gfx .device .waitIdle ();
170212 s_instance = {};
171213 }
@@ -189,32 +231,96 @@ void Engine::add_shader(Shader shader) { m_impl->window.renderer.add_shader(std:
189231
190232void Engine::show (bool reset_dt) {
191233 glfwShowWindow (window ());
192- if (reset_dt) { m_impl->dt = {} ; }
234+ if (reset_dt) { m_impl->window . window . get (). glfw -> reset_dt () ; }
193235}
194236
195237void Engine::hide () { glfwHideWindow (window ()); }
196238
197239bool Engine::running () const { return !glfwWindowShouldClose (window ()); }
198240
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
201245 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 ();
203248}
204249
205250void Engine::render () {
206251 auto cb = vk::CommandBuffer{};
252+ // we skip rendering the scene if acquiring a swapchain image fails (unlikely)
207253 if (m_impl->window .renderer .next_frame ({&cb, 1 })) { m_impl->renderer .render (scene (), renderer (), cb); }
208254 m_impl->window .gui ->end_frame ();
209255 m_impl->window .renderer .render ();
210256}
211257
212258void Engine::request_stop () { glfwSetWindowShouldClose (window (), GLFW_TRUE); }
213259
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+
214300Scene& 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 (); }
218303Input const & Engine::input () const { return state ().input ; }
219304Renderer& 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+ }
220326} // namespace facade
0 commit comments