/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt RHI module ** ** $QT_BEGIN_LICENSE:LGPL3$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qrhigles2_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE /* OpenGL ES 2.0 (3.0) + GLSL 100/120 backend. Binding vertex attribute locations and decomposing uniform buffers into uniforms (as expected by the GLSL 1xx shader generated by SPIRV-Cross) are handled transparently to the application via the reflection data (QShaderDescription). Textures and buffers feature no special logic, it's all just glTexSubImage2D and glBufferSubData (with "dynamic" buffers set to GL_DYNAMIC_DRAW). The swapchain and the associated renderbuffer for depth-stencil will be dummies since we have no control over the underlying buffers here. It is up to the application to create the context and window with the appropriate QSurfaceFormat when it comes to depth/stencil and MSAA. While we try to keep this backend clean GLES 2.0, some GL(ES) 3.0 features like multisample renderbuffers and blits are used when available. */ QRhiGles2::QRhiGles2(QRhiInitParams *params) : ofr(this) { QRhiGles2InitParams *glparams = static_cast(params); ctx = glparams->context; maybeWindow = glparams->window; // may be null fallbackSurface = glparams->fallbackSurface; create(); } QRhiGles2::~QRhiGles2() { destroy(); } bool QRhiGles2::ensureContext(QSurface *surface) const { bool nativeWindowGone = false; if (surface && surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) { surface = fallbackSurface; nativeWindowGone = true; } if (!surface) surface = fallbackSurface; if (buffersSwapped) buffersSwapped = false; else if (!nativeWindowGone && QOpenGLContext::currentContext() == ctx && (surface == fallbackSurface || ctx->surface() == surface)) return true; if (!ctx->makeCurrent(surface)) { qWarning("QRhiGles2: Failed to make context current. Expect bad things to happen."); return false; } return true; } void QRhiGles2::create() { Q_ASSERT(ctx); Q_ASSERT(fallbackSurface); if (!ensureContext(maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments return; f = static_cast(ctx->extraFunctions()); const char *vendor = reinterpret_cast(f->glGetString(GL_VENDOR)); const char *renderer = reinterpret_cast(f->glGetString(GL_RENDERER)); const char *version = reinterpret_cast(f->glGetString(GL_VERSION)); if (vendor && renderer && version) qDebug("OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version); GLint n = 0; f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &n); supportedCompressedFormats.resize(n); if (n > 0) f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, supportedCompressedFormats.data()); caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample) && f->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit); } void QRhiGles2::destroy() { if (!f) return; ensureContext(); executeDeferredReleases(); f = nullptr; } // Strictly speaking this is not necessary since we could do the deletes in // release(). However, it is kind of nice that no release() ever requires a // current context and so GL calls are isolated to specific places (build, // beginFrame, endFrame) as much as possible. void QRhiGles2::executeDeferredReleases() { for (int i = releaseQueue.count() - 1; i >= 0; --i) { const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]); switch (e.type) { case QRhiGles2::DeferredReleaseEntry::Buffer: f->glDeleteBuffers(1, &e.buffer.buffer); break; case QRhiGles2::DeferredReleaseEntry::Pipeline: f->glDeleteProgram(e.pipeline.program); break; case QRhiGles2::DeferredReleaseEntry::Texture: f->glDeleteTextures(1, &e.texture.texture); break; case QRhiGles2::DeferredReleaseEntry::RenderBuffer: f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer); break; case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget: f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer); break; default: break; } releaseQueue.removeAt(i); } } QVector QRhiGles2::supportedSampleCounts() const { return { 1 }; } QRhiSwapChain *QRhiGles2::createSwapChain() { return new QGles2SwapChain(this); } QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size) { return new QGles2Buffer(this, type, usage, size); } int QRhiGles2::ubufAlignment() const { return 256; } bool QRhiGles2::isYUpInFramebuffer() const { return true; } QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const { return QMatrix4x4(); // identity } static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) { const bool srgb = flags.testFlag(QRhiTexture::sRGB); switch (format) { case QRhiTexture::BC1: return srgb ? 0x8C4C : 0x83F0; case QRhiTexture::BC3: return srgb ? 0x8C4E : 0x83F2; case QRhiTexture::BC5: return srgb ? 0x8C4F : 0x83F3; case QRhiTexture::ETC2_RGB8: return srgb ? 0x9275 : 0x9274; case QRhiTexture::ETC2_RGB8A1: return srgb ? 0x9277 : 0x9276; case QRhiTexture::ETC2_RGBA8: return srgb ? 0x9279 : 0x9278; case QRhiTexture::ASTC_4x4: return srgb ? 0x93D0 : 0x93B0; case QRhiTexture::ASTC_5x4: return srgb ? 0x93D1 : 0x93B1; case QRhiTexture::ASTC_5x5: return srgb ? 0x93D2 : 0x93B2; case QRhiTexture::ASTC_6x5: return srgb ? 0x93D3 : 0x93B3; case QRhiTexture::ASTC_6x6: return srgb ? 0x93D4 : 0x93B4; case QRhiTexture::ASTC_8x5: return srgb ? 0x93D5 : 0x93B5; case QRhiTexture::ASTC_8x6: return srgb ? 0x93D6 : 0x93B6; case QRhiTexture::ASTC_8x8: return srgb ? 0x93D7 : 0x93B7; case QRhiTexture::ASTC_10x5: return srgb ? 0x93D8 : 0x93B8; case QRhiTexture::ASTC_10x6: return srgb ? 0x93D9 : 0x93B9; case QRhiTexture::ASTC_10x8: return srgb ? 0x93DA : 0x93BA; case QRhiTexture::ASTC_10x10: return srgb ? 0x93DB : 0x93BB; case QRhiTexture::ASTC_12x10: return srgb ? 0x93DC : 0x93BC; case QRhiTexture::ASTC_12x12: return srgb ? 0x93DD : 0x93BD; default: return 0; // this is reachable, just return an invalid format } } bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const { if (isCompressedFormat(format)) return supportedCompressedFormats.contains(toGlCompressedTextureFormat(format, flags)); return true; } bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const { switch (feature) { case QRhi::MultisampleTexture: return false; case QRhi::MultisampleRenderBuffer: return caps.msaaRenderBuffer; default: Q_UNREACHABLE(); return false; } } QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) { return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags); } QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) { return new QGles2Texture(this, format, pixelSize, sampleCount, flags); } QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) { return new QGles2TextureRenderTarget(this, desc, flags); } QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline() { return new QGles2GraphicsPipeline(this); } QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings() { return new QGles2ShaderResourceBindings(this); } void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb) { Q_ASSERT(inPass); QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps); if (!srb) srb = psD->m_shaderResourceBindings; QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); const bool pipelineChanged = cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation; const bool srbChanged = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation; if (pipelineChanged || srbChanged) { cbD->currentPipeline = ps; cbD->currentPipelineGeneration = psD->generation; cbD->currentSrb = srb; cbD->currentSrbGeneration = srbD->generation; QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline; cmd.args.bindGraphicsPipeline.ps = ps; cmd.args.bindGraphicsPipeline.srb = srb; cmd.args.bindGraphicsPipeline.resOnlyChange = !pipelineChanged && srbChanged; cbD->commands.append(cmd); } } void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb, int startBinding, const QVector &bindings, QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) { Q_ASSERT(inPass); QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); for (int i = 0, ie = bindings.count(); i != ie; ++i) { QRhiBuffer *buf = bindings[i].first; quint32 ofs = bindings[i].second; QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf); Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer; cmd.args.bindVertexBuffer.ps = cbD->currentPipeline; cmd.args.bindVertexBuffer.buffer = bufD->buffer; cmd.args.bindVertexBuffer.offset = ofs; cmd.args.bindVertexBuffer.binding = startBinding + i; cbD->commands.append(cmd); } if (indexBuf) { QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf); Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer; cmd.args.bindIndexBuffer.buffer = ibufD->buffer; cmd.args.bindIndexBuffer.offset = indexOffset; cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; cbD->commands.append(cmd); } } void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) { Q_ASSERT(inPass); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::Viewport; cmd.args.viewport.x = viewport.r.x(); cmd.args.viewport.y = viewport.r.y(); cmd.args.viewport.w = viewport.r.z(); cmd.args.viewport.h = viewport.r.w(); cmd.args.viewport.d0 = viewport.minDepth; cmd.args.viewport.d1 = viewport.maxDepth; QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd); } void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) { Q_ASSERT(inPass); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::Scissor; cmd.args.scissor.x = scissor.r.x(); cmd.args.scissor.y = scissor.r.y(); cmd.args.scissor.w = scissor.r.z(); cmd.args.scissor.h = scissor.r.w(); QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd); } void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QVector4D &c) { Q_ASSERT(inPass); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BlendConstants; cmd.args.blendConstants.r = c.x(); cmd.args.blendConstants.g = c.y(); cmd.args.blendConstants.b = c.z(); cmd.args.blendConstants.a = c.w(); QRHI_RES(QGles2CommandBuffer, cb)->commands.append(cmd); } void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) { Q_ASSERT(inPass); QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::StencilRef; cmd.args.stencilRef.ref = refValue; cmd.args.stencilRef.ps = cbD->currentPipeline; cbD->commands.append(cmd); } void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) { Q_ASSERT(inPass); Q_UNUSED(instanceCount); // no instancing Q_UNUSED(firstInstance); QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::Draw; cmd.args.draw.ps = cbD->currentPipeline; cmd.args.draw.vertexCount = vertexCount; cmd.args.draw.firstVertex = firstVertex; cbD->commands.append(cmd); } void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) { Q_ASSERT(inPass); Q_UNUSED(instanceCount); // no instancing Q_UNUSED(firstInstance); Q_UNUSED(vertexOffset); // no glDrawElementsBaseVertex QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed; cmd.args.drawIndexed.ps = cbD->currentPipeline; cmd.args.drawIndexed.indexCount = indexCount; cmd.args.drawIndexed.firstIndex = firstIndex; cbD->commands.append(cmd); } QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain) { Q_ASSERT(!inFrame); QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); if (!ensureContext(swapChainD->surface)) return QRhi::FrameOpError; inFrame = true; currentSwapChain = swapChainD; executeDeferredReleases(); QRHI_RES(QGles2CommandBuffer, &swapChainD->cb)->resetState(); return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain) { Q_ASSERT(inFrame); inFrame = false; QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); if (!ensureContext(swapChainD->surface)) return QRhi::FrameOpError; executeCommandBuffer(&swapChainD->cb); currentSwapChain = nullptr; ++finishedFrameCount; if (swapChainD->surface) { ctx->swapBuffers(swapChainD->surface); buffersSwapped = true; } return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb) { Q_ASSERT(!inFrame); if (!ensureContext()) return QRhi::FrameOpError; inFrame = true; ofr.active = true; executeDeferredReleases(); ofr.cbWrapper.resetState(); *cb = &ofr.cbWrapper; return QRhi::FrameOpSuccess; } QRhi::FrameOpResult QRhiGles2::endOffscreenFrame() { Q_ASSERT(inFrame && ofr.active); inFrame = false; ofr.active = false; if (!ensureContext()) return QRhi::FrameOpError; executeCommandBuffer(&ofr.cbWrapper); ++finishedFrameCount; return QRhi::FrameOpSuccess;; } QRhi::FrameOpResult QRhiGles2::finish() { Q_ASSERT(!inPass); if (inFrame) { if (ofr.active) { Q_ASSERT(!currentSwapChain); if (!ensureContext()) return QRhi::FrameOpError; executeCommandBuffer(&ofr.cbWrapper); ofr.cbWrapper.resetCommands(); } else { Q_ASSERT(currentSwapChain); if (!ensureContext(currentSwapChain->surface)) return QRhi::FrameOpError; executeCommandBuffer(¤tSwapChain->cb); currentSwapChain->cb.resetCommands(); } } return QRhi::FrameOpSuccess; } void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size()); QGles2Buffer::ChangeRange &r(bufD->ubufChangeRange); if (r.changeBegin == -1 || u.offset < r.changeBegin) r.changeBegin = u.offset; if (r.changeEnd == -1 || u.offset + u.data.size() > r.changeEnd) r.changeEnd = u.offset + u.data.size(); } else { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; cmd.args.bufferSubData.target = bufD->target; cmd.args.bufferSubData.buffer = bufD->buffer; cmd.args.bufferSubData.offset = u.offset; cmd.args.bufferSubData.size = u.data.size(); cmd.args.bufferSubData.data = cbD->retainData(u.data); cbD->commands.append(cmd); } } for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size()); bufD->ubufChangeRange = { 0, u.data.size() }; } else { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; cmd.args.bufferSubData.target = bufD->target; cmd.args.bufferSubData.buffer = bufD->buffer; cmd.args.bufferSubData.offset = u.offset; cmd.args.bufferSubData.size = u.data.size(); cmd.args.bufferSubData.data = cbD->retainData(u.data); cbD->commands.append(cmd); } } for (const QRhiResourceUpdateBatchPrivate::TextureUpload &u : ud->textureUploads) { QGles2Texture *texD = QRHI_RES(QGles2Texture, u.tex); const bool isCompressed = isCompressedFormat(texD->m_format); const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap); const GLenum targetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; for (int layer = 0, layerCount = u.desc.layers.count(); layer != layerCount; ++layer) { const QRhiTextureUploadDescription::Layer &layerDesc(u.desc.layers[layer]); f->glBindTexture(targetBase + layer, texD->texture); for (int level = 0, levelCount = layerDesc.mipImages.count(); level != levelCount; ++level) { const QRhiTextureUploadDescription::Layer::MipLevel mipDesc(layerDesc.mipImages[level]); const int dx = mipDesc.destinationTopLeft.x(); const int dy = mipDesc.destinationTopLeft.y(); if (isCompressed && !mipDesc.compressedData.isEmpty()) { int w, h; if (mipDesc.sourceSize.isEmpty()) { w = qFloor(float(qMax(1, texD->m_pixelSize.width() >> level))); h = qFloor(float(qMax(1, texD->m_pixelSize.height() >> level))); } else { w = mipDesc.sourceSize.width(); h = mipDesc.sourceSize.height(); } if (texD->specified) { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage; cmd.args.compressedSubImage.target = targetBase + layer; cmd.args.compressedSubImage.level = level; cmd.args.compressedSubImage.dx = dx; cmd.args.compressedSubImage.dy = dy; cmd.args.compressedSubImage.w = w; cmd.args.compressedSubImage.h = h; cmd.args.compressedSubImage.glintformat = texD->glintformat; cmd.args.compressedSubImage.size = mipDesc.compressedData.size(); cmd.args.compressedSubImage.data = cbD->retainData(mipDesc.compressedData); cbD->commands.append(cmd); } else { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; cmd.args.compressedImage.target = targetBase + layer; cmd.args.compressedImage.level = level; cmd.args.compressedImage.glintformat = texD->glintformat; cmd.args.compressedImage.w = w; cmd.args.compressedImage.h = h; cmd.args.compressedImage.size = mipDesc.compressedData.size(); cmd.args.compressedImage.data = cbD->retainData(mipDesc.compressedData); cbD->commands.append(cmd); } } else { QImage img = mipDesc.image; int w = img.width(); int h = img.height(); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::SubImage; if (!mipDesc.sourceSize.isEmpty() || !mipDesc.sourceTopLeft.isNull()) { const int sx = mipDesc.sourceTopLeft.x(); const int sy = mipDesc.sourceTopLeft.y(); if (!mipDesc.sourceSize.isEmpty()) { w = mipDesc.sourceSize.width(); h = mipDesc.sourceSize.height(); } img = img.copy(sx, sy, w, h); } cmd.args.subImage.target = targetBase + layer; cmd.args.subImage.level = level; cmd.args.subImage.dx = dx; cmd.args.subImage.dy = dy; cmd.args.subImage.w = w; cmd.args.subImage.h = h; cmd.args.subImage.glformat = texD->glformat; cmd.args.subImage.gltype = texD->gltype; cmd.args.subImage.data = cbD->retainImage(img); cbD->commands.append(cmd); } } } texD->specified = true; } for (const QRhiResourceUpdateBatchPrivate::TextureCopy &u : ud->textureCopies) { Q_ASSERT(u.src && u.dst); QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src); QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst); const QSize size = u.desc.pixelSize.isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize; // source offset is bottom-left const float sx = u.desc.sourceTopLeft.x(); const float sy = srcD->m_pixelSize.height() - (u.desc.sourceTopLeft.y() + size.height() - 1); // destination offset is top-left const float dx = u.desc.destinationTopLeft.x(); const float dy = u.desc.destinationTopLeft.y(); const GLenum srcTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target; const GLenum srcTarget = srcTargetBase + u.desc.sourceLayer; const GLenum dstTargetBase = dstD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target; const GLenum dstTarget = dstTargetBase + u.desc.destinationLayer; QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::CopyTex; cmd.args.copyTex.srcTarget = srcTarget; cmd.args.copyTex.srcTexture = srcD->texture; cmd.args.copyTex.srcLevel = u.desc.sourceLevel; cmd.args.copyTex.srcX = sx; cmd.args.copyTex.srcY = sy; cmd.args.copyTex.dstTarget = dstTarget; cmd.args.copyTex.dstTexture = dstD->texture; cmd.args.copyTex.dstLevel = u.desc.destinationLevel; cmd.args.copyTex.dstX = dx; cmd.args.copyTex.dstY = dy; cmd.args.copyTex.w = size.width(); cmd.args.copyTex.h = size.height(); cbD->commands.append(cmd); } for (const QRhiResourceUpdateBatchPrivate::TextureResolve &u : ud->textureResolves) { Q_ASSERT(u.dst); Q_ASSERT(u.desc.sourceTexture || u.desc.sourceRenderBuffer); if (u.desc.sourceTexture) { qWarning("Multisample textures not supported, skipping resolve"); continue; } QGles2Texture *dstTexD = QRHI_RES(QGles2Texture, u.dst); QGles2RenderBuffer *srcRbD = QRHI_RES(QGles2RenderBuffer, u.desc.sourceRenderBuffer); if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) { qWarning("Resolve source and destination sizes do not match"); continue; } QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer; cmd.args.blitFromRb.renderbuffer = srcRbD->renderbuffer; cmd.args.blitFromRb.w = srcRbD->m_pixelSize.width(); cmd.args.blitFromRb.h = srcRbD->m_pixelSize.height(); cmd.args.blitFromRb.dst = dstTexD; cmd.args.blitFromRb.dstLayer = u.desc.destinationLayer; cmd.args.blitFromRb.dstLevel = u.desc.destinationLevel; cbD->commands.append(cmd); } for (const QRhiResourceUpdateBatchPrivate::TextureRead &u : ud->textureReadbacks) { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::ReadPixels; cmd.args.readPixels.result = u.result; cmd.args.readPixels.texture = QRHI_RES(QGles2Texture, u.rb.texture); cmd.args.readPixels.layer = u.rb.layer; cmd.args.readPixels.level = u.rb.level; cbD->commands.append(cmd); } for (const QRhiResourceUpdateBatchPrivate::TextureMipGen &u : ud->textureMipGens) { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::GenMip; cmd.args.genMip.tex = QRHI_RES(QGles2Texture, u.tex); cbD->commands.append(cmd); } ud->free(); } static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t) { switch (t) { case QRhiGraphicsPipeline::Triangles: return GL_TRIANGLES; case QRhiGraphicsPipeline::TriangleStrip: return GL_TRIANGLE_STRIP; case QRhiGraphicsPipeline::Lines: return GL_LINES; case QRhiGraphicsPipeline::LineStrip: return GL_LINE_STRIP; case QRhiGraphicsPipeline::Points: return GL_POINTS; default: Q_UNREACHABLE(); return GL_TRIANGLES; } } static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c) { switch (c) { case QRhiGraphicsPipeline::Front: return GL_FRONT; case QRhiGraphicsPipeline::Back: return GL_BACK; default: Q_UNREACHABLE(); return GL_BACK; } } static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f) { switch (f) { case QRhiGraphicsPipeline::CCW: return GL_CCW; case QRhiGraphicsPipeline::CW: return GL_CW; default: Q_UNREACHABLE(); return GL_CCW; } } static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f) { switch (f) { case QRhiGraphicsPipeline::Zero: return GL_ZERO; case QRhiGraphicsPipeline::One: return GL_ONE; case QRhiGraphicsPipeline::SrcColor: return GL_SRC_COLOR; case QRhiGraphicsPipeline::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR; case QRhiGraphicsPipeline::DstColor: return GL_DST_COLOR; case QRhiGraphicsPipeline::OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR; case QRhiGraphicsPipeline::SrcAlpha: return GL_SRC_ALPHA; case QRhiGraphicsPipeline::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA; case QRhiGraphicsPipeline::DstAlpha: return GL_DST_ALPHA; case QRhiGraphicsPipeline::OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA; case QRhiGraphicsPipeline::ConstantColor: return GL_CONSTANT_COLOR; case QRhiGraphicsPipeline::OneMinusConstantColor: return GL_ONE_MINUS_CONSTANT_COLOR; case QRhiGraphicsPipeline::ConstantAlpha: return GL_CONSTANT_ALPHA; case QRhiGraphicsPipeline::OneMinusConstantAlpha: return GL_ONE_MINUS_CONSTANT_ALPHA; case QRhiGraphicsPipeline::SrcAlphaSaturate: return GL_SRC_ALPHA_SATURATE; case QRhiGraphicsPipeline::Src1Color: Q_FALLTHROUGH(); case QRhiGraphicsPipeline::OneMinusSrc1Color: Q_FALLTHROUGH(); case QRhiGraphicsPipeline::Src1Alpha: Q_FALLTHROUGH(); case QRhiGraphicsPipeline::OneMinusSrc1Alpha: qWarning("Unsupported blend factor %d", f); return GL_ZERO; default: Q_UNREACHABLE(); return GL_ZERO; } } static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op) { switch (op) { case QRhiGraphicsPipeline::Add: return GL_FUNC_ADD; case QRhiGraphicsPipeline::Subtract: return GL_FUNC_SUBTRACT; case QRhiGraphicsPipeline::ReverseSubtract: return GL_FUNC_REVERSE_SUBTRACT; case QRhiGraphicsPipeline::Min: return GL_MIN; case QRhiGraphicsPipeline::Max: return GL_MAX; default: Q_UNREACHABLE(); return GL_FUNC_ADD; } } static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op) { switch (op) { case QRhiGraphicsPipeline::Never: return GL_NEVER; case QRhiGraphicsPipeline::Less: return GL_LESS; case QRhiGraphicsPipeline::Equal: return GL_EQUAL; case QRhiGraphicsPipeline::LessOrEqual: return GL_LEQUAL; case QRhiGraphicsPipeline::Greater: return GL_GREATER; case QRhiGraphicsPipeline::NotEqual: return GL_NOTEQUAL; case QRhiGraphicsPipeline::GreaterOrEqual: return GL_GEQUAL; case QRhiGraphicsPipeline::Always: return GL_ALWAYS; default: Q_UNREACHABLE(); return GL_ALWAYS; } } static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op) { switch (op) { case QRhiGraphicsPipeline::StencilZero: return GL_ZERO; case QRhiGraphicsPipeline::Keep: return GL_KEEP; case QRhiGraphicsPipeline::Replace: return GL_REPLACE; case QRhiGraphicsPipeline::IncrementAndClamp: return GL_INCR; case QRhiGraphicsPipeline::DecrementAndClamp: return GL_DECR; case QRhiGraphicsPipeline::Invert: return GL_INVERT; case QRhiGraphicsPipeline::IncrementAndWrap: return GL_INCR_WRAP; case QRhiGraphicsPipeline::DecrementAndWrap: return GL_DECR_WRAP; default: Q_UNREACHABLE(); return GL_KEEP; } } static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m) { switch (f) { case QRhiSampler::Nearest: if (m == QRhiSampler::None) return GL_NEAREST; else return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR; case QRhiSampler::Linear: if (m == QRhiSampler::None) return GL_LINEAR; else return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; default: Q_UNREACHABLE(); return GL_LINEAR; } } static inline GLenum toGlMagFilter(QRhiSampler::Filter f) { switch (f) { case QRhiSampler::Nearest: return GL_NEAREST; case QRhiSampler::Linear: return GL_LINEAR; default: Q_UNREACHABLE(); return GL_LINEAR; } } static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m) { switch (m) { case QRhiSampler::Repeat: return GL_REPEAT; case QRhiSampler::ClampToEdge: return GL_CLAMP_TO_EDGE; case QRhiSampler::Mirror: return GL_MIRRORED_REPEAT; case QRhiSampler::MirrorOnce: Q_FALLTHROUGH(); case QRhiSampler::Border: qWarning("Unsupported wrap mode %d", m); return GL_CLAMP_TO_EDGE; default: Q_UNREACHABLE(); return GL_CLAMP_TO_EDGE; } } void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) { QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); GLenum indexType = GL_UNSIGNED_SHORT; quint32 indexStride = sizeof(quint16); quint32 indexOffset = 0; for (const QGles2CommandBuffer::Command &cmd : qAsConst(cbD->commands)) { switch (cmd.cmd) { case QGles2CommandBuffer::Command::Viewport: f->glViewport(cmd.args.viewport.x, cmd.args.viewport.y, cmd.args.viewport.w, cmd.args.viewport.h); f->glDepthRangef(cmd.args.viewport.d0, cmd.args.viewport.d1); break; case QGles2CommandBuffer::Command::Scissor: f->glScissor(cmd.args.scissor.x, cmd.args.scissor.y, cmd.args.scissor.w, cmd.args.scissor.h); break; case QGles2CommandBuffer::Command::BlendConstants: f->glBlendColor(cmd.args.blendConstants.r, cmd.args.blendConstants.g, cmd.args.blendConstants.b, cmd.args.blendConstants.a); break; case QGles2CommandBuffer::Command::StencilRef: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps); if (psD) { f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask); f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask); } else { qWarning("No graphics pipeline active for setStencilRef; ignored"); } } break; case QGles2CommandBuffer::Command::BindVertexBuffer: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps); if (psD) { for (const QRhiVertexInputLayout::Attribute &a : psD->m_vertexInputLayout.attributes) { if (a.binding != cmd.args.bindVertexBuffer.binding) continue; // we do not support more than one vertex buffer f->glBindBuffer(GL_ARRAY_BUFFER, cmd.args.bindVertexBuffer.buffer); const int stride = psD->m_vertexInputLayout.bindings[a.binding].stride; int size = 1; GLenum type = GL_FLOAT; switch (a.format) { case QRhiVertexInputLayout::Attribute::Float4: type = GL_FLOAT; size = 4; break; case QRhiVertexInputLayout::Attribute::Float3: type = GL_FLOAT; size = 3; break; case QRhiVertexInputLayout::Attribute::Float2: type = GL_FLOAT; size = 2; break; case QRhiVertexInputLayout::Attribute::Float: type = GL_FLOAT; size = 1; break; case QRhiVertexInputLayout::Attribute::UNormByte4: type = GL_UNSIGNED_BYTE; size = 4; break; case QRhiVertexInputLayout::Attribute::UNormByte2: type = GL_UNSIGNED_BYTE; size = 2; break; case QRhiVertexInputLayout::Attribute::UNormByte: type = GL_UNSIGNED_BYTE; size = 1; break; default: break; } quint32 ofs = a.offset + cmd.args.bindVertexBuffer.offset; f->glVertexAttribPointer(a.location, size, type, GL_FALSE, stride, reinterpret_cast(quintptr(ofs))); f->glEnableVertexAttribArray(a.location); } } else { qWarning("No graphics pipeline active for setVertexInput; ignored"); } } break; case QGles2CommandBuffer::Command::BindIndexBuffer: indexType = cmd.args.bindIndexBuffer.type; indexStride = indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32); indexOffset = cmd.args.bindIndexBuffer.offset; f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cmd.args.bindIndexBuffer.buffer); break; case QGles2CommandBuffer::Command::Draw: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps); if (psD) f->glDrawArrays(psD->drawMode, cmd.args.draw.firstVertex, cmd.args.draw.vertexCount); else qWarning("No graphics pipeline active for draw; ignored"); } break; case QGles2CommandBuffer::Command::DrawIndexed: { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps); if (psD) { quint32 ofs = cmd.args.drawIndexed.firstIndex * indexStride + indexOffset; f->glDrawElements(psD->drawMode, cmd.args.drawIndexed.indexCount, indexType, reinterpret_cast(quintptr(ofs))); } else { qWarning("No graphics pipeline active for drawIndexed; ignored"); } } break; case QGles2CommandBuffer::Command::BindGraphicsPipeline: if (cmd.args.bindGraphicsPipeline.resOnlyChange) { setChangedUniforms(QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindGraphicsPipeline.ps), cmd.args.bindGraphicsPipeline.srb, false); } else { executeBindGraphicsPipeline(cmd.args.bindGraphicsPipeline.ps, cmd.args.bindGraphicsPipeline.srb); } break; case QGles2CommandBuffer::Command::BindFramebuffer: if (cmd.args.bindFramebuffer.rt) f->glBindFramebuffer(GL_FRAMEBUFFER, QRHI_RES(QGles2TextureRenderTarget, cmd.args.bindFramebuffer.rt)->framebuffer); else f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); break; case QGles2CommandBuffer::Command::Clear: f->glDisable(GL_SCISSOR_TEST); if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) { f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); f->glClearColor(cmd.args.clear.c[0], cmd.args.clear.c[1], cmd.args.clear.c[2], cmd.args.clear.c[3]); } if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) { f->glDepthMask(GL_TRUE); f->glClearDepthf(cmd.args.clear.d); } if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) { f->glStencilMask(GL_TRUE); f->glClearStencil(cmd.args.clear.s); } f->glClear(cmd.args.clear.mask); break; case QGles2CommandBuffer::Command::BufferSubData: f->glBindBuffer(cmd.args.bufferSubData.target, cmd.args.bufferSubData.buffer); f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size, cmd.args.bufferSubData.data); break; case QGles2CommandBuffer::Command::CopyTex: { GLuint fbo; f->glGenFramebuffers(1, &fbo); f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.copyTex.srcTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel); f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture); f->glCopyTexSubImage2D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel, cmd.args.copyTex.dstX, cmd.args.copyTex.dstY, cmd.args.copyTex.srcX, cmd.args.copyTex.srcY, cmd.args.copyTex.w, cmd.args.copyTex.h); f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); } break; case QGles2CommandBuffer::Command::ReadPixels: { // ### more formats QRhiReadbackResult *result = cmd.args.readPixels.result; QGles2Texture *texD = cmd.args.readPixels.texture; GLuint fbo = 0; if (texD) { result->pixelSize = texD->m_pixelSize; result->format = texD->m_format; f->glGenFramebuffers(1, &fbo); f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); const GLenum targetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; const GLenum target = targetBase + cmd.args.readPixels.layer; f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texD->texture, cmd.args.readPixels.level); } else { result->pixelSize = currentSwapChain->pixelSize; result->format = QRhiTexture::RGBA8; // readPixels handles multisample resolving implicitly } result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4); f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(), GL_RGBA, GL_UNSIGNED_BYTE, result->data.data()); if (fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); } if (result->completed) result->completed(); } break; case QGles2CommandBuffer::Command::SubImage: f->glTexSubImage2D(cmd.args.subImage.target, cmd.args.subImage.level, cmd.args.subImage.dx, cmd.args.subImage.dy, cmd.args.subImage.w, cmd.args.subImage.h, cmd.args.subImage.glformat, cmd.args.subImage.gltype, cmd.args.subImage.data); break; case QGles2CommandBuffer::Command::CompressedImage: f->glCompressedTexImage2D(cmd.args.compressedImage.target, cmd.args.compressedImage.level, cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, cmd.args.compressedImage.h, 0, cmd.args.compressedImage.size, cmd.args.compressedImage.data); break; case QGles2CommandBuffer::Command::CompressedSubImage: f->glCompressedTexSubImage2D(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level, cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy, cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h, cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data); break; case QGles2CommandBuffer::Command::BlitFromRenderbuffer: { GLuint fbo[2]; f->glGenFramebuffers(2, fbo); f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer); f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); QGles2Texture *texD = cmd.args.blitFromRb.dst; const GLenum targetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; const GLenum target = targetBase + cmd.args.blitFromRb.dstLayer; f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texD->texture, cmd.args.blitFromRb.dstLevel); f->glBlitFramebuffer(0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h, 0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h, GL_COLOR_BUFFER_BIT, GL_LINEAR); f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); } break; case QGles2CommandBuffer::Command::GenMip: f->glBindTexture(cmd.args.genMip.tex->target, cmd.args.genMip.tex->texture); f->glGenerateMipmap(cmd.args.genMip.tex->target); break; default: break; } } } void QRhiGles2::executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb) { QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps); if (psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) f->glEnable(GL_SCISSOR_TEST); else f->glDisable(GL_SCISSOR_TEST); if (psD->m_cullMode == QRhiGraphicsPipeline::None) { f->glDisable(GL_CULL_FACE); } else { f->glEnable(GL_CULL_FACE); f->glCullFace(toGlCullMode(psD->m_cullMode)); } f->glFrontFace(toGlFrontFace(psD->m_frontFace)); if (!psD->m_targetBlends.isEmpty()) { const QRhiGraphicsPipeline::TargetBlend &blend(psD->m_targetBlends.first()); // no MRT GLboolean wr = blend.colorWrite.testFlag(QRhiGraphicsPipeline::R); GLboolean wg = blend.colorWrite.testFlag(QRhiGraphicsPipeline::G); GLboolean wb = blend.colorWrite.testFlag(QRhiGraphicsPipeline::B); GLboolean wa = blend.colorWrite.testFlag(QRhiGraphicsPipeline::A); f->glColorMask(wr, wg, wb, wa); if (blend.enable) { f->glEnable(GL_BLEND); f->glBlendFuncSeparate(toGlBlendFactor(blend.srcColor), toGlBlendFactor(blend.dstColor), toGlBlendFactor(blend.srcAlpha), toGlBlendFactor(blend.dstAlpha)); f->glBlendEquationSeparate(toGlBlendOp(blend.opColor), toGlBlendOp(blend.opAlpha)); } else { f->glDisable(GL_BLEND); } } else { f->glDisable(GL_BLEND); } if (psD->m_depthTest) f->glEnable(GL_DEPTH_TEST); else f->glDisable(GL_DEPTH_TEST); if (psD->m_depthWrite) f->glDepthMask(GL_TRUE); else f->glDepthMask(GL_FALSE); f->glDepthFunc(toGlCompareOp(psD->m_depthOp)); if (psD->m_stencilTest) { f->glEnable(GL_STENCIL_TEST); f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), 0, psD->m_stencilReadMask); f->glStencilOpSeparate(GL_FRONT, toGlStencilOp(psD->m_stencilFront.failOp), toGlStencilOp(psD->m_stencilFront.depthFailOp), toGlStencilOp(psD->m_stencilFront.passOp)); f->glStencilMaskSeparate(GL_FRONT, psD->m_stencilWriteMask); f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), 0, psD->m_stencilReadMask); f->glStencilOpSeparate(GL_BACK, toGlStencilOp(psD->m_stencilBack.failOp), toGlStencilOp(psD->m_stencilBack.depthFailOp), toGlStencilOp(psD->m_stencilBack.passOp)); f->glStencilMaskSeparate(GL_BACK, psD->m_stencilWriteMask); } else { f->glDisable(GL_STENCIL_TEST); } f->glUseProgram(psD->program); // buffer data cannot change within the pass so this time is as good to update uniforms as any setChangedUniforms(psD, srb, false); } void QRhiGles2::setChangedUniforms(QGles2GraphicsPipeline *psD, QRhiShaderResourceBindings *srb, bool changedOnly) { QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) { const QRhiShaderResourceBinding &b(srbD->m_bindings[i]); QGles2ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]); switch (b.type) { case QRhiShaderResourceBinding::UniformBuffer: { QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b.ubuf.buf); if (changedOnly && bufD->ubufChangeRange.isNull()) // do not set again when nothing changed break; const QByteArray bufView = QByteArray::fromRawData(bufD->ubuf.constData() + b.ubuf.offset, b.ubuf.maybeSize ? b.ubuf.maybeSize : bufD->m_size); for (QGles2GraphicsPipeline::Uniform &uniform : psD->uniforms) { if (uniform.binding == b.binding && (!changedOnly || (uniform.offset >= uint(bufD->ubufChangeRange.changeBegin) && uniform.offset < uint(bufD->ubufChangeRange.changeEnd)))) { memcpy(uniform.data.data(), bufView.constData() + uniform.offset, uniform.data.size()); switch (uniform.type) { case QShaderDescription::Float: f->glUniform1f(uniform.glslLocation, *reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Vec2: f->glUniform2fv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Vec3: f->glUniform3fv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Vec4: f->glUniform4fv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Mat2: f->glUniformMatrix2fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Mat3: f->glUniformMatrix3fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Mat4: f->glUniformMatrix4fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Int: f->glUniform1i(uniform.glslLocation, *reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Int2: f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Int3: f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; case QShaderDescription::Int4: f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast(uniform.data.constData())); break; // ### more types default: break; } } } bufD->ubufChangeRange = QGles2Buffer::ChangeRange(); } break; case QRhiShaderResourceBinding::SampledTexture: { QGles2Texture *texD = QRHI_RES(QGles2Texture, b.stex.tex); QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b.stex.sampler); const bool textureChanged = QRHI_RES(QGles2Texture, b.stex.tex)->generation != bd.stex.texGeneration; if (textureChanged) bd.stex.texGeneration = QRHI_RES(QGles2Texture, b.stex.tex)->generation; const bool samplerChanged = QRHI_RES(QGles2Sampler, b.stex.sampler)->generation != bd.stex.samplerGeneration; if (samplerChanged) bd.stex.samplerGeneration = QRHI_RES(QGles2Sampler, b.stex.sampler)->generation; int texUnit = 0; for (QGles2GraphicsPipeline::Sampler &sampler : psD->samplers) { if (sampler.binding == b.binding) { f->glActiveTexture(GL_TEXTURE0 + texUnit); f->glBindTexture(texD->target, texD->texture); if (textureChanged || samplerChanged) { f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, samplerD->glminfilter); f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, samplerD->glmagfilter); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, samplerD->glwraps); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, samplerD->glwrapt); f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, samplerD->glwrapr); } f->glUniform1i(sampler.glslLocation, texUnit); ++texUnit; } } if (texUnit) f->glActiveTexture(GL_TEXTURE0); } break; default: Q_UNREACHABLE(); break; } } } void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { Q_ASSERT(inFrame && !inPass); enqueueResourceUpdates(cb, resourceUpdates); } void QRhiGles2::beginPass(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QRhiColorClearValue &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates) { Q_ASSERT(!inPass); if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); bool needsColorClear = true; QGles2BasicRenderTargetData *rtD = nullptr; QGles2CommandBuffer::Command fbCmd; fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer; switch (rt->type()) { case QRhiRenderTarget::RtRef: rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d; fbCmd.args.bindFramebuffer.rt = nullptr; break; case QRhiRenderTarget::RtTexture: { QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt); rtD = &rtTex->d; needsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents); fbCmd.args.bindFramebuffer.rt = rtTex; } break; default: Q_UNREACHABLE(); break; } cbD->commands.append(fbCmd); cbD->currentTarget = rt; Q_ASSERT(rtD->attCount == 1 || rtD->attCount == 2); QGles2CommandBuffer::Command clearCmd; clearCmd.cmd = QGles2CommandBuffer::Command::Clear; clearCmd.args.clear.mask = 0; if (rtD->attCount > 1) clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; if (needsColorClear) clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT; memcpy(clearCmd.args.clear.c, &colorClearValue.rgba, sizeof(float) * 4); clearCmd.args.clear.d = depthStencilClearValue.d; clearCmd.args.clear.s = depthStencilClearValue.s; cbD->commands.append(clearCmd); inPass = true; } void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { Q_ASSERT(inPass); inPass = false; QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); cbD->currentTarget = nullptr; if (resourceUpdates) enqueueResourceUpdates(cb, resourceUpdates); } QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size) : QRhiBuffer(rhi, type, usage, size) { } void QGles2Buffer::release() { if (!buffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Buffer; e.buffer.buffer = buffer; buffer = 0; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); } bool QGles2Buffer::build() { QRHI_RES_RHI(QRhiGles2); if (buffer) release(); if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) { // special since we do not support uniform blocks in this backend ubuf.resize(m_size); return true; } if (!rhiD->ensureContext()) return false; if (m_usage.testFlag(QRhiBuffer::VertexBuffer)) target = GL_ARRAY_BUFFER; if (m_usage.testFlag(QRhiBuffer::IndexBuffer)) target = GL_ELEMENT_ARRAY_BUFFER; rhiD->f->glGenBuffers(1, &buffer); rhiD->f->glBindBuffer(target, buffer); rhiD->f->glBufferData(target, m_size, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); return true; } QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags) : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags) { } void QGles2RenderBuffer::release() { if (!renderbuffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer; e.renderbuffer.renderbuffer = renderbuffer; renderbuffer = 0; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); } bool QGles2RenderBuffer::build() { QRHI_RES_RHI(QRhiGles2); if (renderbuffer) release(); if (m_flags.testFlag(ToBeUsedWithSwapChainOnly)) { if (m_type == DepthStencil) return true; qWarning("RenderBuffer: ToBeUsedWithSwapChainOnly is meaningless in combination with Color"); } if (!rhiD->ensureContext()) return false; samples = qMax(1, m_sampleCount); rhiD->f->glGenRenderbuffers(1, &renderbuffer); rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); switch (m_type) { case QRhiRenderBuffer::DepthStencil: if (rhiD->caps.msaaRenderBuffer) rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, m_pixelSize.width(), m_pixelSize.height()); else rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_pixelSize.width(), m_pixelSize.height()); break; case QRhiRenderBuffer::Color: if (rhiD->caps.msaaRenderBuffer) rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, m_pixelSize.width(), m_pixelSize.height()); else rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, m_pixelSize.width(), m_pixelSize.height()); break; default: Q_UNREACHABLE(); break; } return true; } QRhiTexture::Format QGles2RenderBuffer::backingFormat() const { return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; } QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int sampleCount, Flags flags) : QRhiTexture(rhi, format, pixelSize, sampleCount, flags) { } void QGles2Texture::release() { if (!texture) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Texture; e.texture.texture = texture; texture = 0; specified = false; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); } static inline bool isPowerOfTwo(int x) { // Assumption: x >= 1 return x == (x & -x); } bool QGles2Texture::build() { QRHI_RES_RHI(QRhiGles2); if (texture) release(); if (!rhiD->ensureContext()) return false; QSize size = m_pixelSize.isEmpty() ? QSize(16, 16) : m_pixelSize; if (!rhiD->f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures) && (!isPowerOfTwo(size.width()) || !isPowerOfTwo(size.height()))) { size = QSize(qNextPowerOfTwo(size.width()), qNextPowerOfTwo(size.height())); } const bool isCube = m_flags.testFlag(CubeMap); const bool hasMipMaps = m_flags.testFlag(MipMapped); const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1; const bool isCompressed = rhiD->isCompressedFormat(m_format); // ### more formats target = isCube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; glintformat = GL_RGBA; glformat = GL_RGBA; gltype = GL_UNSIGNED_BYTE; if (isCompressed) { glintformat = toGlCompressedTextureFormat(m_format, m_flags); if (!glintformat) { qWarning("Compressed format %d not mappable to GL compressed format", m_format); return false; } } rhiD->f->glGenTextures(1, &texture); if (!isCompressed) { if (hasMipMaps || isCube) { const GLenum targetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target; for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) { rhiD->f->glBindTexture(targetBase + layer, texture); for (int level = 0; level != mipLevelCount; ++level) { const int w = qFloor(float(qMax(1, size.width() >> level))); const int h = qFloor(float(qMax(1, size.height() >> level))); rhiD->f->glTexImage2D(targetBase + layer, level, glintformat, w, h, 0, glformat, gltype, nullptr); } } } else { rhiD->f->glBindTexture(target, texture); rhiD->f->glTexImage2D(target, 0, glintformat, size.width(), size.height(), 0, glformat, gltype, nullptr); } specified = true; } else { // Cannot use glCompressedTexImage2D without valid data, so defer. // Compressed textures will not be used as render targets so this is // not an issue. specified = false; } generation += 1; return true; } QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, AddressMode u, AddressMode v, AddressMode w) : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } void QGles2Sampler::release() { // nothing to do here } bool QGles2Sampler::build() { glminfilter = toGlMinFilter(m_minFilter, m_mipmapMode); glmagfilter = toGlMagFilter(m_magFilter); glwraps = toGlWrapMode(m_addressU); glwrapt = toGlWrapMode(m_addressV); glwrapr = toGlWrapMode(m_addressW); generation += 1; return true; } // dummy, no Vulkan-style RenderPass+Framebuffer concept here QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi) : QRhiRenderPassDescriptor(rhi) { } void QGles2RenderPassDescriptor::release() { // nothing to do here } QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi) : QRhiReferenceRenderTarget(rhi), d(rhi) { } void QGles2ReferenceRenderTarget::release() { // nothing to do here } QRhiRenderTarget::Type QGles2ReferenceRenderTarget::type() const { return RtRef; } QSize QGles2ReferenceRenderTarget::sizeInPixels() const { return d.pixelSize; } QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags) : QRhiTextureRenderTarget(rhi, desc, flags), d(rhi) { } void QGles2TextureRenderTarget::release() { if (!framebuffer) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget; e.textureRenderTarget.framebuffer = framebuffer; framebuffer = 0; QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); } QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor() { return new QGles2RenderPassDescriptor(rhi); } bool QGles2TextureRenderTarget::build() { QRHI_RES_RHI(QRhiGles2); if (framebuffer) release(); Q_ASSERT(!m_desc.colorAttachments.isEmpty()); Q_ASSERT(!m_desc.depthStencilBuffer || !m_desc.depthTexture); if (m_desc.colorAttachments.count() > 1) qWarning("QGles2TextureRenderTarget: Multiple color attachments are not supported"); if (m_desc.depthTexture) qWarning("QGles2TextureRenderTarget: Depth texture is not supported and will be ignored"); if (!rhiD->ensureContext()) return false; rhiD->f->glGenFramebuffers(1, &framebuffer); rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); const QRhiTextureRenderTargetDescription::ColorAttachment &colorAtt(m_desc.colorAttachments.constFirst()); QRhiTexture *texture = colorAtt.texture; QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer; Q_ASSERT(texture || renderBuffer); d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); d.attCount = 1; if (texture) { QGles2Texture *texD = QRHI_RES(QGles2Texture, texture); Q_ASSERT(texD->texture && texD->specified); const GLenum targetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; const GLenum target = targetBase + colorAtt.layer; rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texD->texture, colorAtt.level); d.pixelSize = texD->pixelSize(); } else { QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer); rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbD->renderbuffer); d.pixelSize = rbD->pixelSize(); } if (m_desc.depthStencilBuffer) { QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer); rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbD->renderbuffer); rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbD->renderbuffer); d.attCount += 1; } GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) { qWarning("Framebuffer incomplete: 0x%x", status); return false; } return true; } QRhiRenderTarget::Type QGles2TextureRenderTarget::type() const { return RtTexture; } QSize QGles2TextureRenderTarget::sizeInPixels() const { return d.pixelSize; } QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi) : QRhiShaderResourceBindings(rhi) { } void QGles2ShaderResourceBindings::release() { // nothing to do here } bool QGles2ShaderResourceBindings::build() { boundResourceData.resize(m_bindings.count()); for (int i = 0, ie = m_bindings.count(); i != ie; ++i) { const QRhiShaderResourceBinding &b(m_bindings[i]); BoundResourceData &bd(boundResourceData[i]); switch (b.type) { case QRhiShaderResourceBinding::UniformBuffer: // nothing, we do not track buffer generations break; case QRhiShaderResourceBinding::SampledTexture: // Start with values that will fail the first comparison for sure. bd.stex.texGeneration = UINT_MAX; // QRHI_RES(QGles2Texture, b.stex.tex)->generation; bd.stex.samplerGeneration = UINT_MAX; // QRHI_RES(QGles2Sampler, b.stex.sampler)->generation; break; default: Q_UNREACHABLE(); break; } } generation += 1; return true; } QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi) : QRhiGraphicsPipeline(rhi) { } void QGles2GraphicsPipeline::release() { if (!program) return; QRhiGles2::DeferredReleaseEntry e; e.type = QRhiGles2::DeferredReleaseEntry::Pipeline; e.pipeline.program = program; program = 0; uniforms.clear(); samplers.clear(); QRHI_RES_RHI(QRhiGles2); rhiD->releaseQueue.append(e); } bool QGles2GraphicsPipeline::build() { QRHI_RES_RHI(QRhiGles2); if (program) release(); if (!rhiD->ensureContext()) return false; drawMode = toGlTopology(m_topology); program = rhiD->f->glCreateProgram(); for (const QRhiGraphicsShaderStage &shaderStage : qAsConst(m_shaderStages)) { const bool isVertex = shaderStage.type == QRhiGraphicsShaderStage::Vertex; const bool isFragment = shaderStage.type == QRhiGraphicsShaderStage::Fragment; if (!isVertex && !isFragment) continue; GLuint shader = rhiD->f->glCreateShader(isVertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER); QBakedShader::ShaderSourceVersion ver; if (rhiD->ctx->isOpenGLES()) ver = { 100, QBakedShader::ShaderSourceVersion::GlslEs }; else ver = { 120 }; const QByteArray source = shaderStage.shader.shader({ QBakedShader::GlslShader, ver }).shader; if (source.isEmpty()) { qWarning() << "No GLSL" << ver.version << "shader code found in baked shader" << shaderStage.shader; return false; } const char *srcStr = source.constData(); const GLint srcLength = source.count(); rhiD->f->glShaderSource(shader, 1, &srcStr, &srcLength); rhiD->f->glCompileShader(shader); GLint compiled = 0; rhiD->f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLint infoLogLength = 0; rhiD->f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); QByteArray log; if (infoLogLength > 1) { GLsizei length = 0; log.resize(infoLogLength); rhiD->f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data()); } qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData()); return false; } rhiD->f->glAttachShader(program, shader); rhiD->f->glDeleteShader(shader); if (isVertex) vsDesc = shaderStage.shader.description(); else fsDesc = shaderStage.shader.description(); } for (auto inVar : vsDesc.inputVariables()) { const QByteArray name = inVar.name.toUtf8(); rhiD->f->glBindAttribLocation(program, inVar.location, name.constData()); } rhiD->f->glLinkProgram(program); GLint linked = 0; rhiD->f->glGetProgramiv(program, GL_LINK_STATUS, &linked); if (!linked) { GLint infoLogLength = 0; rhiD->f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); QByteArray log; if (infoLogLength > 1) { GLsizei length = 0; log.resize(infoLogLength); rhiD->f->glGetProgramInfoLog(program, infoLogLength, &length, log.data()); } qWarning("Failed to link shader program: %s", log.constData()); return false; } auto lookupUniforms = [this, rhiD](const QShaderDescription::UniformBlock &ub) { const QByteArray prefix = ub.structName.toUtf8() + '.'; for (const QShaderDescription::BlockVariable &blockMember : ub.members) { // ### no array support for now Uniform uniform; uniform.type = blockMember.type; const QByteArray name = prefix + blockMember.name.toUtf8(); uniform.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData()); if (uniform.glslLocation >= 0) { uniform.binding = ub.binding; uniform.offset = blockMember.offset; uniform.data.resize(blockMember.size); uniforms.append(uniform); } } }; for (const QShaderDescription::UniformBlock &ub : vsDesc.uniformBlocks()) lookupUniforms(ub); for (const QShaderDescription::UniformBlock &ub : fsDesc.uniformBlocks()) lookupUniforms(ub); auto lookupSamplers = [this, rhiD](const QShaderDescription::InOutVariable &v) { Sampler sampler; const QByteArray name = v.name.toUtf8(); sampler.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData()); if (sampler.glslLocation >= 0) { sampler.binding = v.binding; samplers.append(sampler); } }; for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers()) lookupSamplers(v); for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers()) lookupSamplers(v); generation += 1; return true; } QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi) : QRhiCommandBuffer(rhi) { resetState(); } void QGles2CommandBuffer::release() { Q_UNREACHABLE(); } QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi) : QRhiSwapChain(rhi), rt(rhi), cb(rhi) { } void QGles2SwapChain::release() { // nothing to do here } QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer() { return &cb; } QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget() { return &rt; } QSize QGles2SwapChain::surfacePixelSize() { Q_ASSERT(m_window); return m_window->size() * m_window->devicePixelRatio(); } QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor() { return new QGles2RenderPassDescriptor(rhi); } bool QGles2SwapChain::buildOrResize() { surface = m_window; m_currentPixelSize = surfacePixelSize(); pixelSize = m_currentPixelSize; rt.d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); rt.d.pixelSize = pixelSize; rt.d.attCount = m_depthStencil ? 2 : 1; return true; } QT_END_NAMESPACE