You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

315 lines
10 KiB

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glframebuffer.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "glplatform.h"
#include "gltexture.h"
#include "glutils.h"
#include "utils/common.h"
namespace KWin
{
void GLFramebuffer::initStatic()
{
if (GLPlatform::instance()->isGLES()) {
s_supportsPackedDepthStencil = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_OES_packed_depth_stencil"));
s_supportsDepth24 = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_OES_depth24"));
s_blitSupported = hasGLVersion(3, 0);
} else {
s_supportsPackedDepthStencil = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_packed_depth_stencil"));
s_blitSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_blit"));
}
}
void GLFramebuffer::cleanup()
{
Q_ASSERT(s_fbos.isEmpty());
s_blitSupported = false;
}
bool GLFramebuffer::blitSupported()
{
return s_blitSupported;
}
GLFramebuffer *GLFramebuffer::currentFramebuffer()
{
return s_fbos.isEmpty() ? nullptr : s_fbos.top();
}
void GLFramebuffer::pushFramebuffer(GLFramebuffer *fbo)
{
fbo->bind();
s_fbos.push(fbo);
}
GLFramebuffer *GLFramebuffer::popFramebuffer()
{
GLFramebuffer *ret = s_fbos.pop();
if (!s_fbos.isEmpty()) {
s_fbos.top()->bind();
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
return ret;
}
GLFramebuffer::GLFramebuffer()
: m_colorAttachment(nullptr)
{
}
static QString formatFramebufferStatus(GLenum status)
{
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
// An attachment is the wrong type / is invalid / has 0 width or height
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
// There are no images attached to the framebuffer
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
case GL_FRAMEBUFFER_UNSUPPORTED:
// A format or the combination of formats of the attachments is unsupported
return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED");
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
// Not all attached images have the same width and height
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
// The color attachments don't have the same format
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
// The attachments don't have the same number of samples
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
// The draw buffer is missing
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
// The read buffer is missing
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
default:
return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")");
}
}
GLFramebuffer::GLFramebuffer(GLTexture *colorAttachment, Attachment attachment)
: m_size(colorAttachment->size())
, m_colorAttachment(colorAttachment)
{
GLuint prevFbo = 0;
if (const GLFramebuffer *current = currentFramebuffer()) {
prevFbo = current->handle();
}
glGenFramebuffers(1, &m_handle);
glBindFramebuffer(GL_FRAMEBUFFER, m_handle);
initColorAttachment(colorAttachment);
if (attachment == Attachment::CombinedDepthStencil) {
initDepthStencilAttachment();
}
const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// We have an incomplete framebuffer, consider it invalid
qCCritical(KWIN_OPENGL) << "Invalid framebuffer status: " << formatFramebufferStatus(status);
glDeleteFramebuffers(1, &m_handle);
return;
}
m_valid = true;
}
GLFramebuffer::GLFramebuffer(GLuint handle, const QSize &size)
: m_handle(handle)
, m_size(size)
, m_valid(true)
, m_foreign(true)
, m_colorAttachment(nullptr)
{
}
GLFramebuffer::~GLFramebuffer()
{
if (!m_foreign && m_valid) {
glDeleteFramebuffers(1, &m_handle);
}
if (m_depthBuffer) {
glDeleteRenderbuffers(1, &m_depthBuffer);
}
if (m_stencilBuffer && m_stencilBuffer != m_depthBuffer) {
glDeleteRenderbuffers(1, &m_stencilBuffer);
}
}
bool GLFramebuffer::bind()
{
if (!valid()) {
qCCritical(KWIN_OPENGL) << "Can't enable invalid framebuffer object!";
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, handle());
glViewport(0, 0, m_size.width(), m_size.height());
return true;
}
void GLFramebuffer::initColorAttachment(GLTexture *colorAttachment)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
colorAttachment->target(), colorAttachment->texture(), 0);
}
void GLFramebuffer::initDepthStencilAttachment()
{
GLuint buffer = 0;
// Try to attach a depth/stencil combined attachment.
if (s_supportsPackedDepthStencil) {
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_depthBuffer = buffer;
m_stencilBuffer = buffer;
return;
}
}
// Try to attach a depth attachment separately.
GLenum depthFormat;
if (GLPlatform::instance()->isGLES()) {
if (s_supportsDepth24) {
depthFormat = GL_DEPTH_COMPONENT24;
} else {
depthFormat = GL_DEPTH_COMPONENT16;
}
} else {
depthFormat = GL_DEPTH_COMPONENT;
}
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, depthFormat, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_depthBuffer = buffer;
}
// Try to attach a stencil attachment separately.
GLenum stencilFormat;
if (GLPlatform::instance()->isGLES()) {
stencilFormat = GL_STENCIL_INDEX8;
} else {
stencilFormat = GL_STENCIL_INDEX;
}
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, stencilFormat, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_RENDERBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_stencilBuffer = buffer;
}
}
void GLFramebuffer::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter, bool flipX, bool flipY)
{
if (!valid()) {
return;
}
const GLFramebuffer *top = currentFramebuffer();
GLFramebuffer::pushFramebuffer(this);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, handle());
glBindFramebuffer(GL_READ_FRAMEBUFFER, top->handle());
const QRect s = source.isNull() ? QRect(QPoint(0, 0), top->size()) : source;
const QRect d = destination.isNull() ? QRect(QPoint(0, 0), size()) : destination;
GLuint srcX0 = s.x();
GLuint srcY0 = top->size().height() - (s.y() + s.height());
GLuint srcX1 = s.x() + s.width();
GLuint srcY1 = top->size().height() - s.y();
if (flipX) {
std::swap(srcX0, srcX1);
}
if (flipY) {
std::swap(srcY0, srcY1);
}
const GLuint dstX0 = d.x();
const GLuint dstY0 = m_size.height() - (d.y() + d.height());
const GLuint dstX1 = d.x() + d.width();
const GLuint dstY1 = m_size.height() - d.y();
glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, filter);
GLFramebuffer::popFramebuffer();
}
bool GLFramebuffer::blitFromRenderTarget(const RenderTarget &sourceRenderTarget, const RenderViewport &sourceViewport, const QRect &source, const QRect &destination)
{
OutputTransform transform = sourceRenderTarget.texture() ? sourceRenderTarget.texture()->contentTransform() : OutputTransform();
// TODO: Also blit if rotated 180 degrees, it's equivalent to flipping both x and y axis
const bool normal = transform == OutputTransform::Normal;
const bool mirrorX = transform == OutputTransform::FlipX;
const bool mirrorY = transform == OutputTransform::FlipY;
if ((normal || mirrorX || mirrorY) && blitSupported()) {
// either no transformation or flipping only
blitFromFramebuffer(sourceViewport.mapToRenderTarget(source), destination, GL_LINEAR, mirrorX, mirrorY);
return true;
} else {
const auto texture = sourceRenderTarget.texture();
if (!texture) {
// rotations aren't possible without a texture
return false;
}
GLFramebuffer::pushFramebuffer(this);
QMatrix4x4 mat;
mat.ortho(QRectF(QPointF(), size()));
// GLTexture::render renders with origin (0, 0), move it to the correct place
mat.translate(destination.x(), destination.y());
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
texture->render(sourceViewport.mapToRenderTargetTexture(source), infiniteRegion(), destination.size(), 1);
GLFramebuffer::popFramebuffer();
return true;
}
}
GLTexture *GLFramebuffer::colorAttachment() const
{
return m_colorAttachment;
}
}