From 380b4d5d5a3e66d89ca7b3b1601bb23e5a01bf7b Mon Sep 17 00:00:00 2001 From: hachem Date: Sun, 23 Aug 2026 05:53:33 +0200 Subject: feat: unified interface --- src/core/application.cpp | 269 +++++++++++++++-------- src/core/application.h | 8 +- src/engine/engine.cpp | 44 ++-- src/engine/engine.h | 5 + src/platform/opengl/opengl_renderer.cpp | 365 ++++++++++++++++++++++++++++++++ src/platform/opengl/opengl_renderer.h | 65 ++++++ src/platform/vulkan/vulkan_renderer.cpp | 51 ++++- src/platform/vulkan/vulkan_renderer.h | 50 ++--- src/rendering/renderer_backend.h | 56 +++++ src/rendering/rhi/rhi.h | 108 ++++++++++ 10 files changed, 869 insertions(+), 152 deletions(-) create mode 100644 src/platform/opengl/opengl_renderer.cpp create mode 100644 src/platform/opengl/opengl_renderer.h create mode 100644 src/rendering/renderer_backend.h create mode 100644 src/rendering/rhi/rhi.h (limited to 'src') diff --git a/src/core/application.cpp b/src/core/application.cpp index c075ff7..4e47b86 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -13,6 +13,7 @@ #include #include "platform/vulkan/vulkan_renderer.h" +#include "platform/opengl/opengl_renderer.h" #ifdef __APPLE__ #include "platform/metal/metal_context.h" @@ -58,12 +59,12 @@ namespace Donut { while (m_running) { - if (m_vulkan_renderer) + if (m_renderer) { glfwPollEvents(); 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 { @@ -77,86 +78,134 @@ namespace Donut } } - auto Application::build_vulkan_ui() -> void + 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); + + 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 (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(); - - 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; + 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(); + 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(); @@ -168,6 +217,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; @@ -190,9 +285,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 { @@ -235,40 +330,26 @@ namespace Donut auto Application::on_init() -> void { - 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(); - 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(); - return; - } - - Renderer::init(); - m_window->init_im_gui(); - - Renderer::on_window_resize(1280, 720); - RenderCommand::set_face_culling(false); + int w = 0, h = 0; + glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h); - m_engine = create_scope(); - m_engine->set_window_dimensions(1280, 720); + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + m_renderer = create_scope(); + else + m_renderer = create_scope(); - m_state_manager = create_scope(); - m_state_manager->register_state("Config", create_scope()); - m_state_manager->register_state("Simulation", create_scope()); - m_state_manager->register_state("WorldBuilder", create_scope()); - 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 { diff --git a/src/core/application.h b/src/core/application.h index df6669b..f2b5656 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -7,11 +7,10 @@ #include "log.h" #include "engine/engine.h" +#include "rendering/renderer_backend.h" namespace Donut { - class VulkanRenderer; - class Application { public: @@ -34,13 +33,14 @@ namespace Donut auto on_render() -> void; auto on_event(Event& event) -> void; auto setup_docking_layout() -> void; - auto build_vulkan_ui() -> void; + auto build_ui() -> void; + auto build_scene_ui() -> void; private: Scope m_state_manager; Scope m_window; Scope m_engine; - Scope m_vulkan_renderer; + Scope m_renderer; bool m_running; bool m_minimized; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 87eda62..496dd56 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -48,7 +48,7 @@ namespace Donut } m_camera_ubo = UniformBuffer::create(128, 1); - m_disk_ubo = UniformBuffer::create(sizeof(float) * 5, 2); + m_disk_ubo = UniformBuffer::create(sizeof(float) * 6, 2); uint32_t obj_ubo_size = sizeof(int) + 3 * sizeof(float) + 16 * (sizeof(glm::vec4) + sizeof(glm::vec4)) @@ -214,16 +214,25 @@ namespace Donut int _pad4; } data; - glm::vec3 fwd = glm::normalize(cam.get_orbital_target() - cam.get_orbital_position()); - glm::vec3 up = glm::vec3(0, 1, 0); - glm::vec3 right = glm::normalize(glm::cross(fwd, up)); - up = glm::cross(right, fwd); + glm::vec3 pos, fwd; + if (cam.get_camera_mode() == CameraMode::FPS) + { + pos = cam.get_position(); + fwd = cam.get_forward_direction(); + } + else + { + pos = cam.get_orbital_position(); + fwd = glm::normalize(cam.get_orbital_target() - pos); + } + glm::vec3 right = glm::normalize(glm::cross(fwd, glm::vec3(0, 1, 0))); + glm::vec3 up = glm::cross(right, fwd); - data.pos = cam.get_orbital_position(); + data.pos = pos; data.right = right; data.up = up; data.forward = fwd; - data.tan_half_fov = static_cast(tan(glm::radians(60.0f * 0.5f))); + data.tan_half_fov = static_cast(tan(glm::radians(m_bh.fov_degrees * 0.5f))); data.aspect = static_cast(get_compute_width()) / static_cast(m_compute_height); data.moving = cam.is_dragging() || cam.is_panning(); @@ -258,12 +267,15 @@ namespace Donut auto Engine::upload_disk_ubo() -> void { - float r1 = static_cast(m_sag_a.m_rs * 2.2); - float r2 = static_cast(m_sag_a.m_rs * 5.2); - float num = 2.0f; - float thickness = static_cast(m_sag_a.m_rs * m_disk_thickness); - float disk_data[5] = { r1, r2, num, thickness, m_disk_density }; - + const float rs = 1.269e10f; + float disk_data[6] = { + std::max(m_bh.disk_inner_rs, 3.0f) * rs, + std::max(m_bh.disk_outer_rs, m_bh.disk_inner_rs + 0.5f) * rs, + std::max(m_bh.turbulence, 0.0f), + rs * 0.1f, + std::max(m_bh.brightness, 0.0f), + std::max(m_bh.temperature, 1000.0f), + }; m_disk_ubo->set_data(disk_data, sizeof(disk_data)); m_disk_ubo->bind(2); } @@ -278,13 +290,13 @@ namespace Donut float time; } data; - data.max_steps_moving = m_max_steps_moving; - data.max_steps_static = m_max_steps_static; + data.max_steps_moving = m_bh.quality_steps; + data.max_steps_static = m_bh.quality_steps; data.early_exit_distance = m_early_exit_distance; data.time = static_cast(glfwGetTime()) * m_rotation_speed; #ifdef __APPLE__ - data.max_steps_moving = std::min(data.max_steps_moving, 4000); + data.max_steps_moving = std::min(data.max_steps_moving, 6000); data.max_steps_static = std::min(data.max_steps_static, 6000); #endif diff --git a/src/engine/engine.h b/src/engine/engine.h index 0451d40..7bab179 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -12,6 +12,7 @@ #include "object.h" #include "rendering/renderer.h" +#include "rendering/renderer_backend.h" #include "rendering/shader.h" #include "rendering/vertex_array.h" #include "rendering/texture.h" @@ -128,6 +129,8 @@ namespace Donut auto set_hdri_environment(Ref hdri) -> void { m_hdri_environment = hdri; } auto get_hdri_environment() const -> Ref { return m_hdri_environment; } + + auto black_hole_params() -> BlackHoleParams& { return m_bh; } private: auto create_compute_program(const char* path) -> Ref; std::pair, Ref> quad_vao(); @@ -166,6 +169,8 @@ namespace Donut int m_max_steps_static = 30000; float m_early_exit_distance = 5.0e11f; + BlackHoleParams m_bh; + float m_disk_thickness = 0.1f; float m_disk_density = 0.1f; float m_rotation_speed = 1.0f; diff --git a/src/platform/opengl/opengl_renderer.cpp b/src/platform/opengl/opengl_renderer.cpp new file mode 100644 index 0000000..719d225 --- /dev/null +++ b/src/platform/opengl/opengl_renderer.cpp @@ -0,0 +1,365 @@ +#include "opengl_renderer.h" + +#include "engine/engine.h" +#include "core/log.h" +#include "core/camera.h" +#include "core/hdri_manager.h" +#include "rendering/renderer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/hdri_manager.h" +#include "rendering/texture.h" + +namespace Donut +{ + static double g_scroll_accum = 0.0; + static GLFWscrollfun g_prev_scroll = nullptr; + static void gl_scroll_cb(GLFWwindow* w, double x, double y) + { + if (g_prev_scroll) g_prev_scroll(w, x, y); + g_scroll_accum += y; + } + + OpenGLRenderer::OpenGLRenderer() = default; + OpenGLRenderer::~OpenGLRenderer() = default; + + auto OpenGLRenderer::init(void* glfwWindow, int width, int height) -> bool + { + m_window = glfwWindow; + auto* win = static_cast(glfwWindow); + glfwMakeContextCurrent(win); + + Renderer::init(); + RenderCommand::set_face_culling(false); + + const GLubyte* name = glGetString(GL_RENDERER); + m_device_name = name ? reinterpret_cast(name) : "OpenGL"; + + m_engine = create_scope(); + m_engine->set_window_dimensions(width, height); +#ifdef __APPLE__ + m_engine->set_compute_height(256); +#else + m_engine->set_compute_height(540); +#endif + m_engine->update_compute_dimensions(); + + Camera& cam = m_engine->get_camera(); + cam.set_camera_mode(CameraMode::Orbital); + cam.set_orbital_target(glm::vec3(0.0f)); + cam.set_orbital_radius(4e11); + cam.set_orbital_limits(2.2e11, 1.5e12); + cam.set_orbital_speed(0.01f); + cam.set_zoom_speed(3e10); + cam.set_azimuth(0.0f); + cam.set_elevation(1.25f); + + m_hdri_path = "assets/hdri/hdr_blue_nebulae_1.hdr"; + HDRIManager::get().set_current_hdri(m_hdri_path); + + create_scene_resources(); + + DONUT_INFO("OpenGL renderer ready: {} ({}x{})", m_device_name, width, height); + return true; + } + + auto OpenGLRenderer::init_ui() -> bool + { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImGui::StyleColorsDark(); + ImGui_ImplGlfw_InitForOpenGL(static_cast(m_window), true); + ImGui_ImplOpenGL3_Init("#version 410"); + g_prev_scroll = glfwSetScrollCallback(static_cast(m_window), gl_scroll_cb); + m_ui_ready = true; + DONUT_INFO("OpenGL: ImGui backend initialized"); + return true; + } + + auto OpenGLRenderer::shutdown() -> void + { + if (m_ui_ready) + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + m_ui_ready = false; + } + m_engine.reset(); + } + + auto OpenGLRenderer::resize(int width, int height) -> void + { + if (m_engine) m_engine->set_window_dimensions(width, height); + } + + auto OpenGLRenderer::process_input() -> void + { + auto* win = static_cast(m_window); + Camera& cam = m_scene_mode ? m_scene_camera : m_engine->get_camera(); + bool over_ui = m_ui_ready && ImGui::GetIO().WantCaptureMouse; + + double now = glfwGetTime(); + float dt = m_last_frame_time > 0.0 ? static_cast(now - m_last_frame_time) : 0.0f; + m_last_frame_time = now; + + double mx = 0, my = 0; glfwGetCursorPos(win, &mx, &my); + bool left_down = glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; + + if (!m_scene_mode && cam.get_camera_mode() == CameraMode::FPS) + { + if (left_down && !m_left_was_down) { m_last_x = mx; m_last_y = my; } + if (left_down && !over_ui) + cam.on_mouse_move(static_cast(mx - m_last_x), static_cast(m_last_y - my)); + m_last_x = mx; m_last_y = my; m_left_was_down = left_down; + + bool over_kb = m_ui_ready && ImGui::GetIO().WantCaptureKeyboard; + if (!over_kb && dt > 0.0f) + { + float mdt = dt * (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ? 4.0f : 1.0f); + if (glfwGetKey(win, GLFW_KEY_W) == GLFW_PRESS) cam.move_forward(mdt); + if (glfwGetKey(win, GLFW_KEY_S) == GLFW_PRESS) cam.move_backward(mdt); + if (glfwGetKey(win, GLFW_KEY_A) == GLFW_PRESS) cam.move_left(mdt); + if (glfwGetKey(win, GLFW_KEY_D) == GLFW_PRESS) cam.move_right(mdt); + if (glfwGetKey(win, GLFW_KEY_E) == GLFW_PRESS) cam.move_up(mdt); + if (glfwGetKey(win, GLFW_KEY_Q) == GLFW_PRESS) cam.move_down(mdt); + } + g_scroll_accum = 0.0; + } + else + { + if (left_down && !m_left_was_down && !over_ui) + cam.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, 0); + else if (!left_down && m_left_was_down) + cam.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, 0); + m_left_was_down = left_down; + + cam.process_orbital_mouse_move(mx, my); + double scroll = g_scroll_accum; g_scroll_accum = 0.0; + if (scroll != 0.0 && !over_ui) cam.process_orbital_scroll(0.0, scroll); + } + } + + static const float SKYBOX_CUBE[] = { + -1, 1,-1, -1,-1,-1, 1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, + -1,-1, 1, -1,-1,-1, -1, 1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1, + 1,-1,-1, 1,-1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1, 1,-1,-1, + -1,-1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1, 1, -1,-1, 1, + -1, 1,-1, 1, 1,-1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1,-1, + -1,-1,-1, -1,-1, 1, 1,-1,-1, 1,-1,-1, -1,-1, 1, 1,-1, 1, + }; + + auto OpenGLRenderer::create_scene_resources() -> void + { + const float PI = 3.14159265358979f; + + m_grid_shader = Ref(Shader::create("assets/shaders/grid.glsl")); + m_sphere_shader = Ref(Shader::create("assets/shaders/sphere.glsl")); + m_skybox_shader = Ref(Shader::create("assets/shaders/skybox.glsl")); + + m_scene_camera.set_camera_mode(CameraMode::Orbital); + m_scene_camera.set_orbital_target(glm::vec3(0.0f)); + m_scene_camera.set_orbital_radius(15.0); + m_scene_camera.set_orbital_limits(2.0, 200.0); + m_scene_camera.set_orbital_speed(0.01f); + m_scene_camera.set_zoom_speed(2.0); + m_scene_camera.set_azimuth(0.0f); + m_scene_camera.set_elevation(PI / 3.0f); + + std::vector lines; + for (int i = -50; i <= 50; ++i) + { + lines.push_back({ (float)i, 0.0f, -50.0f }); lines.push_back({ (float)i, 0.0f, 50.0f }); + lines.push_back({ -50.0f, 0.0f, (float)i }); lines.push_back({ 50.0f, 0.0f, (float)i }); + } + m_grid_vertex_count = (int)lines.size(); + glGenVertexArrays(1, &m_grid_vao); glGenBuffers(1, &m_grid_vbo); + glBindVertexArray(m_grid_vao); glBindBuffer(GL_ARRAY_BUFFER, m_grid_vbo); + glBufferData(GL_ARRAY_BUFFER, lines.size() * sizeof(glm::vec3), lines.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void*)0); + + const int RINGS = 24, SECT = 48; + std::vector sv; std::vector si; + for (int r = 0; r <= RINGS; ++r) + { + float th = PI * r / RINGS; + for (int s = 0; s <= SECT; ++s) + { + float ph = 2.0f * PI * s / SECT; + glm::vec3 p(sinf(th) * cosf(ph), cosf(th), sinf(th) * sinf(ph)); + sv.insert(sv.end(), { p.x, p.y, p.z, p.x, p.y, p.z }); + } + } + for (int r = 0; r < RINGS; ++r) + for (int s = 0; s < SECT; ++s) + { + int a = r * (SECT + 1) + s, b = a + SECT + 1; + si.insert(si.end(), { (unsigned)a, (unsigned)b, (unsigned)(a + 1), + (unsigned)(a + 1), (unsigned)b, (unsigned)(b + 1) }); + } + m_sphere_index_count = (int)si.size(); + glGenVertexArrays(1, &m_sphere_vao); glGenBuffers(1, &m_sphere_vbo); glGenBuffers(1, &m_sphere_ebo); + glBindVertexArray(m_sphere_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_sphere_vbo); + glBufferData(GL_ARRAY_BUFFER, sv.size() * sizeof(float), sv.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_sphere_ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, si.size() * sizeof(unsigned), si.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); + + glGenVertexArrays(1, &m_skybox_vao); glGenBuffers(1, &m_skybox_vbo); + glBindVertexArray(m_skybox_vao); glBindBuffer(GL_ARRAY_BUFFER, m_skybox_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(SKYBOX_CUBE), SKYBOX_CUBE, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + + glBindVertexArray(0); + m_scene_ready = true; + } + + auto OpenGLRenderer::render_scene(int w, int h) -> void + { + if (!m_grid_shader || !m_sphere_shader || !m_skybox_shader) return; + glViewport(0, 0, w, h); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + float aspect = h > 0 ? (float)w / (float)h : 1.0f; + m_scene_camera.set_projection(45.0f, aspect, 0.1f, 1000.0f); + glm::mat4 proj = m_scene_camera.get_projection_matrix(); + glm::mat4 view = m_scene_camera.get_view_matrix(); + glm::mat4 vp = proj * view; + glm::vec3 cam = m_scene_camera.get_orbital_position(); + + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDepthMask(GL_FALSE); + m_skybox_shader->bind(); + m_skybox_shader->set_mat4("u_Projection", proj); + m_skybox_shader->set_mat4("u_View", glm::mat4(glm::mat3(view))); + auto hdri = HDRIManager::get().get_current_hdri(); + if (hdri) { hdri->bind(0); m_skybox_shader->set_int("u_Skybox", 0); } + glBindVertexArray(m_skybox_vao); glDrawArrays(GL_TRIANGLES, 0, 36); + + glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); + m_sphere_shader->bind(); glBindVertexArray(m_sphere_vao); + for (int i = 0; i < (int)m_scene_objects.size(); ++i) + { + const SceneObject& o = m_scene_objects[i]; + m_sphere_shader->set_mat4("u_ViewProjection", vp); + m_sphere_shader->set_mat4("u_Transform", + glm::translate(glm::mat4(1.0f), o.position) * glm::scale(glm::mat4(1.0f), glm::vec3(o.radius))); + m_sphere_shader->set_float3("u_Color", o.color); + m_sphere_shader->set_float("u_Specular", 0.6f); + m_sphere_shader->set_float("u_Emission", 0.0f); + m_sphere_shader->set_float3("u_LightPos", glm::vec3(10.0f, 20.0f, 10.0f)); + m_sphere_shader->set_float3("u_CameraPos", cam); + m_sphere_shader->set_int("u_IsSelected", i == m_selected_object ? 1 : 0); + m_sphere_shader->set_float3("u_OutlineColor", glm::vec3(1.0f, 1.0f, 0.0f)); + m_sphere_shader->set_float("u_OutlineWidth", 0.15f); + glDrawElements(GL_TRIANGLES, m_sphere_index_count, GL_UNSIGNED_INT, 0); + } + + glDepthMask(GL_FALSE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + m_grid_shader->bind(); + m_grid_shader->set_mat4("u_ViewProjection", vp); + m_grid_shader->set_mat4("u_Transform", glm::mat4(1.0f)); + m_grid_shader->set_float("u_GridSize", 50.0f); + m_grid_shader->set_float3("u_GridColor", glm::vec3(0.55f, 0.55f, 0.6f)); + m_grid_shader->set_float("u_GridAlpha", 0.75f); + m_grid_shader->set_float3("u_CameraPos", cam); + glBindVertexArray(m_grid_vao); glDrawArrays(GL_LINES, 0, m_grid_vertex_count); + glDisable(GL_BLEND); glDepthMask(GL_TRUE); glBindVertexArray(0); + } + + auto OpenGLRenderer::draw_frame(const glm::vec4& clear_color, + const std::function& build_ui) -> void + { + auto* win = static_cast(m_window); + process_input(); + + int w = 0, h = 0; glfwGetFramebufferSize(win, &w, &h); + + if (m_scene_mode) + { + render_scene(w, h); + } + else + { + m_engine->set_window_dimensions(w, h); + m_engine->dispatch_compute(m_engine->get_camera()); + RenderCommand::set_viewport(0, 0, w, h); + RenderCommand::set_clear_color(clear_color); + RenderCommand::clear(); + m_engine->draw_full_screen_quad(); + } + + if (m_ui_ready) + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + if (build_ui) build_ui(); + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + glfwSwapBuffers(win); + } + + auto OpenGLRenderer::set_scene_mode(bool enabled) -> void { m_scene_mode = enabled; } + auto OpenGLRenderer::is_scene_mode() const -> bool { return m_scene_mode; } + + auto OpenGLRenderer::set_free_fly(bool enabled) -> void + { + Camera& cam = m_engine->get_camera(); + if (enabled && cam.get_camera_mode() != CameraMode::FPS) + { + glm::vec3 pos = cam.get_orbital_position(); + glm::vec3 fwd = glm::normalize(cam.get_orbital_target() - pos); + float pitch = glm::degrees(asinf(glm::clamp(fwd.y, -1.0f, 1.0f))); + float yaw = glm::degrees(atan2f(fwd.z, fwd.x)); + cam.set_position(pos); + cam.set_rotation(glm::vec3(pitch, yaw, 0.0f)); + cam.set_movement_speed(2.0e10f); + cam.set_mouse_sensitivity(0.15f); + cam.set_camera_mode(CameraMode::FPS); + } + else if (!enabled) + { + cam.set_camera_mode(CameraMode::Orbital); + } + } + + auto OpenGLRenderer::is_free_fly() const -> bool + { + return m_engine->get_camera().get_camera_mode() == CameraMode::FPS; + } + + auto OpenGLRenderer::reset_camera() -> void + { + Camera& cam = m_engine->get_camera(); + cam.set_camera_mode(CameraMode::Orbital); + cam.set_orbital_radius(4e11); + cam.set_azimuth(0.0f); + cam.set_elevation(1.25f); + } + + auto OpenGLRenderer::set_hdri(const std::string& path) -> void + { + HDRIManager::get().set_current_hdri(path); + m_hdri_path = path; + } + + auto OpenGLRenderer::current_hdri() const -> const std::string& { return m_hdri_path; } + auto OpenGLRenderer::scene_objects() -> std::vector& { return m_scene_objects; } + auto OpenGLRenderer::set_selected_object(int index) -> void { m_selected_object = index; } + auto OpenGLRenderer::black_hole_params() -> BlackHoleParams& { return m_engine->black_hole_params(); } + auto OpenGLRenderer::device_name() const -> const std::string& { return m_device_name; } +} diff --git a/src/platform/opengl/opengl_renderer.h b/src/platform/opengl/opengl_renderer.h new file mode 100644 index 0000000..2a39f1a --- /dev/null +++ b/src/platform/opengl/opengl_renderer.h @@ -0,0 +1,65 @@ +#pragma once + +#include "rendering/renderer_backend.h" +#include "core/memory.h" +#include "core/camera.h" +#include "rendering/shader.h" + +namespace Donut +{ + class Engine; + + class OpenGLRenderer : public RendererBackend + { + public: + OpenGLRenderer(); + ~OpenGLRenderer() override; + + auto init(void* glfwWindow, int width, int height) -> bool override; + auto init_ui() -> bool override; + auto shutdown() -> void override; + auto resize(int width, int height) -> void override; + auto draw_frame(const glm::vec4& clear_color, + const std::function& build_ui) -> void override; + + auto set_scene_mode(bool enabled) -> void override; + auto is_scene_mode() const -> bool override; + auto set_free_fly(bool enabled) -> void override; + auto is_free_fly() const -> bool override; + auto reset_camera() -> void override; + + auto set_hdri(const std::string& path) -> void override; + auto current_hdri() const -> const std::string& override; + auto scene_objects() -> std::vector& override; + auto set_selected_object(int index) -> void override; + auto black_hole_params() -> BlackHoleParams& override; + + auto device_name() const -> const std::string& override; + + private: + auto process_input() -> void; + auto create_scene_resources() -> void; + auto render_scene(int width, int height) -> void; + + void* m_window = nullptr; + Scope m_engine; + + Camera m_scene_camera{ 45.0f, 16.0f / 9.0f, 0.1f, 1000.0f }; + Ref m_grid_shader, m_sphere_shader, m_skybox_shader; + unsigned int m_grid_vao = 0, m_grid_vbo = 0; + unsigned int m_sphere_vao = 0, m_sphere_vbo = 0, m_sphere_ebo = 0; + unsigned int m_skybox_vao = 0, m_skybox_vbo = 0; + int m_grid_vertex_count = 0, m_sphere_index_count = 0; + bool m_scene_ready = false; + std::vector m_scene_objects{ SceneObject{} }; + int m_selected_object = 0; + std::string m_hdri_path; + std::string m_device_name; + bool m_scene_mode = false; + bool m_ui_ready = false; + + double m_last_x = 0.0, m_last_y = 0.0; + bool m_left_was_down = false; + double m_last_frame_time = 0.0; + }; +} diff --git a/src/platform/vulkan/vulkan_renderer.cpp b/src/platform/vulkan/vulkan_renderer.cpp index 1a9f21b..882fbf3 100644 --- a/src/platform/vulkan/vulkan_renderer.cpp +++ b/src/platform/vulkan/vulkan_renderer.cpp @@ -98,6 +98,9 @@ namespace Donut VkDeviceMemory cam_mem = VK_NULL_HANDLE, disk_mem = VK_NULL_HANDLE, obj_mem = VK_NULL_HANDLE, sim_mem = VK_NULL_HANDLE; void* cam_mapped = nullptr; void* sim_mapped = nullptr; + void* disk_mapped = nullptr; + BlackHoleParams bh_params; + std::string gpu_name; Camera camera{ 60.0f, (float)GEO_HI_W / (float)GEO_HI_H, 0.1f, 100.0f }; bool left_was_down = false; double last_frame_time = 0.0; @@ -302,7 +305,8 @@ namespace Donut VkPhysicalDeviceProperties props{}; vkGetPhysicalDeviceProperties(physical, &props); vkGetPhysicalDeviceMemoryProperties(physical, &mem_props); - DONUT_INFO("Vulkan device: {}", props.deviceName); + gpu_name = props.deviceName; + DONUT_INFO("Vulkan device: {}", gpu_name); return true; } @@ -645,9 +649,7 @@ namespace Donut void* p = nullptr; vkMapMemory(device, cam_mem, 0, 128, 0, &cam_mapped); - - float disk_data[8] = { SagA_rs * 3.0f, SagA_rs * 12.0f, 0.4f, SagA_rs * 0.1f, 0.1f, 0, 0, 0 }; - vkMapMemory(device, disk_mem, 0, 32, 0, &p); memcpy(p, disk_data, sizeof(disk_data)); vkUnmapMemory(device, disk_mem); + vkMapMemory(device, disk_mem, 0, 32, 0, &disk_mapped); std::vector obj_data(800, 0); int num_objects = 1; memcpy(obj_data.data(), &num_objects, 4); @@ -1426,18 +1428,30 @@ namespace Donut } glm::vec3 right = glm::normalize(glm::cross(fwd, glm::vec3(0, 1, 0))); cam_data.pos = pos; cam_data.right = right; cam_data.up = glm::cross(right, fwd); cam_data.fwd = fwd; - cam_data.tan_half_fov = (float)tan(glm::radians(60.0f * 0.5f)); + cam_data.tan_half_fov = (float)tan(glm::radians(bh_params.fov_degrees * 0.5f)); cam_data.aspect = (float)GEO_HI_W / (float)GEO_HI_H; cam_data.moving = user_moving ? 1u : 0u; if (cam_mapped) memcpy(cam_mapped, &cam_data, sizeof(cam_data)); constexpr int kStepCeil = 15000; struct SimUBO { int steps_moving; int steps_static; float early_exit; float time; } sim; - sim.steps_static = std::clamp(SettingsManager::get_max_steps_static(), 1000, kStepCeil); + sim.steps_static = std::clamp(bh_params.quality_steps, 1000, kStepCeil); sim.steps_moving = sim.steps_static; sim.early_exit = SettingsManager::get_early_exit_distance(); sim.time = (float)(glfwGetTime() - start_time); if (sim_mapped) memcpy(sim_mapped, &sim, sizeof(sim)); + + const float SagA_rs = 1.269e10f; + float disk_data[8] = { + std::max(bh_params.disk_inner_rs, 3.0f) * SagA_rs, + std::max(bh_params.disk_outer_rs, bh_params.disk_inner_rs + 0.5f) * SagA_rs, + std::max(bh_params.turbulence, 0.0f), + SagA_rs * 0.1f, + std::max(bh_params.brightness, 0.0f), + std::max(bh_params.temperature, 1000.0f), + 0.0f, 0.0f, + }; + if (disk_mapped) memcpy(disk_mapped, disk_data, sizeof(disk_data)); } static double g_ScrollAccum = 0.0; @@ -1554,6 +1568,7 @@ namespace Donut if (cube_mem) vkFreeMemory(device, cube_mem, nullptr); if (cam_mapped) { vkUnmapMemory(device, cam_mem); cam_mapped = nullptr; } if (sim_mapped) { vkUnmapMemory(device, sim_mem); sim_mapped = nullptr; } + if (disk_mapped) { vkUnmapMemory(device, disk_mem); disk_mapped = nullptr; } VkBuffer ubos[4] = { cam_buf, disk_buf, obj_buf, sim_buf }; VkDeviceMemory umem[4] = { cam_mem, disk_mem, obj_mem, sim_mem }; for (int i = 0; i < 4; ++i) { if (ubos[i]) vkDestroyBuffer(device, ubos[i], nullptr); if (umem[i]) vkFreeMemory(device, umem[i], nullptr); } @@ -1714,7 +1729,7 @@ namespace Donut return true; } - auto VulkanRenderer::init_im_gui() -> bool + auto VulkanRenderer::init_ui() -> bool { Impl& v = *m_impl; if (v.device == VK_NULL_HANDLE) return false; @@ -1758,7 +1773,7 @@ namespace Donut return true; } - auto VulkanRenderer::on_resize(int width, int height) -> void + auto VulkanRenderer::resize(int width, int height) -> void { m_impl->framebuffer_resized = true; m_impl->width = width; m_impl->height = height; @@ -1827,6 +1842,26 @@ namespace Donut if (m_impl) m_impl->selected_object = index; } + auto VulkanRenderer::black_hole_params() -> BlackHoleParams& + { + return m_impl->bh_params; + } + + auto VulkanRenderer::reset_camera() -> void + { + if (!m_impl) return; + Camera& cam = m_impl->camera; + cam.set_camera_mode(CameraMode::Orbital); + cam.set_orbital_radius(4e11); + cam.set_azimuth(0.0f); + cam.set_elevation(1.25f); + } + + auto VulkanRenderer::device_name() const -> const std::string& + { + return m_impl->gpu_name; + } + auto VulkanRenderer::draw_frame(const glm::vec4& clear_color, const std::function& build_ui) -> void { Impl& v = *m_impl; diff --git a/src/platform/vulkan/vulkan_renderer.h b/src/platform/vulkan/vulkan_renderer.h index bc6f5b9..81e8486 100644 --- a/src/platform/vulkan/vulkan_renderer.h +++ b/src/platform/vulkan/vulkan_renderer.h @@ -1,48 +1,38 @@ #pragma once -#include -#include -#include -#include +#include "rendering/renderer_backend.h" namespace Donut { auto vulkan_prepare_glfw() -> void; - struct SceneObject - { - glm::vec3 position = { 0.0f, 2.0f, 0.0f }; - float radius = 2.0f; - glm::vec3 color = { 0.85f, 0.35f, 0.2f }; - }; - - class VulkanRenderer + class VulkanRenderer : public RendererBackend { public: VulkanRenderer(); - ~VulkanRenderer(); - - auto init(void* glfwWindow, int width, int height) -> bool; - auto shutdown() -> void; - - auto init_im_gui() -> bool; - - auto draw_frame(const glm::vec4& clearColor, const std::function& buildUI = {}) -> void; - - auto on_resize(int width, int height) -> void; + ~VulkanRenderer() override; - auto set_hdri(const std::string& path) -> void; + auto init(void* glfwWindow, int width, int height) -> bool override; + auto init_ui() -> bool override; + auto shutdown() -> void override; + auto resize(int width, int height) -> void override; - auto current_hdri() const -> const std::string&; + auto draw_frame(const glm::vec4& clear_color, + const std::function& build_ui) -> void override; - auto set_free_fly(bool enabled) -> void; - auto is_free_fly() const -> bool; + auto set_scene_mode(bool enabled) -> void override; + auto is_scene_mode() const -> bool override; + auto set_free_fly(bool enabled) -> void override; + auto is_free_fly() const -> bool override; + auto reset_camera() -> void override; - auto set_scene_mode(bool enabled) -> void; - auto is_scene_mode() const -> bool; + auto set_hdri(const std::string& path) -> void override; + auto current_hdri() const -> const std::string& override; + auto scene_objects() -> std::vector& override; + auto set_selected_object(int index) -> void override; + auto black_hole_params() -> BlackHoleParams& override; - auto scene_objects() -> std::vector&; - auto set_selected_object(int index) -> void; + auto device_name() const -> const std::string& override; private: struct Impl; diff --git a/src/rendering/renderer_backend.h b/src/rendering/renderer_backend.h new file mode 100644 index 0000000..51da214 --- /dev/null +++ b/src/rendering/renderer_backend.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +namespace Donut +{ + struct SceneObject + { + glm::vec3 position = { 0.0f, 2.0f, 0.0f }; + float radius = 2.0f; + glm::vec3 color = { 0.85f, 0.35f, 0.2f }; + }; + + struct BlackHoleParams + { + float disk_inner_rs = 3.0f; + float disk_outer_rs = 12.0f; + float temperature = 4800.0f; + float brightness = 0.9f; + float turbulence = 0.4f; + int quality_steps = 15000; + float fov_degrees = 60.0f; + }; + + class RendererBackend + { + public: + virtual ~RendererBackend() = default; + + virtual auto init(void* glfwWindow, int width, int height) -> bool = 0; + + virtual auto init_ui() -> bool = 0; + virtual auto shutdown() -> void = 0; + virtual auto resize(int width, int height) -> void = 0; + + virtual auto draw_frame(const glm::vec4& clear_color, + const std::function& build_ui) -> void = 0; + + virtual auto set_scene_mode(bool enabled) -> void = 0; + virtual auto is_scene_mode() const -> bool = 0; + virtual auto set_free_fly(bool enabled) -> void = 0; + virtual auto is_free_fly() const -> bool = 0; + virtual auto reset_camera() -> void = 0; + + virtual auto set_hdri(const std::string& path) -> void = 0; + virtual auto current_hdri() const -> const std::string& = 0; + virtual auto scene_objects() -> std::vector& = 0; + virtual auto set_selected_object(int index) -> void = 0; + virtual auto black_hole_params() -> BlackHoleParams& = 0; + + virtual auto device_name() const -> const std::string& = 0; + }; +} diff --git a/src/rendering/rhi/rhi.h b/src/rendering/rhi/rhi.h new file mode 100644 index 0000000..b32df14 --- /dev/null +++ b/src/rendering/rhi/rhi.h @@ -0,0 +1,108 @@ +#pragma once + +#include "core/memory.h" +#include +#include +#include +#include +#include + +namespace Donut::rhi +{ + enum class Format + { + RGBA8, + RGBA16F, + D32, + }; + + enum class BufferType { Vertex, Index, Uniform }; + enum class Topology { Triangles, Lines }; + enum class CullMode { None, Back, Front }; + enum class BlendMode { Opaque, AlphaBlend }; + enum class CompareOp { Always, Less, LessEqual }; + enum class Filter { Nearest, Linear }; + + struct VertexAttribute { uint32_t location; uint32_t components; uint32_t offset; }; + struct VertexLayout { uint32_t stride = 0; std::vector attributes; }; + + enum class ResourceKind { UniformBuffer, Texture }; + struct ResourceSlot { ResourceKind kind; uint32_t binding; }; + + class Buffer { public: virtual ~Buffer() = default; virtual auto update(const void* data, size_t size) -> void = 0; }; + class Texture { public: virtual ~Texture() = default; }; + class Pipeline{ public: virtual ~Pipeline() = default; }; + + class RenderTarget + { + public: + virtual ~RenderTarget() = default; + virtual auto width() const -> int = 0; + virtual auto height() const -> int = 0; + virtual auto color_texture() -> Texture* = 0; + }; + + struct PipelineDesc + { + std::string shader; + VertexLayout vertex_layout; + std::vector resources; + Topology topology = Topology::Triangles; + CullMode cull = CullMode::None; + BlendMode blend = BlendMode::Opaque; + bool depth_test = false; + bool depth_write = false; + CompareOp depth_op = CompareOp::Less; + Format color_format = Format::RGBA8; + bool has_depth = false; + }; + + class CommandList + { + public: + virtual ~CommandList() = default; + + virtual auto begin_render_pass(RenderTarget* target, const glm::vec4& clear) -> void = 0; + virtual auto end_render_pass() -> void = 0; + + virtual auto bind_pipeline(Pipeline* pipeline) -> void = 0; + virtual auto set_viewport(int x, int y, int w, int h, bool flip_y = false) -> void = 0; + + virtual auto bind_uniform(uint32_t binding, Buffer* ubo) -> void = 0; + virtual auto bind_texture(uint32_t binding, Texture* texture) -> void = 0; + + virtual auto bind_vertex_buffer(Buffer* vb) -> void = 0; + virtual auto bind_index_buffer(Buffer* ib) -> void = 0; + + virtual auto draw(uint32_t vertex_count) -> void = 0; + virtual auto draw_indexed(uint32_t index_count) -> void = 0; + }; + + class Device + { + public: + virtual ~Device() = default; + + virtual auto init(void* glfwWindow, int width, int height) -> bool = 0; + virtual auto shutdown() -> void = 0; + virtual auto resize(int width, int height) -> void = 0; + virtual auto wait_idle() -> void = 0; + + virtual auto create_buffer(BufferType type, size_t size, const void* data = nullptr) -> Ref = 0; + virtual auto create_texture(int width, int height, Format format, Filter filter, const void* data = nullptr) -> Ref = 0; + virtual auto create_cubemap_from_hdri(const std::string& equirect_path) -> Ref = 0; + virtual auto create_render_target(int width, int height, Format color, bool with_depth, + Filter sample_filter = Filter::Linear, int mip_levels = 1) -> Ref = 0; + virtual auto create_pipeline(const PipelineDesc& desc) -> Ref = 0; + + virtual auto begin_frame(const glm::vec4& clear) -> CommandList* = 0; + virtual auto end_frame() -> void = 0; + virtual auto record_offscreen(const std::function& record) -> void = 0; + + virtual auto init_imgui() -> void = 0; + virtual auto imgui_new_frame() -> void = 0; + virtual auto imgui_render(CommandList& swapchain_cmds) -> void = 0; + + virtual auto device_name() const -> const std::string& = 0; + }; +} -- cgit v1.3