Files
ObsidianDragon/src/ui/effects/framebuffer.cpp
T
DanSandClaude Opus 5.5 f970cae193 perf(gl): drop the unused depth/stencil buffers from window and FBOs
The GL window asked for a 24-bit depth and 8-bit stencil buffer, and
every acrylic Framebuffer attached a DEPTH24_STENCIL8 renderbuffer the
size of its color texture. Nothing uses them: no code enables depth or
stencil tests (the ImGui GL3 backend disables both), every blit and
readback is color-only, and only Framebuffer::clear() also cleared the
depth buffer, which nothing read. The window and every FBO, including
the full-size capture FBO, carried that memory, and the FBO
renderbuffers were reallocated on every resize.

Now:
- the window requests depth 0 and stencil 0 explicitly (SDL3 requests a
  depth buffer by default when the attributes are simply removed);
  alpha 8 stays for the transparent window;
- Framebuffer has no renderbuffer, and clear() clears color only;
- a permanent startup log line,
  "GL default framebuffer: depth=N stencil=N", reports what the driver
  provided; "(no attachment)" marks a failed query, which is how some
  drivers report a buffer that does not exist.

The log shows depth=0 stencil=0 both offscreen and on WSLg X11, where
"Window transparency enabled by compositor" is still logged, with no
framebuffer-incomplete errors. Screenshot sweeps at 1280x720 and
2560x1440, and a glass run with ui_opacity 0.6 and blur on (so panels go
through the FBO blur), match the previous build within noise. The
driver-side memory saving itself was not measured. DX11 is unaffected:
its swap chain has no depth buffer.

Not verifiable here: macOS (an NSOpenGL 3.2 core context with a
transparent window).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:05:07 -05:00

288 lines
8.2 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "framebuffer.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#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_)
, width_(other.width_)
, height_(other.height_)
, isComplete_(other.isComplete_)
{
other.fbo_ = 0;
other.colorTexture_ = 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_;
width_ = other.width_;
height_ = other.height_;
isComplete_ = other.isComplete_;
other.fbo_ = 0;
other.colorTexture_ = 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);
// Check completeness
isComplete_ = checkComplete();
// Unbind
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 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 (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);
}
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