| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 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 <Qt3DRender/qgraphicsapifilter.h> |
| #include <Qt3DRender/qparameter.h> |
| #include <Qt3DRender/qcullface.h> |
| #include <Qt3DRender/private/renderlogging_p.h> |
| #include <Qt3DRender/private/shader_p.h> |
| #include <Qt3DRender/private/material_p.h> |
| #include <Qt3DRender/private/buffer_p.h> |
| #include <Qt3DRender/private/attribute_p.h> |
| #include <Qt3DRender/private/renderstates_p.h> |
| #include <Qt3DRender/private/renderstateset_p.h> |
| #include <Qt3DRender/private/rendertarget_p.h> |
| #include <Qt3DRender/private/nodemanagers_p.h> |
| #include <Qt3DRender/private/buffermanager_p.h> |
| #include <Qt3DRender/private/managers_p.h> |
| #include <Qt3DRender/private/attachmentpack_p.h> |
| #include <Qt3DRender/private/qbuffer_p.h> |
| #include <Qt3DRender/private/stringtoint_p.h> |
| #include <gltexture_p.h> |
| #include <rendercommand_p.h> |
| #include <graphicshelperinterface_p.h> |
| #include <renderer_p.h> |
| #include <glresourcemanagers_p.h> |
| #include <renderbuffer_p.h> |
| #include <glshader_p.h> |
| #include <openglvertexarrayobject_p.h> |
| #include <QOpenGLShaderProgram> |
| |
| #if !defined(QT_OPENGL_ES_2) |
| #include <QOpenGLFunctions_2_0> |
| #include <QOpenGLFunctions_3_2_Core> |
| #include <QOpenGLFunctions_3_3_Core> |
| #include <QOpenGLFunctions_4_3_Core> |
| #include <graphicshelpergl2_p.h> |
| #include <graphicshelpergl3_2_p.h> |
| #include <graphicshelpergl3_3_p.h> |
| #include <graphicshelpergl4_p.h> |
| #endif |
| #include <graphicshelperes2_p.h> |
| #include <graphicshelperes3_p.h> |
| |
| #include <private/qdebug_p.h> |
| #include <QSurface> |
| #include <QWindow> |
| #include <QOpenGLTexture> |
| #include <QOpenGLDebugLogger> |
| |
| QT_BEGIN_NAMESPACE |
| |
| #ifndef GL_READ_FRAMEBUFFER |
| #define GL_READ_FRAMEBUFFER 0x8CA8 |
| #endif |
| |
| #ifndef GL_DRAW_FRAMEBUFFER |
| #define GL_DRAW_FRAMEBUFFER 0x8CA9 |
| #endif |
| |
| namespace Qt3DRender { |
| namespace Render { |
| namespace OpenGL { |
| |
| |
| static QHash<unsigned int, SubmissionContext*> static_contexts; |
| |
| unsigned int nextFreeContextId() |
| { |
| 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 { |
| |
| GLBuffer::Type attributeTypeToGLBufferType(QAttribute::AttributeType type) |
| { |
| switch (type) { |
| case QAttribute::VertexAttribute: |
| return GLBuffer::ArrayBuffer; |
| case QAttribute::IndexAttribute: |
| return GLBuffer::IndexBuffer; |
| case QAttribute::DrawIndirectAttribute: |
| return GLBuffer::DrawIndirectBuffer; |
| default: |
| Q_UNREACHABLE(); |
| } |
| } |
| |
| void copyGLFramebufferDataToImage(QImage &img, const uchar *srcData, uint stride, uint width, uint height, QAbstractTexture::TextureFormat format) |
| { |
| switch (format) { |
| case QAbstractTexture::RGBA32F: |
| { |
| uchar *srcScanline = (uchar *)srcData + stride * (height - 1); |
| for (uint i = 0; i < height; ++i) { |
| uchar *dstScanline = img.scanLine(i); |
| float *pSrc = (float*)srcScanline; |
| for (uint j = 0; j < width; j++) { |
| *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4*j+2], 1.0f)); |
| *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4*j+1], 1.0f)); |
| *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4*j+0], 1.0f)); |
| *dstScanline++ = (uchar)(255.0f * qBound(0.0f, pSrc[4*j+3], 1.0f)); |
| } |
| srcScanline -= stride; |
| } |
| } break; |
| default: |
| { |
| uchar* srcScanline = (uchar *)srcData + stride * (height - 1); |
| for (uint i = 0; i < height; ++i) { |
| memcpy(img.scanLine(i), srcScanline, stride); |
| srcScanline -= stride; |
| } |
| } break; |
| } |
| } |
| |
| // Render States Helpers |
| template<typename GenericState> |
| void applyStateHelper(const GenericState *state, SubmissionContext *gc) |
| { |
| Q_UNUSED(state); |
| Q_UNUSED(gc); |
| } |
| |
| template<> |
| void applyStateHelper<AlphaFunc>(const AlphaFunc *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->alphaTest(std::get<0>(values), std::get<1>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<BlendEquationArguments>(const BlendEquationArguments *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| // Un-indexed BlendEquationArguments -> Use normal GL1.0 functions |
| if (std::get<5>(values) < 0) { |
| if (std::get<4>(values)) { |
| gc->openGLContext()->functions()->glEnable(GL_BLEND); |
| gc->openGLContext()->functions()->glBlendFuncSeparate(std::get<0>(values), std::get<1>(values), std::get<2>(values), std::get<3>(values)); |
| } else { |
| gc->openGLContext()->functions()->glDisable(GL_BLEND); |
| } |
| } |
| // BlendEquationArguments for a particular Draw Buffer. Different behaviours for |
| // (1) 3.0-3.3: only enablei/disablei supported. |
| // (2) 4.0+: all operations supported. |
| // We just ignore blend func parameter for (1), so no warnings get |
| // printed. |
| else { |
| if (std::get<4>(values)) { |
| gc->enablei(GL_BLEND, std::get<5>(values)); |
| if (gc->supportsDrawBuffersBlend()) { |
| gc->blendFuncSeparatei(std::get<5>(values), std::get<0>(values), std::get<1>(values), std::get<2>(values), std::get<3>(values)); |
| } |
| } else { |
| gc->disablei(GL_BLEND, std::get<5>(values)); |
| } |
| } |
| } |
| |
| template<> |
| void applyStateHelper<BlendEquation>(const BlendEquation *state, SubmissionContext *gc) |
| { |
| gc->blendEquation(std::get<0>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<MSAAEnabled>(const MSAAEnabled *state, SubmissionContext *gc) |
| { |
| gc->setMSAAEnabled(std::get<0>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<DepthRange>(const DepthRange *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->depthRange(std::get<0>(values), std::get<1>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<DepthTest>(const DepthTest *state, SubmissionContext *gc) |
| { |
| gc->depthTest(std::get<0>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<RasterMode>(const RasterMode *state, SubmissionContext *gc) |
| { |
| gc->rasterMode(std::get<0>(state->values()), std::get<1>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<NoDepthMask>(const NoDepthMask *state, SubmissionContext *gc) |
| { |
| gc->depthMask(std::get<0>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<CullFace>(const CullFace *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| if (std::get<0>(values) == QCullFace::NoCulling) { |
| gc->openGLContext()->functions()->glDisable(GL_CULL_FACE); |
| } else { |
| gc->openGLContext()->functions()->glEnable(GL_CULL_FACE); |
| gc->openGLContext()->functions()->glCullFace(std::get<0>(values)); |
| } |
| } |
| |
| template<> |
| void applyStateHelper<FrontFace>(const FrontFace *state, SubmissionContext *gc) |
| { |
| gc->frontFace(std::get<0>(state->values())); |
| } |
| |
| template<> |
| void applyStateHelper<ScissorTest>(const ScissorTest *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glEnable(GL_SCISSOR_TEST); |
| gc->openGLContext()->functions()->glScissor(std::get<0>(values), std::get<1>(values), std::get<2>(values), std::get<3>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<StencilTest>(const StencilTest *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glEnable(GL_STENCIL_TEST); |
| gc->openGLContext()->functions()->glStencilFuncSeparate(GL_FRONT, std::get<0>(values), std::get<1>(values), std::get<2>(values)); |
| gc->openGLContext()->functions()->glStencilFuncSeparate(GL_BACK, std::get<3>(values), std::get<4>(values), std::get<5>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<AlphaCoverage>(const AlphaCoverage *, SubmissionContext *gc) |
| { |
| gc->setAlphaCoverageEnabled(true); |
| } |
| |
| template<> |
| void applyStateHelper<PointSize>(const PointSize *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->pointSize(std::get<0>(values), std::get<1>(values)); |
| } |
| |
| |
| template<> |
| void applyStateHelper<PolygonOffset>(const PolygonOffset *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glEnable(GL_POLYGON_OFFSET_FILL); |
| gc->openGLContext()->functions()->glPolygonOffset(std::get<0>(values), std::get<1>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<ColorMask>(const ColorMask *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glColorMask(std::get<0>(values), std::get<1>(values), std::get<2>(values), std::get<3>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<ClipPlane>(const ClipPlane *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->enableClipPlane(std::get<0>(values)); |
| gc->setClipPlane(std::get<0>(values), std::get<1>(values), std::get<2>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<SeamlessCubemap>(const SeamlessCubemap *, SubmissionContext *gc) |
| { |
| gc->setSeamlessCubemap(true); |
| } |
| |
| template<> |
| void applyStateHelper<StencilOp>(const StencilOp *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glStencilOpSeparate(GL_FRONT, std::get<0>(values), std::get<1>(values), std::get<2>(values)); |
| gc->openGLContext()->functions()->glStencilOpSeparate(GL_BACK, std::get<3>(values), std::get<4>(values), std::get<5>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<StencilMask>(const StencilMask *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| gc->openGLContext()->functions()->glStencilMaskSeparate(GL_FRONT, std::get<0>(values)); |
| gc->openGLContext()->functions()->glStencilMaskSeparate(GL_BACK, std::get<1>(values)); |
| } |
| |
| template<> |
| void applyStateHelper<Dithering>(const Dithering *, SubmissionContext *gc) |
| { |
| gc->openGLContext()->functions()->glEnable(GL_DITHER); |
| } |
| |
| #ifndef GL_LINE_SMOOTH |
| #define GL_LINE_SMOOTH 0x0B20 |
| #endif |
| |
| template<> |
| void applyStateHelper<LineWidth>(const LineWidth *state, SubmissionContext *gc) |
| { |
| const auto values = state->values(); |
| if (std::get<1>(values)) |
| gc->openGLContext()->functions()->glEnable(GL_LINE_SMOOTH); |
| else |
| gc->openGLContext()->functions()->glDisable(GL_LINE_SMOOTH); |
| |
| gc->openGLContext()->functions()->glLineWidth(std::get<0>(values)); |
| } |
| |
| GLint glAttachmentPoint(const QRenderTargetOutput::AttachmentPoint &attachmentPoint) |
| { |
| if (attachmentPoint <= QRenderTargetOutput::Color15) |
| return GL_COLOR_ATTACHMENT0 + attachmentPoint; |
| |
| switch (attachmentPoint) { |
| case QRenderTargetOutput::Depth: |
| case QRenderTargetOutput::DepthStencil: |
| return GL_DEPTH_ATTACHMENT; |
| case QRenderTargetOutput::Stencil: |
| return GL_STENCIL_ATTACHMENT; |
| default: |
| Q_UNREACHABLE(); |
| return GL_NONE; |
| } |
| } |
| |
| } // anonymous |
| |
| |
| SubmissionContext::SubmissionContext() |
| : GraphicsContext() |
| , m_ownCurrent(true) |
| , m_id(nextFreeContextId()) |
| , m_surface(nullptr) |
| , m_activeShader(nullptr) |
| , m_renderTargetFormat(QAbstractTexture::NoFormat) |
| , m_currClearStencilValue(0) |
| , m_currClearDepthValue(1.f) |
| , m_currClearColorValue(0,0,0,0) |
| , m_material(nullptr) |
| , m_activeFBO(0) |
| , m_boundArrayBuffer(nullptr) |
| , m_stateSet(nullptr) |
| , m_renderer(nullptr) |
| , m_uboTempArray(QByteArray(1024, 0)) |
| { |
| static_contexts[m_id] = this; |
| } |
| |
| SubmissionContext::~SubmissionContext() |
| { |
| releaseOpenGL(); |
| |
| Q_ASSERT(static_contexts[m_id] == this); |
| static_contexts.remove(m_id); |
| } |
| |
| void SubmissionContext::initialize() |
| { |
| GraphicsContext::initialize(); |
| m_textureContext.initialize(this); |
| m_imageContext.initialize(this); |
| } |
| |
| void SubmissionContext::resolveRenderTargetFormat() |
| { |
| 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); |
| Q_ASSERT(m_gl); |
| |
| 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<QWindow *>(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_OPENGL_DEBUG) |
| GLint err = m_gl->functions()->glGetError(); |
| if (err != 0) { |
| qCWarning(Backend) << Q_FUNC_INFO << "glGetError:" << err; |
| } |
| #endif |
| |
| if (!isInitialized()) |
| initialize(); |
| initializeHelpers(m_surface); |
| |
| // need to reset these values every frame, may get overwritten elsewhere |
| m_gl->functions()->glClearColor(m_currClearColorValue.redF(), m_currClearColorValue.greenF(), m_currClearColorValue.blueF(), m_currClearColorValue.alphaF()); |
| m_gl->functions()->glClearDepthf(m_currClearDepthValue); |
| m_gl->functions()->glClearStencil(m_currClearStencilValue); |
| |
| if (m_activeShader) { |
| m_activeShader = nullptr; |
| } |
| |
| m_boundArrayBuffer = nullptr; |
| return true; |
| } |
| |
| void SubmissionContext::endDrawing(bool swapBuffers) |
| { |
| if (swapBuffers) |
| m_gl->swapBuffers(m_surface); |
| if (m_ownCurrent) |
| m_gl->doneCurrent(); |
| m_textureContext.endDrawing(); |
| m_imageContext.endDrawing(); |
| } |
| |
| void SubmissionContext::activateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, GLuint defaultFboId) |
| { |
| 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 never |
| // register it |
| } else { |
| fboId = createRenderTarget(renderTargetNodeId, attachments); |
| } |
| } else { |
| fboId = updateRenderTarget(renderTargetNodeId, attachments, true); |
| } |
| } |
| m_activeFBO = fboId; |
| m_activeFBONodeId = renderTargetNodeId; |
| m_glHelper->bindFrameBufferObject(m_activeFBO, GraphicsHelperInterface::FBODraw); |
| // Set active drawBuffers |
| activateDrawBuffers(attachments); |
| } |
| |
| void SubmissionContext::releaseRenderTarget(const Qt3DCore::QNodeId id) |
| { |
| if (m_renderTargets.contains(id)) { |
| const RenderTargetInfo targetInfo = m_renderTargets.take(id); |
| const GLuint fboId = targetInfo.fboId; |
| m_glHelper->releaseFrameBufferObject(fboId); |
| } |
| } |
| |
| GLuint SubmissionContext::createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments) |
| { |
| const GLuint fboId = m_glHelper->createFrameBufferObject(); |
| if (fboId) { |
| // The FBO is created and its attachments are set once |
| // Bind FBO |
| m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); |
| // Insert FBO into hash |
| const RenderTargetInfo info = bindFrameBufferAttachmentHelper(fboId, attachments); |
| m_renderTargets.insert(renderTargetNodeId, info); |
| } else { |
| qCritical("Failed to create FBO"); |
| } |
| return fboId; |
| } |
| |
| GLuint SubmissionContext::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, bool isActiveRenderTarget) |
| { |
| const RenderTargetInfo fboInfo = m_renderTargets.value(renderTargetNodeId); |
| const GLuint fboId =fboInfo.fboId; |
| |
| // We need to check if one of the attachnent have changed QTBUG-64757 |
| bool needsRebuild = attachments != fboInfo.attachments; |
| |
| // Even if the attachment packs are the same, one of the inner texture might have |
| // been resized or recreated, we need to check for that |
| if (!needsRebuild) { |
| // render target exists, has attachment been resized? |
| GLTextureManager *glTextureManager = m_renderer->glResourceManagers()->glTextureManager(); |
| const QSize s = fboInfo.size; |
| |
| const auto attachments_ = attachments.attachments(); |
| for (const Attachment &attachment : attachments_) { |
| const bool textureWasUpdated = m_updateTextureIds.contains(attachment.m_textureUuid); |
| GLTexture *rTex = glTextureManager->lookupResource(attachment.m_textureUuid); |
| if (rTex) { |
| const bool sizeHasChanged = rTex->size() != s; |
| needsRebuild |= sizeHasChanged; |
| if (isActiveRenderTarget && attachment.m_point == QRenderTargetOutput::Color0) |
| m_renderTargetFormat = rTex->properties().format; |
| } |
| needsRebuild |= textureWasUpdated; |
| } |
| } |
| |
| if (needsRebuild) { |
| m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); |
| const RenderTargetInfo updatedInfo = bindFrameBufferAttachmentHelper(fboId, attachments); |
| // Update our stored Render Target Info |
| m_renderTargets.insert(renderTargetNodeId, updatedInfo); |
| } |
| |
| return fboId; |
| } |
| |
| void SubmissionContext::releaseRenderTargets() |
| { |
| const auto keys = m_renderTargets.keys(); |
| for (Qt3DCore::QNodeId renderTargetId : keys) |
| releaseRenderTarget(renderTargetId); |
| } |
| |
| QSize SubmissionContext::renderTargetSize(const QSize &surfaceSize) const |
| { |
| QSize renderTargetSize; |
| if (m_activeFBO != m_defaultFBO) { |
| // For external FBOs we may not have a m_renderTargets entry. |
| if (m_renderTargets.contains(m_activeFBONodeId)) { |
| renderTargetSize = m_renderTargets[m_activeFBONodeId].size; |
| } 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 = surfaceSize; |
| if (m_surface->surfaceClass() == QSurface::Window) { |
| const float dpr = static_cast<QWindow *>(m_surface)->devicePixelRatio(); |
| renderTargetSize *= dpr; |
| } |
| } |
| return renderTargetSize; |
| } |
| |
| QImage SubmissionContext::readFramebuffer(const QRect &rect) |
| { |
| QImage img; |
| const unsigned int area = rect.width() * rect.height(); |
| unsigned int bytes; |
| GLenum format, type; |
| QImage::Format imageFormat; |
| uint stride; |
| |
| /* format value should match GL internalFormat */ |
| GLenum internalFormat = m_renderTargetFormat; |
| |
| switch (m_renderTargetFormat) { |
| case QAbstractTexture::RGBAFormat: |
| case QAbstractTexture::RGBA8_SNorm: |
| case QAbstractTexture::RGBA8_UNorm: |
| case QAbstractTexture::RGBA8U: |
| case QAbstractTexture::SRGB8_Alpha8: |
| #ifdef QT_OPENGL_ES_2 |
| format = GL_RGBA; |
| imageFormat = QImage::Format_RGBA8888_Premultiplied; |
| #else |
| format = GL_BGRA; |
| imageFormat = QImage::Format_ARGB32_Premultiplied; |
| internalFormat = GL_RGBA8; |
| #endif |
| type = GL_UNSIGNED_BYTE; |
| bytes = area * 4; |
| stride = rect.width() * 4; |
| break; |
| case QAbstractTexture::SRGB8: |
| case QAbstractTexture::RGBFormat: |
| case QAbstractTexture::RGB8U: |
| case QAbstractTexture::RGB8_UNorm: |
| #ifdef QT_OPENGL_ES_2 |
| format = GL_RGBA; |
| imageFormat = QImage::Format_RGBX8888; |
| #else |
| format = GL_BGRA; |
| imageFormat = QImage::Format_RGB32; |
| internalFormat = GL_RGB8; |
| #endif |
| type = GL_UNSIGNED_BYTE; |
| bytes = area * 4; |
| stride = rect.width() * 4; |
| break; |
| #ifndef QT_OPENGL_ES_2 |
| case QAbstractTexture::RG11B10F: |
| bytes = area * 4; |
| format = GL_RGB; |
| type = GL_UNSIGNED_INT_10F_11F_11F_REV; |
| imageFormat = QImage::Format_RGB30; |
| stride = rect.width() * 4; |
| break; |
| case QAbstractTexture::RGB10A2: |
| bytes = area * 4; |
| format = GL_RGBA; |
| type = GL_UNSIGNED_INT_2_10_10_10_REV; |
| imageFormat = QImage::Format_A2BGR30_Premultiplied; |
| stride = rect.width() * 4; |
| break; |
| case QAbstractTexture::R5G6B5: |
| bytes = area * 2; |
| format = GL_RGB; |
| type = GL_UNSIGNED_SHORT; |
| internalFormat = GL_UNSIGNED_SHORT_5_6_5_REV; |
| imageFormat = QImage::Format_RGB16; |
| stride = rect.width() * 2; |
| break; |
| case QAbstractTexture::RGBA16F: |
| case QAbstractTexture::RGBA16U: |
| case QAbstractTexture::RGBA32F: |
| case QAbstractTexture::RGBA32U: |
| bytes = area * 16; |
| format = GL_RGBA; |
| type = GL_FLOAT; |
| imageFormat = QImage::Format_ARGB32_Premultiplied; |
| stride = rect.width() * 16; |
| break; |
| #endif |
| default: |
| auto warning = qWarning(); |
| warning << "Unable to convert"; |
| QtDebugUtils::formatQEnum(warning, m_renderTargetFormat); |
| warning << "render target texture format to QImage."; |
| return img; |
| } |
| |
| GLint samples = 0; |
| m_gl->functions()->glGetIntegerv(GL_SAMPLES, &samples); |
| if (samples > 0 && !m_glHelper->supportsFeature(GraphicsHelperInterface::BlitFramebuffer)) { |
| qCWarning(Backend) << Q_FUNC_INFO << "Unable to capture multisampled framebuffer; " |
| "Required feature BlitFramebuffer is missing."; |
| return img; |
| } |
| |
| img = QImage(rect.width(), rect.height(), imageFormat); |
| |
| QScopedArrayPointer<uchar> data(new uchar [bytes]); |
| |
| if (samples > 0) { |
| // resolve multisample-framebuffer to renderbuffer and read pixels from it |
| GLuint fbo, rb; |
| QOpenGLFunctions *gl = m_gl->functions(); |
| gl->glGenFramebuffers(1, &fbo); |
| gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); |
| gl->glGenRenderbuffers(1, &rb); |
| gl->glBindRenderbuffer(GL_RENDERBUFFER, rb); |
| gl->glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, rect.width(), rect.height()); |
| gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb); |
| |
| const GLenum status = gl->glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); |
| if (status != GL_FRAMEBUFFER_COMPLETE) { |
| gl->glDeleteRenderbuffers(1, &rb); |
| gl->glDeleteFramebuffers(1, &fbo); |
| qCWarning(Backend) << Q_FUNC_INFO << "Copy-framebuffer not complete: " << status; |
| return img; |
| } |
| |
| m_glHelper->blitFramebuffer(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height(), |
| 0, 0, rect.width(), rect.height(), |
| GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); |
| gl->glReadPixels(0,0,rect.width(), rect.height(), format, type, data.data()); |
| |
| copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(), m_renderTargetFormat); |
| |
| gl->glBindRenderbuffer(GL_RENDERBUFFER, rb); |
| gl->glDeleteRenderbuffers(1, &rb); |
| gl->glBindFramebuffer(GL_FRAMEBUFFER, m_activeFBO); |
| gl->glDeleteFramebuffers(1, &fbo); |
| } else { |
| // read pixels directly from framebuffer |
| m_gl->functions()->glReadPixels(rect.x(), rect.y(), rect.width(), rect.height(), format, type, data.data()); |
| copyGLFramebufferDataToImage(img, data.data(), stride, rect.width(), rect.height(), m_renderTargetFormat); |
| } |
| |
| return img; |
| } |
| |
| void SubmissionContext::setViewport(const QRectF &viewport, const QSize &surfaceSize) |
| { |
| // // save for later use; this has nothing to do with the viewport but it is |
| // // here that we get to know the surfaceSize from the RenderView. |
| m_surfaceSize = surfaceSize; |
| |
| m_viewport = viewport; |
| QSize size = renderTargetSize(surfaceSize); |
| |
| // Check that the returned size is before calling glViewport |
| if (size.isEmpty()) |
| return; |
| |
| // Qt3D 0------------------> 1 OpenGL 1^ |
| // | | |
| // | | |
| // | | |
| // V | |
| // 1 0---------------------> 1 |
| // The Viewport is defined between 0 and 1 which allows us to automatically |
| // scale to the size of the provided window surface |
| m_gl->functions()->glViewport(m_viewport.x() * size.width(), |
| (1.0 - m_viewport.y() - m_viewport.height()) * size.height(), |
| m_viewport.width() * size.width(), |
| m_viewport.height() * size.height()); |
| } |
| |
| void SubmissionContext::releaseOpenGL() |
| { |
| m_renderBufferHash.clear(); |
| |
| // Stop and destroy the OpenGL logger |
| if (m_debugLogger) { |
| m_debugLogger->stopLogging(); |
| m_debugLogger.reset(nullptr); |
| } |
| } |
| |
| // The OpenGLContext is not current on any surface at this point |
| void SubmissionContext::setOpenGLContext(QOpenGLContext* ctx) |
| { |
| Q_ASSERT(ctx); |
| |
| releaseOpenGL(); |
| m_gl = ctx; |
| } |
| |
| // Called only from RenderThread |
| bool SubmissionContext::activateShader(GLShader *shader) |
| { |
| 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; |
| } |
| |
| SubmissionContext::RenderTargetInfo SubmissionContext::bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments) |
| { |
| // 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; |
| GLTextureManager *glTextureManager = m_renderer->glResourceManagers()->glTextureManager(); |
| const auto attachments_ = attachments.attachments(); |
| for (const Attachment &attachment : attachments_) { |
| GLTexture *rTex = glTextureManager->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); |
| } |
| } |
| } |
| return {fboId, fboSize, attachments}; |
| } |
| |
| void SubmissionContext::activateDrawBuffers(const AttachmentPack &attachments) |
| { |
| const QVector<int> activeDrawBuffers = attachments.getGlDrawBuffers(); |
| |
| if (m_glHelper->checkFrameBufferComplete()) { |
| if (activeDrawBuffers.size() > 1) {// We need MRT |
| if (m_glHelper->supportsFeature(GraphicsHelperInterface::MRT)) { |
| // Set up MRT, glDrawBuffers... |
| m_glHelper->drawBuffers(activeDrawBuffers.size(), activeDrawBuffers.data()); |
| } |
| } |
| } else { |
| qCWarning(Backend) << "FBO incomplete"; |
| } |
| } |
| |
| |
| void SubmissionContext::setActiveMaterial(Material *rmat) |
| { |
| if (m_material == rmat) |
| return; |
| |
| m_textureContext.deactivateTexturesWithScope(TextureSubmissionContext::TextureScopeMaterial); |
| m_imageContext.deactivateImages(); |
| m_material = rmat; |
| } |
| |
| void SubmissionContext::setCurrentStateSet(RenderStateSet *ss) |
| { |
| if (ss == m_stateSet) |
| return; |
| if (ss) |
| applyStateSet(ss); |
| m_stateSet = ss; |
| } |
| |
| RenderStateSet *SubmissionContext::currentStateSet() const |
| { |
| return m_stateSet; |
| } |
| |
| void SubmissionContext::applyState(const StateVariant &stateVariant) |
| { |
| switch (stateVariant.type) { |
| |
| case AlphaCoverageStateMask: { |
| applyStateHelper<AlphaCoverage>(static_cast<const AlphaCoverage *>(stateVariant.constState()), this); |
| break; |
| } |
| case AlphaTestMask: { |
| applyStateHelper<AlphaFunc>(static_cast<const AlphaFunc *>(stateVariant.constState()), this); |
| break; |
| } |
| case BlendStateMask: { |
| applyStateHelper<BlendEquation>(static_cast<const BlendEquation *>(stateVariant.constState()), this); |
| break; |
| } |
| case BlendEquationArgumentsMask: { |
| applyStateHelper<BlendEquationArguments>(static_cast<const BlendEquationArguments *>(stateVariant.constState()), this); |
| break; |
| } |
| case MSAAEnabledStateMask: { |
| applyStateHelper<MSAAEnabled>(static_cast<const MSAAEnabled *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case CullFaceStateMask: { |
| applyStateHelper<CullFace>(static_cast<const CullFace *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case DepthWriteStateMask: { |
| applyStateHelper<NoDepthMask>(static_cast<const NoDepthMask *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case DepthTestStateMask: { |
| applyStateHelper<DepthTest>(static_cast<const DepthTest *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case DepthRangeMask: { |
| applyStateHelper<DepthRange>(static_cast<const DepthRange *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case RasterModeMask: { |
| applyStateHelper<RasterMode>(static_cast<const RasterMode *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case FrontFaceStateMask: { |
| applyStateHelper<FrontFace>(static_cast<const FrontFace *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case ScissorStateMask: { |
| applyStateHelper<ScissorTest>(static_cast<const ScissorTest *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case StencilTestStateMask: { |
| applyStateHelper<StencilTest>(static_cast<const StencilTest *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case PointSizeMask: { |
| applyStateHelper<PointSize>(static_cast<const PointSize *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case PolygonOffsetStateMask: { |
| applyStateHelper<PolygonOffset>(static_cast<const PolygonOffset *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case ColorStateMask: { |
| applyStateHelper<ColorMask>(static_cast<const ColorMask *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case ClipPlaneMask: { |
| applyStateHelper<ClipPlane>(static_cast<const ClipPlane *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case SeamlessCubemapMask: { |
| applyStateHelper<SeamlessCubemap>(static_cast<const SeamlessCubemap *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case StencilOpMask: { |
| applyStateHelper<StencilOp>(static_cast<const StencilOp *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case StencilWriteStateMask: { |
| applyStateHelper<StencilMask>(static_cast<const StencilMask *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case DitheringStateMask: { |
| applyStateHelper<Dithering>(static_cast<const Dithering *>(stateVariant.constState()), this); |
| break; |
| } |
| |
| case LineWidthMask: { |
| applyStateHelper<LineWidth>(static_cast<const LineWidth *>(stateVariant.constState()), this); |
| break; |
| } |
| default: |
| Q_UNREACHABLE(); |
| } |
| } |
| |
| void SubmissionContext::resetMasked(qint64 maskOfStatesToReset) |
| { |
| // TO DO -> Call gcHelper methods instead of raw GL |
| // QOpenGLFunctions shouldn't be used here directly |
| QOpenGLFunctions *funcs = m_gl->functions(); |
| |
| if (maskOfStatesToReset & ScissorStateMask) |
| funcs->glDisable(GL_SCISSOR_TEST); |
| |
| if (maskOfStatesToReset & BlendStateMask) |
| funcs->glDisable(GL_BLEND); |
| |
| if (maskOfStatesToReset & StencilWriteStateMask) |
| funcs->glStencilMask(0); |
| |
| if (maskOfStatesToReset & StencilTestStateMask) |
| funcs->glDisable(GL_STENCIL_TEST); |
| |
| if (maskOfStatesToReset & DepthRangeMask) |
| depthRange(0.0f, 1.0f); |
| |
| if (maskOfStatesToReset & DepthTestStateMask) |
| funcs->glDisable(GL_DEPTH_TEST); |
| |
| if (maskOfStatesToReset & DepthWriteStateMask) |
| funcs->glDepthMask(GL_TRUE); // reset to default |
| |
| if (maskOfStatesToReset & FrontFaceStateMask) |
| funcs->glFrontFace(GL_CCW); // reset to default |
| |
| if (maskOfStatesToReset & CullFaceStateMask) |
| funcs->glDisable(GL_CULL_FACE); |
| |
| if (maskOfStatesToReset & DitheringStateMask) |
| funcs->glDisable(GL_DITHER); |
| |
| if (maskOfStatesToReset & AlphaCoverageStateMask) |
| setAlphaCoverageEnabled(false); |
| |
| if (maskOfStatesToReset & PointSizeMask) |
| pointSize(false, 1.0f); // reset to default |
| |
| if (maskOfStatesToReset & PolygonOffsetStateMask) |
| funcs->glDisable(GL_POLYGON_OFFSET_FILL); |
| |
| if (maskOfStatesToReset & ColorStateMask) |
| funcs->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| |
| if (maskOfStatesToReset & ClipPlaneMask) { |
| GLint max = maxClipPlaneCount(); |
| for (GLint i = 0; i < max; ++i) |
| disableClipPlane(i); |
| } |
| |
| if (maskOfStatesToReset & SeamlessCubemapMask) |
| setSeamlessCubemap(false); |
| |
| if (maskOfStatesToReset & StencilOpMask) |
| funcs->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
| |
| if (maskOfStatesToReset & LineWidthMask) |
| funcs->glLineWidth(1.0f); |
| |
| #ifndef QT_OPENGL_ES_2 |
| if (maskOfStatesToReset & RasterModeMask) |
| m_glHelper->rasterMode(GL_FRONT_AND_BACK, GL_FILL); |
| #endif |
| } |
| |
| void SubmissionContext::applyStateSet(RenderStateSet *ss) |
| { |
| RenderStateSet* previousStates = currentStateSet(); |
| |
| const StateMaskSet invOurState = ~ss->stateMask(); |
| // generate a mask for each set bit in previous, where we do not have |
| // the corresponding bit set. |
| |
| StateMaskSet stateToReset = 0; |
| if (previousStates) { |
| stateToReset = previousStates->stateMask() & invOurState; |
| qCDebug(RenderStates) << "previous states " << QString::number(previousStates->stateMask(), 2); |
| } |
| qCDebug(RenderStates) << " current states " << QString::number(ss->stateMask(), 2) << "inverse " << QString::number(invOurState, 2) << " -> states to change: " << QString::number(stateToReset, 2); |
| |
| // Reset states that aren't active in the current state set |
| resetMasked(stateToReset); |
| |
| // Apply states that weren't in the previous state or that have |
| // different values |
| const QVector<StateVariant> statesToSet = ss->states(); |
| for (const StateVariant &ds : statesToSet) { |
| if (previousStates && previousStates->contains(ds)) |
| continue; |
| applyState(ds); |
| } |
| } |
| |
| void SubmissionContext::clearColor(const QColor &color) |
| { |
| if (m_currClearColorValue != color) { |
| m_currClearColorValue = color; |
| m_gl->functions()->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); |
| } |
| } |
| |
| void SubmissionContext::clearDepthValue(float depth) |
| { |
| if (m_currClearDepthValue != depth) { |
| m_currClearDepthValue = depth; |
| m_gl->functions()->glClearDepthf(depth); |
| } |
| } |
| |
| void SubmissionContext::clearStencilValue(int stencil) |
| { |
| if (m_currClearStencilValue != stencil) { |
| m_currClearStencilValue = stencil; |
| m_gl->functions()->glClearStencil(stencil); |
| } |
| } |
| |
| GLFence SubmissionContext::fenceSync() |
| { |
| return m_glHelper->fenceSync(); |
| } |
| |
| void SubmissionContext::clientWaitSync(GLFence sync, GLuint64 nanoSecTimeout) |
| { |
| qDebug() << Q_FUNC_INFO << sync; |
| m_glHelper->clientWaitSync(sync, nanoSecTimeout); |
| } |
| |
| void SubmissionContext::waitSync(GLFence sync) |
| { |
| qDebug() << Q_FUNC_INFO << sync; |
| m_glHelper->waitSync(sync); |
| } |
| |
| bool SubmissionContext::wasSyncSignaled(GLFence sync) |
| { |
| return m_glHelper->wasSyncSignaled(sync); |
| } |
| |
| void SubmissionContext::deleteSync(GLFence sync) |
| { |
| m_glHelper->deleteSync(sync); |
| } |
| |
| void SubmissionContext::setUpdatedTexture(const Qt3DCore::QNodeIdVector &updatedTextureIds) |
| { |
| m_updateTextureIds = updatedTextureIds; |
| } |
| |
| // It will be easier if the QGraphicContext applies the QUniformPack |
| // than the other way around |
| bool SubmissionContext::setParameters(ShaderParameterPack ¶meterPack, GLShader *shader) |
| { |
| 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) { |
| const ShaderParameterPack::NamedResource &namedTex = parameterPack.textures().at(i); |
| // Given a Texture QNodeId, we retrieve the associated shared GLTexture |
| if (uniformValues.contains(namedTex.glslNameId)) { |
| GLTexture *t = m_renderer->glResourceManagers()->glTextureManager()->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<int>()[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; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Fill Image Uniform Value with proper image units |
| // so that they can be applied as regular uniforms in a second step |
| for (int i = 0; i < parameterPack.images().size(); ++i) { |
| const ShaderParameterPack::NamedResource &namedTex = parameterPack.images().at(i); |
| // Given a Texture QNodeId, we retrieve the associated shared GLTexture |
| if (uniformValues.contains(namedTex.glslNameId)) { |
| ShaderImage *img = m_renderer->nodeManagers()->shaderImageManager()->lookupResource(namedTex.nodeId); |
| if (img != nullptr) { |
| GLTexture *t = m_renderer->glResourceManagers()->glTextureManager()->lookupResource(img->textureId()); |
| if (t == nullptr) { |
| qCWarning(Backend) << "Shader Image referencing invalid texture"; |
| continue; |
| } else { |
| UniformValue &imgUniform = uniformValues.value(namedTex.glslNameId); |
| if (imgUniform.valueType() == UniformValue::ShaderImageValue) { |
| const int imgUnit = m_imageContext.activateImage(img, t); |
| imgUniform.data<int>()[namedTex.uniformArrayIndex] = imgUnit; |
| if (imgUnit == -1) { |
| qCWarning(Backend) << "Unable to bind Image to Texture"; |
| return false; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| QOpenGLShaderProgram *glShader = 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 std::vector<BlockToSSBO> &blockToSSBOs = parameterPack.shaderStorageBuffers(); |
| for (const BlockToSSBO b : blockToSSBOs) { |
| Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); |
| GLBuffer *ssbo = glBufferForRenderBuffer(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(glShader->programId(), b.m_blockIndex, b.m_bindingIndex); |
| // Needed to avoid conflict where the buffer would already |
| // be bound as a VertexArray |
| bindGLBuffer(ssbo, GLBuffer::ShaderStorageBuffer); |
| ssbo->bindBufferBase(this, b.m_bindingIndex, GLBuffer::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 std::vector<BlockToUBO> &blockToUBOs = parameterPack.uniformBuffers(); |
| int uboIndex = 0; |
| for (const BlockToUBO &b : blockToUBOs) { |
| Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); |
| GLBuffer *ubo = glBufferForRenderBuffer(cpuBuffer); |
| bindUniformBlock(glShader->programId(), b.m_blockIndex, uboIndex); |
| // Needed to avoid conflict where the buffer would already |
| // be bound as a VertexArray |
| bindGLBuffer(ubo, GLBuffer::UniformBuffer); |
| ubo->bindBufferBase(this, uboIndex++, GLBuffer::UniformBuffer); |
| // TO DO: Make sure that there's enough binding points |
| } |
| |
| // Update uniforms in the Default Uniform Block |
| const PackUniformHash& values = parameterPack.uniforms(); |
| const auto &activeUniformsIndices = parameterPack.submissionUniformIndices(); |
| const QVector<ShaderUniform> &shaderUniforms = shader->uniforms(); |
| |
| for (const int shaderUniformIndex : activeUniformsIndices) { |
| const ShaderUniform &uniform = shaderUniforms[shaderUniformIndex]; |
| values.apply(uniform.m_nameId, [&] (const UniformValue& v) { |
| // skip invalid textures/images |
| if (!((v.valueType() == UniformValue::TextureValue || |
| v.valueType() == UniformValue::ShaderImageValue) && |
| *v.constData<int>() == -1)) |
| applyUniform(uniform, v); |
| }); |
| |
| } |
| // if not all data is valid, the next frame will be rendered immediately |
| return true; |
| } |
| |
| void SubmissionContext::enableAttribute(const VAOVertexAttribute &attr) |
| { |
| // Bind buffer within the current VAO |
| GLBuffer *buf = m_renderer->glResourceManagers()->glBufferManager()->data(attr.bufferHandle); |
| Q_ASSERT(buf); |
| bindGLBuffer(buf, attr.attributeType); |
| |
| // Don't use QOpenGLShaderProgram::setAttributeBuffer() because of QTBUG-43199. |
| // Use the introspection data and set the attribute explicitly |
| m_glHelper->enableVertexAttributeArray(attr.location); |
| m_glHelper->vertexAttributePointer(attr.shaderDataType, |
| attr.location, |
| attr.vertexSize, |
| attr.dataType, |
| GL_TRUE, // TODO: Support normalization property on QAttribute |
| attr.byteStride, |
| reinterpret_cast<const void *>(qintptr(attr.byteOffset))); |
| |
| |
| // Done by the helper if it supports it |
| if (attr.divisor != 0) |
| m_glHelper->vertexAttribDivisor(attr.location, attr.divisor); |
| } |
| |
| void SubmissionContext::disableAttribute(const SubmissionContext::VAOVertexAttribute &attr) |
| { |
| QOpenGLShaderProgram *prog = activeShader(); |
| prog->disableAttributeArray(attr.location); |
| } |
| |
| // Note: needs to be called while VAO is bound |
| void SubmissionContext::specifyAttribute(const Attribute *attribute, |
| Buffer *buffer, |
| const ShaderAttribute *attributeDescription) |
| { |
| const int location = attributeDescription->m_location; |
| if (location < 0) { |
| qCWarning(Backend) << "failed to resolve location for attribute:" << attribute->name(); |
| return; |
| } |
| |
| const GLint attributeDataType = glDataTypeFromAttributeDataType(attribute->vertexBaseType()); |
| const HGLBuffer glBufferHandle = m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(buffer->peerId()); |
| Q_ASSERT(!glBufferHandle.isNull()); |
| const GLBuffer::Type attributeType = attributeTypeToGLBufferType(attribute->attributeType()); |
| |
| int typeSize = 0; |
| int attrCount = 0; |
| |
| if (attribute->vertexSize() >= 1 && attribute->vertexSize() <= 4) { |
| attrCount = 1; |
| } else if (attribute->vertexSize() == 9) { |
| typeSize = byteSizeFromType(attributeDataType); |
| attrCount = 3; |
| } else if (attribute->vertexSize() == 16) { |
| typeSize = byteSizeFromType(attributeDataType); |
| attrCount = 4; |
| } else { |
| Q_UNREACHABLE(); |
| } |
| |
| Q_ASSERT(!glBufferHandle.isNull()); |
| VAOVertexAttribute attr; |
| attr.bufferHandle = glBufferHandle; |
| attr.attributeType = attributeType; |
| attr.dataType = attributeDataType; |
| attr.divisor = attribute->divisor(); |
| attr.vertexSize = attribute->vertexSize() / attrCount; |
| attr.byteStride = (attribute->byteStride() != 0) ? attribute->byteStride() : (attrCount * attrCount * typeSize); |
| attr.shaderDataType = attributeDescription->m_type; |
| |
| for (int i = 0; i < attrCount; i++) { |
| attr.location = location + i; |
| attr.byteOffset = attribute->byteOffset() + (i * attrCount * typeSize); |
| |
| enableAttribute(attr); |
| |
| // Save this in the current emulated VAO |
| if (m_currentVAO) |
| m_currentVAO->saveVertexAttribute(attr); |
| } |
| } |
| |
| void SubmissionContext::specifyIndices(Buffer *buffer) |
| { |
| GLBuffer *buf = glBufferForRenderBuffer(buffer); |
| if (!bindGLBuffer(buf, GLBuffer::IndexBuffer)) |
| qCWarning(Backend) << Q_FUNC_INFO << "binding index buffer failed"; |
| |
| // bound within the current VAO |
| // Save this in the current emulated VAO |
| if (m_currentVAO) |
| m_currentVAO->saveIndexAttribute(m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(buffer->peerId())); |
| } |
| |
| void SubmissionContext::updateBuffer(Buffer *buffer) |
| { |
| const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(buffer->peerId()); |
| if (it != m_renderBufferHash.end()) |
| uploadDataToGLBuffer(buffer, m_renderer->glResourceManagers()->glBufferManager()->data(it.value())); |
| } |
| |
| QByteArray SubmissionContext::downloadBufferContent(Buffer *buffer) |
| { |
| const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(buffer->peerId()); |
| if (it != m_renderBufferHash.end()) |
| return downloadDataFromGLBuffer(buffer, m_renderer->glResourceManagers()->glBufferManager()->data(it.value())); |
| return QByteArray(); |
| } |
| |
| void SubmissionContext::releaseBuffer(Qt3DCore::QNodeId bufferId) |
| { |
| auto it = m_renderBufferHash.find(bufferId); |
| if (it != m_renderBufferHash.end()) { |
| HGLBuffer glBuffHandle = it.value(); |
| GLBuffer *glBuff = m_renderer->glResourceManagers()->glBufferManager()->data(glBuffHandle); |
| |
| Q_ASSERT(glBuff); |
| // Destroy the GPU resource |
| glBuff->destroy(this); |
| // Destroy the GLBuffer instance |
| m_renderer->glResourceManagers()->glBufferManager()->releaseResource(bufferId); |
| // Remove Id - HGLBuffer entry |
| m_renderBufferHash.erase(it); |
| } |
| } |
| |
| bool SubmissionContext::hasGLBufferForBuffer(Buffer *buffer) |
| { |
| const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(buffer->peerId()); |
| return (it != m_renderBufferHash.end()); |
| } |
| |
| GLBuffer *SubmissionContext::glBufferForRenderBuffer(Buffer *buf) |
| { |
| if (!m_renderBufferHash.contains(buf->peerId())) |
| m_renderBufferHash.insert(buf->peerId(), createGLBufferFor(buf)); |
| return m_renderer->glResourceManagers()->glBufferManager()->data(m_renderBufferHash.value(buf->peerId())); |
| } |
| |
| HGLBuffer SubmissionContext::createGLBufferFor(Buffer *buffer) |
| { |
| GLBuffer *b = m_renderer->glResourceManagers()->glBufferManager()->getOrCreateResource(buffer->peerId()); |
| // b.setUsagePattern(static_cast<QOpenGLBuffer::UsagePattern>(buffer->usage())); |
| Q_ASSERT(b); |
| if (!b->create(this)) |
| qCWarning(Io) << Q_FUNC_INFO << "buffer creation failed"; |
| |
| return m_renderer->glResourceManagers()->glBufferManager()->lookupHandle(buffer->peerId()); |
| } |
| |
| bool SubmissionContext::bindGLBuffer(GLBuffer *buffer, GLBuffer::Type type) |
| { |
| if (type == GLBuffer::ArrayBuffer && buffer == m_boundArrayBuffer) |
| return true; |
| |
| if (buffer->bind(this, type)) { |
| if (type == GLBuffer::ArrayBuffer) |
| m_boundArrayBuffer = buffer; |
| return true; |
| } |
| return false; |
| } |
| |
| void SubmissionContext::uploadDataToGLBuffer(Buffer *buffer, GLBuffer *b, bool releaseBuffer) |
| { |
| if (!bindGLBuffer(b, GLBuffer::ArrayBuffer)) // We're uploading, the type doesn't matter here |
| qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed"; |
| // 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 |
| |
| // TO DO: Handle usage pattern |
| QVector<Qt3DRender::QBufferUpdate> 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.constData(), update->data.size(), 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 |
| const int bufferSize = buffer->data().size(); |
| b->allocate(this, bufferSize, false); // orphan the buffer |
| b->allocate(this, buffer->data().constData(), bufferSize, false); |
| } |
| } |
| |
| if (releaseBuffer) { |
| b->release(this); |
| m_boundArrayBuffer = nullptr; |
| } |
| qCDebug(Io) << "uploaded buffer size=" << buffer->data().size(); |
| } |
| |
| QByteArray SubmissionContext::downloadDataFromGLBuffer(Buffer *buffer, GLBuffer *b) |
| { |
| if (!bindGLBuffer(b, GLBuffer::ArrayBuffer)) // We're downloading, the type doesn't matter here |
| qCWarning(Io) << Q_FUNC_INFO << "buffer bind failed"; |
| |
| QByteArray data = b->download(this, buffer->data().size()); |
| return data; |
| } |
| |
| void SubmissionContext::blitFramebuffer(Qt3DCore::QNodeId inputRenderTargetId, |
| Qt3DCore::QNodeId outputRenderTargetId, |
| QRect inputRect, QRect outputRect, |
| uint defaultFboId, |
| QRenderTargetOutput::AttachmentPoint inputAttachmentPoint, |
| QRenderTargetOutput::AttachmentPoint outputAttachmentPoint, |
| QBlitFramebuffer::InterpolationMethod interpolationMethod) |
| { |
| 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_renderTargets[inputRenderTargetId].size.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_renderTargets[outputRenderTargetId].size.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(glAttachmentPoint(inputAttachmentPoint)); |
| |
| if (!outputBufferIsDefault) { |
| // Note that we use glDrawBuffers, not glDrawBuffer. The |
| // latter is not available with GLES. |
| const int buf = glAttachmentPoint(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 OpenGL |
| } // namespace Render |
| } // namespace Qt3DRender of namespace |
| |
| QT_END_NAMESPACE |