#include "core/application.h" #include "core/assert.h" #include "core/log.h" #include "core/window.h" #include "core/window_manager.h" #include "debugger/debugger.h" #include "ui/ui.h" #include #include #include #include #include #include #include namespace Hsdbg { namespace { using Clock = std::chrono::steady_clock; constexpr float MAX_DELTA_TIME = 0.25f; constexpr double MINIMIZED_WAIT_TIMEOUT = 0.1; // dev aid: HSDBG_CAPTURE= writes the frame numbered // HSDBG_CAPTURE_FRAME (default 240) as a binary ppm; returns true on // that frame so the caller can quit auto capture_frame(uint64_t frame, uint32_t width, uint32_t height) -> bool { static const char* path = std::getenv("HSDBG_CAPTURE"); if (path == nullptr) return false; static const uint64_t target = [] { uint64_t value = 240; if (const char* text = std::getenv("HSDBG_CAPTURE_FRAME")) std::from_chars(text, text + std::char_traits::length(text), value); return value; }(); if (frame != target) return false; const size_t stride = static_cast(width) * 3; std::vector pixels(stride * height); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, static_cast(width), static_cast(height), GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); std::ofstream out(path, std::ios::binary); out << "P6\n" << width << ' ' << height << "\n255\n"; for (uint32_t y = height; y-- > 0;) out.write(reinterpret_cast(pixels.data() + y * stride), static_cast(stride)); Log::info("capture: wrote '{}' ({}x{})", path, width, height); return true; } } Application* Application::s_instance = nullptr; Application::Application(const ApplicationSpec& spec) : m_spec(spec) { HSDBG_ASSERT(s_instance == nullptr, "an Application instance already exists"); s_instance = this; Log::info("{}: starting up", m_spec.name); m_window_manager = std::make_unique(); WindowSpec window_spec; window_spec.title = m_spec.name; window_spec.width = m_spec.width; window_spec.height = m_spec.height; window_spec.vsync = m_spec.vsync; m_window = &m_window_manager->create_window(window_spec); m_debugger = std::make_unique(); m_ui = std::make_unique(*m_window); } Application::~Application() { m_ui.reset(); m_debugger.reset(); m_window = nullptr; m_window_manager.reset(); Log::info("{}: shutting down after {} frames", m_spec.name, m_frame_count); s_instance = nullptr; } auto Application::get() -> Application& { HSDBG_ASSERT(s_instance != nullptr, "no Application instance has been created"); return *s_instance; } auto Application::run() -> void { m_running = true; auto last_frame_time = Clock::now(); while (m_running) { const auto now = Clock::now(); const float elapsed = std::chrono::duration(now - last_frame_time).count(); last_frame_time = now; m_delta_time = elapsed < MAX_DELTA_TIME ? elapsed : MAX_DELTA_TIME; m_time += m_delta_time; m_frame_count += 1; m_window_manager->poll_events(); if (m_window->should_close()) { close(); break; } if (m_window->is_minimized()) { m_window_manager->wait_events(MINIMIZED_WAIT_TIMEOUT); continue; } update(); render(); m_window->swap_buffers(); } } auto Application::close() -> void { m_running = false; } auto Application::update() -> void { m_debugger->update(); // dev aid: HSDBG_STEP_AT=[,…] steps over a line on those // frames while stopped, so a capture can show a target that has moved static const std::vector step_frames = [] { std::vector frames; const char* text = std::getenv("HSDBG_STEP_AT"); for (const char* at = text; at != nullptr && *at != '\0';) { uint64_t value = 0; const auto [next, error] = std::from_chars(at, at + std::char_traits::length(at), value); if (error != std::errc{}) break; frames.push_back(value); at = *next == ',' ? next + 1 : next; } return frames; }(); if (std::ranges::find(step_frames, m_frame_count) != step_frames.end() && m_debugger->is_stopped()) (void)m_debugger->step_over(); } auto Application::render() -> void { // clear the whole framebuffer at the very start of the frame, before any // drawing, so nothing from the previous frame can survive underneath glViewport(0, 0, static_cast(m_window->framebuffer_width()), static_cast(m_window->framebuffer_height())); glDisable(GL_SCISSOR_TEST); glClearColor(0.05f, 0.05f, 0.06f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); m_ui->begin_frame(); m_ui->draw(*m_debugger); m_ui->end_frame(); if (capture_frame(m_frame_count, m_window->framebuffer_width(), m_window->framebuffer_height())) close(); } }