hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/shaders/geodesic.slang9
-rw-r--r--src/core/application.cpp267
-rw-r--r--src/core/application.h8
-rw-r--r--src/engine/engine.cpp44
-rw-r--r--src/engine/engine.h5
-rw-r--r--src/platform/opengl/opengl_renderer.cpp365
-rw-r--r--src/platform/opengl/opengl_renderer.h65
-rw-r--r--src/platform/vulkan/vulkan_renderer.cpp51
-rw-r--r--src/platform/vulkan/vulkan_renderer.h50
-rw-r--r--src/rendering/renderer_backend.h56
-rw-r--r--src/rendering/rhi/rhi.h108
11 files changed, 871 insertions, 157 deletions
diff --git a/assets/shaders/geodesic.slang b/assets/shaders/geodesic.slang
index a1b0f46..111c9b8 100644
--- a/assets/shaders/geodesic.slang
+++ b/assets/shaders/geodesic.slang
@@ -30,6 +30,7 @@ struct Disk
float disk_num;
float thickness;
float disk_density;
+ float temperature;
};
ConstantBuffer<Disk> disk;
@@ -68,9 +69,6 @@ static const float DEFAULT_EARLY_EXIT_DISTANCE = 2e12;
static const float MIN_STEP_SIZE = 1e6;
static const float MAX_STEP_SIZE = 2e10;
-static const float T_PEAK = 4800.0;
-static const float DISK_EXPOSURE = 0.9;
-
struct Hit
{
float4 objectColor;
@@ -147,7 +145,7 @@ float3 DiskEmission(float3 P, float3 rayDir)
float xr = rc / rin;
float flux = max((1.0 - sqrt(1.0 / xr)) / (xr * xr * xr), 0.0);
float Tn = pow(flux / FLUX_PEAK, 0.25);
- float Temit = T_PEAK * Tn;
+ float Temit = disk.temperature * Tn;
float3 rhat = normalize(float3(P.x, 0.0, P.z));
float3 phiHat = normalize(cross(float3(0.0, 1.0, 0.0), rhat));
@@ -174,8 +172,7 @@ float3 DiskEmission(float3 P, float3 rayDir)
bright *= max(turb, 0.0);
}
- float exposure = DISK_EXPOSURE * max(disk.disk_density, 0.0) * 10.0;
- return colour * bright * exposure;
+ return colour * bright * max(disk.disk_density, 0.0);
}
struct Ray
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 <ImGuizmo.h>
#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);
- 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;
+ 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<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();
- 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<Engine>();
- m_engine->set_window_dimensions(1280, 720);
+ if (RendererAPI::get_api() == RendererAPI::API::Vulkan)
+ 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
{
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<StateManager> m_state_manager;
Scope<Window> m_window;
Scope<Engine> m_engine;
- Scope<VulkanRenderer> m_vulkan_renderer;
+ Scope<RendererBackend> 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<float>(tan(glm::radians(60.0f * 0.5f)));
+ data.tan_half_fov = static_cast<float>(tan(glm::radians(m_bh.fov_degrees * 0.5f)));
data.aspect = static_cast<float>(get_compute_width()) / static_cast<float>(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<float>(m_sag_a.m_rs * 2.2);
- float r2 = static_cast<float>(m_sag_a.m_rs * 5.2);
- float num = 2.0f;
- float thickness = static_cast<float>(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<float>(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<CubemapTexture> hdri) -> void { m_hdri_environment = hdri; }
auto get_hdri_environment() const -> Ref<CubemapTexture> { return m_hdri_environment; }
+
+ auto black_hole_params() -> BlackHoleParams& { return m_bh; }
private:
auto create_compute_program(const char* path) -> Ref<Shader>;
std::pair<Ref<VertexArray>, Ref<Texture2D>> 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 <glad/glad.h>
+#include <GLFW/glfw3.h>
+#include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <cmath>
+#include <vector>
+
+#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*>(glfwWindow);
+ glfwMakeContextCurrent(win);
+
+ Renderer::init();
+ RenderCommand::set_face_culling(false);
+
+ const GLubyte* name = glGetString(GL_RENDERER);
+ m_device_name = name ? reinterpret_cast<const char*>(name) : "OpenGL";
+
+ m_engine = create_scope<Engine>();
+ 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<GLFWwindow*>(m_window), true);
+ ImGui_ImplOpenGL3_Init("#version 410");
+ g_prev_scroll = glfwSetScrollCallback(static_cast<GLFWwindow*>(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<GLFWwindow*>(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<float>(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<float>(mx - m_last_x), static_cast<float>(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>(Shader::create("assets/shaders/grid.glsl"));
+ m_sphere_shader = Ref<Shader>(Shader::create("assets/shaders/sphere.glsl"));
+ m_skybox_shader = Ref<Shader>(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<glm::vec3> 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<float> sv; std::vector<unsigned int> 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<void()>& build_ui) -> void
+ {
+ auto* win = static_cast<GLFWwindow*>(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<SceneObject>& { 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<void()>& 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<SceneObject>& 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<Engine> m_engine;
+
+ Camera m_scene_camera{ 45.0f, 16.0f / 9.0f, 0.1f, 1000.0f };
+ Ref<Shader> 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<SceneObject> 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<uint8_t> 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<void()>& 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 <glm/glm.hpp>
-#include <functional>
-#include <string>
-#include <vector>
+#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<void()>& 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<void()>& 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<SceneObject>& override;
+ auto set_selected_object(int index) -> void override;
+ auto black_hole_params() -> BlackHoleParams& override;
- auto scene_objects() -> std::vector<SceneObject>&;
- 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 <glm/glm.hpp>
+#include <functional>
+#include <string>
+#include <vector>
+
+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<void()>& 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<SceneObject>& = 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 <glm/glm.hpp>
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <functional>
+
+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<VertexAttribute> 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<ResourceSlot> 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<Buffer> = 0;
+ virtual auto create_texture(int width, int height, Format format, Filter filter, const void* data = nullptr) -> Ref<Texture> = 0;
+ virtual auto create_cubemap_from_hdri(const std::string& equirect_path) -> Ref<Texture> = 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<RenderTarget> = 0;
+ virtual auto create_pipeline(const PipelineDesc& desc) -> Ref<Pipeline> = 0;
+
+ virtual auto begin_frame(const glm::vec4& clear) -> CommandList* = 0;
+ virtual auto end_frame() -> void = 0;
+ virtual auto record_offscreen(const std::function<void(CommandList&)>& 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;
+ };
+}