From 2a64755b764e2239f249f347eb4bc545356a9a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 23 Apr 2015 09:55:49 +0200 Subject: [PATCH] Improve the rendering per output in SceneOpenGL/EglGbmBackend The complete rendering is now splitted per output including present which means that we only need to make the context per output current once per rendering. Unfortunately our architecture does not properly support gathering the damage for multiple outputs. In fact the damage information is lost after the first output got rendered. Thus we currently only support buffer age for the first output, on other outputs full repaints are caused. --- egl_gbm_backend.cpp | 78 +++++++++++++++++++++++++++++------------ egl_gbm_backend.h | 8 ++++- scene_opengl.cpp | 84 ++++++++++++++++++++++++++++----------------- scene_opengl.h | 3 +- 4 files changed, 118 insertions(+), 55 deletions(-) diff --git a/egl_gbm_backend.cpp b/egl_gbm_backend.cpp index f6531ba545..7fa58f57d0 100644 --- a/egl_gbm_backend.cpp +++ b/egl_gbm_backend.cpp @@ -234,17 +234,23 @@ void EglGbmBackend::present() { for (auto &o: m_outputs) { makeContextCurrent(o); - eglSwapBuffers(eglDisplay(), o.eglSurface); - auto oldBuffer = o.buffer; - o.buffer = m_backend->createBuffer(o.gbmSurface); - m_backend->present(o.buffer, o.output); - delete oldBuffer; - if (supportsBufferAge()) { - eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); - } + presentOnOutput(o); } } +void EglGbmBackend::presentOnOutput(EglGbmBackend::Output &o) +{ + eglSwapBuffers(eglDisplay(), o.eglSurface); + auto oldBuffer = o.buffer; + o.buffer = m_backend->createBuffer(o.gbmSurface); + m_backend->present(o.buffer, o.output); + delete oldBuffer; + if (supportsBufferAge()) { + eglQuerySurface(eglDisplay(), o.eglSurface, EGL_BUFFER_AGE_EXT, &o.bufferAge); + } + +} + void EglGbmBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) @@ -258,24 +264,40 @@ SceneOpenGL::TexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGL::Te QRegion EglGbmBackend::prepareRenderingFrame() { - QRegion repaint; - if (supportsBufferAge()) { - for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { - repaint = repaint.united(accumulatedDamageHistory((*it).bufferAge)); - } - } startRenderTimer(); - return repaint; + return QRegion(); } -void EglGbmBackend::prepareRenderingForScreen(int screenId) +QRegion EglGbmBackend::prepareRenderingForScreen(int screenId) { - makeContextCurrent(m_outputs.at(screenId)); + const Output &o = m_outputs.at(screenId); + makeContextCurrent(o); + if (supportsBufferAge()) { + QRegion region; + + // Note: An age of zero means the buffer contents are undefined + if (o.bufferAge > 0 && o.bufferAge <= o.damageHistory.count()) { + for (int i = 0; i < o.bufferAge - 1; i++) + region |= o.damageHistory[i]; + } else { + region = o.output->geometry(); + } + + return region; + } + return QRegion(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { - if (damagedRegion.isEmpty()) { + Q_UNUSED(renderedRegion) + Q_UNUSED(damagedRegion) +} + +void EglGbmBackend::endRenderingFrameForScreen(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) +{ + Output &o = m_outputs[screenId]; + if (damagedRegion.intersected(o.output->geometry()).isEmpty() && screenId == 0) { // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back @@ -284,7 +306,7 @@ void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegi // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. - if (!renderedRegion.isEmpty()) + if (!renderedRegion.intersected(o.output->geometry()).isEmpty()) glFlush(); for (auto &o: m_outputs) { @@ -292,11 +314,23 @@ void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegi } return; } - present(); + presentOnOutput(o); // Save the damaged region to history - if (supportsBufferAge()) - addToDamageHistory(damagedRegion); + // Note: damage history is only collected for the first screen. For any other screen full repaints + // are triggered. This is due to a limitation in Scene::paintGenericScreen which resets the Toplevel's + // repaint. So multiple calls to Scene::paintScreen as it's done in multi-output rendering only + // have correct damage information for the first screen. If we try to track damage nevertheless, + // it creates artifacts. So for the time being we work around the problem by only supporting buffer + // age on the first output. To properly support buffer age on all outputs the rendering needs to + // be refactored in general. + if (supportsBufferAge() && screenId == 0) { + if (o.damageHistory.count() > 10) { + o.damageHistory.removeLast(); + } + + o.damageHistory.prepend(damagedRegion.intersected(o.output->geometry())); + } } bool EglGbmBackend::usesOverlayWindow() const diff --git a/egl_gbm_backend.h b/egl_gbm_backend.h index 25b3e5f9e6..8485d29c8f 100644 --- a/egl_gbm_backend.h +++ b/egl_gbm_backend.h @@ -44,9 +44,10 @@ public: SceneOpenGL::TexturePrivate *createBackendTexture(SceneOpenGL::Texture *texture) override; QRegion prepareRenderingFrame() override; void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override; + void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override; bool usesOverlayWindow() const override; bool perScreenRendering() const override; - void prepareRenderingForScreen(int screenId) override; + QRegion prepareRenderingForScreen(int screenId) override; protected: void present() override; @@ -63,8 +64,13 @@ private: gbm_surface *gbmSurface = nullptr; EGLSurface eglSurface = EGL_NO_SURFACE; int bufferAge = 0; + /** + * @brief The damage history for the past 10 frames. + */ + QList damageHistory; }; bool makeContextCurrent(const Output &output); + void presentOnOutput(Output &output); DrmBackend *m_backend; gbm_device *m_device = nullptr; QVector m_outputs; diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 972de5d7f6..c33f5ccc09 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -357,9 +357,17 @@ OverlayWindow* OpenGLBackend::overlayWindow() return NULL; } -void OpenGLBackend::prepareRenderingForScreen(int screenId) +QRegion OpenGLBackend::prepareRenderingForScreen(int screenId) +{ + // fallback to repaint complete screen + return screens()->geometry(screenId); +} + +void OpenGLBackend::endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) { Q_UNUSED(screenId) + Q_UNUSED(damage) + Q_UNUSED(damagedRegion) } bool OpenGLBackend::perScreenRendering() const @@ -651,17 +659,6 @@ qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) // actually paint the frame, flushed with the NEXT frame createStackingOrder(toplevels); - m_backend->makeCurrent(); - QRegion repaint = m_backend->prepareRenderingFrame(); - - const GLenum status = glGetGraphicsResetStatus(); - if (status != GL_NO_ERROR) { - handleGraphicsReset(status); - return 0; - } - - int mask = 0; - // After this call, updateRegion will contain the damaged region in the // back buffer. This is the region that needs to be posted to repair // the front buffer. It doesn't include the additional damage returned @@ -669,39 +666,64 @@ qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) // repainted, and may be larger than updateRegion. QRegion updateRegion, validRegion; if (m_backend->perScreenRendering()) { + // trigger start render timer + m_backend->prepareRenderingFrame(); for (int i = 0; i < screens()->count(); ++i) { const QRect &geo = screens()->geometry(i); QRegion update; QRegion valid; - m_backend->prepareRenderingForScreen(i); - paintScreen(&mask, damage.intersected(geo), repaint.intersected(geo), &update, &valid); // call generic implementation - updateRegion = updateRegion.united(update); - validRegion = validRegion.united(valid); + // prepare rendering makes context current on the output + QRegion repaint = m_backend->prepareRenderingForScreen(i); + + const GLenum status = glGetGraphicsResetStatus(); + if (status != GL_NO_ERROR) { + handleGraphicsReset(status); + return 0; + } + + int mask = 0; + paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid); // call generic implementation + + GLVertexBuffer::streamingBuffer()->endOfFrame(); + + m_backend->endRenderingFrameForScreen(i, valid, update); + + GLVertexBuffer::streamingBuffer()->framePosted(); } } else { + m_backend->makeCurrent(); + QRegion repaint = m_backend->prepareRenderingFrame(); + + const GLenum status = glGetGraphicsResetStatus(); + if (status != GL_NO_ERROR) { + handleGraphicsReset(status); + return 0; + } + + int mask = 0; paintScreen(&mask, damage, repaint, &updateRegion, &validRegion); // call generic implementation - } #ifndef KWIN_HAVE_OPENGLES - const QSize &screenSize = screens()->size(); - const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); + const QSize &screenSize = screens()->size(); + const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); - // copy dirty parts from front to backbuffer - if (!m_backend->supportsBufferAge() && - options->glPreferBufferSwap() == Options::CopyFrontBuffer && - validRegion != displayRegion) { - glReadBuffer(GL_FRONT); - copyPixels(displayRegion - validRegion); - glReadBuffer(GL_BACK); - validRegion = displayRegion; - } + // copy dirty parts from front to backbuffer + if (!m_backend->supportsBufferAge() && + options->glPreferBufferSwap() == Options::CopyFrontBuffer && + validRegion != displayRegion) { + glReadBuffer(GL_FRONT); + copyPixels(displayRegion - validRegion); + glReadBuffer(GL_BACK); + validRegion = displayRegion; + } #endif - GLVertexBuffer::streamingBuffer()->endOfFrame(); + GLVertexBuffer::streamingBuffer()->endOfFrame(); - m_backend->endRenderingFrame(validRegion, updateRegion); + m_backend->endRenderingFrame(validRegion, updateRegion); - GLVertexBuffer::streamingBuffer()->framePosted(); + GLVertexBuffer::streamingBuffer()->framePosted(); + } if (m_currentFence) { if (!m_syncManager->updateFences()) { diff --git a/scene_opengl.h b/scene_opengl.h index 248eb8a61b..06d23485b5 100644 --- a/scene_opengl.h +++ b/scene_opengl.h @@ -400,6 +400,7 @@ public: * @param damagedRegion The damaged region that should be posted **/ virtual void endRenderingFrame(const QRegion &damage, const QRegion &damagedRegion) = 0; + virtual void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion); virtual bool makeCurrent() = 0; virtual void doneCurrent() = 0; virtual bool usesOverlayWindow() const = 0; @@ -408,7 +409,7 @@ public: * Default implementation returns @c false. **/ virtual bool perScreenRendering() const; - virtual void prepareRenderingForScreen(int screenId); + virtual QRegion prepareRenderingForScreen(int screenId); /** * @brief Compositor is going into idle mode, flushes any pending paints. **/