diff options
| author | hachem <im@hachem.wtf> | 2026-08-23 17:21:37 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-08-23 17:21:37 +0200 |
| commit | 3fd33ecff7472e4d6fc9e6b3905f56982e497ef2 (patch) | |
| tree | 66e5812f1a18b926e88a0dd549fe14e7c7327165 /src/platform/opengl/opengl_device.cpp | |
| parent | e3aa33fc9a99d8b817bda42a567a4c347e18ab32 (diff) | |
[feat]: complete RHI api
Diffstat (limited to 'src/platform/opengl/opengl_device.cpp')
| -rw-r--r-- | src/platform/opengl/opengl_device.cpp | 279 |
1 files changed, 279 insertions, 0 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>(); } +} |
