EsoErik

Thursday, December 31, 2015

 

Doing anything significant whatsoever with QML: index

Doing anything significant whatsoever with QML, part 5
Doing anything significant whatsoever with QML, part 4
Doing anything significant whatsoever with QML, part 3
Doing anything significant whatsoever with QML, part 2
Doing anything significant whatsoever with QML, part 1

Complete source as it stood at the end of part 5.

Wednesday, December 30, 2015

 

Doing anything significant whatsoever with QML, part 5: ThirtyBitSGContext plugin and ThirtyBitSGLayer

As things stand at the end of part 4, uncommenting the layer.enabled: true line in the following QML results in resampling to 24-bit color, while leaving it commented preserves 30-bit color:

ThirtyBitImageItem {
    id: thirtyBitImageItem
    objectName: "thirtyBitImageItem"
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.top
    anchors.bottom: parent.bottom
    width: (implicitWidth / implicitHeight) * height
    anchors.margins: 10
//    layer.enabled: true
}


Fixing this requires understanding the effect of that layer.enabled: true QML statement. Let's see what the Qt documentation has to say:
layer.enabled : bool
Holds whether the item is layered or not. Layering is disabled by default.
A layered item is rendered into an offscreen surface and cached until it is changed. Enabling layering for complex QML item hierarchies can sometimes be an optimization.
None of the other layer properties have any effect when the layer is disabled.

Good documentation; a normal person could read that and understand it. In other words, the documentation is irrelevant to our situation. We must turn to the Qt source code itself, but where do we look? It's easy to spot the QObject half of a layer (QQuickItemLayer in qtdeclarative/src/quick/items/qquickitem_p.h). However, locating the scene graph half requires detective work. If the layer scene-graph-node-class's name follows convention, it will be QSGLayer. However, there is no file named qsglayer.cpp in the Qt5 source tree. Perhaps, wherever QSGLayer is defined, it's a pure virtual interface class? In that case, finding it wouldn't help, and the Qt5 source tree is rather large, so I don't feel like grepping through it anyway. Munging around with a C++ debugger attached to a minimal QML application with a single item having layer.enabled: true reveals that the layer.enabled: true statement ultimately causes invocation of QSGLayer *QSGContext::createLayer(QSGRenderContext *renderContext), defined in qtdeclarative/src/quick/scenegraph/qsgcontext.cpp:

QSGLayer *QSGContext::createLayer(QSGRenderContext *renderContext)
{
    return new QSGDefaultLayer(renderContext);
}

Ah ha! QSGDefaultLayer is the actual QSGLayer derivative that is instantiated. And, there is a qsgdefaultlayer.cpp (qtdeclarative/src/quick/scenegraph/qsgdefaultlayer.cpp). Relevant excerpts from that file:


QSGDefaultLayer::QSGDefaultLayer(QSGRenderContext *context)
    : QSGLayer()
    , m_item(0)
    , m_device_pixel_ratio(1)
    , m_format(GL_RGBA)
    , m_renderer(0)
    , m_fbo(0)
    , m_secondaryFbo(0)
    , m_transparentTexture(0)
#ifdef QSG_DEBUG_FBO_OVERLAY
    , m_debugOverlay(0)
#endif
    , m_context(context)
    , m_mipmap(false)
    , m_live(true)
    , m_recursive(false)
    , m_dirtyTexture(true)
    , m_multisamplingChecked(false)
    , m_multisampling(false)
    , m_grab(false)
    , m_mirrorHorizontal(false)
    , m_mirrorVertical(true)
{
}



void QSGDefaultLayer::bind()
{
#ifndef QT_NO_DEBUG
    if (!m_recursive && m_fbo && ((m_multisampling && m_secondaryFbo->isBound()) || m_fbo->isBound()))
        qWarning("ShaderEffectSource: \'recursive\' must be set to true when rendering recursively.");
#endif
    QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
    if (!m_fbo && m_format == GL_RGBA) {
        if (m_transparentTexture == 0) {
            funcs->glGenTextures(1, &m_transparentTexture);
            funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
            const uint zero = 0;
            funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &zero);
        } else {
            funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
        }
    } else {
        funcs->glBindTexture(GL_TEXTURE_2D, m_fbo ? m_fbo->texture() : 0);
        updateBindOptions();
    }
}



Interestingly, even though it didn't work for us, there is apparently a codepath for wacko framebuffer formats that avoids resampling. I may eventually revisit this.

As with ThirtyBitSGTexture vs QSGTexture, the changes required to implement ThirtyBitLayer from QSGDefaultLayer's code are minimal. However, in order to make setting layer.enabled: true cause instantiation of ThirtyBitLayer, we must override QSGContext::createLayer(..), which requires us to derive our own QSGContext, ThirtyBitSGContext. In turn, making Qt use our ThirtyBitSGContext requires overriding QSGContext::createDefaultContext(), declared in qtdeclarative/src/quick/scenegraph/qsgcontextplugin.cpp. So, we need to make a plugin, and then somehow tell Qt about it, and then somehow get Qt to use it.

The Qt documentation touches on this only briefly:

Scene Graph Backend

In addition to the public API, the scene graph has an adaptation layer which opens up the implementation to do hardware specific adaptations. This is an undocumented, internal and private plugin API, which lets hardware adaptation teams make the most of their hardware. It includes:
Again, the docs tell us what we've already deduced, and we turn to the source (qtdeclarative/src/quick/scenegraph/qsgcontextplugin.cpp):

#ifndef QT_NO_LIBRARY
Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
    (QSGContextFactoryInterface_iid, QLatin1String("/scenegraph")))
#endif
struct QSGAdaptionPluginData
{
    QSGAdaptionPluginData()
        : tried(false)
        , factory(0)
    {
    }
    ~QSGAdaptionPluginData()
    {
    }
    bool tried;
    QSGContextFactoryInterface *factory;
    QString deviceName;
};
Q_GLOBAL_STATIC(QSGAdaptionPluginData, qsg_adaptation_data)

/*!
    \fn QSGContext *QSGContext::createDefaultContext()
    Creates a default scene graph context for the current hardware.
    This may load a device-specific plugin.
*/
QSGContext *QSGContext::createDefaultContext()
{
    QSGAdaptionPluginData *plugin = contextFactory();
    if (plugin->factory)
        return plugin->factory->create(plugin->deviceName);
    return new QSGContext();
}

QSGAdaptionPluginData *contextFactory()
{
    QSGAdaptionPluginData *plugin = qsg_adaptation_data();
    if (!plugin->tried) {
        plugin->tried = true;
        const QStringList args = QGuiApplication::arguments();
        QString device;
        for (int index = 0; index < args.count(); ++index) {
            if (args.at(index).startsWith(QLatin1String("--device="))) {
                device = args.at(index).mid(9);
                break;
            }
        }
        if (device.isEmpty())
            device = QString::fromLocal8Bit(qgetenv("QMLSCENE_DEVICE"));
#ifndef QT_NO_LIBRARY
        if (!device.isEmpty()) {
            const int index = loader()->indexOf(device);
            if (index != -1)
                plugin->factory = qobject_cast<QSGContextFactoryInterface*>(loader()->instance(index));
            plugin->deviceName = device;
#ifndef QT_NO_DEBUG
            if (!plugin->factory) {
                qWarning("Could not create scene graph context for device '%s'"
                         " - check that plugins are installed correctly in %s",
                         qPrintable(device),
                         qPrintable(QLibraryInfo::location(QLibraryInfo::PluginsPath)));
            }
#endif
        }
#endif // QT_NO_LIBRARY
    }
    return plugin;
}

The key lines of code are bolded. We want createDefaultContext() to return an instance of ThirtyBitSGContext. In order for that to happen, contextFactory() has to discover our plugin. With the QMLSCENE_DEVICE environment variable set to ThirtyBitSGContextPlugin, the following is sufficient for plugin discovery:

ThirtyBitSGContextPlugin.h:

#include < QtCore/qplugin.h>
#include < QtQuick/private/qsgcontext_p.h>
#include < QtQuick/private/qsgcontextplugin_p.h>
#include < QObject>
class QSGContextFactoryInterface;
class ThirtyBitSGContextPlugin
  : public QObject,
    public QSGContextFactoryInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSGContextFactoryInterface" FILE "ThirtyBitSGContextPlugin.json")
    Q_INTERFACES(QSGContextFactoryInterface:QFactoryInterface)
public:
    ThirtyBitSGContextPlugin(QObject *parent = 0);
    QSGContext* create(const QString& key) const override;
    QStringList keys() const override;
    virtual QQuickTextureFactory *createTextureFactoryFromImage(const QImage &) override { return 0; }
    virtual QSGRenderLoop *createWindowManager() override { return 0; }
};



ThirtyBitSGContextPlugin.cpp:
#include "ThirtyBitSGContextPlugin.h"
#include "ThirtyBitSGContext.h"
#include "ThirtyBitSGContextPlugin.h"
#include "ThirtyBitSGContext.h"
ThirtyBitSGContextPlugin::ThirtyBitSGContextPlugin(QObject *parent) :
    QObject(parent)
{
}
QStringList ThirtyBitSGContextPlugin::keys() const
{
    // This never seems to be invoked.  Perhaps it is vestigial?  It does appear to be
    // redundant, given that the json metadata file contains a "Keys" entry.
    QStringList ret;
    ret << "ThirtyBitSGContextPlugin";
    return ret;
}
QSGContext* ThirtyBitSGContextPlugin::create(const QString& key) const
{
    return new ThirtyBitSGContext();
}
ThirtyBitSGContextPlugin.json:
{
    "Keys" : [ "ThirtyBitSGContextPlugin" ]
}



As for ThirtyBitSGContext, there's not much to it:

ThirtyBitSGContext.h:

#pragma once
class QSGContext;
class QSGLayer;
class ThirtyBitSGContext
  : public QSGContext
{
public:
    explicit ThirtyBitSGContext(QObject* parent=nullptr);
    QSGLayer* createLayer(QSGRenderContext* renderContext) override;
};



ThirtyBitSGContext.cpp:
#include < QtQuick/private/qsgcontext_p.h>
#include < QtQuick/private/qsgdefaultlayer_p.h>
#include "ThirtyBitSGContext.h"
#include "ThirtyBitSGLayer.h"
ThirtyBitSGContext::ThirtyBitSGContext(QObject* parent)
  : QSGContext(parent)
{
}
QSGLayer* ThirtyBitSGContext::createLayer(QSGRenderContext* renderContext)
{
    qDebug() << "QSGLayer* createLayer(QSGRenderContext* renderContext)";
    return new ThirtyBitSGLayer(renderContext);
}


ThirtyBitSGLayer is necessarily more complex as we can not inherit from QSGDefaultLayer, owing to the fact that we would need to access private members. I consider this to be obnoxious and unnecessary: QSGDefaultLayer is already part of the Qt “private” API; throwing up additional roadblocks helps nobody. So, fuck its privacy. We'll just go right ahead and copy/paste the entire thing, and then we'll make whatever changes we want (important changes in bold).

ThirtyBitSGLayer.h:

#pragma once

#include < QtQuick/private/qsgadaptationlayer_p.h>
#include < QtQuick/private/qsgcontext_p.h>
#include < qsgsimplerectnode.h>
#define QSG_DEBUG_FBO_OVERLAY
class ThirtyBitSGLayer : public QSGLayer
{
    Q_OBJECT
public:
    ThirtyBitSGLayer(QSGRenderContext *context);
    ~ThirtyBitSGLayer();
    bool updateTexture() Q_DECL_OVERRIDE;
    // The item's "paint node", not effect node.
    QSGNode *item() const { return m_item; }
    void setItem(QSGNode *item) Q_DECL_OVERRIDE;
    QRectF rect() const { return m_rect; }
    void setRect(const QRectF &rect) Q_DECL_OVERRIDE;
    QSize size() const { return m_size; }
    void setSize(const QSize &size) Q_DECL_OVERRIDE;
    void setHasMipmaps(bool mipmap) Q_DECL_OVERRIDE;
    void bind() Q_DECL_OVERRIDE;
    bool hasAlphaChannel() const Q_DECL_OVERRIDE;
    bool hasMipmaps() const Q_DECL_OVERRIDE;
    int textureId() const Q_DECL_OVERRIDE;
    QSize textureSize() const Q_DECL_OVERRIDE { return m_size; }
    GLenum format() const { return m_format; }
    void setFormat(GLenum format) Q_DECL_OVERRIDE;
    bool live() const { return bool(m_live); }
    void setLive(bool live) Q_DECL_OVERRIDE;
    bool recursive() const { return bool(m_recursive); }
    void setRecursive(bool recursive) Q_DECL_OVERRIDE;
    void setDevicePixelRatio(qreal ratio) Q_DECL_OVERRIDE { m_device_pixel_ratio = ratio; }
    bool mirrorHorizontal() const { return bool(m_mirrorHorizontal); }
    void setMirrorHorizontal(bool mirror) Q_DECL_OVERRIDE;
    bool mirrorVertical() const { return bool(m_mirrorVertical); }
    void setMirrorVertical(bool mirror) Q_DECL_OVERRIDE;
    void scheduleUpdate() Q_DECL_OVERRIDE;
    QImage toImage() const Q_DECL_OVERRIDE;
    QRectF normalizedTextureSubRect() const Q_DECL_OVERRIDE;
public Q_SLOTS:
    void markDirtyTexture() Q_DECL_OVERRIDE;
    void invalidated() Q_DECL_OVERRIDE;
protected:
    void grab();
    QSGNode *m_item;
    QRectF m_rect;
    QSize m_size;
    qreal m_device_pixel_ratio;
    GLenum m_format;
    QSGRenderer *m_renderer;
    QOpenGLFramebufferObject *m_fbo;
    QOpenGLFramebufferObject *m_secondaryFbo;
    QSharedPointer<QSGDepthStencilBuffer> m_depthStencilBuffer;
    GLuint m_transparentTexture;
#ifdef QSG_DEBUG_FBO_OVERLAY
    QSGSimpleRectNode *m_debugOverlay;
#endif
    QSGRenderContext *m_context;
    uint m_mipmap : 1;
    uint m_live : 1;
    uint m_recursive : 1;
    uint m_dirtyTexture : 1;
    uint m_multisamplingChecked : 1;
    uint m_multisampling : 1;
    uint m_grab : 1;
    uint m_mirrorHorizontal : 1;
    uint m_mirrorVertical : 1;
};

ThirtyBitSGLayer.cpp:

#include < QtQuick/private/qsgdefaultlayer_p.h>
#include < QOpenGLFramebufferObject>
#include "ThirtyBitSGLayer.h"
#include < QtQml/private/qqmlglobal_p.h>
#include < QtQuick/private/qsgrenderer_p.h>
#ifdef QSG_DEBUG_FBO_OVERLAY
DEFINE_BOOL_CONFIG_OPTION(qmlFboOverlay, QML_FBO_OVERLAY)
#endif
DEFINE_BOOL_CONFIG_OPTION(qmlFboFlushBeforeDetach, QML_FBO_FLUSH_BEFORE_DETACH)
namespace
{
    class BindableFbo : public QSGBindable
    {
    public:
        BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil);
        virtual ~BindableFbo();
        void bind() const Q_DECL_OVERRIDE;
    private:
        QOpenGLFramebufferObject *m_fbo;
        QSGDepthStencilBuffer *m_depthStencil;
    };
    BindableFbo::BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil)
        : m_fbo(fbo)
        , m_depthStencil(depthStencil)
    {
    }
    BindableFbo::~BindableFbo()
    {
        if (qmlFboFlushBeforeDetach())
            QOpenGLContext::currentContext()->functions()->glFlush();
        if (m_depthStencil)
            m_depthStencil->detach();
    }
    void BindableFbo::bind() const
    {
        m_fbo->bind();
        if (m_depthStencil)
            m_depthStencil->attach();
    }
}
ThirtyBitSGLayer::ThirtyBitSGLayer(QSGRenderContext *context)
    : QSGLayer()
    , m_item(0)
    , m_device_pixel_ratio(1)
    , m_format(GL_RGB10_A2)
    , m_renderer(0)
    , m_fbo(0)
    , m_secondaryFbo(0)
    , m_transparentTexture(0)
#ifdef QSG_DEBUG_FBO_OVERLAY
    , m_debugOverlay(0)
#endif
    , m_context(context)
    , m_mipmap(false)
    , m_live(true)
    , m_recursive(false)
    , m_dirtyTexture(true)
    , m_multisamplingChecked(false)
    , m_multisampling(false)
    , m_grab(false)
    , m_mirrorHorizontal(false)
    , m_mirrorVertical(true)
{
}
ThirtyBitSGLayer::~ThirtyBitSGLayer()
{
    invalidated();
}
void ThirtyBitSGLayer::invalidated()
{
    delete m_renderer;
    m_renderer = 0;
    delete m_fbo;
    delete m_secondaryFbo;
    m_fbo = m_secondaryFbo = 0;
#ifdef QSG_DEBUG_FBO_OVERLAY
    delete m_debugOverlay;
    m_debugOverlay = 0;
#endif
    if (m_transparentTexture) {
        QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_transparentTexture);
        m_transparentTexture = 0;
    }
}
int ThirtyBitSGLayer::textureId() const
{
    return m_fbo ? m_fbo->texture() : 0;
}
bool ThirtyBitSGLayer::hasAlphaChannel() const
{
    switch(m_format)
    {
    case GL_RGB:
    case GL_RGB10:
    case GL_RGB12:
    case GL_RGB16:
    case GL_RGB16F:
    case GL_RGB32F:
    case GL_R11F_G11F_B10F:
        return false;
    default:
        return true;
    }
}
bool ThirtyBitSGLayer::hasMipmaps() const
{
    return m_mipmap;
}
void ThirtyBitSGLayer::bind()
{
#ifndef QT_NO_DEBUG
    if (!m_recursive && m_fbo && ((m_multisampling && m_secondaryFbo->isBound()) || m_fbo->isBound()))
        qWarning("ShaderEffectSource: \'recursive\' must be set to true when rendering recursively.");
#endif
    QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
    if (!m_fbo && m_format == GL_RGB10_A2) {
        if (m_transparentTexture == 0) {
            funcs->glGenTextures(1, &m_transparentTexture);
            funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
            const uint zero = 0;
            funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &zero);
        } else {
            funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
        }
    } else {
        funcs->glBindTexture(GL_TEXTURE_2D, m_fbo ? m_fbo->texture() : 0);
        updateBindOptions();
    }
}
bool ThirtyBitSGLayer::updateTexture()
{
    bool doGrab = (m_live || m_grab) && m_dirtyTexture;
    if (doGrab)
        grab();
    if (m_grab)
        emit scheduledUpdateCompleted();
    m_grab = false;
    return doGrab;
}
void ThirtyBitSGLayer::setHasMipmaps(bool mipmap)
{
    if (mipmap == m_mipmap)
        return;
    m_mipmap = mipmap;
    if (m_mipmap && m_fbo && !m_fbo->format().mipmap())
        markDirtyTexture();
}
void ThirtyBitSGLayer::setItem(QSGNode *item)
{
    if (item == m_item)
        return;
    m_item = item;
    if (m_live && !m_item) {
        delete m_fbo;
        delete m_secondaryFbo;
        m_fbo = m_secondaryFbo = 0;
        m_depthStencilBuffer.clear();
    }
    markDirtyTexture();
}
void ThirtyBitSGLayer::setRect(const QRectF &rect)
{
    if (rect == m_rect)
        return;
    m_rect = rect;
    markDirtyTexture();
}
void ThirtyBitSGLayer::setSize(const QSize &size)
{
    if (size == m_size)
        return;
    m_size = size;
    if (m_live && m_size.isNull()) {
        delete m_fbo;
        delete m_secondaryFbo;
        m_fbo = m_secondaryFbo = 0;
        m_depthStencilBuffer.clear();
    }
    markDirtyTexture();
}
void ThirtyBitSGLayer::setFormat(GLenum)
{
    // NOPE!  We're keeping 30-bit fidelity.
}
void ThirtyBitSGLayer::setLive(bool live)
{
    if (live == m_live)
        return;
    m_live = live;
    if (m_live && (!m_item || m_size.isNull())) {
        delete m_fbo;
        delete m_secondaryFbo;
        m_fbo = m_secondaryFbo = 0;
        m_depthStencilBuffer.clear();
    }
    markDirtyTexture();
}
void ThirtyBitSGLayer::scheduleUpdate()
{
    if (m_grab)
        return;
    m_grab = true;
    if (m_dirtyTexture)
        emit updateRequested();
}
void ThirtyBitSGLayer::setRecursive(bool recursive)
{
    m_recursive = recursive;
}
void ThirtyBitSGLayer::setMirrorHorizontal(bool mirror)
{
    m_mirrorHorizontal = mirror;
}
void ThirtyBitSGLayer::setMirrorVertical(bool mirror)
{
    m_mirrorVertical = mirror;
}
void ThirtyBitSGLayer::markDirtyTexture()
{
    m_dirtyTexture = true;
    if (m_live || m_grab)
        emit updateRequested();
}
void ThirtyBitSGLayer::grab()
{
    if (!m_item || m_size.isNull()) {
        delete m_fbo;
        delete m_secondaryFbo;
        m_fbo = m_secondaryFbo = 0;
        m_depthStencilBuffer.clear();
        m_dirtyTexture = false;
        return;
    }
    QSGNode *root = m_item;
    while (root->firstChild() && root->type() != QSGNode::RootNodeType)
        root = root->firstChild();
    if (root->type() != QSGNode::RootNodeType)
        return;
    if (!m_renderer) {
        m_renderer = m_context->createRenderer();
        connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
    }
    m_renderer->setDevicePixelRatio(m_device_pixel_ratio);
    m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
    QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
    bool deleteFboLater = false;
    if (!m_fbo || m_fbo->size() != m_size || m_fbo->format().internalTextureFormat() != m_format
        || (!m_fbo->format().mipmap() && m_mipmap))
    {
        if (!m_multisamplingChecked) {
            if (m_context->openglContext()->format().samples() <= 1) {
                m_multisampling = false;
            } else {
                const QSet<QByteArray> extensions = m_context->openglContext()->extensions();
                m_multisampling = extensions.contains(QByteArrayLiteral("GL_EXT_framebuffer_multisample"))
                    && extensions.contains(QByteArrayLiteral("GL_EXT_framebuffer_blit"));
            }
            m_multisamplingChecked = true;
        }
        if (m_multisampling) {
            // Don't delete the FBO right away in case it is used recursively.
            deleteFboLater = true;
            delete m_secondaryFbo;
            QOpenGLFramebufferObjectFormat format;
            format.setInternalTextureFormat(m_format);
            format.setSamples(m_context->openglContext()->format().samples());
            m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format);
            m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_secondaryFbo);
        } else {
            QOpenGLFramebufferObjectFormat format;
            format.setInternalTextureFormat(m_format);
            format.setMipmap(m_mipmap);
            if (m_recursive) {
                deleteFboLater = true;
                delete m_secondaryFbo;
                m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format);
                funcs->glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
                updateBindOptions(true);
                m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_secondaryFbo);
            } else {
                delete m_fbo;
                delete m_secondaryFbo;
                m_fbo = new QOpenGLFramebufferObject(m_size, format);
                m_secondaryFbo = 0;
                funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
                updateBindOptions(true);
                m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_fbo);
            }
        }
    }
    if (m_recursive && !m_secondaryFbo) {
        // m_fbo already created, m_recursive was just set.
        Q_ASSERT(m_fbo);
        Q_ASSERT(!m_multisampling);
        m_secondaryFbo = new QOpenGLFramebufferObject(m_size, m_fbo->format());
        funcs->glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
        updateBindOptions(true);
    }
    // Render texture.
    root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
    m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
#ifdef QSG_DEBUG_FBO_OVERLAY
    if (qmlFboOverlay()) {
        if (!m_debugOverlay)
            m_debugOverlay = new QSGSimpleRectNode();
        m_debugOverlay->setRect(QRectF(0, 0, m_size.width(), m_size.height()));
        m_debugOverlay->setColor(QColor(0xff, 0x00, 0x80, 0x40));
        root->appendChildNode(m_debugOverlay);
    }
#endif
    m_dirtyTexture = false;
    m_renderer->setDeviceRect(m_size);
    m_renderer->setViewportRect(m_size);
    QRectF mirrored(m_mirrorHorizontal ? m_rect.right() : m_rect.left(),
                    m_mirrorVertical ? m_rect.bottom() : m_rect.top(),
                    m_mirrorHorizontal ? -m_rect.width() : m_rect.width(),
                    m_mirrorVertical ? -m_rect.height() : m_rect.height());
    m_renderer->setProjectionMatrixToRect(mirrored);
    m_renderer->setClearColor(Qt::transparent);
    if (m_multisampling) {
        m_renderer->renderScene(BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data()));
        if (deleteFboLater) {
            delete m_fbo;
            QOpenGLFramebufferObjectFormat format;
            format.setInternalTextureFormat(m_format);
            format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
            format.setMipmap(m_mipmap);
            format.setSamples(0);
            m_fbo = new QOpenGLFramebufferObject(m_size, format);
            funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
            updateBindOptions(true);
        }
        QRect r(QPoint(), m_size);
        QOpenGLFramebufferObject::blitFramebuffer(m_fbo, r, m_secondaryFbo, r);
    } else {
        if (m_recursive) {
            m_renderer->renderScene(BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data()));
            if (deleteFboLater) {
                delete m_fbo;
                QOpenGLFramebufferObjectFormat format;
                format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
                format.setInternalTextureFormat(m_format);
                format.setMipmap(m_mipmap);
                m_fbo = new QOpenGLFramebufferObject(m_size, format);
                funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
                updateBindOptions(true);
            }
            qSwap(m_fbo, m_secondaryFbo);
        } else {
            m_renderer->renderScene(BindableFbo(m_fbo, m_depthStencilBuffer.data()));
        }
    }
    if (m_mipmap) {
        funcs->glBindTexture(GL_TEXTURE_2D, textureId());
        funcs->glGenerateMipmap(GL_TEXTURE_2D);
    }
    root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
#ifdef QSG_DEBUG_FBO_OVERLAY
    if (qmlFboOverlay())
        root->removeChildNode(m_debugOverlay);
#endif
    if (m_recursive)
        markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
}
QImage ThirtyBitSGLayer::toImage() const
{
    if (m_fbo)
        return m_fbo->toImage();
    return QImage();
}
QRectF ThirtyBitSGLayer::normalizedTextureSubRect() const
{
    return QRectF(m_mirrorHorizontal ? 1 : 0,
                  m_mirrorVertical ? 0 : 1,
                  m_mirrorHorizontal ? -1 : 1,
                  m_mirrorVertical ? 1 : -1);
}
#include 
QT_BEGIN_NAMESPACE
QSGDepthStencilBuffer::QSGDepthStencilBuffer(QOpenGLContext *context, const Format &format)
    : m_functions(context)
    , m_manager(0)
    , m_format(format)
    , m_depthBuffer(0)
    , m_stencilBuffer(0)
{
    // 'm_manager' is set by QSGDepthStencilBufferManager::insertBuffer().
}
QSGDepthStencilBuffer::~QSGDepthStencilBuffer()
{
    if (m_manager)
        m_manager->m_buffers.remove(m_format);
}
void QSGDepthStencilBuffer::attach()
{
    m_functions.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                          GL_RENDERBUFFER, m_depthBuffer);
    m_functions.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                          GL_RENDERBUFFER, m_stencilBuffer);
}
void QSGDepthStencilBuffer::detach()
{
    m_functions.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                                          GL_RENDERBUFFER, 0);
    m_functions.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                          GL_RENDERBUFFER, 0);
}
#ifndef GL_DEPTH24_STENCIL8_OES
#define GL_DEPTH24_STENCIL8_OES 0x88F0
#endif
#ifndef GL_DEPTH_COMPONENT24_OES
#define GL_DEPTH_COMPONENT24_OES 0x81A6
#endif
QSGDefaultDepthStencilBuffer::QSGDefaultDepthStencilBuffer(QOpenGLContext *context, const Format &format)
    : QSGDepthStencilBuffer(context, format)
{
    const GLsizei width = format.size.width();
    const GLsizei height = format.size.height();
    if (format.attachments == (DepthAttachment | StencilAttachment)
            && m_functions.hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil))
    {
        m_functions.glGenRenderbuffers(1, &m_depthBuffer);
        m_functions.glBindRenderbuffer(GL_RENDERBUFFER, m_depthBuffer);
        if (format.samples && m_functions.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) {
#if defined(QT_OPENGL_ES_2)
            m_functions.glRenderbufferStorageMultisample(GL_RENDERBUFFER, format.samples,
                GL_DEPTH24_STENCIL8_OES, width, height);
#else
            m_functions.glRenderbufferStorageMultisample(GL_RENDERBUFFER, format.samples,
                GL_DEPTH24_STENCIL8, width, height);
#endif
        } else {
#if defined(QT_OPENGL_ES_2)
            m_functions.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, width, height);
#else
            m_functions.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
#endif
        }
        m_stencilBuffer = m_depthBuffer;
    }
    if (!m_depthBuffer && (format.attachments & DepthAttachment)) {
        m_functions.glGenRenderbuffers(1, &m_depthBuffer);
        m_functions.glBindRenderbuffer(GL_RENDERBUFFER, m_depthBuffer);
        GLenum internalFormat = GL_DEPTH_COMPONENT;
        if (context->isOpenGLES())
            internalFormat = m_functions.hasOpenGLExtension(QOpenGLExtensions::Depth24)
                ? GL_DEPTH_COMPONENT24_OES : GL_DEPTH_COMPONENT16;
        if (format.samples && m_functions.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) {
            m_functions.glRenderbufferStorageMultisample(GL_RENDERBUFFER, format.samples,
                internalFormat, width, height);
        } else {
            m_functions.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, width, height);
        }
    }
    if (!m_stencilBuffer && (format.attachments & StencilAttachment)) {
        m_functions.glGenRenderbuffers(1, &m_stencilBuffer);
        m_functions.glBindRenderbuffer(GL_RENDERBUFFER, m_stencilBuffer);
#ifdef QT_OPENGL_ES
        const GLenum internalFormat = GL_STENCIL_INDEX8;
#else
        const GLenum internalFormat = context->isOpenGLES() ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX;
#endif
        if (format.samples && m_functions.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) {
            m_functions.glRenderbufferStorageMultisample(GL_RENDERBUFFER, format.samples,
                internalFormat, width, height);
        } else {
            m_functions.glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, width, height);
        }
    }
}
QSGDefaultDepthStencilBuffer::~QSGDefaultDepthStencilBuffer()
{
    free();
}
void QSGDefaultDepthStencilBuffer::free()
{
    if (m_depthBuffer)
        m_functions.glDeleteRenderbuffers(1, &m_depthBuffer);
    if (m_stencilBuffer && m_stencilBuffer != m_depthBuffer)
        m_functions.glDeleteRenderbuffers(1, &m_stencilBuffer);
    m_depthBuffer = m_stencilBuffer = 0;
}
QSGDepthStencilBufferManager::~QSGDepthStencilBufferManager()
{
    for (Hash::const_iterator it = m_buffers.constBegin(), cend = m_buffers.constEnd(); it != cend; ++it) {
        QSGDepthStencilBuffer *buffer = it.value().data();
        buffer->free();
        buffer->m_manager = 0;
    }
}
QSharedPointer<QSGDepthStencilBuffer> QSGDepthStencilBufferManager::bufferForFormat(const QSGDepthStencilBuffer::Format &fmt)
{
    Hash::const_iterator it = m_buffers.constFind(fmt);
    if (it != m_buffers.constEnd())
        return it.value().toStrongRef();
    return QSharedPointer<QSGDepthStencilBuffer>();
}
void QSGDepthStencilBufferManager::insertBuffer(const QSharedPointer<QSGDepthStencilBuffer> &buffer)
{
    Q_ASSERT(buffer->m_manager == 0);
    Q_ASSERT(!m_buffers.contains(buffer->m_format));
    buffer->m_manager = this;
    m_buffers.insert(buffer->m_format, buffer.toWeakRef());
}
uint qHash(const QSGDepthStencilBuffer::Format &format)
{
    return qHash(qMakePair(format.size.width(), format.size.height()))
            ^ (uint(format.samples) << 12) ^ (uint(format.attachments) << 28);
}
QT_END_NAMESPACE

You may note that the body of ThirtyBitSGLayer::setFormat has been replaced by the comment, NOPE! We're keeping 30-bit fidelity. This is necessary because the QObject side of our ThirtyBitSGLayer class remains QQuickItemLayer, and QQuickItemLayer calls ThirtyBitSGLayer::setFormat to demand that we revert back to inferior bullshit 24-bit color, which we don't want to do. So, we don't. Ha!

It would be more elegant to make QQuickItemLayer stop being an asshole, but this can only be practically accomplished by patching and rebuilding Qt: the relevant QQuickItemLayer instance is created by QQuickItemPrivate, which can not be modified except by copy/paste/modifying QQuickItem and all of its associated private classes. By itself, this is straightforward (we do it with QSGLayer to make ThirtyBitSGLayer, after all). However, the QQuickItem type is referenced directly in a vast number of places within Qt, and we would have to copy/paste/modify these as well, which would be immensely more work than simply unfucking QQuickItemLayer's definition in the Qt source itself and rebuilding it.

And, that's it. With this code, 30-bit color works in QML scene layers.

The same technique can be used to keep layers in float32-per-channel format, float64-per-channel format, or whatever else you need.

It would be outstanding if Qt's QML stuff eventually provides a simple way of controlling layer formats without resorting to the private API and generally wasting many developer days each and every time someone needs it. However, the same was true for QtWidgets, and it never happened. So, I doubt this will ever be improved, either.


It's probably too hard; the Qt developers simply are not up to the challenge. ;-)

Archives

July 2009   August 2009   September 2009   October 2009   November 2009   December 2009   January 2010   September 2010   December 2010   January 2011   February 2011   April 2011   June 2011   August 2011   February 2012   June 2012   July 2012   August 2012   October 2012   November 2012   January 2014   April 2014   June 2014   August 2014   September 2014   October 2014   January 2015   March 2015   April 2015   June 2015   November 2015   December 2015   January 2016   June 2016   August 2016   January 2017   March 2017   April 2018   April 2019   June 2019   January 2020  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]