diff options
Diffstat (limited to 'src/platform/opengl')
| -rw-r--r-- | src/platform/opengl/opengl_device.cpp | 279 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_device.h | 11 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_renderer.cpp | 377 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_renderer.h | 72 |
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; - }; -} |
