From 5b361d81dbd2af0d0e99b9eb1adebea76ff05db0 Mon Sep 17 00:00:00 2001 From: hachem Date: Thu, 20 Aug 2026 02:27:02 +0200 Subject: [chore]: MASSIVE refactor + more vulkan bs --- src/platform/opengl/opengl_framebuffer.cpp | 246 +++++++++++++++++++++ src/platform/opengl/opengl_framebuffer.h | 35 +++ src/platform/opengl/opengl_index_buffer.cpp | 28 +++ src/platform/opengl/opengl_index_buffer.h | 22 ++ src/platform/opengl/opengl_renderer_api.cpp | 118 ++++++++++ src/platform/opengl/opengl_renderer_api.h | 44 ++++ src/platform/opengl/opengl_shader.cpp | 302 ++++++++++++++++++++++++++ src/platform/opengl/opengl_shader.h | 58 +++++ src/platform/opengl/opengl_texture.cpp | 272 +++++++++++++++++++++++ src/platform/opengl/opengl_texture.h | 68 ++++++ src/platform/opengl/opengl_uniform_buffer.cpp | 29 +++ src/platform/opengl/opengl_uniform_buffer.h | 21 ++ src/platform/opengl/opengl_vertex_array.cpp | 58 +++++ src/platform/opengl/opengl_vertex_array.h | 41 ++++ src/platform/opengl/opengl_vertex_buffer.cpp | 35 +++ src/platform/opengl/opengl_vertex_buffer.h | 25 +++ 16 files changed, 1402 insertions(+) create mode 100644 src/platform/opengl/opengl_framebuffer.cpp create mode 100644 src/platform/opengl/opengl_framebuffer.h create mode 100644 src/platform/opengl/opengl_index_buffer.cpp create mode 100644 src/platform/opengl/opengl_index_buffer.h create mode 100644 src/platform/opengl/opengl_renderer_api.cpp create mode 100644 src/platform/opengl/opengl_renderer_api.h create mode 100644 src/platform/opengl/opengl_shader.cpp create mode 100644 src/platform/opengl/opengl_shader.h create mode 100644 src/platform/opengl/opengl_texture.cpp create mode 100644 src/platform/opengl/opengl_texture.h create mode 100644 src/platform/opengl/opengl_uniform_buffer.cpp create mode 100644 src/platform/opengl/opengl_uniform_buffer.h create mode 100644 src/platform/opengl/opengl_vertex_array.cpp create mode 100644 src/platform/opengl/opengl_vertex_array.h create mode 100644 src/platform/opengl/opengl_vertex_buffer.cpp create mode 100644 src/platform/opengl/opengl_vertex_buffer.h (limited to 'src/platform/opengl') diff --git a/src/platform/opengl/opengl_framebuffer.cpp b/src/platform/opengl/opengl_framebuffer.cpp new file mode 100644 index 0000000..1624439 --- /dev/null +++ b/src/platform/opengl/opengl_framebuffer.cpp @@ -0,0 +1,246 @@ +#include "opengl_framebuffer.h" +#include "core/log.h" + +#include + +namespace Donut +{ + namespace Utils + { + static GLenum TextureTarget(bool multisampled) + { + return multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + } + + static void bind_texture(bool multisampled, uint32_t id) + { + glBindTexture(TextureTarget(multisampled), id); + } + + static void CreateTextures(bool multisampled, uint32_t* out_id, uint32_t count) + { + // glCreateTextures is 4.5 DSA; macOS caps at 4.1. Callers bind each + // texture (with the correct target) before use. + glGenTextures(count, out_id); + } + + static void AttachColorTexture(uint32_t id, int samples, GLenum internal_format, GLenum format, uint32_t width, uint32_t height, int index) + { + bool multisampled = samples > 1; + if (multisampled) + { + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, internal_format, width, height, GL_FALSE); + } + else + { + // Integer color formats require an integer pixel type even when + // data is null, or macOS's strict core profile rejects the call. + GLenum type = (format == GL_RED_INTEGER) ? GL_INT : GL_UNSIGNED_BYTE; + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, TextureTarget(multisampled), id, 0); + } + + static void AttachDepthTexture(uint32_t id, int samples, GLenum format, GLenum attachment_type, uint32_t width, uint32_t height) + { + bool multisampled = samples > 1; + if (multisampled) + { + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, format, width, height, GL_FALSE); + } + else + { + // glTexStorage2D is 4.2; use mutable storage for macOS (4.1). + GLenum depth_format = (format == GL_DEPTH24_STENCIL8) ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT; + GLenum depth_type = (format == GL_DEPTH24_STENCIL8) ? GL_UNSIGNED_INT_24_8 : GL_FLOAT; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, depth_format, depth_type, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment_type, TextureTarget(multisampled), id, 0); + } + + static bool IsDepthFormat(FramebufferTextureFormat format) + { + switch (format) + { + case FramebufferTextureFormat::DEPTH24STENCIL8: return true; + } + return false; + } + + static GLenum DonutFBTextureFormatToGL(FramebufferTextureFormat format) + { + switch (format) + { + case FramebufferTextureFormat::RGBA8: return GL_RGBA8; + case FramebufferTextureFormat::RED_INTEGER: return GL_RED_INTEGER; + } + + return 0; + } + } + + OpenGLFramebuffer::OpenGLFramebuffer(const FramebufferSpecification& spec) + : m_specification(spec) + { + for (auto spec : m_specification.attachments.attachments) + { + if (!Utils::IsDepthFormat(spec.texture_format)) + m_color_attachment_specifications.emplace_back(spec); + else + m_depth_attachment_specification = spec; + } + + invalidate(); + } + + OpenGLFramebuffer::~OpenGLFramebuffer() + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast(m_color_attachments.size()), m_color_attachments.data()); + glDeleteTextures(1, &m_depth_attachment); + } + + auto OpenGLFramebuffer::invalidate() -> void + { + if (m_renderer_id) + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast(m_color_attachments.size()), m_color_attachments.data()); + glDeleteTextures(1, &m_depth_attachment); + + m_color_attachments.clear(); + m_depth_attachment = 0; + } + + glGenFramebuffers(1, &m_renderer_id); // glCreateFramebuffers is 4.5 DSA; unavailable on macOS 4.1 + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + + bool multisample = m_specification.Samples > 1; + + if (m_color_attachment_specifications.size()) + { + m_color_attachments.resize(m_color_attachment_specifications.size()); + Utils::CreateTextures(multisample, m_color_attachments.data(), static_cast(m_color_attachments.size())); + + for (size_t i = 0; i < m_color_attachments.size(); i++) + { + Utils::bind_texture(multisample, m_color_attachments[i]); + switch (m_color_attachment_specifications[i].texture_format) + { + case FramebufferTextureFormat::RGBA8: + Utils::AttachColorTexture(m_color_attachments[i], m_specification.Samples, GL_RGBA8, GL_RGBA, m_specification.Width, m_specification.Height, static_cast(i)); + break; + case FramebufferTextureFormat::RED_INTEGER: + Utils::AttachColorTexture(m_color_attachments[i], m_specification.Samples, GL_R32I, GL_RED_INTEGER, m_specification.Width, m_specification.Height, static_cast(i)); + break; + } + } + } + + if (m_depth_attachment_specification.texture_format != FramebufferTextureFormat::None) + { + Utils::CreateTextures(multisample, &m_depth_attachment, 1); + Utils::bind_texture(multisample, m_depth_attachment); + switch (m_depth_attachment_specification.texture_format) + { + case FramebufferTextureFormat::DEPTH24STENCIL8: + Utils::AttachDepthTexture(m_depth_attachment, m_specification.Samples, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, m_specification.Width, m_specification.Height); + break; + } + } + + if (m_color_attachments.size() > 1) + { + GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; + glDrawBuffers(static_cast(m_color_attachments.size()), buffers); + } + else if (m_color_attachments.empty()) + glDrawBuffer(GL_NONE); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + switch (status) + { + case GL_FRAMEBUFFER_UNDEFINED: + DONUT_ERROR("Framebuffer is undefined"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + DONUT_ERROR("Framebuffer has incomplete attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + DONUT_ERROR("Framebuffer is missing attachment"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + DONUT_ERROR("Framebuffer has incomplete draw buffer"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + DONUT_ERROR("Framebuffer has incomplete read buffer"); + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + DONUT_ERROR("Framebuffer format is unsupported"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + DONUT_ERROR("Framebuffer has incomplete multisample"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: + DONUT_ERROR("Framebuffer has incomplete layer targets"); + break; + default: + DONUT_ERROR("Framebuffer is incomplete (unknown error: {})", status); + break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + auto OpenGLFramebuffer::bind() -> void + { + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + glViewport(0, 0, m_specification.Width, m_specification.Height); + } + + auto OpenGLFramebuffer::unbind() -> void + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + auto OpenGLFramebuffer::resize(uint32_t width, uint32_t height) -> void + { + m_specification.Width = width; + m_specification.Height = height; + + invalidate(); + } + + auto OpenGLFramebuffer::read_pixel(uint32_t attachment_index, int x, int y) -> int + { + glReadBuffer(GL_COLOR_ATTACHMENT0 + attachment_index); + int pixel_data; + glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &pixel_data); + return pixel_data; + } + + auto OpenGLFramebuffer::clear_attachment(uint32_t attachment_index, int value) -> void + { + // glClearTexImage is 4.4 and unavailable on macOS. clear the integer + // attachment by binding this framebuffer and clearing its draw buffer. + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + glClearBufferiv(GL_COLOR, static_cast(attachment_index), &value); + } +}; diff --git a/src/platform/opengl/opengl_framebuffer.h b/src/platform/opengl/opengl_framebuffer.h new file mode 100644 index 0000000..456d924 --- /dev/null +++ b/src/platform/opengl/opengl_framebuffer.h @@ -0,0 +1,35 @@ +#pragma once + +#include "rendering/framebuffer.h" + +namespace Donut +{ + class OpenGLFramebuffer : public Framebuffer + { + public: + OpenGLFramebuffer(const FramebufferSpecification& spec); + virtual ~OpenGLFramebuffer(); + + auto invalidate() -> void; + + virtual auto bind() -> void override; + virtual auto unbind() -> void override; + + virtual auto resize(uint32_t width, uint32_t height) -> void override; + virtual auto read_pixel(uint32_t attachment_index, int x, int y) -> int override; + + virtual auto clear_attachment(uint32_t attachment_index, int value) -> void override; + virtual auto get_color_attachment_renderer_id(uint32_t index = 0) const -> uint32_t override{ return m_color_attachments[index]; } + + virtual auto get_specification() const -> const FramebufferSpecification& override{ return m_specification; } + private: + uint32_t m_renderer_id = 0; + FramebufferSpecification m_specification; + + std::vector m_color_attachment_specifications; + FramebufferTextureSpecification m_depth_attachment_specification = FramebufferTextureFormat::None; + + std::vector m_color_attachments; + uint32_t m_depth_attachment = 0; + }; +}; diff --git a/src/platform/opengl/opengl_index_buffer.cpp b/src/platform/opengl/opengl_index_buffer.cpp new file mode 100644 index 0000000..faa6b81 --- /dev/null +++ b/src/platform/opengl/opengl_index_buffer.cpp @@ -0,0 +1,28 @@ +#include "opengl_index_buffer.h" +#include + +namespace Donut +{ + OpenGLIndexBuffer::OpenGLIndexBuffer(const uint32_t* indices, uint32_t count) + : m_count(count) + { + glGenBuffers(1, &m_renderer_id); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_renderer_id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW); + } + + OpenGLIndexBuffer::~OpenGLIndexBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto OpenGLIndexBuffer::bind() const -> void + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_renderer_id); + } + + auto OpenGLIndexBuffer::unbind() const -> void + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } +}; diff --git a/src/platform/opengl/opengl_index_buffer.h b/src/platform/opengl/opengl_index_buffer.h new file mode 100644 index 0000000..397da43 --- /dev/null +++ b/src/platform/opengl/opengl_index_buffer.h @@ -0,0 +1,22 @@ +#pragma once + +#include "rendering/index_buffer.h" + +namespace Donut +{ + class OpenGLIndexBuffer + : public IndexBuffer + { + public: + OpenGLIndexBuffer(const uint32_t* indices, uint32_t count); + virtual ~OpenGLIndexBuffer(); + + virtual auto bind() const -> void override; + virtual auto unbind() const -> void override; + virtual auto get_count() const -> uint32_t override{ return m_count; } + + private: + uint32_t m_renderer_id; + uint32_t m_count; + }; +}; diff --git a/src/platform/opengl/opengl_renderer_api.cpp b/src/platform/opengl/opengl_renderer_api.cpp new file mode 100644 index 0000000..39ed786 --- /dev/null +++ b/src/platform/opengl/opengl_renderer_api.cpp @@ -0,0 +1,118 @@ +#include "opengl_renderer_api.h" + +#include +#include + +namespace Donut +{ + auto OpenGLRendererAPI::init() -> void + { + if (!glfwGetCurrentContext()) + { + DONUT_ERROR("No OpenGL context is current! Cannot initialize GLAD."); + return; + } + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + DONUT_ERROR("Failed to initialize GLAD!"); + return; + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + } + + auto OpenGLRendererAPI::set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void + { + glViewport(x, y, width, height); + } + + auto OpenGLRendererAPI::set_clear_color(const glm::vec4& color) -> void + { + glClearColor(color.r, color.g, color.b, color.a); + } + + auto OpenGLRendererAPI::clear() -> void + { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + auto OpenGLRendererAPI::enable_depth_test() -> void + { + glEnable(GL_DEPTH_TEST); + } + + auto OpenGLRendererAPI::disable_depth_test() -> void + { + glDisable(GL_DEPTH_TEST); + } + + auto OpenGLRendererAPI::set_face_culling(bool enabled) -> void + { + if (enabled) + { + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + } + else + glDisable(GL_CULL_FACE); + } + + auto OpenGLRendererAPI::enable_blending() -> void + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + auto OpenGLRendererAPI::disable_blending() -> void + { + glDisable(GL_BLEND); + } + + auto OpenGLRendererAPI::draw_indexed(const Ref& vertex_array, uint32_t index_count) -> void + { + uint32_t count = index_count ? index_count : vertex_array->get_index_buffer()->get_count(); + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + } + + auto OpenGLRendererAPI::draw_arrays(uint32_t vertex_count, uint32_t first) -> void + { + glDrawArrays(GL_TRIANGLES, first, vertex_count); + } + + auto OpenGLRendererAPI::draw_lines(const Ref& vertex_array, uint32_t index_count) -> void + { + uint32_t count = index_count ? index_count : vertex_array->get_index_buffer()->get_count(); + glDrawElements(GL_LINES, count, GL_UNSIGNED_INT, nullptr); + } + + auto OpenGLRendererAPI::bind_texture(uint32_t texture_id, uint32_t slot) -> void + { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, texture_id); + } + + auto OpenGLRendererAPI::bind_image_texture(uint32_t texture_id, uint32_t slot, bool read_only) -> void + { + // Image load/store is OpenGL 4.2; the pointer is null on macOS (4.1). + if (glBindImageTexture == nullptr) + return; + glBindImageTexture(slot, texture_id, 0, GL_FALSE, 0, + read_only ? GL_READ_ONLY : GL_WRITE_ONLY, GL_RGBA8); + } + + auto OpenGLRendererAPI::read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) -> void + { + glReadPixels(x, y, width, height, format, type, pixels); + } +}; diff --git a/src/platform/opengl/opengl_renderer_api.h b/src/platform/opengl/opengl_renderer_api.h new file mode 100644 index 0000000..02f18ab --- /dev/null +++ b/src/platform/opengl/opengl_renderer_api.h @@ -0,0 +1,44 @@ +#pragma once + +#include "core/memory.h" +#include "core/log.h" + +#include "rendering/renderer.h" + +#include + +namespace Donut +{ + class OpenGLRendererAPI + : public RendererAPI + { + public: + virtual auto init() -> void override; + virtual void set_viewport(uint32_t x, uint32_t y, + uint32_t width, uint32_t height) override; + virtual auto set_clear_color(const glm::vec4& color) -> void override; + virtual auto clear() -> void override; + virtual auto enable_depth_test() -> void override; + virtual auto disable_depth_test() -> void override; + virtual auto set_face_culling(bool enabled) -> void override; + virtual auto enable_blending() -> void override; + virtual auto disable_blending() -> void override; + + virtual void draw_indexed(const Ref& vertex_array, + uint32_t index_count = 0) override; + + virtual void draw_arrays(uint32_t vertex_count, + uint32_t first = 0) override; + virtual void draw_lines(const Ref& vertex_array, + uint32_t index_count = 0) override; + virtual void bind_texture(uint32_t texture_id, + uint32_t slot = 0) override; + virtual void bind_image_texture(uint32_t texture_id, + uint32_t slot = 0, + bool read_only = false) override; + virtual void read_pixels(uint32_t x, uint32_t y, + uint32_t width, uint32_t height, + uint32_t format, uint32_t type, + void* pixels) override; + }; +}; diff --git a/src/platform/opengl/opengl_shader.cpp b/src/platform/opengl/opengl_shader.cpp new file mode 100644 index 0000000..d15d7e0 --- /dev/null +++ b/src/platform/opengl/opengl_shader.cpp @@ -0,0 +1,302 @@ +#include "opengl_shader.h" + +#include +#include + +#include +#include + +namespace Donut +{ + static uint32_t ShaderTypeFromString(const std::string& type) + { + if (type == "vertex") + return GL_VERTEX_SHADER; + if (type == "fragment" || type == "pixel") + return GL_FRAGMENT_SHADER; + if (type == "compute") + return GL_COMPUTE_SHADER; + return 0; + } + + // Shaders are authored in Slang and compiled to assets/shaders/generated/ + // .glsl by Tools/compile-shaders.sh. Given a legacy ".../.glsl" + // path, prefer that generated file when present; otherwise fall back to the + // hand-written GLSL (e.g. shaders not yet ported to Slang). + static std::string ResolveShaderPath(const std::string& filepath) + { + size_t slash = filepath.find_last_of("/\\"); + std::string dir = (slash == std::string::npos) ? std::string() : filepath.substr(0, slash + 1); + std::string file = (slash == std::string::npos) ? filepath : filepath.substr(slash + 1); + size_t dot = file.rfind('.'); + std::string base = (dot == std::string::npos) ? file : file.substr(0, dot); + + std::string generated = dir + "generated/" + base + ".glsl"; + std::ifstream test(generated); + if (test.good()) + return generated; + return filepath; + } + + OpenGLShader::OpenGLShader(const std::string& filepath) + { + std::string resolved = ResolveShaderPath(filepath); + m_is_slang = (resolved != filepath); + std::string source = read_file(resolved); + auto shader_sources = pre_process(source); + compile(shader_sources); + + auto last_slash = filepath.find_last_of("/\\"); + last_slash = last_slash == std::string::npos ? 0 : last_slash + 1; + auto last_dot = filepath.rfind('.'); + auto count = last_dot == std::string::npos ? filepath.size() - last_slash : last_dot - last_slash; + m_name = filepath.substr(last_slash, count); + } + + OpenGLShader::OpenGLShader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) + : m_name(name) + { + std::unordered_map sources; + sources[GL_VERTEX_SHADER] = vertex_src; + sources[GL_FRAGMENT_SHADER] = fragment_src; + compile(sources); + } + + OpenGLShader::OpenGLShader(const std::string& name, const std::string& compute_src) + : m_name(name) + { + std::unordered_map sources; + sources[GL_COMPUTE_SHADER] = compute_src; + compile(sources); + } + + OpenGLShader::~OpenGLShader() + { + glDeleteProgram(m_renderer_id); + } + + auto OpenGLShader::read_file(const std::string& filepath) -> std::string + { + std::string result; + std::ifstream in(filepath, std::ios::in | + std::ios::binary); + + if (in) + { + in.seekg(0, std::ios::end); + size_t size = in.tellg(); + if (size != -1) + { + result.resize(size); + in.seekg(0, std::ios::beg); + in.read(&result[0], size); + } + } + return result; + } + + auto OpenGLShader::pre_process(const std::string& source) -> std::unordered_map + { + std::unordered_map shader_sources; + + const char* type_token = "#type"; + size_t type_token_length = strlen(type_token); + size_t pos = source.find(type_token, 0); + + while (pos != std::string::npos) + { + size_t eol = source.find_first_of("\r\n", pos); + size_t begin = pos + type_token_length + 1; + std::string type = source.substr(begin, eol - begin); + + size_t next_line_pos = source.find_first_not_of("\r\n", eol); + pos = source.find(type_token, next_line_pos); + shader_sources[ShaderTypeFromString(type)] = (pos == std::string::npos) ? source.substr(next_line_pos) : + source.substr(next_line_pos, pos - next_line_pos); + } + + return shader_sources; + } + + auto OpenGLShader::compile(const std::unordered_map& shader_sources) -> void + { + uint32_t program = glCreateProgram(); + std::vector glShaderIDs(shader_sources.size()); + for (auto& kv : shader_sources) + { + uint32_t type = kv.first; + const std::string& source = kv.second; + + uint32_t shader = glCreateShader(type); + const char* source_c_str = source.c_str(); + glShaderSource(shader, 1, &source_c_str, 0); + glCompileShader(shader); + + int is_compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled); + if (is_compiled == GL_FALSE) + { + int max_length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length); + std::vector info_log(max_length); + glGetShaderInfoLog(shader, max_length, &max_length, &info_log[0]); + glDeleteShader(shader); + for (auto id : glShaderIDs) + glDeleteShader(id); + glDeleteProgram(program); + m_renderer_id = 0; + // info_log.data() is null when the driver returns an empty log + // (e.g. macOS rejecting a compute shader); streaming a null + // char* into std::cout calls strlen(NULL) and crashes. + const char* log = info_log.empty() ? "" : info_log.data(); + std::cout << "Shader compilation failure!" << std::endl << log << std::endl; + return; + } + glAttachShader(program, shader); + glShaderIDs.push_back(shader); + } + + m_renderer_id = program; + glLinkProgram(m_renderer_id); + + int is_linked = 0; + glGetProgramiv(m_renderer_id, GL_LINK_STATUS, (int*)&is_linked); + if (is_linked == GL_FALSE) + { + int max_length = 0; + glGetProgramiv(m_renderer_id, GL_INFO_LOG_LENGTH, &max_length); + std::vector info_log(max_length); + glGetProgramInfoLog(m_renderer_id, max_length, &max_length, &info_log[0]); + glDeleteProgram(m_renderer_id); + for (auto id : glShaderIDs) + glDeleteShader(id); + m_renderer_id = 0; + const char* log = info_log.empty() ? "" : info_log.data(); + std::cout << "Shader link failure!" << std::endl << log << std::endl; + return; + } + + for (auto id : glShaderIDs) + { + glDetachShader(m_renderer_id, id); + glDeleteShader(id); + } + } + + auto OpenGLShader::bind() const -> void + { + glUseProgram(m_renderer_id); + } + + auto OpenGLShader::unbind() const -> void + { + glUseProgram(0); + } + + auto OpenGLShader::set_int(const std::string& name, int value) -> void + { + upload_uniform_int(name, value); + } + + auto OpenGLShader::set_int_array(const std::string& name, int* values, uint32_t count) -> void + { + upload_uniform_int_array(name, values, count); + } + + auto OpenGLShader::set_float(const std::string& name, float value) -> void + { + upload_uniform_float(name, value); + } + + auto OpenGLShader::set_float2(const std::string& name, const glm::vec2& value) -> void + { + upload_uniform_float2(name, value); + } + + auto OpenGLShader::set_float3(const std::string& name, const glm::vec3& value) -> void + { + upload_uniform_float3(name, value); + } + + auto OpenGLShader::set_float4(const std::string& name, const glm::vec4& value) -> void + { + upload_uniform_float4(name, value); + } + + auto OpenGLShader::set_mat4(const std::string& name, const glm::mat4& value) -> void + { + upload_uniform_mat4(name, value); + } + + auto OpenGLShader::upload_uniform_int(const std::string& name, int value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform1i(location, value); + } + + auto OpenGLShader::upload_uniform_int_array(const std::string& name, int* values, uint32_t count) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform1iv(location, count, values); + } + + auto OpenGLShader::upload_uniform_float(const std::string& name, float value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform1f(location, value); + } + + auto OpenGLShader::upload_uniform_float2(const std::string& name, const glm::vec2& value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform2f(location, value.x, value.y); + } + + auto OpenGLShader::upload_uniform_float3(const std::string& name, const glm::vec3& value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform3f(location, value.x, value.y, value.z); + } + + auto OpenGLShader::upload_uniform_float4(const std::string& name, const glm::vec4& value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform4f(location, value.x, value.y, value.z, value.w); + } + + auto OpenGLShader::upload_uniform_mat3(const std::string& name, const glm::mat3& matrix) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniformMatrix3fv(location, 1, m_is_slang ? GL_TRUE : GL_FALSE, glm::value_ptr(matrix)); + } + + auto OpenGLShader::upload_uniform_mat4(const std::string& name, const glm::mat4& matrix) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniformMatrix4fv(location, 1, m_is_slang ? GL_TRUE : GL_FALSE, glm::value_ptr(matrix)); + } + + auto OpenGLShader::dispatch(uint32_t x, uint32_t y, uint32_t z) -> void + { + // Compute shaders require OpenGL 4.3+. On drivers that cap out earlier + // (e.g. macOS, which is frozen at 4.1) glDispatchCompute is never + // loaded and the pointer is null. Guard so we no-op instead of crash. + if (m_renderer_id == 0 || glDispatchCompute == nullptr) + return; + glDispatchCompute(x, y, z); + } + + auto OpenGLShader::dispatch_indirect(uint32_t offset) -> void + { + if (m_renderer_id == 0 || glDispatchComputeIndirect == nullptr) + return; + glDispatchComputeIndirect(offset); + } + + auto OpenGLShader::memory_barrier(uint32_t barriers) -> void + { + if (glMemoryBarrier == nullptr) + return; + glMemoryBarrier(barriers); + } +}; diff --git a/src/platform/opengl/opengl_shader.h b/src/platform/opengl/opengl_shader.h new file mode 100644 index 0000000..289bfac --- /dev/null +++ b/src/platform/opengl/opengl_shader.h @@ -0,0 +1,58 @@ +#pragma once + +#include "rendering/shader.h" + +#include +#include + +namespace Donut +{ + class OpenGLShader + : public Shader + { + public: + OpenGLShader(const std::string& filepath); + OpenGLShader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); + OpenGLShader(const std::string& name, const std::string& compute_src); + virtual ~OpenGLShader(); + + virtual auto bind() const -> void override; + virtual auto unbind() const -> void override; + + virtual auto set_int( const std::string& name, int value) -> void override; + virtual auto set_int_array(const std::string& name, int* values, uint32_t count) -> void override; + virtual auto set_float( const std::string& name, float value) -> void override; + virtual auto set_float2( const std::string& name, const glm::vec2& value) -> void override; + virtual auto set_float3( const std::string& name, const glm::vec3& value) -> void override; + virtual auto set_float4( const std::string& name, const glm::vec4& value) -> void override; + virtual auto set_mat4( const std::string& name, const glm::mat4& value) -> void override; + + virtual auto dispatch(uint32_t x, uint32_t y = 1, uint32_t z = 1) -> void override; + virtual auto dispatch_indirect(uint32_t offset = 0) -> void override; + virtual auto memory_barrier(uint32_t barriers) -> void override; + + virtual auto get_name() const -> const std::string& override{ return m_name; } + virtual auto get_renderer_id() const -> uint32_t override{ return m_renderer_id; } + + auto upload_uniform_int( const std::string& name, int value) -> void; + auto upload_uniform_int_array(const std::string& name, int* values, uint32_t count) -> void; + auto upload_uniform_float( const std::string& name, float value) -> void; + auto upload_uniform_float2( const std::string& name, const glm::vec2& value) -> void; + auto upload_uniform_float3( const std::string& name, const glm::vec3& value) -> void; + auto upload_uniform_float4( const std::string& name, const glm::vec4& value) -> void; + auto upload_uniform_mat3( const std::string& name, const glm::mat3& matrix) -> void; + auto upload_uniform_mat4( const std::string& name, const glm::mat4& matrix) -> void; + + private: + auto read_file(const std::string& filepath) -> std::string; + auto pre_process(const std::string& source) -> std::unordered_map; + auto compile(const std::unordered_map& shader_sources) -> void; + private: + uint32_t m_renderer_id = 0; + std::string m_name; + // True when loaded from a Slang-compiled GLSL. Slang expects row-major + // matrix data, so matrix uniforms are transposed on upload (glm is + // column-major) to keep all matrix math correct. + bool m_is_slang = false; + }; +}; diff --git a/src/platform/opengl/opengl_texture.cpp b/src/platform/opengl/opengl_texture.cpp new file mode 100644 index 0000000..6bcb9cd --- /dev/null +++ b/src/platform/opengl/opengl_texture.cpp @@ -0,0 +1,272 @@ +#include "opengl_texture.h" + +#include "rendering/shader.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include +#include +#include + +// NOTE: This file targets OpenGL 4.1 (the maximum macOS exposes). It uses the +// classic bind-based texture API rather than 4.5 Direct State Access +// (glCreateTextures / glTextureStorage2D / glTextureParameteri / glBindTextureUnit), +// none of which exist on macOS. + +namespace Donut +{ + OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height) + : m_width(width), m_height(height) + { + m_internal_format = GL_RGBA8; + m_data_format = GL_RGBA; + + glGenTextures(1, &m_renderer_id); + glBindTexture(GL_TEXTURE_2D, m_renderer_id); + glTexImage2D(GL_TEXTURE_2D, 0, m_internal_format, m_width, m_height, 0, m_data_format, GL_UNSIGNED_BYTE, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + OpenGLTexture2D::OpenGLTexture2D(const std::string& path) + : m_path(path) + { + m_width = 1; + m_height = 1; + m_internal_format = GL_RGBA8; + m_data_format = GL_RGBA; + + glGenTextures(1, &m_renderer_id); + glBindTexture(GL_TEXTURE_2D, m_renderer_id); + glTexImage2D(GL_TEXTURE_2D, 0, m_internal_format, m_width, m_height, 0, m_data_format, GL_UNSIGNED_BYTE, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + uint32_t white_pixel = 0xFFFFFFFF; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_data_format, GL_UNSIGNED_BYTE, &white_pixel); + + DONUT_INFO("Created default texture (stb_image not available for loading: ", path, ")"); + } + + OpenGLTexture2D::~OpenGLTexture2D() + { + glDeleteTextures(1, &m_renderer_id); + } + + auto OpenGLTexture2D::set_data(void* data, uint32_t size) -> void + { + uint32_t bpp = m_data_format == GL_RGBA ? 4 : 3; + if (size != m_width * m_height * bpp) + { + DONUT_ERROR("Data must be entire texture!"); + return; + } + + glBindTexture(GL_TEXTURE_2D, m_renderer_id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_data_format, GL_UNSIGNED_BYTE, data); + } + + auto OpenGLTexture2D::bind(uint32_t slot) const -> void + { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, m_renderer_id); + } + + auto OpenGLTexture2D::bind_as_image(uint32_t slot, bool read_only) const -> void + { + // Image load/store is OpenGL 4.2 and unavailable on macOS. Guard the + // function pointer so this degrades to a no-op instead of crashing. + if (glBindImageTexture == nullptr) + return; + GLenum access = read_only ? GL_READ_ONLY : GL_WRITE_ONLY; + glBindImageTexture(slot, m_renderer_id, 0, GL_FALSE, 0, access, m_internal_format); + } + + OpenGLCubemapTexture::OpenGLCubemapTexture(uint32_t width, uint32_t height) + : m_width(width), m_height(height) + { + m_internal_format = GL_RGBA16F; + m_data_format = GL_RGBA; + + glGenTextures(1, &m_renderer_id); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_renderer_id); + for (uint32_t i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_internal_format, m_width, m_height, 0, m_data_format, GL_FLOAT, nullptr); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + OpenGLCubemapTexture::OpenGLCubemapTexture(const std::string& path) + : m_path(path) + { + m_width = 1024; + m_height = 1024; + m_internal_format = GL_RGBA16F; + m_data_format = GL_RGBA; + + glGenTextures(1, &m_renderer_id); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_renderer_id); + for (uint32_t i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_internal_format, m_width, m_height, 0, m_data_format, GL_FLOAT, nullptr); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + LoadHDRI(path); + } + + OpenGLCubemapTexture::~OpenGLCubemapTexture() + { + glDeleteTextures(1, &m_renderer_id); + } + + auto OpenGLCubemapTexture::LoadHDRI(const std::string& path) -> void + { + stbi_set_flip_vertically_on_load(true); + int width, height, channels; + float* hdr_data = stbi_loadf(path.c_str(), &width, &height, &channels, 3); + + if (!hdr_data) + { + DONUT_ERROR("Failed to load HDRI: {}", path); + float default_sky[6 * 4] = + { + 0.5f, 0.7f, 1.0f, 1.0f, // Right + 0.5f, 0.7f, 1.0f, 1.0f, // Left + 0.5f, 0.7f, 1.0f, 1.0f, // Top + 0.5f, 0.7f, 1.0f, 1.0f, // Bottom + 0.5f, 0.7f, 1.0f, 1.0f, // Front + 0.5f, 0.7f, 1.0f, 1.0f // Back + }; + + glBindTexture(GL_TEXTURE_CUBE_MAP, m_renderer_id); + for (int i = 0; i < 6; ++i) + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, &default_sky[i * 4]); + return; + } + + convert_equirectangular_to_cubemap(hdr_data, width, height); + stbi_image_free(hdr_data); + + DONUT_INFO("Successfully loaded HDRI: {} ({}x{})", path, width, height); + } + + auto OpenGLCubemapTexture::convert_equirectangular_to_cubemap(float* hdr_data, int width, int height) -> void + { + uint32_t capture_fbo, capture_rbo; + glGenFramebuffers(1, &capture_fbo); + glGenRenderbuffers(1, &capture_rbo); + + glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo); + glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, m_width, m_height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, capture_rbo); + + uint32_t hdr_texture; + glGenTextures(1, &hdr_texture); + glBindTexture(GL_TEXTURE_2D, hdr_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, hdr_data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + auto equirect_shader = Shader::create("assets/shaders/EquirectToCubemap.glsl"); + if (!equirect_shader) + { + DONUT_ERROR("Failed to create equirectangular to cubemap shader"); + return; + } + + uint32_t shader_program = equirect_shader->get_renderer_id(); + + float vertices[] = + { + -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f + }; + + uint32_t cube_vao, cube_vbo; + glGenVertexArrays(1, &cube_vao); + glGenBuffers(1, &cube_vbo); + glBindVertexArray(cube_vao); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + + glm::mat4 capture_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); + glm::mat4 capture_views[] = + { + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) + }; + + glUseProgram(shader_program); + glUniform1i(glGetUniformLocation(shader_program, "u_EquirectangularMap"), 0); + // EquirectToCubemap is authored in Slang (row-major); transpose glm's + // column-major matrices on upload (GL_TRUE) to match. + glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_Projection"), 1, GL_TRUE, &capture_projection[0][0]); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, hdr_texture); + + glViewport(0, 0, m_width, m_height); + glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_View"), 1, GL_TRUE, &capture_views[i][0][0]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_renderer_id, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glBindVertexArray(cube_vao); + glDrawArrays(GL_TRIANGLES, 0, 36); + } + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1, &cube_vbo); + glDeleteTextures(1, &hdr_texture); + glDeleteFramebuffers(1, &capture_fbo); + glDeleteRenderbuffers(1, &capture_rbo); + } + + auto OpenGLCubemapTexture::set_data(void* data, uint32_t size) -> void + { + DONUT_WARN("set_data not implemented for cubemaps"); + } + + auto OpenGLCubemapTexture::bind(uint32_t slot) const -> void + { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_renderer_id); + } + + auto OpenGLCubemapTexture::bind_as_image(uint32_t slot, bool read_only) const -> void + { + if (glBindImageTexture == nullptr) + return; + GLenum access = read_only ? GL_READ_ONLY : GL_WRITE_ONLY; + glBindImageTexture(slot, m_renderer_id, 0, GL_TRUE, 0, access, m_internal_format); + } +}; diff --git a/src/platform/opengl/opengl_texture.h b/src/platform/opengl/opengl_texture.h new file mode 100644 index 0000000..7aa87ea --- /dev/null +++ b/src/platform/opengl/opengl_texture.h @@ -0,0 +1,68 @@ +#pragma once + +#include "rendering/texture.h" +#include "core/log.h" + +#include + +namespace Donut +{ + class OpenGLTexture2D + : public Texture2D + { + public: + OpenGLTexture2D(uint32_t width, uint32_t height); + OpenGLTexture2D(const std::string& path); + virtual ~OpenGLTexture2D(); + + virtual auto get_width() const -> uint32_t override{ return m_width; } + virtual auto get_height() const -> uint32_t override{ return m_height; } + virtual auto get_renderer_id() const -> uint32_t override{ return m_renderer_id; } + + virtual auto set_data(void* data, uint32_t size) -> void override; + virtual auto bind(uint32_t slot = 0) const -> void override; + virtual auto bind_as_image(uint32_t slot = 0, bool read_only = false) const -> void override; + + virtual bool operator==(const Texture& other) const override + { + return m_renderer_id == other.get_renderer_id(); + } + + private: + std::string m_path; + uint32_t m_width, m_height; + uint32_t m_renderer_id; + GLenum m_internal_format, m_data_format; + }; + + class OpenGLCubemapTexture + : public CubemapTexture + { + public: + OpenGLCubemapTexture(uint32_t width, uint32_t height); + OpenGLCubemapTexture(const std::string& path); + virtual ~OpenGLCubemapTexture(); + + virtual auto get_width() const -> uint32_t override{ return m_width; } + virtual auto get_height() const -> uint32_t override{ return m_height; } + virtual auto get_renderer_id() const -> uint32_t override{ return m_renderer_id; } + + virtual auto set_data(void* data, uint32_t size) -> void override; + virtual auto bind(uint32_t slot = 0) const -> void override; + virtual auto bind_as_image(uint32_t slot = 0, bool read_only = false) const -> void override; + + virtual bool operator==(const Texture& other) const override + { + return m_renderer_id == other.get_renderer_id(); + } + + private: + void LoadHDRI(const std::string& path); + auto convert_equirectangular_to_cubemap(float* hdr_data, int width, int height) -> void; + + std::string m_path; + uint32_t m_width, m_height; + uint32_t m_renderer_id; + GLenum m_internal_format, m_data_format; + }; +} diff --git a/src/platform/opengl/opengl_uniform_buffer.cpp b/src/platform/opengl/opengl_uniform_buffer.cpp new file mode 100644 index 0000000..9b3b6d2 --- /dev/null +++ b/src/platform/opengl/opengl_uniform_buffer.cpp @@ -0,0 +1,29 @@ +#include "opengl_uniform_buffer.h" + +namespace Donut +{ + OpenGLUniformBuffer::OpenGLUniformBuffer(uint32_t size, uint32_t binding) + : m_size(size), m_binding(binding) + { + glGenBuffers(1, &m_renderer_id); + glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); + glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); + } + + OpenGLUniformBuffer::~OpenGLUniformBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto OpenGLUniformBuffer::set_data(const void* data, uint32_t size, uint32_t offset) -> void + { + glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); + glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); + } + + auto OpenGLUniformBuffer::bind(uint32_t binding) -> void + { + glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); + } +}; diff --git a/src/platform/opengl/opengl_uniform_buffer.h b/src/platform/opengl/opengl_uniform_buffer.h new file mode 100644 index 0000000..db137a0 --- /dev/null +++ b/src/platform/opengl/opengl_uniform_buffer.h @@ -0,0 +1,21 @@ +#pragma once + +#include "rendering/uniform_buffer.h" +#include + +namespace Donut +{ + class OpenGLUniformBuffer : public UniformBuffer + { + public: + OpenGLUniformBuffer(uint32_t size, uint32_t binding); + virtual ~OpenGLUniformBuffer(); + + virtual auto set_data(const void* data, uint32_t size, uint32_t offset = 0) -> void override; + virtual auto bind(uint32_t binding) -> void override; + private: + uint32_t m_renderer_id = 0; + uint32_t m_size = 0; + uint32_t m_binding = 0; + }; +}; diff --git a/src/platform/opengl/opengl_vertex_array.cpp b/src/platform/opengl/opengl_vertex_array.cpp new file mode 100644 index 0000000..01e6f56 --- /dev/null +++ b/src/platform/opengl/opengl_vertex_array.cpp @@ -0,0 +1,58 @@ +#include + +#include "opengl_vertex_array.h" +#include "rendering/vertex_buffer.h" +#include "rendering/index_buffer.h" + +namespace Donut +{ + OpenGLVertexArray::OpenGLVertexArray() + { + // glCreateVertexArrays is 4.5 DSA; macOS caps at 4.1. glGenVertexArrays + // reserves the name and the VAO is created on first bind (done below). + glGenVertexArrays(1, &m_renderer_id); + } + + OpenGLVertexArray::~OpenGLVertexArray() + { + glDeleteVertexArrays(1, &m_renderer_id); + } + + auto OpenGLVertexArray::bind() const -> void + { + glBindVertexArray(m_renderer_id); + } + + auto OpenGLVertexArray::unbind() const -> void + { + glBindVertexArray(0); + } + + auto OpenGLVertexArray::add_vertex_buffer(const Ref& vertex_buffer) -> void + { + glBindVertexArray(m_renderer_id); + vertex_buffer->bind(); + + const auto& layout = vertex_buffer->get_layout(); + for (const auto& element : layout.get_elements()) + { + glEnableVertexAttribArray(m_vertex_buffer_index); + glVertexAttribPointer(m_vertex_buffer_index, + element.count, + element.type, + element.normalized ? GL_TRUE : GL_FALSE, + layout.get_stride(), + reinterpret_cast(static_cast(element.offset))); + m_vertex_buffer_index++; + } + + m_vertex_buffers.push_back(vertex_buffer); + } + + auto OpenGLVertexArray::set_index_buffer(const Ref& index_buffer) -> void + { + glBindVertexArray(m_renderer_id); + index_buffer->bind(); + m_index_buffer = index_buffer; + } +}; diff --git a/src/platform/opengl/opengl_vertex_array.h b/src/platform/opengl/opengl_vertex_array.h new file mode 100644 index 0000000..5115830 --- /dev/null +++ b/src/platform/opengl/opengl_vertex_array.h @@ -0,0 +1,41 @@ +#pragma once + +#include "core/memory.h" + +#include "rendering/vertex_array.h" +#include "rendering/vertex_buffer.h" +#include "rendering/index_buffer.h" + +#include + +namespace Donut +{ + class OpenGLVertexArray + : public VertexArray + { + public: + OpenGLVertexArray(); + virtual ~OpenGLVertexArray(); + + virtual auto bind() const -> void override; + virtual auto unbind() const -> void override; + + virtual auto add_vertex_buffer(const Ref& vertex_buffer) -> void override; + virtual auto set_index_buffer(const Ref& index_buffer) -> void override; + + virtual const std::vector>& get_vertex_buffers() const override + { + return m_vertex_buffers; + } + + virtual const Ref& get_index_buffer() const override + { + return m_index_buffer; + } + private: + uint32_t m_renderer_id; + uint32_t m_vertex_buffer_index = 0; + std::vector> m_vertex_buffers; + Ref m_index_buffer; + }; +}; diff --git a/src/platform/opengl/opengl_vertex_buffer.cpp b/src/platform/opengl/opengl_vertex_buffer.cpp new file mode 100644 index 0000000..5cd4b82 --- /dev/null +++ b/src/platform/opengl/opengl_vertex_buffer.cpp @@ -0,0 +1,35 @@ +#include "opengl_vertex_buffer.h" +#include "rendering/vertex_buffer.h" + +#include + +namespace Donut +{ + OpenGLVertexBuffer::OpenGLVertexBuffer(const void* data, uint32_t size) + { + glGenBuffers(1, &m_renderer_id); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 + glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); + glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); + } + + OpenGLVertexBuffer::~OpenGLVertexBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto OpenGLVertexBuffer::bind() const -> void + { + glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); + } + + auto OpenGLVertexBuffer::unbind() const -> void + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + auto OpenGLVertexBuffer::set_data(const void* data, uint32_t size) -> void + { + glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); + glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + } +}; diff --git a/src/platform/opengl/opengl_vertex_buffer.h b/src/platform/opengl/opengl_vertex_buffer.h new file mode 100644 index 0000000..df2e0e6 --- /dev/null +++ b/src/platform/opengl/opengl_vertex_buffer.h @@ -0,0 +1,25 @@ +#pragma once + +#include "rendering/vertex_buffer.h" + +namespace Donut +{ + class OpenGLVertexBuffer + : public VertexBuffer + { + public: + OpenGLVertexBuffer(const void* data, uint32_t size); + virtual ~OpenGLVertexBuffer(); + + virtual auto bind() const -> void override; + virtual auto unbind() const -> void override; + virtual auto set_data(const void* data, uint32_t size) -> void override; + + virtual auto get_layout() const -> const VertexBufferLayout& override{ return m_layout; } + virtual auto set_layout(const VertexBufferLayout& layout) -> void override{ m_layout = layout; } + + private: + uint32_t m_renderer_id; + VertexBufferLayout m_layout; + }; +}; -- cgit v1.3