diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f87c94..7379048 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,5 @@ name: ci -on: - push: - tags-ignore: v*.* - branches: - - '*' - pull_request: - branches: - - '*' +on: [push, pull_request] jobs: build-linux: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index f9003bc..de80b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ compile_commands.json wiki imgui.ini + +facade.log* diff --git a/facade-lib/CMakeLists.txt b/facade-lib/CMakeLists.txt index 1efd3f2..9a6a5ff 100644 --- a/facade-lib/CMakeLists.txt +++ b/facade-lib/CMakeLists.txt @@ -65,6 +65,7 @@ target_compile_definitions(${PROJECT_NAME} target_sources(${PROJECT_NAME} PRIVATE include/facade/defines.hpp + include/facade/util/async_queue.hpp include/facade/util/byte_buffer.hpp include/facade/util/colour_space.hpp include/facade/util/data_provider.hpp @@ -85,9 +86,6 @@ target_sources(${PROJECT_NAME} PRIVATE include/facade/util/unique.hpp include/facade/util/visitor.hpp - include/facade/glfw/glfw.hpp - include/facade/glfw/input.hpp - include/facade/vk/buffer.hpp include/facade/vk/cmd.hpp include/facade/vk/defer.hpp @@ -109,10 +107,10 @@ target_sources(${PROJECT_NAME} PRIVATE include/facade/vk/vma.hpp include/facade/vk/wsi.hpp - include/facade/dear_imgui/dear_imgui.hpp + include/facade/glfw/glfw.hpp + include/facade/glfw/input.hpp - include/facade/engine/engine.hpp - include/facade/engine/renderer.hpp + include/facade/dear_imgui/dear_imgui.hpp include/facade/scene/camera.hpp include/facade/scene/fly_cam.hpp @@ -121,11 +119,15 @@ target_sources(${PROJECT_NAME} PRIVATE include/facade/scene/material.hpp include/facade/scene/node.hpp include/facade/scene/rect.hpp + include/facade/scene/renderer.hpp include/facade/scene/scene.hpp include/facade/scene/transform.hpp + include/facade/engine/engine.hpp + include/facade/editor/common.hpp include/facade/editor/inspector.hpp + include/facade/editor/log.hpp src/util/data_provider.cpp src/util/geometry.cpp @@ -133,8 +135,6 @@ target_sources(${PROJECT_NAME} PRIVATE src/util/logger.cpp src/util/time.cpp - src/glfw/glfw.cpp - src/vk/buffer.cpp src/vk/cmd.cpp src/vk/descriptor_set.cpp @@ -153,19 +153,22 @@ target_sources(${PROJECT_NAME} PRIVATE src/vk/vk.cpp src/vk/vma.cpp + src/glfw/glfw.cpp + src/dear_imgui/dear_imgui.cpp src/detail/gltf.cpp src/detail/gltf.hpp - src/engine/engine.cpp - src/engine/renderer.cpp - src/scene/camera.cpp src/scene/material.cpp + src/scene/renderer.cpp src/scene/scene.cpp src/scene/transform.cpp + src/engine/engine.cpp + src/editor/common.cpp src/editor/inspector.cpp + src/editor/log.cpp ) diff --git a/facade-lib/include/facade/editor/common.hpp b/facade-lib/include/facade/editor/common.hpp index 7815e92..d2d758d 100644 --- a/facade-lib/include/facade/editor/common.hpp +++ b/facade-lib/include/facade/editor/common.hpp @@ -1,28 +1,99 @@ #pragma once #include +#include +#include +#include namespace facade::editor { -class Window : public Pinned { +/// +/// \brief Base class for RAII Dear ImGui wrappers whose widgets return a boolean on Begin() +/// +class Openable : public Pinned { public: - explicit Window(char const* label, bool* open_if = {}, int flags = {}); - ~Window(); - bool is_open() const { return m_open; } explicit operator bool() const { return is_open(); } + protected: + Openable(bool is_open); + bool m_open; +}; + +/// +/// \brief Required dependency on an Openable which must be open (when used as an argument) +/// +template +struct NotClosed { + NotClosed([[maybe_unused]] T const& t) { assert(t.is_open()); } +}; + +/// +/// \brief RAII Dear ImGui window +/// +class Window : public Openable { + public: + class Menu; + + explicit Window(char const* label, bool* open_if = {}, int flags = {}); + Window(NotClosed parent, char const* label, glm::vec2 size = {}, bool border = {}, int flags = {}); + ~Window(); + private: - bool m_open{}; + bool m_child{}; }; -class TreeNode : public Pinned { +/// +/// \brief RAII Dear ImGui TreeNode +/// +class TreeNode : public Openable { public: explicit TreeNode(char const* label, int flags = {}); ~TreeNode(); +}; - bool is_open() const { return m_open; } - explicit operator bool() const { return is_open(); } +/// +/// \brief Base class for Dear ImGui MenuBars +/// +class MenuBar : public Openable { + protected: + using Openable::Openable; +}; - private: - bool m_open{}; +/// +/// \brief RAII Dear ImGui Menu object +/// +class Menu : public Openable { + public: + explicit Menu(NotClosed, char const* label, bool enabled = true); + ~Menu(); +}; + +/// +/// \brief RAII Dear ImGui windowed MenuBar +/// +class Window::Menu : public MenuBar { + public: + explicit Menu(NotClosed); + ~Menu(); +}; + +/// +/// \brief RAII Dear ImGui MainMenuBar +/// +class MainMenu : public MenuBar { + public: + explicit MainMenu(); + ~MainMenu(); +}; + +/// +/// \brief RAII Dear ImGui StyleVar stack +/// +class StyleVar : public Pinned { + public: + explicit StyleVar(int index, glm::vec2 value); + explicit StyleVar(int index, float value); + ~StyleVar(); + + explicit operator bool() const { return true; } }; } // namespace facade::editor diff --git a/facade-lib/include/facade/editor/log.hpp b/facade-lib/include/facade/editor/log.hpp new file mode 100644 index 0000000..4f8f218 --- /dev/null +++ b/facade-lib/include/facade/editor/log.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include + +namespace facade::editor { +class Log : public logger::Accessor { + public: + void render(); + + bool show{}; + + private: + void operator()(std::span entries) final; + + std::vector m_list{}; + EnumArray m_level_filter{true, true, true, debug_v}; + int m_display_count{50}; +}; +} // namespace facade::editor diff --git a/facade-lib/include/facade/engine/renderer.hpp b/facade-lib/include/facade/scene/renderer.hpp similarity index 96% rename from facade-lib/include/facade/engine/renderer.hpp rename to facade-lib/include/facade/scene/renderer.hpp index 91b6a98..4ed089e 100644 --- a/facade-lib/include/facade/engine/renderer.hpp +++ b/facade-lib/include/facade/scene/renderer.hpp @@ -24,6 +24,10 @@ struct FrameStats { /// \brief Framerate (until previous frame) /// std::uint32_t fps{}; + /// + /// \brief Current present mode + /// + vk::PresentModeKHR mode{}; }; struct RendererCreateInfo { diff --git a/facade-lib/include/facade/util/async_queue.hpp b/facade-lib/include/facade/util/async_queue.hpp new file mode 100644 index 0000000..886221c --- /dev/null +++ b/facade-lib/include/facade/util/async_queue.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace facade { +/// +/// \brief Basic multi-producer, multi-consumer FIFO queue +/// +template +class AsyncQueue { + public: + /// + /// \brief Push t to the back + /// + void push(Type t) { + auto lock = std::unique_lock{m_mutex}; + m_queue.push_back(std::move(t)); + lock.unlock(); + m_cv.notify_one(); + } + + /// + /// \brief Push [*first, *last) in order to the back + /// + template + void push(It first, It last) { + auto lock = std::unique_lock{m_mutex}; + m_queue.insert(m_queue.end(), first, last); + lock.unlock(); + m_cv.notify_all(); + } + + /// + /// \brief Try to dequeue an item: non-blocking + /// \returns std::nullopt if queue is empty, else the front + /// + std::optional try_pop() { + auto lock = std::unique_lock{m_mutex}; + if (m_queue.empty()) { return {}; } + return pop_front(lock); + } + + /// + /// \brief Try to dequeue an item: blocking + /// \returns std::nullopt if queue is empty or stop has been requested, else the front + /// + std::optional pop(std::stop_token const& stop) { + auto lock = std::unique_lock{m_mutex}; + m_cv.wait(lock, [this, stop] { return !m_queue.empty() || stop.stop_requested(); }); + if (stop.stop_requested() || m_queue.empty()) { return {}; } + return pop_front(lock); + } + + /// + /// \brief Notify all sleeping threads + /// + void ping() { m_cv.notify_all(); } + + /// + /// \brief Empty queue into drain, notify all sleeping threads, and return drain + /// \returns Items left in the queue + /// + std::deque release() { + auto lock = std::unique_lock{m_mutex}; + auto ret = std::move(m_queue); + lock.unlock(); + m_cv.notify_all(); + return ret; + } + + private: + std::optional pop_front(std::unique_lock const&) { + auto ret = std::move(m_queue.front()); + m_queue.pop_front(); + return ret; + } + + std::deque m_queue{}; + std::condition_variable m_cv{}; + std::mutex m_mutex{}; +}; +} // namespace facade diff --git a/facade-lib/include/facade/util/logger.hpp b/facade-lib/include/facade/util/logger.hpp index 97b2a54..0652f7f 100644 --- a/facade-lib/include/facade/util/logger.hpp +++ b/facade-lib/include/facade/util/logger.hpp @@ -1,38 +1,58 @@ #pragma once #include #include +#include +#include namespace facade::logger { -enum class Pipe { eStdOut, eStdErr }; +enum class Pipe : std::uint8_t { eStdOut, eStdErr }; +enum class Level : std::uint8_t { eError, eWarn, eInfo, eDebug, eCOUNT_ }; +inline constexpr char levels_v[] = {'E', 'W', 'I', 'D'}; -inline std::string g_format{"[{thread}] [{severity}] {message} [{timestamp}]"}; +inline std::string g_format{"[T{thread}] [{level}] {message} [{timestamp}]"}; + +struct Entry { + std::string message{}; + Level level{}; +}; + +struct Accessor { + virtual void operator()(std::span entries) = 0; +}; int thread_id(); -std::string format(char severity, std::string_view const message); -void print_to(Pipe pipe, char const* text); +std::string format(Level level, std::string_view const message); +void log_to(Pipe pipe, Entry entry); +void access_buffer(Accessor& accessor); + +class Instance : public Pinned { + public: + Instance(std::size_t buffer_limit = 500, std::size_t buffer_extra = 100); + ~Instance(); +}; template -std::string format(char severity, fmt::format_string fmt, Args const&... args) { - return format(severity, fmt::vformat(fmt, fmt::make_format_args(args...))); +std::string format(Level level, fmt::format_string fmt, Args const&... args) { + return format(level, fmt::vformat(fmt, fmt::make_format_args(args...))); } template void error(fmt::format_string fmt, Args const&... args) { - print_to(Pipe::eStdErr, format('E', fmt, args...).c_str()); + log_to(Pipe::eStdErr, {format(Level::eError, fmt, args...), Level::eError}); } template void warn(fmt::format_string fmt, Args const&... args) { - print_to(Pipe::eStdOut, format('W', fmt, args...).c_str()); + log_to(Pipe::eStdOut, {format(Level::eWarn, fmt, args...), Level::eWarn}); } template void info(fmt::format_string fmt, Args const&... args) { - print_to(Pipe::eStdOut, format('I', fmt, args...).c_str()); + log_to(Pipe::eStdOut, {format(Level::eInfo, fmt, args...), Level::eInfo}); } template void debug(fmt::format_string fmt, Args const&... args) { - if constexpr (debug_v) { print_to(Pipe::eStdOut, format('D', fmt, args...).c_str()); } + if constexpr (debug_v) { log_to(Pipe::eStdOut, {format(Level::eDebug, fmt, args...), Level::eDebug}); } } } // namespace facade::logger diff --git a/facade-lib/src/editor/common.cpp b/facade-lib/src/editor/common.cpp index fc97222..7b276b0 100644 --- a/facade-lib/src/editor/common.cpp +++ b/facade-lib/src/editor/common.cpp @@ -1,15 +1,49 @@ #include #include +#include namespace facade::editor { -Window::Window(char const* label, bool* open_if, int flags) : m_open(ImGui::Begin(label, open_if, flags)) {} +Openable::Openable(bool is_open) : m_open(is_open) {} + +Window::Window(char const* label, bool* open_if, int flags) : Openable(ImGui::Begin(label, open_if, flags)) {} + +Window::Window(NotClosed, char const* label, glm::vec2 size, bool border, int flags) + : Openable(ImGui::BeginChild(label, {size.x, size.y}, border, flags)), m_child(true) {} // ImGui windows requires End() even if Begin() returned false -Window::~Window() { ImGui::End(); } +Window::~Window() { + if (m_child) { + ImGui::EndChild(); + } else { + ImGui::End(); + } +} -TreeNode::TreeNode(char const* label, int flags) : m_open(ImGui::TreeNodeEx(label, flags)) {} +TreeNode::TreeNode(char const* label, int flags) : Openable(ImGui::TreeNodeEx(label, flags)) {} TreeNode::~TreeNode() { if (m_open) { ImGui::TreePop(); } } + +Window::Menu::Menu(NotClosed) : MenuBar(ImGui::BeginMenuBar()) {} + +Window::Menu::~Menu() { + if (m_open) { ImGui::EndMenuBar(); } +} + +MainMenu::MainMenu() : MenuBar(ImGui::BeginMainMenuBar()) {} + +MainMenu::~MainMenu() { + if (m_open) { ImGui::EndMainMenuBar(); } +} + +Menu::Menu(NotClosed, char const* label, bool enabled) : Openable(ImGui::BeginMenu(label, enabled)) {} + +Menu::~Menu() { + if (m_open) { ImGui::EndMenu(); } +} + +StyleVar::StyleVar(int index, glm::vec2 value) { ImGui::PushStyleVar(index, {value.x, value.y}); } +StyleVar::StyleVar(int index, float value) { ImGui::PushStyleVar(index, value); } +StyleVar::~StyleVar() { ImGui::PopStyleVar(); } } // namespace facade::editor diff --git a/facade-lib/src/editor/inspector.cpp b/facade-lib/src/editor/inspector.cpp index 7014a06..8420b26 100644 --- a/facade-lib/src/editor/inspector.cpp +++ b/facade-lib/src/editor/inspector.cpp @@ -20,7 +20,7 @@ struct Modified { }; } // namespace -Inspector::Inspector(Window const& target) { assert(target.is_open()); } +Inspector::Inspector([[maybe_unused]] Window const& target) { assert(target.is_open()); } bool Inspector::inspect(char const* label, glm::vec2& out_vec2, float speed, float lo, float hi) const { float arr[2] = {out_vec2.x, out_vec2.y}; @@ -74,16 +74,16 @@ bool Inspector::inspect(Transform& out_transform) const { return ret.value; } -SceneInspector::SceneInspector(Window const& target, Scene& scene) : Inspector(target), m_scene(scene) { assert(target.is_open()); } +SceneInspector::SceneInspector([[maybe_unused]] Window const& target, Scene& scene) : Inspector(target), m_scene(scene) { assert(target.is_open()); } -bool SceneInspector::inspect(TreeNode const& node, UnlitMaterial& out_material) const { +bool SceneInspector::inspect([[maybe_unused]] TreeNode const& node, UnlitMaterial& out_material) const { assert(node.is_open()); auto ret = Modified{}; ret(inspect("Tint", out_material.tint, 0.01f, 0.0f, 1.0f)); return ret.value; } -bool SceneInspector::inspect(TreeNode const& node, LitMaterial& out_material) const { +bool SceneInspector::inspect([[maybe_unused]] TreeNode const& node, LitMaterial& out_material) const { assert(node.is_open()); auto ret = Modified{}; ret(ImGui::SliderFloat("Metallic", &out_material.metallic, 0.0f, 1.0f)); diff --git a/facade-lib/src/editor/log.cpp b/facade-lib/src/editor/log.cpp new file mode 100644 index 0000000..29dbebc --- /dev/null +++ b/facade-lib/src/editor/log.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +namespace facade::editor { +void Log::render() { + ImGui::SetNextWindowSize({500.0f, 200.0f}, ImGuiCond_Once); + if (auto window = editor::Window{"Log", &show}) { + ImGui::Text("%s", FixedString{"Count: {}", m_display_count}.c_str()); + ImGui::SameLine(); + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushButtonRepeat(true); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { m_display_count = std::clamp(m_display_count - 10, 0, 1000); } + ImGui::SameLine(0.0f, spacing); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { m_display_count = std::clamp(m_display_count + 10, 0, 1000); } + ImGui::PopButtonRepeat(); + + static constexpr std::string_view levels_v[] = {"Error", "Warn", "Info", "Debug"}; + static constexpr auto max_log_level_v = debug_v ? logger::Level::eDebug : logger::Level::eInfo; + for (logger::Level l = logger::Level::eError; l <= max_log_level_v; l = static_cast(static_cast(l) + 1)) { + ImGui::SameLine(); + ImGui::Checkbox(levels_v[static_cast(l)].data(), &m_level_filter[l]); + } + + logger::access_buffer(*this); + auto child = editor::Window{window, "scroll", {}, {}, ImGuiWindowFlags_HorizontalScrollbar}; + static constexpr auto im_colour = [](logger::Level const l) { + switch (l) { + case logger::Level::eError: return ImVec4{1.0f, 0.0f, 0.0f, 1.0f}; + case logger::Level::eWarn: return ImVec4{1.0f, 1.0f, 0.0f, 1.0f}; + default: + case logger::Level::eInfo: return ImVec4{1.0f, 1.0f, 1.0f, 1.0f}; + case logger::Level::eDebug: return ImVec4{0.5f, 0.5f, 0.5f, 1.0f}; + } + }; + if (auto style = editor::StyleVar{ImGuiStyleVar_ItemSpacing, glm::vec2{}}) { + for (auto const* entry : m_list) ImGui::TextColored(im_colour(entry->level), "%s", entry->message.c_str()); + } + } +} + +void Log::operator()(std::span entries) { + m_list.clear(); + for (auto const& entry : entries) { + if (m_list.size() >= static_cast(m_display_count)) { break; } + if (!m_level_filter[entry.level]) { continue; } + m_list.push_back(&entry); + } +} +} // namespace facade::editor diff --git a/facade-lib/src/engine/engine.cpp b/facade-lib/src/engine/engine.cpp index 8666525..18913d0 100644 --- a/facade-lib/src/engine/engine.cpp +++ b/facade-lib/src/engine/engine.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/facade-lib/src/engine/renderer.cpp b/facade-lib/src/scene/renderer.cpp similarity index 99% rename from facade-lib/src/engine/renderer.cpp rename to facade-lib/src/scene/renderer.cpp index 39a62c4..0e1a399 100644 --- a/facade-lib/src/engine/renderer.cpp +++ b/facade-lib/src/scene/renderer.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -82,6 +82,7 @@ struct Renderer::Impl { }) {} void next_frame() { + stats.stats.mode = swapchain.info.presentMode; stats.stats.fps = fps.next_frame(); ++stats.stats.frame_counter; stats.stats.triangles = std::exchange(stats.triangles, 0); diff --git a/facade-lib/src/scene/scene.cpp b/facade-lib/src/scene/scene.cpp index b6d1fe4..39ab2b8 100644 --- a/facade-lib/src/scene/scene.cpp +++ b/facade-lib/src/scene/scene.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/facade-lib/src/util/logger.cpp b/facade-lib/src/util/logger.cpp index dfc4300..08f6cbe 100644 --- a/facade-lib/src/util/logger.cpp +++ b/facade-lib/src/util/logger.cpp @@ -1,12 +1,22 @@ +#include #include #include #include +#include +#include #include #include #include +#include + +#if defined(_WIN32) +#include +#endif namespace facade { namespace { +namespace fs = std::filesystem; + struct Timestamp { char buffer[32]{}; operator char const*() const { return buffer; } @@ -33,18 +43,95 @@ struct GetThreadId { } }; +class FileLogger : Pinned { + public: + FileLogger(char const* path) : m_path(fs::absolute(path)), m_thread(std::jthread{[this](std::stop_token const& stop) { log_to_file(stop); }}) {} + + ~FileLogger() { + m_thread.request_stop(); + flush_residue(queue.release()); + } + + AsyncQueue queue{}; + + private: + void prepare_file() { + if (fs::exists(m_path)) { + auto backup_path = m_path; + backup_path += ".bak"; + if (fs::exists(backup_path)) { fs::remove(backup_path); } + fs::rename(m_path, backup_path); + } + } + + void flush_residue(std::deque const& residue) { + auto file = std::ofstream{m_path, std::ios::app}; + if (!file) { return; } + for (auto const& [message, _] : residue) { file << message << '\n'; } + } + + void log_to_file(std::stop_token const& stop) { + prepare_file(); + while (auto entry = queue.pop(stop)) { + auto file = std::ofstream{m_path, std::ios::app}; + file << entry->message << '\n'; + } + } + + fs::path m_path{}; + std::jthread m_thread{}; +}; + +struct Storage { + struct Buffer { + std::size_t limit{}; + std::size_t extra{}; + + std::vector entries{}; + }; + + Buffer buffer{}; + std::optional file_logger{}; + std::mutex mutex{}; +}; + GetThreadId get_thread_id{}; +Storage g_storage{}; } // namespace int logger::thread_id() { return get_thread_id(); } -std::string logger::format(char severity, std::string_view const message) { - return fmt::format(fmt::runtime(g_format), fmt::arg("thread", thread_id()), fmt::arg("severity", severity), fmt::arg("message", message), - fmt::arg("timestamp", make_timestamp())); +std::string logger::format(Level level, std::string_view const message) { + return fmt::format(fmt::runtime(g_format), fmt::arg("thread", thread_id()), fmt::arg("level", levels_v[static_cast(level)]), + fmt::arg("message", message), fmt::arg("timestamp", make_timestamp())); } -void logger::print_to(Pipe pipe, char const* text) { +void logger::log_to(Pipe pipe, Entry entry) { auto* fd = pipe == Pipe::eStdErr ? stderr : stdout; - std::fprintf(fd, "%s\n", text); + std::fprintf(fd, "%s\n", entry.message.c_str()); + auto lock = std::scoped_lock{g_storage.mutex}; +#if defined(_WIN32) + OutputDebugStringA(entry.message.c_str()); + OutputDebugStringA("\n"); +#endif + if (g_storage.file_logger) { g_storage.file_logger->queue.push(entry); } + g_storage.buffer.entries.push_back(std::move(entry)); +} + +void logger::access_buffer(Accessor& accessor) { + auto lock = std::scoped_lock{g_storage.mutex}; + accessor(g_storage.buffer.entries); +} + +logger::Instance::Instance(std::size_t buffer_limit, std::size_t buffer_extra) { + auto lock = std::scoped_lock{g_storage.mutex}; + g_storage.buffer = Storage::Buffer{buffer_limit, buffer_extra}; + g_storage.file_logger.emplace("facade.log"); +} + +logger::Instance::~Instance() { + auto lock = std::scoped_lock{g_storage.mutex}; + g_storage.file_logger.reset(); + g_storage.buffer.entries.clear(); } } // namespace facade diff --git a/src/main.cpp b/src/main.cpp index d42d7fc..8e38a7b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,16 +11,19 @@ #include -#include -#include - #include +#include #include +#include + #include +#include #include +#include + using namespace facade; namespace { @@ -113,6 +116,86 @@ static constexpr auto test_json_v = R"( } )"; +struct MainMenu { + struct { + bool inspector{}; + bool stats{}; + } windows{}; + + editor::Log log{}; + + void inspector(Scene& scene) { + ImGui::SetNextWindowSize({250.0f, 100.0f}, ImGuiCond_Once); + if (auto window = editor::Window{"Node", &windows.inspector}) { + static auto s_input = int{}; + ImGui::InputInt("Id", &s_input); + editor::SceneInspector{window, scene}.inspect(Id{static_cast(s_input)}); + } + } + + static constexpr std::string_view vsync_status(vk::PresentModeKHR const mode) { + switch (mode) { + case vk::PresentModeKHR::eFifo: return "On"; + case vk::PresentModeKHR::eFifoRelaxed: return "Adaptive"; + case vk::PresentModeKHR::eImmediate: return "Off"; + case vk::PresentModeKHR::eMailbox: return "Double-buffered"; + default: return "Unsupported"; + } + } + + void change_vsync(Engine const& engine) const { + static constexpr vk::PresentModeKHR modes[] = {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eFifoRelaxed, vk::PresentModeKHR::eMailbox, + vk::PresentModeKHR::eImmediate}; + static std::size_t index{0}; + auto const next_mode = [&] { + while (true) { + index = (index + 1) % std::size(modes); + auto const ret = modes[index]; + if (!engine.renderer().is_supported(ret)) { continue; } + return ret; + } + throw Error{"Invariant violated"}; + }(); + engine.renderer().request_mode(next_mode); + logger::info("Requesting present mode: [{}]", present_mode_str(next_mode)); + } + + void stats(Engine const& engine, float const dt) { + ImGui::SetNextWindowSize({200.0f, 200.0f}, ImGuiCond_Once); + if (auto window = editor::Window{"Frame Stats", &windows.stats}) { + auto const& stats = engine.renderer().frame_stats(); + ImGui::Text("%s", FixedString{"Counter: {}", stats.frame_counter}.c_str()); + ImGui::Text("%s", FixedString{"Triangles: {}", stats.triangles}.c_str()); + ImGui::Text("%s", FixedString{"Draw calls: {}", stats.draw_calls}.c_str()); + ImGui::Text("%s", FixedString{"FPS: {}", (stats.fps == 0 ? static_cast(stats.frame_counter) : stats.fps)}.c_str()); + ImGui::Text("%s", FixedString{"Frame time: {:.2f}ms", dt * 1000.0f}.c_str()); + if (ImGui::SmallButton("Vsync")) { change_vsync(engine); } + ImGui::SameLine(); + ImGui::Text("%s", vsync_status(stats.mode).data()); + } + } + + void display(Engine& engine, Scene& scene, float const dt) { + if (auto main = editor::MainMenu{}) { + if (auto file = editor::Menu{main, "File"}) { + ImGui::Separator(); + if (ImGui::MenuItem("Exit")) { engine.request_stop(); } + } + if (auto window = editor::Menu{main, "Window"}) { + if (ImGui::MenuItem("Inspect")) { windows.inspector = true; } + if (ImGui::MenuItem("Stats")) { windows.stats = true; } + if (ImGui::MenuItem("Log")) { log.show = true; } + ImGui::Separator(); + if (ImGui::MenuItem("Close All")) { windows = {}; } + } + } + + if (windows.inspector) { inspector(scene); } + if (windows.stats) { stats(engine, dt); } + if (log.show) { log.render(); } + } +}; + void run() { auto engine = Engine{}; @@ -151,6 +234,8 @@ void run() { float const drot_z[] = {100.0f, -150.0f}; + auto main_menu = MainMenu{}; + engine.show(true); while (engine.running()) { auto const dt = engine.next_frame(); @@ -181,46 +266,14 @@ void run() { if (mouse_look) { fly_cam.rotate({input.mouse.delta_pos().x, -input.mouse.delta_pos().y}); } } + // TEMP CODE auto* node = scene.find_node(node_id); node->instances[0].rotate(glm::radians(drot_z[0]) * dt, {0.0f, 1.0f, 0.0f}); node->instances[1].rotate(glm::radians(drot_z[1]) * dt, {1.0f, 0.0f, 0.0f}); - // TEMP CODE - if (input.keyboard.pressed(GLFW_KEY_M)) { - static constexpr vk::PresentModeKHR modes[] = {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eFifoRelaxed, vk::PresentModeKHR::eMailbox, - vk::PresentModeKHR::eImmediate}; - static std::size_t index{0}; - auto const next_mode = [&] { - while (true) { - index = (index + 1) % std::size(modes); - auto const ret = modes[index]; - if (!engine.renderer().is_supported(ret)) { continue; } - return ret; - } - throw Error{"Invariant violated"}; - }(); - engine.renderer().request_mode(next_mode); - logger::info("Requesting present mode: [{}]", present_mode_str(next_mode)); - } - ImGui::ShowDemoWindow(); - ImGui::SetNextWindowSize({250.0f, 100.0f}, ImGuiCond_Once); - if (auto window = editor::Window{"Node"}) { - static auto s_input = int{}; - ImGui::InputInt("Id", &s_input); - editor::SceneInspector{window, scene}.inspect(Id{static_cast(s_input)}); - } - - ImGui::SetNextWindowSize({200.0f, 150.0f}, ImGuiCond_Once); - if (auto window = editor::Window("Frame Stats")) { - auto const& stats = engine.renderer().frame_stats(); - ImGui::Text("%s", FixedString{"Counter: {}", stats.frame_counter}.c_str()); - ImGui::Text("%s", FixedString{"Triangles: {}", stats.triangles}.c_str()); - ImGui::Text("%s", FixedString{"Draw calls: {}", stats.draw_calls}.c_str()); - ImGui::Text("%s", FixedString{"FPS: {}", (stats.fps == 0 ? static_cast(stats.frame_counter) : stats.fps)}.c_str()); - ImGui::Text("%s", FixedString{"Frame time: {:.2f}ms", dt * 1000.0f}.c_str()); - } + main_menu.display(engine, scene, dt); // TEMP CODE engine.render(scene); @@ -230,18 +283,21 @@ void run() { int main() { try { - run(); - } catch (InitError const& e) { - logger::error("Initialization failure: {}", e.what()); - return EXIT_FAILURE; - } catch (Error const& e) { - logger::error("Runtime error: {}", e.what()); - return EXIT_FAILURE; - } catch (std::exception const& e) { - logger::error("Fatal error: {}", e.what()); - return EXIT_FAILURE; - } catch (...) { - logger::error("Unknown error"); - return EXIT_FAILURE; - } + auto logger_instance = logger::Instance{}; + try { + run(); + } catch (InitError const& e) { + logger::error("Initialization failure: {}", e.what()); + return EXIT_FAILURE; + } catch (Error const& e) { + logger::error("Runtime error: {}", e.what()); + return EXIT_FAILURE; + } catch (std::exception const& e) { + logger::error("Fatal error: {}", e.what()); + return EXIT_FAILURE; + } catch (...) { + logger::error("Unknown error"); + return EXIT_FAILURE; + } + } catch (std::exception const& e) { std::cerr << e.what() << '\n'; } }