Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion lib/engine/include/facade/engine/editor/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +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) {}
};

///
Expand Down
27 changes: 22 additions & 5 deletions lib/engine/include/facade/engine/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <facade/glfw/glfw.hpp>
#include <facade/scene/scene.hpp>
#include <facade/util/time.hpp>
#include <facade/util/unique_task.hpp>
#include <facade/vk/shader.hpp>

namespace facade {
Expand All @@ -28,6 +29,7 @@ struct EngineCreateInfo {
class Engine {
public:
using CreateInfo = EngineCreateInfo;
using State = Glfw::State;

Engine(Engine&&) noexcept;
Engine& operator=(Engine&&) noexcept;
Expand Down Expand Up @@ -64,9 +66,9 @@ class Engine {
///
bool running() const;
///
/// \brief Poll events and obtain delta time
/// \brief Poll events and obtain updated state
///
float poll();
State const& poll();
///
/// \brief Render the scene
///
Expand All @@ -77,14 +79,29 @@ 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<void()> on_loaded = {});
///
/// \brief Obtain status of in-flight async load request (if active)
///
LoadStatus load_status() const;

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:
void update_load_request();

struct Impl;
inline static Impl const* s_instance{};
std::unique_ptr<Impl> m_impl{};
Expand Down
4 changes: 3 additions & 1 deletion lib/engine/src/editor/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ 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(); }
}

void Popup::open(char const* id) { ImGui::OpenPopup(id); }

void Popup::close_current() { ImGui::CloseCurrentPopup(); }

Menu::Menu(NotClosed<MenuBar>, char const* label, bool enabled) : Openable(ImGui::BeginMenu(label, enabled)) {}
Expand Down
122 changes: 114 additions & 8 deletions lib/engine/src/engine.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_vulkan.h>
#include <imgui.h>
#include <djson/json.hpp>
#include <facade/defines.hpp>
#include <facade/engine/engine.hpp>
#include <facade/engine/scene_renderer.hpp>
#include <facade/glfw/glfw_wsi.hpp>
#include <facade/render/renderer.hpp>
#include <facade/util/data_provider.hpp>
#include <facade/util/error.hpp>
#include <facade/util/logger.hpp>
#include <facade/vk/cmd.hpp>
#include <facade/vk/vk.hpp>
#include <glm/gtc/color_space.hpp>
#include <glm/mat4x4.hpp>
#include <filesystem>
#include <future>

namespace facade {
namespace fs = std::filesystem;

namespace {
static constexpr std::size_t command_buffers_v{1};

Expand Down Expand Up @@ -150,6 +157,34 @@ 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, std::atomic<LoadStatus>* out_status) {
auto const provider = FileDataProvider::mount_parent_dir(path);
auto json = dj::Json::from_file(path);
return out_scene.load_gltf(json, provider, out_status);
}

template <typename T>
bool ready(std::future<T> const& future) {
return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::ready;
}

template <typename T>
bool timeout(std::future<T> const& future) {
return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::timeout;
}

template <typename T>
bool busy(std::future<T> const& future) {
return future.valid() && future.wait_for(std::chrono::seconds{}) == std::future_status::deferred;
}

struct LoadRequest {
std::string path{};
std::future<Scene> future{};
std::atomic<LoadStatus> status{};
float start_time{};
};
} // namespace

struct Engine::Impl {
Expand All @@ -158,14 +193,21 @@ struct Engine::Impl {
Scene scene;

std::uint8_t msaa;
DeltaTime dt{};

std::mutex mutex{};

struct {
LoadRequest request{};
UniqueTask<void()> callback{};
} load{};

Impl(UniqueWin window, std::uint8_t msaa, bool validation)
: window(std::move(window), std::make_unique<DearImGui>(), msaa, validation), renderer(this->window.gfx), scene(this->window.gfx), msaa(msaa) {
s_instance = this;
}

~Impl() {
load.request.future = {};
window.gfx.device.waitIdle();
s_instance = {};
}
Expand All @@ -189,32 +231,96 @@ 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& {
// 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();
return m_impl->dt();
m_impl->window.window.get().glfw->poll_events();
return m_impl.get()->window.window.get().state();
}

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();
}

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, UniqueTask<void()> on_loaded) {
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);
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;
}

LoadStatus Engine::load_status() const {
auto lock = std::scoped_lock{m_impl->mutex};
return m_impl->load.request.status.load();
}

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; }

void Engine::update_load_request() {
auto lock = std::unique_lock{m_impl->mutex};
// 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
4 changes: 4 additions & 0 deletions lib/glfw/include/facade/glfw/glfw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct Glfw {

std::vector<char const*> vk_extensions() const;
void poll_events();
void reset_dt();

bool operator==(Glfw const&) const = default;
};
Expand All @@ -27,6 +28,9 @@ using UniqueWin = Unique<Glfw::Window, Glfw::Deleter>;
struct Glfw::State {
Input input{};
std::vector<std::string> file_drops{};
float dt{};

static std::string to_filename(std::string_view path);
};

struct Glfw::Window {
Expand Down
37 changes: 27 additions & 10 deletions lib/glfw/src/glfw.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
#include <facade/glfw/glfw.hpp>
#include <facade/util/error.hpp>
#include <facade/util/time.hpp>
#include <filesystem>
#include <mutex>
#include <unordered_map>

namespace facade {
namespace fs = std::filesystem;

namespace {
std::weak_ptr<Glfw> g_glfw{};
std::mutex g_mutex{};

std::unordered_map<GLFWwindow*, Glfw::State> g_states{};
struct {
std::unordered_map<GLFWwindow*, Glfw::State> states{};
// polling / dt is shared across all windows
DeltaTime dt{};
} g_states{};

std::shared_ptr<Glfw> get_or_make_glfw() {
auto lock = std::scoped_lock{g_mutex};
Expand All @@ -35,14 +43,23 @@ 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 = {}; }

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();
Expand All @@ -52,13 +69,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<double>{x, y}); });
glfwSetScrollCallback(ret, [](GLFWwindow* w, double x, double y) { g_states[w].input.mouse.on_scroll(glm::tvec2<double>{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<double>{x, y}); });
glfwSetScrollCallback(ret, [](GLFWwindow* w, double x, double y) { g_states.states[w].input.mouse.on_scroll(glm::tvec2<double>{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;
}
Expand All @@ -70,7 +87,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<char const*> Glfw::vk_extensions() const {
Expand All @@ -95,6 +112,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
Loading