aboutsummaryrefslogtreecommitdiff
path: root/src/platform/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/opengl')
-rw-r--r--src/platform/opengl/opengl_device.cpp279
-rw-r--r--src/platform/opengl/opengl_device.h11
-rw-r--r--src/platform/opengl/opengl_renderer.cpp377
-rw-r--r--src/platform/opengl/opengl_renderer.h72
4 files changed, 290 insertions, 449 deletions
diff --git a/src/platform/opengl/opengl_device.cpp b/src/platform/opengl/opengl_device.cpp
new file mode 100644
index 0000000..3d45d24
--- /dev/null
+++ b/src/platform/opengl/opengl_device.cpp
@@ -0,0 +1,279 @@
+#include "opengl_device.h"
+
+#include "core/log.h"
+#include "core/hdri_manager.h"
+#include "rendering/shader.h"
+#include "rendering/texture.h"
+
+#include <glad/glad.h>
+#include <GLFW/glfw3.h>
+#include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+
+namespace Donut::RHI
+{
+ namespace
+ {
+ auto gl_topology(Topology t) -> GLenum { return t == Topology::Lines ? GL_LINES : GL_TRIANGLES; }
+ auto gl_compare(CompareOp o) -> GLenum { return o == CompareOp::Always ? GL_ALWAYS : o == CompareOp::LessEqual ? GL_LEQUAL : GL_LESS; }
+ auto gl_filter(Filter f) -> GLint { return f == Filter::Nearest ? GL_NEAREST : GL_LINEAR; }
+ auto gl_internal(Format f) -> GLint { return f == Format::RGBA16F ? GL_RGBA16F : f == Format::D32 ? GL_DEPTH_COMPONENT32F : GL_RGBA8; }
+
+ // ---- Buffer -------------------------------------------------------
+ class GLBuffer : public Buffer
+ {
+ public:
+ GLBuffer(BufferType type, size_t size, const void* data)
+ {
+ m_target = type == BufferType::Index ? GL_ELEMENT_ARRAY_BUFFER
+ : type == BufferType::Uniform ? GL_UNIFORM_BUFFER : GL_ARRAY_BUFFER;
+ glGenBuffers(1, &m_id);
+ glBindBuffer(m_target, m_id);
+ glBufferData(m_target, (GLsizeiptr)size, data, GL_DYNAMIC_DRAW);
+ }
+ ~GLBuffer() override { if (m_id) glDeleteBuffers(1, &m_id); }
+ auto update(const void* data, size_t size) -> void override
+ {
+ glBindBuffer(m_target, m_id);
+ glBufferSubData(m_target, 0, (GLsizeiptr)size, data);
+ }
+ GLuint m_id = 0; GLenum m_target = GL_ARRAY_BUFFER;
+ };
+
+ // ---- Texture ------------------------------------------------------
+ class GLTexture : public Texture
+ {
+ public:
+ GLTexture(int w, int h, Format fmt, Filter filter, const void* data)
+ {
+ glGenTextures(1, &m_id); m_own = true; m_target = GL_TEXTURE_2D;
+ glBindTexture(GL_TEXTURE_2D, m_id);
+ GLenum ext = fmt == Format::D32 ? GL_DEPTH_COMPONENT : GL_RGBA;
+ GLenum type = fmt == Format::RGBA8 ? GL_UNSIGNED_BYTE : GL_FLOAT;
+ glTexImage2D(GL_TEXTURE_2D, 0, gl_internal(fmt), w, h, 0, ext, type, data);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter(filter));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter(filter));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ explicit GLTexture(Ref<CubemapTexture> cube)
+ : m_cube(std::move(cube))
+ {
+ m_target = GL_TEXTURE_CUBE_MAP;
+ m_id = m_cube ? m_cube->get_renderer_id() : 0;
+ }
+ ~GLTexture() override { if (m_own && m_id) glDeleteTextures(1, &m_id); }
+ GLuint m_id = 0; GLenum m_target = GL_TEXTURE_2D; bool m_own = false;
+ Ref<CubemapTexture> m_cube; // keepalive for cubemaps
+ };
+
+ // ---- RenderTarget -------------------------------------------------
+ class GLRenderTarget : public RenderTarget
+ {
+ public:
+ GLRenderTarget(int w, int h, Format color, bool depth, Filter filter)
+ : m_w(w), m_h(h)
+ {
+ m_color = create_scope<GLTexture>(w, h, color, filter, nullptr);
+ glGenFramebuffers(1, &m_fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_color->m_id, 0);
+ if (depth)
+ {
+ glGenRenderbuffers(1, &m_depth);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_depth);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, w, h);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depth);
+ }
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ DONUT_ERROR("GL RHI: render target incomplete");
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+ ~GLRenderTarget() override
+ {
+ if (m_depth) glDeleteRenderbuffers(1, &m_depth);
+ if (m_fbo) glDeleteFramebuffers(1, &m_fbo);
+ }
+ auto width() const -> int override { return m_w; }
+ auto height() const -> int override { return m_h; }
+ auto color_texture() -> Texture* override { return m_color.get(); }
+ GLuint m_fbo = 0, m_depth = 0; int m_w, m_h; Scope<GLTexture> m_color;
+ };
+
+ // ---- Pipeline -----------------------------------------------------
+ class GLPipeline : public Pipeline
+ {
+ public:
+ explicit GLPipeline(const PipelineDesc& d) : m_desc(d)
+ {
+ std::string path = "assets/shaders/generated/" + d.shader + ".glsl";
+ m_shader = Ref<Shader>(Shader::create(path));
+ m_prog = m_shader ? m_shader->get_renderer_id() : 0;
+ if (!m_prog) { DONUT_ERROR("GL RHI: shader '{}' failed", d.shader); return; }
+
+ glUseProgram(m_prog);
+ for (const auto& r : d.resources)
+ {
+ if (r.kind == ResourceKind::UniformBuffer)
+ {
+ GLuint bi = glGetUniformBlockIndex(m_prog, r.name.c_str());
+ if (bi != GL_INVALID_INDEX) glUniformBlockBinding(m_prog, bi, r.binding);
+ }
+ else // Texture: point the sampler at texture unit == binding
+ {
+ GLint loc = glGetUniformLocation(m_prog, r.name.c_str());
+ if (loc >= 0) glUniform1i(loc, (GLint)r.binding);
+ }
+ }
+ glUseProgram(0);
+ }
+ PipelineDesc m_desc; Ref<Shader> m_shader; GLuint m_prog = 0;
+ };
+
+ // ---- CommandList (immediate) --------------------------------------
+ class GLCommandList : public CommandList
+ {
+ public:
+ explicit GLCommandList(GLuint vao) : m_vao(vao) {}
+
+ auto begin_render_pass(RenderTarget* target, const glm::vec4& clear) -> void override
+ {
+ auto* rt = static_cast<GLRenderTarget*>(target);
+ glBindFramebuffer(GL_FRAMEBUFFER, rt ? rt->m_fbo : 0);
+ int w = rt ? rt->m_w : m_default_w, h = rt ? rt->m_h : m_default_h;
+ glViewport(0, 0, w, h);
+ glDisable(GL_SCISSOR_TEST);
+ glDepthMask(GL_TRUE);
+ glClearColor(clear.r, clear.g, clear.b, clear.a);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ }
+ auto end_render_pass() -> void override {}
+
+ auto bind_pipeline(Pipeline* p) -> void override
+ {
+ m_pipe = static_cast<GLPipeline*>(p);
+ glUseProgram(m_pipe->m_prog);
+ const auto& d = m_pipe->m_desc;
+ if (d.depth_test) { glEnable(GL_DEPTH_TEST); glDepthFunc(gl_compare(d.depth_op)); }
+ else glDisable(GL_DEPTH_TEST);
+ glDepthMask(d.depth_write ? GL_TRUE : GL_FALSE);
+ if (d.cull == CullMode::None) glDisable(GL_CULL_FACE);
+ else { glEnable(GL_CULL_FACE); glCullFace(d.cull == CullMode::Back ? GL_BACK : GL_FRONT); }
+ if (d.blend == BlendMode::AlphaBlend) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); }
+ else glDisable(GL_BLEND);
+ }
+ auto set_viewport(int x, int y, int w, int h, bool /*flip_y*/) -> void override { glViewport(x, y, w, h); }
+ auto bind_uniform(uint32_t binding, Buffer* ubo) -> void override
+ {
+ glBindBufferBase(GL_UNIFORM_BUFFER, binding, static_cast<GLBuffer*>(ubo)->m_id);
+ }
+ auto bind_texture(uint32_t binding, Texture* tex) -> void override
+ {
+ auto* t = static_cast<GLTexture*>(tex);
+ glActiveTexture(GL_TEXTURE0 + binding);
+ glBindTexture(t->m_target, t->m_id);
+ }
+ auto bind_vertex_buffer(Buffer* vb) -> void override
+ {
+ glBindBuffer(GL_ARRAY_BUFFER, static_cast<GLBuffer*>(vb)->m_id);
+ const auto& layout = m_pipe->m_desc.vertex_layout;
+ for (const auto& a : layout.attributes)
+ {
+ glEnableVertexAttribArray(a.location);
+ glVertexAttribPointer(a.location, (GLint)a.components, GL_FLOAT, GL_FALSE,
+ (GLsizei)layout.stride, (const void*)(uintptr_t)a.offset);
+ }
+ }
+ auto bind_index_buffer(Buffer* ib) -> void override
+ {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLBuffer*>(ib)->m_id);
+ }
+ auto draw(uint32_t count) -> void override
+ {
+ glDrawArrays(gl_topology(m_pipe->m_desc.topology), 0, (GLsizei)count);
+ }
+ auto draw_indexed(uint32_t count) -> void override
+ {
+ glDrawElements(gl_topology(m_pipe->m_desc.topology), (GLsizei)count, GL_UNSIGNED_INT, nullptr);
+ }
+
+ GLuint m_vao; GLPipeline* m_pipe = nullptr; int m_default_w = 1, m_default_h = 1;
+ };
+
+ // ---- Device -------------------------------------------------------
+ class GLDevice : public Device
+ {
+ public:
+ auto init(void* window, int width, int height) -> bool override
+ {
+ m_window = window;
+ glfwMakeContextCurrent(static_cast<GLFWwindow*>(window));
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { DONUT_ERROR("GL RHI: GLAD load failed"); return false; }
+ m_width = width; m_height = height;
+ const char* name = (const char*)glGetString(GL_RENDERER);
+ m_name = name ? name : "OpenGL";
+ glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); // GL core needs one bound VAO
+ m_cmds = create_scope<GLCommandList>(m_vao);
+ DONUT_INFO("GL RHI device: {} ({}x{})", m_name, width, height);
+ return true;
+ }
+ auto shutdown() -> void override
+ {
+ if (m_imgui) { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); m_imgui = false; }
+ if (m_vao) { glDeleteVertexArrays(1, &m_vao); m_vao = 0; }
+ }
+ auto resize(int w, int h) -> void override { m_width = w; m_height = h; }
+ auto wait_idle() -> void override { glFinish(); }
+
+ auto create_buffer(BufferType t, size_t size, const void* data) -> Ref<Buffer> override
+ { return create_ref<GLBuffer>(t, size, data); }
+ auto create_texture(int w, int h, Format f, Filter fl, const void* data) -> Ref<Texture> override
+ { return create_ref<GLTexture>(w, h, f, fl, data); }
+ auto create_cubemap_from_hdri(const std::string& path) -> Ref<Texture> override
+ {
+ HDRIManager::get().set_current_hdri(path);
+ return create_ref<GLTexture>(HDRIManager::get().get_current_hdri());
+ }
+ auto create_render_target(int w, int h, Format color, bool depth, Filter fl, int /*mips*/) -> Ref<RenderTarget> override
+ { return create_ref<GLRenderTarget>(w, h, color, depth, fl); }
+ auto create_pipeline(const PipelineDesc& d) -> Ref<Pipeline> override
+ { return create_ref<GLPipeline>(d); }
+
+ auto begin_frame(const glm::vec4& /*clear*/) -> CommandList* override
+ {
+ glfwGetFramebufferSize(static_cast<GLFWwindow*>(m_window), &m_width, &m_height);
+ m_cmds->m_default_w = m_width; m_cmds->m_default_h = m_height;
+ glBindVertexArray(m_vao);
+ return m_cmds.get();
+ }
+ auto end_frame() -> void override { glfwSwapBuffers(static_cast<GLFWwindow*>(m_window)); }
+
+ auto init_imgui() -> void override
+ {
+ IMGUI_CHECKVERSION(); ImGui::CreateContext();
+ ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable;
+ ImGui_ImplGlfw_InitForOpenGL(static_cast<GLFWwindow*>(m_window), true);
+ ImGui_ImplOpenGL3_Init("#version 410");
+ m_imgui = true;
+ DONUT_INFO("GL RHI: ImGui backend initialized");
+ }
+ auto imgui_new_frame() -> void override
+ {
+ ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame();
+ }
+ auto imgui_render(CommandList&) -> void override
+ {
+ ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ }
+ auto device_name() const -> const std::string& override { return m_name; }
+
+ private:
+ void* m_window = nullptr; std::string m_name; GLuint m_vao = 0;
+ int m_width = 0, m_height = 0; bool m_imgui = false;
+ Scope<GLCommandList> m_cmds;
+ };
+ }
+
+ auto create_opengl_device() -> Scope<Device> { return create_scope<GLDevice>(); }
+}
diff --git a/src/platform/opengl/opengl_device.h b/src/platform/opengl/opengl_device.h
new file mode 100644
index 0000000..4004396
--- /dev/null
+++ b/src/platform/opengl/opengl_device.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "rendering/rhi.h"
+
+namespace Donut::RHI
+{
+ // OpenGL implementation of the RHI Device: emulates the explicit model with
+ // immediate-mode GL 4.1 (one global VAO, glBindBufferBase for UBOs, FBOs for
+ // render targets, per-draw attribute setup, bindings assigned by name).
+ auto create_opengl_device() -> Scope<Device>;
+}
diff --git a/src/platform/opengl/opengl_renderer.cpp b/src/platform/opengl/opengl_renderer.cpp
deleted file mode 100644
index ae12f10..0000000
--- a/src/platform/opengl/opengl_renderer.cpp
+++ /dev/null
@@ -1,377 +0,0 @@
-#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" // Renderer::init / RenderCommand
-
-#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" // CubemapTexture::bind
-
-namespace Donut
-{
- // Scroll is accumulated through a chained GLFW callback (keeps ImGui's scroll
- // handling intact), consumed once per frame for orbital zoom.
- 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(); // GLAD load + default GL state
- 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); // capped for the macOS watchdog (tiled fragment pass)
-#else
- m_engine->set_compute_height(540);
-#endif
- m_engine->update_compute_dimensions();
-
- // Match the Vulkan framing exactly (camera sits outside the disk).
- 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; // scroll unused in free-fly
- }
- 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);
- }
- }
-
- // 36-vertex unit cube for the skybox (positions only; the shader forces depth=1).
- 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);
-
- // Grid: +/-50 lines on the XZ plane (Grid shader scales by u_GridSize/50).
- 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);
-
- // Sphere: indexed UV sphere (position + normal), unit radius.
- 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 }); // pos, normal (unit sphere)
- }
- }
- 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)));
-
- // Skybox cube.
- 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();
-
- // Skybox behind everything (depth test on, write off; shader forces depth=1).
- 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))); // strip translation
- 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);
-
- // Lit spheres.
- 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);
- }
-
- // Transparent grid on top (test, no depth write).
- 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);
- // Geodesic pass -> low-res texture (watchdog-tiled on macOS), then
- // upscale to the window. Disk/camera come from the shared BlackHoleParams.
- 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); // the Engine re-reads it each frame
- 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
deleted file mode 100644
index 4258f40..0000000
--- a/src/platform/opengl/opengl_renderer.h
+++ /dev/null
@@ -1,72 +0,0 @@
-#pragma once
-
-#include "rendering/renderer_backend.h"
-#include "core/memory.h"
-#include "core/camera.h"
-#include "rendering/shader.h"
-
-// OpenGL implementation of the shared RendererBackend interface. It renders the
-// SAME cross-compiled Geodesic shader the Vulkan backend does (driven by the
-// same BlackHoleParams + camera), so the application behaves identically on
-// either backend. Wraps the Engine, which owns the GL geodesic/present passes
-// and the macOS watchdog-safe tiled rendering.
-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; // grid + sphere + skybox meshes/shaders
- auto render_scene(int width, int height) -> void;
-
- void* m_window = nullptr;
- Scope<Engine> m_engine;
-
- // World-builder scene view (normal-scale): mirrors the Vulkan scene view
- // with the same shared Grid/Sphere/Skybox shaders.
- 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;
- };
-}