Qt 6.6.1 Android. glReadPixels reads the pixel color from wrong position
-
Hello,
I read the pixel color and print it to the console. It looks like triangle is not on his place:
main.cpp
#include <QtGui/QOpenGLFunctions> #include <QtGui/QMouseEvent> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGLWidgets/QOpenGLWidget> #include <QtWidgets/QApplication> #include <QtWidgets/QLabel> #include <QtWidgets/QVBoxLayout> #include <QtWidgets/QWidget> class OpenGLWidget : public QOpenGLWidget, private QOpenGLFunctions { Q_OBJECT private: void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.f, 1.f, 0.f, 1.f); QString vertexShaderSource = "attribute vec2 aPosition;\n" "void main()\n" "{\n" " gl_Position = vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragmentShaderSource = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "//out vec4 fragColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n"; m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertexShaderSource); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragmentShaderSource); m_program.link(); m_program.bind(); float vertPositions[] = { -0.5f, -0.5f, 0.5f, -0.5f, 0.f, 0.5f }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_aPositionLocation = m_program.attributeLocation("aPosition"); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); m_program.setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program.enableAttributeArray(m_aPositionLocation); glDrawArrays(GL_TRIANGLES, 0, 3); if (m_mouseClicked) { // Read the pixel GLubyte pixel[4]; glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f; m_mouseClicked = false; } } void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x(); m_mouseY = height() - event->pos().y() - 1; m_mouseClicked = true; update(); } private: int m_mouseX; int m_mouseY; bool m_mouseClicked = false; QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_aPositionLocation; }; class MainWindow : public QWidget { Q_OBJECT public: MainWindow() { setWindowTitle("OpenGL, Qt6, C++"); resize(300, 300); m_nameLabel = new QLabel("Click on object or background"); m_nameLabel->setSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed); OpenGLWidget *openGLWidget = new OpenGLWidget(); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(m_nameLabel); layout->addWidget(openGLWidget); setLayout(layout); } private: QLabel *m_nameLabel; }; #include "main.moc" int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow w; w.show(); return app.exec(); }
pro
QT += core gui openglwidgets widgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ main.cpp TARGET = app
Cross-refs:
-
I tried to call
glGetError()
afterglReadPixels()
but it returns 0.glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); qDebug() << glGetError();
-
I created the bug report: https://bugreports.qt.io/browse/QTBUG-120648
-
I attacked a zip file of the example to the bug report above. You can try to run on real device and the Android emulator. This example works as expected on Desktop and WebAssembly. It prints a color in mouse click position:
-
I tried to make an offset with
m_mouseX + width() / 2.f
for X andm_mouseY + height() / 2.f
forglReadPixels()
. It is close on Android but it is not enough.void paintGL() override { glClearColor(0.2f, 0.2f, 0.2f, 1.f); glClear(GL_COLOR_BUFFER_BIT); m_program.bind(); m_vertPosBuffer.bind(); m_program.setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program.enableAttributeArray(m_aPositionLocation); glDrawArrays(GL_TRIANGLES, 0, 3); // qDebug() << glGetError() << "\n"; if (m_mouseClicked) { // Read the pixel GLubyte pixel[4]; glReadPixels(m_mouseX + width() / 2.f, m_mouseY + height() / 2.f, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); // qDebug() << glGetError() << "\n"; qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f << "\n"; qDebug() << m_mouseX << m_mouseY << "\n"; m_mouseClicked = false; } } void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x(); m_mouseY = height() - event->pos().y() - 1; m_mouseClicked = true; update(); }
-
@jeremy_k I tried to print this value:
qDebug() << devicePixelRatio();
It shows 1. I use
QWidget::devicePixelRatio()
becauseQOpenGLWidget
inherits fromQWidget
. I didn't useQScreen
. I thinkQWidget::devicePixelRatio()
andQScreen::devicePixelRatio()
is the same. But I see that I cannot set this value. It is a read-only value. I see in docs that I can set DPI to 2 by running an app from the consoleQT_SCALE_FACTOR=2 ./myapp
but I did find how to set it to 2 for Android. -
QWidget's devicePixelRatio is really QPaintDevice::devicePixelRatio(). Given the different painting path, I don't know if that is reliable in this situation. Checking both the paint device and the screen is a cheap development-time test.
The DPI isn't intended to be configured by application code. It's detected via the platform plugin.
-
@jeremy_k I was wrong. Unfortunately, "adding half a screen to the coordinates of glReadPixels" is not close. I added a projection matrix and the second triangle to my example. You can see how far a real triangle from what glReadPixels reads:
-
I rewrote the first example to JavaScript/WebGL and built it to APK with using Cordova. It works without problems with
glReadPixels
. But EXE with Cordova requires 150 MB. I don't like it. I think it is good idea to use Qt for Desktop/WebAssembly and WebGL/Cordova for Android and Web.> npm install cordova -g > npm install http-server -g > cordova create read-pixels-webgl-js io.github.ivan_8observer8.read_pixels AppClass > emulator.exe -avd GalaxyNexus > cordova build android > cd platforms\android\app\build\outputs\apk\debug > adb install app-debug.apk > adb logcat "eglCodecCommon:S"
If you changed a source code you should to rebuild an app for Android:
cordova build android
, uninstall the app:adb uninstall io.github.ivan_8observer8.read_pixels
and install it again:adb install app-debug.apk
Demo: https://replit.com/@8Observer8/read-pixels-webgl-js
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover"> <link rel="stylesheet" type="text/css" href="css/style.css"> <title>Read Pixels</title> </head> <body> <canvas id="renderCanvas" width="350" height="350"></canvas> <script type="module" src="./js/index.js"></script> </body> </html>
js/index.js
import { gl, initWebGLContext } from "./webgl-context.js"; function init() { console.log("------------------init------------------"); if (!initWebGLContext("renderCanvas")) return; gl.clearColor(0.2, 0.2, 0.2, 1.0); const vertexShaderSource = ` attribute vec2 aPosition; void main() { gl_Position = vec4(aPosition, 0.0, 1.0); }`; const fragmentShaderSource = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }`; const vShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vShader, vertexShaderSource); gl.compileShader(vShader); const fShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fShader, fragmentShaderSource); gl.compileShader(fShader); const program = gl.createProgram(); gl.attachShader(program, vShader); gl.attachShader(program, fShader); gl.linkProgram(program); gl.useProgram(program); const vertices = new Float32Array([ 0, 0.5, -0.5, -0.5, 0.5, -0.5 ]); const vbo = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vbo); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); const aPositionLocation = gl.getAttribLocation(program, "aPosition"); gl.vertexAttribPointer(aPositionLocation, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aPositionLocation); window.onresize = () => { const w = gl.canvas.clientWidth; const h = gl.canvas.clientHeight; gl.canvas.width = w; gl.canvas.height = h; gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); draw(); }; window.onresize(null); gl.canvas.onclick = (event) => { const rect = event.target.getBoundingClientRect(); const x = event.clientX - rect.left; const y = rect.bottom - event.clientY - 1; draw(); const pixels = new Uint8Array(4); gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels); const output = `${(pixels[0] / 255.0).toFixed(2)} ${(pixels[1] / 255.0).toFixed(2)} ${(pixels[2] / 255.0).toFixed(2)} `; // console.log( // (pixels[0] / 255.0).toFixed(2), // (pixels[1] / 255.0).toFixed(2), // (pixels[2] / 255.0).toFixed(2)); console.log(output); }; } function draw() { gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, 3); } init();
js/webgl-context.js
export let gl = null; export function initWebGLContext(canvasName) { const canvas = document.getElementById(canvasName); if (canvas === null) { console.log(`Failed to get a canvas element with the name "${canvasName}"`); return false; } gl = canvas.getContext("webgl", { alpha: false, premultipliedAlpha: false }); return true; }
css/style.css
html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; } #renderCanvas { width: 100%; height: 100%; }
-
@jeremy_k you was right! Thank you very much! You showed me a way and I found a solution in comments here: OpenGL support broken with high-dpi (Retina) on OS X. It is for macOS but it is true for Android too. The comment of Laszlo Agocs helped me:
You need to adjust the GL positions based on the devicePixelRatio(). If the window is size N,M and devicePixelRatio() is 2 then the GL framebuffer, viewport will all have a size of 2N,2M. Try multiplying mouseX and mouseY with devicePixelRatio().
Solution:
void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x() * devicePixelRatio(); m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio(); m_mouseClicked = true; update(); }
main.cpp
#include <QtGui/QMouseEvent> #include <QtGui/QOpenGLFunctions> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { int m_mouseX; int m_mouseY; bool m_mouseClicked = false; QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.2f, 0.2f, 0.2f, 1.f); qDebug() << "Device pixel ratio:" << devicePixelRatio(); QString vertexShaderSource = "attribute vec2 aPosition;\n" "void main()\n" "{\n" " gl_Position = vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragmentShaderSource = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "void main()\n" "{\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertexShaderSource); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragmentShaderSource); m_program.link(); m_program.bind(); float vertPositions[] = { -0.5f, -0.5f, 0.5f, -0.5f, 0.f, 0.5f }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); if (m_mouseClicked) { // Read the pixel GLubyte pixel[4]; glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); // qDebug() << glGetError() << "\n"; qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f; m_mouseClicked = false; } } void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x() * devicePixelRatio(); m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio(); m_mouseClicked = true; update(); } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
pick-color-of-simple-triangle-qopenglwindow-qt6-cpp.pro
QT += core gui opengl widgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ main.cpp TARGET = app
-
-