/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL$ ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) 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.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-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "dragonactivatedsurface_p.h" // Backend utils #include // Backend types #include #include #include #include #include #include #include // GL types #include #include #include #if !defined(QT_OPENGL_ES_2) #include #include #include //#include #include #include #include //#include #endif #include #include #include #include // Qt3DRender::Render #include #include #include #include #ifndef GL_ARRAY_BUFFER # define GL_ARRAY_BUFFER 0x8892 #endif #ifndef GL_UNIFORM_BUFFER # define GL_UNIFORM_BUFFER 0x8A11 #endif #ifndef GL_ELEMENT_ARRAY_BUFFER # define GL_ELEMENT_ARRAY_BUFFER 0x8893 #endif #ifndef GL_SHADER_STORAGE_BUFFER # define GL_SHADER_STORAGE_BUFFER 0x90D2 #endif #ifndef GL_PIXEL_PACK_BUFFER # define GL_PIXEL_PACK_BUFFER 0x88EB #endif #ifndef GL_PIXEL_UNPACK_BUFFER # define GL_PIXEL_UNPACK_BUFFER 0x88EC #endif #ifndef GL_DRAW_INDIRECT_BUFFER # define GL_DRAW_INDIRECT_BUFFER 0x8F3F #endif QT_BEGIN_NAMESPACE using namespace Qt3DCore; namespace Qt3DRender { using GraphicsHelperInterface = Dragon::GraphicsHelperInterface; namespace Dragon { namespace { GLenum glBufferTypes[] = {GL_ARRAY_BUFFER, GL_UNIFORM_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_SHADER_STORAGE_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_DRAW_INDIRECT_BUFFER}; GLint glDataTypeFromAttributeDataType(QAttribute::VertexBaseType dataType) { switch (dataType) { case QAttribute::Byte: return GL_BYTE; case QAttribute::UnsignedByte: return GL_UNSIGNED_BYTE; case QAttribute::Short: return GL_SHORT; case QAttribute::UnsignedShort: return GL_UNSIGNED_SHORT; case QAttribute::Int: return GL_INT; case QAttribute::UnsignedInt: return GL_UNSIGNED_INT; case QAttribute::HalfFloat: #ifdef GL_HALF_FLOAT return GL_HALF_FLOAT; #endif #ifndef QT_OPENGL_ES_2 // Otherwise compile error as Qt defines GL_DOUBLE as GL_FLOAT when using ES2 case QAttribute::Double: return GL_DOUBLE; #endif case QAttribute::Float: break; default: qWarning() << Q_FUNC_INFO << "unsupported dataType:" << dataType; } return GL_FLOAT; } 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(); } } GLuint byteSizeFromType(GLint type) { switch (type) { case GL_FLOAT: return sizeof(float); #ifndef QT_OPENGL_ES_2 // Otherwise compile error as Qt defines GL_DOUBLE as GL_FLOAT when using ES2 case GL_DOUBLE: return sizeof(double); #endif case GL_UNSIGNED_BYTE: return sizeof(unsigned char); case GL_UNSIGNED_INT: return sizeof(GLuint); case GL_FLOAT_VEC2: return sizeof(float) * 2; case GL_FLOAT_VEC3: return sizeof(float) * 3; case GL_FLOAT_VEC4: return sizeof(float) * 4; #ifdef GL_DOUBLE_VEC3 // Required to compile on pre GL 4.1 systems case GL_DOUBLE_VEC2: return sizeof(double) * 2; case GL_DOUBLE_VEC3: return sizeof(double) * 3; case GL_DOUBLE_VEC4: return sizeof(double) * 4; #endif default: qWarning() << Q_FUNC_INFO << "unsupported:" << QString::number(type, 16); } return 0; } } // namespace ActivatedSurface::ActivatedSurface(QSurface *surface, QOpenGLContext *glContext, Render::SurfaceLocker *lock) : m_surface(surface) , m_glContext(glContext) { Q_UNUSED(lock) Q_ASSERT(lock->isSurfaceValid()); m_valid = m_glContext->makeCurrent(surface); if (!m_valid) return; if (m_glContext->format().majorVersion() >= 3) { m_supportsVAO = true; } else { QSet extensions = m_glContext->extensions(); m_supportsVAO = extensions.contains(QByteArrayLiteral("GL_OES_vertex_array_object")) || extensions.contains(QByteArrayLiteral("GL_ARB_vertex_array_object")) || extensions.contains(QByteArrayLiteral("GL_APPLE_vertex_array_object")); } m_defaultFBO = m_glContext->defaultFramebufferObject(); //qCDebug(Render::Backend) << "VAO support = " << m_supportsVAO; // TODO we might not want to do this repeatedly, put here now because we need them in multiple places m_glHelper = resolveHighestOpenGLFunctions(); } ActivatedSurface::~ActivatedSurface() { if (!m_valid) return; // TODO should be cached between frames instead of released here... for (GLuint frameBufferId : m_renderTargets.values()) { m_glHelper->releaseFrameBufferObject(frameBufferId); } m_glContext->doneCurrent(); } bool ActivatedSurface::isValid() const { return m_valid; } void ActivatedSurface::clearBackBuffer(const ClearBackBufferInfo &info) { auto clearBufferTypes = info.buffers; if (clearBufferTypes & QClearBuffers::ColorBuffer) { const QVector4D vCol = info.color; const QColor color = QColor::fromRgbF(vCol.x(), vCol.y(), vCol.z(), vCol.w()); m_glContext->functions()->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } if (clearBufferTypes & QClearBuffers::DepthBuffer) { const float depth = info.depth; m_glContext->functions()->glClearDepthf(depth); } if (clearBufferTypes & QClearBuffers::StencilBuffer) { const int stencil = info.stencil; m_glContext->functions()->glClearStencil(stencil); } if (clearBufferTypes != QClearBuffers::None) { GLbitfield mask = 0; if (clearBufferTypes & QClearBuffers::ColorBuffer) mask |= GL_COLOR_BUFFER_BIT; if (clearBufferTypes & QClearBuffers::DepthBuffer) mask |= GL_DEPTH_BUFFER_BIT; if (clearBufferTypes & QClearBuffers::StencilBuffer) mask |= GL_STENCIL_BUFFER_BIT; m_glContext->functions()->glClear(mask); } } void ActivatedSurface::applyUniform(const ShaderUniform &description, const UniformValue &v) { const UniformType type = m_glHelper->uniformTypeFromGLType(description.m_type); switch (type) { case UniformType::Float: // See QTBUG-57510 and uniform_p.h if (v.storedType() == UniformType::Int) { float value = float(*v.constData()); UniformValue floatV(value); applyUniformHelper(description, floatV); } else { applyUniformHelper(description, v); } break; case UniformType::Vec2: applyUniformHelper(description, v); break; case UniformType::Vec3: applyUniformHelper(description, v); break; case UniformType::Vec4: applyUniformHelper(description, v); break; case UniformType::Double: applyUniformHelper(description, v); break; case UniformType::DVec2: applyUniformHelper(description, v); break; case UniformType::DVec3: applyUniformHelper(description, v); break; case UniformType::DVec4: applyUniformHelper(description, v); break; case UniformType::Sampler: case UniformType::Int: applyUniformHelper(description, v); break; case UniformType::IVec2: applyUniformHelper(description, v); break; case UniformType::IVec3: applyUniformHelper(description, v); break; case UniformType::IVec4: applyUniformHelper(description, v); break; case UniformType::UInt: applyUniformHelper(description, v); break; case UniformType::UIVec2: applyUniformHelper(description, v); break; case UniformType::UIVec3: applyUniformHelper(description, v); break; case UniformType::UIVec4: applyUniformHelper(description, v); break; case UniformType::Bool: applyUniformHelper(description, v); break; case UniformType::BVec2: applyUniformHelper(description, v); break; case UniformType::BVec3: applyUniformHelper(description, v); break; case UniformType::BVec4: applyUniformHelper(description, v); break; case UniformType::Mat2: applyUniformHelper(description, v); break; case UniformType::Mat3: applyUniformHelper(description, v); break; case UniformType::Mat4: applyUniformHelper(description, v); break; case UniformType::Mat2x3: applyUniformHelper(description, v); break; case UniformType::Mat3x2: applyUniformHelper(description, v); break; case UniformType::Mat2x4: applyUniformHelper(description, v); break; case UniformType::Mat4x2: applyUniformHelper(description, v); break; case UniformType::Mat3x4: applyUniformHelper(description, v); break; case UniformType::Mat4x3: applyUniformHelper(description, v); break; default: break; } } DrawContext ActivatedSurface::beginDrawing(bool autoSwapBuffers) { return DrawContext(m_glContext, m_surface, autoSwapBuffers); } UniformValue standardUniformValue(StandardUniform standardUniformType, const Immutable &renderView, const Immutable &cameraMatrices, const Matrix4x4 &model) { auto getProjectionMatrix = [](const Immutable lens) { return lens->projection(); }; auto resolveViewport = [](const QRectF &fractionalViewport, const QSize &surfaceSize) { return QRectF(fractionalViewport.x() * surfaceSize.width(), (1.0 - fractionalViewport.y() - fractionalViewport.height()) * surfaceSize.height(), fractionalViewport.width() * surfaceSize.width(), fractionalViewport.height() * surfaceSize.height()); }; switch (standardUniformType) { case ModelMatrix: return UniformValue(model); case ViewMatrix: return UniformValue(cameraMatrices->viewMatrix); case ProjectionMatrix: return UniformValue(getProjectionMatrix(renderView->cameraLens)); case ModelViewMatrix: return UniformValue(cameraMatrices->viewMatrix * model); case ViewProjectionMatrix: return UniformValue(getProjectionMatrix(renderView->cameraLens) * cameraMatrices->viewMatrix); case ModelViewProjectionMatrix: return UniformValue(cameraMatrices->viewProjectionMatrix * model); case InverseModelMatrix: return UniformValue(model.inverted()); case InverseViewMatrix: return UniformValue(cameraMatrices->viewMatrix.inverted()); case InverseProjectionMatrix: { return UniformValue(getProjectionMatrix(renderView->cameraLens).inverted()); } case InverseModelViewMatrix: return UniformValue((cameraMatrices->viewMatrix * model).inverted()); case InverseViewProjectionMatrix: { const Matrix4x4 viewProjectionMatrix = getProjectionMatrix(renderView->cameraLens) * cameraMatrices->viewMatrix; return UniformValue(viewProjectionMatrix.inverted()); } case InverseModelViewProjectionMatrix: return UniformValue((cameraMatrices->viewProjectionMatrix * model).inverted()); case ModelNormalMatrix: return UniformValue(convertToQMatrix4x4(model).normalMatrix()); case ModelViewNormalMatrix: return UniformValue(convertToQMatrix4x4(cameraMatrices->viewMatrix * model).normalMatrix()); case ViewportMatrix: { QMatrix4x4 viewportMatrix; // TODO: Implement on Matrix4x4 viewportMatrix.viewport(resolveViewport(renderView->viewport, renderView->surfaceSize)); return UniformValue(Matrix4x4(viewportMatrix)); } case InverseViewportMatrix: { QMatrix4x4 viewportMatrix; // TODO: Implement on Matrix4x4 viewportMatrix.viewport(resolveViewport(renderView->viewport, renderView->surfaceSize)); return UniformValue(Matrix4x4(viewportMatrix.inverted())); } case AspectRatio: return float(renderView->surfaceSize.width()) / float(renderView->surfaceSize.height()); case Exposure: return UniformValue(renderView->cameraLens->exposure()); case Gamma: return UniformValue(renderView->gamma); case Time: // TODO add back time // return UniformValue(float(m_renderer->time() / 1000000000.0f)); return UniformValue(0.0f); case EyePosition: return UniformValue(cameraMatrices->eyePosition); case SkinningPalette: { // const Armature *armature = entity->renderComponent(); // if (!armature) { // qCWarning(Jobs, "Requesting skinningPalette uniform but no armature set on entity"); // return UniformValue(); // } // return armature->skinningPaletteUniform(); qWarning() << "WARNING: Armatures not supported in Dragon renderer"; return UniformValue(); } default: Q_UNREACHABLE(); } return UniformValue(); } bool ActivatedSurface::bindVertexArrayObject(const Immutable &vao) { if (m_supportsVAO) { Q_ASSERT(!vao->m_vao.isNull()); Q_ASSERT(vao->m_vao->isCreated()); vao->m_vao->bind(); return true; } else { qDebug() << "WARNING: Emulated VAO not yet supported"; return false; // // Unbind any other VAO that may have been bound and not released correctly // if (m_ctx->m_currentVAO != nullptr && m_ctx->m_currentVAO != this) // m_ctx->m_currentVAO->release(); // m_ctx->m_currentVAO = this; // // We need to specify array and vertex attributes // for (const SubmissionContext::VAOVertexAttribute &attr : qAsConst(m_vertexAttributes)) // m_ctx->enableAttribute(attr); // if (!m_indexAttribute.isNull()) // m_ctx->bindGLBuffer(m_ctx->m_renderer->nodeManagers()->glBufferManager()->data(m_indexAttribute), // GLBuffer::IndexBuffer); } } Immutable ActivatedSurface::createVertexArrayObject( VAOIdentifier key, const Mutable &uploadedShader, const Immutable &geometry, const ValueContainer &attributes, const MutableContainer &uploadedBuffers) { GLVertexArrayObject vao; if (m_supportsVAO) { vao.m_vao.reset(new QOpenGLVertexArrayObject); vao.m_vao->create(); bindVertexArrayObject(vao); } vao.m_owners = key; // TODO return bound vao for other commands to use or just move internals here const auto attributeIds = geometry->attributes(); for (QNodeId attributeId : attributeIds) { Q_ASSERT(attributes.contains(attributeId)); const auto &attribute = attributes[attributeId]; const auto &glBuffer = uploadedBuffers[attribute->bufferId()]; const auto attributeType = attributeTypeToGLBufferType(attribute->attributeType()); // Index Attribute bool attributeWasDirty = attributes.dirtyOrNew().contains(attributeId); // TODO fix this by checking in which cases we need a real update bool geometryDirtyOrSomething = true; if (!attributeWasDirty && !geometryDirtyOrSomething) continue; if (attribute->attributeType() == QAttribute::IndexAttribute) { GLenum glType = glBufferTypes[attributeType]; m_glContext->functions()->glBindBuffer(glType, glBuffer->bufferId()); vao.m_indexAttribute = glBuffer; continue; } if (attribute->attributeType() != QAttribute::VertexAttribute) { qWarning() << "WARNING: Indirect attributes are not yet implemented in the Dragon renderer."; continue; } // Find the location for the attribute const QVector shaderAttributes = uploadedShader->attributes; Render::ShaderAttribute attributeDescription; bool foundDescription = false; for (const auto &shaderAttribute : shaderAttributes) { if (shaderAttribute.m_nameId == attribute->nameId()) { foundDescription = true; attributeDescription = shaderAttribute; break; } } // Strictly not necessary to check both, location is -1 by default if (!foundDescription || attributeDescription.m_location < 0) continue; const int location = attributeDescription.m_location; const GLint attributeDataType = glDataTypeFromAttributeDataType(attribute->vertexBaseType()); uint typeSize = 0; uint 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(); } GLVertexArrayObject::VAOVertexAttribute attr; 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 (uint i = 0; i < attrCount; i++) { // TODO consider making location uint attr.location = location + int(i); attr.byteOffset = attribute->byteOffset() + (i * attrCount * typeSize); GLenum glType = glBufferTypes[attributeType]; m_glContext->functions()->glBindBuffer(glType, glBuffer->bufferId()); // 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(qintptr(attr.byteOffset))); // Done by the helper if it supports it if (attr.divisor != 0) m_glHelper->vertexAttribDivisor(attr.location, attr.divisor); // Save this in the current emulated VAO vao.saveVertexAttribute(attr); } } // TODO do this in a cleaner way? vao.m_vao->release(); return vao; } void ActivatedSurface::memoryBarrier(const QMemoryBarrier::Operations &operations) { if (operations != QMemoryBarrier::None) m_glHelper->memoryBarrier(operations); } // TODO Review (make more functional?) void ActivatedSurface::activateRenderTarget(GLuint fboId, const AttachmentPack &attachments) { m_activeFBO = fboId; m_glHelper->bindFrameBufferObject(m_activeFBO, GraphicsHelperInterface::FBODraw); // Set active drawBuffers activateDrawBuffers(attachments); } // TODO Review (make more functional?) void ActivatedSurface::activateDrawBuffers(const AttachmentPack &attachments) { if (!m_glHelper->checkFrameBufferComplete()) { qWarning() << "WARNING: FBO incomplete"; return; } const QVector activeDrawBuffers = attachments.drawBuffers; if (activeDrawBuffers.size() <= 1) return; // We need MRT if the number is more than 1 if (!m_glHelper->supportsFeature(GraphicsHelperInterface::MRT)) { qWarning() << "WARNING: Active draw buffers > 1, but MRT is not supported."; return; } // Set up MRT, glDrawBuffers... m_glHelper->drawBuffers(activeDrawBuffers.size(), activeDrawBuffers.data()); } // TODO Review (make more functional?) GLuint ActivatedSurface::createRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, const MutableContainer &glTextures) { 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, glTextures); } else { qCritical("Failed to create FBO"); } return fboId; } // TODO Review (make more functional?) GLuint ActivatedSurface::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, const MutableContainer &glTextures) { Q_ASSERT(m_renderTargets.contains(renderTargetNodeId)); 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? const QSize s = m_renderTargetsSize[fboId]; const auto attachments_ = attachments.outputs; for (const auto &attachment : attachments_) { // ### 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 (!glTextures.contains(attachment->textureUuid)) { qWarning() << "Could not find texture attachment" << attachment->textureUuid; continue; } const auto &glTexture = glTextures[attachment->textureUuid]; needsResize |= (glTexture->size() != s); } } if (needsResize) { m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); bindFrameBufferAttachmentHelper(fboId, attachments, glTextures); } return fboId; } void ActivatedSurface::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()) { qWarning() << "WARNING: Got empty viewport size. Skipping."; 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_glContext->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()); } // TODO Review (make more functional?) QSize ActivatedSurface::bindFrameBufferAttachmentHelper(GLuint fboId, const AttachmentPack &attachments, const MutableContainer &glTextures) { // 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; const auto attachments_ = attachments.outputs; for (const auto &output : attachments_) { if (!glTextures.contains(output->textureUuid)) { qWarning() << Q_FUNC_INFO << "WARNING: Could not find texture attachment" << output->textureUuid; continue; } const auto &glTexture = glTextures[output->textureUuid]; // TODO HACK workaround to avoid copying helpers to Dragon just yet Dragon::Attachment attachment; attachment.m_name = output->name; attachment.m_mipLevel = output->mipLevel; attachment.m_layer = output->layer; attachment.m_textureUuid = output->textureUuid; attachment.m_point = output->point; attachment.m_face = output->face; // END HACK if (!m_glHelper->frameBufferNeedsRenderBuffer(attachment)) { QOpenGLTexture *glTex = glTexture->openGLTexture.data(); if (glTex != nullptr) { 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 { // TODO implement qWarning() << Q_FUNC_INFO << "WARNING: Not implemented"; // RenderBuffer *renderBuffer = glTexture ? // glTexture->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); // } } } // TODO hack, return from this function and use it properly in renderTargetSize m_renderTargetsSize.insert(fboId, fboSize); return fboSize; } // TODO review QSize ActivatedSurface::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_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_glContext->functions() ->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &attachmentObjectType); m_glContext->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) { int dpr = static_cast(m_surface)->devicePixelRatio(); renderTargetSize *= dpr; } } return renderTargetSize; } GraphicsApiFilterData ActivatedSurface::contextInfo() const { GraphicsApiFilterData result; QStringList extensions; const auto exts = m_glContext->extensions(); for (const QByteArray &ext : exts) extensions << QString::fromUtf8(ext); result.m_major = m_glContext->format().version().first; result.m_minor = m_glContext->format().version().second; result.m_api = m_glContext->isOpenGLES() ? QGraphicsApiFilter::OpenGLES : QGraphicsApiFilter::OpenGL; result.m_profile = static_cast( m_glContext->format().profile()); result.m_extensions = extensions; result.m_vendor = QString::fromUtf8( reinterpret_cast(m_glContext->functions()->glGetString(GL_VENDOR))); return result; } QSharedPointer ActivatedSurface::resolveHighestOpenGLFunctions() const { Q_ASSERT(m_glContext); QSharedPointer glHelper; if (m_glContext->isOpenGLES()) { if (m_glContext->format().majorVersion() >= 3) { if (m_glContext->format().minorVersion() >= 2) { glHelper.reset(new Dragon::GraphicsHelperES3_2()); //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL ES 3.2 Helper"; } else { glHelper.reset(new Dragon::GraphicsHelperES3()); //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL ES 3.0 Helper"; } } else { glHelper.reset(new Dragon::GraphicsHelperES2()); //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL ES2 Helper"; } glHelper->initializeHelper(m_glContext, nullptr); } #ifndef QT_OPENGL_ES_2 else { QAbstractOpenGLFunctions *glFunctions = nullptr; // TODO: Note that max OpenGL backend is currently GL3_3, check 4.3 behavior /*if ((glFunctions = m_glContext->versionFunctions()) != nullptr) { //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL 4.3"; glHelper.reset(new Dragon::GraphicsHelperGL4()); } else */ if ((glFunctions = m_glContext->versionFunctions()) != nullptr) { //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL 3.3"; glHelper.reset(new Dragon::GraphicsHelperGL3_3()); } else if ((glFunctions = m_glContext->versionFunctions()) != nullptr) { //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL 3.2"; glHelper.reset(new Dragon::GraphicsHelperGL3_2()); } else if ((glFunctions = m_glContext->versionFunctions()) != nullptr) { //qCDebug(Render::Backend) << Q_FUNC_INFO << " Building OpenGL 2 Helper"; glHelper.reset(new Dragon::GraphicsHelperGL2()); } Q_ASSERT_X(glHelper, "GraphicsContext::resolveHighestOpenGLFunctions", "unable to create valid helper for available OpenGL version"); glHelper->initializeHelper(m_glContext, glFunctions); } #endif return glHelper; } namespace { // Render States Helpers template void applyStateHelper(const GenericState *state, ActivatedSurface *gc) { Q_UNUSED(state) Q_UNUSED(gc) } template<> void applyStateHelper(const AlphaFunc *state, ActivatedSurface *gc) { const auto values = state->values(); gc->glHelper()->alphaTest(std::get<0>(values), std::get<1>(values)); } template<> void applyStateHelper(const BlendEquationArguments *state, ActivatedSurface *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->glHelper()->enablei(GL_BLEND, std::get<5>(values)); if (gc->glHelper()->supportsFeature(GraphicsHelperInterface::DrawBuffersBlend)) { gc->glHelper()->blendFuncSeparatei(std::get<5>(values), std::get<0>(values), std::get<1>(values), std::get<2>(values), std::get<3>(values)); } } else { gc->glHelper()->disablei(GL_BLEND, std::get<5>(values)); } } } template<> void applyStateHelper(const BlendEquation *state, ActivatedSurface *gc) { gc->glHelper()->blendEquation(std::get<0>(state->values())); } template<> void applyStateHelper(const MSAAEnabled *state, ActivatedSurface *gc) { gc->glHelper()->setMSAAEnabled(std::get<0>(state->values())); } template<> void applyStateHelper(const DepthTest *state, ActivatedSurface *gc) { gc->glHelper()->depthTest(std::get<0>(state->values())); } template<> void applyStateHelper(const NoDepthMask *state, ActivatedSurface *gc) { gc->glHelper()->depthMask(std::get<0>(state->values())); } template<> void applyStateHelper(const CullFace *state, ActivatedSurface *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(const FrontFace *state, ActivatedSurface *gc) { gc->glHelper()->frontFace(std::get<0>(state->values())); } template<> void applyStateHelper(const ScissorTest *state, ActivatedSurface *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(const StencilTest *state, ActivatedSurface *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(const AlphaCoverage *, ActivatedSurface *gc) { gc->glHelper()->setAlphaCoverageEnabled(true); } template<> void applyStateHelper(const PointSize *state, ActivatedSurface *gc) { const auto values = state->values(); gc->glHelper()->pointSize(std::get<0>(values), std::get<1>(values)); } template<> void applyStateHelper(const PolygonOffset *state, ActivatedSurface *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(const ColorMask *state, ActivatedSurface *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(const ClipPlane *state, ActivatedSurface *gc) { const auto values = state->values(); gc->glHelper()->enableClipPlane(std::get<0>(values)); gc->glHelper()->setClipPlane(std::get<0>(values), std::get<1>(values), std::get<2>(values)); } template<> void applyStateHelper(const SeamlessCubemap *, ActivatedSurface *gc) { gc->glHelper()->setSeamlessCubemap(true); } template<> void applyStateHelper(const StencilOp *state, ActivatedSurface *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(const StencilMask *state, ActivatedSurface *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(const Dithering *, ActivatedSurface *gc) { gc->openGLContext()->functions()->glEnable(GL_DITHER); } #ifndef GL_LINE_SMOOTH #define GL_LINE_SMOOTH 0x0B20 #endif template<> void applyStateHelper(const LineWidth *state, ActivatedSurface *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)); } } // namespace QOpenGLContext *ActivatedSurface::openGLContext() const { return m_glContext; } void ActivatedSurface::applyState(const StateVariant &stateVariant) { switch (stateVariant.type) { case AlphaCoverageStateMask: { applyStateHelper(static_cast( stateVariant.constState()), this); break; } case AlphaTestMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case BlendStateMask: { applyStateHelper(static_cast( stateVariant.constState()), this); break; } case BlendEquationArgumentsMask: { applyStateHelper(static_cast( stateVariant.constState()), this); break; } case MSAAEnabledStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case CullFaceStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case DepthWriteStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case DepthTestStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case FrontFaceStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case ScissorStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case StencilTestStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case PointSizeMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case PolygonOffsetStateMask: { applyStateHelper(static_cast( stateVariant.constState()), this); break; } case ColorStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case ClipPlaneMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case SeamlessCubemapMask: { applyStateHelper(static_cast( stateVariant.constState()), this); break; } case StencilOpMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case StencilWriteStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case DitheringStateMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } case LineWidthMask: { applyStateHelper(static_cast(stateVariant.constState()), this); break; } default: Q_UNREACHABLE(); } } void ActivatedSurface::resetMasked(qint64 maskOfStatesToReset) { // TODO -> Call gcHelper methods instead of raw GL // QOpenGLFunctions shouldn't be used here directly QOpenGLFunctions *funcs = m_glContext->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 & 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) m_glHelper->setAlphaCoverageEnabled(false); if (maskOfStatesToReset & PointSizeMask) m_glHelper->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 = m_glHelper->maxClipPlaneCount(); for (GLint i = 0; i < max; ++i) m_glHelper->disableClipPlane(i); } if (maskOfStatesToReset & SeamlessCubemapMask) m_glHelper->setSeamlessCubemap(false); if (maskOfStatesToReset & StencilOpMask) funcs->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); if (maskOfStatesToReset & LineWidthMask) funcs->glLineWidth(1.0f); } RenderStateSet ActivatedSurface::applyStateSet(const RenderStateSet &previous, const RenderStateSet &next) { const StateMaskSet invOurState = ~next.stateMask(); // generate a mask for each set bit in previous, where we do not have // the corresponding bit set. StateMaskSet stateToReset = previous.stateMask() & invOurState; // TODO Reset states that aren't active in the current state set // TODO I don't trust that this currently checks the values of the states, // hence, let's reset all just to make sure for now resetMasked(stateToReset); // Apply states that weren't in the previous state or that have // different values for (const StateVariant &ds : next.states()) { if (previous.contains(ds)) { continue; } applyState(ds); } return next; } // TODO consider moving to Commands void ActivatedSurface::blitFramebuffer(const BlitFramebufferInfo &blitFramebufferInfo, uint defaultFboId, const MutableContainer &glTextures) { const auto &inputTarget = blitFramebufferInfo.sourceRenderTarget; const auto &inputAttachments = blitFramebufferInfo.sourceAttachments; const auto &outputTarget = blitFramebufferInfo.destinationRenderTarget; const auto &outputAttachments = blitFramebufferInfo.destinationAttachments; const auto &inputRect = blitFramebufferInfo.node->sourceRect(); const auto &outputRect = blitFramebufferInfo.node->destinationRect(); const auto &inputAttachmentPoint = blitFramebufferInfo.node->sourceAttachmentPoint(); const auto &outputAttachmentPoint = blitFramebufferInfo.node->destinationAttachmentPoint(); const auto &interpolationMethod = blitFramebufferInfo.node->interpolationMethod(); GLuint inputFboId = defaultFboId; bool inputBufferIsDefault = true; if (inputTarget.has_value()) { const auto &inputRenderTarget = inputTarget.get(); if (m_renderTargets.contains(inputRenderTarget->peerId())) inputFboId = updateRenderTarget(inputRenderTarget->peerId(), inputAttachments.get(), glTextures); else inputFboId = createRenderTarget(inputRenderTarget->peerId(), inputAttachments.get(), glTextures); inputBufferIsDefault = false; } GLuint outputFboId = defaultFboId; bool outputBufferIsDefault = true; if (outputTarget.has_value()) { const auto &outputRenderTarget = outputTarget.get(); if (m_renderTargets.contains(outputRenderTarget->peerId())) outputFboId = updateRenderTarget(outputRenderTarget->peerId(), outputAttachments.get(), glTextures); else outputFboId = createRenderTarget(outputRenderTarget->peerId(), outputAttachments.get(), glTextures); 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 // TODO verify that this is the correct ID const GLuint lastDrawFboId = defaultFboId; // Activate input framebuffer for reading m_glHelper->bindFrameBufferObject(inputFboId, GraphicsHelperInterface::FBORead); // Activate output framebuffer for writing m_glHelper->bindFrameBufferObject(outputFboId, GraphicsHelperInterface::FBODraw); //Bind texture if (!inputBufferIsDefault) m_glHelper->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; m_glHelper->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, mode); // Reset draw buffer m_glHelper->bindFrameBufferObject(lastDrawFboId, GraphicsHelperInterface::FBOReadAndDraw); if (outputAttachmentPoint != QRenderTargetOutput::Color0) { const int buf = QRenderTargetOutput::Color0; m_glHelper->drawBuffers(1, &buf); } } DrawContext::DrawContext(QOpenGLContext *openGLContext, QSurface *surface, bool autoSwapBuffers) : m_openGLContext(openGLContext) , m_surface(surface) , m_autoSwapBuffers(autoSwapBuffers) { } DrawContext::~DrawContext() { if (m_autoSwapBuffers) m_openGLContext->swapBuffers(m_surface); } #define QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformTypeEnum, BaseType, Func) \ template<> \ void ActivatedSurface::applyUniformHelper(const ShaderUniform &description, const UniformValue &value) const \ { \ const int count = qMin(description.m_size, int(value.byteSize() / description.m_rawByteSize)); \ m_glHelper->Func(description.m_location, count, value.constData()); \ } QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Float, float, glUniform1fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Vec2, float, glUniform2fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Vec3, float, glUniform3fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Vec4, float, glUniform4fv) // OpenGL expects int* as values for booleans QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Bool, int, glUniform1iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::BVec2, int, glUniform2iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::BVec3, int, glUniform3iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::BVec4, int, glUniform4iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Int, int, glUniform1iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::IVec2, int, glUniform2iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::IVec3, int, glUniform3iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::IVec4, int, glUniform4iv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::UInt, uint, glUniform1uiv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::UIVec2, uint, glUniform2uiv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::UIVec3, uint, glUniform3uiv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::UIVec4, uint, glUniform4uiv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat2, float, glUniformMatrix2fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat3, float, glUniformMatrix3fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat4, float, glUniformMatrix4fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat2x3, float, glUniformMatrix2x3fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat3x2, float, glUniformMatrix3x2fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat2x4, float, glUniformMatrix2x4fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat4x2, float, glUniformMatrix4x2fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat3x4, float, glUniformMatrix3x4fv) QT3D_DRAGON_UNIFORM_TYPE_IMPL(UniformType::Mat4x3, float, glUniformMatrix4x3fv) } // namespace Dragon } // namespace Qt3DRender QT_END_NAMESPACE