aboutsummaryrefslogtreecommitdiff
path: root/src/rendering/framebuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rendering/framebuffer.cpp')
-rw-r--r--src/rendering/framebuffer.cpp246
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);
}
};