aboutsummaryrefslogtreecommitdiff
path: root/src/core/application.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/application.cpp')
-rw-r--r--src/core/application.cpp276
1 files changed, 180 insertions, 96 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp
index f9e52a4..ae8a5d9 100644
--- a/src/core/application.cpp
+++ b/src/core/application.cpp
@@ -13,6 +13,7 @@
#include <ImGuizmo.h>
#include "platform/vulkan/vulkan_renderer.h"
+#include "platform/opengl/opengl_renderer.h"
#ifdef __APPLE__
#include "platform/metal/metal_context.h"
@@ -60,12 +61,12 @@ namespace Donut
{
while (m_running)
{
- if (m_vulkan_renderer)
+ if (m_renderer)
{
glfwPollEvents(); // before the ImGui frame so input is current
if (!m_minimized)
- m_vulkan_renderer->draw_frame(glm::vec4(0.05f, 0.06f, 0.10f, 1.0f),
- [this] { build_vulkan_ui(); });
+ m_renderer->draw_frame(glm::vec4(0.05f, 0.06f, 0.10f, 1.0f),
+ [this] { build_ui(); });
}
else
{
@@ -79,86 +80,138 @@ namespace Donut
}
}
- auto Application::build_vulkan_ui() -> void
+ // One-time ImGui theme: rounded, roomy, dark with a warm accretion-disk accent.
+ static auto apply_donut_style() -> void
{
- ImGui::Begin("Donut - Vulkan backend");
- ImGui::Text("Geodesic black hole through Vulkan (MoltenVK).");
- ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate);
- ImGui::Separator();
+ ImGuiStyle& s = ImGui::GetStyle();
+ s.WindowRounding = 7.0f; s.ChildRounding = 5.0f; s.FrameRounding = 4.0f;
+ s.GrabRounding = 4.0f; s.PopupRounding = 4.0f; s.ScrollbarRounding = 5.0f; s.TabRounding = 4.0f;
+ s.WindowPadding = ImVec2(12, 12); s.FramePadding = ImVec2(9, 5);
+ s.ItemSpacing = ImVec2(9, 8); s.ItemInnerSpacing = ImVec2(7, 5);
+ s.WindowBorderSize = 0.0f; s.FrameBorderSize = 0.0f; s.WindowTitleAlign = ImVec2(0.02f, 0.5f);
- if (m_vulkan_renderer)
- {
- bool scene = m_vulkan_renderer->is_scene_mode();
- ImGui::Text("View");
- if (ImGui::RadioButton("Black hole", !scene)) m_vulkan_renderer->set_scene_mode(false);
- ImGui::SameLine();
- if (ImGui::RadioButton("Scene", scene)) m_vulkan_renderer->set_scene_mode(true);
- ImGui::Separator();
+ const ImVec4 amber = ImVec4(0.98f, 0.62f, 0.20f, 1.00f);
+ const ImVec4 amberHi = ImVec4(1.00f, 0.73f, 0.36f, 1.00f);
+ ImVec4* c = s.Colors;
+ c[ImGuiCol_WindowBg] = ImVec4(0.07f, 0.08f, 0.10f, 0.97f);
+ c[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.11f, 0.13f, 0.55f);
+ c[ImGuiCol_PopupBg] = ImVec4(0.09f, 0.10f, 0.12f, 0.98f);
+ c[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
+ c[ImGuiCol_TitleBgActive] = ImVec4(0.13f, 0.14f, 0.17f, 1.00f);
+ c[ImGuiCol_Text] = ImVec4(0.90f, 0.91f, 0.93f, 1.00f);
+ c[ImGuiCol_TextDisabled] = ImVec4(0.48f, 0.50f, 0.54f, 1.00f);
+ c[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.17f, 0.20f, 1.00f);
+ c[ImGuiCol_FrameBgHovered] = ImVec4(0.22f, 0.24f, 0.28f, 1.00f);
+ c[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.28f, 0.33f, 1.00f);
+ c[ImGuiCol_Header] = ImVec4(0.17f, 0.19f, 0.23f, 1.00f);
+ c[ImGuiCol_HeaderHovered] = ImVec4(0.24f, 0.27f, 0.32f, 1.00f);
+ c[ImGuiCol_HeaderActive] = ImVec4(0.28f, 0.31f, 0.37f, 1.00f);
+ c[ImGuiCol_Button] = ImVec4(0.20f, 0.22f, 0.26f, 1.00f);
+ c[ImGuiCol_ButtonHovered] = ImVec4(0.27f, 0.30f, 0.35f, 1.00f);
+ c[ImGuiCol_ButtonActive] = amber;
+ c[ImGuiCol_SliderGrab] = amber;
+ c[ImGuiCol_SliderGrabActive]= amberHi;
+ c[ImGuiCol_CheckMark] = amberHi;
+ c[ImGuiCol_Separator] = ImVec4(0.20f, 0.22f, 0.26f, 1.00f);
+ c[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.26f, 0.30f, 1.00f);
+ }
- if (scene)
- {
- ImGui::TextDisabled("Drag to orbit, scroll to zoom. HDRI skybox behind.");
- ImGui::Separator();
+ auto Application::build_ui() -> void
+ {
+ static bool styled = false;
+ if (!styled) { apply_donut_style(); styled = true; }
+ if (!m_renderer) return;
+ RendererBackend& r = *m_renderer;
- auto& objs = m_vulkan_renderer->scene_objects();
- static int sel = 0;
- if (sel >= (int)objs.size()) sel = (int)objs.size() - 1;
+ // Full-viewport dock space with a pass-through centre, so the panel can be
+ // docked to any edge while the render shows through the middle.
+ ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
- ImGui::Text("Spheres (%d)", (int)objs.size());
- if (ImGui::Button("Add") && objs.size() < 64)
- {
- SceneObject o;
- o.position = glm::vec3(((int)objs.size() % 5) * 5.0f - 10.0f, 2.0f, ((int)objs.size() / 5) * 5.0f);
- o.radius = 1.5f;
- o.color = glm::vec3(0.35f + 0.12f * (objs.size() % 5), 0.55f, 0.9f - 0.12f * (objs.size() % 4));
- objs.push_back(o);
- sel = (int)objs.size() - 1;
- }
- ImGui::SameLine();
- if (ImGui::Button("Delete") && !objs.empty())
- {
- objs.erase(objs.begin() + sel);
- if (sel >= (int)objs.size()) sel = (int)objs.size() - 1;
- }
+ ImGui::SetNextWindowSize(ImVec2(340, 580), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Donut \xc2\xb7 Black Hole");
- ImGui::BeginChild("obj_list", ImVec2(0, 90), true);
- for (int i = 0; i < (int)objs.size(); ++i)
- {
- std::string label = "Sphere " + std::to_string(i);
- if (ImGui::Selectable(label.c_str(), sel == i)) sel = i;
- }
- ImGui::EndChild();
+ // --- header: device + performance -----------------------------------
+ ImGuiIO& io = ImGui::GetIO();
+ ImGui::TextDisabled("%s", r.device_name().empty() ? "GPU" : r.device_name().c_str());
+ ImGui::Text("%.0f FPS", io.Framerate);
+ ImGui::SameLine(); ImGui::TextDisabled("%.1f ms/frame", io.Framerate > 0 ? 1000.0f / io.Framerate : 0.0f);
+ ImGui::Spacing();
- m_vulkan_renderer->set_selected_object(sel);
- if (sel >= 0 && sel < (int)objs.size())
- {
- SceneObject& o = objs[sel];
- ImGui::DragFloat3("Position", &o.position.x, 0.1f);
- ImGui::DragFloat("Radius", &o.radius, 0.05f, 0.1f, 20.0f);
- ImGui::ColorEdit3("Color", &o.color.x);
- }
+ if (ImGui::CollapsingHeader("Renderer", ImGuiTreeNodeFlags_DefaultOpen))
+ {
+ auto& s = SettingsManager::get_settings();
+ const char* apis[] = { "OpenGL", "Vulkan" };
+ int cur = (s.graphics.render_api == "Vulkan") ? 1 : 0;
+ if (ImGui::Combo("Graphics API", &cur, apis, 2))
+ {
+ s.graphics.render_api = apis[cur];
+ SettingsManager::save_settings();
}
+ const char* running = (RendererAPI::get_api() == RendererAPI::API::Vulkan) ? "Vulkan" : "OpenGL";
+ if (s.graphics.render_api != running)
+ ImGui::TextColored(ImVec4(0.98f, 0.62f, 0.20f, 1.0f), "Restart to apply (%s running)", running);
else
+ ImGui::TextDisabled("Active backend: %s", running);
+ }
+
+ ImGui::Spacing();
+ bool scene = r.is_scene_mode();
+ ImGui::TextDisabled("VIEW");
+ if (ImGui::RadioButton("Black hole", !scene)) r.set_scene_mode(false);
+ ImGui::SameLine();
+ if (ImGui::RadioButton("Scene", scene)) r.set_scene_mode(true);
+ ImGui::Separator();
+
+ if (scene)
+ {
+ build_scene_ui();
+ }
+ else
+ {
+ BlackHoleParams& bh = r.black_hole_params();
+
+ if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen))
{
- bool free_fly = m_vulkan_renderer->is_free_fly();
- ImGui::Text("Camera");
- if (ImGui::RadioButton("Orbital", !free_fly)) m_vulkan_renderer->set_free_fly(false);
+ bool ff = r.is_free_fly();
+ if (ImGui::RadioButton("Orbital", !ff)) r.set_free_fly(false);
ImGui::SameLine();
- if (ImGui::RadioButton("Free-fly", free_fly)) m_vulkan_renderer->set_free_fly(true);
- ImGui::TextDisabled(free_fly ? "WASD move | Q/E down-up | Shift boost | drag to look"
- : "Drag to orbit | scroll to zoom");
- ImGui::Separator();
+ if (ImGui::RadioButton("Free-fly", ff)) r.set_free_fly(true);
+ ImGui::SameLine();
+ if (ImGui::Button("Reset view")) r.reset_camera();
+ ImGui::SliderFloat("FOV", &bh.fov_degrees, 20.0f, 90.0f, "%.0f\xc2\xb0");
+ ImGui::TextDisabled(ff ? "WASD move | Q/E down-up | Shift boost | drag look"
+ : "Drag to orbit | scroll to zoom");
+ }
+
+ if (ImGui::CollapsingHeader("Accretion disk", ImGuiTreeNodeFlags_DefaultOpen))
+ {
+ ImGui::SliderFloat("Temperature", &bh.temperature, 2000.0f, 15000.0f, "%.0f K");
+ ImGui::SliderFloat("Brightness", &bh.brightness, 0.0f, 3.0f, "%.2f");
+ ImGui::SliderFloat("Inner radius", &bh.disk_inner_rs, 3.0f, 12.0f, "%.1f r_s");
+ if (bh.disk_outer_rs < bh.disk_inner_rs + 0.5f) bh.disk_outer_rs = bh.disk_inner_rs + 0.5f;
+ ImGui::SliderFloat("Outer radius", &bh.disk_outer_rs, bh.disk_inner_rs + 0.5f, 25.0f, "%.1f r_s");
+ ImGui::SliderFloat("Turbulence", &bh.turbulence, 0.0f, 2.0f, "%.2f");
+ ImGui::TextDisabled("Inner edge is the ISCO (3 r_s). Novikov-Thorne profile.");
+ }
+ if (ImGui::CollapsingHeader("Quality"))
+ {
+ ImGui::SliderInt("Integration steps", &bh.quality_steps, 2000, 15000);
+ ImGui::TextDisabled("Deeper lensing at higher cost. Settled frame is 4x supersampled.");
+ }
+
+ if (ImGui::CollapsingHeader("Environment", ImGuiTreeNodeFlags_DefaultOpen))
+ {
auto& hdri = HDRIManager::get();
- std::string current = m_vulkan_renderer->current_hdri();
+ std::string current = r.current_hdri();
std::string preview = hdri.get_hdri_name(current);
- if (ImGui::BeginCombo("HDRI", preview.c_str()))
+ if (ImGui::BeginCombo("Starfield", preview.c_str()))
{
for (const auto& path : hdri.get_available_hdri())
{
bool selected = (path == current);
if (ImGui::Selectable(hdri.get_hdri_name(path).c_str(), selected))
- m_vulkan_renderer->set_hdri(path);
+ r.set_hdri(path);
if (selected) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
@@ -170,6 +223,52 @@ namespace Donut
ImGui::End();
}
+ auto Application::build_scene_ui() -> void
+ {
+ RendererBackend& r = *m_renderer;
+ if (!ImGui::CollapsingHeader("Objects", ImGuiTreeNodeFlags_DefaultOpen))
+ return;
+
+ ImGui::TextDisabled("Drag to orbit, scroll to zoom. HDRI skybox behind.");
+ auto& objs = r.scene_objects();
+ static int sel = 0;
+ if (sel >= (int)objs.size()) sel = (int)objs.size() - 1;
+
+ ImGui::Text("Spheres (%d)", (int)objs.size());
+ if (ImGui::Button("Add") && objs.size() < 64)
+ {
+ SceneObject o;
+ o.position = glm::vec3(((int)objs.size() % 5) * 5.0f - 10.0f, 2.0f, ((int)objs.size() / 5) * 5.0f);
+ o.radius = 1.5f;
+ o.color = glm::vec3(0.35f + 0.12f * (objs.size() % 5), 0.55f, 0.9f - 0.12f * (objs.size() % 4));
+ objs.push_back(o);
+ sel = (int)objs.size() - 1;
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Delete") && !objs.empty())
+ {
+ objs.erase(objs.begin() + sel);
+ if (sel >= (int)objs.size()) sel = (int)objs.size() - 1;
+ }
+
+ ImGui::BeginChild("obj_list", ImVec2(0, 90), true);
+ for (int i = 0; i < (int)objs.size(); ++i)
+ {
+ std::string label = "Sphere " + std::to_string(i);
+ if (ImGui::Selectable(label.c_str(), sel == i)) sel = i;
+ }
+ ImGui::EndChild();
+
+ r.set_selected_object(sel);
+ if (sel >= 0 && sel < (int)objs.size())
+ {
+ SceneObject& o = objs[sel];
+ ImGui::DragFloat3("Position", &o.position.x, 0.1f);
+ ImGui::DragFloat("Radius", &o.radius, 0.05f, 0.1f, 20.0f);
+ ImGui::ColorEdit3("Color", &o.color.x);
+ }
+ }
+
auto Application::close() -> void
{
m_running = false;
@@ -192,9 +291,9 @@ namespace Donut
else
m_minimized = false;
- if (m_vulkan_renderer)
+ if (m_renderer)
{
- m_vulkan_renderer->on_resize(e.get_width(), e.get_height());
+ m_renderer->resize(e.get_width(), e.get_height());
}
else
{
@@ -237,44 +336,29 @@ namespace Donut
auto Application::on_init() -> void
{
- // (Logger, settings and API selection happen in the constructor.)
+ // (Logger, settings and API selection happen in the constructor.) Both
+ // backends run the SAME application through the RendererBackend interface,
+ // so the app behaves identically regardless of the active graphics API.
+ int w = 0, h = 0;
+ glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h);
if (RendererAPI::get_api() == RendererAPI::API::Vulkan)
- {
- int w = 0, h = 0;
- glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h);
- m_vulkan_renderer = create_scope<VulkanRenderer>();
- if (!m_vulkan_renderer->init(m_window->get_native_window(), w, h))
- DONUT_ERROR("Vulkan renderer initialization failed");
- else
- m_vulkan_renderer->init_im_gui();
- // Scene rendering hooks in here in the B-3 phase; the Vulkan path now
- // clears, presents, and draws the ImGui UI.
- return;
- }
-
- Renderer::init();
- m_window->init_im_gui();
-
- Renderer::on_window_resize(1280, 720);
- RenderCommand::set_face_culling(false);
-
- m_engine = create_scope<Engine>();
- m_engine->set_window_dimensions(1280, 720);
+ m_renderer = create_scope<VulkanRenderer>();
+ else
+ m_renderer = create_scope<OpenGLRenderer>();
- m_state_manager = create_scope<StateManager>();
- m_state_manager->register_state("Config", create_scope<ConfigState>());
- m_state_manager->register_state("Simulation", create_scope<SimulationState>());
- m_state_manager->register_state("WorldBuilder", create_scope<WorldBuilderState>());
- m_state_manager->switch_to_state("Config");
+ if (!m_renderer->init(m_window->get_native_window(), w, h))
+ DONUT_ERROR("Renderer initialization failed");
+ else
+ m_renderer->init_ui();
}
auto Application::on_shutdown() -> void
{
- if (m_vulkan_renderer)
+ if (m_renderer)
{
- m_vulkan_renderer->shutdown();
- m_vulkan_renderer.reset();
+ m_renderer->shutdown();
+ m_renderer.reset();
}
else
{