Files
dusklight/src/dusk/imgui/ImGuiConsole.cpp
T
2026-03-14 04:07:30 -07:00

234 lines
6.7 KiB
C++

#include <array>
#include <numeric>
#include <string_view>
#include <chrono>
#include <thread>
#include "fmt/format.h"
#include "imgui.h"
#include "aurora/gfx.h"
#include "ImGuiConsole.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#if _WIN32
#include "Windows.h"
#endif
using namespace std::string_literals;
using namespace std::string_view_literals;
namespace dusk {
void ImGuiStringViewText(std::string_view text) {
// begin()/end() do not work on MSVC
ImGui::TextUnformatted(text.data(), text.data() + text.size());
}
std::string BytesToString(size_t bytes) {
constexpr std::array suffixes{ "B"sv, "KB"sv, "MB"sv, "GB"sv, "TB"sv, "PB"sv, "EB"sv };
uint32_t s = 0;
auto count = static_cast<double>(bytes);
while (count >= 1024.0 && s < 7) {
s++;
count /= 1024.0;
}
if (count - floor(count) == 0.0)
{
return fmt::format(FMT_STRING("{}{}"), static_cast<size_t>(count), suffixes[s]);
}
return fmt::format(FMT_STRING("{:.1f}{}"), count, suffixes[s]);
}
void SetOverlayWindowLocation(int corner) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 workPos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!
ImVec2 workSize = viewport->WorkSize;
ImVec2 windowPos;
ImVec2 windowPosPivot;
constexpr float padding = 10.0f;
windowPos.x = (corner & 1) != 0 ? (workPos.x + workSize.x - padding) : (workPos.x + padding);
windowPos.y = (corner & 2) != 0 ? (workPos.y + workSize.y - padding) : (workPos.y + padding);
windowPosPivot.x = (corner & 1) != 0 ? 1.0f : 0.0f;
windowPosPivot.y = (corner & 2) != 0 ? 1.0f : 0.0f;
ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPosPivot);
}
bool ShowCornerContextMenu(int& corner, int avoidCorner) {
bool result = false;
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::MenuItem("Custom", nullptr, corner == -1)) {
corner = -1;
result = true;
}
if (ImGui::MenuItem("Top-left", nullptr, corner == 0, avoidCorner != 0)) {
corner = 0;
result = true;
}
if (ImGui::MenuItem("Top-right", nullptr, corner == 1, avoidCorner != 1)) {
corner = 1;
result = true;
}
if (ImGui::MenuItem("Bottom-left", nullptr, corner == 2, avoidCorner != 2)) {
corner = 2;
result = true;
}
if (ImGui::MenuItem("Bottom-right", nullptr, corner == 3, avoidCorner != 3)) {
corner = 3;
result = true;
}
ImGui::EndPopup();
}
return result;
}
ImGuiConsole g_imguiConsole;
ImGuiConsole::ImGuiConsole() {}
void ImGuiConsole::draw() {
if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) {
return;
}
if (ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuTools.draw();
ImGui::EndMainMenuBar();
}
}
bool ImGuiConsole::CheckMenuViewToggle(ImGuiKey key, bool& active) {
if (ImGui::IsKeyPressed(key)) {
active = !active;
}
return active;
}
std::string_view backend_name(AuroraBackend backend) {
switch (backend) {
default:
return "Auto"sv;
case BACKEND_D3D12:
return "D3D12"sv;
case BACKEND_D3D11:
return "D3D11"sv;
case BACKEND_METAL:
return "Metal"sv;
case BACKEND_VULKAN:
return "Vulkan"sv;
case BACKEND_OPENGL:
return "OpenGL"sv;
case BACKEND_OPENGLES:
return "OpenGL ES"sv;
case BACKEND_WEBGPU:
return "WebGPU"sv;
case BACKEND_NULL:
return "Null"sv;
}
}
}
class Limiter
{
using delta_clock = std::chrono::high_resolution_clock;
using duration_t = std::chrono::nanoseconds;
public:
void Reset()
{
m_oldTime = delta_clock::now();
}
void Sleep(duration_t targetFrameTime)
{
if (targetFrameTime.count() == 0)
{
return;
}
auto start = delta_clock::now();
duration_t adjustedSleepTime = SleepTime(targetFrameTime);
if (adjustedSleepTime.count() > 0)
{
NanoSleep(adjustedSleepTime);
duration_t overslept = TimeSince(start) - adjustedSleepTime;
if (overslept < duration_t{ targetFrameTime })
{
m_overheadTimes[m_overheadTimeIdx] = overslept;
m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size();
}
}
Reset();
}
duration_t SleepTime(duration_t targetFrameTime)
{
const auto sleepTime = duration_t{ targetFrameTime } - TimeSince(m_oldTime);
m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) /
m_overheadTimes.size();
if (sleepTime > m_overhead)
{
return sleepTime - m_overhead;
}
return duration_t{ 0 };
}
private:
delta_clock::time_point m_oldTime;
std::array<duration_t, 4> m_overheadTimes{};
size_t m_overheadTimeIdx = 0;
duration_t m_overhead = duration_t{ 0 };
duration_t TimeSince(delta_clock::time_point start)
{
return std::chrono::duration_cast<duration_t>(delta_clock::now() - start);
}
#if _WIN32
bool m_initialized;
double m_countPerNs;
void NanoSleep(const duration_t duration)
{
if (!m_initialized)
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
m_countPerNs = static_cast<double>(freq.QuadPart) / 1000000000.0;
m_initialized = true;
}
DWORD ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
auto tickCount =
static_cast<LONGLONG>(static_cast<double>(duration.count()) * m_countPerNs);
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
if (ms > 10)
{
// Adjust for Sleep overhead
::Sleep(ms - 10);
}
auto end = count.QuadPart + tickCount;
do
{
QueryPerformanceCounter(&count);
} while (count.QuadPart < end);
}
#else
void NanoSleep(const duration_t duration)
{
std::this_thread::sleep_for(duration);
}
#endif
};
static Limiter g_frameLimiter;
void frame_limiter()
{
g_frameLimiter.Sleep(
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds{ 1 }) / 60);
}