EsoErik

Wednesday, December 23, 2015

 

Doing anything significant whatsoever with QML, part 4: 30-bit color QQuickFramebufferObject support (continued)

Returning a custom QSGNode from ThirtyBitImageItem's QuickImage::updatePaintNode seems like the most promising approach.  Rather than attempting to add functionality to the QQuickImage class, which is part of Qt's private API, it may be sufficient to derive my own implementation of QSGTexture and return that from QuickImage::updatePaintNode!

I arrived at this approach after applying a patch set that adds 16-bpc support to QImage.  (I had to do a bit of development to bring these patches up to date for applying against Qt 5.6.0-beta1).  The patch set does not address QML, so I started in on the work required to make QML use 16-bpc QImages when advantageous, at which point I noticed that the internal format of QML SGDefaultLayer textures is always specified as unsized (GL_RGBA), and storage of texture data in a QImage only occurs when necessary.  It's necessary only when pulling pixel data from the GPU, which is comparatively slow and not done unless explicitly demanded.  Thus, the possibility exists that I can put 10-bpc texture data into the QML scene graph pipeline without losing fidelity or sacrificing alpha channel depth.  Furthermore, QImage does have native 10-bpc support, even without the 16-bpc patches.  Even rendering a QQuickWindow to a 10-bpc QImage should be possible, though loss of alpha depth can not be avoided when doing so.

The previous paragraph turned out to be incorrect; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA ...) yields a texture with 8-bpc fidelity.  So, simply returning a real 30-bit texture from ::updatePaintNode is not sufficient for retaining 30-bit fidelity with layer.enabled = true.  We are going to have to derive and implement more stuff in order to have 30-bit items fully participate in a QML scene, and this will inevitably require 30-bit QSGTextures.

However, simply attaching an instance of our 30-bit QSGTexture derivative to a QSGSimpleTextureNode returned from ::updatePaintNode does preserve 30-bit fidelity.  The relevant code:

ThirtyBitSGTexture.h:

#pragma once
#include "common.h"

class ThirtyBitSGTexture
  : public QSGTexture
{
    Q_OBJECT
public:
    ThirtyBitSGTexture();
    virtual ~ThirtyBitSGTexture();

    void setOwnsTexture(bool owns) { m_owns_texture = owns; }
    bool ownsTexture() const { return m_owns_texture; }

    void setTextureId(int id);
    int textureId() const;
    void setTextureSize(const QSize &size) { m_texture_size = size; }
    QSize textureSize() const { return m_texture_size; }

    void setHasAlphaChannel(bool alpha) { m_has_alpha = alpha; }
    bool hasAlphaChannel() const { return m_has_alpha; }

    bool hasMipmaps() const { return mipmapFiltering() != QSGTexture::None; }

    void setImage(const QImage &image);
    const QImage &image() { return m_image; }

    virtual void bind();

    static ThirtyBitSGTexture *fromImage(const QImage &image) {
        ThirtyBitSGTexture *t = new ThirtyBitSGTexture();
        t->setImage(image);
        return t;
    }

protected:
    QImage m_image;

    GLuint m_texture_id;
    QSize m_texture_size;
    QRectF m_texture_rect;

    uint m_has_alpha : 1;
    uint m_dirty_texture : 1;
    uint m_dirty_bind_options : 1;
    uint m_owns_texture : 1;
    uint m_mipmaps_generated : 1;
    uint m_retain_image: 1;
};


ThirtyBitSGTexture.cpp:

#include "common.h"
#include "ThirtyBitSGTexture.h"
#include < QtQml/private/qqmlglobal_p.h>
#include < QtQuick/private/qquickprofiler_p.h>
#include < QtQuick/private/qsgcontext_p.h>
#include < QtQuick/private/qsgmaterialshader_p.h>
#include < QtQuick/private/qsgtexture_p.h>


inline static bool isPowerOfTwo(int x)
{
    // Assumption: x >= 1
    return x == (x & -x);
}

ThirtyBitSGTexture::ThirtyBitSGTexture()
    : QSGTexture()
    , m_texture_id(0)
    , m_has_alpha(false)
    , m_dirty_texture(false)
    , m_dirty_bind_options(false)
    , m_owns_texture(true)
    , m_mipmaps_generated(false)
    , m_retain_image(false)
{
}


ThirtyBitSGTexture::~ThirtyBitSGTexture()
{
    if (m_texture_id && m_owns_texture && QOpenGLContext::currentContext())
        QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id);
}

void ThirtyBitSGTexture::setImage(const QImage &image)
{
    m_image = image;
    m_texture_size = image.size();
    m_has_alpha = image.hasAlphaChannel();
    m_dirty_texture = true;
    m_dirty_bind_options = true;
    m_mipmaps_generated = false;
 }

int ThirtyBitSGTexture::textureId() const
{
    if (m_dirty_texture) {
        if (m_image.isNull()) {
            // The actual texture and id will be updated/deleted in a later bind()
            // or ~ThirtyBitSGTexture so just keep it minimal here.
            return 0;
        } else if (m_texture_id == 0){
            // Generate a texture id for use later and return it.
            QOpenGLContext::currentContext()->functions()->glGenTextures(1, &const_cast<ThirtyBitSGTexture *>(this)->m_texture_id);
            return m_texture_id;
        }
    }
    return m_texture_id;
}

void ThirtyBitSGTexture::setTextureId(int id)
{
    if (m_texture_id && m_owns_texture)
        QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_texture_id);

    m_texture_id = id;
    m_dirty_texture = false;
    m_dirty_bind_options = true;
    m_image = QImage();
    m_mipmaps_generated = false;
}

void ThirtyBitSGTexture::bind()
{
    QOpenGLContext *context = QOpenGLContext::currentContext();
    QOpenGLFunctions *funcs = context->functions();
    if (!m_dirty_texture) {
        funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id);
        if (mipmapFiltering() != QSGTexture::None && !m_mipmaps_generated) {
            funcs->glGenerateMipmap(GL_TEXTURE_2D);
            m_mipmaps_generated = true;
        }
        updateBindOptions(m_dirty_bind_options);
        m_dirty_bind_options = false;
        return;
    }

    m_dirty_texture = false;

    if (m_image.isNull()) {
        if (m_texture_id && m_owns_texture) {
            funcs->glDeleteTextures(1, &m_texture_id);
        }
        m_texture_id = 0;
        m_texture_size = QSize();
        m_has_alpha = false;

        return;
    }

    if (m_texture_id == 0)
        funcs->glGenTextures(1, &m_texture_id);
    funcs->glBindTexture(GL_TEXTURE_2D, m_texture_id);

    QImage tmp = (m_image.format() == QImage::Format_RGB30 || m_image.format() == QImage::Format_A2RGB30_Premultiplied)
                ? m_image
                : m_image.convertToFormat(QImage::Format_A2RGB30_Premultiplied);

    if (tmp.width() * 4 != tmp.bytesPerLine())
        tmp = tmp.copy();

    updateBindOptions(m_dirty_bind_options);

    funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, m_texture_size.width(), m_texture_size.height(), 0, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV, tmp.constBits());

    if (mipmapFiltering() != QSGTexture::None) {
        funcs->glGenerateMipmap(GL_TEXTURE_2D);
        m_mipmaps_generated = true;
    }

    m_texture_rect = QRectF(0, 0, 1, 1);

    m_dirty_bind_options = false;
    if (!m_retain_image)
        m_image = QImage();
}


Image::as10BpcQImage() const definition from Image.cpp:

QImage Image::as10BpcQImage() const
{
    if(!m_isValid) return QImage();
    const std::uint8_t* fiPixIt{reinterpret_cast<const std::uint8_t*>(m_rawData.get())};
    const std::uint8_t* const fiPixEndIt{fiPixIt + m_byteCount};
    std::uint8_t* qiPixIt;
    QImage ret;
    float aFactor;
    switch(m_channelCount)
    {
    default:
        qWarning("m_channelCount must be 4.");
        break;
    case 4:
    {
        ret = QImage(m_size, QImage::Format_A2RGB30_Premultiplied);
        qiPixIt = ret.bits();
        for(;;)
        {
            const FIRGBA16& fiPix = *reinterpret_cast<const FIRGBA16*>(fiPixIt);
            aFactor = static_cast<float>(fiPix.alpha) / 0xffff;
            // Should be endian safe (not tested on big endian, however)
            *reinterpret_cast<std::uint32_t*>(qiPixIt) =
                ((static_cast<std::uint32_t>(fiPix.alpha          ) >> 14) << 30) |
                ((static_cast<std::uint32_t>(fiPix.red   * aFactor) >>  6) << 20) |
                ((static_cast<std::uint32_t>(fiPix.green * aFactor) >>  6) << 10) |
                 (static_cast<std::uint32_t>(fiPix.blue  * aFactor) >>  6);
            fiPixIt += 8;
            if(fiPixIt >= fiPixEndIt) break;
            qiPixIt += 4;
        }
        break;
    }}
    return ret;

}

Next up: making our own QSGMaterialNode and/or QSGLayer, etc, in order to preserve 30-bit fidelity with layer.enabled = true.

Comments:

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

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  

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

Subscribe to Posts [Atom]