aboutsummaryrefslogtreecommitdiff
path: root/src/engine/engine.cpp
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-23 17:21:37 +0200
committerhachem <im@hachem.wtf>2026-08-23 17:21:37 +0200
commit3fd33ecff7472e4d6fc9e6b3905f56982e497ef2 (patch)
tree66e5812f1a18b926e88a0dd549fe14e7c7327165 /src/engine/engine.cpp
parente3aa33fc9a99d8b817bda42a567a4c347e18ab32 (diff)
[feat]: complete RHI api
Diffstat (limited to 'src/engine/engine.cpp')
-rw-r--r--src/engine/engine.cpp625
1 files changed, 0 insertions, 625 deletions
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
deleted file mode 100644
index bf5a2b6..0000000
--- a/src/engine/engine.cpp
+++ /dev/null
@@ -1,625 +0,0 @@
-#include <iostream>
-#include <fstream>
-#include <sstream>
-
-#include <GLFW/glfw3.h>
-#include <glad/glad.h>
-
-#define STB_IMAGE_WRITE_IMPLEMENTATION
-#include "stb_image_write.h"
-
-#include "engine.h"
-#include "core/log.h"
-#include "core/hdri_manager.h"
-#include "rendering/vertex_buffer.h"
-#include "rendering/index_buffer.h"
-
-namespace Donut
-{
- Engine::Engine()
- : m_sag_a(glm::vec3(0.0f, 0.0f, 0.0f), 8.54e36f)
- {
- m_width = 1280;
- m_height = 720;
-
- m_camera.set_camera_mode(CameraMode::Orbital);
- m_camera.set_orbital_radius(1e11);
- m_camera.set_orbital_limits(1e9, 1e13);
- m_camera.set_orbital_speed(0.01f);
- m_camera.set_zoom_speed(1e10f);
-
- m_objects =
- {
- { glm::vec4(0.00f, 0.00f, 0.00f, m_sag_a.m_rs), glm::vec4(0, 0, 0, 1), static_cast<float>(m_sag_a.m_mass) }
- };
-
- // The geodesic ray tracer used to be a compute shader; it is now a
- // fullscreen vertex+fragment pass (see dispatch_compute) so it runs on
- // macOS OpenGL 4.1, which has no compute shaders.
- m_compute_program = Ref<Shader>(Shader::create("assets/shaders/Geodesic.glsl"));
- m_shader_program = Ref<Shader>(Shader::create("assets/shaders/TexturedQuad.glsl"));
- m_blur_shader = Ref<Shader>(Shader::create("assets/shaders/Blur.glsl"));
-
- auto& hdri_manager = HDRIManager::get();
- m_hdri_environment = hdri_manager.get_current_hdri();
- if (!m_hdri_environment)
- {
- hdri_manager.set_current_hdri("assets/hdri/HDR_blue_nebulae-1.hdr");
- m_hdri_environment = hdri_manager.get_current_hdri();
- if (!m_hdri_environment)
- DONUT_WARN("Failed to load default HDRI, using fallback");
- }
-
- m_camera_ubo = UniformBuffer::create(128, 1);
- m_disk_ubo = UniformBuffer::create(sizeof(float) * 6, 2); // r1,r2,turbulence,thickness,brightness,temperature
-
- uint32_t obj_ubo_size = sizeof(int) + 3 * sizeof(float)
- + 16 * (sizeof(glm::vec4) + sizeof(glm::vec4))
- + 16 * sizeof(float);
- m_objects_ubo = UniformBuffer::create(obj_ubo_size, 3);
-
- m_simulation_ubo = UniformBuffer::create(sizeof(int) * 2 + sizeof(float) * 2, 4);
-
- auto result = QuadVAO();
- m_quad_vao = result.first;
- m_texture = result.second;
-
- // GLSL 4.10 forbids explicit binding qualifiers on uniform blocks, so
- // associate the geodesic shader's blocks with their UBO binding points
- // from the host side instead.
- if (m_compute_program)
- {
- uint32_t prog = m_compute_program->get_renderer_id();
- struct { const char* name; uint32_t point; } blocks[] =
- {
- { "Camera", 1 }, { "Disk", 2 }, { "Objects", 3 }, { "Simulation", 4 }
- };
- for (const auto& b : blocks)
- {
- uint32_t idx = glGetUniformBlockIndex(prog, b.name);
- if (idx != GL_INVALID_INDEX)
- glUniformBlockBinding(prog, idx, b.point);
- }
- }
-
- glGenFramebuffers(1, &m_geodesic_fbo);
- }
-
- auto Engine::update_window_dimensions() -> void
- {
- update_compute_dimensions();
- }
-
- auto Engine::set_window_dimensions(int width, int height) -> void
- {
- int old_width = m_width;
- int old_height = m_height;
- int old_compute_height = m_compute_height;
-
- m_width = width;
- m_height = height;
-
- if (old_width != m_width ||
- old_height != m_height ||
- old_compute_height != m_compute_height)
- update_compute_dimensions();
- }
-
- auto Engine::update_performance(float delta_time) -> void
- {
- if (delta_time > 0.0f)
- m_current_fps = 1.0f / delta_time;
- }
-
- auto Engine::update_compute_dimensions() -> void
- {
- m_texture = Texture2D::create(get_compute_width(), m_compute_height);
- }
-
- auto Engine::draw_full_screen_quad() -> void
- {
- RenderCommand::set_viewport(0, 0, m_width, m_height);
-
- m_shader_program->bind();
- m_quad_vao->bind();
-
- m_texture->bind(0);
- m_shader_program->set_int("u_ScreenTexture", 0);
-
- RenderCommand::disable_depth_test();
- RenderCommand::draw_arrays(6);
- RenderCommand::enable_depth_test();
- }
-
- auto Engine::draw_blur_pass() -> void
- {
- RenderCommand::set_viewport(0, 0, m_width, m_height);
-
- m_blur_shader->bind();
- m_quad_vao->bind();
-
- m_texture->bind(0);
- m_blur_shader->set_int("u_ScreenTexture", 0);
- m_blur_shader->set_float2("u_Resolution", glm::vec2(m_width, m_height));
- m_blur_shader->set_float("u_BlurStrength", m_blur_strength);
- m_blur_shader->set_float("u_GlowIntensity", m_glow_intensity);
-
- RenderCommand::disable_depth_test();
- RenderCommand::draw_arrays(6);
- RenderCommand::enable_depth_test();
- }
-
- auto Engine::draw_geodesic_pass(int cw, int ch) -> void
- {
- m_quad_vao->bind();
- RenderCommand::disable_depth_test();
-
-#ifdef __APPLE__
- // macOS aborts any GPU submission that runs longer than a couple of
- // seconds ("GPU Hang"). The geodesic ray-marcher can far exceed that in
- // a single fullscreen draw, so render it in scissored tiles and flush
- // after each, keeping every submission short enough to survive the
- // watchdog. Compute-capable platforms draw it in one pass.
- // Largest tile that keeps a tile's worst-case work (tile^2 * step_cap)
- // inside the safe watchdog zone measured on this GPU (~25M pixel-steps
- // per submission); bigger tiles mean fewer glFinish stalls.
- const int tile = 64;
- glEnable(GL_SCISSOR_TEST);
- for (int y = 0; y < ch; y += tile)
- {
- int th = std::min(tile, ch - y);
- for (int x = 0; x < cw; x += tile)
- {
- int tw = std::min(tile, cw - x);
- glScissor(x, y, tw, th);
- RenderCommand::draw_arrays(6);
- glFinish();
- }
- }
- glDisable(GL_SCISSOR_TEST);
-#else
- RenderCommand::draw_arrays(6);
-#endif
-
- RenderCommand::enable_depth_test();
- }
-
- auto Engine::dispatch_compute(const Camera& cam) -> void
- {
- auto& hdri_manager = HDRIManager::get();
- m_hdri_environment = hdri_manager.get_current_hdri();
-
- int cw = get_compute_width();
- int ch = m_compute_height;
-
- // Render the geodesic pass into m_texture through an FBO. This replaces
- // the old compute dispatch + image_store path, which relied on OpenGL
- // 4.3 compute and 4.2 image load/store that macOS does not provide.
- glBindFramebuffer(GL_FRAMEBUFFER, m_geodesic_fbo);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture->get_renderer_id(), 0);
- glViewport(0, 0, cw, ch);
-
- m_compute_program->bind();
- upload_camera_ubo(cam);
- upload_disk_ubo();
- upload_objects_ubo(m_objects);
- upload_simulation_ubo();
- m_compute_program->set_float2("u_Resolution", glm::vec2(static_cast<float>(cw), static_cast<float>(ch)));
-
- if (m_hdri_environment)
- {
- m_hdri_environment->bind(5);
- m_compute_program->set_int("u_HDRIEnvironment", 5);
- }
-
- draw_geodesic_pass(cw, ch);
-
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- }
-
- auto Engine::upload_camera_ubo(const Camera& cam) -> void
- {
- struct UBOData
- {
- glm::vec3 pos; float _pad0;
- glm::vec3 right; float _pad1;
- glm::vec3 up; float _pad2;
- glm::vec3 forward; float _pad3;
- float tan_half_fov;
- float aspect;
- bool moving;
- int _pad4;
- } data;
-
- 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 = pos;
- data.right = right;
- data.up = up;
- data.forward = fwd;
- 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();
-
- m_camera_ubo->set_data(&data, sizeof(UBOData));
- m_camera_ubo->bind(1);
- }
-
- auto Engine::upload_objects_ubo(const std::vector<ObjectData>& objs) -> void
- {
- struct UBOData
- {
- int num_objects;
- float _pad0, _pad1, _pad2;
- glm::vec4 pos_radius[16];
- glm::vec4 color[16];
- float mass[16];
- } data;
-
- size_t count = std::min(objs.size(), size_t(16));
- data.num_objects = static_cast<int>(count);
-
- for (size_t i = 0; i < count; ++i)
- {
- data.pos_radius[i] = objs[i].m_pos_radius;
- data.color[i] = objs[i].m_color;
- data.mass[i] = objs[i].m_mass;
- }
-
- m_objects_ubo->set_data(&data, sizeof(data));
- m_objects_ubo->bind(3);
- }
-
- auto Engine::upload_disk_ubo() -> void
- {
- // Layout matches the shared Geodesic Disk struct (same as Vulkan). Radii
- // are in Schwarzschild radii x the shader's SagA_rs constant (1.269e10).
- 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, // slab half-thickness (fixed)
- 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);
- }
-
- auto Engine::upload_simulation_ubo() -> void
- {
- struct UBOData
- {
- int max_steps_moving;
- int max_steps_static;
- float early_exit_distance;
- float time;
- } data;
-
- data.max_steps_moving = m_bh.quality_steps;
- data.max_steps_static = m_bh.quality_steps; // resolution/tiling is the lever, not step count
- data.early_exit_distance = m_early_exit_distance;
- data.time = static_cast<float>(glfwGetTime()) * m_rotation_speed;
-
-#ifdef __APPLE__
- // macOS has no compute shaders, so the geodesic pass runs as a tiled
- // fragment shader under the OS GPU watchdog. Cap the step count so a
- // single 64px tile stays under the safe per-submission budget (~25M
- // pixel-steps); Windows/Linux keep the full count.
- data.max_steps_moving = std::min(data.max_steps_moving, 6000);
- data.max_steps_static = std::min(data.max_steps_static, 6000);
-#endif
-
- m_simulation_ubo->set_data(&data, sizeof(data));
- m_simulation_ubo->bind(4);
- }
-
- auto Engine::update_physics(float delta_time) -> void
- {
- for (auto& obj : m_objects)
- {
- for (auto& obj2 : m_objects)
- {
- if (&obj == &obj2) continue;
- float dx = obj2.m_pos_radius.x - obj.m_pos_radius.x;
- float dy = obj2.m_pos_radius.y - obj.m_pos_radius.y;
- float dz = obj2.m_pos_radius.z - obj.m_pos_radius.z;
- float distance = sqrt(dx * dx + dy * dy + dz * dz);
- if (distance > 0)
- {
- std::vector<double> direction = {dx / distance, dy / distance, dz / distance};
- double Gforce = (G * obj.m_mass * obj2.m_mass) / (distance * distance);
- double acc1 = Gforce / obj.m_mass;
- std::vector<double> acc = {direction[0] * acc1, direction[1] * acc1, direction[2] * acc1};
-
- if (m_gravity)
- {
- obj.m_velocity.x += static_cast<float>(acc[0]);
- obj.m_velocity.y += static_cast<float>(acc[1]);
- obj.m_velocity.z += static_cast<float>(acc[2]);
-
- obj.m_pos_radius.x += static_cast<float>(obj.m_velocity.x);
- obj.m_pos_radius.y += static_cast<float>(obj.m_velocity.y);
- obj.m_pos_radius.z += static_cast<float>(obj.m_velocity.z);
- }
- }
- }
- }
- }
-
- auto Engine::render_scene() -> void
- {
- RenderCommand::clear();
- m_shader_program->bind();
- m_quad_vao->bind();
- m_texture->bind(0);
- RenderCommand::draw_arrays(6);
- }
-
- auto Engine::create_compute_program(const char* path) -> Ref<Shader>
- {
- std::ifstream in(path);
- if(!in.is_open())
- {
- std::cerr << "Failed to open compute shader: " << path << "\n";
- return nullptr;
- }
- std::stringstream ss;
- ss << in.rdbuf();
- std::string src_str = ss.str();
- return Ref<Shader>(Shader::create_compute("ComputeShader", src_str));
- }
-
- auto Engine::QuadVAO() -> std::pair<Ref<VertexArray>, Ref<Texture2D>>
- {
- float quad_vertices[] =
- {
- // Positions // TexCoords
- -1.0f, 1.0f, 0.0f, 1.0f,
- -1.0f, -1.0f, 0.0f, 0.0f,
- 1.0f, -1.0f, 1.0f, 0.0f,
- -1.0f, 1.0f, 0.0f, 1.0f,
- 1.0f, -1.0f, 1.0f, 0.0f,
- 1.0f, 1.0f, 1.0f, 1.0f
- };
-
- auto vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(quad_vertices, static_cast<uint32_t>(sizeof(quad_vertices))));
- VertexBufferLayout layout;
- layout.push<float>(2); // Position (x, y)
- layout.push<float>(2); // TexCoord (u, v)
- vertex_buffer->set_layout(layout);
-
- auto vertex_array = Ref<VertexArray>(VertexArray::create());
- vertex_array->add_vertex_buffer(vertex_buffer);
- auto texture = Texture2D::create(get_compute_width(), m_compute_height);
-
- return { vertex_array, texture };
- }
-
- auto Engine::load_objects_from_scene(const std::vector<Donut::Object>& objects) -> void
- {
- m_objects.clear();
- m_objects.push_back(
- {
- glm::vec4(0.00f, 0.00f, 0.00f, m_sag_a.m_rs),
- glm::vec4(0, 0, 0, 1),
- static_cast<float>(m_sag_a.m_mass)
- });
-
- for (const auto& obj : objects)
- {
- ObjectData engine_obj;
-
- float scale_factor = 1e10f;
- engine_obj.m_pos_radius = glm::vec4
- (
- obj.m_centre.x * scale_factor,
- obj.m_centre.y * scale_factor,
- obj.m_centre.z * scale_factor,
- obj.m_radius * scale_factor
- );
-
- engine_obj.m_color = glm::vec4(obj.m_material.m_color, 1.0f);
-
- float volume = (4.0f / 3.0f) * 3.14159f * engine_obj.m_pos_radius.w * engine_obj.m_pos_radius.w * engine_obj.m_pos_radius.w;
- float density = 1e12f;
- engine_obj.m_mass = volume * density;
- engine_obj.m_velocity = glm::vec3(0.0f, 0.0f, 0.0f);
-
- m_objects.push_back(engine_obj);
- }
-
- DONUT_INFO("Loaded {} objects from WorldBuilder scene (scaled up by {})", objects.size(), 1e10f);
- print_object_info();
- }
-
- auto Engine::print_object_info() const -> void
- {
- DONUT_INFO("=== Object Information ===");
- DONUT_INFO("Total objects: {}", m_objects.size());
-
- for (size_t i = 0; i < m_objects.size(); ++i)
- {
- const auto& obj = m_objects[i];
- DONUT_INFO("Object {}: Pos=({}, {}, {}), Radius={}, Mass={}, Color=({}, {}, {})",
- i,
- obj.m_pos_radius.x, obj.m_pos_radius.y, obj.m_pos_radius.z,
- obj.m_pos_radius.w,
- obj.m_mass,
- obj.m_color.x, obj.m_color.y, obj.m_color.z
- );
- }
- DONUT_INFO("Camera position: ({}, {}, {})",
- m_camera.get_orbital_position().x,
- m_camera.get_orbital_position().y,
- m_camera.get_orbital_position().z
- );
- DONUT_INFO("Camera radius: {}", m_camera.get_orbital_radius());
- DONUT_INFO("========================");
- }
-
- auto Engine::export_high_res_frame(const std::string& filename, int width, int height) -> void
- {
- DONUT_INFO("Exporting high-resolution frame: {}x{} to {}", width, height, filename);
-
- if (width <= 0 || height <= 0)
- {
- DONUT_ERROR("Invalid dimensions for export: {}x{}", width, height);
- return;
- }
-
- if (filename.empty())
- {
- DONUT_ERROR("Invalid filename for export");
- return;
- }
-
- int original_width = m_width;
- int original_height = m_height;
- int original_compute_height = m_compute_height;
-
- m_width = width;
- m_height = height;
- m_compute_height = height;
-
- int compute_height = height;
- int compute_width = (width * compute_height) / height;
-
- if (compute_width <= 0 || compute_height <= 0)
- {
- DONUT_ERROR("Invalid compute dimensions: {}x{}", compute_width, compute_height);
- return;
- }
-
- FramebufferSpecification fb_spec;
- fb_spec.Width = width;
- fb_spec.Height = height;
- fb_spec.attachments = { FramebufferTextureFormat::RGBA8 };
-
- auto high_res_framebuffer = Framebuffer::create(fb_spec);
- if (!high_res_framebuffer)
- {
- DONUT_ERROR("Failed to create high-resolution framebuffer");
- return;
- }
-
- auto high_res_texture = Texture2D::create(compute_width, compute_height);
- if (!high_res_texture)
- {
- DONUT_ERROR("Failed to create high-resolution texture");
- return;
- }
-
- // Render the geodesic pass into high_res_texture through the geodesic FBO.
- glBindFramebuffer(GL_FRAMEBUFFER, m_geodesic_fbo);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, high_res_texture->get_renderer_id(), 0);
- glViewport(0, 0, compute_width, compute_height);
-
- m_compute_program->bind();
-
- struct UBOData
- {
- glm::vec3 pos; float _pad0;
- glm::vec3 right; float _pad1;
- glm::vec3 up; float _pad2;
- glm::vec3 forward; float _pad3;
- float tan_half_fov;
- float aspect;
- bool moving;
- int _pad4;
- } data;
-
- glm::vec3 fwd = glm::normalize(m_camera.get_orbital_target() - m_camera.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);
-
- data.pos = m_camera.get_orbital_position();
- data.right = right;
- data.up = up;
- data.forward = fwd;
- data.tan_half_fov = static_cast<float>(tan(glm::radians(60.0f * 0.5f)));
- data.aspect = static_cast<float>(compute_width) / static_cast<float>(compute_height);
- data.moving = m_camera.is_dragging() || m_camera.is_panning();
-
- m_camera_ubo->set_data(&data, sizeof(UBOData));
- m_camera_ubo->bind(1);
-
- upload_disk_ubo();
- upload_objects_ubo(m_objects);
- upload_simulation_ubo();
- m_compute_program->set_float2("u_Resolution", glm::vec2(static_cast<float>(compute_width), static_cast<float>(compute_height)));
-
- if (m_hdri_environment)
- {
- m_hdri_environment->bind(5);
- m_compute_program->set_int("u_HDRIEnvironment", 5);
- }
-
- draw_geodesic_pass(compute_width, compute_height);
-
- // Display the rendered frame into the high-res framebuffer for read-back.
- high_res_framebuffer->bind();
- RenderCommand::set_viewport(0, 0, width, height);
- RenderCommand::clear();
-
- m_shader_program->bind();
- m_quad_vao->bind();
-
- high_res_texture->bind(0);
- m_shader_program->set_int("u_ScreenTexture", 0);
-
- RenderCommand::disable_depth_test();
- RenderCommand::draw_arrays(6);
- RenderCommand::enable_depth_test();
-
- std::vector<unsigned char> pixels(width * height * 4);
- DONUT_INFO("Reading {} pixels from framebuffer...", width * height);
- RenderCommand::read_pixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
-
- DONUT_INFO("Flipping image vertically...");
- std::vector<unsigned char> flipped_pixels(width * height * 4);
- for (int y = 0; y < height; ++y)
- {
- for (int x = 0; x < width; ++x)
- {
- int src_index = (y * width + x) * 4;
- int dst_index = ((height - 1 - y) * width + x) * 4;
- flipped_pixels[dst_index + 0] = pixels[src_index + 0]; // R
- flipped_pixels[dst_index + 1] = pixels[src_index + 1]; // G
- flipped_pixels[dst_index + 2] = pixels[src_index + 2]; // B
- flipped_pixels[dst_index + 3] = pixels[src_index + 3]; // A
- }
- }
-
- DONUT_INFO("Saving PNG file: {}...", filename);
- int result = stbi_write_png(filename.c_str(), width, height, 4, flipped_pixels.data(), width * 4);
-
- if (result)
- DONUT_INFO("Successfully exported high-resolution frame to: {}", filename);
- else
- DONUT_ERROR("Failed to export high-resolution frame to: {}", filename);
-
- high_res_framebuffer->unbind();
-
- m_width = original_width;
- m_height = original_height;
- m_compute_height = original_compute_height;
-
- RenderCommand::set_viewport(0, 0, original_width, original_height);
- }
-
-}