Commit 1860f7dd authored by Florian Oetke's avatar Florian Oetke
Browse files

basis for debug console [#39]

parent 3a89bfda
......@@ -32,7 +32,9 @@
"F3": {"type":"once", "action":"playback"},
"Space": {"type":"once", "action":"pause"},
"F11": {"type":"once", "action":"toggle_ui"}
"F11": {"type":"once", "action":"toggle_ui"},
"Caret": {"type":"once", "action":"console"}
},
"pad_sticks": {
......
......@@ -27,6 +27,7 @@ namespace mirrage {
char** argv,
char** env)
: Engine(org, title, version_major, version_minor, debug, false, argc, argv, env)
, _debug_ui(gui(), bus())
, _renderer_factory(std::make_unique<renderer::Deferred_renderer_factory>(
*this,
window(),
......@@ -56,6 +57,8 @@ namespace mirrage {
void Game_engine::_on_post_frame(util::Time dt)
{
_debug_ui.draw();
_global_render->update(dt);
_global_render->draw();
_renderer_factory->finish_frame();
......
......@@ -11,6 +11,7 @@
#include <mirrage/engine.hpp>
#include <mirrage/graphic/device.hpp>
#include <mirrage/gui/debug_ui.hpp>
#include <mirrage/utils/maybe.hpp>
......@@ -39,6 +40,7 @@ namespace mirrage {
void _on_post_frame(util::Time) override;
private:
gui::Debug_ui _debug_ui;
std::unique_ptr<renderer::Deferred_renderer_factory> _renderer_factory;
std::unique_ptr<renderer::Deferred_renderer> _global_render;
renderer::Render_pass_mask _render_pass_mask;
......
......@@ -13,6 +13,7 @@
#include <mirrage/info.hpp>
#include <mirrage/asset/asset_manager.hpp>
#include <mirrage/gui/debug_ui.hpp>
#include <SDL2/SDL.h>
#include <doctest.h>
......@@ -81,7 +82,9 @@ namespace {
static auto fileAppender = plog::RollingFileAppender<plog::TxtFormatter>(
(write_dir + "/mirrage.log").c_str(), 1024L * 1024L, 4);
static auto consoleAppender = plog::ColorConsoleAppender<plog::TxtFormatter>();
plog::init(plog::debug, &fileAppender).addAppender(&consoleAppender);
plog::init(plog::debug, &fileAppender)
.addAppender(&consoleAppender)
.addAppender(&gui::debug_console_appender());
::argc = argc;
......
......@@ -350,13 +350,6 @@ namespace mirrage {
_draw_settings_window();
_draw_histogram_window();
_draw_animation_window();
if(_show_profiler) {
_meta_system.renderer().profiler().enable();
_draw_profiler_window();
} else {
_meta_system.renderer().profiler().disable();
}
}
_meta_system.renderer().debug_draw({renderer::Debug_geometry{{0, 1, 0}, {0, 5, 0}, {1, 0, 0}},
......@@ -392,11 +385,6 @@ namespace mirrage {
nk_layout_row_dynamic(ctx, 20, 1);
auto show_profiler = _show_profiler ? 1 : 0;
if(nk_checkbox_label(ctx, "Show Profiler", &show_profiler)) {
_show_profiler = show_profiler == 1;
}
if(!_meta_system.nims().is_playing()) {
nk_layout_row_dynamic(ctx, 20, 1);
......@@ -536,117 +524,6 @@ namespace mirrage {
nk_end(ctx);
}
namespace {
template <typename T>
auto to_fixed_str(T num, int digits)
{
auto ss = std::stringstream{};
ss << std::fixed << std::setprecision(digits) << num;
return ss.str();
}
auto pad_left(const std::string& str, int padding)
{
return std::string(std::size_t(padding), ' ') + str;
}
template <std::size_t N, typename Container, typename Comp>
auto top_n(const Container& container, Comp&& less)
{
auto max_elements = std::array<decltype(container.begin()), N>();
max_elements.fill(container.end());
for(auto iter = container.begin(); iter != container.end(); iter++) {
// compare with each of the top elements
for(auto i = std::size_t(0); i < N; i++) {
if(max_elements[i] == container.end() || less(*max_elements[i], *iter)) {
// move top elements to make room
for(auto j = i + 1; j < N; j++) {
max_elements[j] = max_elements[j - 1];
}
max_elements[i] = iter;
break;
}
}
}
return max_elements;
}
template <typename Container, typename T>
auto index_of(const Container& container, const T& element) -> int
{
auto top_entry = std::find(container.begin(), container.end(), element);
if(top_entry == container.end())
return -1;
return gsl::narrow<int>(std::distance(container.begin(), top_entry));
}
} // namespace
void Test_screen::_draw_profiler_window()
{
auto ctx = _gui.ctx();
if(nk_begin_titled(ctx,
"profiler",
"Profiler",
_gui.centered_right(330, 380),
NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_MINIMIZABLE)) {
nk_layout_row_dynamic(ctx, 20, 1);
if(nk_button_label(ctx, "Reset")) {
_meta_system.renderer().profiler().reset();
}
#if 0
if(_performance_log.is_nothing() && nk_button_label(ctx, "Record")) {
_performance_log = _engine.assets().save_raw("log:perf.log"_aid);
}
#endif
constexpr auto rows = std::array<float, 5>{{0.4f, 0.15f, 0.15f, 0.15f, 0.15f}};
nk_layout_row(ctx, NK_DYNAMIC, 25, rows.size(), rows.data());
nk_label(ctx, "RenderPass", NK_TEXT_CENTERED);
nk_label(ctx, "Curr (ms)", NK_TEXT_CENTERED);
nk_label(ctx, "Min (ms)", NK_TEXT_CENTERED);
nk_label(ctx, "Avg (ms)", NK_TEXT_CENTERED);
nk_label(ctx, "Max (ms)", NK_TEXT_CENTERED);
nk_layout_row(ctx, NK_DYNAMIC, 10, rows.size(), rows.data());
auto print_entry =
[&](auto&& printer, const Profiler_result& result, int depth = 0, int rank = -1) -> void {
auto color = [&] {
switch(rank) {
case 0: return nk_rgb(255, 0, 0);
case 1: return nk_rgb(255, 220, 128);
default: return nk_rgb(255, 255, 255);
}
}();
nk_label_colored(ctx, pad_left(result.name(), depth * 4).c_str(), NK_TEXT_LEFT, color);
nk_label_colored(ctx, to_fixed_str(result.time_ms(), 2).c_str(), NK_TEXT_RIGHT, color);
nk_label_colored(ctx, to_fixed_str(result.time_min_ms(), 2).c_str(), NK_TEXT_RIGHT, color);
nk_label_colored(ctx, to_fixed_str(result.time_avg_ms(), 2).c_str(), NK_TEXT_RIGHT, color);
nk_label_colored(ctx, to_fixed_str(result.time_max_ms(), 2).c_str(), NK_TEXT_RIGHT, color);
auto worst_timings = top_n<2>(
result, [](auto&& lhs, auto&& rhs) { return lhs.time_avg_ms() < rhs.time_avg_ms(); });
for(auto iter = result.begin(); iter != result.end(); iter++) {
auto rank = index_of(worst_timings, iter);
printer(printer, *iter, depth + 1, rank);
}
};
auto& result = _meta_system.renderer().profiler().results();
print_entry(print_entry, result);
}
nk_end(ctx);
}
void Test_screen::_draw_histogram_window()
{
#ifdef HPC_HISTOGRAM_DEBUG_VIEW
......
......@@ -66,7 +66,6 @@ namespace mirrage {
util::Time _record_timer{0};
bool _show_ui = true;
bool _show_profiler = false;
std::size_t _last_selected_histogram = 0;
util::maybe<asset::ostream> _performance_log;
......@@ -81,7 +80,6 @@ namespace mirrage {
void _update_sun_position();
void _draw_settings_window();
void _draw_profiler_window();
void _draw_histogram_window();
void _draw_animation_window();
};
......
......@@ -10,6 +10,7 @@ file(GLOB_RECURSE HEADER_FILES
add_library(mirrage_gui STATIC
src/color_picker.cpp
src/debug_ui.cpp
src/gui.cpp
src/menu.cpp
src/text_edit.cpp
......
/** Console and UI elements for debugging ************************************
* *
* Copyright (c) 2018 Florian Oetke *
* This file is distributed under the MIT License *
* See LICENSE file for details. *
\*****************************************************************************/
#pragma once
#include <mirrage/utils/console_command.hpp>
#include <mirrage/utils/messagebus.hpp>
#include <unordered_set>
namespace mirrage::gui {
class Gui;
class Debug_ui;
class Debug_console_appender : public plog::IAppender {
public:
void write(const plog::Record& record) override;
private:
friend class Debug_ui;
struct Msg {
plog::Severity severity;
std::string msg;
Msg(plog::Severity s, std::string msg) : severity(s), msg(std::move(msg)) {}
};
std::vector<Msg> _messages;
};
inline auto& debug_console_appender()
{
static auto inst = Debug_console_appender();
return inst;
}
class Debug_ui {
public:
Debug_ui(Gui&, util::Message_bus&);
void draw();
private:
static constexpr auto max_command_length = 256;
Gui& _gui;
util::Mailbox_collection _mailbox;
bool _show_console = false;
bool _show_console_changed = false;
std::uint32_t _scroll_x = 0;
std::uint32_t _scroll_y = 0;
bool _scroll_lock = true;
std::array<char, max_command_length> _command_input_buffer{};
int _command_input_length = 0;
util::Console_command_container _commands;
std::unordered_set<std::string> _shown_debug_menus;
};
class Debug_menu {
public:
Debug_menu(std::string name);
virtual ~Debug_menu();
virtual void draw(Gui&) = 0;
virtual void on_show() = 0;
virtual void on_hide() = 0;
static void draw_all(const std::string& name, Gui& gui)
{
for(auto dm : instances())
if(dm->_name == name)
dm->draw(gui);
}
static auto is_debug_menu(const std::string& name) -> bool
{
for(auto dm : instances())
if(dm->_name == name)
return true;
return false;
}
template <class Stream>
static Stream& print_names(Stream& stream)
{
auto first = true;
for(auto dm : instances()) {
if(first)
first = false;
else
stream << ", ";
stream << dm->_name;
}
return stream;
}
private:
friend class Debug_ui;
static auto instances() -> std::vector<Debug_menu*>&
{
static auto list = std::vector<Debug_menu*>();
return list;
}
std::string _name;
};
} // namespace mirrage::gui
......@@ -58,6 +58,10 @@ namespace mirrage::gui {
struct Nk_renderer;
}
extern nk_flags nk_complete_begin(struct nk_context* ctx, char* memory, int* len, int max);
extern void nk_complete_end(struct nk_context*);
struct Gui_vertex {
glm::vec2 position;
glm::vec2 uv;
......
#include <mirrage/gui/debug_ui.hpp>
#include <mirrage/gui/gui.hpp>
#include <mirrage/input/events.hpp>
#include <mirrage/utils/console_command.hpp>
namespace mirrage::gui {
namespace {
constexpr auto msg_height = 16.f;
constexpr auto msg_color = std::array<nk_color, 7>{{
nk_color{255, 255, 255, 255}, // none = 0,
nk_color{255, 0, 0, 255}, // fatal = 1,
nk_color{255, 128, 0, 255}, // error = 2,
nk_color{255, 200, 80, 255}, // warning = 3,
nk_color{255, 255, 255, 255}, // info = 4,
nk_color{140, 140, 140, 255}, // debug = 5,
nk_color{140, 140, 140, 255} // verbose = 6
}};
} // namespace
void Debug_console_appender::write(const plog::Record& record)
{
_messages.emplace_back(record.getSeverity(), plog::TxtFormatter::format(record));
}
Debug_ui::Debug_ui(Gui& gui, util::Message_bus& bus) : _gui(gui), _mailbox(bus)
{
_commands.add("show <ui>", [&](std::string ui) {
if(Debug_menu::is_debug_menu(ui)) {
_shown_debug_menus.insert(std::move(ui));
_show_console = false;
} else
LOG(plog::error) << "Unknown ui menu " << ui
<< " expected one of: " << Debug_menu::print_names;
});
_commands.add("hide <ui>", [&](std::string ui) {
if(Debug_menu::is_debug_menu(ui))
_shown_debug_menus.erase(ui);
else
LOG(plog::error) << "Unknown ui menu " << ui
<< " expected one of: " << Debug_menu::print_names;
});
_commands.add("list_uis", [&]() { LOG(plog::info) << "UI menus: " << Debug_menu::print_names; });
_mailbox.subscribe_to([&](input::Once_action& e) {
switch(e.id) {
case "console"_strid:
_show_console = !_show_console;
_show_console_changed = true;
break;
}
});
}
void Debug_ui::draw()
{
_mailbox.update_subscriptions();
for(auto& dm : _shown_debug_menus) {
Debug_menu::draw_all(dm, _gui);
}
if(!_show_console)
return;
auto viewport = _gui.virtual_viewport();
auto width = viewport.z - 100;
auto ctx = _gui.ctx();
if(nk_begin(ctx, "debug_console", nk_rect(50, 0, float(width), float(400)), NK_WINDOW_NO_SCROLLBAR)) {
nk_layout_row_dynamic(ctx, 360, 1);
auto max_y_scroll = std::uint32_t(
util::max(350, msg_height * debug_console_appender()._messages.size()) - 350);
if(_scroll_lock) {
_scroll_y = max_y_scroll;
}
if(nk_group_scrolled_offset_begin(
ctx, &_scroll_x, &_scroll_y, "debug_console_out", NK_WINDOW_BORDER)) {
nk_layout_row_dynamic(ctx, 12, 1);
auto begin = int(std::floor(float(_scroll_y) / msg_height));
auto end = int(std::ceil(float(_scroll_y + 350) / msg_height));
auto i = 0;
for(const auto& msg : debug_console_appender()._messages) {
if(i < begin || i > end)
nk_spacing(ctx, 1);
else
nk_text_colored(ctx,
msg.msg.c_str(),
int(msg.msg.size()) - 1,
NK_TEXT_ALIGN_LEFT,
msg_color[std::size_t(msg.severity)]);
i++;
}
}
nk_group_end(ctx);
_scroll_lock = _scroll_y >= max_y_scroll;
if(_show_console_changed) {
_show_console_changed = false;
nk_edit_focus(ctx, 0);
}
nk_layout_row_dynamic(ctx, 30, 1);
auto cmd_event = nk_complete_begin(
ctx, _command_input_buffer.data(), &_command_input_length, max_command_length);
auto cmd = std::string_view(_command_input_buffer.data(), std::size_t(_command_input_length));
if(cmd_event & NK_EDIT_COMMITED) {
if(util::Console_command_container::call(cmd)) {
_command_input_length = 0;
}
}
if(cmd_event & NK_EDIT_ACTIVE) {
nk_layout_row_dynamic(ctx, 12, 1);
for(auto& s : util::Console_command_container::complete(cmd)) {
nk_label(ctx, s->api().c_str(), NK_TEXT_LEFT);
}
nk_complete_end(ctx);
}
}
nk_end(ctx);
}
Debug_menu::Debug_menu(std::string name) : _name(std::move(name)) { instances().emplace_back(this); }
Debug_menu::~Debug_menu() { util::erase_fast(instances(), this); }
} // namespace mirrage::gui
......@@ -20,6 +20,30 @@ extern void ref_embedded_assets_mirrage_gui();
namespace mirrage::gui {
nk_flags nk_complete_begin(struct nk_context* ctx, char* memory, int* len, int max)
{
auto header = nk_widget_bounds(ctx);
auto cmd_event = nk_edit_string(ctx, NK_EDIT_FIELD | NK_EDIT_SIG_ENTER, memory, len, max, nullptr);
if(cmd_event & NK_EDIT_ACTIVE) {
struct nk_rect bounds;
bounds.x = 0;
bounds.y = header.y + header.h - ctx->style.window.combo_border;
bounds.w = header.w;
bounds.h = nk_null_rect.h;
nk_popup_begin(ctx,
NK_POPUP_DYNAMIC,
"__##Complete##__",
NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER | NK_PANEL_COMBO,
bounds);
}
return cmd_event;
}
void nk_complete_end(struct nk_context* ctx) { nk_tooltip_end(ctx); }
namespace {
struct Nk_renderer;
......
......@@ -170,6 +170,9 @@ namespace mirrage::renderer {
std::unique_ptr<Asset_loaders> _asset_loaders;
Render_pass_mask _all_passes_mask;
class Profiler_menu;
std::unique_ptr<Profiler_menu> _profiler_menu;
void _present();
auto _rank_device(vk::PhysicalDevice, util::maybe<std::uint32_t> gqueue) -> int;
auto _init_device(vk::PhysicalDevice, util::maybe<std::uint32_t> gqueue)
......
......@@ -9,6 +9,8 @@
#include <mirrage/graphic/device.hpp>
#include <mirrage/graphic/render_pass.hpp>
#include <mirrage/graphic/window.hpp>
#include <mirrage/gui/debug_ui.hpp>
#include <mirrage/gui/gui.hpp>
#include <glm/glm.hpp>
#include <gsl/gsl>
......@@ -24,8 +26,153 @@ namespace mirrage::renderer {
{
return std::any_of(passes.begin(), passes.end(), [&](auto& p) { return p->requires_gbuffer(); });
}
template <typename T>
auto to_fixed_str(T num, int digits)
{
auto ss = std::stringstream{};
ss << std::fixed << std::setprecision(digits) << num;
return ss.str();
}
auto pad_left(const std::string& str, int padding)
{
return std::string(std::size_t(padding), ' ') + str;
}
template <std::size_t N, typename Container, typename Comp>
auto top_n(const Container& container, Comp&& less)
{
auto max_elements = std::array<decltype(container.begin()), N>();
max_elements.fill(container.end());
for(auto iter = container.begin(); iter != container.end(); iter++) {
// compare with each of the top elements
for(auto i = std::size_t(0); i < N; i++) {
if(max_elements[i] == container.end() || less(*max_elements[i], *iter)) {
// move top elements to make room
for(auto j = i + 1; j < N; j++) {
max_elements[j] = max_elements[j - 1];
}
max_elements[i] = iter;
break;
}
}
}
return max_elements;
}
template <typename Container, typename T>
auto index_of(const Container& container, const T& element) -> int
{
auto top_entry = std::find(container.begin(), container.end(), element);
if(top_entry == container.end())
return -1;
return gsl::narrow<int>