/**************************************************************************** ** ** 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:COMM$ ** ** 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. ** ** $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 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; } }; targetBlend.enable = true; targetBlend.opAlpha = getRHIBlendOp(equation); gp->setTargetBlends({ targetBlend }); } void applyStateHelper(const MSAAEnabled *state, QRhiGraphicsPipeline *gp, const QSurfaceFormat &format) noexcept { 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 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_ownCurrent(true), m_id(nextFreeContextId()), m_surface(nullptr), m_activeShader(nullptr), m_renderTargetFormat(QAbstractTexture::NoFormat), m_material(nullptr), m_activeFBO(0), m_renderer(nullptr), m_uboTempArray(QByteArray(1024, 0)), m_initialized(false), m_maxTextureUnits(0), m_defaultFBO(0), m_rhi(nullptr), m_currentSwapChain(nullptr), m_currentRenderPassDescriptor(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; // m_textureContext.initialize(this); Qt3DRender::API requestedApi = Qt3DRender::API::OpenGL; const auto userRequestedApi = qgetenv("QT3D_RHI_DEFAULT_API").toLower(); if (!userRequestedApi.isEmpty()) { if (userRequestedApi == QByteArrayLiteral("opengl")) { 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; } } 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); } #endif #ifdef Q_OS_WIN if (requestedApi == Qt3DRender::API::DirectX) { QRhiD3D11InitParams params; params.enableDebugLayer = true; m_rhi = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); } #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); } #endif if (requestedApi == Qt3DRender::API::Null) { QRhiInitParams params; m_rhi = QRhi::create(QRhi::Null, ¶ms, rhiFlags); } 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); #else qWarning() << "RHI: OpenGL not supported"; #endif } Q_ASSERT(m_rhi != nullptr); } void SubmissionContext::resolveRenderTargetFormat() { RHI_UNIMPLEMENTED; //* const QSurfaceFormat format = m_gl->format(); //* const uint a = (format.alphaBufferSize() == -1) ? 0 : format.alphaBufferSize(); //* const uint r = format.redBufferSize(); //* const uint g = format.greenBufferSize(); //* const uint b = format.blueBufferSize(); //* //* #define RGBA_BITS(r,g,b,a) (r | (g << 6) | (b << 12) | (a << 18)) //* //* const uint bits = RGBA_BITS(r,g,b,a); //* switch (bits) { //* case RGBA_BITS(8,8,8,8): //* m_renderTargetFormat = QAbstractTexture::RGBA8_UNorm; //* break; //* case RGBA_BITS(8,8,8,0): //* m_renderTargetFormat = QAbstractTexture::RGB8_UNorm; //* break; //* case RGBA_BITS(5,6,5,0): //* m_renderTargetFormat = QAbstractTexture::R5G6B5; //* break; //* } //* #undef RGBA_BITS } bool SubmissionContext::beginDrawing(QSurface *surface) { Q_ASSERT(surface); m_surface = surface; // TO DO: Find a way to make to pause work if the window is not exposed // if (m_surface && m_surface->surfaceClass() == QSurface::Window) { // qDebug() << Q_FUNC_INFO << 1; // if (!static_cast(m_surface)->isExposed()) // return false; // qDebug() << Q_FUNC_INFO << 2; // } // Makes the surface current on the OpenGLContext // and sets the right glHelper // m_ownCurrent = !(m_gl->surface() == m_surface); // if (m_ownCurrent && !makeCurrent(m_surface)) // return false; // TODO: cache surface format somewhere rather than doing this every time render surface changes resolveRenderTargetFormat(); #if defined(QT3D_RENDER_ASPECT_RHI_DEBUG) GLint err = m_gl->functions()->glGetError(); if (err != 0) { qCWarning(Backend) << Q_FUNC_INFO << "glGetError:" << err; } #endif Q_ASSERT(isInitialized()); // need to reset these values every frame, may get overwritten elsewhere RHI_UNIMPLEMENTED; if (m_activeShader) { m_activeShader = nullptr; } // Check if we have a swapchain for the Window, if not create one SwapChainInfo *swapChainInfo = swapChainForSurface(surface); QRhiSwapChain *swapChain = swapChainInfo->swapChain; // TO DO: Check if that's required all the time { // Rebuild RenderPassDescriptor // TODO -> this is not necessary, swapChain->buildOrResize already does it // swapChainInfo->renderBuffer->setPixelSize(surface->size()); // swapChainInfo->renderBuffer->build(); // Resize swapchain if needed if (m_surface->size() != swapChain->surfacePixelSize()) { bool couldRebuild = swapChain->buildOrResize(); if (!couldRebuild) return false; } } m_currentSwapChain = swapChain; m_currentRenderPassDescriptor = swapChainInfo->renderPassDescriptor; // Begin Frame const auto success = m_rhi->beginFrame(m_currentSwapChain); return success == QRhi::FrameOpSuccess; } void SubmissionContext::endDrawing(bool swapBuffers) { m_rhi->endFrame(m_currentSwapChain, {}); RHI_UNIMPLEMENTED; //* if (swapBuffers) //* m_gl->swapBuffers(m_surface); //* if (m_ownCurrent) //* m_gl->doneCurrent(); // m_textureContext.endDrawing(); //* static int i = 0; //* if (i++ == 10) //* std::exit(0); } void SubmissionContext::activateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, GLuint defaultFboId) { RHI_UNIMPLEMENTED; GLuint fboId = defaultFboId; // Default FBO if (renderTargetNodeId) { // New RenderTarget if (!m_renderTargets.contains(renderTargetNodeId)) { if (m_defaultFBO && fboId == m_defaultFBO) { // this is the default fbo that some platforms create (iOS), we just register it // Insert FBO into hash m_renderTargets.insert(renderTargetNodeId, fboId); } else { RHI_UNIMPLEMENTED; fboId = createRenderTarget(renderTargetNodeId, attachments); } } else { RHI_UNIMPLEMENTED; fboId = updateRenderTarget(renderTargetNodeId, attachments, true); } } m_activeFBO = fboId; //* m_glHelper->bindFrameBufferObject(m_activeFBO, GraphicsHelperInterface::FBODraw); // Set active drawBuffers activateDrawBuffers(attachments); } GLuint SubmissionContext::createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments) { RHI_UNIMPLEMENTED; return 0; //* const GLuint fboId = m_glHelper->createFrameBufferObject(); //* if (fboId) { //* // The FBO is created and its attachments are set once //* // Insert FBO into hash //* m_renderTargets.insert(renderTargetNodeId, fboId); //* // Bind FBO //* m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); //* bindFrameBufferAttachmentHelper(fboId, attachments); //* } else { //* qCritical("Failed to create FBO"); //* } //* return fboId; } GLuint SubmissionContext::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, bool isActiveRenderTarget) { RHI_UNIMPLEMENTED; return 0; //* const GLuint fboId = m_renderTargets.value(renderTargetNodeId); //* //* // We need to check if one of the attachment was resized //* bool needsResize = !m_renderTargetsSize.contains(fboId); // not even initialized yet? //* if (!needsResize) { //* // render target exists, has attachment been resized? //* RHITextureManager *rhiTextureManager = //m_renderer->rhiResourceManagers()->rhiTextureManager(); //* const QSize s = m_renderTargetsSize[fboId]; //* const auto attachments_ = attachments.attachments(); //* for (const Attachment &attachment : attachments_) { //* RHITexture *rTex = rhiTextureManager->lookupResource(attachment.m_textureUuid); //* // ### TODO QTBUG-64757 this check is insufficient since the //* // texture may have changed to another one with the same size. That //* // case is not handled atm. //* if (rTex) { //* needsResize |= rTex->size() != s; //* if (isActiveRenderTarget && attachment.m_point == //QRenderTargetOutput::Color0) //* m_renderTargetFormat = rTex->properties().format; //* } //* } //* } //* //* if (needsResize) { //* m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); //* bindFrameBufferAttachmentHelper(fboId, attachments); //* } //* //* return fboId; } QSize SubmissionContext::renderTargetSize(const QSize &surfaceSize) const { RHI_UNIMPLEMENTED; return surfaceSize; //* QSize renderTargetSize{}; //* if (m_activeFBO != m_defaultFBO) { //* // For external FBOs we may not have a m_renderTargets entry. //* if (m_renderTargetsSize.contains(m_activeFBO)) { //* renderTargetSize = m_renderTargetsSize[m_activeFBO]; //* } else if (surfaceSize.isValid()) { //* renderTargetSize = surfaceSize; //* } else { //* // External FBO (when used with QtQuick2 Scene3D) //* //* // Query FBO color attachment 0 size //* GLint attachmentObjectType = GL_NONE; //* GLint attachment0Name = 0; //* m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, //* GL_COLOR_ATTACHMENT0, //* GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, //* &attachmentObjectType); //* m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, //* GL_COLOR_ATTACHMENT0, //* GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, //* &attachment0Name); //* //* if (attachmentObjectType == GL_RENDERBUFFER && //m_glHelper->supportsFeature(GraphicsHelperInterface::RenderBufferDimensionRetrieval)) //* renderTargetSize = m_glHelper->getRenderBufferDimensions(attachment0Name); //* else if (attachmentObjectType == GL_TEXTURE && //m_glHelper->supportsFeature(GraphicsHelperInterface::TextureDimensionRetrieval)) //* // Assumes texture level 0 and GL_TEXTURE_2D target //* renderTargetSize = m_glHelper->getTextureDimensions(attachment0Name, //GL_TEXTURE_2D); //* else //* return renderTargetSize; //* } //* } else { //* renderTargetSize = m_surface->size(); //* if (m_surface->surfaceClass() == QSurface::Window) { //* const float dpr = static_cast(m_surface)->devicePixelRatio(); //* renderTargetSize *= dpr; //* } //* } //* return renderTargetSize; } QImage SubmissionContext::readFramebuffer(const QRect &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); } 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 only from RenderThread bool SubmissionContext::activateShader(RHIShader *shader) { RHI_UNIMPLEMENTED; //* if (shader->shaderProgram() != m_activeShader) { //* // Ensure material uniforms are re-applied //* m_material = nullptr; //* //* m_activeShader = shader->shaderProgram(); //* if (Q_LIKELY(m_activeShader != nullptr)) { //* m_activeShader->bind(); //* } else { //* m_glHelper->useProgram(0); //* qWarning() << "No shader program found"; //* return false; //* } //* } return true; } void SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &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) { RHI_UNIMPLEMENTED; //* const QVector 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::setActiveMaterial(Material *rmat) { if (m_material == rmat) return; // m_textureContext.deactivateTexturesWithScope(TextureSubmissionContext::TextureScopeMaterial); m_material = rmat; } 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 graphicsPipeline->setDepthWrite(true); graphicsPipeline->setDepthTest(false); graphicsPipeline->setSampleCount(format().samples()); const QVector statesToSet = ss->states(); for (const StateVariant &ds : statesToSet) applyState(ds, graphicsPipeline); } StateVariant *SubmissionContext::getState(RenderStateSet *ss, StateMask type) const { const QVector &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().begin() + 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->buildOrResize()) { swapChainInfo.swapChain = swapChain; swapChainInfo.renderBuffer = renderBuffer; swapChainInfo.renderPassDescriptor = renderPassDescriptor; } else { swapChain->releaseAndDestroyLater(); m_swapChains.remove(surface); return nullptr; } } return &swapChainInfo; } QRhiCommandBuffer *SubmissionContext::currentFrameCommandBuffer() const { return m_currentSwapChain->currentFrameCommandBuffer(); } QRhiRenderTarget *SubmissionContext::currentFrameRenderTarget() const { return m_currentSwapChain->currentFrameRenderTarget(); } 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(); } // It will be easier if the QGraphicContext applies the QUniformPack // than the other way around bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack) { static const int irradianceId = StringToInt::lookupId(QLatin1String("envLight_irradiance")); static const int specularId = StringToInt::lookupId(QLatin1String("envLight_specular")); // Activate textures and update TextureUniform in the pack // with the correct textureUnit // Set the pinned texture of the previous material texture // to pinable so that we should easily find an available texture unit // m_textureContext.deactivateTexturesWithScope(TextureSubmissionContext::TextureScopeMaterial); // Update the uniforms with the correct texture unit id's PackUniformHash &uniformValues = parameterPack.uniforms(); // Fill Texture Uniform Value with proper texture units // so that they can be applied as regular uniforms in a second step for (int i = 0; i < parameterPack.textures().size(); ++i) { RHI_UNIMPLEMENTED; //* const ShaderParameterPack::NamedResource &namedTex = parameterPack.textures().at(i); //* // Given a Texture QNodeId, we retrieve the associated shared RHITexture //* if (uniformValues.contains(namedTex.glslNameId)) { //* RHITexture *t = //m_renderer->rhiResourceManagers()->rhiTextureManager()->lookupResource(namedTex.nodeId); //* if (t != nullptr) { //* UniformValue &texUniform = uniformValues.value(namedTex.glslNameId); //* if (texUniform.valueType() == UniformValue::TextureValue) { //* const int texUnit = //m_textureContext.activateTexture(TextureSubmissionContext::TextureScopeMaterial, m_gl, t); //* texUniform.data()[namedTex.uniformArrayIndex] = texUnit; //* if (texUnit == -1) { //* if (namedTex.glslNameId != irradianceId && //* namedTex.glslNameId != specularId) { //* // Only return false if we are not dealing with env light textures //* qCWarning(Backend) << "Unable to find suitable Texture Unit"; //* return false; //* } //* } //* } //* } //* } } RHIShader *shader = activeShader(); // TO DO: We could cache the binding points somehow and only do the binding when necessary // for SSBO and UBO // Bind Shader Storage block to SSBO and update SSBO const QVector &blockToSSBOs = parameterPack.shaderStorageBuffers(); for (const BlockToSSBO &b : blockToSSBOs) { RHI_UNIMPLEMENTED; Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); RHIBuffer *ssbo = rhiBufferForRenderBuffer(cpuBuffer); // bindShaderStorageBlock // This is currently not required as we are introspecting the bindingIndex // value from the shaders and not replacing them, making such a call useless // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); // bindShaderStorageBlock(shader->programId(), b.m_blockIndex, b.m_bindingIndex); // Needed to avoid conflict where the buffer would already // be bound as a VertexArray bindRHIBuffer(ssbo, RHIBuffer::ShaderStorageBuffer); ssbo->bindBufferBase(this, b.m_bindingIndex, RHIBuffer::ShaderStorageBuffer); // TO DO: Make sure that there's enough binding points } // Bind UniformBlocks to UBO and update UBO from Buffer // TO DO: Convert ShaderData to Buffer so that we can use that generic process const QVector blockToUBOs = parameterPack.uniformBuffers(); int uboIndex = 0; for (const BlockToUBO &b : blockToUBOs) { RHI_UNIMPLEMENTED; Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); RHIBuffer *ubo = rhiBufferForRenderBuffer(cpuBuffer); // bindUniformBlock(shader->programId(), b.m_blockIndex, uboIndex); // Needed to avoid conflict where the buffer would already // be bound as a VertexArray bindRHIBuffer(ubo, RHIBuffer::UniformBuffer); ubo->bindBufferBase(this, uboIndex++, RHIBuffer::UniformBuffer); // TO DO: Make sure that there's enough binding points } // Update uniforms in the Default Uniform Block const PackUniformHash values = parameterPack.uniforms(); const QVector activeUniforms = parameterPack.submissionUniforms(); for (const ShaderUniform &uniform : activeUniforms) { RHI_UNIMPLEMENTED; // We can use [] as we are sure the the uniform wouldn't // be un activeUniforms if there wasn't a matching value const UniformValue &v = values.value(uniform.m_nameId); // skip invalid textures/images if ((v.valueType() == UniformValue::TextureValue || v.valueType() == UniformValue::ShaderImageValue) && *v.constData() == -1) continue; // applyUniform(uniform, v); } // if not all data is valid, the next frame will be rendered immediately return true; } 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(this); // 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, bool releaseBuffer) { // 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 QVector updates = std::move(buffer->pendingBufferUpdates()); 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(this, 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->orphan(this); // orphan the buffer b->allocate(this, buffer->data(), false); } } if (releaseBuffer) { b->release(this); } 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) { RHI_UNIMPLEMENTED; //* GLuint inputFboId = defaultFboId; //* bool inputBufferIsDefault = true; //* if (!inputRenderTargetId.isNull()) { //* RenderTarget *renderTarget = //m_renderer->nodeManagers()->renderTargetManager()->lookupResource(inputRenderTargetId); //* if (renderTarget) { //* AttachmentPack attachments(renderTarget, //m_renderer->nodeManagers()->attachmentManager()); //* if (m_renderTargets.contains(inputRenderTargetId)) //* inputFboId = updateRenderTarget(inputRenderTargetId, attachments, false); //* else //* inputFboId = createRenderTarget(inputRenderTargetId, attachments); //* } //* inputBufferIsDefault = false; //* } //* //* GLuint outputFboId = defaultFboId; //* bool outputBufferIsDefault = true; //* if (!outputRenderTargetId.isNull()) { //* RenderTarget *renderTarget = //m_renderer->nodeManagers()->renderTargetManager()->lookupResource(outputRenderTargetId); //* if (renderTarget) { //* AttachmentPack attachments(renderTarget, //m_renderer->nodeManagers()->attachmentManager()); //* if (m_renderTargets.contains(outputRenderTargetId)) //* outputFboId = updateRenderTarget(outputRenderTargetId, attachments, false); //* else //* outputFboId = createRenderTarget(outputRenderTargetId, attachments); //* } //* outputBufferIsDefault = false; //* } //* //* // Up until this point the input and output rects are normal Qt rectangles. //* // Convert them to GL rectangles (Y at bottom). //* const int inputFboHeight = inputFboId == defaultFboId ? m_surfaceSize.height() : //m_renderTargetsSize[inputFboId].height(); //* const GLint srcX0 = inputRect.left(); //* const GLint srcY0 = inputFboHeight - (inputRect.top() + inputRect.height()); //* const GLint srcX1 = srcX0 + inputRect.width(); //* const GLint srcY1 = srcY0 + inputRect.height(); //* //* const int outputFboHeight = outputFboId == defaultFboId ? m_surfaceSize.height() : //m_renderTargetsSize[outputFboId].height(); //* const GLint dstX0 = outputRect.left(); //* const GLint dstY0 = outputFboHeight - (outputRect.top() + outputRect.height()); //* const GLint dstX1 = dstX0 + outputRect.width(); //* const GLint dstY1 = dstY0 + outputRect.height(); //* //* //Get the last bounded framebuffers //* const GLuint lastDrawFboId = boundFrameBufferObject(); //* //* // Activate input framebuffer for reading //* bindFramebuffer(inputFboId, GraphicsHelperInterface::FBORead); //* //* // Activate output framebuffer for writing //* bindFramebuffer(outputFboId, GraphicsHelperInterface::FBODraw); //* //* //Bind texture //* if (!inputBufferIsDefault) //* readBuffer(GL_COLOR_ATTACHMENT0 + inputAttachmentPoint); //* //* if (!outputBufferIsDefault) { //* // Note that we use glDrawBuffers, not glDrawBuffer. The //* // latter is not available with GLES. //* const int buf = outputAttachmentPoint; //* drawBuffers(1, &buf); //* } //* //* // Blit framebuffer //* const GLenum mode = interpolationMethod ? GL_NEAREST : GL_LINEAR; //* m_glHelper->blitFramebuffer(srcX0, srcY0, srcX1, srcY1, //* dstX0, dstY0, dstX1, dstY1, //* GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT, //* mode); //* //* // Reset draw buffer //* bindFramebuffer(lastDrawFboId, GraphicsHelperInterface::FBOReadAndDraw); //* if (outputAttachmentPoint != QRenderTargetOutput::Color0) { //* const int buf = QRenderTargetOutput::Color0; //* drawBuffers(1, &buf); //* } } 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(QVector &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; b.setGeneratedShaders({ { QShader::SpirvShader, 100 }, #ifndef QT_NO_OPENGL { QShader::GlslShader, glslVersionForFormat(format()) }, #endif { QShader::HlslShader, QShaderVersion(50) }, { QShader::MslShader, QShaderVersion(12) }, }); b.setGeneratedShaderVariants({ QShader::Variant {}, #ifndef QT_NO_OPENGL QShader::Variant {}, #endif QShader::Variant {}, QShader::Variant {} }); // 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 QVector sharedShaderIds = rhiShaderManager->shaderIdsForProgram(rhiShader); if (sharedShaderIds.size() == 1) { // Shader in the cache hasn't been loaded yet QVector 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