/**************************************************************************** ** ** Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "submissioncontext_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) #include #endif #ifndef QT_NO_OPENGL #include #endif #if QT_CONFIG(vulkan) #include #endif #include QT_BEGIN_NAMESPACE using namespace Qt3DCore; namespace Qt3DRender { namespace Render { namespace Rhi { static QHash static_contexts; unsigned int nextFreeContextId() noexcept { for (unsigned int i = 0; i < 0xffff; ++i) { if (!static_contexts.contains(i)) return i; } qFatal("Couldn't find free context ID"); return 0; } 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(srcData) + stride * (height - 1); // for (uint i = 0; i < height; ++i) { // uchar *dstScanline = img.scanLine(i); // float *pSrc = reinterpret_cast(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 void applyStateHelper(const GenericState *state, QRhiGraphicsPipeline *gp) noexcept { Q_UNUSED(state); Q_UNUSED(gp); qWarning() << "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(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(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(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: qWarning() << "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(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)); } 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_ownsRhiCtx(false), m_drivenExternally(false), m_id(nextFreeContextId()), m_surface(nullptr), m_renderTargetFormat(QAbstractTexture::NoFormat), m_material(nullptr), m_renderer(nullptr), m_uboTempArray(QByteArray(1024, 0)), m_initialized(false), m_rhi(nullptr), m_currentSwapChain(nullptr), m_currentRenderPassDescriptor(nullptr), m_defaultRenderTarget(nullptr), m_defaultCommandBuffer(nullptr) #ifndef QT_NO_OPENGL , m_fallbackSurface(nullptr) #endif { static_contexts[m_id] = this; 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(); Q_ASSERT(static_contexts[m_id] == this); static_contexts.remove(m_id); } 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(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) { qWarning() << "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 qWarning() << "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); m_surface = 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 (m_surface->size() != 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 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; // 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 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(stateVariant.constState()), graphicsPipeline); break; } case AlphaTestMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case BlendStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case BlendEquationArgumentsMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case MSAAEnabledStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline, m_renderer->format()); break; } case CullFaceStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case DepthWriteStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case DepthTestStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case DepthRangeMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case RasterModeMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case FrontFaceStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case ScissorStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case StencilTestStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case PointSizeMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case PolygonOffsetStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case ColorStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case ClipPlaneMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case SeamlessCubemapMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case StencilOpMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case StencilWriteStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case DitheringStateMask: { applyStateHelper(static_cast(stateVariant.constState()), graphicsPipeline); break; } case LineWidthMask: { applyStateHelper(static_cast(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 (int 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(surface); Q_ASSERT(window != nullptr); const int samples = format().samples(); swapChain->setWindow(window); swapChain->setFlags(QRhiSwapChain::Flags {}); 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(this->m_rhi->nativeHandles()); return rhi_gl->context->format(); } return QSurfaceFormat::defaultFormat(); } void SubmissionContext::updateBuffer(Buffer *buffer) { const QHash::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::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::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 = std::move(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 int 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 constexpr int getFirstAvailableBit(const std::bitset &bits) { for (std::size_t i = 0; i < N; i++) { if (!bits.test(i)) return i; } return -1; } // This function ensures that the shader stages all have the same bindings void preprocessRHIShader(std::vector &shaderCodes) { // Map the variable names to bindings std::map 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]( int &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 int indexStartOffset = match.capturedStart(indexCapture); const int indexEndOffset = match.capturedEnd(indexCapture); const int 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 int indexStartOffset = match.capturedStart(indexCapture); const int indexEndOffset = match.capturedEnd(indexCapture); const int 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 int 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(); } } int glslVersionForFormat(const QSurfaceFormat &format) noexcept { const int major = format.majorVersion(); const int minor = format.minorVersion(); static const QHash, 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; } } } // Called by GL Command Thread SubmissionContext::ShaderCreationInfo SubmissionContext::createShaderProgram(RHIShader *shader) { // Compile shaders const auto &shaderCode = shader->shaderCode(); QShaderBaker b; QVector generatedShaders; #if QT_FEATURE_vulkan if (m_rhi->backend() == QRhi::Vulkan) generatedShaders.push_back({ QShader::SpirvShader, 100 }); #endif #ifndef QT_NO_OPENGL if (m_rhi->backend() == QRhi::OpenGLES2) generatedShaders.push_back({ QShader::GlslShader, glslVersionForFormat(format()) }); #endif #ifdef Q_OS_WIN if (m_rhi->backend() == QRhi::D3D11) generatedShaders.push_back({ QShader::HlslShader, QShaderVersion(50) }); #endif #ifdef Q_OS_MACOS if (m_rhi->backend() == QRhi::Metal) generatedShaders.push_back({ QShader::MslShader, QShaderVersion(12) }); #endif QVector 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 (int i = QShaderProgram::Vertex; i <= QShaderProgram::Compute; ++i) { const QShaderProgram::ShaderType type = static_cast(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 &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 shaderCodes = shaderNode->shaderCode(); preprocessRHIShader(shaderCodes); rhiShader->setShaderCode(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