From 538d0b5ce71c89ac5ecddcb3de8ee88d89d16de3 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Sat, 3 Aug 2024 22:41:23 +0200 Subject: [PATCH] Asset+Render: Add Framebuffers --- src/inferno/asset/asset-manager.h | 1 + src/inferno/asset/texture.cpp | 68 +++++++++++++++++++++++++----- src/inferno/asset/texture.h | 50 ++++++++++++++++------ src/inferno/render/buffer.cpp | 13 +++--- src/inferno/render/buffer.h | 16 ++++--- src/inferno/render/framebuffer.cpp | 62 ++++++++++++++++++++++++++- src/inferno/render/framebuffer.h | 43 +++++++++++++++++-- 7 files changed, 210 insertions(+), 43 deletions(-) diff --git a/src/inferno/asset/asset-manager.h b/src/inferno/asset/asset-manager.h index 1931ace..8dd8276 100644 --- a/src/inferno/asset/asset-manager.h +++ b/src/inferno/asset/asset-manager.h @@ -36,6 +36,7 @@ public: virtual bool isTexture() const { return false; } virtual bool isTexture2D() const { return false; } virtual bool isTextureCubemap() const { return false; } + virtual bool isTextureFramebuffer() const { return false; } protected: Asset(std::string_view path) diff --git a/src/inferno/asset/texture.cpp b/src/inferno/asset/texture.cpp index 43809f4..9243ba6 100644 --- a/src/inferno/asset/texture.cpp +++ b/src/inferno/asset/texture.cpp @@ -24,12 +24,20 @@ Texture::~Texture() } void Texture::init(uint32_t width, uint32_t height, uint8_t channels) +{ + init(width, height, + (channels == 3) ? GL_RGB8 : GL_RGBA8, + (channels == 3) ? GL_RGB : GL_RGBA, + GL_UNSIGNED_BYTE); +} + +void Texture::init(uint32_t width, uint32_t height, uint32_t internalFormat, uint32_t dataFormat, uint32_t dataType) { m_width = width; m_height = height; - - m_internalFormat = (channels == 3) ? GL_RGB8 : GL_RGBA8; - m_dataFormat = (channels == 3) ? GL_RGB : GL_RGBA; + m_internalFormat = internalFormat; + m_dataFormat = dataFormat; + m_dataType = dataType; } // ----------------------------------------- @@ -49,7 +57,7 @@ std::shared_ptr Texture2D::create(std::string_view path) VERIFY(data, "failed to load image: '{}'", path); result->init(width, height, channels); - result->create(data); + result->createImpl(data); // Clean resources stbi_image_free(data); @@ -78,7 +86,7 @@ std::shared_ptr Texture2D::create(aiTexture* texture) } result->init(width, height, channels); - result->create(data); + result->createImpl(data); return result; } @@ -99,7 +107,7 @@ void Texture2D::unbind() const glBindTexture(GL_TEXTURE_2D, 0); } -void Texture2D::create(unsigned char* data) +void Texture2D::createImpl(unsigned char* data) { m_id = UINT_MAX; @@ -121,7 +129,7 @@ void Texture2D::create(unsigned char* data) m_width, m_height, // Image width/height 0, // Always 0 (legacy) m_dataFormat, // Texture source format - GL_UNSIGNED_BYTE, // Texture source datatype + m_dataType, // Texture source datatype data); // Image data // Set the texture wrapping / filtering options @@ -143,7 +151,7 @@ std::shared_ptr TextureCubemap::create(std::string_view path) { auto result = std::shared_ptr(new TextureCubemap(path)); - result->create(); + result->createImpl(); return result; } @@ -164,7 +172,7 @@ void TextureCubemap::unbind() const glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } -void TextureCubemap::create() +void TextureCubemap::createImpl() { m_id = UINT_MAX; @@ -206,7 +214,7 @@ void TextureCubemap::create() m_width, m_height, // Image width/height 0, // Always 0 (legacy) m_dataFormat, // Texture source format - GL_UNSIGNED_BYTE, // Texture source datatype + m_dataType, // Texture source datatype data); // Image data // Clean resources @@ -224,4 +232,44 @@ void TextureCubemap::create() glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } +// ----------------------------------------- + +std::shared_ptr TextureFramebuffer::create(uint32_t width, uint32_t height, uint32_t internalFormat, uint32_t dataFormat, uint32_t dataType) +{ + auto result = std::shared_ptr(new TextureFramebuffer("")); + + result->init(width, height, internalFormat, dataFormat, dataType); + result->createImpl(); + + return result; +} + +void TextureFramebuffer::createImpl() +{ + m_id = UINT_MAX; + + // Create texture object + glGenTextures(1, &m_id); + + // Bind texture object + glBindTexture(GL_TEXTURE_2D, m_id); + + // Generate texture + glTexImage2D( + GL_TEXTURE_2D, // Texture target + 0, // Midmap level, base starts at level 0 + m_internalFormat, // Texture format + m_width, m_height, // Image width/height + 0, // Always 0 (legacy) + m_dataFormat, // Texture source format + m_dataType, // Texture source datatype + NULL); // Image data + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Unbind texture object + glBindTexture(GL_TEXTURE_2D, 0); +} + } // namespace Inferno diff --git a/src/inferno/asset/texture.h b/src/inferno/asset/texture.h index f73e594..e05ea6a 100644 --- a/src/inferno/asset/texture.h +++ b/src/inferno/asset/texture.h @@ -10,20 +10,20 @@ #include // std::shared_ptr #include +#include "glad/glad.h" + #include "inferno/asset/asset-manager.h" struct aiTexture; namespace Inferno { -class Texture2D; -class TextureCubemap; - class Texture : public Asset { public: virtual ~Texture(); void init(uint32_t width, uint32_t height, uint8_t channels); + void init(uint32_t width, uint32_t height, uint32_t internalFormat, uint32_t dataFormat, uint32_t dataType); virtual void bind(uint32_t unit = 0) const = 0; virtual void unbind() const = 0; @@ -33,12 +33,11 @@ public: uint32_t id() const { return m_id; } uint32_t internalFormat() const { return m_internalFormat; } uint32_t dataFormat() const { return m_dataFormat; } + uint32_t dataType() const { return m_dataType; } - virtual bool is2D() const { return false; } - virtual bool isCubemap() const { return false; } - - friend Texture2D; - friend TextureCubemap; + virtual bool isTexture2D() const override { return false; } + virtual bool isTextureCubemap() const override { return false; } + virtual bool isTextureFramebuffer() const override { return false; } protected: Texture(std::string_view path) @@ -52,6 +51,7 @@ protected: uint32_t m_id { 0 }; uint32_t m_internalFormat { 0 }; uint32_t m_dataFormat { 0 }; + uint32_t m_dataType { GL_UNSIGNED_BYTE }; private: virtual bool isTexture() const override { return true; } @@ -76,10 +76,9 @@ private: { } - virtual bool isTexture2D() const override { return true; } + void createImpl(unsigned char* data); -private: - void create(unsigned char* data); + virtual bool isTexture2D() const override { return true; } }; // ------------------------------------- @@ -100,10 +99,34 @@ private: { } + void createImpl(); + virtual bool isTextureCubemap() const override { return true; } +}; + +// ----------------------------------------- + +class TextureFramebuffer final : public Texture { +public: + virtual ~TextureFramebuffer() = default; + + // Factory function + static std::shared_ptr create( + uint32_t width, uint32_t height, + uint32_t internalFormat, uint32_t dataFormat, uint32_t dataType = GL_UNSIGNED_BYTE); + + virtual void bind(uint32_t) const override {} + virtual void unbind() const override {} private: - void create(); + TextureFramebuffer(std::string_view path) + : Texture(path) + { + } + + void createImpl(); + + virtual bool isTextureFramebuffer() const override { return true; } }; // ----------------------------------------- @@ -117,6 +140,9 @@ inline bool Asset::fastIs() const { return isTexture2D(); } template<> inline bool Asset::fastIs() const { return isTextureCubemap(); } + +template<> +inline bool Asset::fastIs() const { return isTextureFramebuffer(); } // clang-format on } // namespace Inferno diff --git a/src/inferno/render/buffer.cpp b/src/inferno/render/buffer.cpp index 65ef9c1..82804e9 100644 --- a/src/inferno/render/buffer.cpp +++ b/src/inferno/render/buffer.cpp @@ -246,18 +246,13 @@ void BufferLayout::calculateOffsetsAndStride() // ----------------------------------------- VertexBuffer::VertexBuffer(size_t size) + : VertexBuffer(size, nullptr) { - glGenBuffers(1, &m_id); - bind(); - - // Reserve data on the GPU - glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); - - unbind(); } -VertexBuffer::VertexBuffer(float* vertices, size_t size) +VertexBuffer::VertexBuffer(size_t size, float* vertices) { + m_id = UINT_MAX; glGenBuffers(1, &m_id); bind(); @@ -297,6 +292,7 @@ void VertexBuffer::uploadData(const void* data, uint32_t size) IndexBuffer::IndexBuffer(uint32_t* indices, size_t size) : m_count(size / sizeof(uint32_t)) { + m_id = UINT_MAX; glCreateBuffers(1, &m_id); bind(); @@ -335,6 +331,7 @@ void IndexBuffer::uploadData(const void* data, uint32_t size) VertexArray::VertexArray() { + m_id = UINT_MAX; glCreateVertexArrays(1, &m_id); } diff --git a/src/inferno/render/buffer.h b/src/inferno/render/buffer.h index 713478c..cefe1c2 100644 --- a/src/inferno/render/buffer.h +++ b/src/inferno/render/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -31,9 +31,10 @@ enum class BufferElementType { // ----------------------------------------- // Describes one element of the BufferLayout -class BufferElement { +class BufferElement final { public: BufferElement(BufferElementType type, std::string name, bool normalized = false); + ~BufferElement() = default; uint32_t getTypeSize() const; uint32_t getTypeCount() const; @@ -65,10 +66,11 @@ private: // ----------------------------------------- // Layout that describes raw vertex data -class BufferLayout { +class BufferLayout final { public: BufferLayout() {} BufferLayout(const std::initializer_list& elements); + ~BufferLayout() = default; const std::vector& getElements() const { return m_elements; } uint32_t getStride() const { return m_stride; } @@ -90,10 +92,10 @@ private: // ----------------------------------------- // GPU memory which holds raw vertex data -class VertexBuffer { // Vertex Buffer Object, VBO +class VertexBuffer final { // Vertex Buffer Object, VBO public: VertexBuffer(size_t size); - VertexBuffer(float* vertices, size_t size); + VertexBuffer(size_t size, float* vertices); ~VertexBuffer(); void bind() const; @@ -113,7 +115,7 @@ private: // ----------------------------------------- // Vertices order of rendering -class IndexBuffer { // Element Buffer Object, EBO +class IndexBuffer final { // Element Buffer Object, EBO public: IndexBuffer(uint32_t* indices, size_t size); ~IndexBuffer(); @@ -133,7 +135,7 @@ private: // ----------------------------------------- // Array that holds the vertex attributes configuration -class VertexArray { // Vertex Array Object, VAO +class VertexArray final { // Vertex Array Object, VAO public: VertexArray(); ~VertexArray(); diff --git a/src/inferno/render/framebuffer.cpp b/src/inferno/render/framebuffer.cpp index 063396e..6d4a73a 100644 --- a/src/inferno/render/framebuffer.cpp +++ b/src/inferno/render/framebuffer.cpp @@ -1,21 +1,79 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ +#include // int8_t + +#include "glad/glad.h" +#include "ruc/format/log.h" +#include "ruc/meta/assert.h" + +#include "inferno/asset/texture.h" #include "inferno/render/framebuffer.h" namespace Inferno { -Framebuffer::Framebuffer() +Framebuffer::Framebuffer(const Properties& init) { + VERIFY(static_cast(init.type) != 0, + "malformed framebuffer type: {}", init.type); + + m_id = UINT_MAX; + glGenFramebuffers(1, &m_id); + bind(); + + if (init.type & Type::Color) { + ruc::error("color!"); + // Set color attachment 0 out of 32 + m_textures[0] = TextureFramebuffer::create(init.width, init.height, GL_RGB, GL_RGB); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_textures[0]->id(), 0); + } + + // This combined texture is required for older GPUs + if (init.type & Type::Depth && init.type & Stencil) { + ruc::error("both!"); + m_textures[3] = TextureFramebuffer::create(init.width, init.height, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_textures[3]->id(), 0); + } + else if (init.type & Type::Depth) { + ruc::error("depth!"); + m_textures[1] = TextureFramebuffer::create(init.width, init.height, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_textures[1]->id(), 0); + } + else if (init.type & Type::Stencil) { + ruc::error("stencil!"); + m_textures[2] = TextureFramebuffer::create(init.width, init.height, GL_STENCIL_INDEX, GL_STENCIL_INDEX); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_textures[2]->id(), 0); + } + + check(); + unbind(); } Framebuffer::~Framebuffer() { + glDeleteFramebuffers(1, &m_id); } // ----------------------------------------- +bool Framebuffer::check() const +{ + VERIFY(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + "malformed framebuffer: {:#x}", glCheckFramebufferStatus(GL_FRAMEBUFFER)); + return true; +} + +void Framebuffer::bind() const +{ + glBindFramebuffer(GL_FRAMEBUFFER, m_id); +} + +void Framebuffer::unbind() const +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + } // namespace Inferno diff --git a/src/inferno/render/framebuffer.h b/src/inferno/render/framebuffer.h index bc8a147..7f08f19 100644 --- a/src/inferno/render/framebuffer.h +++ b/src/inferno/render/framebuffer.h @@ -1,17 +1,52 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ #pragma once +#include // int8_t +#include // std::shared_ptr + +#include "inferno/asset/texture.h" +#include "ruc/meta/core.h" + namespace Inferno { -class Framebuffer { +class Framebuffer final { // Frame Buffer Object, FBO public: - Framebuffer(); - virtual ~Framebuffer(); + enum Type : int8_t { + None = 0, + Color = BIT(0), + Depth = BIT(1), + Stencil = BIT(2), + }; + + struct Properties { + Type type { None }; + uint32_t width { 1280 }; + uint32_t height { 720 }; + }; + + Framebuffer(const Properties& init); + ~Framebuffer(); + + bool check() const; + + void bind() const; + void unbind() const; + +private: + uint32_t m_id { 0 }; + std::shared_ptr m_textures[4] { nullptr, nullptr, nullptr, nullptr }; }; +// Make bitwise OR '|' return the enum type instead of int +inline Framebuffer::Type operator|(Framebuffer::Type lhs, Framebuffer::Type rhs) +{ + return static_cast( + static_cast(lhs) | static_cast(rhs)); +} + } // namespace Inferno