refactor(grabber): Simplify frame scaling by removing OpenGL

Replaced the complex, low-level OpenGL pipeline in HyperionGrabber with a simple, high-level `QImage::scaled()` call.

This change significantly reduces code complexity and improves maintainability without sacrificing performance, as `QImage::scaled()` is highly optimized and often hardware-accelerated. The new implementation is more robust and easier to understand.

This resolves the following issues:
- Removes fragile, hard-to-debug OpenGL code.
- Eliminates potential bugs related to FBOs, shaders, and texture handling.
- Corrects the image data pipeline to ensure raw pixel data is processed consistently.
This commit is contained in:
Tobias J. Endres 2025-08-15 00:41:07 +02:00
parent be1342393e
commit 4f8d53fedb
2 changed files with 16 additions and 166 deletions

View File

@ -4,15 +4,6 @@
#include <QGuiApplication>
#include <QBuffer>
#include <QImage>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOffscreenSurface>
// public
@ -61,58 +52,6 @@ HyperionGrabber::HyperionGrabber(QHash<QString, QString> opts)
_hclient_p = new HyperionClient(addr, port, _hyperionPriority_m);
_hclient_p->ledAdjustments(redAdjust, greenAdjust, blueAdjust, temperature, threshold, transform);
// Initialize OpenGL for hardware acceleration
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGL);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setVersion(3, 3); // Or higher, depending on your OpenGL version
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setRedBufferSize(8);
format.setGreenBufferSize(8);
format.setBlueBufferSize(8);
format.setAlphaBufferSize(8);
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
_offscreenSurface = new QOffscreenSurface();
_offscreenSurface->setFormat(format);
_offscreenSurface->create();
_context = new QOpenGLContext(this);
_context->setFormat(format);
_context->create();
_context->makeCurrent(_offscreenSurface);
_functions = _context->functions();
_functions->initializeOpenGLFunctions();
// Shaders for scaling
const char *vertexShaderSource = R"(
attribute highp vec4 vertices;
attribute highp vec2 texCoords;
varying highp vec2 v_texCoords;
void main() {
gl_Position = vertices;
v_texCoords = texCoords;
}
)";
const char *fragmentShaderSource = R"(
uniform sampler2D textureSampler;
varying highp vec2 v_texCoords;
void main() {
gl_FragColor = texture2D(textureSampler, v_texCoords);
}
)";
_shaderProgram = new QOpenGLShaderProgram(this);
_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
_shaderProgram->link();
_fbo = new QOpenGLFramebufferObject(1, 1, QOpenGLFramebufferObject::CombinedDepthStencil); // Placeholder size, will be resized later
_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); // Placeholder, will be created from QVideoFrame
_waylandGrabber_p = new WaylandGrabber(this);
connect(_waylandGrabber_p, &WaylandGrabber::frameReady, this, &HyperionGrabber::_processFrame);
_waylandGrabber_p->start();
@ -140,19 +79,6 @@ HyperionGrabber::~HyperionGrabber()
delete _timer_p;
}
delete _hclient_p;
// OpenGL cleanup
if (_context) {
_context->makeCurrent(_offscreenSurface);
delete _fbo;
delete _shaderProgram;
delete _texture;
_context->doneCurrent();
delete _context;
}
if (_offscreenSurface) {
delete _offscreenSurface;
}
}
// private
@ -197,95 +123,33 @@ void HyperionGrabber::_processFrame(const QVideoFrame &frame)
return;
}
_context->makeCurrent(_offscreenSurface);
// Convert QVideoFrame to QImage
QImage inputImage = frame.toImage();
if (inputImage.isNull()) {
QImage image = frame.toImage();
if (image.isNull()) {
qWarning() << "Failed to convert QVideoFrame to QImage.";
_context->doneCurrent();
return;
}
// qDebug() << "Input QImage dimensions:" << inputImage.width() << "x" << inputImage.height();
// qDebug() << "Input QImage format:" << inputImage.format();
// Resize FBO if needed
QSize targetSize(inputImage.width() / _scale_m, inputImage.height() / _scale_m);
if (_fbo->size() != targetSize) {
delete _fbo;
_fbo = new QOpenGLFramebufferObject(targetSize, QOpenGLFramebufferObject::CombinedDepthStencil);
// Calculate target size
QSize targetSize(image.width() / _scale_m, image.height() / _scale_m);
if (!targetSize.isValid()) {
qWarning() << "Invalid target size for scaling.";
return;
}
_fbo->bind();
_functions->glViewport(0, 0, _fbo->width(), _fbo->height());
// Scale the image using Qt's optimized scaling
QImage scaledImage = image.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
// qDebug() << "FBO size:" << _fbo->size();
_functions->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
_functions->glClear(GL_COLOR_BUFFER_BIT);
// Create texture from QImage
if (_texture->isCreated()) {
_texture->destroy();
// Convert to a format suitable for Hyperion (RGB888)
if (scaledImage.format() != QImage::Format_RGB888) {
scaledImage = scaledImage.convertToFormat(QImage::Format_RGB888);
}
_texture->create();
_texture->setData(inputImage.flipped(Qt::Vertical)); // Flip vertically
// qDebug() << "Texture size after setData:" << _texture->width() << "x" << _texture->height();
_shaderProgram->bind();
_shaderProgram->setUniformValue("textureSampler", 0); // Texture unit 0
// Vertices for a quad
GLfloat vertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
// Texture coordinates (flipped vertically for QImage top-left origin)
GLfloat texCoords[] = {
0.0f, 1.0f, // Top-left of texture (maps to bottom-left of quad)
1.0f, 1.0f, // Top-right of texture (maps to bottom-right of quad)
0.0f, 0.0f, // Bottom-left of texture (maps to top-left of quad)
1.0f, 0.0f, // Bottom-right of texture (maps to top-right of quad)
};
_shaderProgram->setAttributeArray("vertices", vertices, 2);
_shaderProgram->enableAttributeArray("vertices");
_shaderProgram->setAttributeArray("texCoords", texCoords, 2);
_shaderProgram->enableAttributeArray("texCoords");
_functions->glActiveTexture(GL_TEXTURE0);
_texture->bind();
_functions->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
_shaderProgram->disableAttributeArray("vertices");
_shaderProgram->disableAttributeArray("texCoords");
_shaderProgram->release();
_fbo->release();
// Read pixels back from FBO
QImage scaledImage = _fbo->toImage();
scaledImage = scaledImage.flipped(Qt::Vertical); // Flip vertically again if needed
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
scaledImage.save(&buffer, "PNG"); // Save as PNG for now, can be optimized later
int rawDataSize = scaledImage.sizeInBytes();
// Update the image size in the client and send
_hclient_p->setImgSize(scaledImage.width(), scaledImage.height());
_hclient_p->sendImage(scaledImage.constBits(), rawDataSize);
_context->doneCurrent();
_hclient_p->sendImage(scaledImage.constBits(), scaledImage.sizeInBytes());
}
void HyperionGrabber::_setImgSize(int width, int height)
{
_hclient_p->setImgSize(width, height);
}
}

View File

@ -1,17 +1,10 @@
#include <QObject>
#include <QTimer>
#include <QImage>
#include <QVideoFrame>
#include "hyperionclient.h"
#include "WaylandGrabber.h"
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QVideoFrame>
#include <QOffscreenSurface>
class HyperionGrabber : public QObject
{
@ -31,13 +24,6 @@ private:
unsigned short _frameskip_m = 0;
long long _frameCounter_m = 0;
QOpenGLContext *_context;
QOpenGLFramebufferObject *_fbo;
QOpenGLShaderProgram *_shaderProgram;
QOpenGLTexture *_texture;
QOpenGLFunctions *_functions;
QOffscreenSurface *_offscreenSurface;
QString _parseColorArr(QString, bool);
private slots: