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>
288 lines
8.2 KiB
C++
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
|