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.
icc-effect-5.14.5
Martin Gräßlin 2015-04-23 09:55:49 +02:00
parent c33a74ae58
commit 2a64755b76
4 changed files with 118 additions and 55 deletions

View File

@ -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

View File

@ -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<QRegion> damageHistory;
};
bool makeContextCurrent(const Output &output);
void presentOnOutput(Output &output);
DrmBackend *m_backend;
gbm_device *m_device = nullptr;
QVector<Output> m_outputs;

View File

@ -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()) {

View File

@ -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.
**/