egl_gbm_backend.cpp 25.1 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
#include "egl_gbm_backend.h"
10
11
#include "basiceglsurfacetexture_internal.h"
#include "basiceglsurfacetexture_wayland.h"
12
13
14
// kwin
#include "composite.h"
#include "drm_backend.h"
15
#include "drm_buffer_gbm.h"
16
#include "drm_output.h"
17
#include "gbm_surface.h"
18
#include "logging.h"
19
#include "options.h"
20
#include "renderloop_p.h"
21
#include "screens.h"
22
#include "surfaceitem_wayland.h"
23
#include "drm_gpu.h"
24
#include "linux_dmabuf.h"
25
#include "dumb_swapchain.h"
26
#include "kwineglutils_p.h"
27
#include "shadowbuffer.h"
28
#include "drm_pipeline.h"
29
30
// kwin libs
#include <kwinglplatform.h>
31
#include <kwineglimagetexture.h>
32
33
// system
#include <gbm.h>
34
#include <unistd.h>
35
#include <errno.h>
36
#include <drm_fourcc.h>
37
38
// kwayland server
#include "KWaylandServer/surface_interface.h"
39
#include "KWaylandServer/linuxdmabufv1clientbuffer.h"
40
#include "KWaylandServer/clientconnection.h"
41
42
43
44

namespace KWin
{

45
EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend, DrmGpu *gpu)
46
    : AbstractEglDrmBackend(drmBackend, gpu)
47
48
49
{
}

50
51
52
53
54
EglGbmBackend::~EglGbmBackend()
{
    cleanup();
}

55
56
void EglGbmBackend::cleanupSurfaces()
{
57
58
    // shadow buffer needs context current for destruction
    makeCurrent();
59
    m_outputs.clear();
60
61
}

62
63
64
65
66
67
68
69
70
71
void EglGbmBackend::cleanupRenderData(Output::RenderData &render)
{
    render.gbmSurface = nullptr;
    render.importSwapchain = nullptr;
    if (render.shadowBuffer) {
        makeContextCurrent(render);
        render.shadowBuffer = nullptr;
    }
}

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

    // Use eglGetPlatformDisplayEXT() to get the display pointer
    // if the implementation supports it.
79
    if (display == EGL_NO_DISPLAY) {
80
81
82
83
        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;

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

91
        if (!m_gpu->gbmDevice()) {
92
93
94
            setFailed("Could not create gbm device");
            return false;
        }
95

96
        display = eglGetPlatformDisplayEXT(platform, m_gpu->gbmDevice(), nullptr);
97
        m_gpu->setEglDisplay(display);
98
    }
99

100
    if (display == EGL_NO_DISPLAY) {
101
        return false;
102
    }
103
104
105
106
107
108
    setEglDisplay(display);
    return initEglAPI();
}

void EglGbmBackend::init()
{
109
110
111
112
    if (!initializeEgl()) {
        setFailed("Could not initialize egl");
        return;
    }
113

114
115
116
117
118
    if (!initRenderingContext()) {
        setFailed("Could not initialize rendering context");
        return;
    }
    initBufferAge();
119
120
121
122
123
    // at the moment: no secondary GPU -> no OpenGL context!
    if (isPrimary()) {
        initKWinGL();
        initWayland();
    }
124
125
126
127
}

bool EglGbmBackend::initRenderingContext()
{
128
129
130
    if (!initBufferConfigs()) {
        return false;
    }
131
    if (isPrimary()) {
132
133
134
135
136
137
138
        if (!createContext() || !makeCurrent()) {
            return false;
        }
    }
    const auto outputs = m_gpu->outputs();
    for (const auto &output : outputs) {
        addOutput(output);
139
140
    }
    return true;
141
}
Xaver Hugl's avatar
Xaver Hugl committed
142

143
bool EglGbmBackend::resetOutput(Output &output)
144
{
145
146
    const QSize size = output.output->sourceSize();
    QVector<uint64_t> modifiers = output.output->supportedModifiers(m_gbmFormat);
Xaver Hugl's avatar
Xaver Hugl committed
147
148

    QSharedPointer<GbmSurface> gbmSurface;
149
#if HAVE_GBM_BO_GET_FD_FOR_PLANE
Xaver Hugl's avatar
Xaver Hugl committed
150
    if (modifiers.isEmpty()) {
151
152
153
154
#else
    // modifiers have to be disabled with multi-gpu if gbm_bo_get_fd_for_plane is not available
    if (modifiers.isEmpty() || output.output->gpu() != m_gpu) {
#endif
Xaver Hugl's avatar
Xaver Hugl committed
155
        int flags = GBM_BO_USE_RENDERING;
156
        if (output.output->gpu() == m_gpu) {
Xaver Hugl's avatar
Xaver Hugl committed
157
158
159
160
161
            flags |= GBM_BO_USE_SCANOUT;
        } else {
            flags |= GBM_BO_USE_LINEAR;
        }
        gbmSurface = QSharedPointer<GbmSurface>::create(m_gpu, size, m_gbmFormat, flags);
162
    } else {
Xaver Hugl's avatar
Xaver Hugl committed
163
164
165
166
167
168
169
        gbmSurface = QSharedPointer<GbmSurface>::create(m_gpu, size, m_gbmFormat, modifiers);
        if (!gbmSurface->isValid()) {
            // the egl / gbm implementation may reject the modifier list from another gpu
            // as a fallback use linear, to at least make CPU copy more efficient
            modifiers = {DRM_FORMAT_MOD_LINEAR};
            gbmSurface = QSharedPointer<GbmSurface>::create(m_gpu, size, m_gbmFormat, modifiers);
        }
170
    }
Xaver Hugl's avatar
Xaver Hugl committed
171
    if (!gbmSurface->isValid()) {
172
        qCCritical(KWIN_DRM) << "Creating GBM surface failed:" << strerror(errno);
173
        return false;
174
    }
175
176
177
178
    cleanupRenderData(output.old);
    output.old = output.current;
    output.current = {};
    output.current.gbmSurface = gbmSurface;
179

180
    if (!output.output->needsSoftwareTransformation())  {
181
        output.current.shadowBuffer = nullptr;
182
    } else {
183
184
185
        makeContextCurrent(output.current);
        output.current.shadowBuffer = QSharedPointer<ShadowBuffer>::create(output.output->pixelSize());
        if (!output.current.shadowBuffer->isComplete()) {
186
187
188
            return false;
        }
    }
189
190
191
    return true;
}

192
bool EglGbmBackend::addOutput(DrmAbstractOutput *drmOutput)
193
{
194
195
    Output newOutput;
    newOutput.output = drmOutput;
196
197
    if (!isPrimary() && !renderingBackend()->addOutput(drmOutput)) {
        return false;
198
    }
199
    m_outputs.insert(drmOutput, newOutput);
200
    return true;
201
202
}

203
void EglGbmBackend::removeOutput(DrmAbstractOutput *drmOutput)
204
{
205
    Q_ASSERT(m_outputs.contains(drmOutput));
206
    if (isPrimary()) {
207
208
        // shadow buffer needs context current for destruction
        makeCurrent();
209
    } else {
210
        renderingBackend()->removeOutput(drmOutput);
211
    }
212
    m_outputs.remove(drmOutput);
213
214
}

215
bool EglGbmBackend::swapBuffers(DrmAbstractOutput *drmOutput, const QRegion &dirty)
216
{
217
218
    Q_ASSERT(m_outputs.contains(drmOutput));
    Output &output = m_outputs[drmOutput];
219
220
221
222
223
224
225
226
    renderFramebufferToSurface(output);
    if (output.current.gbmSurface->swapBuffers()) {
        cleanupRenderData(output.old);
        updateBufferAge(output, dirty);
        return true;
    } else {
        return false;
    }
227
228
}

229
bool EglGbmBackend::exportFramebuffer(DrmAbstractOutput *drmOutput, void *data, const QSize &size, uint32_t stride)
230
{
231
232
    Q_ASSERT(m_outputs.contains(drmOutput));
    auto bo = m_outputs[drmOutput].current.gbmSurface->currentBuffer();
233
    if (!bo->map(GBM_BO_TRANSFER_READ)) {
234
235
        return false;
    }
236
    if (stride != bo->stride()) {
237
238
239
        // shouldn't happen if formats are the same
        return false;
    }
240
    return memcpy(data, bo->mappedData(), size.height() * stride);
241
242
}

243
bool EglGbmBackend::exportFramebufferAsDmabuf(DrmAbstractOutput *drmOutput, int *fds, int *strides, int *offsets, uint32_t *num_fds, uint32_t *format, uint64_t *modifier)
244
{
245
246
    Q_ASSERT(m_outputs.contains(drmOutput));
    auto bo = m_outputs[drmOutput].current.gbmSurface->currentBuffer()->getBo();
247
#if HAVE_GBM_BO_GET_FD_FOR_PLANE
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
    if (gbm_bo_get_handle_for_plane(bo, 0).s32 != -1) {
        *num_fds = gbm_bo_get_plane_count(bo);
        for (uint32_t i = 0; i < *num_fds; i++) {
            fds[i] = gbm_bo_get_fd_for_plane(bo, i);
            if (fds[i] < 0) {
                qCWarning(KWIN_DRM) << "failed to export gbm_bo as dma-buf:" << strerror(errno);
                for (uint32_t f = 0; f < i; f++) {
                    close(fds[f]);
                }
                return false;
            }
            strides[i] = gbm_bo_get_stride_for_plane(bo, i);
            offsets[i] = gbm_bo_get_offset(bo, i);
        }
        *modifier = gbm_bo_get_modifier(bo);
    } else {
264
#endif
265
266
267
268
269
270
271
272
        fds[0] = gbm_bo_get_fd(bo);
        if (fds[0] < 0) {
            qCWarning(KWIN_DRM) << "failed to export gbm_bo as dma-buf:" << strerror(errno);
            return false;
        }
        *num_fds = 1;
        strides[0] = gbm_bo_get_stride(bo);
        *modifier = DRM_FORMAT_MOD_INVALID;
273
#if HAVE_GBM_BO_GET_FD_FOR_PLANE
274
    }
275
#endif
276
    *format = gbm_bo_get_format(bo);
277
    return true;
278
279
}

280
QRegion EglGbmBackend::beginFrameForSecondaryGpu(DrmAbstractOutput *drmOutput)
281
{
282
283
    Q_ASSERT(m_outputs.contains(drmOutput));
    return prepareRenderingForOutput(m_outputs[drmOutput]);
284
285
}

286
QSharedPointer<DrmBuffer> EglGbmBackend::importFramebuffer(Output &output, const QRegion &dirty) const
287
{
288
    if (!renderingBackend()->swapBuffers(output.output, dirty)) {
289
        qCWarning(KWIN_DRM) << "swapping buffers failed on output" << output.output;
290
        return nullptr;
291
292
    }
    const auto size = output.output->modeSize();
293
    if (output.current.importMode == ImportMode::Dmabuf) {
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
        struct gbm_import_fd_modifier_data data;
        data.width = size.width();
        data.height = size.height();
        if (renderingBackend()->exportFramebufferAsDmabuf(output.output, data.fds, data.strides, data.offsets, &data.num_fds, &data.format, &data.modifier)) {
            gbm_bo *importedBuffer = nullptr;
            if (data.modifier == DRM_FORMAT_MOD_INVALID) {
                struct gbm_import_fd_data data1;
                data1.fd = data.fds[0];
                data1.width = size.width();
                data1.height = size.height();
                data1.stride = data.strides[0];
                data1.format = data.format;
                importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD, &data1, GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR);
            } else {
                importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, 0);
            }
            for (uint32_t i = 0; i < data.num_fds; i++) {
                close(data.fds[i]);
            }
313
314
315
            if (importedBuffer) {
                auto buffer = QSharedPointer<DrmGbmBuffer>::create(m_gpu, importedBuffer, nullptr);
                if (buffer->bufferId() > 0) {
316
                    return buffer;
317
318
319
320
                }
            }
        }
        qCDebug(KWIN_DRM) << "import with dmabuf failed! Switching to CPU import on output" << output.output;
321
        output.current.importMode = ImportMode::DumbBuffer;
322
323
    }
    // ImportMode::DumbBuffer
324
325
326
327
    if (!output.current.importSwapchain || output.current.importSwapchain->size() != size) {
        output.current.importSwapchain = QSharedPointer<DumbSwapchain>::create(m_gpu, size);
        if (output.current.importSwapchain->isEmpty()) {
            output.current.importSwapchain = nullptr;
328
329
        }
    }
330
331
    if (output.current.importSwapchain) {
        auto buffer = output.current.importSwapchain->acquireBuffer();
332
        if (renderingBackend()->exportFramebuffer(output.output, buffer->data(), size, buffer->stride())) {
333
            return buffer;
334
335
336
337
        }
    }
    qCWarning(KWIN_DRM) << "all imports failed on output" << output.output;
    // TODO turn off output?
338
    return nullptr;
339
340
}

341
342
void EglGbmBackend::renderFramebufferToSurface(Output &output)
{
343
    if (!output.current.shadowBuffer) {
344
345
346
        // No additional render target.
        return;
    }
347
348
    makeContextCurrent(output.current);
    output.current.shadowBuffer->render(output.output);
349
350
}

351
bool EglGbmBackend::makeContextCurrent(const Output::RenderData &render) const
352
{
353
    Q_ASSERT(isPrimary());
354
    const auto surface = render.gbmSurface;
355
    if (!surface) {
356
357
        return false;
    }
358
    if (eglMakeCurrent(eglDisplay(), surface->eglSurface(), surface->eglSurface(), context()) == EGL_FALSE) {
359
        qCCritical(KWIN_DRM) << "eglMakeCurrent failed:" << getEglErrorString();
360
361
        return false;
    }
362
363
364
    if (!GLPlatform::instance()->isGLES()) {
        glDrawBuffer(GL_BACK);
    }
365
366
367
368
369
370
371
372
373
374
375
    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,
376
        EGL_RENDERABLE_TYPE,      isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
377
378
379
380
381
382
        EGL_CONFIG_CAVEAT,        EGL_NONE,
        EGL_NONE,
    };

    EGLint count;
    EGLConfig configs[1024];
383
384
385
    if (!eglChooseConfig(eglDisplay(), config_attribs, configs,
                         sizeof(configs) / sizeof(EGLConfig),
                         &count)) {
386
        qCCritical(KWIN_DRM) << "eglChooseConfig failed:" << getEglErrorString();
387
388
        return false;
    }
389
390
391

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

Xaver Hugl's avatar
Xaver Hugl committed
392
    uint32_t fallbackFormat = 0;
Xaver Hugl's avatar
Xaver Hugl committed
393
394
    EGLConfig fallbackConfig = nullptr;

395
    // Loop through all configs, choosing the first one that has suitable format.
396
397
398
399
    for (EGLint i = 0; i < count; i++) {
        EGLint gbmFormat;
        eglGetConfigAttrib(eglDisplay(), configs[i], EGL_NATIVE_VISUAL_ID, &gbmFormat);

400
401
402
403
        if (!m_gpu->isFormatSupported(gbmFormat)) {
            continue;
        }

Xaver Hugl's avatar
Xaver Hugl committed
404
405
406
407
408
409
410
411
        // Query number of bits for color channel
        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);

        // prefer XRGB8888 as it's most likely to be supported by secondary GPUs as well
412
        if (gbmFormat == GBM_FORMAT_XRGB8888) {
Xaver Hugl's avatar
Xaver Hugl committed
413
            m_gbmFormat = gbmFormat;
414
415
            setConfig(configs[i]);
            return true;
Xaver Hugl's avatar
Xaver Hugl committed
416
417
418
        } else if (!fallbackConfig && blueSize >= 8 && redSize >= 8 && greenSize >= 8) {
            fallbackFormat = gbmFormat;
            fallbackConfig = configs[i];
419
        }
420
421
    }

Xaver Hugl's avatar
Xaver Hugl committed
422
423
424
425
426
427
    if (fallbackConfig) {
        m_gbmFormat = fallbackFormat;
        setConfig(fallbackConfig);
        return true;
    }

428
    qCCritical(KWIN_DRM) << "Choosing EGL config did not return a suitable config. There were"
429
430
431
432
433
434
435
436
437
438
439
440
                         << count << "configs:";
    for (EGLint i = 0; i < count; i++) {
        EGLint gbmFormat, blueSize, redSize, greenSize, alphaSize;
        eglGetConfigAttrib(eglDisplay(), configs[i], EGL_NATIVE_VISUAL_ID, &gbmFormat);
        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);
        gbm_format_name_desc name;
        gbm_format_get_name(gbmFormat, &name);
        qCCritical(KWIN_DRM, "EGL config %d has format %s with %d,%d,%d,%d bits for r,g,b,a",  i, name.name, redSize, greenSize, blueSize, alphaSize);
    }
441
    return false;
442
443
}

444
445
446
447
static QVector<EGLint> regionToRects(const QRegion &region, AbstractWaylandOutput *output)
{
    const int height = output->modeSize().height();

448
449
450
    const QMatrix4x4 matrix = DrmOutput::logicalToNativeMatrix(output->geometry(),
                                                               output->scale(),
                                                               output->transform());
451
452
453
454
455
456
457
458
459
460
461
462
463
464

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

465
void EglGbmBackend::aboutToStartPainting(AbstractOutput *drmOutput, const QRegion &damagedRegion)
466
{
467
468
469
    Q_ASSERT_X(drmOutput, "aboutToStartPainting", "not using per screen rendering");
    Q_ASSERT(m_outputs.contains(drmOutput));
    const Output &output = m_outputs[drmOutput];
470
    if (output.current.bufferAge > 0 && !damagedRegion.isEmpty() && supportsPartialUpdate()) {
471
472
473
        const QRegion region = damagedRegion & output.output->geometry();

        QVector<EGLint> rects = regionToRects(region, output.output);
474
        const bool correct = eglSetDamageRegionKHR(eglDisplay(), output.current.gbmSurface->eglSurface(),
475
476
                                                   rects.data(), rects.count()/4);
        if (!correct) {
477
            qCWarning(KWIN_DRM) << "eglSetDamageRegionKHR failed:" << getEglErrorString();
478
479
480
481
        }
    }
}

482
PlatformSurfaceTexture *EglGbmBackend::createPlatformSurfaceTextureInternal(SurfacePixmapInternal *pixmap)
483
{
484
485
486
487
488
489
    return new BasicEGLSurfaceTextureInternal(this, pixmap);
}

PlatformSurfaceTexture *EglGbmBackend::createPlatformSurfaceTextureWayland(SurfacePixmapWayland *pixmap)
{
    return new BasicEGLSurfaceTextureWayland(this, pixmap);
490
491
}

492
493
void EglGbmBackend::setViewport(const Output &output) const
{
494
495
    const QSize size = output.output->pixelSize();
    glViewport(0, 0, size.width(), size.height());
496
497
}

498
QRegion EglGbmBackend::beginFrame(AbstractOutput *drmOutput)
499
{
500
501
    Q_ASSERT(m_outputs.contains(drmOutput));
    Output &output = m_outputs[drmOutput];
502
503
504
    if (output.surfaceInterface) {
        qCDebug(KWIN_DRM) << "Direct scanout stopped on output" << output.output->name();
    }
505
    output.surfaceInterface = nullptr;
506
    if (isPrimary()) {
Xaver Hugl's avatar
Xaver Hugl committed
507
        return prepareRenderingForOutput(output);
508
    } else {
Xaver Hugl's avatar
Xaver Hugl committed
509
        return renderingBackend()->beginFrameForSecondaryGpu(output.output);
510
511
    }
}
512

513
bool EglGbmBackend::doesRenderFit(DrmAbstractOutput *output, const Output::RenderData &render)
514
{
515
516
517
    if (!render.gbmSurface) {
        return false;
    }
518
    QSize surfaceSize = output->sourceSize();
519
520
521
    if (surfaceSize != render.gbmSurface->size()) {
        return false;
    }
522
    bool needsTexture = output->needsSoftwareTransformation();
523
    if (needsTexture) {
524
        return render.shadowBuffer && render.shadowBuffer->textureSize() == output->pixelSize();
525
526
527
528
529
530
531
532
533
534
535
536
537
538
    } else {
        return render.shadowBuffer == nullptr;
    }
}

QRegion EglGbmBackend::prepareRenderingForOutput(Output &output)
{
    // check if the current surface still fits
    if (!doesRenderFit(output.output, output.current)) {
        if (doesRenderFit(output.output, output.old)) {
            cleanupRenderData(output.current);
            output.current = output.old;
            output.old = {};
        } else {
539
            resetOutput(output);
540
541
542
543
544
        }
    }
    makeContextCurrent(output.current);
    if (output.current.shadowBuffer) {
        output.current.shadowBuffer->bind();
545
    }
546
547
    setViewport(output);

548
    const QRect geometry = output.output->geometry();
549
    if (supportsBufferAge()) {
550
551
        auto current = &output.current;
        return current->damageJournal.accumulate(current->bufferAge, geometry);
552
    }
553
554

    return geometry;
555
556
}

557
QSharedPointer<DrmBuffer> EglGbmBackend::endFrameWithBuffer(AbstractOutput *drmOutput, const QRegion &dirty)
558
{
559
560
    Q_ASSERT(m_outputs.contains(drmOutput));
    Output &output = m_outputs[drmOutput];
561
562
    if (isPrimary()) {
        renderFramebufferToSurface(output);
563
564
565
566
567
        auto buffer = output.current.gbmSurface->swapBuffersForDrm();
        if (buffer) {
            updateBufferAge(output, dirty);
        }
        return buffer;
568
    } else {
569
        return importFramebuffer(output, dirty);
570
571
572
    }
}

573
void EglGbmBackend::endFrame(AbstractOutput *drmOutput, const QRegion &renderedRegion,
574
                             const QRegion &damagedRegion)
575
{
576
    Q_ASSERT(m_outputs.contains(drmOutput));
577
578
    Q_UNUSED(renderedRegion)

579
    Output &output = m_outputs[drmOutput];
580
    cleanupRenderData(output.old);
581

582
    const QRegion dirty = damagedRegion.intersected(output.output->geometry());
583
    QSharedPointer<DrmBuffer> buffer = endFrameWithBuffer(drmOutput, dirty);
584
    if (!buffer || !output.output->present(buffer, dirty)) {
585
        RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(output.output->renderLoop());
586
587
588
        renderLoopPrivate->notifyFrameFailed();
        return;
    }
589
}
590

591
592
void EglGbmBackend::updateBufferAge(Output &output, const QRegion &dirty)
{
593
    if (supportsBufferAge()) {
594
        eglQuerySurface(eglDisplay(), output.current.gbmSurface->eglSurface(), EGL_BUFFER_AGE_EXT, &output.current.bufferAge);
595
        output.current.damageJournal.add(dirty);
596
    }
597
598
}

599
bool EglGbmBackend::scanout(AbstractOutput *drmOutput, SurfaceItem *surfaceItem)
600
{
601
    Q_ASSERT(m_outputs.contains(drmOutput));
602
603
604
605
606
607
    SurfaceItemWayland *item = qobject_cast<SurfaceItemWayland *>(surfaceItem);
    if (!item) {
        return false;
    }

    KWaylandServer::SurfaceInterface *surface = item->surface();
608
609
610
611
612
613
    if (!surface) {
        return false;
    }

    auto buffer = qobject_cast<KWaylandServer::LinuxDmaBufV1ClientBuffer *>(surface->buffer());
    if (!buffer) {
614
615
        return false;
    }
616
    Output &output = m_outputs[drmOutput];
617
    if (buffer->size() != output.output->modeSize()) {
618
619
        return false;
    }
Xaver Hugl's avatar
Xaver Hugl committed
620
621
622
623
    if (!buffer->planes().count()) {
        return false;
    }
    if (!output.output->isFormatSupported(buffer->format())) {
624
625
        return false;
    }
Xaver Hugl's avatar
Xaver Hugl committed
626

627
    gbm_bo *importedBuffer;
Xaver Hugl's avatar
Xaver Hugl committed
628
629
630
631
632
    const auto planes = buffer->planes();
    if (planes.first().modifier != DRM_FORMAT_MOD_INVALID
        || planes.first().offset > 0
        || planes.count() > 1) {
        if (!m_gpu->addFB2ModifiersSupported() || !output.output->supportedModifiers(buffer->format()).contains(planes.first().modifier)) {
633
634
            return false;
        }
635
        gbm_import_fd_modifier_data data = {};
636
637
638
        data.format = buffer->format();
        data.width = (uint32_t) buffer->size().width();
        data.height = (uint32_t) buffer->size().height();
Xaver Hugl's avatar
Xaver Hugl committed
639
640
641
642
643
644
        data.num_fds = planes.count();
        data.modifier = planes.first().modifier;
        for (int i = 0; i < planes.count(); i++) {
            data.fds[i] = planes[i].fd;
            data.offsets[i] = planes[i].offset;
            data.strides[i] = planes[i].stride;
645
646
647
        }
        importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD_MODIFIER, &data, GBM_BO_USE_SCANOUT);
    } else {
Xaver Hugl's avatar
Xaver Hugl committed
648
        auto plane = planes.first();
649
650
        gbm_import_fd_data data = {};
        data.fd = plane.fd;
651
652
        data.width = (uint32_t) buffer->size().width();
        data.height = (uint32_t) buffer->size().height();
653
        data.stride = plane.stride;
654
        data.format = buffer->format();
655
656
657
        importedBuffer = gbm_bo_import(m_gpu->gbmDevice(), GBM_BO_IMPORT_FD, &data, GBM_BO_USE_SCANOUT);
    }
    if (!importedBuffer) {
658
        if (errno != EINVAL) {
659
            qCWarning(KWIN_DRM) << "Importing buffer for direct scanout failed:" << strerror(errno);
660
        }
661
662
663
664
        return false;
    }
    // damage tracking for screen casting
    QRegion damage;
Xaver Hugl's avatar
Xaver Hugl committed
665
    if (output.surfaceInterface == surface && buffer->size() == output.output->modeSize()) {
666
667
        QRegion trackedDamage = surfaceItem->damage();
        surfaceItem->resetDamage();
668
669
670
671
672
673
674
675
        for (const auto &rect : trackedDamage) {
            auto damageRect = QRect(rect);
            damageRect.translate(output.output->geometry().topLeft());
            damage |= damageRect;
        }
    } else {
        damage = output.output->geometry();
    }
676
    auto bo = QSharedPointer<DrmGbmBuffer>::create(m_gpu, importedBuffer, buffer);
677
678
    // ensure that a context is current like with normal presentation
    makeCurrent();
679
    if (output.output->present(bo, damage)) {
680
        output.current.damageJournal.clear();
681
        if (output.surfaceInterface != surface) {
682
683
684
            auto path = surface->client()->executablePath();
            qCDebug(KWIN_DRM).nospace() << "Direct scanout starting on output " << output.output->name() << " for application \"" << path << "\"";
        }
685
        output.surfaceInterface = surface;
686
687
688
689
        return true;
    } else {
        return false;
    }
690
691
}

692
QSharedPointer<DrmBuffer> EglGbmBackend::renderTestFrame(DrmAbstractOutput *output)
693
{
694
695
696
    beginFrame(output);
    glClear(GL_COLOR_BUFFER_BIT);
    return endFrameWithBuffer(output, output->geometry());
697
698
}

699
QSharedPointer<GLTexture> EglGbmBackend::textureForOutput(AbstractOutput *output) const
700
{
701
702
703
704
    Q_ASSERT(m_outputs.contains(output));
    auto &renderOutput = m_outputs[output];
    if (renderOutput.current.shadowBuffer) {
        const auto glTexture = QSharedPointer<KWin::GLTexture>::create(renderOutput.current.shadowBuffer->texture(), GL_RGBA8, output->pixelSize());
705
706
707
        glTexture->setYInverted(true);
        return glTexture;
    }
708
    GbmBuffer *gbmBuffer = renderOutput.current.gbmSurface->currentBuffer().get();
709
    if (!gbmBuffer) {
710
        qCWarning(KWIN_DRM) << "Failed to record frame: No gbm buffer!";
711
712
713
        return {};
    }
    EGLImageKHR image = eglCreateImageKHR(eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR, gbmBuffer->getBo(), nullptr);
714
715
716
717
718
    if (image == EGL_NO_IMAGE_KHR) {
        qCWarning(KWIN_DRM) << "Failed to record frame: Error creating EGLImageKHR - " << glGetError();
        return {};
    }

719
720
721
722
723
    return QSharedPointer<EGLImageTexture>::create(eglDisplay(), image, GL_RGBA8, static_cast<DrmAbstractOutput*>(output)->modeSize());
}

bool EglGbmBackend::directScanoutAllowed(AbstractOutput *output) const
{
724
    return !m_backend->usesSoftwareCursor() && !output->directScanoutInhibited();
725
726
}

727
bool EglGbmBackend::hasOutput(AbstractOutput *output) const
728
{
729
    return m_outputs.contains(output);
730
731
}

732
733
734
735
736
uint32_t EglGbmBackend::drmFormat() const
{
    return m_gbmFormat;
}

737
}