diff options
Diffstat (limited to 'src')
50 files changed, 1477 insertions, 2332 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp index 4d21586..8708417 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -88,30 +88,45 @@ namespace Donut if (m_vulkan_renderer) { - bool free_fly = m_vulkan_renderer->is_free_fly(); - ImGui::Text("Camera"); - if (ImGui::RadioButton("Orbital", !free_fly)) m_vulkan_renderer->set_free_fly(false); + bool scene = m_vulkan_renderer->is_scene_mode(); + ImGui::Text("View"); + if (ImGui::RadioButton("Black hole", !scene)) m_vulkan_renderer->set_scene_mode(false); ImGui::SameLine(); - if (ImGui::RadioButton("Free-fly", free_fly)) m_vulkan_renderer->set_free_fly(true); - ImGui::TextDisabled(free_fly ? "WASD move | Q/E down-up | Shift boost | drag to look" - : "Drag to orbit | scroll to zoom"); + if (ImGui::RadioButton("Scene", scene)) m_vulkan_renderer->set_scene_mode(true); ImGui::Separator(); - auto& hdri = HDRIManager::get(); - std::string current = m_vulkan_renderer->current_hdri(); - std::string preview = hdri.get_hdri_name(current); - if (ImGui::BeginCombo("HDRI", preview.c_str())) + if (scene) { - for (const auto& path : hdri.get_available_hdri()) + ImGui::TextDisabled("World-builder scene (grid). Drag to orbit, scroll to zoom."); + ImGui::TextDisabled("Lit sphere + skybox coming next."); + } + else + { + bool free_fly = m_vulkan_renderer->is_free_fly(); + ImGui::Text("Camera"); + if (ImGui::RadioButton("Orbital", !free_fly)) m_vulkan_renderer->set_free_fly(false); + ImGui::SameLine(); + if (ImGui::RadioButton("Free-fly", free_fly)) m_vulkan_renderer->set_free_fly(true); + ImGui::TextDisabled(free_fly ? "WASD move | Q/E down-up | Shift boost | drag to look" + : "Drag to orbit | scroll to zoom"); + ImGui::Separator(); + + auto& hdri = HDRIManager::get(); + std::string current = m_vulkan_renderer->current_hdri(); + std::string preview = hdri.get_hdri_name(current); + if (ImGui::BeginCombo("HDRI", preview.c_str())) { - bool selected = (path == current); - if (ImGui::Selectable(hdri.get_hdri_name(path).c_str(), selected)) - m_vulkan_renderer->set_hdri(path); - if (selected) ImGui::SetItemDefaultFocus(); + for (const auto& path : hdri.get_available_hdri()) + { + bool selected = (path == current); + if (ImGui::Selectable(hdri.get_hdri_name(path).c_str(), selected)) + m_vulkan_renderer->set_hdri(path); + if (selected) ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); } - ImGui::EndCombo(); + ImGui::TextDisabled("Switching rebuilds the cubemap (brief pause)."); } - ImGui::TextDisabled("Switching rebuilds the cubemap (brief pause)."); } ImGui::End(); diff --git a/src/platform/opengl/opengl_framebuffer.cpp b/src/platform/opengl/opengl_framebuffer.cpp deleted file mode 100644 index 1624439..0000000 --- a/src/platform/opengl/opengl_framebuffer.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "opengl_framebuffer.h" -#include "core/log.h" - -#include <glad/glad.h> - -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<GLsizei>(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<GLsizei>(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<uint32_t>(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<int>(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<int>(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<GLsizei>(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<GLint>(attachment_index), &value); - } -}; diff --git a/src/platform/opengl/opengl_framebuffer.h b/src/platform/opengl/opengl_framebuffer.h deleted file mode 100644 index 456d924..0000000 --- a/src/platform/opengl/opengl_framebuffer.h +++ /dev/null @@ -1,35 +0,0 @@ -#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<FramebufferTextureSpecification> m_color_attachment_specifications; - FramebufferTextureSpecification m_depth_attachment_specification = FramebufferTextureFormat::None; - - std::vector<uint32_t> 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 deleted file mode 100644 index faa6b81..0000000 --- a/src/platform/opengl/opengl_index_buffer.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "opengl_index_buffer.h" -#include <glad/glad.h> - -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 deleted file mode 100644 index 397da43..0000000 --- a/src/platform/opengl/opengl_index_buffer.h +++ /dev/null @@ -1,22 +0,0 @@ -#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 deleted file mode 100644 index 39ed786..0000000 --- a/src/platform/opengl/opengl_renderer_api.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "opengl_renderer_api.h" - -#include <glad/glad.h> -#include <GLFW/glfw3.h> - -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<VertexArray>& 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<VertexArray>& 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 deleted file mode 100644 index 02f18ab..0000000 --- a/src/platform/opengl/opengl_renderer_api.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include "core/log.h" - -#include "rendering/renderer.h" - -#include <glad/glad.h> - -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<VertexArray>& 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<VertexArray>& 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 deleted file mode 100644 index d15d7e0..0000000 --- a/src/platform/opengl/opengl_shader.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include "opengl_shader.h" - -#include <glad/glad.h> -#include <glm/gtc/type_ptr.hpp> - -#include <fstream> -#include <iostream> - -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/ - // <name>.glsl by Tools/compile-shaders.sh. Given a legacy ".../<name>.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<uint32_t, std::string> 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<uint32_t, std::string> 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<uint32_t, std::string> - { - std::unordered_map<uint32_t, std::string> 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<uint32_t, std::string>& shader_sources) -> void - { - uint32_t program = glCreateProgram(); - std::vector<uint32_t> 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<char> 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<char> 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 deleted file mode 100644 index 289bfac..0000000 --- a/src/platform/opengl/opengl_shader.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "rendering/shader.h" - -#include <unordered_map> -#include <glm/glm.hpp> - -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<uint32_t, std::string>; - auto compile(const std::unordered_map<uint32_t, std::string>& 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 deleted file mode 100644 index 6bcb9cd..0000000 --- a/src/platform/opengl/opengl_texture.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include "opengl_texture.h" - -#include "rendering/shader.h" - -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" -#include <glm/glm.hpp> -#include <glm/gtc/matrix_transform.hpp> -#include <glm/gtc/type_ptr.hpp> - -// 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 deleted file mode 100644 index 7aa87ea..0000000 --- a/src/platform/opengl/opengl_texture.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "rendering/texture.h" -#include "core/log.h" - -#include <glad/glad.h> - -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 deleted file mode 100644 index 9b3b6d2..0000000 --- a/src/platform/opengl/opengl_uniform_buffer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#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 deleted file mode 100644 index db137a0..0000000 --- a/src/platform/opengl/opengl_uniform_buffer.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "rendering/uniform_buffer.h" -#include <glad/glad.h> - -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 deleted file mode 100644 index 01e6f56..0000000 --- a/src/platform/opengl/opengl_vertex_array.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include <glad/glad.h> - -#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<VertexBuffer>& 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<const void*>(static_cast<uintptr_t>(element.offset))); - m_vertex_buffer_index++; - } - - m_vertex_buffers.push_back(vertex_buffer); - } - - auto OpenGLVertexArray::set_index_buffer(const Ref<IndexBuffer>& 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 deleted file mode 100644 index 5115830..0000000 --- a/src/platform/opengl/opengl_vertex_array.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "core/memory.h" - -#include "rendering/vertex_array.h" -#include "rendering/vertex_buffer.h" -#include "rendering/index_buffer.h" - -#include <vector> - -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<VertexBuffer>& vertex_buffer) -> void override; - virtual auto set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void override; - - virtual const std::vector<Ref<VertexBuffer>>& get_vertex_buffers() const override - { - return m_vertex_buffers; - } - - virtual const Ref<IndexBuffer>& get_index_buffer() const override - { - return m_index_buffer; - } - private: - uint32_t m_renderer_id; - uint32_t m_vertex_buffer_index = 0; - std::vector<Ref<VertexBuffer>> m_vertex_buffers; - Ref<IndexBuffer> m_index_buffer; - }; -}; diff --git a/src/platform/opengl/opengl_vertex_buffer.cpp b/src/platform/opengl/opengl_vertex_buffer.cpp deleted file mode 100644 index 5cd4b82..0000000 --- a/src/platform/opengl/opengl_vertex_buffer.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "opengl_vertex_buffer.h" -#include "rendering/vertex_buffer.h" - -#include <glad/glad.h> - -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 deleted file mode 100644 index df2e0e6..0000000 --- a/src/platform/opengl/opengl_vertex_buffer.h +++ /dev/null @@ -1,25 +0,0 @@ -#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; - }; -}; diff --git a/src/platform/vulkan/vulkan_index_buffer.cpp b/src/platform/vulkan/vulkan_index_buffer.cpp deleted file mode 100644 index a6e5912..0000000 --- a/src/platform/vulkan/vulkan_index_buffer.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "vulkan_index_buffer.h" - -namespace Donut -{ - VulkanIndexBuffer::VulkanIndexBuffer(uint32_t* indices, uint32_t count) - : m_count(count) - { - // TODO(Hachem): Implement Vulkan index buffer creation - } - - VulkanIndexBuffer::~VulkanIndexBuffer() - { - // TODO(Hachem): Implement Vulkan index buffer cleanup - } - - auto VulkanIndexBuffer::bind() const -> void - { - // TODO(Hachem): Implement Vulkan index buffer binding - } - - auto VulkanIndexBuffer::unbind() const -> void - { - // TODO(Hachem): Implement Vulkan index buffer unbinding - } -}; diff --git a/src/platform/vulkan/vulkan_index_buffer.h b/src/platform/vulkan/vulkan_index_buffer.h deleted file mode 100644 index a0734a8..0000000 --- a/src/platform/vulkan/vulkan_index_buffer.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "rendering/index_buffer.h" - -namespace Donut -{ - class VulkanIndexBuffer - : public IndexBuffer - { - public: - VulkanIndexBuffer(uint32_t* indices, uint32_t count); - virtual ~VulkanIndexBuffer(); - - 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/vulkan/vulkan_renderer.cpp b/src/platform/vulkan/vulkan_renderer.cpp index 69d6501..cad0a44 100644 --- a/src/platform/vulkan/vulkan_renderer.cpp +++ b/src/platform/vulkan/vulkan_renderer.cpp @@ -121,6 +121,20 @@ namespace Donut VkPipelineLayout present_pipeline_layout = VK_NULL_HANDLE; VkPipeline present_pipeline = VK_NULL_HANDLE; + // World-builder scene view (grid now; sphere/skybox next). Normal-scale + // orbital camera; geometry drawn straight into the swapchain render pass. + bool scene_mode = false; + Camera scene_camera{ 45.0f, (float)GEO_W / (float)GEO_H, 0.1f, 1000.0f }; + VkBuffer grid_vb = VK_NULL_HANDLE; VkDeviceMemory grid_vb_mem = VK_NULL_HANDLE; + uint32_t grid_vertex_count = 0; + VkBuffer grid_ubo = VK_NULL_HANDLE; VkDeviceMemory grid_ubo_mem = VK_NULL_HANDLE; + void* grid_ubo_mapped = nullptr; + VkDescriptorSetLayout grid_set_layout = VK_NULL_HANDLE; + VkDescriptorPool grid_pool = VK_NULL_HANDLE; + VkDescriptorSet grid_set = VK_NULL_HANDLE; + VkPipelineLayout grid_pipeline_layout = VK_NULL_HANDLE; + VkPipeline grid_pipeline = VK_NULL_HANDLE; + auto find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags flags) const -> uint32_t; auto create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props, VkBuffer& buf, VkDeviceMemory& mem) const -> bool; static auto load_spirv(const std::string& path) -> std::vector<uint32_t>; @@ -138,8 +152,10 @@ namespace Donut auto create_hdri_cubemap(const char* path) -> bool; auto rebuild_hdri_cubemap(const char* path) -> void; auto create_present_resources() -> bool; + auto create_scene_resources() -> bool; auto process_input() -> void; auto update_geodesic_uniforms() -> void; + auto update_scene_uniforms() -> void; auto destroy_geodesic_resources() -> void; auto recreate_swapchain() -> bool; auto cleanup_swapchain() -> void; @@ -931,6 +947,120 @@ namespace Donut return true; } + auto VulkanRenderer::Impl::create_scene_resources() -> bool + { + const VkMemoryPropertyFlags host_vis = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + // Scene camera: normal-scale orbital viewer (matches the OpenGL world-builder). + scene_camera.set_camera_mode(CameraMode::Orbital); + scene_camera.set_orbital_target(glm::vec3(0.0f)); + scene_camera.set_orbital_radius(15.0); + scene_camera.set_orbital_limits(2.0, 200.0); + scene_camera.set_orbital_speed(0.01f); + scene_camera.set_zoom_speed(2.0); + scene_camera.set_azimuth(0.0f); + scene_camera.set_elevation((float)std::numbers::pi / 3.0f); + scene_camera.update_orbital(); + + // Line grid on the XZ plane (+/-50 units, 1-unit cells). Grid.slang scales + // the position by u_GridSize/50, so u_GridSize = 50 keeps it 1:1. + std::vector<glm::vec3> lines; + const int N = 50; + for (int i = -N; i <= N; ++i) + { + lines.push_back({ (float)i, 0.0f, (float)-N }); + lines.push_back({ (float)i, 0.0f, (float) N }); + lines.push_back({ (float)-N, 0.0f, (float)i }); + lines.push_back({ (float) N, 0.0f, (float)i }); + } + grid_vertex_count = (uint32_t)lines.size(); + VkDeviceSize vbsize = lines.size() * sizeof(glm::vec3); + if (!create_buffer(vbsize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, host_vis, grid_vb, grid_vb_mem)) return false; + void* mp = nullptr; vkMapMemory(device, grid_vb_mem, 0, vbsize, 0, &mp); memcpy(mp, lines.data(), vbsize); vkUnmapMemory(device, grid_vb_mem); + + // $Globals UBO (set 0, binding 0), std140, 176 bytes; refilled each frame. + if (!create_buffer(176, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, host_vis, grid_ubo, grid_ubo_mem)) return false; + vkMapMemory(device, grid_ubo_mem, 0, 176, 0, &grid_ubo_mapped); + + VkDescriptorSetLayoutBinding b{}; b.binding = 0; b.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; b.descriptorCount = 1; b.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo dslci{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; dslci.bindingCount = 1; dslci.pBindings = &b; + VK_CHECK(vkCreateDescriptorSetLayout(device, &dslci, nullptr, &grid_set_layout)); + VkDescriptorPoolSize psize{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }; + VkDescriptorPoolCreateInfo dpci{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; dpci.maxSets = 1; dpci.poolSizeCount = 1; dpci.pPoolSizes = &psize; + VK_CHECK(vkCreateDescriptorPool(device, &dpci, nullptr, &grid_pool)); + VkDescriptorSetAllocateInfo dsai{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; dsai.descriptorPool = grid_pool; dsai.descriptorSetCount = 1; dsai.pSetLayouts = &grid_set_layout; + VK_CHECK(vkAllocateDescriptorSets(device, &dsai, &grid_set)); + VkDescriptorBufferInfo bi{ grid_ubo, 0, VK_WHOLE_SIZE }; + VkWriteDescriptorSet w{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; w.dstSet = grid_set; w.dstBinding = 0; w.descriptorCount = 1; w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; w.pBufferInfo = &bi; + vkUpdateDescriptorSets(device, 1, &w, 0, nullptr); + + VkShaderModule vmod, fmod; + if (!create_shader_module("assets/shaders/generated/Grid.vertexMain.spv", vmod)) return false; + if (!create_shader_module("assets/shaders/generated/Grid.fragmentMain.spv", fmod)) return false; + VkPipelineLayoutCreateInfo plci{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; plci.setLayoutCount = 1; plci.pSetLayouts = &grid_set_layout; + VK_CHECK(vkCreatePipelineLayout(device, &plci, nullptr, &grid_pipeline_layout)); + VkPipelineShaderStageCreateInfo stages[2]{}; + stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stages[0].module = vmod; stages[0].pName = "main"; + stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stages[1].module = fmod; stages[1].pName = "main"; + VkVertexInputBindingDescription vib{ 0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX }; + VkVertexInputAttributeDescription via{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 }; + VkPipelineVertexInputStateCreateInfo vin{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + vin.vertexBindingDescriptionCount = 1; vin.pVertexBindingDescriptions = &vib; + vin.vertexAttributeDescriptionCount = 1; vin.pVertexAttributeDescriptions = &via; + VkPipelineInputAssemblyStateCreateInfo ia{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; ia.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + VkPipelineViewportStateCreateInfo vps{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; vps.viewportCount = 1; vps.scissorCount = 1; + VkDynamicState dyn[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dsci{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; dsci.dynamicStateCount = 2; dsci.pDynamicStates = dyn; + VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; rs.polygonMode = VK_POLYGON_MODE_FILL; rs.cullMode = VK_CULL_MODE_NONE; rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rs.lineWidth = 1.0f; + VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + VkPipelineColorBlendAttachmentState cba{}; + cba.blendEnable = VK_TRUE; + cba.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; cba.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; cba.colorBlendOp = VK_BLEND_OP_ADD; + cba.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; cba.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; cba.alphaBlendOp = VK_BLEND_OP_ADD; + cba.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + VkPipelineColorBlendStateCreateInfo cb{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; cb.attachmentCount = 1; cb.pAttachments = &cba; + VkGraphicsPipelineCreateInfo gpci{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + gpci.stageCount = 2; gpci.pStages = stages; + gpci.pVertexInputState = &vin; gpci.pInputAssemblyState = &ia; gpci.pViewportState = &vps; + gpci.pDynamicState = &dsci; + gpci.pRasterizationState = &rs; gpci.pMultisampleState = &ms; gpci.pColorBlendState = &cb; + gpci.layout = grid_pipeline_layout; gpci.renderPass = render_pass; gpci.subpass = 0; + VkResult pr = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpci, nullptr, &grid_pipeline); + vkDestroyShaderModule(device, vmod, nullptr); vkDestroyShaderModule(device, fmod, nullptr); + if (pr != VK_SUCCESS) { DONUT_ERROR("Vulkan: grid pipeline creation failed ({})", (int)pr); return false; } + DONUT_INFO("Vulkan: scene resources ready ({} grid verts)", grid_vertex_count); + return true; + } + + auto VulkanRenderer::Impl::update_scene_uniforms() -> void + { + struct GridUBO { + glm::mat4 view_projection; // 0 (SPIR-V RowMajor -> upload transposed) + glm::mat4 transform; // 64 + float grid_size; float p0[3]; // 128 + glm::vec3 grid_color; // 144 + float grid_alpha; // 156 + glm::vec3 camera_pos; // 160 + float p1; // 172 + } g{}; + static_assert(sizeof(GridUBO) == 176, "GridUBO std140 layout mismatch"); + + uint32_t h = swapchain_extent.height ? swapchain_extent.height : 1; + float aspect = (float)swapchain_extent.width / (float)h; + scene_camera.set_projection(45.0f, aspect, 0.1f, 1000.0f); + glm::mat4 vp = scene_camera.get_projection_matrix() * scene_camera.get_view_matrix(); + + // Slang's mul(M,v) + the SPIR-V RowMajor decoration means glm's column-major + // matrices upload directly here (no transpose) to read as the intended M. + g.view_projection = vp; + g.transform = glm::mat4(1.0f); + g.grid_size = 50.0f; + g.grid_color = glm::vec3(0.55f, 0.55f, 0.6f); + g.grid_alpha = 0.75f; + g.camera_pos = scene_camera.get_orbital_position(); + if (grid_ubo_mapped) memcpy(grid_ubo_mapped, &g, sizeof(g)); + } + auto VulkanRenderer::Impl::update_geodesic_uniforms() -> void { struct CamUBO { @@ -984,7 +1114,7 @@ namespace Donut glfwGetCursorPos(window, &mx, &my); bool left_down = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; - if (camera.get_camera_mode() == CameraMode::FPS) + if (!scene_mode && camera.get_camera_mode() == CameraMode::FPS) { // Left-drag looks around (screen-up looks up); WASD/QE move. if (left_down && !left_was_down) { fps_last_x = mx; fps_last_y = my; } @@ -1010,24 +1140,35 @@ namespace Donut } else { + Camera& cam = scene_mode ? scene_camera : camera; if (left_down && !left_was_down && !over_ui) - camera.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, 0); + cam.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, 0); else if (!left_down && left_was_down) - camera.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, 0); + cam.process_orbital_mouse_button(GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, 0); left_was_down = left_down; - camera.process_orbital_mouse_move(mx, my); // orbits only while dragging + cam.process_orbital_mouse_move(mx, my); // orbits only while dragging double scroll = g_ScrollAccum; g_ScrollAccum = 0.0; if (scroll != 0.0 && !over_ui) - camera.process_orbital_scroll(0.0, scroll); + cam.process_orbital_scroll(0.0, scroll); - user_moving = camera.is_dragging() || camera.is_panning(); + user_moving = cam.is_dragging() || cam.is_panning(); } } auto VulkanRenderer::Impl::destroy_geodesic_resources() -> void { + if (grid_pipeline) vkDestroyPipeline(device, grid_pipeline, nullptr); + if (grid_pipeline_layout) vkDestroyPipelineLayout(device, grid_pipeline_layout, nullptr); + if (grid_pool) vkDestroyDescriptorPool(device, grid_pool, nullptr); + if (grid_set_layout) vkDestroyDescriptorSetLayout(device, grid_set_layout, nullptr); + if (grid_ubo_mapped) { vkUnmapMemory(device, grid_ubo_mem); grid_ubo_mapped = nullptr; } + if (grid_ubo) vkDestroyBuffer(device, grid_ubo, nullptr); + if (grid_ubo_mem) vkFreeMemory(device, grid_ubo_mem, nullptr); + if (grid_vb) vkDestroyBuffer(device, grid_vb, nullptr); + if (grid_vb_mem) vkFreeMemory(device, grid_vb_mem, nullptr); + if (present_pipeline) vkDestroyPipeline(device, present_pipeline, nullptr); if (present_pipeline_layout) vkDestroyPipelineLayout(device, present_pipeline_layout, nullptr); if (present_pool) vkDestroyDescriptorPool(device, present_pool, nullptr); @@ -1061,6 +1202,31 @@ namespace Donut VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; VK_CHECK(vkBeginCommandBuffer(cmd, &begin)); + if (scene_mode) + { + // Scene view: grid lines drawn straight into the swapchain, then ImGui. + VkClearValue cv{}; cv.color = { { clear.r, clear.g, clear.b, clear.a } }; + VkRenderPassBeginInfo rpbi{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; + rpbi.renderPass = render_pass; rpbi.framebuffer = framebuffers[image_index]; + rpbi.renderArea = { { 0, 0 }, swapchain_extent }; + rpbi.clearValueCount = 1; rpbi.pClearValues = &cv; + vkCmdBeginRenderPass(cmd, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, grid_pipeline); + // Negative-height viewport flips Y so the scene reads like the GL path. + VkViewport gvp{ 0, (float)swapchain_extent.height, (float)swapchain_extent.width, -(float)swapchain_extent.height, 0, 1 }; + VkRect2D gsc{ { 0, 0 }, swapchain_extent }; + vkCmdSetViewport(cmd, 0, 1, &gvp); + vkCmdSetScissor(cmd, 0, 1, &gsc); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, grid_pipeline_layout, 0, 1, &grid_set, 0, nullptr); + VkDeviceSize goff = 0; vkCmdBindVertexBuffers(cmd, 0, 1, &grid_vb, &goff); + vkCmdDraw(cmd, grid_vertex_count, 1, 0, 0); + if (draw_data) + ImGui_ImplVulkan_RenderDrawData(draw_data, cmd); + vkCmdEndRenderPass(cmd); + VK_CHECK(vkEndCommandBuffer(cmd)); + return true; + } + // Geodesic offscreen pass VkClearValue geo_clear{}; geo_clear.color = { { 0, 0, 0, 1 } }; VkRenderPassBeginInfo grp{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; @@ -1153,6 +1319,7 @@ namespace Donut if (!v.create_sync_objects()) return false; if (!v.create_geodesic_resources()) return false; if (!v.create_present_resources()) return false; + if (!v.create_scene_resources()) return false; DONUT_INFO("Vulkan renderer ready: {} swapchain images, {}x{}", (int)v.images.size(), v.swapchain_extent.width, v.swapchain_extent.height); @@ -1253,6 +1420,16 @@ namespace Donut return m_impl->camera.get_camera_mode() == CameraMode::FPS; } + auto VulkanRenderer::set_scene_mode(bool enabled) -> void + { + if (m_impl) m_impl->scene_mode = enabled; + } + + auto VulkanRenderer::is_scene_mode() const -> bool + { + return m_impl && m_impl->scene_mode; + } + auto VulkanRenderer::draw_frame(const glm::vec4& clear_color, const std::function<void()>& build_ui) -> void { Impl& v = *m_impl; @@ -1286,7 +1463,7 @@ namespace Donut if (v.geo_in_use != VK_NULL_HANDLE) vkWaitForFences(v.device, 1, &v.geo_in_use, VK_TRUE, UINT64_MAX); v.process_input(); - v.update_geodesic_uniforms(); + if (v.scene_mode) v.update_scene_uniforms(); else v.update_geodesic_uniforms(); vkResetCommandBuffer(v.command_buffers[v.current_frame], 0); if (!v.record_command_buffer(v.command_buffers[v.current_frame], image_index, clear_color, draw_data)) return; diff --git a/src/platform/vulkan/vulkan_renderer.h b/src/platform/vulkan/vulkan_renderer.h index 0e643c1..e6920c5 100644 --- a/src/platform/vulkan/vulkan_renderer.h +++ b/src/platform/vulkan/vulkan_renderer.h @@ -47,6 +47,11 @@ namespace Donut auto set_free_fly(bool enabled) -> void; auto is_free_fly() const -> bool; + // Switch between the black-hole geodesic view and the world-builder scene + // view (grid/sphere/skybox drawn with real Vulkan geometry pipelines). + auto set_scene_mode(bool enabled) -> void; + auto is_scene_mode() const -> bool; + private: struct Impl; Impl* m_impl = nullptr; diff --git a/src/platform/vulkan/vulkan_renderer_api.cpp b/src/platform/vulkan/vulkan_renderer_api.cpp deleted file mode 100644 index 56499f4..0000000 --- a/src/platform/vulkan/vulkan_renderer_api.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "vulkan_renderer_api.h" - -namespace Donut -{ - auto VulkanRendererAPI::init() -> void - { - // TODO(Hachem): Implement Vulkan renderer API initialization - } - - auto VulkanRendererAPI::set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void - { - // TODO(Hachem): Implement Vulkan viewport setting - } - - auto VulkanRendererAPI::set_clear_color(const glm::vec4& color) -> void - { - // TODO(Hachem): Implement Vulkan clear color setting - } - - auto VulkanRendererAPI::clear() -> void - { - // TODO(Hachem): Implement Vulkan clear - } - - auto VulkanRendererAPI::enable_depth_test() -> void - { - // TODO(Hachem): Implement Vulkan depth test enabling - } - - auto VulkanRendererAPI::disable_depth_test() -> void - { - // TODO(Hachem): Implement Vulkan depth test disabling - } - - auto VulkanRendererAPI::set_face_culling(bool enabled) -> void - { - // TODO(Hachem): Implement Vulkan face culling setting - } - - auto VulkanRendererAPI::enable_blending() -> void - { - // TODO(Hachem): Implement Vulkan blending enabling - } - - auto VulkanRendererAPI::disable_blending() -> void - { - // TODO(Hachem): Implement Vulkan blending disabling - } - - auto VulkanRendererAPI::draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count) -> void - { - // TODO(Hachem): Implement Vulkan indexed drawing - } - - auto VulkanRendererAPI::draw_arrays(uint32_t vertex_count, uint32_t first) -> void - { - // TODO(Hachem): Implement Vulkan array drawing - } - - auto VulkanRendererAPI::draw_lines(const Ref<VertexArray>& vertex_array, uint32_t index_count) -> void - { - // TODO(Hachem): Implement Vulkan line drawing - } - - auto VulkanRendererAPI::bind_texture(uint32_t texture_id, uint32_t slot) -> void - { - // TODO(Hachem): Implement Vulkan texture binding - } - - auto VulkanRendererAPI::bind_image_texture(uint32_t texture_id, uint32_t slot, bool read_only) -> void - { - // TODO(Hachem): Implement Vulkan image texture binding - } - - auto VulkanRendererAPI::read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, - uint32_t format, uint32_t type, void* pixels) -> void - { - // TODO(Hachem): Implement Vulkan pixel reading - // For now, this is a placeholder implementation - // In a real Vulkan implementation, this would involve: - // 1. Creating a staging buffer - // 2. Copying the framebuffer to the staging buffer - // 3. Mapping the staging buffer and copying to the pixels array - // 4. Unmapping and destroying the staging buffer - } -}; diff --git a/src/platform/vulkan/vulkan_renderer_api.h b/src/platform/vulkan/vulkan_renderer_api.h deleted file mode 100644 index 15bf76d..0000000 --- a/src/platform/vulkan/vulkan_renderer_api.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "core/memory.h" -#include "rendering/renderer.h" - -namespace Donut -{ - class VulkanRendererAPI - : 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<VertexArray>& 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<VertexArray>& 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/vulkan/vulkan_shader.cpp b/src/platform/vulkan/vulkan_shader.cpp deleted file mode 100644 index 7549761..0000000 --- a/src/platform/vulkan/vulkan_shader.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "vulkan_shader.h" - -#include <fstream> -#include <glm/gtc/type_ptr.hpp> - -namespace Donut -{ - VulkanShader::VulkanShader(const std::string& filepath) - { - // TODO(Hachem): Implement Vulkan shader creation from filepath - } - - VulkanShader::VulkanShader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) - : m_name(name) - { - // TODO(Hachem): Implement Vulkan shader creation from source - } - - VulkanShader::VulkanShader(const std::string& name, const std::string& compute_src) - : m_name(name) - { - // TODO(Hachem): Implement Vulkan compute shader creation - } - - VulkanShader::~VulkanShader() - { - // TODO(Hachem): Implement Vulkan shader cleanup - } - - auto VulkanShader::bind() const -> void - { - // TODO(Hachem): Implement Vulkan shader binding - } - - auto VulkanShader::unbind() const -> void - { - // TODO(Hachem): Implement Vulkan shader unbinding - } - - auto VulkanShader::set_int(const std::string& name, int value) -> void - { - // TODO(Hachem): Implement Vulkan shader int uniform setting - } - - auto VulkanShader::set_int_array(const std::string& name, int* values, uint32_t count) -> void - { - // TODO(Hachem): Implement Vulkan shader int array uniform setting - } - - auto VulkanShader::set_float(const std::string& name, float value) -> void - { - // TODO(Hachem): Implement Vulkan shader float uniform setting - } - - auto VulkanShader::set_float2(const std::string& name, const glm::vec2& value) -> void - { - // TODO(Hachem): Implement Vulkan shader float2 uniform setting - } - - auto VulkanShader::set_float3(const std::string& name, const glm::vec3& value) -> void - { - // TODO(Hachem): Implement Vulkan shader float3 uniform setting - } - - auto VulkanShader::set_float4(const std::string& name, const glm::vec4& value) -> void - { - // TODO(Hachem): Implement Vulkan shader float4 uniform setting - } - - auto VulkanShader::set_mat4(const std::string& name, const glm::mat4& value) -> void - { - // TODO(Hachem): Implement Vulkan shader mat4 uniform setting - } - - auto VulkanShader::dispatch(uint32_t x, uint32_t y, uint32_t z) -> void - { - // TODO(Hachem): Implement Vulkan compute shader dispatch - } - - auto VulkanShader::dispatch_indirect(uint32_t offset) -> void - { - // TODO(Hachem): Implement Vulkan indirect compute shader dispatch - } - - auto VulkanShader::memory_barrier(uint32_t barriers) -> void - { - // TODO(Hachem): Implement Vulkan memory barrier - } - - auto VulkanShader::upload_uniform_int(const std::string& name, int value) -> void - { - // TODO(Hachem): Implement Vulkan uniform int upload - } - - auto VulkanShader::upload_uniform_int_array(const std::string& name, int* values, uint32_t count) -> void - { - // TODO(Hachem): Implement Vulkan uniform int array upload - } - - auto VulkanShader::upload_uniform_float(const std::string& name, float value) -> void - { - // TODO(Hachem): Implement Vulkan uniform float upload - } - - auto VulkanShader::upload_uniform_float2(const std::string& name, const glm::vec2& value) -> void - { - // TODO(Hachem): Implement Vulkan uniform float2 upload - } - - auto VulkanShader::upload_uniform_float3(const std::string& name, const glm::vec3& value) -> void - { - // TODO(Hachem): Implement Vulkan uniform float3 upload - } - - auto VulkanShader::upload_uniform_float4(const std::string& name, const glm::vec4& value) -> void - { - // TODO(Hachem): Implement Vulkan uniform float4 upload - } - - auto VulkanShader::upload_uniform_mat3(const std::string& name, const glm::mat3& matrix) -> void - { - // TODO(Hachem): Implement Vulkan uniform mat3 upload - } - - auto VulkanShader::upload_uniform_mat4(const std::string& name, const glm::mat4& matrix) -> void - { - // TODO(Hachem): Implement Vulkan uniform mat4 upload - } - - auto VulkanShader::read_file(const std::string& filepath) -> std::string - { - // TODO(Hachem): Implement file reading for Vulkan shader - return ""; - } - - auto VulkanShader::pre_process(const std::string& source) -> std::unordered_map<uint32_t, std::string> - { - // TODO(Hachem): Implement shader preprocessing for Vulkan - return {}; - } - - auto VulkanShader::compile(const std::unordered_map<uint32_t, std::string>& shader_sources) -> void - { - // TODO(Hachem): Implement Vulkan shader compilation - } -}; diff --git a/src/platform/vulkan/vulkan_shader.h b/src/platform/vulkan/vulkan_shader.h deleted file mode 100644 index 9453130..0000000 --- a/src/platform/vulkan/vulkan_shader.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "rendering/shader.h" - -#include <string> -#include <unordered_map> - -namespace Donut -{ - class VulkanShader - : public Shader - { - public: - VulkanShader(const std::string& filepath); - VulkanShader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); - VulkanShader(const std::string& name, const std::string& compute_src); - virtual ~VulkanShader(); - - 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<uint32_t, std::string>; - auto compile(const std::unordered_map<uint32_t, std::string>& shader_sources) -> void; - uint32_t m_renderer_id; - std::string m_name; - }; -}; diff --git a/src/platform/vulkan/vulkan_texture.cpp b/src/platform/vulkan/vulkan_texture.cpp deleted file mode 100644 index a970a53..0000000 --- a/src/platform/vulkan/vulkan_texture.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "vulkan_texture.h" - -namespace Donut -{ - VulkanTexture2D::VulkanTexture2D(uint32_t width, uint32_t height) - : m_width(width), m_height(height) - { - m_internal_format = 0; - m_data_format = 0; - m_renderer_id = 0; - } - - VulkanTexture2D::VulkanTexture2D(const std::string& path) - : m_path(path) - { - m_width = 1; - m_height = 1; - m_internal_format = 0; - m_data_format = 0; - m_renderer_id = 0; - } - - VulkanTexture2D::~VulkanTexture2D() - { - } - - auto VulkanTexture2D::set_data(void* data, uint32_t size) -> void - { - } - - auto VulkanTexture2D::bind(uint32_t slot) const -> void - { - } - - auto VulkanTexture2D::bind_as_image(uint32_t slot, bool read_only) const -> void - { - } - - // Vulkan Cubemap Implementation (Placeholder) - VulkanCubemapTexture::VulkanCubemapTexture(uint32_t width, uint32_t height) - : m_width(width), m_height(height) - { - m_internal_format = 0; - m_data_format = 0; - m_renderer_id = 0; - } - - VulkanCubemapTexture::VulkanCubemapTexture(const std::string& path) - : m_path(path) - { - m_width = 1024; - m_height = 1024; - m_internal_format = 0; - m_data_format = 0; - m_renderer_id = 0; - } - - VulkanCubemapTexture::~VulkanCubemapTexture() - { - } - - auto VulkanCubemapTexture::set_data(void* data, uint32_t size) -> void - { - } - - auto VulkanCubemapTexture::bind(uint32_t slot) const -> void - { - } - - auto VulkanCubemapTexture::bind_as_image(uint32_t slot, bool read_only) const -> void - { - } -}; diff --git a/src/platform/vulkan/vulkan_texture.h b/src/platform/vulkan/vulkan_texture.h deleted file mode 100644 index ea8471b..0000000 --- a/src/platform/vulkan/vulkan_texture.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "rendering/texture.h" - -namespace Donut -{ - class VulkanTexture2D - : public Texture2D - { - public: - VulkanTexture2D(uint32_t width, uint32_t height); - VulkanTexture2D(const std::string& path); - virtual ~VulkanTexture2D(); - - 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 == ((VulkanTexture2D&)other).m_renderer_id; - } - private: - std::string m_path; - uint32_t m_width, m_height; - uint32_t m_renderer_id; - uint32_t m_internal_format, m_data_format; - }; - - class VulkanCubemapTexture - : public CubemapTexture - { - public: - VulkanCubemapTexture(uint32_t width, uint32_t height); - VulkanCubemapTexture(const std::string& path); - virtual ~VulkanCubemapTexture(); - - 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 == ((VulkanCubemapTexture&)other).m_renderer_id; - } - private: - std::string m_path; - uint32_t m_width, m_height; - uint32_t m_renderer_id; - uint32_t m_internal_format, m_data_format; - }; -}; diff --git a/src/platform/vulkan/vulkan_uniform_buffer.cpp b/src/platform/vulkan/vulkan_uniform_buffer.cpp deleted file mode 100644 index 1a64099..0000000 --- a/src/platform/vulkan/vulkan_uniform_buffer.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "vulkan_uniform_buffer.h" - -namespace Donut -{ - VulkanUniformBuffer::VulkanUniformBuffer(uint32_t size, uint32_t binding) - : m_size(size), m_binding(binding) - { - // TODO: Implement Vulkan uniform buffer - } - - VulkanUniformBuffer::~VulkanUniformBuffer() - { - // TODO: Implement Vulkan uniform buffer cleanup - } - - auto VulkanUniformBuffer::set_data(const void* data, uint32_t size, uint32_t offset) -> void - { - // TODO: Implement Vulkan uniform buffer data setting - } - - auto VulkanUniformBuffer::bind(uint32_t binding) -> void - { - // TODO: Implement Vulkan uniform buffer binding - } -}; diff --git a/src/platform/vulkan/vulkan_uniform_buffer.h b/src/platform/vulkan/vulkan_uniform_buffer.h deleted file mode 100644 index 7d9ad0a..0000000 --- a/src/platform/vulkan/vulkan_uniform_buffer.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "rendering/uniform_buffer.h" - -namespace Donut -{ - class VulkanUniformBuffer : public UniformBuffer - { - public: - VulkanUniformBuffer(uint32_t size, uint32_t binding); - virtual ~VulkanUniformBuffer(); - - 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_size; - uint32_t m_binding; - }; -}; diff --git a/src/platform/vulkan/vulkan_vertex_array.cpp b/src/platform/vulkan/vulkan_vertex_array.cpp deleted file mode 100644 index 73219b1..0000000 --- a/src/platform/vulkan/vulkan_vertex_array.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "vulkan_vertex_array.h" - -namespace Donut -{ - VulkanVertexArray::VulkanVertexArray() - { - // TODO(Hachem): Implement Vulkan vertex array creation - } - - VulkanVertexArray::~VulkanVertexArray() - { - // TODO(Hachem): Implement Vulkan vertex array cleanup - } - - auto VulkanVertexArray::bind() const -> void - { - // TODO(Hachem): Implement Vulkan vertex array binding - } - - auto VulkanVertexArray::unbind() const -> void - { - // TODO(Hachem): Implement Vulkan vertex array unbinding - } - - auto VulkanVertexArray::add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void - { - // TODO(Hachem): Implement Vulkan vertex buffer addition - m_vertex_buffers.push_back(vertex_buffer); - } - - auto VulkanVertexArray::set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void - { - // TODO(Hachem): Implement Vulkan index buffer setting - m_index_buffer = index_buffer; - } -}; diff --git a/src/platform/vulkan/vulkan_vertex_array.h b/src/platform/vulkan/vulkan_vertex_array.h deleted file mode 100644 index 0d282a2..0000000 --- a/src/platform/vulkan/vulkan_vertex_array.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "rendering/vertex_array.h" -#include "core/memory.h" - -namespace Donut -{ - class VulkanVertexArray - : public VertexArray - { - public: - VulkanVertexArray(); - virtual ~VulkanVertexArray(); - - virtual auto bind() const -> void override; - virtual auto unbind() const -> void override; - - virtual auto add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void override; - virtual auto set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void override; - - virtual auto get_vertex_buffers() const -> const std::vector<Ref<VertexBuffer>>& { return m_vertex_buffers; } - virtual auto get_index_buffer() const -> const Ref<IndexBuffer>& { return m_index_buffer; } - private: - uint32_t m_renderer_id; - std::vector<Ref<VertexBuffer>> m_vertex_buffers; - Ref<IndexBuffer> m_index_buffer; - }; -}; diff --git a/src/platform/vulkan/vulkan_vertex_buffer.cpp b/src/platform/vulkan/vulkan_vertex_buffer.cpp deleted file mode 100644 index 6a222c1..0000000 --- a/src/platform/vulkan/vulkan_vertex_buffer.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "vulkan_vertex_buffer.h" - -namespace Donut -{ - VulkanVertexBuffer::VulkanVertexBuffer(uint32_t size) - { - // TODO(Hachem): Implement Vulkan vertex buffer creation with size - } - - VulkanVertexBuffer::VulkanVertexBuffer(float* vertices, uint32_t size) - { - // TODO(Hachem): Implement Vulkan vertex buffer creation with data - } - - VulkanVertexBuffer::~VulkanVertexBuffer() - { - // TODO(Hachem): Implement Vulkan vertex buffer cleanup - } - - auto VulkanVertexBuffer::bind() const -> void - { - // TODO(Hachem): Implement Vulkan vertex buffer binding - } - - auto VulkanVertexBuffer::unbind() const -> void - { - // TODO(Hachem): Implement Vulkan vertex buffer unbinding - } - - auto VulkanVertexBuffer::set_data(const void* data, uint32_t size) -> void - { - // TODO(Hachem): Implement Vulkan vertex buffer data setting - } -};
\ No newline at end of file diff --git a/src/platform/vulkan/vulkan_vertex_buffer.h b/src/platform/vulkan/vulkan_vertex_buffer.h deleted file mode 100644 index 3218021..0000000 --- a/src/platform/vulkan/vulkan_vertex_buffer.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "rendering/vertex_buffer.h" - -namespace Donut -{ - class VulkanVertexBuffer - : public VertexBuffer - { - public: - VulkanVertexBuffer(uint32_t size); - VulkanVertexBuffer(float* vertices, uint32_t size); - virtual ~VulkanVertexBuffer(); - - 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; - }; -}; diff --git a/src/rendering/framebuffer.cpp b/src/rendering/framebuffer.cpp index 089c31e..3da4b48 100644 --- a/src/rendering/framebuffer.cpp +++ b/src/rendering/framebuffer.cpp @@ -1,17 +1,251 @@ #include "framebuffer.h" -#include "renderer.h" +#include "core/log.h" -#include "platform/opengl/opengl_framebuffer.h" +#include <glad/glad.h> namespace Donut { - auto Framebuffer::create(const FramebufferSpecification& spec) -> Ref<Framebuffer> + 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; + } + } + + Framebuffer::Framebuffer(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(); + } + + Framebuffer::~Framebuffer() + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast<GLsizei>(m_color_attachments.size()), m_color_attachments.data()); + glDeleteTextures(1, &m_depth_attachment); + } + + auto Framebuffer::invalidate() -> void { - switch (Renderer::get_api()) + if (m_renderer_id) + { + glDeleteFramebuffers(1, &m_renderer_id); + glDeleteTextures(static_cast<GLsizei>(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()) { - case RendererAPI::API::OpenGL: return create_ref<OpenGLFramebuffer>(spec); + m_color_attachments.resize(m_color_attachment_specifications.size()); + Utils::CreateTextures(multisample, m_color_attachments.data(), static_cast<uint32_t>(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<int>(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<int>(i)); + break; + } + } } - return nullptr; + 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<GLsizei>(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 Framebuffer::bind() -> void + { + glBindFramebuffer(GL_FRAMEBUFFER, m_renderer_id); + glViewport(0, 0, m_specification.Width, m_specification.Height); + } + + auto Framebuffer::unbind() -> void + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + auto Framebuffer::resize(uint32_t width, uint32_t height) -> void + { + m_specification.Width = width; + m_specification.Height = height; + + invalidate(); + } + + auto Framebuffer::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 Framebuffer::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<GLint>(attachment_index), &value); + } + + auto Framebuffer::create(const FramebufferSpecification& spec) -> Ref<Framebuffer> + { + return create_ref<Framebuffer>(spec); } }; diff --git a/src/rendering/framebuffer.h b/src/rendering/framebuffer.h index a01a81b..cf1fbef 100644 --- a/src/rendering/framebuffer.h +++ b/src/rendering/framebuffer.h @@ -3,17 +3,19 @@ #include "core/memory.h" #include "texture.h" +#include <vector> + namespace Donut { enum class FramebufferTextureFormat { None = 0, - + RGBA8, RED_INTEGER, - + DEPTH24STENCIL8, - + Depth = DEPTH24STENCIL8 }; @@ -47,19 +49,32 @@ namespace Donut class Framebuffer { public: - virtual ~Framebuffer() = default; + Framebuffer(const FramebufferSpecification& spec); + ~Framebuffer(); - virtual auto bind() -> void = 0; - virtual auto unbind() -> void = 0; + auto bind() -> void; + auto unbind() -> void; - virtual auto resize(uint32_t width, uint32_t height) -> void = 0; - virtual auto read_pixel(uint32_t attachment_index, int x, int y) -> int = 0; + auto resize(uint32_t width, uint32_t height) -> void; + auto read_pixel(uint32_t attachment_index, int x, int y) -> int; - virtual auto clear_attachment(uint32_t attachment_index, int value) -> void = 0; - virtual auto get_color_attachment_renderer_id(uint32_t index = 0) const -> uint32_t = 0; + auto clear_attachment(uint32_t attachment_index, int value) -> void; + auto get_color_attachment_renderer_id(uint32_t index = 0) const -> uint32_t { return m_color_attachments[index]; } - virtual auto get_specification() const -> const FramebufferSpecification& = 0; + auto get_specification() const -> const FramebufferSpecification& { return m_specification; } static auto create(const FramebufferSpecification& spec) -> Ref<Framebuffer>; + + private: + auto invalidate() -> void; + + uint32_t m_renderer_id = 0; + FramebufferSpecification m_specification; + + std::vector<FramebufferTextureSpecification> m_color_attachment_specifications; + FramebufferTextureSpecification m_depth_attachment_specification = FramebufferTextureFormat::None; + + std::vector<uint32_t> m_color_attachments; + uint32_t m_depth_attachment = 0; }; }; diff --git a/src/rendering/index_buffer.cpp b/src/rendering/index_buffer.cpp index 9603c72..2c933e0 100644 --- a/src/rendering/index_buffer.cpp +++ b/src/rendering/index_buffer.cpp @@ -1,21 +1,34 @@ #include "index_buffer.h" -#include "renderer.h" -#include "platform/opengl/opengl_index_buffer.h" -#include "platform/vulkan/vulkan_index_buffer.h" +#include <glad/glad.h> -namespace Donut +namespace Donut { + IndexBuffer::IndexBuffer(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); + } + + IndexBuffer::~IndexBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto IndexBuffer::bind() const -> void + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_renderer_id); + } + + auto IndexBuffer::unbind() const -> void + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + auto IndexBuffer::create(const uint32_t* indices, uint32_t count) -> IndexBuffer* { - switch (Renderer::get_api()) - { - case RendererAPI::API::OpenGL: - return new OpenGLIndexBuffer(indices, count); - case RendererAPI::API::Vulkan: - return new VulkanIndexBuffer((uint32_t*)indices, count); - default: - return nullptr; - } + return new IndexBuffer(indices, count); } }; diff --git a/src/rendering/index_buffer.h b/src/rendering/index_buffer.h index 8389f2d..0b1b2b6 100644 --- a/src/rendering/index_buffer.h +++ b/src/rendering/index_buffer.h @@ -7,12 +7,17 @@ namespace Donut class IndexBuffer { public: - virtual ~IndexBuffer() = default; + IndexBuffer(const uint32_t* indices, uint32_t count); + ~IndexBuffer(); - virtual auto bind() const -> void = 0; - virtual auto unbind() const -> void = 0; - virtual auto get_count() const -> uint32_t = 0; + auto bind() const -> void; + auto unbind() const -> void; + auto get_count() const -> uint32_t { return m_count; } static auto create(const uint32_t* indices, uint32_t count) -> IndexBuffer*; + + private: + uint32_t m_renderer_id = 0; + uint32_t m_count = 0; }; }; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 28b7962..1c82447 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1,26 +1,130 @@ #include "renderer.h" -#include "platform/opengl/opengl_renderer_api.h" -#include "platform/vulkan/vulkan_renderer_api.h" +#include "core/log.h" +#include <glad/glad.h> +#include <GLFW/glfw3.h> #include <glm/gtc/matrix_transform.hpp> -namespace Donut +namespace Donut { + RendererAPI::API RendererAPI::s_api = RendererAPI::API::OpenGL; + auto RendererAPI::create() -> Scope<RendererAPI> - { - switch (s_api) + { + return create_scope<RendererAPI>(); + } + + auto RendererAPI::init() -> void + { + if (!glfwGetCurrentContext()) + { + DONUT_ERROR("No OpenGL context is current! Cannot initialize GLAD."); + return; + } + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - case API::OpenGL: - return create_scope<OpenGLRendererAPI>(); - case API::Vulkan: - return create_scope<VulkanRendererAPI>(); - default: - return nullptr; + 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); } - RendererAPI::API RendererAPI::s_api = RendererAPI::API::OpenGL; + auto RendererAPI::set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void + { + glViewport(x, y, width, height); + } + + auto RendererAPI::set_clear_color(const glm::vec4& color) -> void + { + glClearColor(color.r, color.g, color.b, color.a); + } + + auto RendererAPI::clear() -> void + { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + auto RendererAPI::enable_depth_test() -> void + { + glEnable(GL_DEPTH_TEST); + } + + auto RendererAPI::disable_depth_test() -> void + { + glDisable(GL_DEPTH_TEST); + } + + auto RendererAPI::set_face_culling(bool enabled) -> void + { + if (enabled) + { + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + } + else + glDisable(GL_CULL_FACE); + } + + auto RendererAPI::enable_blending() -> void + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + auto RendererAPI::disable_blending() -> void + { + glDisable(GL_BLEND); + } + + auto RendererAPI::draw_indexed(const Ref<VertexArray>& 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 RendererAPI::draw_arrays(uint32_t vertex_count, uint32_t first) -> void + { + glDrawArrays(GL_TRIANGLES, first, vertex_count); + } + + auto RendererAPI::draw_lines(const Ref<VertexArray>& 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 RendererAPI::bind_texture(uint32_t texture_id, uint32_t slot) -> void + { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, texture_id); + } + + auto RendererAPI::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 RendererAPI::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); + } auto Renderer::init() -> void { diff --git a/src/rendering/renderer.h b/src/rendering/renderer.h index 189723d..d7855a4 100644 --- a/src/rendering/renderer.h +++ b/src/rendering/renderer.h @@ -9,6 +9,8 @@ namespace Donut { + // Single concrete backend: the OpenGL renderer. (Vulkan runs through its own + // dedicated VulkanRenderer, not this immediate-mode command layer.) class RendererAPI { public: @@ -20,31 +22,26 @@ namespace Donut }; public: - virtual ~RendererAPI() = default; + RendererAPI() = default; + ~RendererAPI() = default; - virtual auto init() -> void = 0; - virtual auto set_viewport(uint32_t x, uint32_t y, - uint32_t width, uint32_t height) -> void = 0; - virtual auto set_clear_color(const glm::vec4& color) -> void = 0; - virtual auto clear() -> void = 0; - virtual auto enable_depth_test() -> void = 0; - virtual auto disable_depth_test() -> void = 0; - virtual auto set_face_culling(bool enabled) -> void = 0; - virtual auto enable_blending() -> void = 0; - virtual auto disable_blending() -> void = 0; + auto init() -> void; + auto set_viewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) -> void; + auto set_clear_color(const glm::vec4& color) -> void; + auto clear() -> void; + auto enable_depth_test() -> void; + auto disable_depth_test() -> void; + auto set_face_culling(bool enabled) -> void; + auto enable_blending() -> void; + auto disable_blending() -> void; - virtual auto draw_indexed(const Ref<VertexArray>& vertex_array, - uint32_t index_count = 0) -> void = 0; - - virtual auto draw_arrays(uint32_t vertex_count, uint32_t first = 0) -> void = 0; - virtual auto draw_lines(const Ref<VertexArray>& vertex_array, - uint32_t index_count = 0) -> void = 0; - virtual auto bind_texture(uint32_t texture_id, uint32_t slot = 0) -> void = 0; - virtual auto bind_image_texture(uint32_t texture_id, - uint32_t slot = 0, - bool read_only = false) -> void = 0; - virtual auto read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, - uint32_t format, uint32_t type, void* pixels) -> void = 0; + auto draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void; + auto draw_arrays(uint32_t vertex_count, uint32_t first = 0) -> void; + auto draw_lines(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) -> void; + auto bind_texture(uint32_t texture_id, uint32_t slot = 0) -> void; + auto bind_image_texture(uint32_t texture_id, uint32_t slot = 0, bool read_only = false) -> void; + auto read_pixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t format, uint32_t type, void* pixels) -> void; inline static auto get_api() -> API { return s_api; } inline static auto set_api(API api) -> void { s_api = api; } diff --git a/src/rendering/shader.cpp b/src/rendering/shader.cpp index 167b855..80015e6 100644 --- a/src/rendering/shader.cpp +++ b/src/rendering/shader.cpp @@ -1,48 +1,318 @@ #include "shader.h" -#include "renderer.h" -#include "platform/opengl/opengl_shader.h" -#include "platform/vulkan/vulkan_shader.h" +#include <glad/glad.h> +#include <glm/gtc/type_ptr.hpp> + +#include <fstream> +#include <iostream> namespace Donut { - auto Shader::create(const std::string& filepath) -> Shader* + 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/ + // <name>.glsl by tools/compile-shaders.sh. Given a legacy ".../<name>.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; + } + + Shader::Shader(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); + } + + Shader::Shader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) + : m_name(name) + { + std::unordered_map<uint32_t, std::string> sources; + sources[GL_VERTEX_SHADER] = vertex_src; + sources[GL_FRAGMENT_SHADER] = fragment_src; + compile(sources); + } + + Shader::Shader(const std::string& name, const std::string& compute_src) + : m_name(name) + { + std::unordered_map<uint32_t, std::string> sources; + sources[GL_COMPUTE_SHADER] = compute_src; + compile(sources); + } + + Shader::~Shader() + { + glDeleteProgram(m_renderer_id); + } + + auto Shader::read_file(const std::string& filepath) -> std::string { - switch (Renderer::get_api()) + std::string result; + std::ifstream in(filepath, std::ios::in | + std::ios::binary); + + if (in) { - case RendererAPI::API::OpenGL: - return new OpenGLShader(filepath); - case RendererAPI::API::Vulkan: - return new VulkanShader(filepath); - default: - return nullptr; + 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 Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) -> Shader* + auto Shader::pre_process(const std::string& source) -> std::unordered_map<uint32_t, std::string> { - switch (Renderer::get_api()) + std::unordered_map<uint32_t, std::string> 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) { - case RendererAPI::API::OpenGL: - return new OpenGLShader(name, vertex_src, fragment_src); - case RendererAPI::API::Vulkan: - return new VulkanShader(name, vertex_src, fragment_src); - default: - return nullptr; + 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 Shader::create_compute(const std::string& name, const std::string& compute_src) -> Shader* + auto Shader::compile(const std::unordered_map<uint32_t, std::string>& shader_sources) -> void { - switch (Renderer::get_api()) + uint32_t program = glCreateProgram(); + std::vector<uint32_t> 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<char> 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) { - case RendererAPI::API::OpenGL: - return new OpenGLShader(name, compute_src); - case RendererAPI::API::Vulkan: - return new VulkanShader(name, compute_src); - default: - return nullptr; + int max_length = 0; + glGetProgramiv(m_renderer_id, GL_INFO_LOG_LENGTH, &max_length); + std::vector<char> 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 Shader::bind() const -> void + { + glUseProgram(m_renderer_id); + } + + auto Shader::unbind() const -> void + { + glUseProgram(0); + } + + auto Shader::set_int(const std::string& name, int value) -> void + { + upload_uniform_int(name, value); + } + + auto Shader::set_int_array(const std::string& name, int* values, uint32_t count) -> void + { + upload_uniform_int_array(name, values, count); + } + + auto Shader::set_float(const std::string& name, float value) -> void + { + upload_uniform_float(name, value); + } + + auto Shader::set_float2(const std::string& name, const glm::vec2& value) -> void + { + upload_uniform_float2(name, value); + } + + auto Shader::set_float3(const std::string& name, const glm::vec3& value) -> void + { + upload_uniform_float3(name, value); + } + + auto Shader::set_float4(const std::string& name, const glm::vec4& value) -> void + { + upload_uniform_float4(name, value); + } + + auto Shader::set_mat4(const std::string& name, const glm::mat4& value) -> void + { + upload_uniform_mat4(name, value); + } + + auto Shader::upload_uniform_int(const std::string& name, int value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform1i(location, value); + } + + auto Shader::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 Shader::upload_uniform_float(const std::string& name, float value) -> void + { + int location = glGetUniformLocation(m_renderer_id, name.c_str()); + glUniform1f(location, value); + } + + auto Shader::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 Shader::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 Shader::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 Shader::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 Shader::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 Shader::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 Shader::dispatch_indirect(uint32_t offset) -> void + { + if (m_renderer_id == 0 || glDispatchComputeIndirect == nullptr) + return; + glDispatchComputeIndirect(offset); + } + + auto Shader::memory_barrier(uint32_t barriers) -> void + { + if (glMemoryBarrier == nullptr) + return; + glMemoryBarrier(barriers); + } + + auto Shader::create(const std::string& filepath) -> Shader* + { + return new Shader(filepath); + } + + auto Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) -> Shader* + { + return new Shader(name, vertex_src, fragment_src); + } + + auto Shader::create_compute(const std::string& name, const std::string& compute_src) -> Shader* + { + return new Shader(name, compute_src); } auto ShaderLibrary::add(const Ref<Shader>& shader) -> void diff --git a/src/rendering/shader.h b/src/rendering/shader.h index bc3fd9a..409aca3 100644 --- a/src/rendering/shader.h +++ b/src/rendering/shader.h @@ -11,34 +11,58 @@ #define TEXTURE_FETCH_BARRIER_BIT 0x00000008 #define IMAGE_ACCESS_BARRIER_BIT 0x00000020 -namespace Donut +namespace Donut { class Shader { public: - virtual ~Shader() = default; + Shader(const std::string& filepath); + Shader(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); + Shader(const std::string& name, const std::string& compute_src); + ~Shader(); - virtual auto bind() const -> void = 0; - virtual auto unbind() const -> void = 0; + auto bind() const -> void; + auto unbind() const -> void; - virtual auto set_int( const std::string& name, int value) -> void = 0; - virtual auto set_int_array(const std::string& name, int* values, uint32_t count) -> void = 0; - virtual auto set_float( const std::string& name, float value) -> void = 0; - virtual auto set_float2( const std::string& name, const glm::vec2& value) -> void = 0; - virtual auto set_float3( const std::string& name, const glm::vec3& value) -> void = 0; - virtual auto set_float4( const std::string& name, const glm::vec4& value) -> void = 0; - virtual auto set_mat4( const std::string& name, const glm::mat4& value) -> void = 0; + auto set_int( const std::string& name, int value) -> void; + auto set_int_array(const std::string& name, int* values, uint32_t count) -> void; + auto set_float( const std::string& name, float value) -> void; + auto set_float2( const std::string& name, const glm::vec2& value) -> void; + auto set_float3( const std::string& name, const glm::vec3& value) -> void; + auto set_float4( const std::string& name, const glm::vec4& value) -> void; + auto set_mat4( const std::string& name, const glm::mat4& value) -> void; - virtual auto dispatch(uint32_t x, uint32_t y = 1, uint32_t z = 1) -> void = 0; - virtual auto dispatch_indirect(uint32_t offset = 0) -> void = 0; - virtual auto memory_barrier(uint32_t barriers) -> void = 0; + auto dispatch(uint32_t x, uint32_t y = 1, uint32_t z = 1) -> void; + auto dispatch_indirect(uint32_t offset = 0) -> void; + auto memory_barrier(uint32_t barriers) -> void; - virtual auto get_name() const -> const std::string& = 0; - virtual auto get_renderer_id() const -> uint32_t = 0; + auto get_name() const -> const std::string& { return m_name; } + auto get_renderer_id() const -> uint32_t { 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; static auto create(const std::string& filepath) -> Shader*; static auto create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) -> Shader*; static auto create_compute(const std::string& name, const std::string& compute_src) -> Shader*; + + private: + auto read_file(const std::string& filepath) -> std::string; + auto pre_process(const std::string& source) -> std::unordered_map<uint32_t, std::string>; + auto compile(const std::unordered_map<uint32_t, std::string>& shader_sources) -> void; + + 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; }; class ShaderLibrary @@ -49,7 +73,7 @@ namespace Donut auto load(const std::string& filepath) -> Ref<Shader>; auto load(const std::string& name, const std::string& filepath) -> Ref<Shader>; - Ref<Shader> Get(const std::string& name); + auto Get(const std::string& name) -> Ref<Shader>; auto exists(const std::string& name) const -> bool; private: diff --git a/src/rendering/texture.cpp b/src/rendering/texture.cpp index 2470e46..12e07f1 100644 --- a/src/rendering/texture.cpp +++ b/src/rendering/texture.cpp @@ -1,68 +1,296 @@ #include "texture.h" -#include "renderer.h" -#include "platform/opengl/opengl_texture.h" -#include "platform/vulkan/vulkan_texture.h" +#include "shader.h" +#include "core/log.h" + +#include <glad/glad.h> + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> + +// 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. It also hosts the single STB_IMAGE_IMPLEMENTATION +// for the whole build (the Vulkan backend links stbi_loadf from here too). namespace Donut { - auto Texture2D::create(uint32_t width, uint32_t height) -> Ref<Texture2D> + Texture2D::Texture2D(uint32_t width, uint32_t height) + : m_width(width), m_height(height) { - switch (Renderer::get_api()) + 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); + } + + Texture2D::Texture2D(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, ")"); + } + + Texture2D::~Texture2D() + { + glDeleteTextures(1, &m_renderer_id); + } + + auto Texture2D::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) { - case RendererAPI::API::OpenGL: - return create_ref<OpenGLTexture2D>(width, height); - case RendererAPI::API::Vulkan: - return create_ref<VulkanTexture2D>(width, height); - case RendererAPI::API::None: - return nullptr; - default: - return nullptr; + 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 Texture2D::create(const std::string& path) -> Ref<Texture2D> + auto Texture2D::bind(uint32_t slot) const -> void { - switch (Renderer::get_api()) + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, m_renderer_id); + } + + auto Texture2D::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); + } + + CubemapTexture::CubemapTexture(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); + } + + CubemapTexture::CubemapTexture(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); + } + + CubemapTexture::~CubemapTexture() + { + glDeleteTextures(1, &m_renderer_id); + } + + auto CubemapTexture::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) { - case RendererAPI::API::OpenGL: - return create_ref<OpenGLTexture2D>(path); - case RendererAPI::API::Vulkan: - return create_ref<VulkanTexture2D>(path); - case RendererAPI::API::None: - return nullptr; - default: - return nullptr; + 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 CubemapTexture::create(uint32_t width, uint32_t height) -> Ref<CubemapTexture> + auto CubemapTexture::convert_equirectangular_to_cubemap(float* hdr_data, int width, int height) -> void { - switch (Renderer::get_api()) + 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) { - case RendererAPI::API::OpenGL: - return create_ref<OpenGLCubemapTexture>(width, height); - case RendererAPI::API::Vulkan: - return create_ref<VulkanCubemapTexture>(width, height); - case RendererAPI::API::None: - return nullptr; - default: - return nullptr; + 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 CubemapTexture::set_data(void* data, uint32_t size) -> void + { + DONUT_WARN("set_data not implemented for cubemaps"); + } + + auto CubemapTexture::bind(uint32_t slot) const -> void + { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_renderer_id); + } + + auto CubemapTexture::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); + } + + auto Texture2D::create(uint32_t width, uint32_t height) -> Ref<Texture2D> + { + return create_ref<Texture2D>(width, height); + } + + auto Texture2D::create(const std::string& path) -> Ref<Texture2D> + { + return create_ref<Texture2D>(path); + } + + auto CubemapTexture::create(uint32_t width, uint32_t height) -> Ref<CubemapTexture> + { + return create_ref<CubemapTexture>(width, height); } auto CubemapTexture::create_from_hdri(const std::string& path) -> Ref<CubemapTexture> { - switch (Renderer::get_api()) - { - case RendererAPI::API::OpenGL: - return create_ref<OpenGLCubemapTexture>(path); - case RendererAPI::API::Vulkan: - return create_ref<VulkanCubemapTexture>(path); - case RendererAPI::API::None: - return nullptr; - default: - return nullptr; - } + return create_ref<CubemapTexture>(path); } -};
\ No newline at end of file +}; diff --git a/src/rendering/texture.h b/src/rendering/texture.h index e9b3e0a..5f68c78 100644 --- a/src/rendering/texture.h +++ b/src/rendering/texture.h @@ -3,6 +3,7 @@ #include "core/memory.h" #include <string> +#include <cstdint> #include <glm/glm.hpp> namespace Donut @@ -18,25 +19,65 @@ namespace Donut virtual auto set_data(void* data, uint32_t size) -> void = 0; virtual auto bind(uint32_t slot = 0) const -> void = 0; - virtual void bind_as_image(uint32_t slot = 0, - bool read_only = false) const = 0; + virtual auto bind_as_image(uint32_t slot = 0, bool read_only = false) const -> void = 0; - virtual bool operator==(const Texture& other) const = 0; + virtual auto operator==(const Texture& other) const -> bool = 0; }; - class Texture2D - : public Texture + class Texture2D : public Texture { public: + Texture2D(uint32_t width, uint32_t height); + Texture2D(const std::string& path); + ~Texture2D(); + + auto get_width() const -> uint32_t override { return m_width; } + auto get_height() const -> uint32_t override { return m_height; } + auto get_renderer_id() const -> uint32_t override { return m_renderer_id; } + + auto set_data(void* data, uint32_t size) -> void override; + auto bind(uint32_t slot = 0) const -> void override; + auto bind_as_image(uint32_t slot = 0, bool read_only = false) const -> void override; + + auto operator==(const Texture& other) const -> bool override { return m_renderer_id == other.get_renderer_id(); } + static auto create(uint32_t width, uint32_t height) -> Ref<Texture2D>; static auto create(const std::string& path) -> Ref<Texture2D>; + + private: + std::string m_path; + uint32_t m_width = 0, m_height = 0; + uint32_t m_renderer_id = 0; + uint32_t m_internal_format = 0, m_data_format = 0; // GLenum values }; - class CubemapTexture - : public Texture + class CubemapTexture : public Texture { public: + CubemapTexture(uint32_t width, uint32_t height); + CubemapTexture(const std::string& path); + ~CubemapTexture(); + + auto get_width() const -> uint32_t override { return m_width; } + auto get_height() const -> uint32_t override { return m_height; } + auto get_renderer_id() const -> uint32_t override { return m_renderer_id; } + + auto set_data(void* data, uint32_t size) -> void override; + auto bind(uint32_t slot = 0) const -> void override; + auto bind_as_image(uint32_t slot = 0, bool read_only = false) const -> void override; + + auto operator==(const Texture& other) const -> bool override { return m_renderer_id == other.get_renderer_id(); } + static auto create(uint32_t width, uint32_t height) -> Ref<CubemapTexture>; static auto create_from_hdri(const std::string& path) -> Ref<CubemapTexture>; + + private: + auto LoadHDRI(const std::string& path) -> void; + auto convert_equirectangular_to_cubemap(float* hdr_data, int width, int height) -> void; + + std::string m_path; + uint32_t m_width = 0, m_height = 0; + uint32_t m_renderer_id = 0; + uint32_t m_internal_format = 0, m_data_format = 0; // GLenum values }; -};
\ No newline at end of file +}; diff --git a/src/rendering/texture_manager.cpp b/src/rendering/texture_manager.cpp index ad2c68b..3afba0d 100644 --- a/src/rendering/texture_manager.cpp +++ b/src/rendering/texture_manager.cpp @@ -1,13 +1,13 @@ #include "texture_manager.h" -#include "platform/opengl/opengl_texture.h" -#include "platform/opengl/opengl_renderer_api.h" +#include "renderer.h" + #include <glad/glad.h> namespace Donut { auto TextureManager::create_texture(uint32_t width, uint32_t height) -> Ref<Texture2D> { - return create_ref<OpenGLTexture2D>(width, height); + return create_ref<Texture2D>(width, height); } auto TextureManager::bind_texture(uint32_t texture_id, uint32_t slot) -> void diff --git a/src/rendering/uniform_buffer.cpp b/src/rendering/uniform_buffer.cpp index c344aec..c4118b8 100644 --- a/src/rendering/uniform_buffer.cpp +++ b/src/rendering/uniform_buffer.cpp @@ -1,23 +1,36 @@ #include "uniform_buffer.h" -#include "renderer.h" -#include "platform/opengl/opengl_uniform_buffer.h" -#include "platform/vulkan/vulkan_uniform_buffer.h" +#include <glad/glad.h> namespace Donut { + UniformBuffer::UniformBuffer(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); + } + + UniformBuffer::~UniformBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto UniformBuffer::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 UniformBuffer::bind(uint32_t binding) -> void + { + glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); + } + auto UniformBuffer::create(uint32_t size, uint32_t binding) -> Ref<UniformBuffer> { - switch (Renderer::get_api()) - { - case RendererAPI::API::OpenGL: - return create_ref<OpenGLUniformBuffer>(size, binding); - case RendererAPI::API::Vulkan: - return create_ref<VulkanUniformBuffer>(size, binding); - case RendererAPI::API::None: - return nullptr; - default: - return nullptr; - } + return create_ref<UniformBuffer>(size, binding); } }; diff --git a/src/rendering/uniform_buffer.h b/src/rendering/uniform_buffer.h index eb28e53..b5617b9 100644 --- a/src/rendering/uniform_buffer.h +++ b/src/rendering/uniform_buffer.h @@ -8,11 +8,17 @@ namespace Donut class UniformBuffer { public: - virtual ~UniformBuffer() = default; + UniformBuffer(uint32_t size, uint32_t binding); + ~UniformBuffer(); - virtual auto set_data(const void* data, uint32_t size, uint32_t offset = 0) -> void = 0; - virtual auto bind(uint32_t binding) -> void = 0; + auto set_data(const void* data, uint32_t size, uint32_t offset = 0) -> void; + auto bind(uint32_t binding) -> void; static auto create(uint32_t size, uint32_t binding) -> Ref<UniformBuffer>; + + private: + uint32_t m_renderer_id = 0; + uint32_t m_size = 0; + uint32_t m_binding = 0; }; }; diff --git a/src/rendering/vertex_array.cpp b/src/rendering/vertex_array.cpp index 8045707..cddcf35 100644 --- a/src/rendering/vertex_array.cpp +++ b/src/rendering/vertex_array.cpp @@ -1,32 +1,61 @@ -#include "vertex_array.h" -#include "renderer.h" +#include <glad/glad.h> -#include "platform/opengl/opengl_vertex_array.h" -#include "platform/vulkan/vulkan_vertex_array.h" +#include "vertex_array.h" namespace Donut { - auto VertexBufferElement::get_size_of_type(uint32_t type) -> uint32_t + VertexArray::VertexArray() + { + // 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); + } + + VertexArray::~VertexArray() + { + glDeleteVertexArrays(1, &m_renderer_id); + } + + auto VertexArray::bind() const -> void + { + glBindVertexArray(m_renderer_id); + } + + auto VertexArray::unbind() const -> void { - switch (type) + glBindVertexArray(0); + } + + auto VertexArray::add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void + { + glBindVertexArray(m_renderer_id); + vertex_buffer->bind(); + + const auto& layout = vertex_buffer->get_layout(); + for (const auto& element : layout.get_elements()) { - case 0x1406: return 4; // GL_FLOAT - case 0x1405: return 4; // GL_UNSIGNED_INT - case 0x1401: return 1; // GL_UNSIGNED_BYTE - default: return 0; + 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<const void*>(static_cast<uintptr_t>(element.offset))); + m_vertex_buffer_index++; } + + m_vertex_buffers.push_back(vertex_buffer); + } + + auto VertexArray::set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void + { + glBindVertexArray(m_renderer_id); + index_buffer->bind(); + m_index_buffer = index_buffer; } auto VertexArray::create() -> VertexArray* { - switch (Renderer::get_api()) - { - case RendererAPI::API::OpenGL: - return new OpenGLVertexArray(); - case RendererAPI::API::Vulkan: - return new VulkanVertexArray(); - default: - return nullptr; - } + return new VertexArray(); } }; diff --git a/src/rendering/vertex_array.h b/src/rendering/vertex_array.h index 921df56..1f06758 100644 --- a/src/rendering/vertex_array.h +++ b/src/rendering/vertex_array.h @@ -9,20 +9,27 @@ namespace Donut { - class VertexArray + class VertexArray { public: - virtual ~VertexArray() = default; + VertexArray(); + ~VertexArray(); - virtual auto bind() const -> void = 0; - virtual auto unbind() const -> void = 0; + auto bind() const -> void; + auto unbind() const -> void; - virtual auto add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void = 0; - virtual auto set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void = 0; + auto add_vertex_buffer(const Ref<VertexBuffer>& vertex_buffer) -> void; + auto set_index_buffer(const Ref<IndexBuffer>& index_buffer) -> void; - virtual auto get_vertex_buffers() const -> const std::vector<Ref<VertexBuffer>>& = 0; - virtual auto get_index_buffer() const -> const Ref<IndexBuffer>& = 0; + auto get_vertex_buffers() const -> const std::vector<Ref<VertexBuffer>>& { return m_vertex_buffers; } + auto get_index_buffer() const -> const Ref<IndexBuffer>& { return m_index_buffer; } static auto create() -> VertexArray*; + + private: + uint32_t m_renderer_id = 0; + uint32_t m_vertex_buffer_index = 0; + std::vector<Ref<VertexBuffer>> m_vertex_buffers; + Ref<IndexBuffer> m_index_buffer; }; }; diff --git a/src/rendering/vertex_buffer.cpp b/src/rendering/vertex_buffer.cpp index 354d0cd..1bc471c 100644 --- a/src/rendering/vertex_buffer.cpp +++ b/src/rendering/vertex_buffer.cpp @@ -1,21 +1,50 @@ #include "vertex_buffer.h" -#include "renderer.h" -#include "platform/opengl/opengl_vertex_buffer.h" -#include "platform/vulkan/vulkan_vertex_buffer.h" +#include <glad/glad.h> namespace Donut { - auto VertexBuffer::create(const void* data, uint32_t size) -> VertexBuffer* + auto VertexBufferElement::get_size_of_type(uint32_t type) -> uint32_t { - switch (Renderer::get_api()) + switch (type) { - case RendererAPI::API::OpenGL: - return new OpenGLVertexBuffer(data, size); - case RendererAPI::API::Vulkan: - return new VulkanVertexBuffer((float*)data, size); - default: - return nullptr; + case 0x1406: return 4; // GL_FLOAT + case 0x1405: return 4; // GL_UNSIGNED_INT + case 0x1401: return 1; // GL_UNSIGNED_BYTE + default: return 0; } } + + VertexBuffer::VertexBuffer(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); + } + + VertexBuffer::~VertexBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + auto VertexBuffer::bind() const -> void + { + glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); + } + + auto VertexBuffer::unbind() const -> void + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + auto VertexBuffer::set_data(const void* data, uint32_t size) -> void + { + glBindBuffer(GL_ARRAY_BUFFER, m_renderer_id); + glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + } + + auto VertexBuffer::create(const void* data, uint32_t size) -> VertexBuffer* + { + return new VertexBuffer(data, size); + } }; diff --git a/src/rendering/vertex_buffer.h b/src/rendering/vertex_buffer.h index 6be7e34..4e61700 100644 --- a/src/rendering/vertex_buffer.h +++ b/src/rendering/vertex_buffer.h @@ -3,9 +3,9 @@ #include <cstdint> #include <vector> -namespace Donut +namespace Donut { - struct VertexBufferElement + struct VertexBufferElement { uint32_t type; uint32_t count; @@ -22,27 +22,27 @@ namespace Donut ~VertexBufferLayout() = default; template<typename T> - auto push(uint32_t count) -> void + auto push(uint32_t count) -> void { static_assert(false); } template<> - void push<float>(uint32_t count) + void push<float>(uint32_t count) { m_elements.push_back({ 0x1406, count, 0, m_stride }); // GL_FLOAT m_stride += count * VertexBufferElement::get_size_of_type(0x1406); } template<> - void push<uint32_t>(uint32_t count) + void push<uint32_t>(uint32_t count) { m_elements.push_back({ 0x1405, count, 0, m_stride }); // GL_UNSIGNED_INT m_stride += count * VertexBufferElement::get_size_of_type(0x1405); } template<> - void push<uint8_t>(uint32_t count) + void push<uint8_t>(uint32_t count) { m_elements.push_back({ 0x1401, count, 1, m_stride }); // GL_UNSIGNED_BYTE m_stride += count * VertexBufferElement::get_size_of_type(0x1401); @@ -59,15 +59,20 @@ namespace Donut class VertexBuffer { public: - virtual ~VertexBuffer() = default; + VertexBuffer(const void* data, uint32_t size); + ~VertexBuffer(); - virtual auto bind() const -> void = 0; - virtual auto unbind() const -> void = 0; - virtual auto set_data(const void* data, uint32_t size) -> void = 0; + auto bind() const -> void; + auto unbind() const -> void; + auto set_data(const void* data, uint32_t size) -> void; - virtual auto get_layout() const -> const VertexBufferLayout& = 0; - virtual auto set_layout(const VertexBufferLayout& layout) -> void = 0; + auto get_layout() const -> const VertexBufferLayout& { return m_layout; } + auto set_layout(const VertexBufferLayout& layout) -> void { m_layout = layout; } static auto create(const void* data, uint32_t size) -> VertexBuffer*; + + private: + uint32_t m_renderer_id = 0; + VertexBufferLayout m_layout; }; }; |
