QML app: How to capture camera image in-memory - qt

I have a QML-based app where I need to capture images from the camera in order to do QR-code recognition/decoding (using qzxing).
Following the example for the CameraCapture QML class, I can capture images, however they will always be saved to a local file. I don't want to save to a file since I don't want to stress the underlying storage (flash memory, sd card) in order to do QR code recognition.
Thus, I want to grab camera images in-memory. Googling for this turned out it seems to be impossible with QML-only.
So I was looking for a C++ based solution, however I cannot seem to come up with a working solution.
Currently I have code which tries to capture the image in C++ while providing a visible preview of the camera for the user in QML:
QML
ImageReader {
id: reader
}
Rectangle {
width: 400
height: 400
x: 30; y: 90
Camera {
id: camera
}
VideoOutput {
source: camera
focus: visible
anchors.fill: parent
}
}
Timer {
id: scanTimer
interval: 3000
running: true
repeat: false
onTriggered: {
console.log("capture image")
reader.startCapture()
}
}
C++
ImageReader::ImageReader(QObject *parent) : QObject(parent)
{
doCapture = true;
camera = new QCamera();
capture = new QCameraImageCapture(camera);
capture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
QObject::connect(capture, &QCameraImageCapture::imageCaptured, [=] (int id, QImage img) {
qDebug() << "Captured image";
camera->stop();
});
QObject::connect(capture, &QCameraImageCapture::readyForCaptureChanged, [=] (bool state) {
qDebug() << "Ready for capture changed: " << state;
if (!doCapture)
return;
if(state == true) {
qDebug() << "is ready for capture";
camera->searchAndLock();
capture->capture();
camera->unlock();
doCapture = false;
}
});
}
Q_INVOKABLE void ImageReader::startCapture()
{
camera->start();
}
There are several issues with this approach:
imageCaptured is never triggered, and the lambda is never called, so no image is captured
readyForCaptureChange is called, however, the preview (QML code) goes all white and never goes back to a live-video from the camera
in the console I get several warnings/the following output:
** (myapp:24700): WARNING **: 19:26:09.403: capsfilter2: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:09.406: capsfilter2: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:09.412: imagebin-capsfilter: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:09.426: viewfinderbin-capsfilter: transform_caps returned caps which are not a real subset of the filter caps
[D] onTriggered:65 - capture image
[W] unknown:0 - Starting camera without viewfinder available
** (myapp:24700): WARNING **: 19:26:12.463: capsfilter8: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:12.469: capsfilter8: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:12.483: imagebin-capsfilter: transform_caps returned caps which are not a real subset of the filter caps
** (myapp:24700): WARNING **: 19:26:12.495: viewfinderbin-capsfilter: transform_caps returned caps which are not a real subset of the filter caps
[D] ImageReader::ImageReader(QObject*)::<lambda:26 - Ready for capture changed: true
[D] ImageReader::ImageReader(QObject*)::<lambda:32 - is ready for capture
[D] ImageReader::ImageReader(QObject*)::<lambda:26 - Ready for capture changed: false
[D] ImageReader::ImageReader(QObject*)::<lambda:26 - Ready for capture changed: true
Can anyone guide me to a working solution to achieve the following:
show a preview (video) of the camera to the user
capture a picture from the camera so I can trigger QR-code decoding
must work with existing QML interface implementation (but can contain C++ code, no problem)
I am running this on a XPeria 10 ii phone with SailfishOS 4.1.0.24 installed. Thus, QT version should be 5.2.

You can access the QCamera using the mediaObject property of the Camera item and then use the QCameraImageCapture where the QCameraImageCapture::CaptureToBuffer mode is set and use the imageCaptured signal to get the QImage.
#include <QCamera>
#include <QCameraImageCapture>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class CameraHelper: public QObject{
Q_OBJECT
Q_PROPERTY(QObject* qcamera READ qcamera WRITE setQcamera NOTIFY qcameraChanged)
public:
using QObject::QObject;
QObject *qcamera() const{
return q_qcamera;
}
void setQcamera(QObject *qcamera)
{
if (q_qcamera == qcamera)
return;
if(m_capture){
m_capture->deleteLater();
}
q_qcamera = qcamera;
if(q_qcamera){
if(QCamera *camera = qvariant_cast<QCamera *>(q_qcamera->property("mediaObject"))){
m_capture = new QCameraImageCapture(camera, this);
m_capture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
connect(m_capture, &QCameraImageCapture::imageCaptured, this, &CameraHelper::handleImageCaptured);
}
else
m_capture = nullptr;
}
emit qcameraChanged();
}
public Q_SLOTS:
void capture(){
if(m_capture)
m_capture->capture();
}
Q_SIGNALS:
void qcameraChanged();
void imageChanged(const QImage & image);
private:
void handleImageCaptured(int , const QImage &preview){
Q_EMIT imageChanged(preview);
}
QPointer<QObject> q_qcamera;
QCameraImageCapture *m_capture = nullptr;
};
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
CameraHelper camera_helper;
QObject::connect(&camera_helper, &CameraHelper::imageChanged, [](const QImage & qimage){
qDebug() << qimage;
});
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("cameraHelper", &camera_helper);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
#include "main.moc"
import QtQuick 2.12
import QtQuick.Window 2.12
import QtMultimedia 5.15
Window {
width: 640
height: 480
visible: true
Camera {
id: camera
Component.onCompleted: cameraHelper.qcamera = camera
}
VideoOutput {
source: camera
focus : visible // to receive focus and capture key events when visible
anchors.fill: parent
MouseArea {
anchors.fill: parent;
onClicked: cameraHelper.capture();
}
}
}

Related

Qt: Track mouse position while QDrag is running

I am developing a Qt application with multiple windows and want to implement cross-window drag&drop functionality for some elements in my program.
To do so, I attach an event filter to the to-be-dragged QML elements and listen for the MousePress/MouseMove events to start the drag procedure as follows:
QDrag *drag = new QDrag(quickItem);
QMimeData* mimeData = new QMimeData();
mimeData->setText("Test");
drag->setHotSpot(QPoint(0, 0));
drag->setMimeData(mimeData);
drag->exec();
This works fine, but now I would like to show a little tooltip (being a QWidget) while dragging, following the mouse cursor and displaying a short text depending on the element the mouse is currently over (similar to the "Copy to ..." or "Move to..." labels appearing when you drag files around in Windows Explorer).
However, while dragging the element, I don't receive any MouseMove events neither on the QDrag object nor on the quickItem itself, which makes it impossible to track the mouse position. Since the mouse is grabbed during dragging, there should be some event in Qt that frequently reports the mouse position, no matter where on the screen the mouse is.
I am aware of the QDrag::setPixmap method, however this won't allow me to change my tooltip text during dragging and has some other limitations I would like to avoid.
Is there some way to listen to mouse move events while QDrag is running, without using platform-specific system APIs?
Update:
I don't think that there is way of doing this without using OS libraries nor getting the mouse position every X miliseconds. It looks like a really specific problem that Qt framework does not contemplate. You will need to write your own class to control this using win32 for windows, x11 for linux and the equivalent of Mac.
If you want to get the mouse position when your window is active and you are dragging something, check this:
Searching a bit I've found a solution for getting it when your window has the focus using QObject::eventFilter.
Create a class (for example EventListener) that inherits from QObject and overrides eventFilter and a method to set this as your qml window (which inherits from QObject) event filter with installEventFilter.
eventslistener.h:
#include <QEvent>
#include <QObject>
#include <QDebug>
#include <QDropEvent>
class EventsListener : public QObject
{
Q_OBJECT
public:
EventsListener(QObject * ptr) : QObject (ptr) {
}
Q_INVOKABLE void handleEventsOf(QObject *object) {
if (object)
object->installEventFilter(this);
}
bool eventFilter(QObject *object, QEvent *event) override {
if(event->type() == QEvent::DragMove) {
QDragMoveEvent *mouseEvent = static_cast<QDragMoveEvent*>(event);
qDebug() << "Mouse position dragging (x, y): (" << mouseEvent->pos().x() << ", " << mouseEvent->pos().y() << ")";
return false; //this is must return false or drop event will be handled by this method and drag&drop won't work correctly
}
return false;
}
};
Now we need to access to an instance (singleton in this case) of this class with qmlRegisterSingletonType. You may wish to use qmlRegisterType instead to register this eventlistener as a type (instead of a singleton) and use signals to notify directly qml the mouse position.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "eventlistener.h"
static QObject *eventsListenerInstance(QQmlEngine *qmlEngine, QJSEngine *engine)
{
return new EventsListener(engine);
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<EventsListener>("AppEventListener", 1, 0, "EventsListener", eventsListenerInstance);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import ...
import AppEventListener 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
id: item
property string display
property alias dropEnabled: acceptDropCB.checked
color: dropArea.containsDrag ? "#CFC" : "#EEE"
Component.onCompleted: EventsListener.handleEventsOf(item)
...
}

How can I reset a timer every time I receive a touch event from a qml page

import QtQuick 2.6;
import QtQuick.Controls 2.1 ;
import QtQuick.Layouts 1.3 ;
Page{
id: page
width: 800
height: 1024
background: Rectangle {
color: "black" ;
anchors.fill:parent ;
}
Rectangle {
id:rect1
x: 0
y:10
width: 100
height: 100
color : "red"
MouseArea {
anchors.fill: parent
onClicked: tmr.restart()
}
}
Rectangle {
id:rect2
x: 0
y:110
width: 100
height: 100
color : "blue"
MouseArea {
anchors.fill: parent
onClicked: tmr.restart()
}
}
Timer {
id : tmr
interval : 30000
repeat : true
running: true
onTriggered: {
console.log ("hello world ")
}
}
}
I develop a software for embedded imx6 freescale device using qt framework.
Basically I just want to restart my timer every time I click and every time I get a touch event on my screen whether the click/touch happen inside the mouse area of my rectangles or outside of them.
The idea is similar to a screensaver.
There are multiple ways, and the right way depends on your requirements.
If you don't need to guarantee that the timer triggers during a input you can just layer a MouseArea on top of everything. In this MouseArea you handle the pressed-signals, but dont accept them.
This allows you to handle the mouse input in the lower layers later. However you only realize whenever a new press happens, and the Timer might trigger e.g. during a half-an-hour finger-move input.
The second way is to have all MouseAreas report uppon their handled signals, that the signal happend, to reset the Timer. For all unhandled signals, you layer a MouseArea beneath everything else, handle all signals there to catch what has been falling through.
Resorting to C++ you might create a Item at the root of your Item-tree, and override the childMouseEventFitler
See my answer here for more on this.
In this case you should add a MouseArea right inside this Item, so it has something to filter at any place.
Note! This method will be triggered for each MouseArea that might be under your click. But in your scenario, this would be fine, I guess.
Thanks to GrecKo I looked into the general eventFilter again, and indeed it is really easy.
you create a simple QObject following the singleton pattern, in which you reimplement the eventFilter-method, so that it will emit a signal
mouseeventspy.h
#pragma once
#include <QObject>
#include <QtQml>
#include <QQmlEngine>
#include <QJSEngine>
class MouseEventSpy : public QObject
{
Q_OBJECT
public:
explicit MouseEventSpy(QObject *parent = 0);
static MouseEventSpy* instance();
static QObject* singletonProvider(QQmlEngine* engine, QJSEngine* script);
protected:
bool eventFilter(QObject* watched, QEvent* event);
signals:
void mouseEventDetected(/*Pass meaningfull information to QML?*/);
};
mouseeventspy.cpp
#include "mouseeventspy.h"
#include <QQmlEngine>
#include <QJSEngine>
#include <QEvent>
MouseEventSpy::MouseEventSpy(QObject *parent) : QObject(parent)
{
qDebug() << "created Instance";
}
// This implements the SINGLETON PATTERN (*usually evil*)
// so you can get the instance in C++
MouseEventSpy* MouseEventSpy::instance()
{
static MouseEventSpy* inst;
if (inst == nullptr)
{
// If no instance has been created yet, creat a new and install it as event filter.
// Uppon first use of the instance, it will automatically
// install itself in the QGuiApplication
inst = new MouseEventSpy();
QGuiApplication* app = qGuiApp;
app->installEventFilter(inst);
}
return inst;
}
// This is the method to fullfill the signature required by
// qmlRegisterSingletonType.
QObject* MouseEventSpy::singletonProvider(QQmlEngine *, QJSEngine *)
{
return MouseEventSpy::instance();
}
// This is the method is necessary for 'installEventFilter'
bool MouseEventSpy::eventFilter(QObject* watched, QEvent* event)
{
QEvent::Type t = event->type();
if ((t == QEvent::MouseButtonDblClick
|| t == QEvent::MouseButtonPress
|| t == QEvent::MouseButtonRelease
|| t == QEvent::MouseMove)
&& event->spontaneous() // Take only mouse events from outside of Qt
)
emit mouseEventDetected();
return QObject::eventFilter(watched, event);
}
Than you register it as singleton type to QML like this:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "mouseeventspy.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterSingletonType<MouseEventSpy>("MouseEventSpy", 1, 0, "MouseEventSpy", MouseEventSpy::singletonProvider);
// We do this now uppon creation of the first instance.
// app.installEventFilter(MouseEventSpy::instance());
engine.load(QUrl(QStringLiteral("main.qml")));
return app.exec();
}
Now in QML you can import the instance of the singleton in the necessary files and use the signal, e.g. to reset a Timer
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import MouseEventSpy 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Connections {
target: MouseEventSpy
onMouseEventDetected: myTimer.restart()
}
Timer {
id: myTimer
interval: 1000
onTriggered: console.log('It has been 1 seconds since the last mouse event')
}
Text {
anchors.center: parent
text: myTimer.running ? 'Timer is Running\nMove the mouse to reset'
: 'Move the Mouse to make the timer run again.'
}
}

Taking texture from Image and drawing it in QQuickFramebufferObject. Not all instances are rendered

What I'm doing, in short:
Deriving a MyItem class from QQuickFramebufferObject
In MyItem I have a QQuickItem* sourceItem property from which I fetch a texture and draw it in a triangle
From QML I supply an Image called sourceItem to MyItem. This Image has cache: false
When the Image finishes loading, I wait one frame and then call update on MyItem
I have a 6x6 grid of MyItem instances
The problem is that some of the instances of MyItem draw nothing:
Any ideas?
My code:
main.cpp:
#include <QQmlApplicationEngine>
#include <QGuiApplication>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QSGTextureProvider>
#include <QSGTexture>
#include <QQuickWindow>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include "propertyhelper.h" // this file is from http://syncor.blogspot.bg/2014/11/qt-auto-property.html
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
AUTO_PROPERTY(QQuickItem*, sourceItem)
public:
Renderer *createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
public:
MyItemRenderer() {
initializeOpenGLFunctions();
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
"in highp vec2 aPos;\
out highp vec2 vTexCoord;\
\
void main() {\
gl_Position = vec4(aPos, 0.0, 1.0);\
vTexCoord = aPos * .5 + .5;\
}"
);
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
"in highp vec2 vTexCoord;\
out vec4 outputColor;\
uniform sampler2D uTex;\
\
void main() {\
outputColor = texture(uTex, vTexCoord);\
}"
);
m_program.link();
m_program.setUniformValue("uTex", 0);
createGeometry();
}
void synchronize(QQuickFramebufferObject* qqfbo){
auto item = (MyItem*)qqfbo;
m_window = item->window();
m_tex = item->sourceItem()->textureProvider()->texture();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
void paintGeometry() {
m_program.enableAttributeArray("aPos");
m_program.setAttributeArray("aPos", m_vertices.constData());
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
m_program.disableAttributeArray("aPos");
}
void createGeometry() {
m_vertices << QVector2D(-1, -1);
m_vertices << QVector2D(1, -1);
m_vertices << QVector2D(-1, 1);
}
void render() {
glDisable(GL_DEPTH_TEST);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
m_program.bind();
glActiveTexture(GL_TEXTURE0);
m_tex->bind();
paintGeometry();
m_window->resetOpenGLState();
}
private:
QQuickWindow* m_window;
QVector<QVector2D> m_vertices;
QSGTexture* m_tex;
QOpenGLShaderProgram m_program;
};
QQuickFramebufferObject::Renderer *MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char** argv) {
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
id: window
width: 600
height: 600
Flow {
anchors.fill: parent
Repeater {
model: 36
delegate: MyItemWrapper {
width: 100
height: 100
}
}
}
}
MyItemWrapper.qml:
import QtQuick 2.5
import MyItem 1.0
Item {
Image {
x: -100000 // hide the sourceItem
id: sourceItem
layer.enabled: true
source: "https://images-na.ssl-images-amazon.com/images/M/MV5BMTg2MTMyMzU0M15BMl5BanBnXkFtZTgwOTU3ODk4NTE#._V1_SX300.jpg"
cache: false
function updateCppItemOnce() {
window.afterRendering.disconnect(updateCppItemOnce);
cppItem.update();
}
onStatusChanged: {
if (status == Image.Ready) {
window.afterRendering.connect(updateCppItemOnce);
}
}
}
MyItem {
sourceItem: sourceItem
anchors.fill: parent
id: cppItem
}
}
Notes:
I tried enabling OpenGL logging but got no messages
I've tried the testcase on 3 other PCs, all of them much weaker/slower than this one. The bug doesn't occur on them. So I think the bug happens only on fast PCs - it may be a timing problem.
I now realized that instead of the clunky way I was calling update(), I could just connect to the item->sourceItem()->textureProvider()->textureChanged() signal. Did that and now it works like a charm.
I'd still like to know why my original way fails, though :)
Gunnar Sletta provided the solution as a comment in my bugreport.
I suspect the problem is your use of the 'afterRendering' signal. When running on a threaded render loop this signal will fire when rendering is done and the slot will be called almost right away on the main thread. Depending on where the threaded render loop is when the image completes loading that means you'll have the slot invoked potentially right away. When the scene graph reaches the sync phase, the order in which nodes are processed is arbitrary, so some nodes may be processed before the image and some after. Those processed before the image gets a texture gets null and those processed after gets a valid texture.
It would be better to connect to afterSynchronization signal as this would actually fire first after the synchronization of the Image was guaranteed to have completed.
Indeed, using afterSynchronizing fixed my problem.

According to CodeXL, I'm not succeeding in disabling the ANGLE layer in Qt

I've taken all care to disable the ANGLE layer in my Qt app, but apparently it's not happening. When I run the app in the CodeXL debugger, the event log contains lines like:
DLL Loaded: C:\Windows\SysWOW64\d3d11.dll
So it's loading Direct3D, which in Qt only happens via ANGLE I think. Also hitting the "Break" button in CodeXL does nothing, which to me means that no real OpenGL calls are happening, they're getting translated to D3D only.
The event log also says this:
Debug String: Failed to load opengl32.dll (The specified module could not be found.)
Why might that happen, how can I fix it?
The reason I want to disable ANGLE is because otherwise I can't debug with CodeXL (it doesn't support D3D debugging).
My system:
Windows 10
First GPU: Intel HD Graphics 5500
Second GPU: AMD Radeon R5 M330 (I think this is the one my app uses)
My code:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickFramebufferObject>
#include <QOpenGLShaderProgram>
#include <QOpenGLFramebufferObject>
#include <QQuickWindow>
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
public:
Renderer* createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer {
public:
void render() {
update();
}
QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
};
QQuickFramebufferObject::Renderer* MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char **argv) {
qputenv("QT_OPENGL_BUGLIST", "Z:/disable_angle.txt");
QGuiApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
QGuiApplication app(argc, argv);
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.0
import MyItem 1.0
import QtQuick.Window 2.2
Window {
visible: true
width: 400
height: 400
MyItem {
anchors.fill: parent
}
}
Z:/disable_angle.txt:
{
"entries": [
{
"id": 1,
"description": "Disable angle",
"os": {
"type": "win"
},
"features": [
"disable_angle"
]
}
]
}
Set QT_LOGGING_RULES environment variable to 'qt.qpa.gl=true'. Thus, you will see some additional debug output that will help to understand what exactly Qt chooses (opengl/angle/software).
Try changing Z:/disable_angle.txt to Z:\disable_angle.txt
Note the backslash :) It's Windows, so this may be the culprit.
Apart from disable_angle (your config seems correct to me), try also setting disable_d3d11 and disable_d3d9.
"disable_angle", "disable_d3d11", "disable_d3d9"
If you want to completely disable Angle, you should set the application attribute Qt::AA_UseDesktopOpenGL. If this is not enough, linking against OpenGL32.lib might help.

QOpenGLTexture::allocateStorage causes GL_INVALID_VALUE error

I've distilled this to trivial Qt code in which the only interesting thing is several lines in synchronize() handling a texture. I get a GL_INVALID_VALUE from the allocateStorage line. Anyone knows why? I suspect it's probably due to the parameters I pass to this function.
My code:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
public:
Renderer* createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
public:
MyItemRenderer() {
initializeOpenGLFunctions();
}
void render() {
}
QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
protected:
void synchronize(QQuickFramebufferObject* qqfbo) {
Q_UNUSED(qqfbo)
QOpenGLTexture tex(QOpenGLTexture::Target2D);
tex.setSize(100, 100);
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::Int8);
qDebug() << "starting loop";
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
qDebug("\tgl error: 0x%x", err, 0, 16);
}
}
};
QQuickFramebufferObject::Renderer* MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char **argv) {
QGuiApplication app(argc, argv);
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.0
import MyItem 1.0
import QtQuick.Window 2.2
Window {
visible: true
width: 400
height: 400
MyItem {
anchors.fill: parent
}
}
After looking at the source of QOpenGLTexture::createMutableTexture and QOpenGLTexture::allocateStorage() (no-argument version) I saw that two-argument allocateStorage overload is really, really misleading. The docs make it seem like its 2 args will be used as the internal format of the texture (3rd arg of the glTexImage2D call in the implementation), when in fact they're used only as the "source-data-to-transfer-from-system-RAM" format/type (7th and 8th args of glTexImage2D), which, in the case of passing a null pointer to glTexImage2D, don't matter at all except in fringe OpenGL ES 2 cases.
The way to actually request a certain internal format for the texture is to call tex.setFormat before calling tex.allocateStorage. So I replaced my allocateStorage line with this:
tex.setFormat(QOpenGLTexture::RGBA8_UNorm);
tex.allocateStorage();
And that fixed the error.
You have...
QOpenGLTexture tex(QOpenGLTexture::Target2D);
According to the docs that doesn't actually create the underlying texture object. I think you then need...
tex.create();
before setting the size and allocating storage. So...
QOpenGLTexture tex(QOpenGLTexture::Target2D);
if (!tex.create()) {
/*
* Take appropriate action.
*/
}
tex.setSize(100, 100);
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::Int8);
Edit 1:
Actually, looking at the code it appears that tex.allocateStorage should allocate a texture id if necessary. However, if that fails for some reason (no current context for example) then that could be causing the problem you're seeing.
During "allocatestorage", You are conveying opeGL driver about the texture storage format.
For the formats "GL_BGRA or GL_RGBA", OpenGL recommends "GL_UNSIGNED_BYTE",
which is in Qt ----- "QOpenGLTexture::BGRA" (for GL_BGRA) and "QOpenGLTexture::UInt8" (for GL_UNSIGNED_BYTE).
https://www.khronos.org/opengl/wiki/Textures_-_more
So your allocate storage function based on (openGL recommendation) should be
tex.allocateStorage(QOpenGLTexture::BGRA, QOpenGLTexture::UInt8);

Resources