Unsolved Render Custom Texture to QQuickWindow
-
Hi,
My application is supposed to render an opengl texture I manually created in a QQuick Window.
The code consists of a class TextureProvider, which inherits from QQuickItem and is instantiated in QML. The class reimplements the updatePaintNode(...) routine to add my texture node and create image data.
The texture is created in a custom GLTexture class, that subclasses QSGTexture. Everytime the updatePaintNode(...) routine of TextureProvider is called, I create a new grayscale texture with slightly increased intensities and try to render it to a slightly different position. It seems like everything is working as expected, the rendering thread calls the bind method of my texture class during rendering, and the texture is also valid (I can download it and wrap it in a qt image inside the bind function).
In my application window I can only see a black square instead of a grayscale one though.
This maybe a little confusing, so please have a look at my minimal example to get a better understanding of the problem:
In case you don´t have Opengl32.lib, you can leave it out. I only need it in textureprovider.h:108 to to get the pixel data from the texture and save it to disk. Uncomment the line/block and it should be fine.TEMPLATE = app QT += quick qml gui SOURCES += main.cpp \ textureprovider.cpp HEADERS += textureprovider.h LIBS += Opengl32.lib RESOURCES += rendercontrol.qrc
main.cpp
#include <QGuiApplication> #include "textureprovider.h" #include <QQmlApplicationEngine> #include <QSurfaceFormat> int main(int argc, char **argv) { QGuiApplication app(argc, argv); QSurfaceFormat sfc; sfc.setMajorVersion(3); sfc.setMinorVersion(3); sfc.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(sfc); qmlRegisterType<DUMMY::TextureProvider>("dummy.texture.provider", 1 ,0, "GLTextureSrc"); QQmlApplicationEngine eng(QUrl("qrc:/rendercontrol/demo.qml")); return app.exec(); }
textureprovider.h
#ifndef TEXTUREPROVIDER_H #define TEXTUREPROVIDER_H #include <QQuickItem> #include <QSGTexture> #include <QtGui/QOpenGLFunctions_3_3_Core> #include <QOpenGLFunctions> #include <QTimer> #include <gl/GL.h> #include <vector> constexpr int BYTE_MAX = 255; namespace DUMMY { class GLTexture : public QSGTexture { public: GLTexture(bool recreate = false) : m_width (300) , m_height(300) , m_data_gray(std::vector<unsigned char>(m_width * m_height * 4, (unsigned char)200)) { m_texsz = QSize(m_width, m_height); if(recreate) createTextures(true); } void createTextures(bool recreate = false) { static int pxl_val; m_api = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>(); if(m_texid0 == 0 || recreate) { // create data for the texture -> grayscale image with increasing intensities // Modulo BYTE_MAY since to ensure pixel values are in range [0; 255] m_data_gray = std::vector<unsigned char>(m_width * m_height * 4, (unsigned char)(pxl_val % BYTE_MAX)); pxl_val+=20; m_api->initializeOpenGLFunctions(); m_api->glGenTextures(1, &m_texid0); m_api->glBindTexture(GL_TEXTURE_2D, m_texid0); m_api->glTexImage2D(GL_TEXTURE_2D , 0 , GL_RGBA , m_width , m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , m_data_gray.data()); m_api->glBindTexture(GL_TEXTURE_2D, 0); } } bool hasAlphaChannel() const override { return true; } bool hasMipmaps() const override { return false; } int textureId() const override { return m_texid0; } QSize textureSize() const override { return m_texsz; } void bind() override { auto glcontext = QOpenGLContext::currentContext(); assert(glcontext); auto fence0 = m_api->glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); m_api->glClientWaitSync(fence0, 0, 2000000000); //timeout 2s m_api->glBindTexture( GL_TEXTURE_2D, m_texid0 ); /* save image to disk -> this is working! */ { static int cnt; std::vector<uchar> vec(m_width * m_height * 4, (uchar)0); ::glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, vec.data()); QImage im(vec.data(), m_width, m_height, QImage::Format_RGBA8888); im.save(std::string("image_" + std::to_string(cnt++) + ".png").c_str()); } } QOpenGLFunctions_3_3_Core* m_api; QSize m_texsz; int m_width; int m_height; std::vector<unsigned char> m_data_gray; GLuint m_texid0 = 0; }; class TextureProvider : public QQuickItem { public: TextureProvider(); ~TextureProvider(); protected: QSGNode* updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override; DUMMY::GLTexture* m_tex; QTimer m_timer; }; } #endif // TEXTUREPROVIDER_H
textureprovider.cpp
#include "textureprovider.h" #include <QSGSimpleRectNode> #include <QSGSimpleTextureNode> #include <QQuickWindow> DUMMY::TextureProvider::TextureProvider() : m_tex(new DUMMY::GLTexture()) { setFlag(TextureProvider::ItemHasContents, true); m_timer.setInterval(128); connect(&m_timer, &QTimer::timeout, this, &QQuickItem::update); m_timer.start(); } DUMMY::TextureProvider::~TextureProvider() { delete m_tex; } QSGNode* DUMMY::TextureProvider::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) { QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node); if (!n) { n = new QSGSimpleTextureNode(); n->setOwnsTexture(true); auto tex = new DUMMY::GLTexture(true); n->setTexture(tex); } auto t = static_cast<DUMMY::GLTexture*>(n->texture()); // create new texture for rendering t->createTextures(true); static int offset; if(offset > 300) offset = 0; //Apply offset to rectangle to see what happens on the screen n->setRect(offset , offset , m_tex->textureSize().width() , m_tex->textureSize().height()); offset+=20; n->markDirty(QSGNode::DirtyMaterial); return n; }
demo.qml
import QtQuick 2.0 import QtQuick.Window 2.0 import dummy.texture.provider 1.0 Window { visible: true minimumWidth: 1024 minimumHeight: 768 GLTextureSrc{ id: texSrc width: 300 height: 300 objectName: "texture_ren" } }
rendercontrol.qrc
<RCC> <qresource prefix="/rendercontrol"> <file>demo.qml</file> </qresource> </RCC>
This is what my output window looks like.
This is a sequence of recttangles I saved inside the bind() function (line 104 - 124 inside textureprovider.h) to disk and what the rectangle in the window should actually look like:
Why can I donwload the pixel data from the texture inside the bind function (line 104 - 124 inside textureprovider.h) but I don´t get the expected output to the window? The texture seems to be valid in the redering thread and was created using the correct context.
Any help is much appreciated!
Thanks!My system config:
Windows 10
QT 5.12.5
I tested on an AMD RADEON PRO WX5100 as well as on an Intel HD Graphics 630 gpu.Best,
-
@tssm said in Render Custom Texture to QQuickWindow:
n->setRect(offset
, offset
, m_tex->textureSize().width()
, m_tex->textureSize().height());Have you set the source rect? I had issues in my code with that:
n->setSourceRect(0 , 0 , m_tex->textureSize().width() , m_tex->textureSize().height());
I don't know about the opengl part. You could create a texture using an image as a canvas and use QPainter on it to test there isn't a problem with your QSG code. I have not done very much opengl using Qt itself.
QImage canvas(rect.width(), rect.height(), QImage::Format_RGBA8888); canvas.fill(QColor("transparent")); QPainter painter(&canvas); QFont font = painter.font(); //font.setPixelSize(48); font.setPixelSize(rect.width()); font.setBold(true); painter.setFont(font); painter.setPen(color); QRect bounding = QRect(0, 0, rect.width(), rect.height()); painter.drawText(0, 0, rect.width(), rect.height(), Qt::AlignCenter, ch, &bounding); QSGTexture *texture = this->window()->createTextureFromImage(canvas);
-
Thanks for the hint! I tried and you are right. It is working when creating the texture as a canvas. The
n->setSourceRect(0 , 0 , m_tex->textureSize().width() , m_tex->textureSize().height());
function does not influence the result. That means my implementation of QSGTexture is the problem, but I don´t see how to narrow down the search from there...
-
Did you ever got this fixed?