diff options
| author | hachem <im@hachem.wtf> | 2026-08-20 22:20:49 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-08-20 22:20:49 +0200 |
| commit | 45baf48db974c66cfdc51f26cee191abbbd89a0b (patch) | |
| tree | b60c27506f1e1c37b7238161156c83e7e159fc12 /src/rendering/framebuffer.cpp | |
| parent | 00bf9a8cce28ab1dbc77ba24428be12391c4a576 (diff) | |
[feat]: reduce renderer abstraction
Diffstat (limited to 'src/rendering/framebuffer.cpp')
| -rw-r--r-- | src/rendering/framebuffer.cpp | 246 |
1 files changed, 240 insertions, 6 deletions
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); } }; |
