// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "framebuffer.h" #ifdef DRAGONX_HAS_GLAD #include #include #include "../../util/logger.h" // GL 3.0 constants — may be missing from some GLAD configurations #ifndef GL_RGBA16F #define GL_RGBA16F 0x881A #endif #ifndef GL_HALF_FLOAT #define GL_HALF_FLOAT 0x140B #endif namespace dragonx { namespace ui { namespace effects { // ============================================================================ // Framebuffer Implementation (GLEW Available) // ============================================================================ Framebuffer::Framebuffer() = default; Framebuffer::~Framebuffer() { destroy(); } Framebuffer::Framebuffer(Framebuffer&& other) noexcept : fbo_(other.fbo_) , colorTexture_(other.colorTexture_) , depthRbo_(other.depthRbo_) , width_(other.width_) , height_(other.height_) , isComplete_(other.isComplete_) { other.fbo_ = 0; other.colorTexture_ = 0; other.depthRbo_ = 0; other.width_ = 0; other.height_ = 0; other.isComplete_ = false; } Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept { if (this != &other) { destroy(); fbo_ = other.fbo_; colorTexture_ = other.colorTexture_; depthRbo_ = other.depthRbo_; width_ = other.width_; height_ = other.height_; isComplete_ = other.isComplete_; other.fbo_ = 0; other.colorTexture_ = 0; other.depthRbo_ = 0; other.width_ = 0; other.height_ = 0; other.isComplete_ = false; } return *this; } bool Framebuffer::init(int width, int height, bool useFloat16) { if (width <= 0 || height <= 0) { DEBUG_LOGF("Framebuffer::init - Invalid dimensions: %dx%d\n", width, height); return false; } // Clean up existing resources cleanup(); width_ = width; height_ = height; useFloat16_ = useFloat16; // Generate framebuffer glGenFramebuffers(1, &fbo_); glBindFramebuffer(GL_FRAMEBUFFER, fbo_); // Create color texture — use RGBA16F for blur intermediates to avoid banding glGenTextures(1, &colorTexture_); glBindTexture(GL_TEXTURE_2D, colorTexture_); if (useFloat16) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, nullptr); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Attach color texture to framebuffer glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture_, 0); // Create depth renderbuffer (optional, but good for completeness) glGenRenderbuffers(1, &depthRbo_); glBindRenderbuffer(GL_RENDERBUFFER, depthRbo_); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbo_); // Check completeness isComplete_ = checkComplete(); // Unbind glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0); if (!isComplete_) { DEBUG_LOGF("Framebuffer::init - Framebuffer is not complete!\n"); cleanup(); return false; } return true; } bool Framebuffer::resize(int width, int height) { if (width == width_ && height == height_) { return true; // No change needed } return init(width, height, useFloat16_); } void Framebuffer::destroy() { cleanup(); } void Framebuffer::cleanup() { if (colorTexture_) { glDeleteTextures(1, &colorTexture_); colorTexture_ = 0; } if (depthRbo_) { glDeleteRenderbuffers(1, &depthRbo_); depthRbo_ = 0; } if (fbo_) { glDeleteFramebuffers(1, &fbo_); fbo_ = 0; } width_ = 0; height_ = 0; isComplete_ = false; } bool Framebuffer::checkComplete() { GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { const char* errorStr = "Unknown"; switch (status) { case GL_FRAMEBUFFER_UNDEFINED: errorStr = "UNDEFINED"; break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: errorStr = "INCOMPLETE_ATTACHMENT"; break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: errorStr = "INCOMPLETE_MISSING_ATTACHMENT"; break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: errorStr = "INCOMPLETE_DRAW_BUFFER"; break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: errorStr = "INCOMPLETE_READ_BUFFER"; break; case GL_FRAMEBUFFER_UNSUPPORTED: errorStr = "UNSUPPORTED"; break; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: errorStr = "INCOMPLETE_MULTISAMPLE"; break; case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: errorStr = "INCOMPLETE_LAYER_TARGETS"; break; } DEBUG_LOGF("Framebuffer error: %s (0x%X)\n", errorStr, status); return false; } return true; } void Framebuffer::bind() { glBindFramebuffer(GL_FRAMEBUFFER, fbo_); glViewport(0, 0, width_, height_); } void Framebuffer::unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } void Framebuffer::clear(float r, float g, float b, float a) { glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void Framebuffer::blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY) { glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_); glBlitFramebuffer( srcX, srcY, srcX + srcWidth, srcY + srcHeight, // Source rect dstX, dstY, dstX + srcWidth, dstY + srcHeight, // Dest rect GL_COLOR_BUFFER_BIT, GL_LINEAR ); glBindFramebuffer(GL_FRAMEBUFFER, 0); } void Framebuffer::captureScreen(int viewportWidth, int viewportHeight) { // Blit from default framebuffer (0) to this framebuffer glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_); glBlitFramebuffer( 0, 0, viewportWidth, viewportHeight, // Source (screen) 0, 0, width_, height_, // Dest (this FBO) GL_COLOR_BUFFER_BIT, GL_LINEAR ); glBindFramebuffer(GL_FRAMEBUFFER, 0); } // ============================================================================ // FramebufferPingPong Implementation // ============================================================================ bool FramebufferPingPong::init(int width, int height, bool useFloat16) { if (!buffers_[0].init(width, height, useFloat16)) return false; if (!buffers_[1].init(width, height, useFloat16)) return false; currentSource_ = 0; return true; } bool FramebufferPingPong::resize(int width, int height) { if (!buffers_[0].resize(width, height)) return false; if (!buffers_[1].resize(width, height)) return false; return true; } void FramebufferPingPong::destroy() { buffers_[0].destroy(); buffers_[1].destroy(); currentSource_ = 0; } void FramebufferPingPong::swap() { currentSource_ = 1 - currentSource_; } } // namespace effects } // namespace ui } // namespace dragonx #else // !DRAGONX_HAS_GLAD // ============================================================================ // Stub Implementation (No GLAD - Acrylic effects disabled) // ============================================================================ namespace dragonx { namespace ui { namespace effects { Framebuffer::Framebuffer() = default; Framebuffer::~Framebuffer() = default; Framebuffer::Framebuffer(Framebuffer&&) noexcept = default; Framebuffer& Framebuffer::operator=(Framebuffer&&) noexcept = default; bool Framebuffer::init(int, int, bool) { return false; } bool Framebuffer::resize(int, int) { return false; } void Framebuffer::destroy() {} void Framebuffer::cleanup() {} bool Framebuffer::checkComplete() { return false; } void Framebuffer::bind() {} void Framebuffer::unbind() {} void Framebuffer::clear(float, float, float, float) {} void Framebuffer::blitFrom(unsigned int, int, int, int, int, int, int) {} void Framebuffer::captureScreen(int, int) {} bool FramebufferPingPong::init(int, int, bool) { return false; } bool FramebufferPingPong::resize(int, int) { return false; } void FramebufferPingPong::destroy() {} void FramebufferPingPong::swap() {} } // namespace effects } // namespace ui } // namespace dragonx #endif // DRAGONX_HAS_GLAD