diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2018-03-02 10:27:07 +0100 |
---|---|---|
committer | Paul Lemire <paul.lemire@kdab.com> | 2018-03-16 11:21:02 +0000 |
commit | c1b495f70c3b4f0c23d44dfcd99aed5882312100 (patch) | |
tree | ced8484d36d999395f520f2d7262d832afc73c99 /src/render/graphicshelpers/graphicscontext.cpp | |
parent | 02dff327fae71d6efdda091f8ae0a4483e72e849 (diff) |
Split GraphicsContext in two
- GraphicsContext is now a stateless wrapper around GL calls
- SubmissionContext is a GraphicsContext + caches and states used to render a
frame
Change-Id: I147b56dfd4c403911faadc0e9821fff9a892f71c
Reviewed-by: Mike Krus <mike.krus@kdab.com>
Diffstat (limited to 'src/render/graphicshelpers/graphicscontext.cpp')
-rw-r--r-- | src/render/graphicshelpers/graphicscontext.cpp | 1119 |
1 files changed, 26 insertions, 1093 deletions
diff --git a/src/render/graphicshelpers/graphicscontext.cpp b/src/render/graphicshelpers/graphicscontext.cpp index 93f924052..ddd7281d1 100644 --- a/src/render/graphicshelpers/graphicscontext.cpp +++ b/src/render/graphicshelpers/graphicscontext.cpp @@ -111,88 +111,45 @@ QOpenGLShader::ShaderType shaderType(Qt3DRender::QShaderProgram::ShaderType type namespace Qt3DRender { namespace Render { -static QHash<unsigned int, GraphicsContext*> static_contexts; - -static void logOpenGLDebugMessage(const QOpenGLDebugMessage &debugMessage) -{ - qDebug() << "OpenGL debug message:" << debugMessage; -} - namespace { -GLBuffer::Type attributeTypeToGLBufferType(QAttribute::AttributeType type) +void logOpenGLDebugMessage(const QOpenGLDebugMessage &debugMessage) { - switch (type) { - case QAttribute::VertexAttribute: - return GLBuffer::ArrayBuffer; - case QAttribute::IndexAttribute: - return GLBuffer::IndexBuffer; - case QAttribute::DrawIndirectAttribute: - return GLBuffer::DrawIndirectBuffer; - default: - Q_UNREACHABLE(); - } + qDebug() << "OpenGL debug message:" << debugMessage; } } // anonymous -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; -} - GraphicsContext::GraphicsContext() : m_initialized(false) - , m_id(nextFreeContextId()) + , m_supportsVAO(false) + , m_maxTextureUnits(0) + , m_defaultFBO(0) , m_gl(nullptr) - , m_surface(nullptr) , m_glHelper(nullptr) - , m_ownCurrent(true) - , m_activeShader(nullptr) - , m_activeShaderDNA(0) - , m_renderTargetFormat(QAbstractTexture::NoFormat) - , m_currClearStencilValue(0) - , m_currClearDepthValue(1.f) - , m_currClearColorValue(0,0,0,0) - , m_material(nullptr) - , m_activeFBO(0) - , m_defaultFBO(0) - , m_boundArrayBuffer(nullptr) - , m_stateSet(nullptr) - , m_renderer(nullptr) - , m_uboTempArray(QByteArray(1024, 0)) - , m_supportsVAO(true) + , m_shaderCache(nullptr) , m_debugLogger(nullptr) - , m_currentVAO(nullptr) { - static_contexts[m_id] = this; } GraphicsContext::~GraphicsContext() { - releaseOpenGL(); +} - Q_ASSERT(static_contexts[m_id] == this); - static_contexts.remove(m_id); +void GraphicsContext::setOpenGLContext(QOpenGLContext* ctx) +{ + Q_ASSERT(ctx); + m_gl = ctx; } void GraphicsContext::initialize() { m_initialized = true; - Q_ASSERT(m_gl); + Q_ASSERT(m_gl && m_shaderCache); - GLint numTexUnits; - m_gl->functions()->glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTexUnits); - qCDebug(Backend) << "context supports" << numTexUnits << "texture units"; - - m_activeTextures.resize(numTexUnits); + m_gl->functions()->glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &m_maxTextureUnits); + qCDebug(Backend) << "context supports" << m_maxTextureUnits << "texture units"; if (m_gl->format().majorVersion() >= 3) { m_supportsVAO = true; @@ -207,96 +164,6 @@ void GraphicsContext::initialize() qCDebug(Backend) << "VAO support = " << m_supportsVAO; } -void GraphicsContext::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 GraphicsContext::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(); - - // Sets or Create the correct m_glHelper - // for the current surface - activateGLHelper(); - -#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 (!m_initialized) { - initialize(); - } - - // 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_activeShaderDNA = 0; - } - - // reset active textures - for (int u = 0; u < m_activeTextures.size(); ++u) - m_activeTextures[u].texture = nullptr; - - m_boundArrayBuffer = nullptr; - - static int callCount = 0; - ++callCount; - const int shaderPurgePeriod = 600; - if (callCount % shaderPurgePeriod == 0) - m_shaderCache.purge(); - - return true; -} - void GraphicsContext::clearBackBuffer(QClearBuffers::BufferTypeFlags buffers) { if (buffers != QClearBuffers::None) { @@ -313,120 +180,6 @@ void GraphicsContext::clearBackBuffer(QClearBuffers::BufferTypeFlags buffers) } } -void GraphicsContext::endDrawing(bool swapBuffers) -{ - if (swapBuffers) - m_gl->swapBuffers(m_surface); - if (m_ownCurrent) - m_gl->doneCurrent(); - decayTextureScores(); -} - -QSize GraphicsContext::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_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, - &attachmentObjectType); - m_gl->functions()->glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, - &attachment0Name); - - if (attachmentObjectType == GL_RENDERBUFFER && m_glHelper->supportsFeature(GraphicsHelperInterface::RenderBufferDimensionRetrieval)) - renderTargetSize = m_glHelper->getRenderBufferDimensions(attachment0Name); - else if (attachmentObjectType == GL_TEXTURE && m_glHelper->supportsFeature(GraphicsHelperInterface::TextureDimensionRetrieval)) - // Assumes texture level 0 and GL_TEXTURE_2D target - renderTargetSize = m_glHelper->getTextureDimensions(attachment0Name, GL_TEXTURE_2D); - else - return renderTargetSize; - } - } else { - renderTargetSize = m_surface->size(); - if (m_surface->surfaceClass() == QSurface::Window) { - int dpr = static_cast<QWindow *>(m_surface)->devicePixelRatio(); - renderTargetSize *= dpr; - } - } - return renderTargetSize; -} - -void GraphicsContext::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 GraphicsContext::releaseOpenGL() -{ - m_shaderCache.clear(); - m_renderBufferHash.clear(); - - // Stop and destroy the OpenGL logger - if (m_debugLogger) { - m_debugLogger->stopLogging(); - m_debugLogger.reset(nullptr); - } - - qDeleteAll(m_glHelpers); -} - -// The OpenGLContext is not current on any surface at this point -void GraphicsContext::setOpenGLContext(QOpenGLContext* ctx) -{ - Q_ASSERT(ctx); - - releaseOpenGL(); - m_gl = ctx; -} - -void GraphicsContext::activateGLHelper() -{ - // Sets the correct GL Helper depending on the surface - // If no helper exists, create one - m_glHelper = m_glHelpers.value(m_surface); - if (!m_glHelper) { - m_glHelper = resolveHighestOpenGLFunctions(); - m_glHelpers.insert(m_surface, m_glHelper); - // Note: OpenGLContext is current at this point - m_gl->functions()->glDisable(GL_DITHER); - } -} - bool GraphicsContext::hasValidGLHelper() const { return m_glHelper != nullptr; @@ -510,13 +263,13 @@ void GraphicsContext::introspectShaderInterface(Shader *shader, QOpenGLShaderPro void GraphicsContext::loadShader(Shader *shader, ShaderManager *manager) { bool wasPresent = false; - QOpenGLShaderProgram *shaderProgram = m_shaderCache.getShaderProgramAndAddRef(shader->dna(), shader->peerId(), &wasPresent); + QOpenGLShaderProgram *shaderProgram = m_shaderCache->getShaderProgramAndAddRef(shader->dna(), shader->peerId(), &wasPresent); if (!shaderProgram && !wasPresent) { // No matching QOpenGLShader in the cache so create one shaderProgram = createShaderProgram(shader); // Store in cache (even when failed and shaderProgram is null) - m_shaderCache.insert(shader->dna(), shader->peerId(), shaderProgram); + m_shaderCache->insert(shader->dna(), shader->peerId(), shaderProgram); } // Ensure the Shader node knows about the program interface @@ -524,7 +277,7 @@ void GraphicsContext::loadShader(Shader *shader, ShaderManager *manager) // Find an already loaded shader that shares the same QOpenGLShaderProgram Shader *refShader = nullptr; - const QVector<Qt3DCore::QNodeId> sharedShaderIds = m_shaderCache.shaderIdsForProgram(shader->dna()); + const QVector<Qt3DCore::QNodeId> sharedShaderIds = m_shaderCache->shaderIdsForProgram(shader->dna()); for (const Qt3DCore::QNodeId sharedShaderId : sharedShaderIds) { Shader *sharedShader = manager->lookupResource(sharedShaderId); // Note: no need to check if shader->peerId != sharedShader->peerId @@ -548,137 +301,9 @@ void GraphicsContext::loadShader(Shader *shader, ShaderManager *manager) } } -// Called only from RenderThread -bool GraphicsContext::activateShader(ProgramDNA shaderDNA) -{ - if (shaderDNA != m_activeShaderDNA) { - // Ensure material uniforms are re-applied - m_material = nullptr; - - m_activeShader = m_shaderCache.getShaderProgramForDNA(shaderDNA); - if (Q_LIKELY(m_activeShader != nullptr)) { - m_activeShader->bind(); - m_activeShaderDNA = shaderDNA; - } else { - m_glHelper->useProgram(0); - qWarning() << "No shader program found for DNA"; - m_activeShaderDNA = 0; - return false; - } - } - return true; -} - void GraphicsContext::removeShaderProgramReference(Shader *shaderNode) { - m_shaderCache.removeRef(shaderNode->dna(), shaderNode->peerId()); -} - -void GraphicsContext::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 just register it - // Insert FBO into hash - m_renderTargets.insert(renderTargetNodeId, fboId); - } else { - fboId = createRenderTarget(renderTargetNodeId, attachments); - } - } else { - fboId = updateRenderTarget(renderTargetNodeId, attachments, true); - } - } - m_activeFBO = fboId; - m_glHelper->bindFrameBufferObject(m_activeFBO, GraphicsHelperInterface::FBODraw); - // Set active drawBuffers - activateDrawBuffers(attachments); -} - -GLuint GraphicsContext::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 - // Insert FBO into hash - m_renderTargets.insert(renderTargetNodeId, fboId); - // Bind FBO - m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); - bindFrameBufferAttachmentHelper(fboId, attachments); - } else { - qCritical("Failed to create FBO"); - } - return fboId; -} - -GLuint GraphicsContext::updateRenderTarget(Qt3DCore::QNodeId renderTargetNodeId, const AttachmentPack &attachments, bool isActiveRenderTarget) -{ - 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? - GLTextureManager *glTextureManager = m_renderer->nodeManagers()->glTextureManager(); - const QSize s = m_renderTargetsSize[fboId]; - const auto attachments_ = attachments.attachments(); - for (const Attachment &attachment : attachments_) { - GLTexture *rTex = glTextureManager->lookupResource(attachment.m_textureUuid); - // ### TODO QTBUG-64757 this check is insufficient since the - // texture may have changed to another one with the same size. That - // case is not handled atm. - needsResize |= (rTex != nullptr && rTex->size() != s); - if (isActiveRenderTarget) { - if (attachment.m_point == QRenderTargetOutput::Color0) - m_renderTargetFormat = rTex->properties().format; - } - } - } - - if (needsResize) { - m_glHelper->bindFrameBufferObject(fboId, GraphicsHelperInterface::FBODraw); - bindFrameBufferAttachmentHelper(fboId, attachments); - } - - return fboId; -} - -void GraphicsContext::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->nodeManagers()->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->getOrCreateGLTexture() : nullptr; - 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 { - RenderBuffer *renderBuffer = rTex ? rTex->getOrCreateRenderBuffer() : nullptr; - if (renderBuffer) { - if (fboSize.isEmpty()) - fboSize = QSize(renderBuffer->width(), renderBuffer->height()); - else - fboSize = QSize(qMin(fboSize.width(), renderBuffer->width()), qMin(fboSize.height(), renderBuffer->height())); - m_glHelper->bindFrameBufferAttachment(renderBuffer, attachment); - } - } - } - m_renderTargetsSize.insert(fboId, fboSize); + m_shaderCache->removeRef(shaderNode->dna(), shaderNode->peerId()); } void GraphicsContext::activateDrawBuffers(const AttachmentPack &attachments) @@ -697,64 +322,6 @@ void GraphicsContext::activateDrawBuffers(const AttachmentPack &attachments) } } - -void GraphicsContext::setActiveMaterial(Material *rmat) -{ - if (m_material == rmat) - return; - - deactivateTexturesWithScope(TextureScopeMaterial); - m_material = rmat; -} - -int GraphicsContext::activateTexture(TextureScope scope, GLTexture *tex, int onUnit) -{ - // Returns the texture unit to use for the texture - // This always return a valid unit, unless there are more textures than - // texture unit available for the current material - onUnit = assignUnitForTexture(tex); - - // check we didn't overflow the available units - if (onUnit == -1) - return -1; - - // actually re-bind if required, the tex->dna on the unit not being the same - // Note: tex->dna() could be 0 if the texture has not been created yet - if (m_activeTextures[onUnit].texture != tex) { - QOpenGLTexture *glTex = tex->getOrCreateGLTexture(); - if (glTex == nullptr) - return -1; - glTex->bind(onUnit); - m_activeTextures[onUnit].texture = tex; - } - -#if defined(QT3D_RENDER_ASPECT_OPENGL_DEBUG) - int err = m_gl->functions()->glGetError(); - if (err) - qCWarning(Backend) << "GL error after activating texture" << QString::number(err, 16) - << tex->textureId() << "on unit" << onUnit; -#endif - - m_activeTextures[onUnit].score = 200; - m_activeTextures[onUnit].pinned = true; - m_activeTextures[onUnit].scope = scope; - - return onUnit; -} - -void GraphicsContext::deactivateTexturesWithScope(TextureScope ts) -{ - for (int u=0; u<m_activeTextures.size(); ++u) { - if (!m_activeTextures[u].pinned) - continue; // inactive, ignore - - if (m_activeTextures[u].scope == ts) { - m_activeTextures[u].pinned = false; - m_activeTextures[u].score = qMax(m_activeTextures[u].score, 1) - 1; - } - } // of units iteration -} - /*! * \internal * Finds the highest supported opengl version and internally use the most optimized @@ -843,34 +410,6 @@ GraphicsHelperInterface *GraphicsContext::resolveHighestOpenGLFunctions() return glHelper; } -void GraphicsContext::deactivateTexture(GLTexture* tex) -{ - for (int u=0; u<m_activeTextures.size(); ++u) { - if (m_activeTextures[u].texture == tex) { - Q_ASSERT(m_activeTextures[u].pinned); - m_activeTextures[u].pinned = false; - return; - } - } // of units iteration - - qCWarning(Backend) << Q_FUNC_INFO << "texture not active:" << tex; -} - -void GraphicsContext::setCurrentStateSet(RenderStateSet *ss) -{ - if (ss == m_stateSet) - return; - - if (ss) - ss->apply(this); - m_stateSet = ss; -} - -RenderStateSet *GraphicsContext::currentStateSet() const -{ - return m_stateSet; -} - const GraphicsApiFilterData *GraphicsContext::contextInfo() const { return &m_contextInfo; @@ -1064,26 +603,17 @@ GLuint GraphicsContext::boundFrameBufferObject() void GraphicsContext::clearColor(const QColor &color) { - if (m_currClearColorValue != color) { - m_currClearColorValue = color; - m_gl->functions()->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); - } + m_gl->functions()->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } void GraphicsContext::clearDepthValue(float depth) { - if (m_currClearDepthValue != depth) { - m_currClearDepthValue = depth; - m_gl->functions()->glClearDepthf(depth); - } + m_gl->functions()->glClearDepthf(depth); } void GraphicsContext::clearStencilValue(int stencil) { - if (m_currClearStencilValue != stencil) { - m_currClearStencilValue = stencil; - m_gl->functions()->glClearStencil(stencil); - } + m_gl->functions()->glClearStencil(stencil); } void GraphicsContext::enableClipPlane(int clipPlane) @@ -1106,6 +636,11 @@ GLint GraphicsContext::maxClipPlaneCount() return m_glHelper->maxClipPlaneCount(); } +GLint GraphicsContext::maxTextureUnitsCount() +{ + return m_maxTextureUnits; +} + void GraphicsContext::enablePrimitiveRestart(int restartIndex) { if (m_glHelper->supportsFeature(GraphicsHelperInterface::PrimitiveRestart)) @@ -1154,140 +689,6 @@ void GraphicsContext::setSeamlessCubemap(bool enable) m_glHelper->setSeamlessCubemap(enable); } -/*! - \internal - Returns a texture unit for a texture, -1 if all texture units are assigned. - Tries to use the texture unit with the texture that hasn't been used for the longest time - if the texture happens not to be already pinned on a texture unit. - */ -GLint GraphicsContext::assignUnitForTexture(GLTexture *tex) -{ - int lowestScoredUnit = -1; - int lowestScore = 0xfffffff; - - for (int u=0; u<m_activeTextures.size(); ++u) { - if (m_activeTextures[u].texture == tex) - return u; - - // No texture is currently active on the texture unit - // we save the texture unit with the texture that has been on there - // the longest time while not being used - if (!m_activeTextures[u].pinned) { - int score = m_activeTextures[u].score; - if (score < lowestScore) { - lowestScore = score; - lowestScoredUnit = u; - } - } - } // of units iteration - - if (lowestScoredUnit == -1) - qCWarning(Backend) << Q_FUNC_INFO << "No free texture units!"; - - return lowestScoredUnit; -} - -void GraphicsContext::decayTextureScores() -{ - for (int u = 0; u < m_activeTextures.size(); u++) - m_activeTextures[u].score = qMax(m_activeTextures[u].score - 1, 0); -} - -QOpenGLShaderProgram* GraphicsContext::activeShader() const -{ - Q_ASSERT(m_activeShader); - return m_activeShader; -} - -void GraphicsContext::setRenderer(Renderer *renderer) -{ - m_renderer = renderer; -} - -// It will be easier if the QGraphicContext applies the QUniformPack -// than the other way around -bool GraphicsContext::setParameters(ShaderParameterPack ¶meterPack) -{ - // 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 - NodeManagers *manager = m_renderer->nodeManagers(); - - deactivateTexturesWithScope(TextureScopeMaterial); - // Update the uniforms with the correct texture unit id's - PackUniformHash &uniformValues = parameterPack.uniforms(); - - for (int i = 0; i < parameterPack.textures().size(); ++i) { - const ShaderParameterPack::NamedTexture &namedTex = parameterPack.textures().at(i); - // Given a Texture QNodeId, we retrieve the associated shared GLTexture - if (uniformValues.contains(namedTex.glslNameId)) { - GLTexture *t = manager->glTextureManager()->lookupResource(namedTex.texId); - if (t != nullptr) { - UniformValue &texUniform = uniformValues[namedTex.glslNameId]; - Q_ASSERT(texUniform.valueType() == UniformValue::TextureValue); - const int texUnit = activateTexture(TextureScopeMaterial, t); - texUniform.data<int>()[namedTex.uniformArrayIndex] = texUnit; - if (texUnit == -1) - return false; - } - } - } - - QOpenGLShaderProgram *shader = activeShader(); - - // TO DO: We could cache the binding points somehow and only do the binding when necessary - // for SSBO and UBO - - // Bind Shader Storage block to SSBO and update SSBO - const QVector<BlockToSSBO> blockToSSBOs = parameterPack.shaderStorageBuffers(); - int ssboIndex = 0; - for (const BlockToSSBO b : blockToSSBOs) { - Buffer *cpuBuffer = m_renderer->nodeManagers()->bufferManager()->lookupResource(b.m_bufferID); - GLBuffer *ssbo = glBufferForRenderBuffer(cpuBuffer, GLBuffer::ShaderStorageBuffer); - bindShaderStorageBlock(shader->programId(), b.m_blockIndex, ssboIndex); - // Needed to avoid conflict where the buffer would already - // be bound as a VertexArray - bindGLBuffer(ssbo, GLBuffer::ShaderStorageBuffer); - ssbo->bindBufferBase(this, ssboIndex++, 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 QVector<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, GLBuffer::UniformBuffer); - bindUniformBlock(shader->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 QVector<ShaderUniform> activeUniforms = parameterPack.submissionUniforms(); - - for (const ShaderUniform &uniform : activeUniforms) { - // We can use [] as we are sure the the uniform wouldn't - // be un activeUniforms if there wasn't a matching value - const UniformValue &v = values[uniform.m_nameId]; - - // skip invalid textures - if (v.valueType() == UniformValue::TextureValue && *v.constData<int>() == -1) - continue; - - applyUniform(uniform, v); - } - // if not all data is valid, the next frame will be rendered immediately - return true; -} - void GraphicsContext::readBuffer(GLenum mode) { m_glHelper->readBuffer(mode); @@ -1298,36 +699,6 @@ void GraphicsContext::drawBuffer(GLenum mode) m_glHelper->drawBuffer(mode); } -void GraphicsContext::enableAttribute(const VAOVertexAttribute &attr) -{ - // Bind buffer within the current VAO - GLBuffer *buf = m_renderer->nodeManagers()->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 GraphicsContext::disableAttribute(const GraphicsContext::VAOVertexAttribute &attr) -{ - QOpenGLShaderProgram *prog = activeShader(); - prog->disableAttributeArray(attr.location); -} - void GraphicsContext::applyUniform(const ShaderUniform &description, const UniformValue &v) { const UniformType type = m_glHelper->uniformTypeFromGLType(description.m_type); @@ -1439,282 +810,11 @@ void GraphicsContext::applyUniform(const ShaderUniform &description, const Unifo } } -// Note: needs to be called while VAO is bound -void GraphicsContext::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->nodeManagers()->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(); - } - - for (int i = 0; i < attrCount; i++) { - VAOVertexAttribute attr; - attr.bufferHandle = glBufferHandle; - attr.attributeType = attributeType; - attr.location = location + i; - attr.dataType = attributeDataType; - attr.byteOffset = attribute->byteOffset() + (i * attrCount * typeSize); - attr.vertexSize = attribute->vertexSize() / attrCount; - attr.byteStride = (attribute->byteStride() != 0) ? attribute->byteStride() : (attrCount * attrCount * typeSize); - attr.divisor = attribute->divisor(); - attr.shaderDataType = attributeDescription->m_type; - - enableAttribute(attr); - - // Save this in the current emulated VAO - if (m_currentVAO) - m_currentVAO->saveVertexAttribute(attr); - } -} - -void GraphicsContext::specifyIndices(Buffer *buffer) -{ - GLBuffer *buf = glBufferForRenderBuffer(buffer, GLBuffer::IndexBuffer); - 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->nodeManagers()->glBufferManager()->lookupHandle(buffer->peerId())); -} - -void GraphicsContext::updateBuffer(Buffer *buffer) -{ - const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(buffer->peerId()); - if (it != m_renderBufferHash.end()) - uploadDataToGLBuffer(buffer, m_renderer->nodeManagers()->glBufferManager()->data(it.value())); -} - -QByteArray GraphicsContext::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->nodeManagers()->glBufferManager()->data(it.value())); - return QByteArray(); -} - - - -void GraphicsContext::releaseBuffer(Qt3DCore::QNodeId bufferId) -{ - auto it = m_renderBufferHash.find(bufferId); - if (it != m_renderBufferHash.end()) { - HGLBuffer glBuffHandle = it.value(); - GLBuffer *glBuff = m_renderer->nodeManagers()->glBufferManager()->data(glBuffHandle); - - Q_ASSERT(glBuff); - // Destroy the GPU resource - glBuff->destroy(this); - // Destroy the GLBuffer instance - m_renderer->nodeManagers()->glBufferManager()->releaseResource(bufferId); - // Remove Id - HGLBuffer entry - m_renderBufferHash.erase(it); - } -} - -bool GraphicsContext::hasGLBufferForBuffer(Buffer *buffer) -{ - const QHash<Qt3DCore::QNodeId, HGLBuffer>::iterator it = m_renderBufferHash.find(buffer->peerId()); - return (it != m_renderBufferHash.end()); -} - -void GraphicsContext::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_renderTargetsSize[inputFboId].height(); - const GLint srcX0 = inputRect.left(); - const GLint srcY0 = inputFboHeight - (inputRect.top() + inputRect.height()); - const GLint srcX1 = srcX0 + inputRect.width(); - const GLint srcY1 = srcY0 + inputRect.height(); - - const int outputFboHeight = outputFboId == defaultFboId ? m_surfaceSize.height() : m_renderTargetsSize[outputFboId].height(); - const GLint dstX0 = outputRect.left(); - const GLint dstY0 = outputFboHeight - (outputRect.top() + outputRect.height()); - const GLint dstX1 = dstX0 + outputRect.width(); - const GLint dstY1 = dstY0 + outputRect.height(); - - //Get the last bounded framebuffers - const GLuint lastDrawFboId = boundFrameBufferObject(); - - // Activate input framebuffer for reading - bindFramebuffer(inputFboId, GraphicsHelperInterface::FBORead); - - // Activate output framebuffer for writing - bindFramebuffer(outputFboId, GraphicsHelperInterface::FBODraw); - - //Bind texture - if (!inputBufferIsDefault) - readBuffer(GL_COLOR_ATTACHMENT0 + inputAttachmentPoint); - - if (!outputBufferIsDefault) - drawBuffer(GL_COLOR_ATTACHMENT0 + outputAttachmentPoint); - - // 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 - bindFramebuffer(lastDrawFboId, GraphicsHelperInterface::FBOReadAndDraw); -} - void GraphicsContext::memoryBarrier(QMemoryBarrier::Operations barriers) { m_glHelper->memoryBarrier(barriers); } -GLBuffer *GraphicsContext::glBufferForRenderBuffer(Buffer *buf, GLBuffer::Type type) -{ - if (!m_renderBufferHash.contains(buf->peerId())) - m_renderBufferHash.insert(buf->peerId(), createGLBufferFor(buf, type)); - return m_renderer->nodeManagers()->glBufferManager()->data(m_renderBufferHash.value(buf->peerId())); -} - -HGLBuffer GraphicsContext::createGLBufferFor(Buffer *buffer, GLBuffer::Type type) -{ - GLBuffer *b = m_renderer->nodeManagers()->glBufferManager()->getOrCreateResource(buffer->peerId()); - // b.setUsagePattern(static_cast<QOpenGLBuffer::UsagePattern>(buffer->usage())); - Q_ASSERT(b); - if (!b->create(this)) - qCWarning(Render::Io) << Q_FUNC_INFO << "buffer creation failed"; - - if (!bindGLBuffer(b, type)) - qCWarning(Render::Io) << Q_FUNC_INFO << "buffer binding failed"; - - return m_renderer->nodeManagers()->glBufferManager()->lookupHandle(buffer->peerId()); -} - -bool GraphicsContext::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 GraphicsContext::uploadDataToGLBuffer(Buffer *buffer, GLBuffer *b, bool releaseBuffer) -{ - if (!bindGLBuffer(b, GLBuffer::ArrayBuffer)) // We're uploading, the type doesn't matter here - qCWarning(Render::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(Render::Io) << "uploaded buffer size=" << buffer->data().size(); -} - -QByteArray GraphicsContext::downloadDataFromGLBuffer(Buffer *buffer, GLBuffer *b) -{ - if (!bindGLBuffer(b, GLBuffer::ArrayBuffer)) // We're downloading, the type doesn't matter here - qCWarning(Render::Io) << Q_FUNC_INFO << "buffer bind failed"; - - QByteArray data = b->download(this, buffer->data().size()); - return data; -} - GLint GraphicsContext::elementType(GLint type) { switch (type) { @@ -1832,173 +932,6 @@ GLint GraphicsContext::glDataTypeFromAttributeDataType(QAttribute::VertexBaseTyp return GL_FLOAT; } -static 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; - } -} - -QImage GraphicsContext::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: - // unsupported format - Q_UNREACHABLE(); - return img; - } - - GLint samples = 0; - m_gl->functions()->glGetIntegerv(GL_SAMPLES, &samples); - if (samples > 0 && !m_glHelper->supportsFeature(GraphicsHelperInterface::BlitFramebuffer)) { - qWarning () << 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); - qWarning () << 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; -} - QT3D_UNIFORM_TYPE_IMPL(UniformType::Float, float, glUniform1fv) QT3D_UNIFORM_TYPE_IMPL(UniformType::Vec2, float, glUniform2fv) QT3D_UNIFORM_TYPE_IMPL(UniformType::Vec3, float, glUniform3fv) |