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.
This commit is contained in:
parent
c33a74ae58
commit
2a64755b76
4 changed files with 118 additions and 55 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
||||
**/
|
||||
|
|
Loading…
Reference in a new issue