From 03886f50b70905fea247e45ee2783e3e66515d26 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Thu, 27 Oct 2022 12:35:37 -0700 Subject: [PATCH 1/5] Move DeltaTime to Glfw - Don't expose `Glfw::Window` through engine; users should not be able to access `Glfw` directly. - Return entire state in `Engine::poll()` instead of just delta time. --- lib/engine/include/facade/engine/engine.hpp | 10 +++++-- lib/engine/src/engine.cpp | 16 ++++++----- lib/glfw/include/facade/glfw/glfw.hpp | 2 ++ lib/glfw/src/glfw.cpp | 31 ++++++++++++++------- src/main.cpp | 7 +++-- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/lib/engine/include/facade/engine/engine.hpp b/lib/engine/include/facade/engine/engine.hpp index 275aeb3..63369f0 100644 --- a/lib/engine/include/facade/engine/engine.hpp +++ b/lib/engine/include/facade/engine/engine.hpp @@ -28,6 +28,7 @@ struct EngineCreateInfo { class Engine { public: using CreateInfo = EngineCreateInfo; + using State = Glfw::State; Engine(Engine&&) noexcept; Engine& operator=(Engine&&) noexcept; @@ -66,7 +67,7 @@ class Engine { /// /// \brief Poll events and obtain delta time /// - float poll(); + State const& poll(); /// /// \brief Render the scene /// @@ -77,12 +78,15 @@ class Engine { /// void request_stop(); + glm::uvec2 window_extent() const; + glm::uvec2 framebuffer_extent() const; + Scene& scene() const; Gfx const& gfx() const; - Glfw::Window const& window() const; - Glfw::State const& state() const; + State const& state() const; Input const& input() const; Renderer& renderer() const; + GLFWwindow* window() const; private: struct Impl; diff --git a/lib/engine/src/engine.cpp b/lib/engine/src/engine.cpp index 40e7f27..c28e2af 100644 --- a/lib/engine/src/engine.cpp +++ b/lib/engine/src/engine.cpp @@ -158,7 +158,6 @@ struct Engine::Impl { Scene scene; std::uint8_t msaa; - DeltaTime dt{}; Impl(UniqueWin window, std::uint8_t msaa, bool validation) : window(std::move(window), std::make_unique(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx), msaa(msaa) { @@ -189,17 +188,17 @@ void Engine::add_shader(Shader shader) { m_impl->window.renderer.add_shader(std: void Engine::show(bool reset_dt) { glfwShowWindow(window()); - if (reset_dt) { m_impl->dt = {}; } + if (reset_dt) { m_impl->window.window.get().glfw->reset_dt(); } } void Engine::hide() { glfwHideWindow(window()); } bool Engine::running() const { return !glfwWindowShouldClose(window()); } -float Engine::poll() { - window().glfw->poll_events(); +auto Engine::poll() -> State const& { m_impl->window.gui->new_frame(); - return m_impl->dt(); + m_impl->window.window.get().glfw->poll_events(); + return m_impl.get()->window.window.get().state(); } void Engine::render() { @@ -211,10 +210,13 @@ void Engine::render() { void Engine::request_stop() { glfwSetWindowShouldClose(window(), GLFW_TRUE); } +glm::uvec2 Engine::window_extent() const { return m_impl->window.window.get().window_extent(); } +glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get().framebuffer_extent(); } + Scene& Engine::scene() const { return m_impl->scene; } Gfx const& Engine::gfx() const { return m_impl->window.gfx; } -Glfw::Window const& Engine::window() const { return m_impl->window.window; } -Glfw::State const& Engine::state() const { return window().state(); } +GLFWwindow* Engine::window() const { return m_impl->window.window.get(); } +Glfw::State const& Engine::state() const { return m_impl->window.window.get().state(); } Input const& Engine::input() const { return state().input; } Renderer& Engine::renderer() const { return m_impl->window.renderer; } } // namespace facade diff --git a/lib/glfw/include/facade/glfw/glfw.hpp b/lib/glfw/include/facade/glfw/glfw.hpp index 4a36e40..0365aad 100644 --- a/lib/glfw/include/facade/glfw/glfw.hpp +++ b/lib/glfw/include/facade/glfw/glfw.hpp @@ -18,6 +18,7 @@ struct Glfw { std::vector vk_extensions() const; void poll_events(); + void reset_dt(); bool operator==(Glfw const&) const = default; }; @@ -27,6 +28,7 @@ using UniqueWin = Unique; struct Glfw::State { Input input{}; std::vector file_drops{}; + float dt{}; }; struct Glfw::Window { diff --git a/lib/glfw/src/glfw.cpp b/lib/glfw/src/glfw.cpp index 3bb4189..5ec4b13 100644 --- a/lib/glfw/src/glfw.cpp +++ b/lib/glfw/src/glfw.cpp @@ -1,14 +1,21 @@ #include #include +#include +#include #include #include namespace facade { +namespace fs = std::filesystem; + namespace { std::weak_ptr g_glfw{}; std::mutex g_mutex{}; -std::unordered_map g_states{}; +struct { + std::unordered_map states{}; + DeltaTime dt{}; +} g_states{}; std::shared_ptr get_or_make_glfw() { auto lock = std::scoped_lock{g_mutex}; @@ -35,14 +42,18 @@ constexpr Action to_action(int glfw_action) { } // namespace void Glfw::poll_events() { - for (auto& [_, state] : g_states) { + auto const dt = g_states.dt(); + for (auto& [_, state] : g_states.states) { state.input.keyboard.next_frame(); state.input.mouse.next_frame(); state.file_drops.clear(); + state.dt = dt; } glfwPollEvents(); } +void Glfw::reset_dt() { g_states.dt = {}; } + auto Glfw::Window::make() -> UniqueWin { auto ret = Window{}; ret.glfw = get_or_make_glfw(); @@ -52,13 +63,13 @@ auto Glfw::Window::make() -> UniqueWin { ret.win = glfwCreateWindow(1, 1, "[untitled]", nullptr, nullptr); if (!ret.win) { throw InitError{"GLFW window creation failed"}; } if (glfwRawMouseMotionSupported()) { glfwSetInputMode(ret, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); } - glfwSetKeyCallback(ret, [](GLFWwindow* w, int key, int, int action, int) { g_states[w].input.keyboard.on_key(key, to_action(action)); }); - glfwSetMouseButtonCallback(ret, [](GLFWwindow* w, int button, int action, int) { g_states[w].input.mouse.on_button(button, to_action(action)); }); - glfwSetCursorPosCallback(ret, [](GLFWwindow* w, double x, double y) { g_states[w].input.mouse.on_position(glm::tvec2{x, y}); }); - glfwSetScrollCallback(ret, [](GLFWwindow* w, double x, double y) { g_states[w].input.mouse.on_scroll(glm::tvec2{x, y}); }); + glfwSetKeyCallback(ret, [](GLFWwindow* w, int key, int, int action, int) { g_states.states[w].input.keyboard.on_key(key, to_action(action)); }); + glfwSetMouseButtonCallback(ret, [](GLFWwindow* w, int button, int action, int) { g_states.states[w].input.mouse.on_button(button, to_action(action)); }); + glfwSetCursorPosCallback(ret, [](GLFWwindow* w, double x, double y) { g_states.states[w].input.mouse.on_position(glm::tvec2{x, y}); }); + glfwSetScrollCallback(ret, [](GLFWwindow* w, double x, double y) { g_states.states[w].input.mouse.on_scroll(glm::tvec2{x, y}); }); glfwSetDropCallback(ret, [](GLFWwindow* w, int count, char const** paths) { - auto& file_drops = g_states[w].file_drops; - for (int i = 0; i < count; ++i) { file_drops.push_back(paths[i]); } + auto& file_drops = g_states.states[w].file_drops; + for (int i = 0; i < count; ++i) { file_drops.push_back(fs::absolute(paths[i]).generic_string()); } }); return ret; } @@ -70,7 +81,7 @@ void Glfw::Deleter::operator()(Glfw const& glfw) const { void Glfw::Deleter::operator()(Window const& window) const { glfwDestroyWindow(window.win); - g_states.erase(window.win); + g_states.states.erase(window.win); } std::vector Glfw::vk_extensions() const { @@ -95,6 +106,6 @@ glm::uvec2 Glfw::Window::framebuffer_extent() const { Glfw::State const& Glfw::Window::state() const { auto lock = std::scoped_lock{g_mutex}; - return g_states[win]; + return g_states.states[win]; } } // namespace facade diff --git a/src/main.cpp b/src/main.cpp index 61d06ee..9f803ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -261,9 +261,9 @@ void run() { auto main_menu = MainMenu{}; while (engine->running()) { - auto const dt = engine->poll(); - auto const& state = engine->state(); + auto const& state = engine->poll(); auto const& input = state.input; + auto const dt = state.dt; bool const mouse_look = input.mouse.held(GLFW_MOUSE_BUTTON_RIGHT); if (input.keyboard.pressed(GLFW_KEY_ESCAPE)) { engine->request_stop(); } @@ -271,7 +271,10 @@ void run() { if (!state.file_drops.empty()) { auto load = [file = state.file_drops.front(), &engine] { + auto name = file; + if (auto i = name.find_last_of('/'); i != std::string::npos) { name = name.substr(i + 1); } auto scene = Scene{engine->gfx()}; + logger::info("Loading GLTF [{}]...", name); if (!load_gltf(scene, file)) { logger::warn("Failed to load GLTF: [{}]", file); } scene.dir_lights.push_back(DirLight{.direction = glm::normalize(glm::vec3{-1.0f, -1.0f, -1.0f}), .diffuse = glm::vec3{5.0f}}); return scene; From 02e3b9b03bfe649fe8a94c595e1886837a6ff392 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Thu, 27 Oct 2022 21:06:08 -0700 Subject: [PATCH 2/5] Add Engine::load_async Takes an optional `on_done` callback; loaded scene swapped and callback invoked on poll thread (during `poll()`). TODO: solve progress notification across the threads. --- lib/engine/include/facade/engine/engine.hpp | 6 +- lib/engine/src/engine.cpp | 66 ++++++++++++++++++++- lib/scene/include/facade/scene/scene.hpp | 39 ++++++------ lib/scene/src/scene.cpp | 40 +++++++------ src/main.cpp | 36 ++++------- 5 files changed, 122 insertions(+), 65 deletions(-) diff --git a/lib/engine/include/facade/engine/engine.hpp b/lib/engine/include/facade/engine/engine.hpp index 63369f0..ba81944 100644 --- a/lib/engine/include/facade/engine/engine.hpp +++ b/lib/engine/include/facade/engine/engine.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace facade { struct Gfx; @@ -78,17 +79,20 @@ class Engine { /// void request_stop(); + bool load_async(std::string gltf_json_path, std::function on_loaded = {}); + glm::uvec2 window_extent() const; glm::uvec2 framebuffer_extent() const; Scene& scene() const; - Gfx const& gfx() const; State const& state() const; Input const& input() const; Renderer& renderer() const; GLFWwindow* window() const; private: + void update_futures(); + struct Impl; inline static Impl const* s_instance{}; std::unique_ptr m_impl{}; diff --git a/lib/engine/src/engine.cpp b/lib/engine/src/engine.cpp index c28e2af..8beccf2 100644 --- a/lib/engine/src/engine.cpp +++ b/lib/engine/src/engine.cpp @@ -1,18 +1,25 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include #include #include +#include +#include namespace facade { +namespace fs = std::filesystem; + namespace { static constexpr std::size_t command_buffers_v{1}; @@ -150,6 +157,27 @@ struct RenderWindow { : window(std::move(window)), vulkan(GlfwWsi{this->window}, validation), gfx(vulkan.gfx()), renderer(gfx, this->window, gui.get(), Renderer::CreateInfo{command_buffers_v, msaa}), gui(std::move(gui)) {} }; + +bool load_gltf(Scene& out_scene, char const* path) { + auto const provider = FileDataProvider::mount_parent_dir(path); + auto json = dj::Json::from_file(path); + return out_scene.load_gltf(json, provider); +} + +template +bool ready(std::future const& future) { + return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::ready; +} + +template +bool timeout(std::future const& future) { + return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::timeout; +} + +template +bool busy(std::future const& future) { + return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::deferred; +} } // namespace struct Engine::Impl { @@ -159,6 +187,11 @@ struct Engine::Impl { std::uint8_t msaa; + std::vector> discard{}; + std::future future{}; + std::mutex mutex{}; + std::function on_loaded{}; + Impl(UniqueWin window, std::uint8_t msaa, bool validation) : window(std::move(window), std::make_unique(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx), msaa(msaa) { s_instance = this; @@ -196,6 +229,7 @@ void Engine::hide() { glfwHideWindow(window()); } bool Engine::running() const { return !glfwWindowShouldClose(window()); } auto Engine::poll() -> State const& { + update_futures(); m_impl->window.gui->new_frame(); m_impl->window.window.get().glfw->poll_events(); return m_impl.get()->window.window.get().state(); @@ -213,10 +247,40 @@ void Engine::request_stop() { glfwSetWindowShouldClose(window(), GLFW_TRUE); } glm::uvec2 Engine::window_extent() const { return m_impl->window.window.get().window_extent(); } glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get().framebuffer_extent(); } +bool Engine::load_async(std::string gltf_json_path, std::function on_loaded) { + if (!fs::is_regular_file(gltf_json_path)) { return false; } + auto lock = std::scoped_lock{m_impl->mutex}; + m_impl->on_loaded = std::move(on_loaded); + if (m_impl->future.valid()) { m_impl->discard.push_back(std::move(m_impl->future)); } + auto path = gltf_json_path; + if (auto const i = path.find_last_of('/'); i != std::string::npos) { path = path.substr(i + 1); } + logger::info("[Engine] Loading GLTF [{}]...", path); + auto func = [this, path = std::move(gltf_json_path)] { + auto scene = Scene{m_impl->window.gfx}; + if (!load_gltf(scene, path.c_str())) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } + return scene; + }; + m_impl->future = std::async(std::launch::async, func); + return true; +} + Scene& Engine::scene() const { return m_impl->scene; } -Gfx const& Engine::gfx() const { return m_impl->window.gfx; } GLFWwindow* Engine::window() const { return m_impl->window.window.get(); } Glfw::State const& Engine::state() const { return m_impl->window.window.get().state(); } Input const& Engine::input() const { return state().input; } Renderer& Engine::renderer() const { return m_impl->window.renderer; } + +void Engine::update_futures() { + auto lock = std::scoped_lock{m_impl->mutex}; + std::erase_if(m_impl->discard, [](std::future const& future) { return !busy(future); }); + if (ready(m_impl->future)) { + m_impl->scene = m_impl->future.get(); + // TODO: log time + logger::info("...GLTF loaded"); + if (m_impl->on_loaded) { + m_impl->on_loaded(); + m_impl->on_loaded = {}; + } + } +} } // namespace facade diff --git a/lib/scene/include/facade/scene/scene.hpp b/lib/scene/include/facade/scene/scene.hpp index e9a557a..ede754f 100644 --- a/lib/scene/include/facade/scene/scene.hpp +++ b/lib/scene/include/facade/scene/scene.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -36,28 +37,39 @@ struct DataProvider; class Scene { public: + struct Tree { + struct Data { + std::vector roots{}; + }; + + std::vector roots{}; + Id camera{}; + Id id{}; + }; + static constexpr auto id_v = Id{0}; explicit Scene(Gfx const& gfx); bool load_gltf(dj::Json const& root, DataProvider const& provider) noexcept(false); - bool load_gltf(std::string_view path); Id add(Camera camera); Id add(Sampler sampler); Id add(std::unique_ptr material); - Id add(StaticMesh mesh); - Id add(Image image); + Id add(Geometry const& geometry); + Id add(Image::View image, Id sampler, ColourSpace colour_space = ColourSpace::eSrgb); Id add(Mesh mesh); Id add(Node node, Id parent); - Id id() const { return m_tree.id; } - std::size_t scene_count() const { return m_storage.data.trees.size(); } - bool load(Id id); + Id tree_id() const { return m_tree.id; } + std::size_t tree_count() const { return m_storage.data.trees.size(); } + bool load(Id id); Ptr find(Id id) const; Ptr find(Id id); Ptr find(Id id) const; + Ptr find(Id id) const; + Ptr find(Id id) const; Ptr find(Id id) const; std::span roots() { return m_tree.roots; } std::span roots() const { return m_tree.roots; } @@ -75,16 +87,6 @@ class Scene { private: struct TreeBuilder; - struct Tree { - struct Data { - std::vector roots{}; - }; - - std::vector roots{}; - Id camera{}; - Id id{}; - }; - struct Data { std::vector nodes{}; std::vector trees{}; @@ -95,16 +97,15 @@ class Scene { std::vector samplers{}; std::vector> materials{}; std::vector static_meshes{}; - std::vector images{}; std::vector textures{}; std::vector meshes{}; - std::vector instances{}; + Data data{}; Id next_node{}; }; void add_default_camera(); - bool load_tree(Id id); + bool load_tree(Id id); Id add_unchecked(Mesh mesh); Id add_unchecked(std::vector& out, Node&& node); static Node const* find_node(std::span nodes, Id id); diff --git a/lib/scene/src/scene.cpp b/lib/scene/src/scene.cpp index e1900ac..54a928c 100644 --- a/lib/scene/src/scene.cpp +++ b/lib/scene/src/scene.cpp @@ -72,10 +72,6 @@ Mesh to_mesh(gltf::Mesh const& mesh) { return ret; } -Texture to_texture(Gfx const& gfx, vk::Sampler sampler, Image const& image, gltf::Texture const& texture) { - return Texture{gfx, sampler, image.view(), Texture::CreateInfo{.mip_mapped = true, .colour_space = texture.colour_space}}; -} - struct Img1x1 { std::byte bytes[4]{}; @@ -129,7 +125,7 @@ struct Scene::TreeBuilder { if (set_cam) { camera = node_id; } } - Tree operator()(Tree::Data const& tree, Id id) { + Tree operator()(Tree::Data const& tree, Id id) { auto ret = Tree{.id = id}; for (auto const index : tree.roots) { assert(index < in_gnodes.size()); @@ -162,10 +158,9 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider) noexce for (auto gltf_camera : asset.cameras) { add(to_camera(std::move(gltf_camera))); } } - m_storage.images = std::move(asset.images); for (auto const& sampler : asset.samplers) { add(to_sampler(m_gfx, sampler)); } for (auto const& material : asset.materials) { add(to_material(material)); } - for (auto const& geometry : asset.geometries) { add(StaticMesh{m_gfx, geometry}); } + for (auto const& geometry : asset.geometries) { add(geometry); } for (auto const& mesh : asset.meshes) { add(to_mesh(mesh)); } auto get_sampler = [this](std::optional sampler_id) { @@ -173,7 +168,8 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider) noexce return m_storage.samplers[*sampler_id].sampler(); }; for (auto const& texture : asset.textures) { - m_storage.textures.push_back(to_texture(m_gfx, get_sampler(texture.sampler), m_storage.images.at(texture.source), texture)); + auto const tci = Texture::CreateInfo{.mip_mapped = true, .colour_space = texture.colour_space}; + m_storage.textures.emplace_back(m_gfx, get_sampler(texture.sampler), asset.images.at(texture.source), tci); } m_storage.data.nodes = std::move(asset.nodes); @@ -202,16 +198,23 @@ Id Scene::add(std::unique_ptr material) { return id; } -Id Scene::add(StaticMesh mesh) { +Id Scene::add(Geometry const& geometry) { auto const id = m_storage.static_meshes.size(); - m_storage.static_meshes.push_back(std::move(mesh)); + m_storage.static_meshes.emplace_back(m_gfx, geometry); return id; } -Id Scene::add(Image image) { - auto const id = m_storage.images.size(); - m_storage.images.push_back(std::move(image)); - return id; +Id Scene::add(Image::View image, Id sampler_id, ColourSpace colour_space) { + auto sampler = [&] { + if (sampler_id >= m_storage.samplers.size()) { + logger::warn("[Scene] Invalid sampler id: [{}], using default", sampler_id.value()); + return default_sampler(); + } + return m_storage.samplers[sampler_id].sampler(); + }(); + auto const ret = m_storage.textures.size(); + m_storage.textures.emplace_back(m_gfx, sampler, image, Texture::CreateInfo{.colour_space = colour_space}); + return ret; } Id Scene::add(Mesh mesh) { @@ -226,8 +229,8 @@ Id Scene::add(Node node, Id parent) { throw Error{fmt::format("Scene {}: Invalid parent Node Id: {}", m_name, parent)}; } -bool Scene::load(Id id) { - if (id >= scene_count()) { return false; } +bool Scene::load(Id id) { + if (id >= tree_count()) { return false; } return load_tree(id); } @@ -235,7 +238,8 @@ Ptr Scene::find(Id id) { return const_cast(std::as_const(*thi Ptr Scene::find(Id id) const { return find_node(m_tree.roots, id); } Ptr Scene::find(Id id) const { return id >= m_storage.materials.size() ? nullptr : m_storage.materials[id].get(); } - +Ptr Scene::find(Id id) const { return id >= m_storage.static_meshes.size() ? nullptr : &m_storage.static_meshes[id]; } +Ptr Scene::find(Id id) const { return id >= m_storage.textures.size() ? nullptr : &m_storage.textures[id]; } Ptr Scene::find(Id id) const { return id >= m_storage.meshes.size() ? nullptr : &m_storage.meshes[id]; } bool Scene::select(Id id) { @@ -261,7 +265,7 @@ void Scene::add_default_camera() { m_tree.camera = add_unchecked(m_tree.roots, std::move(node)); } -bool Scene::load_tree(Id id) { +bool Scene::load_tree(Id id) { assert(id < m_storage.data.trees.size()); m_tree = TreeBuilder{*this, m_storage.data.nodes}(m_storage.data.trees[id], id); return true; diff --git a/src/main.cpp b/src/main.cpp index 9f803ec..6c44097 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,12 +26,6 @@ using namespace facade; namespace { -bool load_gltf(Scene& out, std::string_view path) { - auto const provider = FileDataProvider::mount_parent_dir(path); - auto json = dj::Json::from_file(path.data()); - return out.load_gltf(json, provider); -} - static constexpr auto test_json_v = R"( { "scene": 0, @@ -221,21 +215,20 @@ void run() { auto material_id = Id{}; auto node_id = Id{}; - auto post_scene_load = [&] { - engine->scene().camera().transform.set_position({0.0f, 0.0f, 5.0f}); + auto post_scene_load = [&](Scene& scene) { + scene.camera().transform.set_position({0.0f, 0.0f, 5.0f}); auto material = std::make_unique(); material->albedo = {1.0f, 0.0f, 0.0f}; - material_id = engine->scene().add(std::move(material)); - auto static_mesh_id = engine->scene().add(StaticMesh{engine->gfx(), make_cubed_sphere(1.0f, 32)}); - auto mesh_id = engine->scene().add(Mesh{.primitives = {Mesh::Primitive{static_mesh_id, material_id}}}); + material_id = scene.add(std::move(material)); + auto static_mesh_id = scene.add(make_cubed_sphere(1.0f, 32)); + auto mesh_id = scene.add(Mesh{.primitives = {Mesh::Primitive{static_mesh_id, material_id}}}); auto node = Node{}; node.attach(mesh_id); node.instances.emplace_back().set_position({1.0f, -5.0f, -20.0f}); node.instances.emplace_back().set_position({-1.0f, 1.0f, 0.0f}); - node_id = engine->scene().add(std::move(node), 0); - engine->show(true); + node_id = scene.add(std::move(node), 0); }; auto init = [&] { @@ -247,11 +240,11 @@ void run() { engine->add_shader(lit); engine->add_shader(shaders::unlit()); - auto scene = Scene{engine->gfx()}; + auto& scene = engine->scene(); scene.dir_lights.push_back(DirLight{.direction = glm::normalize(glm::vec3{-1.0f, -1.0f, -1.0f}), .diffuse = glm::vec3{5.0f}}); scene.load_gltf(dj::Json::parse(test_json_v), DummyDataProvider{}); - engine->scene() = std::move(scene); - post_scene_load(); + post_scene_load(engine->scene()); + engine->show(true); }; init(); @@ -270,16 +263,7 @@ void run() { glfwSetInputMode(engine->window(), GLFW_CURSOR, mouse_look ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); if (!state.file_drops.empty()) { - auto load = [file = state.file_drops.front(), &engine] { - auto name = file; - if (auto i = name.find_last_of('/'); i != std::string::npos) { name = name.substr(i + 1); } - auto scene = Scene{engine->gfx()}; - logger::info("Loading GLTF [{}]...", name); - if (!load_gltf(scene, file)) { logger::warn("Failed to load GLTF: [{}]", file); } - scene.dir_lights.push_back(DirLight{.direction = glm::normalize(glm::vec3{-1.0f, -1.0f, -1.0f}), .diffuse = glm::vec3{5.0f}}); - return scene; - }; - load(); + engine->load_async(state.file_drops.front(), [&] { post_scene_load(engine->scene()); }); } auto& camera = engine->scene().camera(); From 7092b7e005499a0c624fbb11063113eafa5e166d Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Thu, 27 Oct 2022 22:56:43 -0700 Subject: [PATCH 3/5] Add LoadStatus --- .../include/facade/engine/editor/common.hpp | 1 + lib/engine/include/facade/engine/engine.hpp | 1 + lib/engine/src/editor/common.cpp | 2 + lib/engine/src/engine.cpp | 48 +++++++++++++------ lib/scene/CMakeLists.txt | 1 + .../include/facade/scene/load_status.hpp | 48 +++++++++++++++++++ lib/scene/include/facade/scene/scene.hpp | 3 +- lib/scene/src/detail/gltf.cpp | 22 +++++++-- lib/scene/src/detail/gltf.hpp | 4 +- lib/scene/src/scene.cpp | 16 +++++-- src/main.cpp | 9 ++++ 11 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 lib/scene/include/facade/scene/load_status.hpp diff --git a/lib/engine/include/facade/engine/editor/common.hpp b/lib/engine/include/facade/engine/editor/common.hpp index 45790ff..bf1ca6c 100644 --- a/lib/engine/include/facade/engine/editor/common.hpp +++ b/lib/engine/include/facade/engine/editor/common.hpp @@ -93,6 +93,7 @@ class Popup : public Openable { explicit Popup(char const* id, int flags = {}); ~Popup(); + static void open(char const* id); static void close_current(); }; diff --git a/lib/engine/include/facade/engine/engine.hpp b/lib/engine/include/facade/engine/engine.hpp index ba81944..90e81dd 100644 --- a/lib/engine/include/facade/engine/engine.hpp +++ b/lib/engine/include/facade/engine/engine.hpp @@ -80,6 +80,7 @@ class Engine { void request_stop(); bool load_async(std::string gltf_json_path, std::function on_loaded = {}); + LoadStatus load_status() const; glm::uvec2 window_extent() const; glm::uvec2 framebuffer_extent() const; diff --git a/lib/engine/src/editor/common.cpp b/lib/engine/src/editor/common.cpp index 5e643ed..3429350 100644 --- a/lib/engine/src/editor/common.cpp +++ b/lib/engine/src/editor/common.cpp @@ -43,6 +43,8 @@ Popup::~Popup() { if (m_open) { ImGui::EndPopup(); } } +void Popup::open(char const* id) { ImGui::OpenPopup(id); } + void Popup::close_current() { ImGui::CloseCurrentPopup(); } Menu::Menu(NotClosed, char const* label, bool enabled) : Openable(ImGui::BeginMenu(label, enabled)) {} diff --git a/lib/engine/src/engine.cpp b/lib/engine/src/engine.cpp index 8beccf2..475ee6d 100644 --- a/lib/engine/src/engine.cpp +++ b/lib/engine/src/engine.cpp @@ -158,10 +158,10 @@ struct RenderWindow { renderer(gfx, this->window, gui.get(), Renderer::CreateInfo{command_buffers_v, msaa}), gui(std::move(gui)) {} }; -bool load_gltf(Scene& out_scene, char const* path) { +bool load_gltf(Scene& out_scene, char const* path, std::atomic* out_status) { auto const provider = FileDataProvider::mount_parent_dir(path); auto json = dj::Json::from_file(path); - return out_scene.load_gltf(json, provider); + return out_scene.load_gltf(json, provider, out_status); } template @@ -178,6 +178,11 @@ template bool busy(std::future const& future) { return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::deferred; } + +struct LoadRequest { + std::future future{}; + std::unique_ptr> status{}; +}; } // namespace struct Engine::Impl { @@ -187,10 +192,13 @@ struct Engine::Impl { std::uint8_t msaa; - std::vector> discard{}; - std::future future{}; std::mutex mutex{}; - std::function on_loaded{}; + + struct { + std::vector discard{}; + LoadRequest request{}; + std::function on_loaded{}; + } load{}; Impl(UniqueWin window, std::uint8_t msaa, bool validation) : window(std::move(window), std::make_unique(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx), msaa(msaa) { @@ -198,6 +206,8 @@ struct Engine::Impl { } ~Impl() { + load.discard.clear(); + load.request = {}; window.gfx.device.waitIdle(); s_instance = {}; } @@ -250,20 +260,27 @@ glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get bool Engine::load_async(std::string gltf_json_path, std::function on_loaded) { if (!fs::is_regular_file(gltf_json_path)) { return false; } auto lock = std::scoped_lock{m_impl->mutex}; - m_impl->on_loaded = std::move(on_loaded); - if (m_impl->future.valid()) { m_impl->discard.push_back(std::move(m_impl->future)); } + m_impl->load.on_loaded = std::move(on_loaded); + if (m_impl->load.request.future.valid()) { m_impl->load.discard.push_back(std::move(m_impl->load.request)); } auto path = gltf_json_path; if (auto const i = path.find_last_of('/'); i != std::string::npos) { path = path.substr(i + 1); } logger::info("[Engine] Loading GLTF [{}]...", path); + if (!m_impl->load.request.status) { m_impl->load.request.status = std::make_unique>(); } + m_impl->load.request.status->store(LoadStatus::eStartingThread); auto func = [this, path = std::move(gltf_json_path)] { auto scene = Scene{m_impl->window.gfx}; - if (!load_gltf(scene, path.c_str())) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } + if (!load_gltf(scene, path.c_str(), m_impl->load.request.status.get())) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } return scene; }; - m_impl->future = std::async(std::launch::async, func); + m_impl->load.request.future = std::async(std::launch::async, func); return true; } +LoadStatus Engine::load_status() const { + if (!m_impl->load.request.status) { return LoadStatus::eNone; } + return m_impl->load.request.status->load(); +} + Scene& Engine::scene() const { return m_impl->scene; } GLFWwindow* Engine::window() const { return m_impl->window.window.get(); } Glfw::State const& Engine::state() const { return m_impl->window.window.get().state(); } @@ -272,15 +289,16 @@ Renderer& Engine::renderer() const { return m_impl->window.renderer; } void Engine::update_futures() { auto lock = std::scoped_lock{m_impl->mutex}; - std::erase_if(m_impl->discard, [](std::future const& future) { return !busy(future); }); - if (ready(m_impl->future)) { - m_impl->scene = m_impl->future.get(); + std::erase_if(m_impl->load.discard, [](LoadRequest const& request) { return !busy(request.future); }); + if (ready(m_impl->load.request.future)) { + m_impl->scene = m_impl->load.request.future.get(); // TODO: log time logger::info("...GLTF loaded"); - if (m_impl->on_loaded) { - m_impl->on_loaded(); - m_impl->on_loaded = {}; + if (m_impl->load.on_loaded) { + m_impl->load.on_loaded(); + m_impl->load.on_loaded = {}; } + m_impl->load.request.status->store(LoadStatus::eNone); } } } // namespace facade diff --git a/lib/scene/CMakeLists.txt b/lib/scene/CMakeLists.txt index 0a0a2d3..25bd881 100644 --- a/lib/scene/CMakeLists.txt +++ b/lib/scene/CMakeLists.txt @@ -37,6 +37,7 @@ target_sources(${PROJECT_NAME} PRIVATE include/${target_prefix}/scene/fly_cam.hpp include/${target_prefix}/scene/id.hpp include/${target_prefix}/scene/lights.hpp + include/${target_prefix}/scene/load_status.hpp include/${target_prefix}/scene/material.hpp include/${target_prefix}/scene/node_data.hpp include/${target_prefix}/scene/node.hpp diff --git a/lib/scene/include/facade/scene/load_status.hpp b/lib/scene/include/facade/scene/load_status.hpp new file mode 100644 index 0000000..78069d8 --- /dev/null +++ b/lib/scene/include/facade/scene/load_status.hpp @@ -0,0 +1,48 @@ +#pragma once +#include +#include + +namespace facade { +enum class LoadStatus : std::uint8_t { + eNone, + eStartingThread, + eParsingBuffers, + eParsingBufferViews, + eParsingAccessors, + eParsingCameras, + eParsingSamplers, + eLoadingImages, + eParsingTextures, + eParsingMeshes, + eParsingMaterials, + eBuildingGeometry, + eBuildingNodes, + eBuildingScenes, + eUploadingResources, + eReady, + eCOUNT_, +}; + +constexpr auto load_status_str = EnumArray{ + "None", + "Starting Thread", + "Parsing Buffers", + "Parsing BufferViews", + "Parsing Accessors", + "Parsing Cameras", + "Parsing Samplers", + "Loading Images", + "Parsing Textures", + "Parsing Meshes", + "Parsing Materials", + "Building Geometry", + "Building Nodes", + "Building Scenes", + "Uploading Resources", + "Ready", +}; + +static_assert(std::size(load_status_str.t) == static_cast(LoadStatus::eCOUNT_)); + +constexpr float load_progress(LoadStatus const stage) { return static_cast(stage) / static_cast(LoadStatus::eReady); } +} // namespace facade diff --git a/lib/scene/include/facade/scene/scene.hpp b/lib/scene/include/facade/scene/scene.hpp index ede754f..7118edc 100644 --- a/lib/scene/include/facade/scene/scene.hpp +++ b/lib/scene/include/facade/scene/scene.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ class Scene { explicit Scene(Gfx const& gfx); - bool load_gltf(dj::Json const& root, DataProvider const& provider) noexcept(false); + bool load_gltf(dj::Json const& root, DataProvider const& provider, std::atomic* out_status = {}) noexcept(false); Id add(Camera camera); Id add(Sampler sampler); diff --git a/lib/scene/src/detail/gltf.cpp b/lib/scene/src/detail/gltf.cpp index df6810c..362f57b 100644 --- a/lib/scene/src/detail/gltf.cpp +++ b/lib/scene/src/detail/gltf.cpp @@ -466,19 +466,27 @@ struct Data { m.double_sided = json["doubleSided"].as_bool(dj::Boolean{m.double_sided}).value; } - Storage parse(dj::Json const& scene) { + Storage parse(dj::Json const& scene, std::atomic& out_status) { storage = {}; + out_status = LoadStatus::eParsingBuffers; for (auto const& b : scene["buffers"].array_view()) { buffer(b); } + out_status = LoadStatus::eParsingBufferViews; for (auto const& bv : scene["bufferViews"].array_view()) { buffer_view(bv); } + out_status = LoadStatus::eParsingAccessors; for (auto const& s : scene["accessors"].array_view()) { accessor(s); } + out_status = LoadStatus::eParsingCameras; for (auto const& c : scene["cameras"].array_view()) { camera(c); } + out_status = LoadStatus::eParsingSamplers; for (auto const& s : scene["samplers"].array_view()) { sampler(s); } + out_status = LoadStatus::eLoadingImages; for (auto const& i : scene["images"].array_view()) { image(i); } + out_status = LoadStatus::eParsingTextures; for (auto const& t : scene["textures"].array_view()) { texture(t); } - for (auto const& m : scene["materials"].array_view()) { material(m); } - + out_status = LoadStatus::eParsingMeshes; for (auto const& m : scene["meshes"].array_view()) { mesh(m); } + out_status = LoadStatus::eParsingMaterials; + for (auto const& m : scene["materials"].array_view()) { material(m); } // Texture will use ColourSpace::sRGB by default; change non-colour textures to be linear auto set_linear = [this](std::size_t index) { storage.textures.at(index).colour_space = ColourSpace::eLinear; }; @@ -573,10 +581,12 @@ std::vector children(dj::Json const& json) { } } // namespace -Asset Asset::parse(dj::Json const& json, DataProvider const& provider) { +Asset Asset::parse(dj::Json const& json, DataProvider const& provider, std::atomic& out_status) { auto ret = Asset{}; - auto storage = Data::Parser{provider}.parse(json); + auto storage = Data::Parser{provider}.parse(json, out_status); if (storage.accessors.empty()) { return {}; } + + out_status = LoadStatus::eBuildingGeometry; ret.cameras = std::move(storage.cameras); ret.images = std::move(storage.images); ret.materials = std::move(storage.materials); @@ -593,6 +603,7 @@ Asset Asset::parse(dj::Json const& json, DataProvider const& provider) { } } + out_status = LoadStatus::eBuildingNodes; auto const& nodes = json["nodes"].array_view(); ret.nodes.reserve(nodes.size()); for (auto const& node : nodes) { @@ -608,6 +619,7 @@ Asset Asset::parse(dj::Json const& json, DataProvider const& provider) { ret.nodes.push_back(Node{node["name"].as(), transform(node), children(node["children"]), index, type}); } + out_status = LoadStatus::eBuildingScenes; auto const& scenes = json["scenes"].array_view(); ret.scenes.reserve(scenes.size()); for (auto const& scene : scenes) { diff --git a/lib/scene/src/detail/gltf.hpp b/lib/scene/src/detail/gltf.hpp index 4a1077e..c5f0a2d 100644 --- a/lib/scene/src/detail/gltf.hpp +++ b/lib/scene/src/detail/gltf.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -146,7 +148,7 @@ struct Asset { std::vector scenes{}; std::size_t start_scene{}; - static Asset parse(dj::Json const& json, DataProvider const& provider); + static Asset parse(dj::Json const& json, DataProvider const& provider, std::atomic& out_status); }; } // namespace gltf } // namespace facade diff --git a/lib/scene/src/scene.cpp b/lib/scene/src/scene.cpp index 54a928c..11c475d 100644 --- a/lib/scene/src/scene.cpp +++ b/lib/scene/src/scene.cpp @@ -146,11 +146,17 @@ struct Scene::TreeBuilder { } }; -bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider) noexcept(false) { - auto asset = gltf::Asset::parse(root, provider); - if (asset.geometries.empty() || asset.scenes.empty()) { return false; } +bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider, std::atomic* out_status) noexcept(false) { + auto status = std::atomic{}; + if (!out_status) { out_status = &status; } + auto asset = gltf::Asset::parse(root, provider, *out_status); + if (asset.geometries.empty() || asset.scenes.empty()) { + *out_status = LoadStatus::eReady; + return false; + } if (asset.start_scene >= asset.scenes.size()) { throw Error{fmt::format("Invalid start scene: {}", asset.start_scene)}; } + *out_status = LoadStatus::eUploadingResources; m_storage = {}; if (asset.cameras.empty()) { add(Camera{.name = "default"}); @@ -175,7 +181,9 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider) noexce m_storage.data.nodes = std::move(asset.nodes); for (auto& scene : asset.scenes) { m_storage.data.trees.push_back(Tree::Data{.roots = std::move(scene.root_nodes)}); } - return load(asset.start_scene); + auto const ret = load(asset.start_scene); + *out_status = LoadStatus::eReady; + return ret; } Scene::Scene(Gfx const& gfx) : m_gfx(gfx), m_sampler(gfx) { add_default_camera(); } diff --git a/src/main.cpp b/src/main.cpp index 6c44097..790dc42 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -252,6 +252,7 @@ void run() { float const drot_z[] = {100.0f, -150.0f}; auto main_menu = MainMenu{}; + auto load_status = LoadStatus{}; while (engine->running()) { auto const& state = engine->poll(); @@ -264,6 +265,14 @@ void run() { if (!state.file_drops.empty()) { engine->load_async(state.file_drops.front(), [&] { post_scene_load(engine->scene()); }); + editor::Popup::open("Loading"); + } + load_status = engine->load_status(); + + if (auto popup = editor::Popup{"Loading"}) { + ImGui::Text("Loading..."); + ImGui::ProgressBar(load_progress(load_status), ImVec2{400.0f, 0}, load_status_str[load_status].data()); + if (load_status == LoadStatus::eReady || load_status == LoadStatus::eNone) { editor::Popup::close_current(); } } auto& camera = engine->scene().camera(); From 9a632e8062f87e26b9ccf96044f3b741ed6b2446 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Fri, 28 Oct 2022 10:04:32 -0700 Subject: [PATCH 4/5] Store only one future and progress Algorithm for deferring existing requests is buggy: get random malloc "unaligned fastbin chunk detected" crashes, attempts to root cause have been unsuccessful. Winding back to storing a single request and denying subsequent ones until the stored one is idle. --- .../include/facade/engine/editor/common.hpp | 13 ++++- lib/engine/include/facade/engine/engine.hpp | 4 +- lib/engine/src/editor/common.cpp | 2 +- lib/engine/src/engine.cpp | 56 ++++++++++--------- lib/glfw/include/facade/glfw/glfw.hpp | 2 + lib/glfw/src/glfw.cpp | 5 ++ .../include/facade/scene/load_status.hpp | 4 +- lib/scene/include/facade/scene/scene.hpp | 1 - lib/scene/src/scene.cpp | 4 +- lib/util/CMakeLists.txt | 1 + lib/util/include/facade/util/unique_task.hpp | 41 ++++++++++++++ src/main.cpp | 19 ++++--- 12 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 lib/util/include/facade/util/unique_task.hpp diff --git a/lib/engine/include/facade/engine/editor/common.hpp b/lib/engine/include/facade/engine/editor/common.hpp index bf1ca6c..88950c7 100644 --- a/lib/engine/include/facade/engine/editor/common.hpp +++ b/lib/engine/include/facade/engine/editor/common.hpp @@ -90,11 +90,22 @@ class MainMenu : public MenuBar { /// class Popup : public Openable { public: - explicit Popup(char const* id, int flags = {}); + explicit Popup(char const* id, int flags = {}) : Popup(id, false, flags) {} ~Popup(); static void open(char const* id); static void close_current(); + + protected: + explicit Popup(char const* id, bool modal, int flags); +}; + +/// +/// \brief RAII Dear ImGui PopupModal +/// +class Modal : public Popup { + public: + explicit Modal(char const* id, int flags = {}) : Popup(id, true, flags) {} }; /// diff --git a/lib/engine/include/facade/engine/engine.hpp b/lib/engine/include/facade/engine/engine.hpp index 90e81dd..5929cd5 100644 --- a/lib/engine/include/facade/engine/engine.hpp +++ b/lib/engine/include/facade/engine/engine.hpp @@ -2,8 +2,8 @@ #include #include #include +#include #include -#include namespace facade { struct Gfx; @@ -79,7 +79,7 @@ class Engine { /// void request_stop(); - bool load_async(std::string gltf_json_path, std::function on_loaded = {}); + bool load_async(std::string gltf_json_path, UniqueTask on_loaded = {}); LoadStatus load_status() const; glm::uvec2 window_extent() const; diff --git a/lib/engine/src/editor/common.cpp b/lib/engine/src/editor/common.cpp index 3429350..6efa27a 100644 --- a/lib/engine/src/editor/common.cpp +++ b/lib/engine/src/editor/common.cpp @@ -37,7 +37,7 @@ MainMenu::~MainMenu() { if (m_open) { ImGui::EndMainMenuBar(); } } -Popup::Popup(char const* id, int flags) : Openable(ImGui::BeginPopup(id, flags)) {} +Popup::Popup(char const* id, bool modal, int flags) : Openable(modal ? ImGui::BeginPopupModal(id, {}, flags) : ImGui::BeginPopup(id, flags)) {} Popup::~Popup() { if (m_open) { ImGui::EndPopup(); } diff --git a/lib/engine/src/engine.cpp b/lib/engine/src/engine.cpp index 475ee6d..5053043 100644 --- a/lib/engine/src/engine.cpp +++ b/lib/engine/src/engine.cpp @@ -180,8 +180,10 @@ bool busy(std::future const& future) { } struct LoadRequest { + std::string path{}; std::future future{}; - std::unique_ptr> status{}; + std::atomic status{}; + float start_time{}; }; } // namespace @@ -195,9 +197,8 @@ struct Engine::Impl { std::mutex mutex{}; struct { - std::vector discard{}; LoadRequest request{}; - std::function on_loaded{}; + UniqueTask callback{}; } load{}; Impl(UniqueWin window, std::uint8_t msaa, bool validation) @@ -206,8 +207,7 @@ struct Engine::Impl { } ~Impl() { - load.discard.clear(); - load.request = {}; + load.request.future = {}; window.gfx.device.waitIdle(); s_instance = {}; } @@ -257,19 +257,21 @@ void Engine::request_stop() { glfwSetWindowShouldClose(window(), GLFW_TRUE); } glm::uvec2 Engine::window_extent() const { return m_impl->window.window.get().window_extent(); } glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get().framebuffer_extent(); } -bool Engine::load_async(std::string gltf_json_path, std::function on_loaded) { +bool Engine::load_async(std::string gltf_json_path, UniqueTask on_loaded) { if (!fs::is_regular_file(gltf_json_path)) { return false; } auto lock = std::scoped_lock{m_impl->mutex}; - m_impl->load.on_loaded = std::move(on_loaded); - if (m_impl->load.request.future.valid()) { m_impl->load.discard.push_back(std::move(m_impl->load.request)); } - auto path = gltf_json_path; - if (auto const i = path.find_last_of('/'); i != std::string::npos) { path = path.substr(i + 1); } - logger::info("[Engine] Loading GLTF [{}]...", path); - if (!m_impl->load.request.status) { m_impl->load.request.status = std::make_unique>(); } - m_impl->load.request.status->store(LoadStatus::eStartingThread); - auto func = [this, path = std::move(gltf_json_path)] { - auto scene = Scene{m_impl->window.gfx}; - if (!load_gltf(scene, path.c_str(), m_impl->load.request.status.get())) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } + if (m_impl->load.request.future.valid()) { + logger::warn("[Engine] Denied attempt to load_async when a load request is already in flight"); + return false; + } + m_impl->load.callback = std::move(on_loaded); + m_impl->load.request.path = std::move(gltf_json_path); + logger::info("[Engine] Loading GLTF [{}]...", State::to_filename(m_impl->load.request.path)); + m_impl->load.request.status.store(LoadStatus::eStartingThread); + m_impl->load.request.start_time = time::since_start(); + auto func = [path = m_impl->load.request.path, gfx = m_impl->window.gfx, status = &m_impl->load.request.status] { + auto scene = Scene{gfx}; + if (!load_gltf(scene, path.c_str(), status)) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } return scene; }; m_impl->load.request.future = std::async(std::launch::async, func); @@ -277,8 +279,8 @@ bool Engine::load_async(std::string gltf_json_path, std::function on_loa } LoadStatus Engine::load_status() const { - if (!m_impl->load.request.status) { return LoadStatus::eNone; } - return m_impl->load.request.status->load(); + auto lock = std::scoped_lock{m_impl->mutex}; + return m_impl->load.request.status.load(); } Scene& Engine::scene() const { return m_impl->scene; } @@ -288,17 +290,17 @@ Input const& Engine::input() const { return state().input; } Renderer& Engine::renderer() const { return m_impl->window.renderer; } void Engine::update_futures() { - auto lock = std::scoped_lock{m_impl->mutex}; - std::erase_if(m_impl->load.discard, [](LoadRequest const& request) { return !busy(request.future); }); + auto lock = std::unique_lock{m_impl->mutex}; if (ready(m_impl->load.request.future)) { m_impl->scene = m_impl->load.request.future.get(); - // TODO: log time - logger::info("...GLTF loaded"); - if (m_impl->load.on_loaded) { - m_impl->load.on_loaded(); - m_impl->load.on_loaded = {}; - } - m_impl->load.request.status->store(LoadStatus::eNone); + m_impl->load.request.status.store(LoadStatus::eNone); + logger::info("...GLTF [{}] loaded in [{:.2f}s]", State::to_filename(m_impl->load.request.path), time::since_start() - m_impl->load.request.start_time); + // move out the callback + auto callback = std::move(m_impl->load.callback); + // unlock mutex to prevent possible deadlock (eg callback calls load_gltf again) + lock.unlock(); + // invoke callback + if (callback) { callback(); } } } } // namespace facade diff --git a/lib/glfw/include/facade/glfw/glfw.hpp b/lib/glfw/include/facade/glfw/glfw.hpp index 0365aad..fa93dce 100644 --- a/lib/glfw/include/facade/glfw/glfw.hpp +++ b/lib/glfw/include/facade/glfw/glfw.hpp @@ -29,6 +29,8 @@ struct Glfw::State { Input input{}; std::vector file_drops{}; float dt{}; + + static std::string to_filename(std::string_view path); }; struct Glfw::Window { diff --git a/lib/glfw/src/glfw.cpp b/lib/glfw/src/glfw.cpp index 5ec4b13..79bb4f6 100644 --- a/lib/glfw/src/glfw.cpp +++ b/lib/glfw/src/glfw.cpp @@ -54,6 +54,11 @@ void Glfw::poll_events() { void Glfw::reset_dt() { g_states.dt = {}; } +std::string Glfw::State::to_filename(std::string_view path) { + if (auto const i = path.find_last_of('/'); i != std::string_view::npos) { path = path.substr(i + 1); } + return std::string{path}; +} + auto Glfw::Window::make() -> UniqueWin { auto ret = Window{}; ret.glfw = get_or_make_glfw(); diff --git a/lib/scene/include/facade/scene/load_status.hpp b/lib/scene/include/facade/scene/load_status.hpp index 78069d8..d5bd0bd 100644 --- a/lib/scene/include/facade/scene/load_status.hpp +++ b/lib/scene/include/facade/scene/load_status.hpp @@ -19,7 +19,6 @@ enum class LoadStatus : std::uint8_t { eBuildingNodes, eBuildingScenes, eUploadingResources, - eReady, eCOUNT_, }; @@ -39,10 +38,9 @@ constexpr auto load_status_str = EnumArray{ "Building Nodes", "Building Scenes", "Uploading Resources", - "Ready", }; static_assert(std::size(load_status_str.t) == static_cast(LoadStatus::eCOUNT_)); -constexpr float load_progress(LoadStatus const stage) { return static_cast(stage) / static_cast(LoadStatus::eReady); } +constexpr float load_progress(LoadStatus const stage) { return static_cast(stage) / (static_cast(LoadStatus::eCOUNT_) - 1.0f); } } // namespace facade diff --git a/lib/scene/include/facade/scene/scene.hpp b/lib/scene/include/facade/scene/scene.hpp index 7118edc..071cc2f 100644 --- a/lib/scene/include/facade/scene/scene.hpp +++ b/lib/scene/include/facade/scene/scene.hpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/scene/src/scene.cpp b/lib/scene/src/scene.cpp index 11c475d..1893845 100644 --- a/lib/scene/src/scene.cpp +++ b/lib/scene/src/scene.cpp @@ -151,7 +151,7 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider, std::a if (!out_status) { out_status = &status; } auto asset = gltf::Asset::parse(root, provider, *out_status); if (asset.geometries.empty() || asset.scenes.empty()) { - *out_status = LoadStatus::eReady; + *out_status = LoadStatus::eNone; return false; } if (asset.start_scene >= asset.scenes.size()) { throw Error{fmt::format("Invalid start scene: {}", asset.start_scene)}; } @@ -182,7 +182,7 @@ bool Scene::load_gltf(dj::Json const& root, DataProvider const& provider, std::a for (auto& scene : asset.scenes) { m_storage.data.trees.push_back(Tree::Data{.roots = std::move(scene.root_nodes)}); } auto const ret = load(asset.start_scene); - *out_status = LoadStatus::eReady; + *out_status = LoadStatus::eNone; return ret; } diff --git a/lib/util/CMakeLists.txt b/lib/util/CMakeLists.txt index bc1a9d6..c31cc85 100644 --- a/lib/util/CMakeLists.txt +++ b/lib/util/CMakeLists.txt @@ -81,6 +81,7 @@ target_sources(${PROJECT_NAME} PRIVATE include/${target_prefix}/util/ring_buffer.hpp include/${target_prefix}/util/time.hpp include/${target_prefix}/util/type_id.hpp + include/${target_prefix}/util/unique_task.hpp include/${target_prefix}/util/unique.hpp include/${target_prefix}/util/visitor.hpp diff --git a/lib/util/include/facade/util/unique_task.hpp b/lib/util/include/facade/util/unique_task.hpp new file mode 100644 index 0000000..df8a260 --- /dev/null +++ b/lib/util/include/facade/util/unique_task.hpp @@ -0,0 +1,41 @@ +#pragma once +#include + +namespace facade { +template +class UniqueTask; + +/// +/// \brief Type erased move-only callable (discount std::move_only_function) +/// +template +class UniqueTask { + public: + UniqueTask() = default; + + template + requires(!std::same_as && std::is_invocable_r_v) + UniqueTask(T t) : m_func(std::make_unique>(std::move(t))) {} + + Ret operator()(Args... args) const { + assert(m_func); + return (*m_func)(args...); + } + + explicit operator bool() const { return m_func != nullptr; } + + private: + struct Base { + virtual ~Base() = default; + virtual void operator()() = 0; + }; + template + struct Func : Base { + F f; + Func(F&& f) : f(std::move(f)) {} + void operator()() final { f(); } + }; + + std::unique_ptr m_func{}; +}; +} // namespace facade diff --git a/src/main.cpp b/src/main.cpp index 790dc42..29d2da2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -252,7 +252,11 @@ void run() { float const drot_z[] = {100.0f, -150.0f}; auto main_menu = MainMenu{}; - auto load_status = LoadStatus{}; + + struct { + LoadStatus status{}; + std::string title{}; + } loading{}; while (engine->running()) { auto const& state = engine->poll(); @@ -265,14 +269,15 @@ void run() { if (!state.file_drops.empty()) { engine->load_async(state.file_drops.front(), [&] { post_scene_load(engine->scene()); }); - editor::Popup::open("Loading"); + loading.title = fmt::format("Loading {}...", state.to_filename(state.file_drops.front())); + editor::Popup::open(loading.title.c_str()); } - load_status = engine->load_status(); + loading.status = engine->load_status(); - if (auto popup = editor::Popup{"Loading"}) { - ImGui::Text("Loading..."); - ImGui::ProgressBar(load_progress(load_status), ImVec2{400.0f, 0}, load_status_str[load_status].data()); - if (load_status == LoadStatus::eReady || load_status == LoadStatus::eNone) { editor::Popup::close_current(); } + if (auto popup = editor::Modal{loading.title.c_str()}) { + ImGui::Text("%s", load_status_str[loading.status].data()); + ImGui::ProgressBar(load_progress(loading.status), ImVec2{400.0f, 0}, load_status_str[loading.status].data()); + if (loading.status == LoadStatus::eNone) { editor::Popup::close_current(); } } auto& camera = engine->scene().camera(); From 4aa9a0c0ca6b74f44b736a3e3433c9161b125347 Mon Sep 17 00:00:00 2001 From: Karn Kaul Date: Fri, 28 Oct 2022 10:22:25 -0700 Subject: [PATCH 5/5] Add some comments, fixup minor stuff --- lib/engine/include/facade/engine/engine.hpp | 12 ++++- lib/engine/src/engine.cpp | 50 +++++++++++++------ lib/glfw/src/glfw.cpp | 1 + .../include/facade/scene/load_status.hpp | 2 +- lib/scene/src/scene.cpp | 4 +- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/lib/engine/include/facade/engine/engine.hpp b/lib/engine/include/facade/engine/engine.hpp index 5929cd5..b96b2ca 100644 --- a/lib/engine/include/facade/engine/engine.hpp +++ b/lib/engine/include/facade/engine/engine.hpp @@ -66,7 +66,7 @@ class Engine { /// bool running() const; /// - /// \brief Poll events and obtain delta time + /// \brief Poll events and obtain updated state /// State const& poll(); /// @@ -79,7 +79,15 @@ class Engine { /// void request_stop(); + /// + /// \brief Load a GLTF scene asynchronously + /// + /// Subsequent requests will be rejected if one is in flight + /// bool load_async(std::string gltf_json_path, UniqueTask on_loaded = {}); + /// + /// \brief Obtain status of in-flight async load request (if active) + /// LoadStatus load_status() const; glm::uvec2 window_extent() const; @@ -92,7 +100,7 @@ class Engine { GLFWwindow* window() const; private: - void update_futures(); + void update_load_request(); struct Impl; inline static Impl const* s_instance{}; diff --git a/lib/engine/src/engine.cpp b/lib/engine/src/engine.cpp index 5053043..dfb08b8 100644 --- a/lib/engine/src/engine.cpp +++ b/lib/engine/src/engine.cpp @@ -239,7 +239,9 @@ void Engine::hide() { glfwHideWindow(window()); } bool Engine::running() const { return !glfwWindowShouldClose(window()); } auto Engine::poll() -> State const& { - update_futures(); + // the code in this call locks the mutex, so it's not inlined here + update_load_request(); + // ImGui wants all widget calls within BeginFrame() / EndFrame(), so begin here m_impl->window.gui->new_frame(); m_impl->window.window.get().glfw->poll_events(); return m_impl.get()->window.window.get().state(); @@ -247,6 +249,7 @@ auto Engine::poll() -> State const& { void Engine::render() { auto cb = vk::CommandBuffer{}; + // we skip rendering the scene if acquiring a swapchain image fails (unlikely) if (m_impl->window.renderer.next_frame({&cb, 1})) { m_impl->renderer.render(scene(), renderer(), cb); } m_impl->window.gui->end_frame(); m_impl->window.renderer.render(); @@ -258,22 +261,33 @@ glm::uvec2 Engine::window_extent() const { return m_impl->window.window.get().wi glm::uvec2 Engine::framebuffer_extent() const { return m_impl->window.window.get().framebuffer_extent(); } bool Engine::load_async(std::string gltf_json_path, UniqueTask on_loaded) { - if (!fs::is_regular_file(gltf_json_path)) { return false; } + if (!fs::is_regular_file(gltf_json_path)) { + // early return if file will fail to load anyway + logger::error("[Engine] Invalid GLTF JSON path: [{}]", gltf_json_path); + return false; + } + // shared state will need to be accessed, lock the mutex auto lock = std::scoped_lock{m_impl->mutex}; if (m_impl->load.request.future.valid()) { + // we don't support discarding in-flight requests logger::warn("[Engine] Denied attempt to load_async when a load request is already in flight"); return false; } + + // ready to start loading + logger::info("[Engine] Loading GLTF [{}]...", State::to_filename(gltf_json_path)); + // populate load request m_impl->load.callback = std::move(on_loaded); m_impl->load.request.path = std::move(gltf_json_path); - logger::info("[Engine] Loading GLTF [{}]...", State::to_filename(m_impl->load.request.path)); m_impl->load.request.status.store(LoadStatus::eStartingThread); m_impl->load.request.start_time = time::since_start(); auto func = [path = m_impl->load.request.path, gfx = m_impl->window.gfx, status = &m_impl->load.request.status] { auto scene = Scene{gfx}; if (!load_gltf(scene, path.c_str(), status)) { logger::error("[Engine] Failed to load GLTF: [{}]", path); } + // return the scene even on failure, it will be empty but valid return scene; }; + // store future m_impl->load.request.future = std::async(std::launch::async, func); return true; } @@ -289,18 +303,24 @@ Glfw::State const& Engine::state() const { return m_impl->window.window.get().st Input const& Engine::input() const { return state().input; } Renderer& Engine::renderer() const { return m_impl->window.renderer; } -void Engine::update_futures() { +void Engine::update_load_request() { auto lock = std::unique_lock{m_impl->mutex}; - if (ready(m_impl->load.request.future)) { - m_impl->scene = m_impl->load.request.future.get(); - m_impl->load.request.status.store(LoadStatus::eNone); - logger::info("...GLTF [{}] loaded in [{:.2f}s]", State::to_filename(m_impl->load.request.path), time::since_start() - m_impl->load.request.start_time); - // move out the callback - auto callback = std::move(m_impl->load.callback); - // unlock mutex to prevent possible deadlock (eg callback calls load_gltf again) - lock.unlock(); - // invoke callback - if (callback) { callback(); } - } + // early return if future isn't valid or is still busy + if (!ready(m_impl->load.request.future)) { return; } + + // transfer scene (under mutex lock) + m_impl->scene = m_impl->load.request.future.get(); + // reset load status + m_impl->load.request.status.store(LoadStatus::eNone); + // move out the path + auto path = std::move(m_impl->load.request.path); + // move out the callback + auto callback = std::move(m_impl->load.callback); + auto const duration = time::since_start() - m_impl->load.request.start_time; + // unlock mutex to prevent possible deadlock (eg callback calls load_gltf again) + lock.unlock(); + logger::info("...GLTF [{}] loaded in [{:.2f}s]", State::to_filename(path), duration); + // invoke callback + if (callback) { callback(); } } } // namespace facade diff --git a/lib/glfw/src/glfw.cpp b/lib/glfw/src/glfw.cpp index 79bb4f6..7df3829 100644 --- a/lib/glfw/src/glfw.cpp +++ b/lib/glfw/src/glfw.cpp @@ -14,6 +14,7 @@ std::mutex g_mutex{}; struct { std::unordered_map states{}; + // polling / dt is shared across all windows DeltaTime dt{}; } g_states{}; diff --git a/lib/scene/include/facade/scene/load_status.hpp b/lib/scene/include/facade/scene/load_status.hpp index d5bd0bd..79c0f25 100644 --- a/lib/scene/include/facade/scene/load_status.hpp +++ b/lib/scene/include/facade/scene/load_status.hpp @@ -42,5 +42,5 @@ constexpr auto load_status_str = EnumArray{ static_assert(std::size(load_status_str.t) == static_cast(LoadStatus::eCOUNT_)); -constexpr float load_progress(LoadStatus const stage) { return static_cast(stage) / (static_cast(LoadStatus::eCOUNT_) - 1.0f); } +constexpr float load_progress(LoadStatus const stage) { return static_cast(stage) / static_cast(LoadStatus::eCOUNT_); } } // namespace facade diff --git a/lib/scene/src/scene.cpp b/lib/scene/src/scene.cpp index 1893845..0137d19 100644 --- a/lib/scene/src/scene.cpp +++ b/lib/scene/src/scene.cpp @@ -137,6 +137,7 @@ struct Scene::TreeBuilder { // add node with default camera assert(!out_scene.m_storage.cameras.empty()); auto node = Node{}; + node.name = "camera"; // TODO node.transform.set_position({0.0f, 0.0f, 5.0f}); node.attach(Id{0}); @@ -267,8 +268,9 @@ Node const& Scene::camera() const { Texture Scene::make_texture(Image::View image) const { return Texture{m_gfx, default_sampler(), image}; } void Scene::add_default_camera() { - m_storage.cameras.push_back({}); + m_storage.cameras.push_back(Camera{.name = "default"}); auto node = Node{}; + node.name = "camera"; node.attach>(0); m_tree.camera = add_unchecked(m_tree.roots, std::move(node)); }