diff --git a/src/plugins/platforms/drm/CMakeLists.txt b/src/plugins/platforms/drm/CMakeLists.txt index 079902cf32..2f4f336f45 100644 --- a/src/plugins/platforms/drm/CMakeLists.txt +++ b/src/plugins/platforms/drm/CMakeLists.txt @@ -14,6 +14,7 @@ set(DRM_SOURCES egl_multi_backend.cpp abstract_egl_drm_backend.cpp dumb_swapchain.cpp + shadowbuffer.cpp ) if (HAVE_GBM) diff --git a/src/plugins/platforms/drm/egl_gbm_backend.cpp b/src/plugins/platforms/drm/egl_gbm_backend.cpp index ef47a550cd..06ba529549 100644 --- a/src/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/src/plugins/platforms/drm/egl_gbm_backend.cpp @@ -24,6 +24,7 @@ #include "linux_dmabuf.h" #include "dumb_swapchain.h" #include "kwineglutils_p.h" +#include "shadowbuffer.h" // kwin libs #include #include @@ -60,21 +61,12 @@ void EglGbmBackend::cleanupSurfaces() m_outputs.clear(); } -void EglGbmBackend::cleanupFramebuffer(Output &output) -{ - if (!output.render.framebuffer) { - return; - } - makeContextCurrent(output); - glDeleteTextures(1, &output.render.texture); - output.render.texture = 0; - glDeleteFramebuffers(1, &output.render.framebuffer); - output.render.framebuffer = 0; -} - void EglGbmBackend::cleanupOutput(Output &output) { - cleanupFramebuffer(output); + if (output.shadowBuffer) { + makeContextCurrent(output); + output.shadowBuffer = nullptr; + } if (output.eglSurface != EGL_NO_SURFACE) { // gbm buffers have to be released before destroying the egl surface @@ -205,7 +197,15 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput) output.eglSurface = eglSurface; output.gbmSurface = gbmSurface; - resetFramebuffer(output); + if (output.output->hardwareTransforms()) { + output.shadowBuffer = nullptr; + } else { + makeContextCurrent(output); + output.shadowBuffer = QSharedPointer::create(output.output->pixelSize()); + if (!output.shadowBuffer->isComplete()) { + return false; + } + } return true; } @@ -393,138 +393,14 @@ void EglGbmBackend::importFramebuffer(Output &output) const // TODO turn off output? } -const float vertices[] = { - -1.0f, 1.0f, - -1.0f, -1.0f, - 1.0f, -1.0f, - - -1.0f, 1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f, -}; - -const float texCoords[] = { - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - - 0.0f, 1.0f, - 1.0f, 0.0f, - 1.0f, 1.0f -}; - -bool EglGbmBackend::resetFramebuffer(Output &output) -{ - cleanupFramebuffer(output); - - if (output.output->hardwareTransforms()) { - // No need for an extra render target. - return true; - } - - makeContextCurrent(output); - - glGenFramebuffers(1, &output.render.framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, output.render.framebuffer); - GLRenderTarget::setKWinFramebuffer(output.render.framebuffer); - - glGenTextures(1, &output.render.texture); - glBindTexture(GL_TEXTURE_2D, output.render.texture); - - const QSize texSize = output.output->pixelSize(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texSize.width(), texSize.height(), - 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glBindTexture(GL_TEXTURE_2D, 0); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - output.render.texture, 0); - - if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - qCWarning(KWIN_DRM) << "Error: framebuffer not complete"; - return false; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - GLRenderTarget::setKWinFramebuffer(0); - - return true; -} - -void EglGbmBackend::initRenderTarget(Output &output) -{ - if (output.render.vbo) { - // Already initialized. - return; - } - QSharedPointer vbo(new GLVertexBuffer(KWin::GLVertexBuffer::Static)); - vbo->setData(6, 2, vertices, texCoords); - output.render.vbo = vbo; -} - void EglGbmBackend::renderFramebufferToSurface(Output &output) { - if (!output.render.framebuffer) { + if (!output.shadowBuffer) { // No additional render target. return; } makeContextCurrent(output); - - const auto size = output.output->modeSize(); - glViewport(0, 0, size.width(), size.height()); - - auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); - - QMatrix4x4 mvpMatrix; - - const DrmOutput *drmOutput = output.output; - switch (drmOutput->transform()) { - case DrmOutput::Transform::Normal: - case DrmOutput::Transform::Flipped: - break; - case DrmOutput::Transform::Rotated90: - case DrmOutput::Transform::Flipped90: - mvpMatrix.rotate(90, 0, 0, 1); - break; - case DrmOutput::Transform::Rotated180: - case DrmOutput::Transform::Flipped180: - mvpMatrix.rotate(180, 0, 0, 1); - break; - case DrmOutput::Transform::Rotated270: - case DrmOutput::Transform::Flipped270: - mvpMatrix.rotate(270, 0, 0, 1); - break; - } - switch (drmOutput->transform()) { - case DrmOutput::Transform::Flipped: - case DrmOutput::Transform::Flipped90: - case DrmOutput::Transform::Flipped180: - case DrmOutput::Transform::Flipped270: - mvpMatrix.scale(-1, 1); - break; - default: - break; - } - - shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); - - initRenderTarget(output); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - GLRenderTarget::setKWinFramebuffer(0); - glBindTexture(GL_TEXTURE_2D, output.render.texture); - output.render.vbo->render(GL_TRIANGLES); - ShaderManager::instance()->popShader(); - glBindTexture(GL_TEXTURE_2D, 0); -} - -void EglGbmBackend::prepareRenderFramebuffer(const Output &output) const -{ - // When render.framebuffer is 0 we may just reset to the screen framebuffer. - glBindFramebuffer(GL_FRAMEBUFFER, output.render.framebuffer); - GLRenderTarget::setKWinFramebuffer(output.render.framebuffer); + output.shadowBuffer->render(output.output); } bool EglGbmBackend::makeContextCurrent(const Output &output) const @@ -706,7 +582,9 @@ QRegion EglGbmBackend::beginFrame(int screenId) QRegion EglGbmBackend::prepareRenderingForOutput(Output &output) const { makeContextCurrent(output); - prepareRenderFramebuffer(output); + if (output.shadowBuffer) { + output.shadowBuffer->bind(); + } setViewport(output); if (supportsBufferAge()) { @@ -861,8 +739,8 @@ QSharedPointer EglGbmBackend::textureForOutput(AbstractOutput *abstra } DrmOutput *drmOutput = itOutput->output; - if (!drmOutput->hardwareTransforms()) { - const auto glTexture = QSharedPointer::create(itOutput->render.texture, GL_RGBA8, drmOutput->pixelSize()); + if (itOutput->shadowBuffer) { + const auto glTexture = QSharedPointer::create(itOutput->shadowBuffer->texture(), GL_RGBA8, drmOutput->pixelSize()); glTexture->setYInverted(true); return glTexture; } diff --git a/src/plugins/platforms/drm/egl_gbm_backend.h b/src/plugins/platforms/drm/egl_gbm_backend.h index 0a02474218..67017ef9b0 100644 --- a/src/plugins/platforms/drm/egl_gbm_backend.h +++ b/src/plugins/platforms/drm/egl_gbm_backend.h @@ -32,6 +32,7 @@ class DrmOutput; class GbmSurface; class GbmBuffer; class DumbSwapchain; +class ShadowBuffer; /** * @brief OpenGL Backend using Egl on a GBM surface. @@ -91,11 +92,7 @@ private: */ QList damageHistory; - struct { - GLuint framebuffer = 0; - GLuint texture = 0; - QSharedPointer vbo; - } render; + QSharedPointer shadowBuffer; KWaylandServer::SurfaceInterface *surfaceInterface = nullptr; ImportMode importMode = ImportMode::Dmabuf; @@ -108,9 +105,6 @@ private: bool makeContextCurrent(const Output &output) const; void setViewport(const Output &output) const; - bool resetFramebuffer(Output &output); - void initRenderTarget(Output &output); - void prepareRenderFramebuffer(const Output &output) const; void renderFramebufferToSurface(Output &output); QRegion prepareRenderingForOutput(Output &output) const; diff --git a/src/plugins/platforms/drm/egl_stream_backend.cpp b/src/plugins/platforms/drm/egl_stream_backend.cpp index ee4053b367..e824f9870c 100644 --- a/src/plugins/platforms/drm/egl_stream_backend.cpp +++ b/src/plugins/platforms/drm/egl_stream_backend.cpp @@ -25,6 +25,7 @@ #include "drm_gpu.h" #include "dumb_swapchain.h" #include "kwineglutils_p.h" +#include "shadowbuffer.h" #include #include @@ -88,13 +89,13 @@ EglStreamBackend::~EglStreamBackend() void EglStreamBackend::cleanupSurfaces() { - for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { + for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { cleanupOutput(*it); } m_outputs.clear(); } -void EglStreamBackend::cleanupOutput(const Output &o) +void EglStreamBackend::cleanupOutput(Output &o) { if (o.eglSurface != EGL_NO_SURFACE) { eglDestroySurface(eglDisplay(), o.eglSurface); @@ -102,6 +103,7 @@ void EglStreamBackend::cleanupOutput(const Output &o) if (o.eglStream != EGL_NO_STREAM_KHR) { pEglDestroyStreamKHR(eglDisplay(), o.eglStream); } + o.shadowBuffer = nullptr; } bool EglStreamBackend::initializeEgl() @@ -356,6 +358,15 @@ bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput) o.eglStream = stream; o.eglSurface = eglSurface; + + if (!drmOutput->hardwareTransforms()) { + makeContextCurrent(o); + o.shadowBuffer = QSharedPointer::create(o.output->pixelSize()); + if (!o.shadowBuffer->isComplete()) { + cleanupOutput(o); + return false; + } + } } else { QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() : drmOutput->modeSize(); o.dumbSwapchain = QSharedPointer::create(m_gpu, size); @@ -477,6 +488,9 @@ QRegion EglStreamBackend::beginFrame(int screenId) const Output &o = m_outputs.at(screenId); if (isPrimary()) { makeContextCurrent(o); + if (o.shadowBuffer) { + o.shadowBuffer->bind(); + } return o.output->geometry(); } else { return renderingBackend()->beginFrameForSecondaryGpu(o.output); @@ -496,6 +510,9 @@ void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, con QSharedPointer buffer; if (isPrimary()) { buffer = renderOutput.buffer; + if (renderOutput.shadowBuffer) { + renderOutput.shadowBuffer->render(renderOutput.output); + } if (!eglSwapBuffers(eglDisplay(), renderOutput.eglSurface)) { qCCritical(KWIN_DRM) << "eglSwapBuffers() failed:" << getEglErrorString(); frameFailed = true; diff --git a/src/plugins/platforms/drm/egl_stream_backend.h b/src/plugins/platforms/drm/egl_stream_backend.h index 6d37513c7e..55747196a2 100644 --- a/src/plugins/platforms/drm/egl_stream_backend.h +++ b/src/plugins/platforms/drm/egl_stream_backend.h @@ -20,6 +20,7 @@ namespace KWin class DrmOutput; class DrmDumbBuffer; class DumbSwapchain; +class ShadowBuffer; /** * @brief OpenGL Backend using Egl with an EGLDevice. @@ -65,13 +66,14 @@ private: QSharedPointer buffer; EGLSurface eglSurface = EGL_NO_SURFACE; EGLStreamKHR eglStream = EGL_NO_STREAM_KHR; + QSharedPointer shadowBuffer; // for operation as secondary GPU QSharedPointer dumbSwapchain; }; bool resetOutput(Output &output, DrmOutput *drmOutput); bool makeContextCurrent(const Output &output); - void cleanupOutput(const Output &output); + void cleanupOutput(Output &output); QVector m_outputs; KWaylandServer::EglStreamControllerInterface *m_eglStreamControllerInterface; diff --git a/src/plugins/platforms/drm/shadowbuffer.cpp b/src/plugins/platforms/drm/shadowbuffer.cpp new file mode 100644 index 0000000000..db3395f71d --- /dev/null +++ b/src/plugins/platforms/drm/shadowbuffer.cpp @@ -0,0 +1,131 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "shadowbuffer.h" + +#include "logging.h" +#include "drm_output.h" + +namespace KWin +{ + +static const float vertices[] = { + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + + -1.0f, 1.0f, + 1.0f, -1.0f, + 1.0f, 1.0f, +}; + +static const float texCoords[] = { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f +}; + +ShadowBuffer::ShadowBuffer(const QSize &size) +{ + glGenFramebuffers(1, &m_framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + GLRenderTarget::setKWinFramebuffer(m_framebuffer); + + glGenTextures(1, &m_texture); + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + qCCritical(KWIN_DRM) << "Error: framebuffer not complete!"; + return; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLRenderTarget::setKWinFramebuffer(0); + + m_vbo.reset(new GLVertexBuffer(KWin::GLVertexBuffer::Static)); + m_vbo->setData(6, 2, vertices, texCoords); +} + +ShadowBuffer::~ShadowBuffer() +{ + glDeleteTextures(1, &m_texture); + glDeleteFramebuffers(1, &m_framebuffer); +} + +void ShadowBuffer::render(DrmOutput *output) +{ + const auto size = output->modeSize(); + glViewport(0, 0, size.width(), size.height()); + auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + + QMatrix4x4 mvpMatrix; + + switch (output->transform()) { + case DrmOutput::Transform::Normal: + case DrmOutput::Transform::Flipped: + break; + case DrmOutput::Transform::Rotated90: + case DrmOutput::Transform::Flipped90: + mvpMatrix.rotate(90, 0, 0, 1); + break; + case DrmOutput::Transform::Rotated180: + case DrmOutput::Transform::Flipped180: + mvpMatrix.rotate(180, 0, 0, 1); + break; + case DrmOutput::Transform::Rotated270: + case DrmOutput::Transform::Flipped270: + mvpMatrix.rotate(270, 0, 0, 1); + break; + } + switch (output->transform()) { + case DrmOutput::Transform::Flipped: + case DrmOutput::Transform::Flipped90: + case DrmOutput::Transform::Flipped180: + case DrmOutput::Transform::Flipped270: + mvpMatrix.scale(-1, 1); + break; + default: + break; + } + + shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLRenderTarget::setKWinFramebuffer(0); + glBindTexture(GL_TEXTURE_2D, m_texture); + m_vbo->render(GL_TRIANGLES); + ShaderManager::instance()->popShader(); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void ShadowBuffer::bind() +{ + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + GLRenderTarget::setKWinFramebuffer(m_framebuffer); +} + +bool ShadowBuffer::isComplete() const +{ + return m_texture && m_framebuffer && m_vbo; +} + +int ShadowBuffer::texture() const +{ + return m_texture; +} + +} diff --git a/src/plugins/platforms/drm/shadowbuffer.h b/src/plugins/platforms/drm/shadowbuffer.h new file mode 100644 index 0000000000..bf48614536 --- /dev/null +++ b/src/plugins/platforms/drm/shadowbuffer.h @@ -0,0 +1,38 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include +#include + +namespace KWin +{ + +class DrmOutput; + +class ShadowBuffer +{ +public: + ShadowBuffer(const QSize &size); + ~ShadowBuffer(); + + bool isComplete() const; + + void bind(); + void render(DrmOutput *output); + + int texture() const; + +private: + GLuint m_texture; + GLuint m_framebuffer; + QScopedPointer m_vbo; +}; + +}