Revision control
Copy as Markdown
// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "submissioncontext_p.h"
#include <Qt3DCore/private/qbuffer_p.h>
#include <Qt3DRender/qgraphicsapifilter.h>
#include <Qt3DRender/qparameter.h>
#include <Qt3DRender/qcullface.h>
#include <Qt3DRender/qfrontface.h>
#include <Qt3DRender/qdepthtest.h>
#include <Qt3DRender/qblendequation.h>
#include <Qt3DRender/qblendequationarguments.h>
#include <Qt3DRender/qstenciloperationarguments.h>
#include <Qt3DRender/qstenciltestarguments.h>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DRender/private/shader_p.h>
#include <Qt3DRender/private/material_p.h>
#include <Qt3DRender/private/buffer_p.h>
#include <Qt3DRender/private/attribute_p.h>
#include <Qt3DRender/private/renderstates_p.h>
#include <Qt3DRender/private/renderstateset_p.h>
#include <Qt3DRender/private/rendertarget_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/buffermanager_p.h>
#include <Qt3DRender/private/managers_p.h>
#include <Qt3DRender/private/attachmentpack_p.h>
#include <Qt3DRender/private/stringtoint_p.h>
#if QT_CONFIG(qt3d_vulkan) && QT_CONFIG(vulkan)
# include <Qt3DRender/private/vulkaninstance_p.h>
#endif
#include <QGuiApplication>
#include <texture_p.h>
#include <rendercommand_p.h>
#include <renderer_p.h>
#include <rhiresourcemanagers_p.h>
#include <renderbuffer_p.h>
#include <rhishader_p.h>
#include <QOpenGLShaderProgram>
#include <private/qdebug_p.h>
#include <QSurface>
#include <QWindow>
#include <rhi/qrhi.h>
#include <rhi/qshaderbaker.h>
#include <bitset>
QT_BEGIN_NAMESPACE
namespace Qt3DRender {
namespace Render {
namespace Rhi {
namespace {
//RHIBuffer::Type attributeTypeToGLBufferType(QAttribute::AttributeType type) noexcept
//{
// switch (type) {
// case QAttribute::VertexAttribute:
// return RHIBuffer::ArrayBuffer;
// case QAttribute::IndexAttribute:
// return RHIBuffer::IndexBuffer;
// case QAttribute::DrawIndirectAttribute:
// return RHIBuffer::DrawIndirectBuffer;
// default:
// Q_UNREACHABLE();
// }
//}
//void copyGLFramebufferDataToImage(QImage &img, const uchar *srcData, uint stride, uint width,
// uint height, QAbstractTexture::TextureFormat format) noexcept
//{
// switch (format) {
// case QAbstractTexture::RGBA32F: {
// uchar *srcScanline = const_cast<uchar *>(srcData) + stride * (height - 1);
// for (uint i = 0; i < height; ++i) {
// uchar *dstScanline = img.scanLine(i);
// float *pSrc = reinterpret_cast<float *>(srcScanline);
// for (uint j = 0; j < width; j++) {
// *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 2], 1.0f));
// *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 1], 1.0f));
// *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 0], 1.0f));
// *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4 * j + 3], 1.0f));
// }
// srcScanline -= stride;
// }
// } break;
// default: {
// uchar *srcScanline = (uchar *)srcData + stride * (height - 1);
// for (uint i = 0; i < height; ++i) {
// memcpy(img.scanLine(i), srcScanline, stride);
// srcScanline -= stride;
// }
// } break;
// }
//}
// Render States Helpers
template<typename GenericState>
void applyStateHelper(const GenericState *state, QRhiGraphicsPipeline *gp) noexcept
{
Q_UNUSED(state);
Q_UNUSED(gp);
qCWarning(Backend) << "RHI Unhandled render state" << typeid(GenericState).name();
}
void applyStateHelper(const BlendEquationArguments *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
// We assume a single color attachment
QRhiGraphicsPipeline::TargetBlend targetBlend {};
const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends();
if (hasTargetBlend)
targetBlend = *(gp->cbeginTargetBlends());
auto getRHIBlendFactor = [](int arg) {
switch (arg) {
case QBlendEquationArguments::Zero:
return QRhiGraphicsPipeline::Zero;
case QBlendEquationArguments::One:
return QRhiGraphicsPipeline::One;
case QBlendEquationArguments::SourceColor:
return QRhiGraphicsPipeline::SrcColor;
case QBlendEquationArguments::SourceAlpha:
return QRhiGraphicsPipeline::SrcAlpha;
// ### Qt 6 Fix values
// case QBlendEquationArguments::Source1Alpha:
// return QRhiGraphicsPipeline::Src1Alpha;
// case QBlendEquationArguments::Source1Color:
// return QRhiGraphicsPipeline::Src1Color;
case QBlendEquationArguments::DestinationColor:
return QRhiGraphicsPipeline::DstColor;
case QBlendEquationArguments::DestinationAlpha:
return QRhiGraphicsPipeline::DstAlpha;
case QBlendEquationArguments::SourceAlphaSaturate:
return QRhiGraphicsPipeline::SrcAlphaSaturate;
case QBlendEquationArguments::ConstantColor:
return QRhiGraphicsPipeline::ConstantColor;
case QBlendEquationArguments::ConstantAlpha:
return QRhiGraphicsPipeline::ConstantAlpha;
case QBlendEquationArguments::OneMinusSourceColor:
return QRhiGraphicsPipeline::OneMinusSrcColor;
case QBlendEquationArguments::OneMinusSourceAlpha:
return QRhiGraphicsPipeline::OneMinusSrcAlpha;
case QBlendEquationArguments::OneMinusDestinationAlpha:
return QRhiGraphicsPipeline::OneMinusDstAlpha;
case QBlendEquationArguments::OneMinusDestinationColor:
return QRhiGraphicsPipeline::OneMinusDstColor;
case QBlendEquationArguments::OneMinusConstantColor:
return QRhiGraphicsPipeline::OneMinusConstantColor;
case QBlendEquationArguments::OneMinusConstantAlpha:
return QRhiGraphicsPipeline::OneMinusConstantAlpha;
case QBlendEquationArguments::OneMinusSource1Alpha:
return QRhiGraphicsPipeline::OneMinusSrc1Alpha;
case QBlendEquationArguments::OneMinusSource1Color:
return QRhiGraphicsPipeline::OneMinusSrc1Color;
default:
qDebug() << "Unhandled blend equation argument" << arg;
return QRhiGraphicsPipeline::Zero;
}
};
targetBlend.srcAlpha = getRHIBlendFactor(std::get<2>(values));
targetBlend.dstAlpha = getRHIBlendFactor(std::get<3>(values));
targetBlend.srcColor = getRHIBlendFactor(std::get<0>(values));
targetBlend.dstColor = getRHIBlendFactor(std::get<1>(values));
gp->setTargetBlends({ targetBlend });
}
void applyStateHelper(const BlendEquation *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
const QBlendEquation::BlendFunction equation =
static_cast<QBlendEquation::BlendFunction>(std::get<0>(values));
// We assume a single color attachment
QRhiGraphicsPipeline::TargetBlend targetBlend;
const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends();
if (hasTargetBlend)
targetBlend = *(gp->cbeginTargetBlends());
auto getRHIBlendOp = [](QBlendEquation::BlendFunction equation) {
switch (equation) {
case QBlendEquation::Add:
return QRhiGraphicsPipeline::Add;
case QBlendEquation::Subtract:
return QRhiGraphicsPipeline::Subtract;
case QBlendEquation::ReverseSubtract:
return QRhiGraphicsPipeline::ReverseSubtract;
case QBlendEquation::Min:
return QRhiGraphicsPipeline::Min;
case QBlendEquation::Max:
return QRhiGraphicsPipeline::Max;
default:
return QRhiGraphicsPipeline::Add;
}
};
targetBlend.enable = true;
targetBlend.opAlpha = getRHIBlendOp(equation);
gp->setTargetBlends({ targetBlend });
}
void applyStateHelper(const MSAAEnabled *state, QRhiGraphicsPipeline *gp,
const QSurfaceFormat &format) noexcept
{
Q_UNUSED(state);
gp->setSampleCount(format.samples());
}
void applyStateHelper(const DepthTest *state, QRhiGraphicsPipeline *gp) noexcept
{
gp->setDepthTest(true);
const QDepthTest::DepthFunction depthFunc =
static_cast<QDepthTest::DepthFunction>(std::get<0>(state->values()));
switch (depthFunc) {
case QDepthTest::Never:
gp->setDepthOp(QRhiGraphicsPipeline::Never);
break;
case QDepthTest::Always:
gp->setDepthOp(QRhiGraphicsPipeline::Always);
break;
case QDepthTest::Less:
gp->setDepthOp(QRhiGraphicsPipeline::Less);
break;
case QDepthTest::LessOrEqual:
gp->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
break;
case QDepthTest::Equal:
gp->setDepthOp(QRhiGraphicsPipeline::Equal);
break;
case QDepthTest::GreaterOrEqual:
gp->setDepthOp(QRhiGraphicsPipeline::GreaterOrEqual);
break;
case QDepthTest::Greater:
gp->setDepthOp(QRhiGraphicsPipeline::Greater);
break;
case QDepthTest::NotEqual:
gp->setDepthOp(QRhiGraphicsPipeline::NotEqual);
break;
}
}
void applyStateHelper(const NoDepthMask *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
gp->setDepthWrite(std::get<0>(values));
}
void applyStateHelper(const CullFace *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
const QCullFace::CullingMode cullingMode =
static_cast<QCullFace::CullingMode>(std::get<0>(values));
switch (cullingMode) {
case QCullFace::NoCulling:
gp->setCullMode(QRhiGraphicsPipeline::None);
break;
case QCullFace::Front:
gp->setCullMode(QRhiGraphicsPipeline::Front);
break;
case QCullFace::Back:
gp->setCullMode(QRhiGraphicsPipeline::Back);
break;
case QCullFace::FrontAndBack:
qCWarning(Backend) << "RHI doesn't handle FrontAndBack CullFace";
break;
}
}
void applyStateHelper(const FrontFace *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
const QFrontFace::WindingDirection cullingMode =
static_cast<QFrontFace::WindingDirection>(std::get<0>(values));
switch (cullingMode) {
case QFrontFace::ClockWise:
gp->setFrontFace(QRhiGraphicsPipeline::CW);
break;
case QFrontFace::CounterClockWise:
gp->setFrontFace(QRhiGraphicsPipeline::CCW);
break;
}
}
void applyStateHelper(const PolygonOffset *state, QRhiGraphicsPipeline *gp)
{
const auto values = state->values();
auto slopeScaledDepthBias = std::get<0>(values);
auto depthBias = std::get<1>(values);
gp->setSlopeScaledDepthBias(slopeScaledDepthBias);
gp->setDepthBias(depthBias);
}
void applyStateHelper(const StencilTest *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
gp->setStencilTest(true);
auto getCompareOp = [](int compareOp) {
switch (compareOp) {
case QStencilTestArguments::Never:
return QRhiGraphicsPipeline::Never;
case QStencilTestArguments::Always:
return QRhiGraphicsPipeline::Always;
case QStencilTestArguments::Less:
return QRhiGraphicsPipeline::Less;
case QStencilTestArguments::LessOrEqual:
return QRhiGraphicsPipeline::LessOrEqual;
case QStencilTestArguments::Equal:
return QRhiGraphicsPipeline::Equal;
case QStencilTestArguments::GreaterOrEqual:
return QRhiGraphicsPipeline::GreaterOrEqual;
case QStencilTestArguments::Greater:
return QRhiGraphicsPipeline::Greater;
case QStencilTestArguments::NotEqual:
return QRhiGraphicsPipeline::NotEqual;
default:
qDebug() << "Unhandled stencil test argument";
return QRhiGraphicsPipeline::Never;
}
};
QRhiGraphicsPipeline::StencilOpState frontCompare = gp->stencilFront();
frontCompare.compareOp = getCompareOp(std::get<0>(values));
gp->setStencilFront(frontCompare);
QRhiGraphicsPipeline::StencilOpState backCompare = gp->stencilBack();
backCompare.compareOp = getCompareOp(std::get<3>(values));
gp->setStencilBack(backCompare);
}
void applyStateHelper(const ColorMask *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
// We assume a single color attachment
QRhiGraphicsPipeline::TargetBlend targetBlend;
const bool hasTargetBlend = gp->cbeginTargetBlends() != gp->cendTargetBlends();
if (hasTargetBlend)
targetBlend = *(gp->cbeginTargetBlends());
const bool redEnabled = std::get<0>(values);
const bool greenEnabled = std::get<1>(values);
const bool blueEnabled = std::get<2>(values);
const bool alphaEnabled = std::get<3>(values);
QRhiGraphicsPipeline::ColorMask colorMask;
if (redEnabled)
colorMask |= QRhiGraphicsPipeline::R;
if (greenEnabled)
colorMask |= QRhiGraphicsPipeline::G;
if (blueEnabled)
colorMask |= QRhiGraphicsPipeline::B;
if (alphaEnabled)
colorMask |= QRhiGraphicsPipeline::A;
targetBlend.colorWrite = colorMask;
gp->setTargetBlends({ targetBlend });
}
void applyStateHelper(const StencilOp *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
auto getRHIStencilOp = [](int op) {
switch (op) {
case QStencilOperationArguments::Zero:
return QRhiGraphicsPipeline::StencilZero;
case QStencilOperationArguments::Keep:
return QRhiGraphicsPipeline::Keep;
case QStencilOperationArguments::Replace:
return QRhiGraphicsPipeline::Replace;
case QStencilOperationArguments::Increment:
return QRhiGraphicsPipeline::IncrementAndClamp;
case QStencilOperationArguments::Decrement:
return QRhiGraphicsPipeline::DecrementAndClamp;
case QStencilOperationArguments::IncrementWrap:
return QRhiGraphicsPipeline::IncrementAndWrap;
case QStencilOperationArguments::DecrementWrap:
return QRhiGraphicsPipeline::DecrementAndWrap;
case QStencilOperationArguments::Invert:
return QRhiGraphicsPipeline::Invert;
default:
qDebug() << "Unhandled stencil operation argument";
return QRhiGraphicsPipeline::StencilZero;
}
};
QRhiGraphicsPipeline::StencilOpState frontCompare = gp->stencilFront();
frontCompare.depthFailOp = getRHIStencilOp(std::get<1>(values));
frontCompare.failOp = getRHIStencilOp(std::get<0>(values));
frontCompare.passOp = getRHIStencilOp(std::get<2>(values));
gp->setStencilFront(frontCompare);
QRhiGraphicsPipeline::StencilOpState backCompare = gp->stencilBack();
backCompare.depthFailOp = getRHIStencilOp(std::get<4>(values));
backCompare.failOp = getRHIStencilOp(std::get<3>(values));
backCompare.passOp = getRHIStencilOp(std::get<5>(values));
gp->setStencilBack(backCompare);
}
void applyStateHelper(const StencilMask *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
gp->setStencilWriteMask(std::get<0>(values));
gp->setStencilReadMask(std::get<1>(values));
}
void applyStateHelper(const LineWidth *state, QRhiGraphicsPipeline *gp) noexcept
{
const auto values = state->values();
gp->setLineWidth(std::get<0>(values));
// no GL_LINE_SMOOTH equivalent on RHI
}
static QShader::Stage rhiShaderStage(QShaderProgram::ShaderType type) noexcept
{
switch (type) {
case QShaderProgram::Vertex:
return QShader::VertexStage;
case QShaderProgram::Fragment:
return QShader::FragmentStage;
case QShaderProgram::TessellationControl:
return QShader::TessellationControlStage;
case QShaderProgram::TessellationEvaluation:
return QShader::TessellationEvaluationStage;
case QShaderProgram::Geometry:
return QShader::GeometryStage;
case QShaderProgram::Compute:
return QShader::ComputeStage;
default:
std::abort();
}
}
} // anonymous
SubmissionContext::SubmissionContext()
: m_initialized(false),
m_ownsRhiCtx(false),
m_drivenExternally(false),
m_material(nullptr),
m_renderer(nullptr),
m_rhi(nullptr),
m_currentSwapChain(nullptr),
m_currentRenderPassDescriptor(nullptr),
m_defaultRenderTarget(nullptr),
m_defaultCommandBuffer(nullptr)
#ifndef QT_NO_OPENGL
,
m_fallbackSurface(nullptr)
#endif
{
m_contextInfo.m_api = QGraphicsApiFilter::RHI;
// We set those version numbers because QShaderGenerator wants major > 0
m_contextInfo.m_major = 1;
m_contextInfo.m_minor = 0;
}
SubmissionContext::~SubmissionContext()
{
releaseResources();
}
void SubmissionContext::initialize()
{
m_initialized = true;
// If the RHI instance was already set (Scene3D)
// no point in continuing below;
if (m_rhi)
return;
m_ownsRhiCtx = true;
Qt3DRender::API requestedApi = Qt3DRender::API::RHI;
const auto userRequestedApi = qgetenv("QSG_RHI_BACKEND").toLower();
if (!userRequestedApi.isEmpty()) {
if (userRequestedApi == QByteArrayLiteral("opengl") ||
userRequestedApi == QByteArrayLiteral("gl") ||
userRequestedApi == QByteArrayLiteral("gles2")) {
requestedApi = Qt3DRender::API::OpenGL;
} else if (userRequestedApi == QByteArrayLiteral("vulkan")) {
requestedApi = Qt3DRender::API::Vulkan;
} else if (userRequestedApi == QByteArrayLiteral("metal")) {
requestedApi = Qt3DRender::API::Metal;
} else if (userRequestedApi == QByteArrayLiteral("d3d11")) {
requestedApi = Qt3DRender::API::DirectX;
} else if (userRequestedApi == QByteArrayLiteral("null")) {
requestedApi = Qt3DRender::API::Null;
}
}
// If nothing specified, deduce best backend API based on platform
if (requestedApi == Qt3DRender::API::RHI) {
#if defined(Q_OS_WIN)
requestedApi = Qt3DRender::API::DirectX;
#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
requestedApi = Qt3DRender::API::Metal;
#elif QT_CONFIG(opengl)
requestedApi = Qt3DRender::API::OpenGL;
#else
requestedApi = Qt3DRender::API::Vulkan;
#endif
}
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
#if QT_CONFIG(qt3d_vulkan) && QT_CONFIG(vulkan)
if (requestedApi == Qt3DRender::API::Vulkan) {
QRhiVulkanInitParams params;
params.inst = &Qt3DRender::staticVulkanInstance();
m_rhi = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags);
qCWarning(Backend) << "Initializing RHI with Vulkan backend";
}
#endif
#ifdef Q_OS_WIN
if (requestedApi == Qt3DRender::API::DirectX) {
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
m_rhi = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags);
qCWarning(Backend) << "Initializing RHI with DirectX backend";
}
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
if (requestedApi == Qt3DRender::API::Metal) {
QRhiMetalInitParams params;
m_rhi = QRhi::create(QRhi::Metal, ¶ms, rhiFlags);
qCWarning(Backend) << "Initializing RHI with Metal backend";
}
#endif
if (requestedApi == Qt3DRender::API::Null) {
QRhiInitParams params;
m_rhi = QRhi::create(QRhi::Null, ¶ms, rhiFlags);
qCWarning(Backend) << "Initializing RHI with Null backend";
}
if (requestedApi != Qt3DRender::API::OpenGL && m_rhi == nullptr) {
qCWarning(Backend) << "RHI: Unable to use requested RHI Api, trying to fall back on OpenGL";
requestedApi = Qt3DRender::API::OpenGL;
}
if (requestedApi == Qt3DRender::API::OpenGL) {
#ifndef QT_NO_OPENGL
m_fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
QRhiGles2InitParams params;
params.format = QSurfaceFormat::defaultFormat();
params.fallbackSurface = m_fallbackSurface;
m_rhi = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags);
qCWarning(Backend) << "Initializing RHI with OpenGL backend";
#else
qCWarning(Backend) << "RHI: OpenGL not supported";
#endif
}
Q_ASSERT(m_rhi != nullptr);
}
void SubmissionContext::setDrivenExternally(bool drivenExternally)
{
Q_ASSERT(!m_initialized);
m_drivenExternally = drivenExternally;
}
bool SubmissionContext::drivenExternally() const
{
return m_drivenExternally;
}
bool SubmissionContext::beginDrawing(QSurface *surface)
{
Q_ASSERT(surface);
Q_ASSERT(isInitialized());
// In the Scene3D case it does not make sense to create SwapChains as we
// can only record commands that will be used against QtQuick default's
// swap chain. Also when rendering through Scene3D, QtQuick takes care of
// beginning the frame
if (m_drivenExternally)
return true;
// Check if we have a swapchain for the Window, if not create one
SwapChainInfo *swapChainInfo = swapChainForSurface(surface);
QRhiSwapChain *swapChain = swapChainInfo->swapChain;
// Resize swapchain if needed
if (swapChain->surfacePixelSize() != swapChain->currentPixelSize()) {
bool couldRebuild = swapChain->createOrResize();
if (!couldRebuild)
return false;
}
m_currentSwapChain = swapChain;
m_currentRenderPassDescriptor = swapChainInfo->renderPassDescriptor;
const auto success = m_rhi->beginFrame(m_currentSwapChain);
return success == QRhi::FrameOpSuccess;
}
void SubmissionContext::endDrawing(bool swapBuffers)
{
Q_UNUSED(swapBuffers);
const bool shouldEndFrame = !m_drivenExternally;
// When rendering through Scene3D, QtQuick takes care of ending the frame
if (shouldEndFrame)
m_rhi->endFrame(m_currentSwapChain, {});
}
QImage SubmissionContext::readFramebuffer(const QRect &rect)
{
Q_UNUSED(rect);
RHI_UNIMPLEMENTED;
return {};
//* QImage img;
//* const unsigned int area = rect.width() * rect.height();
//* unsigned int bytes;
//* GLenum format, type;
//* QImage::Format imageFormat;
//* uint stride;
//*
//* /* format value should match GL internalFormat */
//* GLenum internalFormat = m_renderTargetFormat;
//*
//* switch (m_renderTargetFormat) {
//* case QAbstractTexture::RGBAFormat:
//* case QAbstractTexture::RGBA8_SNorm:
//* case QAbstractTexture::RGBA8_UNorm:
//* case QAbstractTexture::RGBA8U:
//* case QAbstractTexture::SRGB8_Alpha8:
//*#ifdef QT_OPENGL_ES_2
//* format = GL_RGBA;
//* imageFormat = QImage::Format_RGBA8888_Premultiplied;
//*#else
//* format = GL_BGRA;
//* imageFormat = QImage::Format_ARGB32_Premultiplied;
//* internalFormat = GL_RGBA8;
//*#endif
//* type = GL_UNSIGNED_BYTE;
//* bytes = area * 4;
//* stride = rect.width() * 4;
//* break;
//* case QAbstractTexture::SRGB8:
//* case QAbstractTexture::RGBFormat:
//* case QAbstractTexture::RGB8U:
//* case QAbstractTexture::RGB8_UNorm:
//*#ifdef QT_OPENGL_ES_2
//* format = GL_RGBA;
//* imageFormat = QImage::Format_RGBX8888;
//*#else
//* format = GL_BGRA;
//* imageFormat = QImage::Format_RGB32;
//* internalFormat = GL_RGB8;
//*#endif
//* type = GL_UNSIGNED_BYTE;
//* bytes = area * 4;
//* stride = rect.width() * 4;
//* break;
//*#ifndef QT_OPENGL_ES_2
//* case QAbstractTexture::RG11B10F:
//* bytes = area * 4;
//* format = GL_RGB;
//* type = GL_UNSIGNED_INT_10F_11F_11F_REV;
//* imageFormat = QImage::Format_RGB30;
//* stride = rect.width() * 4;
//* break;
//* case QAbstractTexture::RGB10A2:
//* bytes = area * 4;
//* format = GL_RGBA;
//* type = GL_UNSIGNED_INT_2_10_10_10_REV;
//* imageFormat = QImage::Format_A2BGR30_Premultiplied;
//* stride = rect.width() * 4;
//* break;
//* case QAbstractTexture::R5G6B5:
//* bytes = area * 2;
//* format = GL_RGB;
//* type = GL_UNSIGNED_SHORT;
//* internalFormat = GL_UNSIGNED_SHORT_5_6_5_REV;
//* imageFormat = QImage::Format_RGB16;
//* stride = rect.width() * 2;
//* break;
//* case QAbstractTexture::RGBA16F:
//* case QAbstractTexture::RGBA16U:
//* case QAbstractTexture::RGBA32F:
//* case QAbstractTexture::RGBA32U:
//* bytes = area * 16;
//* format = GL_RGBA;
//* type = GL_FLOAT;
//* imageFormat = QImage::Format_ARGB32_Premultiplied;
//* stride = rect.width() * 16;
//* break;
//*#endif
//* default:
//* auto warning = qWarning();
//* warning << "Unable to convert";
//* QtDebugUtils::formatQEnum(warning, m_renderTargetFormat);
//* warning << "render target texture format to QImage.";
//* return img;
//* }
//*
//* GLint samples = 0;
//* m_gl->functions()->glGetIntegerv(GL_SAMPLES, &samples);
//* if (samples > 0 &&
//!m_glHelper->supportsFeature(GraphicsHelperInterface::BlitFramebuffer)) {
//* qCWarning(Backend) << Q_FUNC_INFO << "Unable to capture multisampled framebuffer; "
//* "Required feature BlitFramebuffer is missing.";
//* return img;
//* }
//*
//* img = QImage(rect.width(), rect.height(), imageFormat);
//*
//* QScopedArrayPointer<uchar> data(new uchar [bytes]);
//*
//* if (samples > 0) {
//* // resolve multisample-framebuffer to renderbuffer and read pixels from it
//* GLuint fbo, rb;
//* QOpenGLFunctions *gl = m_gl->functions();
//* gl->glGenFramebuffers(1, &fbo);
//* gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
//* gl->glGenRenderbuffers(1, &rb);
//* gl->glBindRenderbuffer(GL_RENDERBUFFER, rb);
//* gl->glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, rect.width(),
//rect.height());
//* gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
//GL_RENDERBUFFER, rb);
//*
//* const GLenum status = gl->glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
//* if (status != GL_FRAMEBUFFER_COMPLETE) {
//* gl->glDeleteRenderbuffers(1, &rb);
//* gl->glDeleteFramebuffers(1, &fbo);
//* qCWarning(Backend) << Q_FUNC_INFO << "Copy-framebuffer not complete: " << status;
//* return img;
//* }
//*
//* m_glHelper->blitFramebuffer(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() +
//rect.height(),
//* 0, 0, rect.width(), rect.height(),
//* GL_COLOR_BUFFER_BIT, GL_NEAREST);
//* gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
//* gl->glReadPixels(0,0,rect.width(), rect.height(), format, type, data.data());
//*
//* copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(),
//m_renderTargetFormat);
//*
//* gl->glBindRenderbuffer(GL_RENDERBUFFER, rb);
//* gl->glDeleteRenderbuffers(1, &rb);
//* gl->glBindFramebuffer(GL_FRAMEBUFFER, m_activeFBO);
//* gl->glDeleteFramebuffers(1, &fbo);
//* } else {
//* // read pixels directly from framebuffer
//* m_gl->functions()->glReadPixels(rect.x(), rect.y(), rect.width(), rect.height(),
//format, type, data.data());
//* copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(),
//m_renderTargetFormat);
//* }
//*
//* return img;
}
void SubmissionContext::releaseResources()
{
m_renderBufferHash.clear();
RHI_UNIMPLEMENTED;
if (m_currentUpdates) {
m_currentUpdates->release();
m_currentUpdates = nullptr;
}
// Free RHI resources
{
qCDebug(Backend) << Q_FUNC_INFO;
// We must ensure no remaining resource before deleting m_rhi.
m_renderer->rhiResourceManagers()->releaseAllResources();
auto it = m_swapChains.begin();
while (it != m_swapChains.end()) {
SwapChainInfo &swapChainInfo = it.value();
delete swapChainInfo.renderPassDescriptor;
delete swapChainInfo.renderBuffer;
delete swapChainInfo.swapChain;
it = m_swapChains.erase(it);
}
// Only destroy RHI context if we created it
if (m_ownsRhiCtx)
delete m_rhi;
m_rhi = nullptr;
#ifndef QT_NO_OPENGL
delete m_fallbackSurface;
m_fallbackSurface = nullptr;
#endif
}
//* // Stop and destroy the OpenGL logger
//* if (m_debugLogger) {
//* m_debugLogger->stopLogging();
//* m_debugLogger.reset(nullptr);
//* }
}
// Called when Scene3D is used
void SubmissionContext::setRHIContext(QRhi *ctx)
{
m_rhi = ctx;
}
// Scene3D
void SubmissionContext::setDefaultRenderTarget(QRhiRenderTarget *target)
{
m_defaultRenderTarget = target;
}
// Scene3D
void SubmissionContext::setCommandBuffer(QRhiCommandBuffer *commandBuffer)
{
m_defaultCommandBuffer = commandBuffer;
}
void SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId,
const AttachmentPack &attachments)
{
Q_UNUSED(fboId);
Q_UNUSED(attachments);
RHI_UNIMPLEMENTED;
// Set FBO attachments. These are normally textures, except that on Open GL
// ES <= 3.1 we must use a renderbuffer if a combined depth+stencil is
// desired since this cannot be achieved neither with a single texture (not
// before GLES 3.2) nor with separate textures (no suitable format for
// stencil before 3.1 with the appropriate extension).
//* QSize fboSize;
//* RHITextureManager *rhiTextureManager =
//m_renderer->rhiResourceManagers()->rhiTextureManager();
//* const auto attachments_ = attachments.attachments();
//* for (const Attachment &attachment : attachments_) {
//* RHITexture *rTex = rhiTextureManager->lookupResource(attachment.m_textureUuid);
//* if (!m_glHelper->frameBufferNeedsRenderBuffer(attachment)) {
//* QOpenGLTexture *glTex = rTex ? rTex->getGLTexture() : nullptr;
//* if (glTex != nullptr) {
//* // The texture can not be rendered simultaniously by another renderer
//* Q_ASSERT(!rTex->isExternalRenderingEnabled());
//* if (fboSize.isEmpty())
//* fboSize = QSize(glTex->width(), glTex->height());
//* else
//* fboSize = QSize(qMin(fboSize.width(), glTex->width()),
//qMin(fboSize.height(), glTex->height()));
//* m_glHelper->bindFrameBufferAttachment(glTex, attachment);
//* }
//* } else {
//* RenderBuffer *renderBuffer = rTex ? rTex->getOrCreateRenderBuffer() : nullptr;
//* if (renderBuffer) {
//* if (fboSize.isEmpty())
//* fboSize = QSize(renderBuffer->width(), renderBuffer->height());
//* else
//* fboSize = QSize(qMin(fboSize.width(), renderBuffer->width()),
//qMin(fboSize.height(), renderBuffer->height()));
//* m_glHelper->bindFrameBufferAttachment(renderBuffer, attachment);
//* }
//* }
//* }
//* m_renderTargetsSize.insert(fboId, fboSize);
}
void SubmissionContext::activateDrawBuffers(const AttachmentPack &attachments)
{
Q_UNUSED(attachments);
RHI_UNIMPLEMENTED;
//* const std::vector<int> activeDrawBuffers = attachments.getGlDrawBuffers();
//*
//* if (m_glHelper->checkFrameBufferComplete()) {
//* if (activeDrawBuffers.size() > 1) {// We need MRT
//* if (m_glHelper->supportsFeature(GraphicsHelperInterface::MRT)) {
//* // Set up MRT, glDrawBuffers...
//* m_glHelper->drawBuffers(activeDrawBuffers.size(), activeDrawBuffers.data());
//* }
//* }
//* } else {
//* qCWarning(Backend) << "FBO incomplete";
//* }
}
void SubmissionContext::applyState(const StateVariant &stateVariant,
QRhiGraphicsPipeline *graphicsPipeline)
{
switch (stateVariant.type) {
case AlphaCoverageStateMask: {
applyStateHelper(static_cast<const AlphaCoverage *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case AlphaTestMask: {
applyStateHelper(static_cast<const AlphaFunc *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case BlendStateMask: {
applyStateHelper(static_cast<const BlendEquation *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case BlendEquationArgumentsMask: {
applyStateHelper(static_cast<const BlendEquationArguments *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case MSAAEnabledStateMask: {
applyStateHelper(static_cast<const MSAAEnabled *>(stateVariant.constState()),
graphicsPipeline, m_renderer->format());
break;
}
case CullFaceStateMask: {
applyStateHelper(static_cast<const CullFace *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case DepthWriteStateMask: {
applyStateHelper(static_cast<const NoDepthMask *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case DepthTestStateMask: {
applyStateHelper(static_cast<const DepthTest *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case DepthRangeMask: {
applyStateHelper(static_cast<const DepthRange *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case RasterModeMask: {
applyStateHelper(static_cast<const RasterMode *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case FrontFaceStateMask: {
applyStateHelper(static_cast<const FrontFace *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case ScissorStateMask: {
applyStateHelper(static_cast<const ScissorTest *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case StencilTestStateMask: {
applyStateHelper(static_cast<const StencilTest *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case PointSizeMask: {
applyStateHelper(static_cast<const PointSize *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case PolygonOffsetStateMask: {
applyStateHelper(static_cast<const PolygonOffset *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case ColorStateMask: {
applyStateHelper(static_cast<const ColorMask *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case ClipPlaneMask: {
applyStateHelper(static_cast<const ClipPlane *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case SeamlessCubemapMask: {
applyStateHelper(static_cast<const SeamlessCubemap *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case StencilOpMask: {
applyStateHelper(static_cast<const StencilOp *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case StencilWriteStateMask: {
applyStateHelper(static_cast<const StencilMask *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case DitheringStateMask: {
applyStateHelper(static_cast<const Dithering *>(stateVariant.constState()),
graphicsPipeline);
break;
}
case LineWidthMask: {
applyStateHelper(static_cast<const LineWidth *>(stateVariant.constState()),
graphicsPipeline);
break;
}
default:
Q_UNREACHABLE();
}
}
void SubmissionContext::applyStateSet(const RenderStateSet *ss,
QRhiGraphicsPipeline *graphicsPipeline)
{
// Set default state values on graphicsPipeline
QRhiGraphicsPipeline::Flags flags{};
graphicsPipeline->setFlags(flags);
graphicsPipeline->setDepthWrite(true);
graphicsPipeline->setDepthTest(true);
graphicsPipeline->setDepthOp(QRhiGraphicsPipeline::Less);
graphicsPipeline->setCullMode(QRhiGraphicsPipeline::Back);
graphicsPipeline->setSampleCount(format().samples());
graphicsPipeline->setStencilTest(false);
graphicsPipeline->setStencilReadMask(0xff);
graphicsPipeline->setStencilWriteMask(0xff);
QRhiGraphicsPipeline::StencilOpState stencilOp;
stencilOp.failOp = QRhiGraphicsPipeline::Keep;
stencilOp.depthFailOp = QRhiGraphicsPipeline::Keep;
stencilOp.passOp = QRhiGraphicsPipeline::Keep;
stencilOp.compareOp = QRhiGraphicsPipeline::Always;
graphicsPipeline->setStencilFront(stencilOp);
graphicsPipeline->setStencilBack(stencilOp);
QRhiGraphicsPipeline::TargetBlend blend;
blend.colorWrite = QRhiGraphicsPipeline::ColorMask(0xF); // R | G | B | A
blend.enable = false;
blend.srcColor = QRhiGraphicsPipeline::One;
blend.dstColor = QRhiGraphicsPipeline::Zero;
blend.srcAlpha = QRhiGraphicsPipeline::One;
blend.dstAlpha = QRhiGraphicsPipeline::Zero;
graphicsPipeline->setTargetBlends({blend});
const auto &statesToSet = ss->states();
for (const StateVariant &ds : statesToSet)
applyState(ds, graphicsPipeline);
}
StateVariant *SubmissionContext::getState(RenderStateSet *ss, StateMask type) const
{
const auto &statesToSet = ss->states();
for (qsizetype i = 0, m = statesToSet.size(); i < m; ++i) {
const StateVariant &ds = statesToSet.at(i);
if (ds.type == type)
return ss->states().data() + i;
}
return nullptr;
}
SubmissionContext::SwapChainInfo *SubmissionContext::swapChainForSurface(QSurface *surface) noexcept
{
SwapChainInfo &swapChainInfo = m_swapChains[surface];
auto &swapChain = swapChainInfo.swapChain;
if (swapChain == nullptr) {
swapChain = m_rhi->newSwapChain();
Q_ASSERT(surface->surfaceClass() == QSurface::Window);
QWindow *window = static_cast<QWindow *>(surface);
Q_ASSERT(window != nullptr);
const int samples = format().samples();
swapChain->setWindow(window);
// Allow to read back from swap chain for RenderCapture to work
swapChain->setFlags(QRhiSwapChain::Flags { QRhiSwapChain::UsedAsTransferSource });
swapChain->setSampleCount(samples);
QRhiRenderBuffer *renderBuffer =
m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(), samples,
QRhiRenderBuffer::UsedWithSwapChainOnly);
swapChain->setDepthStencil(renderBuffer);
QRhiRenderPassDescriptor *renderPassDescriptor =
swapChain->newCompatibleRenderPassDescriptor();
swapChain->setRenderPassDescriptor(renderPassDescriptor);
// Build swapChain the first time
if (swapChain->createOrResize()) {
swapChainInfo.swapChain = swapChain;
swapChainInfo.renderBuffer = renderBuffer;
swapChainInfo.renderPassDescriptor = renderPassDescriptor;
} else {
swapChain->deleteLater();
m_swapChains.remove(surface);
return nullptr;
}
}
return &swapChainInfo;
}
QRhiCommandBuffer *SubmissionContext::currentFrameCommandBuffer() const
{
// When rendering with Scene3D, we have to use the Command Buffer provided by QtQuick
// When Qt3D renders on its own, we use our own Command Buffer which we can
// retrieve from the current Swap Chain
if (m_defaultCommandBuffer)
return m_defaultCommandBuffer;
return m_currentSwapChain->currentFrameCommandBuffer();
}
QRhiRenderTarget *SubmissionContext::currentFrameRenderTarget() const
{
return m_currentSwapChain->currentFrameRenderTarget();
}
QRhiRenderTarget *SubmissionContext::defaultRenderTarget() const
{
return m_defaultRenderTarget;
}
QRhiSwapChain *SubmissionContext::currentSwapChain() const
{
return m_currentSwapChain;
}
QRhiRenderPassDescriptor *SubmissionContext::currentRenderPassDescriptor() const
{
return m_currentRenderPassDescriptor;
}
QSurfaceFormat SubmissionContext::format() const noexcept
{
if (this->m_rhi && this->m_rhi->backend() == QRhi::OpenGLES2) {
auto rhi_gl = static_cast<const QRhiGles2NativeHandles *>(this->m_rhi->nativeHandles());
return rhi_gl->context->format();
}
return QSurfaceFormat::defaultFormat();
}
void SubmissionContext::updateBuffer(Buffer *buffer)
{
const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it =
m_renderBufferHash.find(buffer->peerId());
if (it != m_renderBufferHash.end())
uploadDataToRHIBuffer(
buffer, m_renderer->rhiResourceManagers()->rhiBufferManager()->data(it.value()));
}
QByteArray SubmissionContext::downloadBufferContent(Buffer *buffer)
{
const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it =
m_renderBufferHash.find(buffer->peerId());
if (it != m_renderBufferHash.end())
return downloadDataFromRHIBuffer(
buffer, m_renderer->rhiResourceManagers()->rhiBufferManager()->data(it.value()));
return QByteArray();
}
void SubmissionContext::releaseBuffer(Qt3DCore::QNodeId bufferId)
{
auto it = m_renderBufferHash.find(bufferId);
if (it != m_renderBufferHash.end()) {
HRHIBuffer glBuffHandle = it.value();
RHIBuffer *glBuff =
m_renderer->rhiResourceManagers()->rhiBufferManager()->data(glBuffHandle);
Q_ASSERT(glBuff);
// Destroy the GPU resource
glBuff->destroy();
// Destroy the RHIBuffer instance
m_renderer->rhiResourceManagers()->rhiBufferManager()->releaseResource(bufferId);
// Remove Id - HRHIBuffer entry
m_renderBufferHash.erase(it);
}
}
bool SubmissionContext::hasRHIBufferForBuffer(Buffer *buffer)
{
const QHash<Qt3DCore::QNodeId, HRHIBuffer>::iterator it =
m_renderBufferHash.find(buffer->peerId());
return (it != m_renderBufferHash.end());
}
RHIBuffer *SubmissionContext::rhiBufferForRenderBuffer(Buffer *buf)
{
if (!m_renderBufferHash.contains(buf->peerId()))
m_renderBufferHash.insert(buf->peerId(), createRHIBufferFor(buf));
return m_renderer->rhiResourceManagers()->rhiBufferManager()->data(
m_renderBufferHash.value(buf->peerId()));
}
HRHIBuffer SubmissionContext::createRHIBufferFor(Buffer *buffer)
{
m_renderer->rhiResourceManagers()->rhiBufferManager()->getOrCreateResource(buffer->peerId());
return m_renderer->rhiResourceManagers()->rhiBufferManager()->lookupHandle(buffer->peerId());
}
bool SubmissionContext::bindRHIBuffer(RHIBuffer *buffer, RHIBuffer::Type type)
{
return buffer->bind(this, type);
}
void SubmissionContext::uploadDataToRHIBuffer(Buffer *buffer, RHIBuffer *b)
{
// If the buffer is dirty (hence being called here)
// there are two possible cases
// * setData was called changing the whole data or functor (or the usage pattern)
// * partial buffer updates where received
// Note: we are only storing the updates data CPU side at this point
// actually upload will be performed when the buffer will be bound
// as we would otherwise need to know the usage type of the buffer
auto updates = Qt3DCore::moveAndClear(buffer->pendingBufferUpdates());
if (updates.empty())
qCWarning(Backend) << "Buffer has no data to upload";
for (auto it = updates.begin(); it != updates.end(); ++it) {
auto update = it;
// We have a partial update
if (update->offset >= 0) {
// accumulate sequential updates as single one
qsizetype bufferSize = update->data.size();
auto it2 = it + 1;
while ((it2 != updates.end()) && (it2->offset - update->offset == bufferSize)) {
bufferSize += it2->data.size();
++it2;
}
update->data.resize(bufferSize);
while (it + 1 != it2) {
++it;
update->data.replace(it->offset - update->offset, it->data.size(), it->data);
it->data.clear();
}
// TO DO: based on the number of updates .., it might make sense to
// sometime use glMapBuffer rather than glBufferSubData
b->update(update->data, update->offset);
} else {
// We have an update that was done by calling QBuffer::setData
// which is used to resize or entirely clear the buffer
// Note: we use the buffer data directly in that case
b->allocate(buffer->data(), false);
}
}
qCDebug(Io) << "uploaded buffer size=" << buffer->data().size();
}
QByteArray SubmissionContext::downloadDataFromRHIBuffer(Buffer *buffer, RHIBuffer *b)
{
if (!bindRHIBuffer(b,
RHIBuffer::ArrayBuffer)) // We're downloading, the type doesn't matter here
qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed";
return b->download(this, buffer->data().size());
}
void SubmissionContext::blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId,
Qt3DCore::QNodeId outputRenderTargetId, QRect inputRect,
QRect outputRect, uint defaultFboId,
QRenderTargetOutput::AttachmentPoint inputAttachmentPoint,
QRenderTargetOutput::AttachmentPoint outputAttachmentPoint,
QBlitFramebuffer::InterpolationMethod interpolationMethod)
{
Q_UNUSED(inputRenderTargetId);
Q_UNUSED(outputRenderTargetId);
Q_UNUSED(inputRect);
Q_UNUSED(outputRect);
Q_UNUSED(defaultFboId);
Q_UNUSED(inputAttachmentPoint);
Q_UNUSED(outputAttachmentPoint);
Q_UNUSED(interpolationMethod);
RHI_UNIMPLEMENTED;
}
namespace {
template<std::size_t N>
constexpr int getFirstAvailableBit(const std::bitset<N> &bits)
{
for (std::size_t i = 0; i < N; i++) {
if (!bits.test(i))
return int(i);
}
return -1;
}
// This function ensures that the shader stages all have the same bindings
void preprocessRHIShader(std::vector<QByteArray> &shaderCodes)
{
// Map the variable names to bindings
std::map<QByteArray, int> bindings;
bindings["qt3d_render_view_uniforms"] = 0;
bindings["qt3d_command_uniforms"] = 1;
std::bitset<512> assignedBindings;
assignedBindings.set(0);
assignedBindings.set(1);
thread_local const QRegularExpression samplerRegex(
QStringLiteral("binding\\s*=\\s*([0-9]+).*\\)\\s*uniform\\s*[ui]?sampler[a-zA-Z0-9]+"
"\\s*([a-zA-Z0-9_]+)\\s*;"));
thread_local const QRegularExpression uboRegex(
QStringLiteral("(?:std140\\s*,\\s*binding\\s*=\\s*([0-9]+).*|binding\\s*=\\s*([0-9]+)"
"\\s*,\\s*std140.*)\\)\\s*uniform\\s*([a-zA-Z0-9_]+)"));
auto replaceBinding = [&bindings, &assignedBindings](
qsizetype &offset, QRegularExpressionMatch &match, QString &code,
int indexCapture, int variableCapture) noexcept {
int index = match.captured(indexCapture).toInt();
QByteArray variable = match.captured(variableCapture).toUtf8();
auto it = bindings.find(variable);
if (it == bindings.end()) {
// 1. Check if the index is already used
if (assignedBindings.test(index)) {
index = getFirstAvailableBit(assignedBindings);
if (index == -1) {
return;
}
const qsizetype indexStartOffset = match.capturedStart(indexCapture);
const qsizetype indexEndOffset = match.capturedEnd(indexCapture);
const qsizetype indexLength = indexEndOffset - indexStartOffset;
code.replace(indexStartOffset, indexLength, QByteArray::number(index));
}
assignedBindings.set(index);
bindings.emplace(std::move(variable), index);
} else {
int indexToUse = it->second;
const qsizetype indexStartOffset = match.capturedStart(indexCapture);
const qsizetype indexEndOffset = match.capturedEnd(indexCapture);
const qsizetype indexLength = indexEndOffset - indexStartOffset;
code.replace(indexStartOffset, indexLength, QByteArray::number(indexToUse));
}
// This may fail in the case where the replaced offset is an incredibly long number,
// which seems quite unlikely
offset = match.capturedEnd(0);
};
for (QByteArray &shaderCode : shaderCodes) {
// Since QRegularExpression::match takes a QString anyway, convert once beforehand
QString shaderString = shaderCode;
// Regex for the sampler variables
qsizetype offset = 0;
auto match = samplerRegex.match(shaderString, offset);
while (match.hasMatch()) {
const int indexCapture = 1;
const int variableCapture = 2;
replaceBinding(offset, match, shaderString, indexCapture, variableCapture);
match = samplerRegex.match(shaderString, offset);
}
// Regex for the UBOs
offset = 0;
match = uboRegex.match(shaderString, offset);
while (match.hasMatch()) {
const int indexCapture = !match.capturedView(1).isEmpty() ? 1 : 2;
const int variableCapture = 3;
replaceBinding(offset, match, shaderString, indexCapture, variableCapture);
match = uboRegex.match(shaderString, offset);
}
shaderCode = shaderString.toUtf8();
}
}
QShaderVersion glslVersionForFormat(const QSurfaceFormat &format) noexcept
{
const int major = format.majorVersion();
const int minor = format.minorVersion();
const auto type = format.renderableType();
if (type != QSurfaceFormat::OpenGLES) {
static const QHash<std::pair<int, int>, int> glVersionToGLSLVersion = {
{ { 4, 6 }, 460 }, { { 4, 5 }, 450 }, { { 4, 4 }, 440 }, { { 4, 3 }, 430 },
{ { 4, 2 }, 420 }, { { 4, 1 }, 410 }, { { 4, 0 }, 400 }, { { 3, 3 }, 330 },
{ { 3, 2 }, 150 }, { { 3, 2 }, 120 }, { { 3, 1 }, 120 },
};
const auto it = glVersionToGLSLVersion.find({ major, minor });
if (it == glVersionToGLSLVersion.end()) {
if (major < 3) {
return 120;
} else {
return major * 100 + minor * 10;
}
} else {
return *it;
}
}
else {
static const QHash<std::pair<int, int>, int> glVersionToGLSLVersion = {
{ { 3, 2 }, 320 }, { { 3, 1 }, 310 }, { { 3, 0 }, 300 },
};
const auto it = glVersionToGLSLVersion.find({ major, minor });
if (it == glVersionToGLSLVersion.end()) {
if (major < 3) {
return {100, QShaderVersion::GlslEs};
} else {
return {major * 100 + minor * 10, QShaderVersion::GlslEs};
}
} else {
return {*it, QShaderVersion::GlslEs};
}
}
}
}
// Called by GL Command Thread
SubmissionContext::ShaderCreationInfo SubmissionContext::createShaderProgram(RHIShader *shader)
{
// Compile shaders
const auto &shaderCode = shader->shaderCode();
QShaderBaker b;
QList<QShaderBaker::GeneratedShader> generatedShaders;
#if QT_FEATURE_vulkan
if (m_rhi->backend() == QRhi::Vulkan)
generatedShaders.emplace_back(QShader::SpirvShader, 100);
#endif
#ifndef QT_NO_OPENGL
if (m_rhi->backend() == QRhi::OpenGLES2)
generatedShaders.emplace_back(QShader::GlslShader, glslVersionForFormat(format()));
#endif
#ifdef Q_OS_WIN
if (m_rhi->backend() == QRhi::D3D11)
generatedShaders.emplace_back(QShader::HlslShader, QShaderVersion(50));
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
if (m_rhi->backend() == QRhi::Metal)
generatedShaders.emplace_back(QShader::MslShader, QShaderVersion(12));
#endif
QList<QShader::Variant> generatedShaderVariants(generatedShaders.size());
b.setGeneratedShaders(generatedShaders);
b.setGeneratedShaderVariants(generatedShaderVariants);
// TODO handle caching as QShader does not have a built-in mechanism for that
QString logs;
bool success = true;
for (size_t i = QShaderProgram::Vertex; i <= QShaderProgram::Compute; ++i) {
const QShaderProgram::ShaderType type = static_cast<QShaderProgram::ShaderType>(i);
if (!shaderCode.at(i).isEmpty()) {
// Note: logs only return the error but not all the shader code
// we could append it
const auto rhiStage = rhiShaderStage(type);
b.setSourceString(shaderCode.at(i), rhiStage);
QShader bakedShader = b.bake();
if (b.errorMessage() != QString() || !bakedShader.isValid()) {
qDebug() << "Shader Error: " << b.errorMessage() << shaderCode.at(i).data()
<< rhiStage;
logs += b.errorMessage();
success = false;
}
shader->m_stages[rhiStage] = std::move(bakedShader);
}
}
// Perform shader introspection
if (success)
shader->introspect();
return { success, logs };
}
// Called by Renderer::updateResources
void SubmissionContext::loadShader(Shader *shaderNode, ShaderManager *shaderManager,
RHIShaderManager *rhiShaderManager)
{
const Qt3DCore::QNodeId shaderId = shaderNode->peerId();
RHIShader *rhiShader = rhiShaderManager->lookupResource(shaderId);
// We already have a shader associated with the node
if (rhiShader != nullptr) {
// We need to abandon it
rhiShaderManager->abandon(rhiShader, shaderNode);
}
// We create or adopt an already created rhiShader
rhiShader = rhiShaderManager->createOrAdoptExisting(shaderNode);
const std::vector<Qt3DCore::QNodeId> &sharedShaderIds =
rhiShaderManager->shaderIdsForProgram(rhiShader);
if (sharedShaderIds.size() == 1) {
{
// Shader in the cache hasn't been loaded yet
// We want a copy of the QByteArray as preprocessRHIShader will
// modify them
std::vector<QByteArray> shaderCodes = shaderNode->shaderCode();
preprocessRHIShader(shaderCodes);
rhiShader->setShaderCode(std::move(shaderCodes));
}
const ShaderCreationInfo loadResult = createShaderProgram(rhiShader);
shaderNode->setStatus(loadResult.linkSucceeded ? QShaderProgram::Ready
: QShaderProgram::Error);
shaderNode->setLog(loadResult.logs);
// Loaded in the sense we tried to load it (and maybe it failed)
rhiShader->setLoaded(true);
} else {
// Find an already loaded shader that shares the same QShaderProgram
for (const Qt3DCore::QNodeId &sharedShaderId : sharedShaderIds) {
if (sharedShaderId != shaderNode->peerId()) {
Shader *refShader = shaderManager->lookupResource(sharedShaderId);
// We only introspect once per actual OpenGL shader program
// rather than once per ShaderNode.
shaderNode->initializeFromReference(*refShader);
break;
}
}
}
shaderNode->unsetDirty();
// Ensure we will rebuilt material caches
shaderNode->requestCacheRebuild();
}
const GraphicsApiFilterData *SubmissionContext::contextInfo() const
{
return &m_contextInfo;
}
} // namespace Rhi
} // namespace Render
} // namespace Qt3DRender of namespace
QT_END_NAMESPACE