从 Image 中获取纹理并在 QQuickFramebufferObject 中绘制它。并非所有实例都被渲染

Posted

技术标签:

【中文标题】从 Image 中获取纹理并在 QQuickFramebufferObject 中绘制它。并非所有实例都被渲染【英文标题】:Taking texture from Image and drawing it in QQuickFramebufferObject. Not all instances are rendered 【发布时间】:2017-08-24 14:26:13 【问题描述】:

简而言之,我在做什么

QQuickFramebufferObject 派生MyItem 类 在MyItem 中,我有一个QQuickItem* sourceItem 属性,我从中获取纹理并将其绘制成三角形 我从 QML 向 MyItem 提供了一个名为 sourceItem 的图像。这张图片有cache: false 当图像完成加载后,我等待一帧然后在MyItem 上调用update 我有一个由 MyItem 实例组成的 6x6 网格

问题是MyItem 的一些实例什么也没画:

有什么想法吗?

我的代码:

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
    

注意事项:

我尝试启用 OpenGL 日志记录,但没有收到任何消息 我已经在其他 3 台 PC 上尝试过测试用例,它们都比这台更弱/慢得多。该错误不会发生在他们身上。所以我认为这个错误只发生在速度很快的 PC 上——它可能是一个时间问题。

【问题讨论】:

这怎么可能是题外话? ​@StefanMonov 从规则中:“寻求调试帮助的问题(“为什么这段代码不起作用?”)必须包括所需的行为、特定的问题或错误以及 i> 所需的最短代码 在问题本身中重现它。没有明确问题陈述的问题对其他读者没有用处。请参阅:如何创建最小、完整和可验证的示例。 ”(强调我的) @DonaldDuck:我知道这条规则。事实上,我已经付出了很多努力来使测试用例的长度最小化。看起来并非如此的原因是重现我的问题需要使用低级 API (OpenGL),即使是最简单的事情也需要大量样板。这是不可避免的。同样重要的是用于将 Qt 与 OpenGL 集成的样板。最后,我意识到即使是我的最小测试用例也很长,因此我在顶部包含了“我在做什么,简而言之”项目符号列表,以方便理解。 【参考方案1】:

我现在意识到,我可以直接连接到item-&gt;sourceItem()-&gt;textureProvider()-&gt;textureChanged() 信号,而不是我调用update() 的笨拙方式。这样做了,现在它就像一个魅力。

不过,我仍然想知道为什么我原来的方法会失败 :)

【讨论】:

【参考方案2】:

Gunnar Sletta 在my bugreport 中以评论的形式提供了解决方案。

我怀疑问题在于您使用了“afterRendering”信号。当在线程渲染循环上运行时,该信号将在渲染完成时触发,并且几乎立即在主线程上调用插槽。取决于图像完成加载时线程渲染循环的位置,这意味着您可能会立即调用插槽。当场景图到达同步阶段时,节点的处理顺序是任意的,所以可能有的节点在图像之前处理,有的在图像之后。在图像获得纹理之前处理的那些会为空,而在图像获得纹理之后处理的那些会获得有效的纹理。

最好连接到 afterSynchronization 信号,因为它实际上会在保证图像同步完成后首先触发。

确实,使用afterSynchronizing 解决了我的问题。

【讨论】:

以上是关于从 Image 中获取纹理并在 QQuickFramebufferObject 中绘制它。并非所有实例都被渲染的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL-在一个函数中加载多重纹理

WebGL 创建纹理

从图库中获取图像并在 ImageView 中显示

Three.js 从图像文件创建纹理

模块中的搅拌机“纹理不可用”第 35 行

使用 3Dsmax 进行纹理烘焙