egl_gbm_backend.cpp 19.3 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
10
11
12
#include "egl_gbm_backend.h"
// kwin
#include "composite.h"
#include "drm_backend.h"
13
#include "drm_output.h"
14
#include "gbm_surface.h"
15
#include "logging.h"
16
17
#include "options.h"
#include "screens.h"
18
#include "drm_gpu.h"
19
20
// kwin libs
#include <kwinglplatform.h>
21
#include <kwineglimagetexture.h>
22
23
24
25
26
27
// system
#include <gbm.h>

namespace KWin
{

28
EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu)
29
    : AbstractEglBackend()
30
    , m_backend(drmBackend)
31
    , m_gpu(gpu)
32
{
33
    // Egl is always direct rendering.
34
    setIsDirectRendering(true);
35
    setSyncsToVBlank(true);
36
37
    connect(m_gpu, &DrmGpu::outputEnabled, this, &EglGbmBackend::createOutput);
    connect(m_gpu, &DrmGpu::outputDisabled, this, &EglGbmBackend::removeOutput);
38
39
40
41
42
43
44
}

EglGbmBackend::~EglGbmBackend()
{
    cleanup();
}

45
46
void EglGbmBackend::cleanupSurfaces()
{
47
    for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
48
49
        cleanupOutput(*it);
    }
50
    m_outputs.clear();
51
52
}

53
void EglGbmBackend::cleanupFramebuffer(Output &output)
54
{
55
56
57
58
59
60
61
62
63
64
65
66
    if (!output.render.framebuffer) {
        return;
    }
    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);
67
    output.output->releaseGbm();
68

69
70
    if (output.eglSurface != EGL_NO_SURFACE) {
        eglDestroySurface(eglDisplay(), output.eglSurface);
71
    }
72
73
}

74
75
76
bool EglGbmBackend::initializeEgl()
{
    initClientExtensions();
77
    EGLDisplay display = m_gpu->eglDisplay();
78
79
80

    // Use eglGetPlatformDisplayEXT() to get the display pointer
    // if the implementation supports it.
81
    if (display == EGL_NO_DISPLAY) {
82
83
84
85
        const bool hasMesaGBM = hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_gbm"));
        const bool hasKHRGBM = hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_gbm"));
        const GLenum platform = hasMesaGBM ? EGL_PLATFORM_GBM_MESA : EGL_PLATFORM_GBM_KHR;

86
        if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")) ||
87
                (!hasMesaGBM && !hasKHRGBM)) {
88
89
            setFailed("Missing one or more extensions between EGL_EXT_platform_base, "
                      "EGL_MESA_platform_gbm, EGL_KHR_platform_gbm");
90
91
            return false;
        }
92

93
        auto device = gbm_create_device(m_gpu->fd());
94
        if (!device) {
95
96
97
            setFailed("Could not create gbm device");
            return false;
        }
98
        m_gpu->setGbmDevice(device);
99

100
        display = eglGetPlatformDisplayEXT(platform, device, nullptr);
101
        m_gpu->setEglDisplay(display);
102
    }
103

104
    if (display == EGL_NO_DISPLAY) {
105
        return false;
106
    }
107
108
109
110
111
112
    setEglDisplay(display);
    return initEglAPI();
}

void EglGbmBackend::init()
{
113
114
115
116
    if (!initializeEgl()) {
        setFailed("Could not initialize egl");
        return;
    }
117
118
119
120
121
122
123
124
125
126
127
128
129
    if (!initRenderingContext()) {
        setFailed("Could not initialize rendering context");
        return;
    }

    initKWinGL();
    initBufferAge();
    initWayland();
}

bool EglGbmBackend::initRenderingContext()
{
    initBufferConfigs();
130
131
    if (!createContext()) {
        return false;
132
133
    }

Roman Gilg's avatar
Roman Gilg committed
134
    const auto outputs = m_backend->drmOutputs();
135

136
    for (DrmOutput *drmOutput: outputs) {
137
        createOutput(drmOutput);
138
    }
139

140
    if (m_outputs.isEmpty()) {
141
        qCCritical(KWIN_DRM) << "Create Window Surfaces failed";
142
143
        return false;
    }
144
145

    // Set our first surface as the one for the abstract backend, just to make it happy.
146
    setSurface(m_outputs.first().eglSurface);
147

148
    return makeContextCurrent(m_outputs.first());
149
150
}

151
152
std::shared_ptr<GbmSurface> EglGbmBackend::createGbmSurface(const QSize &size) const
{
153
    auto gbmSurface = std::make_shared<GbmSurface>(m_gpu->gbmDevice(),
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
                                                   size.width(), size.height(),
                                                   GBM_FORMAT_XRGB8888,
                                                   GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
    if (!gbmSurface) {
        qCCritical(KWIN_DRM) << "Creating GBM surface failed";
        return nullptr;
    }
    return gbmSurface;
}

EGLSurface EglGbmBackend::createEglSurface(std::shared_ptr<GbmSurface> gbmSurface) const
{
    auto eglSurface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(),
                                                        (void *)(gbmSurface->surface()), nullptr);
    if (eglSurface == EGL_NO_SURFACE) {
        qCCritical(KWIN_DRM) << "Creating EGL surface failed";
        return EGL_NO_SURFACE;
    }
    return eglSurface;
}

bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
176
{
177
    output.output = drmOutput;
178
179
    const QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() :
                                                         drmOutput->modeSize();
David Edmundson's avatar
David Edmundson committed
180

181
    auto gbmSurface = createGbmSurface(size);
182
183
    if (!gbmSurface) {
        return false;
184
    }
185
    auto eglSurface = createEglSurface(gbmSurface);
186
187
    if (eglSurface == EGL_NO_SURFACE) {
        return false;
188
189
190
191
192
193
    }

    // destroy previous surface
    if (output.eglSurface != EGL_NO_SURFACE) {
        if (surface() == output.eglSurface) {
            setSurface(eglSurface);
194
        }
195
        eglDestroySurface(eglDisplay(), output.eglSurface);
196
    }
197
198
    output.eglSurface = eglSurface;
    output.gbmSurface = gbmSurface;
199
200

    resetFramebuffer(output);
201
202
203
204
205
    return true;
}

void EglGbmBackend::createOutput(DrmOutput *drmOutput)
{
206
207
    Output newOutput;
    if (resetOutput(newOutput, drmOutput)) {
208
209
210
        connect(drmOutput, &DrmOutput::modeChanged, this,
            [drmOutput, this] {
                auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
211
212
                    [drmOutput] (const auto &output) {
                        return output.output == drmOutput;
213
214
215
216
217
218
219
220
                    }
                );
                if (it == m_outputs.end()) {
                    return;
                }
                resetOutput(*it, drmOutput);
            }
        );
221
        m_outputs << newOutput;
222
223
224
    }
}

225
226
227
228
229
230
231
232
233
234
void EglGbmBackend::removeOutput(DrmOutput *drmOutput)
{
    auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
        [drmOutput] (const Output &output) {
            return output.output == drmOutput;
        }
    );
    if (it == m_outputs.end()) {
        return;
    }
235

236
237
238
239
    cleanupOutput(*it);
    m_outputs.erase(it);
}

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
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;
}

299
void EglGbmBackend::initRenderTarget(Output &output)
300
{
301
    if (output.render.vbo) {
302
        // Already initialized.
303
        return;
304
305
306
307
308
309
310
311
312
313
314
315
    }
    std::shared_ptr<GLVertexBuffer> 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) {
        // No additional render target.
        return;
    }
316
    initRenderTarget(output);
317
318
319
320
321
322
323

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    GLRenderTarget::setKWinFramebuffer(0);

    const auto size = output.output->modeSize();
    glViewport(0, 0, size.width(), size.height());

324
325
    auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);

326
327
    QMatrix4x4 rotationMatrix;
    rotationMatrix.rotate(output.output->rotation(), 0, 0, 1);
328
    shader->setUniform(GLShader::ModelViewProjectionMatrix, rotationMatrix);
329
330
331

    glBindTexture(GL_TEXTURE_2D, output.render.texture);
    output.render.vbo->render(GL_TRIANGLES);
332
    ShaderManager::instance()->popShader();
333
334
335
336
337
338
339
340
341
}

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);
}

342
bool EglGbmBackend::makeContextCurrent(const Output &output) const
343
{
344
345
346
347
348
    const EGLSurface surface = output.eglSurface;
    if (surface == EGL_NO_SURFACE) {
        return false;
    }
    if (eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_FALSE) {
349
        qCCritical(KWIN_DRM) << "Make Context Current failed" << eglGetError();
350
351
352
353
354
355
356
357
358
359
360
361
362
        return false;
    }
    return true;
}

bool EglGbmBackend::initBufferConfigs()
{
    const EGLint config_attribs[] = {
        EGL_SURFACE_TYPE,         EGL_WINDOW_BIT,
        EGL_RED_SIZE,             1,
        EGL_GREEN_SIZE,           1,
        EGL_BLUE_SIZE,            1,
        EGL_ALPHA_SIZE,           0,
363
        EGL_RENDERABLE_TYPE,      isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
364
365
366
367
368
369
        EGL_CONFIG_CAVEAT,        EGL_NONE,
        EGL_NONE,
    };

    EGLint count;
    EGLConfig configs[1024];
370
371
372
    if (!eglChooseConfig(eglDisplay(), config_attribs, configs,
                         sizeof(configs) / sizeof(EGLConfig),
                         &count)) {
373
        qCCritical(KWIN_DRM) << "choose config failed";
374
375
        return false;
    }
376
377
378

    qCDebug(KWIN_DRM) << "EGL buffer configs count:" << count;

379
    // Loop through all configs, choosing the first one that has suitable format.
380
381
    for (EGLint i = 0; i < count; i++) {
        EGLint gbmFormat;
382
        // Query some configuration parameters, to show in debug log.
383
384
385
        eglGetConfigAttrib(eglDisplay(), configs[i], EGL_NATIVE_VISUAL_ID, &gbmFormat);

        if (KWIN_DRM().isDebugEnabled()) {
386
            // GBM formats are declared as FOURCC code (integer from ASCII chars, so use this fact).
387
388
            char gbmFormatStr[sizeof(EGLint) + 1] = {0};
            memcpy(gbmFormatStr, &gbmFormat, sizeof(EGLint));
389
390

            // Query number of bits for color channel.
391
392
393
394
395
396
            EGLint blueSize, redSize, greenSize, alphaSize;
            eglGetConfigAttrib(eglDisplay(), configs[i], EGL_RED_SIZE, &redSize);
            eglGetConfigAttrib(eglDisplay(), configs[i], EGL_GREEN_SIZE, &greenSize);
            eglGetConfigAttrib(eglDisplay(), configs[i], EGL_BLUE_SIZE, &blueSize);
            eglGetConfigAttrib(eglDisplay(), configs[i], EGL_ALPHA_SIZE, &alphaSize);
            qCDebug(KWIN_DRM) << "  EGL config #" << i << " has GBM FOURCC format:" << gbmFormatStr
397
398
                              << "; color sizes (RGBA order):"
                              << redSize << greenSize << blueSize << alphaSize;
399
400
401
402
403
404
        }

        if ((gbmFormat == GBM_FORMAT_XRGB8888) || (gbmFormat == GBM_FORMAT_ARGB8888)) {
            setConfig(configs[i]);
            return true;
        }
405
406
    }

407
408
    qCCritical(KWIN_DRM) << "Choosing EGL config did not return a suitable config. There were"
                         << count << "configs.";
409
    return false;
410
411
412
413
}

void EglGbmBackend::present()
{
414
415
    Q_UNREACHABLE();
    // Not in use. This backend does per-screen rendering.
416
417
}

418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
static QVector<EGLint> regionToRects(const QRegion &region, AbstractWaylandOutput *output)
{
    const int height = output->modeSize().height();

    const QMatrix4x4 matrix = output->transformation();

    QVector<EGLint> rects;
    rects.reserve(region.rectCount() * 4);
    for (const QRect &_rect : region) {
        const QRect rect = matrix.mapRect(_rect);

        rects << rect.left();
        rects << height - (rect.y() + rect.height());
        rects << rect.width();
        rects << rect.height();
    }
    return rects;
}

void EglGbmBackend::aboutToStartPainting(const QRegion &damagedRegion)
{
    // See EglGbmBackend::endRenderingFrameForScreen comment for the reason why we only support screenId=0
    if (m_outputs.count() > 1) {
        return;
    }

    const Output &output = m_outputs.at(0);
    if (output.bufferAge > 0 && !damagedRegion.isEmpty() && supportsPartialUpdate()) {
        const QRegion region = damagedRegion & output.output->geometry();

        QVector<EGLint> rects = regionToRects(region, output.output);
        const bool correct = eglSetDamageRegionKHR(eglDisplay(), output.eglSurface,
                                                   rects.data(), rects.count()/4);
        if (!correct) {
            qCWarning(KWIN_DRM) << "failed eglSetDamageRegionKHR" << eglGetError();
        }
    }
}

457
void EglGbmBackend::presentOnOutput(Output &output, const QRegion &damagedRegion)
458
{
459
460
461
462
463
464
465
    if (supportsSwapBuffersWithDamage()) {
        QVector<EGLint> rects = regionToRects(output.damageHistory.constFirst(), output.output);
        eglSwapBuffersWithDamageEXT(eglDisplay(), output.eglSurface,
                                    rects.data(), rects.count()/4);
    } else {
        eglSwapBuffers(eglDisplay(), output.eglSurface);
    }
466
    output.buffer = new DrmSurfaceBuffer(m_gpu->fd(), output.gbmSurface);
467

468
    Q_EMIT output.output->outputChange(damagedRegion);
469
    m_backend->present(output.buffer, output.output);
470

471
    if (supportsBufferAge()) {
472
        eglQuerySurface(eglDisplay(), output.eglSurface, EGL_BUFFER_AGE_EXT, &output.bufferAge);
473
474
475
    }
}

476
477
478
479
480
481
void EglGbmBackend::screenGeometryChanged(const QSize &size)
{
    Q_UNUSED(size)
    // TODO, create new buffer?
}

482
SceneOpenGLTexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGLTexture *texture)
483
484
485
486
487
488
{
    return new EglGbmTexture(texture, this);
}

QRegion EglGbmBackend::prepareRenderingFrame()
{
489
490
491
492
    startRenderTimer();
    return QRegion();
}

493
494
495
496
497
498
499
500
501
502
void EglGbmBackend::setViewport(const Output &output) const
{
    const QSize &overall = screens()->size();
    const QRect &v = output.output->geometry();
    qreal scale = output.output->scale();

    glViewport(-v.x() * scale, (v.height() - overall.height() + v.y()) * scale,
               overall.width() * scale, overall.height() * scale);
}

503
504
QRegion EglGbmBackend::prepareRenderingForScreen(int screenId)
{
505
506
507
    const Output &output = m_outputs.at(screenId);

    makeContextCurrent(output);
508
    prepareRenderFramebuffer(output);
509
510
    setViewport(output);

511
    if (supportsBufferAge()) {
512
513
514
        QRegion region;

        // Note: An age of zero means the buffer contents are undefined
515
516
517
        if (output.bufferAge > 0 && output.bufferAge <= output.damageHistory.count()) {
            for (int i = 0; i < output.bufferAge - 1; i++)
                region |= output.damageHistory[i];
518
        } else {
519
            region = output.output->geometry();
520
        }
521
522

        return region;
523
    }
524
    return output.output->geometry();
525
526
}

527
void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
528
{
529
530
    Q_UNUSED(renderedRegion)
    Q_UNUSED(damagedRegion)
531
532
}

533
534
535
void EglGbmBackend::endRenderingFrameForScreen(int screenId,
                                               const QRegion &renderedRegion,
                                               const QRegion &damagedRegion)
536
{
537
    Output &output = m_outputs[screenId];
538
    renderFramebufferToSurface(output);
539
540

    if (damagedRegion.intersected(output.output->geometry()).isEmpty() && screenId == 0) {
541
542
543
544
545
546
547
548

        // If the damaged region of a window is fully occluded, the only
        // rendering done, if any, will have been to repair a reused back
        // buffer, making it identical to the front buffer.
        //
        // 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.
549
        if (!renderedRegion.intersected(output.output->geometry()).isEmpty())
550
551
            glFlush();

552
553
        for (auto &output: m_outputs) {
            output.bufferAge = 1;
554
        }
555
556
        return;
    }
557
    presentOnOutput(output, damagedRegion);
558
559

    // Save the damaged region to history
560
561
562
563
564
565
566
    // 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.
567
    if (supportsBufferAge() && screenId == 0) {
568
569
        if (output.damageHistory.count() > 10) {
            output.damageHistory.removeLast();
570
        }
571
        output.damageHistory.prepend(damagedRegion.intersected(output.output->geometry()));
572
    }
573
574
575
576
577
578
579
}

bool EglGbmBackend::usesOverlayWindow() const
{
    return false;
}

580
581
582
583
584
bool EglGbmBackend::perScreenRendering() const
{
    return true;
}

585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *abstractOutput) const
{
    const QVector<KWin::EglGbmBackend::Output>::const_iterator itOutput = std::find_if(m_outputs.begin(), m_outputs.end(),
        [abstractOutput] (const auto &output) {
            return output.output == abstractOutput;
        }
    );
    if (itOutput == m_outputs.end()) {
        return {};
    }

    DrmOutput *drmOutput = itOutput->output;
    if (!drmOutput->hardwareTransforms()) {
        const auto glTexture = QSharedPointer<KWin::GLTexture>::create(itOutput->render.texture, GL_RGBA8, drmOutput->pixelSize());
        glTexture->setYInverted(true);
        return glTexture;
    }

    EGLImageKHR image = eglCreateImageKHR(eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR, itOutput->buffer->getBo(), nullptr);
    if (image == EGL_NO_IMAGE_KHR) {
        qCWarning(KWIN_DRM) << "Failed to record frame: Error creating EGLImageKHR - " << glGetError();
        return {};
    }

    return QSharedPointer<EGLImageTexture>::create(eglDisplay(), image, GL_RGBA8, drmOutput->modeSize());
}

612
613
614
615
/************************************************
 * EglTexture
 ************************************************/

616
EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend)
617
618
619
620
621
622
    : AbstractEglTexture(texture, backend)
{
}

EglGbmTexture::~EglGbmTexture() = default;

623
}