File indexing completed on 2024-09-15 04:51:15

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
0006     SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "glframebuffer.h"
0012 #include "core/rendertarget.h"
0013 #include "core/renderviewport.h"
0014 #include "glplatform.h"
0015 #include "gltexture.h"
0016 #include "glutils.h"
0017 #include "utils/common.h"
0018 
0019 namespace KWin
0020 {
0021 
0022 void GLFramebuffer::initStatic()
0023 {
0024     if (GLPlatform::instance()->isGLES()) {
0025         s_supported = true;
0026         s_supportsPackedDepthStencil = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_OES_packed_depth_stencil"));
0027         s_supportsDepth24 = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_OES_depth24"));
0028         s_blitSupported = hasGLVersion(3, 0);
0029     } else {
0030         s_supported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_object"));
0031         s_supportsPackedDepthStencil = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_packed_depth_stencil"));
0032         s_blitSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_blit"));
0033     }
0034 }
0035 
0036 void GLFramebuffer::cleanup()
0037 {
0038     Q_ASSERT(s_fbos.isEmpty());
0039     s_supported = false;
0040     s_blitSupported = false;
0041 }
0042 
0043 bool GLFramebuffer::blitSupported()
0044 {
0045     return s_blitSupported;
0046 }
0047 
0048 GLFramebuffer *GLFramebuffer::currentFramebuffer()
0049 {
0050     return s_fbos.isEmpty() ? nullptr : s_fbos.top();
0051 }
0052 
0053 void GLFramebuffer::pushFramebuffer(GLFramebuffer *fbo)
0054 {
0055     fbo->bind();
0056     s_fbos.push(fbo);
0057 }
0058 
0059 GLFramebuffer *GLFramebuffer::popFramebuffer()
0060 {
0061     GLFramebuffer *ret = s_fbos.pop();
0062     if (!s_fbos.isEmpty()) {
0063         s_fbos.top()->bind();
0064     } else {
0065         glBindFramebuffer(GL_FRAMEBUFFER, 0);
0066     }
0067 
0068     return ret;
0069 }
0070 
0071 GLFramebuffer::GLFramebuffer()
0072     : m_colorAttachment(nullptr)
0073 {
0074 }
0075 
0076 static QString formatFramebufferStatus(GLenum status)
0077 {
0078     switch (status) {
0079     case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
0080         // An attachment is the wrong type / is invalid / has 0 width or height
0081         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
0082     case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
0083         // There are no images attached to the framebuffer
0084         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
0085     case GL_FRAMEBUFFER_UNSUPPORTED:
0086         // A format or the combination of formats of the attachments is unsupported
0087         return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED");
0088     case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
0089         // Not all attached images have the same width and height
0090         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT");
0091     case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
0092         // The color attachments don't have the same format
0093         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT");
0094     case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
0095         // The attachments don't have the same number of samples
0096         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
0097     case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
0098         // The draw buffer is missing
0099         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
0100     case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
0101         // The read buffer is missing
0102         return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
0103     default:
0104         return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")");
0105     }
0106 }
0107 
0108 GLFramebuffer::GLFramebuffer(GLTexture *colorAttachment, Attachment attachment)
0109     : m_size(colorAttachment->size())
0110     , m_colorAttachment(colorAttachment)
0111 {
0112     if (!s_supported) {
0113         qCCritical(KWIN_OPENGL) << "Framebuffer objects aren't supported!";
0114         return;
0115     }
0116 
0117     GLuint prevFbo = 0;
0118     if (const GLFramebuffer *current = currentFramebuffer()) {
0119         prevFbo = current->handle();
0120     }
0121 
0122     glGenFramebuffers(1, &m_handle);
0123     glBindFramebuffer(GL_FRAMEBUFFER, m_handle);
0124 
0125     initColorAttachment(colorAttachment);
0126     if (attachment == Attachment::CombinedDepthStencil) {
0127         initDepthStencilAttachment();
0128     }
0129 
0130     const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
0131     glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
0132 
0133     if (status != GL_FRAMEBUFFER_COMPLETE) {
0134         // We have an incomplete framebuffer, consider it invalid
0135         qCCritical(KWIN_OPENGL) << "Invalid framebuffer status: " << formatFramebufferStatus(status);
0136         glDeleteFramebuffers(1, &m_handle);
0137         return;
0138     }
0139 
0140     m_valid = true;
0141 }
0142 
0143 GLFramebuffer::GLFramebuffer(GLuint handle, const QSize &size)
0144     : m_handle(handle)
0145     , m_size(size)
0146     , m_valid(true)
0147     , m_foreign(true)
0148     , m_colorAttachment(nullptr)
0149 {
0150 }
0151 
0152 GLFramebuffer::~GLFramebuffer()
0153 {
0154     if (!m_foreign && m_valid) {
0155         glDeleteFramebuffers(1, &m_handle);
0156     }
0157     if (m_depthBuffer) {
0158         glDeleteRenderbuffers(1, &m_depthBuffer);
0159     }
0160     if (m_stencilBuffer && m_stencilBuffer != m_depthBuffer) {
0161         glDeleteRenderbuffers(1, &m_stencilBuffer);
0162     }
0163 }
0164 
0165 bool GLFramebuffer::bind()
0166 {
0167     if (!valid()) {
0168         qCCritical(KWIN_OPENGL) << "Can't enable invalid framebuffer object!";
0169         return false;
0170     }
0171 
0172     glBindFramebuffer(GL_FRAMEBUFFER, handle());
0173     glViewport(0, 0, m_size.width(), m_size.height());
0174 
0175     return true;
0176 }
0177 
0178 void GLFramebuffer::initColorAttachment(GLTexture *colorAttachment)
0179 {
0180     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
0181                            colorAttachment->target(), colorAttachment->texture(), 0);
0182 }
0183 
0184 void GLFramebuffer::initDepthStencilAttachment()
0185 {
0186     GLuint buffer = 0;
0187 
0188     // Try to attach a depth/stencil combined attachment.
0189     if (s_supportsPackedDepthStencil) {
0190         glGenRenderbuffers(1, &buffer);
0191         glBindRenderbuffer(GL_RENDERBUFFER, buffer);
0192         glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_size.width(), m_size.height());
0193         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
0194         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
0195 
0196         if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
0197             glDeleteRenderbuffers(1, &buffer);
0198         } else {
0199             m_depthBuffer = buffer;
0200             m_stencilBuffer = buffer;
0201             return;
0202         }
0203     }
0204 
0205     // Try to attach a depth attachment separately.
0206     GLenum depthFormat;
0207     if (GLPlatform::instance()->isGLES()) {
0208         if (s_supportsDepth24) {
0209             depthFormat = GL_DEPTH_COMPONENT24;
0210         } else {
0211             depthFormat = GL_DEPTH_COMPONENT16;
0212         }
0213     } else {
0214         depthFormat = GL_DEPTH_COMPONENT;
0215     }
0216 
0217     glGenRenderbuffers(1, &buffer);
0218     glBindRenderbuffer(GL_RENDERBUFFER, buffer);
0219     glRenderbufferStorage(GL_RENDERBUFFER, depthFormat, m_size.width(), m_size.height());
0220     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
0221     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
0222         glDeleteRenderbuffers(1, &buffer);
0223     } else {
0224         m_depthBuffer = buffer;
0225     }
0226 
0227     // Try to attach a stencil attachment separately.
0228     GLenum stencilFormat;
0229     if (GLPlatform::instance()->isGLES()) {
0230         stencilFormat = GL_STENCIL_INDEX8;
0231     } else {
0232         stencilFormat = GL_STENCIL_INDEX;
0233     }
0234 
0235     glGenRenderbuffers(1, &buffer);
0236     glBindRenderbuffer(GL_RENDERBUFFER, buffer);
0237     glRenderbufferStorage(GL_RENDERBUFFER, stencilFormat, m_size.width(), m_size.height());
0238     glFramebufferRenderbuffer(GL_RENDERBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
0239     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
0240         glDeleteRenderbuffers(1, &buffer);
0241     } else {
0242         m_stencilBuffer = buffer;
0243     }
0244 }
0245 
0246 void GLFramebuffer::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter, bool flipX, bool flipY)
0247 {
0248     if (!valid()) {
0249         return;
0250     }
0251 
0252     const GLFramebuffer *top = currentFramebuffer();
0253     GLFramebuffer::pushFramebuffer(this);
0254 
0255     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, handle());
0256     glBindFramebuffer(GL_READ_FRAMEBUFFER, top->handle());
0257 
0258     const QRect s = source.isNull() ? QRect(QPoint(0, 0), top->size()) : source;
0259     const QRect d = destination.isNull() ? QRect(QPoint(0, 0), size()) : destination;
0260 
0261     GLuint srcX0 = s.x();
0262     GLuint srcY0 = top->size().height() - (s.y() + s.height());
0263     GLuint srcX1 = s.x() + s.width();
0264     GLuint srcY1 = top->size().height() - s.y();
0265     if (flipX) {
0266         std::swap(srcX0, srcX1);
0267     }
0268     if (flipY) {
0269         std::swap(srcY0, srcY1);
0270     }
0271 
0272     const GLuint dstX0 = d.x();
0273     const GLuint dstY0 = m_size.height() - (d.y() + d.height());
0274     const GLuint dstX1 = d.x() + d.width();
0275     const GLuint dstY1 = m_size.height() - d.y();
0276 
0277     glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, filter);
0278 
0279     GLFramebuffer::popFramebuffer();
0280 }
0281 
0282 bool GLFramebuffer::blitFromRenderTarget(const RenderTarget &sourceRenderTarget, const RenderViewport &sourceViewport, const QRect &source, const QRect &destination)
0283 {
0284     OutputTransform transform = sourceRenderTarget.texture() ? sourceRenderTarget.texture()->contentTransform() : OutputTransform();
0285 
0286     // TODO: Also blit if rotated 180 degrees, it's equivalent to flipping both x and y axis
0287     const bool normal = transform == OutputTransform::Normal;
0288     const bool mirrorX = transform == OutputTransform::FlipX;
0289     const bool mirrorY = transform == OutputTransform::FlipY;
0290     if ((normal || mirrorX || mirrorY) && blitSupported()) {
0291         // either no transformation or flipping only
0292         blitFromFramebuffer(sourceViewport.mapToRenderTarget(source), destination, GL_LINEAR, mirrorX, mirrorY);
0293         return true;
0294     } else {
0295         const auto texture = sourceRenderTarget.texture();
0296         if (!texture) {
0297             // rotations aren't possible without a texture
0298             return false;
0299         }
0300 
0301         GLFramebuffer::pushFramebuffer(this);
0302 
0303         QMatrix4x4 mat;
0304         mat.ortho(QRectF(QPointF(), size()));
0305         // GLTexture::render renders with origin (0, 0), move it to the correct place
0306         mat.translate(destination.x(), destination.y());
0307 
0308         ShaderBinder binder(ShaderTrait::MapTexture);
0309         binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
0310 
0311         texture->render(sourceViewport.mapToRenderTargetTexture(source), infiniteRegion(), destination.size(), 1);
0312 
0313         GLFramebuffer::popFramebuffer();
0314         return true;
0315     }
0316 }
0317 
0318 GLTexture *GLFramebuffer::colorAttachment() const
0319 {
0320     return m_colorAttachment;
0321 }
0322 
0323 }