diff --git a/src/plugins/platforms/drm/abstract_egl_drm_backend.h b/src/plugins/platforms/drm/abstract_egl_drm_backend.h index 39f976b903..bc7085262c 100644 --- a/src/plugins/platforms/drm/abstract_egl_drm_backend.h +++ b/src/plugins/platforms/drm/abstract_egl_drm_backend.h @@ -25,7 +25,7 @@ public: void screenGeometryChanged(const QSize &size) override; virtual int screenCount() const = 0; - virtual void addOutput(DrmOutput *output) = 0; + virtual bool addOutput(DrmOutput *output) = 0; virtual void removeOutput(DrmOutput *output) = 0; virtual bool swapBuffers(DrmOutput *output) { Q_UNUSED(output) diff --git a/src/plugins/platforms/drm/drm_backend.cpp b/src/plugins/platforms/drm/drm_backend.cpp index 462195b63d..4d15dd5b03 100644 --- a/src/plugins/platforms/drm/drm_backend.cpp +++ b/src/plugins/platforms/drm/drm_backend.cpp @@ -186,8 +186,22 @@ bool DrmBackend::initialize() } } else { const auto devices = m_udev->listGPUs(); + bool bootVga = false; for (const UdevDevice::Ptr &device : devices) { - addGpu(device->devNode()); + if (addGpu(device->devNode())) { + bootVga |= device->isBootVga(); + } + } + + // if a boot device is set, honor that setting + // if not, prefer gbm for rendering because that works better + if (!bootVga && !m_gpus.isEmpty() && m_gpus[0]->useEglStreams()) { + for (int i = 1; i < m_gpus.count(); i++) { + if (!m_gpus[i]->useEglStreams()) { + m_gpus.swapItemsAt(i, 0); + break; + } + } } } @@ -220,11 +234,10 @@ void DrmBackend::handleUdevEvent() } if (device->action() == QStringLiteral("add")) { - if (m_gpus.isEmpty() || !primaryGpu()->useEglStreams()) { - if (addGpu(device->devNode())) { - updateOutputs(); - updateCursor(); - } + qCDebug(KWIN_DRM) << "New gpu found:" << device->devNode(); + if (addGpu(device->devNode())) { + updateOutputs(); + updateCursor(); } } else if (device->action() == QStringLiteral("remove")) { DrmGpu *gpu = findGpu(device->devNum()); @@ -234,6 +247,7 @@ void DrmBackend::handleUdevEvent() kwinApp()->quit(); return; } else { + qCDebug(KWIN_DRM) << "Removing gpu" << gpu->devNode(); emit gpuRemoved(gpu); m_gpus.removeOne(gpu); delete gpu; @@ -247,7 +261,7 @@ void DrmBackend::handleUdevEvent() gpu = addGpu(device->devNode()); } if (gpu) { - qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; + qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device" << gpu->devNode(); updateOutputs(); updateCursor(); } @@ -257,6 +271,9 @@ void DrmBackend::handleUdevEvent() DrmGpu *DrmBackend::addGpu(const QString &fileName) { + if (primaryGpu() && primaryGpu()->useEglStreams()) { + return nullptr; + } int fd = session()->openRestricted(fileName); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << fileName; @@ -280,17 +297,12 @@ DrmGpu *DrmBackend::addGpu(const QString &fileName) } DrmGpu *gpu = new DrmGpu(this, fileName, fd, buf.st_rdev); - if (!gpu->useEglStreams() || m_gpus.isEmpty()) { - m_gpus.append(gpu); - m_active = true; - connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput); - connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput); - emit gpuAdded(gpu); - return gpu; - } else { - delete gpu; - return nullptr; - } + m_gpus.append(gpu); + m_active = true; + connect(gpu, &DrmGpu::outputAdded, this, &DrmBackend::addOutput); + connect(gpu, &DrmGpu::outputRemoved, this, &DrmBackend::removeOutput); + emit gpuAdded(gpu); + return gpu; } void DrmBackend::addOutput(DrmOutput *o) @@ -319,6 +331,7 @@ void DrmBackend::updateOutputs() auto gpu = *it; gpu->updateOutputs(); if (gpu->outputs().isEmpty() && gpu != primaryGpu()) { + qCDebug(KWIN_DRM) << "removing unused GPU" << gpu->devNode(); it = m_gpus.erase(it); emit gpuRemoved(gpu); delete gpu; @@ -594,8 +607,7 @@ OpenGLBackend *DrmBackend::createOpenGLBackend() AbstractEglBackend::setPrimaryBackend(primaryBackend); EglMultiBackend *backend = new EglMultiBackend(this, primaryBackend); for (int i = 1; i < m_gpus.count(); i++) { - auto backendi = new EglGbmBackend(this, m_gpus.at(i)); - backend->addBackend(backendi); + backend->addGpu(m_gpus[i]); } return backend; #else diff --git a/src/plugins/platforms/drm/drm_output.cpp b/src/plugins/platforms/drm/drm_output.cpp index a396f1b824..83b5c0f519 100644 --- a/src/plugins/platforms/drm/drm_output.cpp +++ b/src/plugins/platforms/drm/drm_output.cpp @@ -656,14 +656,12 @@ bool DrmOutput::presentAtomically(const QSharedPointer &buffer) return false; } -#if HAVE_EGL_STREAMS - if (m_gpu->useEglStreams() && !m_modesetRequested) { - // EglStreamBackend queues normal page flips through EGL, - // modesets are still performed through DRM-KMS + // EglStreamBackend queues normal page flips through EGL when used as the rendering backend, + // modesets are still performed through DRM-KMS + if (m_gpu->useEglStreams() && !m_modesetRequested && m_gpu == m_backend->primaryGpu()) { m_pageFlipPending = true; return true; } -#endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; @@ -814,12 +812,12 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) flags |= DRM_MODE_ATOMIC_NONBLOCK; } -#if HAVE_EGL_STREAMS - if (!m_gpu->useEglStreams()) - // EglStreamBackend uses the NV_output_drm_flip_event EGL extension - // to register the flip event through eglStreamConsumerAcquireAttribNV -#endif + // EglStreamBackend uses the NV_output_drm_flip_event EGL extension + // to register the flip event through eglStreamConsumerAcquireAttribNV + // but only when used as the rendering GPU + if (!m_gpu->useEglStreams() || m_gpu != m_backend->primaryGpu()) { flags |= DRM_MODE_PAGE_FLIP_EVENT; + } } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; diff --git a/src/plugins/platforms/drm/dumb_swapchain.cpp b/src/plugins/platforms/drm/dumb_swapchain.cpp index 75a410f8e5..a76559d3ca 100644 --- a/src/plugins/platforms/drm/dumb_swapchain.cpp +++ b/src/plugins/platforms/drm/dumb_swapchain.cpp @@ -32,7 +32,7 @@ DumbSwapchain::DumbSwapchain(DrmGpu *gpu, const QSize &size) m_buffers << buffer; } if (m_buffers.count() < 3) { - qCWarning(KWIN_DRM) << "Failed to create gbm buffers for swapchain!"; + qCWarning(KWIN_DRM) << "Failed to create dumb buffers for swapchain!"; m_buffers.clear(); } } diff --git a/src/plugins/platforms/drm/egl_gbm_backend.cpp b/src/plugins/platforms/drm/egl_gbm_backend.cpp index 97b8f0c676..ca2a78704a 100644 --- a/src/plugins/platforms/drm/egl_gbm_backend.cpp +++ b/src/plugins/platforms/drm/egl_gbm_backend.cpp @@ -208,7 +208,7 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput) return true; } -void EglGbmBackend::addOutput(DrmOutput *drmOutput) +bool EglGbmBackend::addOutput(DrmOutput *drmOutput) { if (isPrimary()) { Output newOutput; @@ -228,13 +228,18 @@ void EglGbmBackend::addOutput(DrmOutput *drmOutput) } ); outputs << newOutput; + } else { + return false; } } else { Output newOutput; newOutput.output = drmOutput; - renderingBackend()->addOutput(drmOutput); + if (!renderingBackend()->addOutput(drmOutput)) { + return false; + } m_outputs << newOutput; } + return true; } void EglGbmBackend::removeOutput(DrmOutput *drmOutput) @@ -269,7 +274,7 @@ bool EglGbmBackend::swapBuffers(DrmOutput *drmOutput) renderFramebufferToSurface(*it); auto error = eglSwapBuffers(eglDisplay(), it->eglSurface); if (error != EGL_TRUE) { - qCDebug(KWIN_DRM) << "an error occurred while swapping buffers" << error; + qCCritical(KWIN_DRM) << "an error occurred while swapping buffers" << error; it->secondaryBuffer = nullptr; return false; } diff --git a/src/plugins/platforms/drm/egl_gbm_backend.h b/src/plugins/platforms/drm/egl_gbm_backend.h index af7ca5f6e1..0a02474218 100644 --- a/src/plugins/platforms/drm/egl_gbm_backend.h +++ b/src/plugins/platforms/drm/egl_gbm_backend.h @@ -57,7 +57,7 @@ public: return m_outputs.count(); } - void addOutput(DrmOutput *output) override; + bool addOutput(DrmOutput *output) override; void removeOutput(DrmOutput *output) override; bool swapBuffers(DrmOutput *output) override; bool exportFramebuffer(DrmOutput *output, void *data, const QSize &size, uint32_t stride) override; diff --git a/src/plugins/platforms/drm/egl_multi_backend.cpp b/src/plugins/platforms/drm/egl_multi_backend.cpp index 221391394d..cbe71b3f34 100644 --- a/src/plugins/platforms/drm/egl_multi_backend.cpp +++ b/src/plugins/platforms/drm/egl_multi_backend.cpp @@ -10,6 +10,7 @@ #include "egl_multi_backend.h" #include "logging.h" #include "egl_gbm_backend.h" +#include "egl_stream_backend.h" #include "drm_backend.h" #include "drm_gpu.h" @@ -49,6 +50,7 @@ void EglMultiBackend::init() setExtensions(m_backends[0]->extensions()); m_backends[0]->makeCurrent(); + m_initialized = true; } QRegion EglMultiBackend::beginFrame(int screenId) @@ -120,11 +122,6 @@ AbstractEglDrmBackend *EglMultiBackend::findBackend(int screenId, int& internalS return nullptr; } -void EglMultiBackend::addBackend(AbstractEglDrmBackend *backend) -{ - m_backends.append(backend); -} - bool EglMultiBackend::directScanoutAllowed(int screenId) const { int internalScreenId; @@ -135,9 +132,15 @@ bool EglMultiBackend::directScanoutAllowed(int screenId) const void EglMultiBackend::addGpu(DrmGpu *gpu) { - // secondary GPUs are atm guaranteed to be gbm - auto backend = new EglGbmBackend(m_platform, gpu); - backend->init(); + AbstractEglDrmBackend *backend; + if (gpu->useEglStreams()) { + backend = new EglStreamBackend(m_platform, gpu); + } else { + backend = new EglGbmBackend(m_platform, gpu); + } + if (m_initialized) { + backend->init(); + } m_backends.append(backend); } diff --git a/src/plugins/platforms/drm/egl_multi_backend.h b/src/plugins/platforms/drm/egl_multi_backend.h index 574e625f67..de08376652 100644 --- a/src/plugins/platforms/drm/egl_multi_backend.h +++ b/src/plugins/platforms/drm/egl_multi_backend.h @@ -37,8 +37,6 @@ public: void screenGeometryChanged(const QSize &size) override; - void addBackend(AbstractEglDrmBackend *backend); - bool directScanoutAllowed(int screen) const override; public Q_SLOTS: @@ -48,6 +46,7 @@ public Q_SLOTS: private: DrmBackend *m_platform; QVector m_backends; + bool m_initialized = false; AbstractEglDrmBackend *findBackend(int screenId, int& internalScreenId) const; }; diff --git a/src/plugins/platforms/drm/egl_stream_backend.cpp b/src/plugins/platforms/drm/egl_stream_backend.cpp index 5db02a270a..28143db138 100644 --- a/src/plugins/platforms/drm/egl_stream_backend.cpp +++ b/src/plugins/platforms/drm/egl_stream_backend.cpp @@ -22,12 +22,14 @@ #include "wayland_server.h" #include #include +#include "drm_gpu.h" +#include "dumb_swapchain.h" + #include #include #include #include #include -#include "drm_gpu.h" namespace KWin { @@ -250,23 +252,31 @@ void EglStreamBackend::init() return; } - if (!initializeEgl()) { - setFailed("Failed to initialize EGL api"); - return; - } - if (!initRenderingContext()) { - setFailed("Failed to initialize rendering context"); - return; - } + if (isPrimary()) { + if (!initializeEgl()) { + setFailed("Failed to initialize EGL api"); + return; + } + if (!initRenderingContext()) { + setFailed("Failed to initialize rendering context"); + return; + } - initKWinGL(); - setSupportsBufferAge(false); - initWayland(); + initKWinGL(); + setSupportsBufferAge(false); + initWayland(); - using namespace KWaylandServer; - m_eglStreamControllerInterface = new EglStreamControllerInterface(waylandServer()->display()); - connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this, - &EglStreamBackend::attachStreamConsumer); + using namespace KWaylandServer; + m_eglStreamControllerInterface = new EglStreamControllerInterface(waylandServer()->display()); + connect(m_eglStreamControllerInterface, &EglStreamControllerInterface::streamConsumerAttached, this, + &EglStreamBackend::attachStreamConsumer); + } else { + // secondary NVidia GPUs only import dumb buffers + const auto outputs = m_gpu->outputs(); + for (DrmOutput *drmOutput : outputs) { + addOutput(drmOutput); + } + } } bool EglStreamBackend::initRenderingContext() @@ -297,69 +307,80 @@ bool EglStreamBackend::resetOutput(Output &o, DrmOutput *drmOutput) // dumb buffer used for modesetting o.buffer = QSharedPointer::create(m_gpu, drmOutput->pixelSize()); - EGLAttrib streamAttribs[] = { - EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode - EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE, - EGL_NONE - }; - EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs); - if (stream == EGL_NO_STREAM_KHR) { - qCCritical(KWIN_DRM) << "Failed to create EGL stream for output"; - return false; - } - - EGLAttrib outputAttribs[3]; - if (drmOutput->primaryPlane()) { - outputAttribs[0] = EGL_DRM_PLANE_EXT; - outputAttribs[1] = drmOutput->primaryPlane()->id(); - } else { - outputAttribs[0] = EGL_DRM_CRTC_EXT; - outputAttribs[1] = drmOutput->crtc()->id(); - } - outputAttribs[2] = EGL_NONE; - EGLint numLayers; - EGLOutputLayerEXT outputLayer; - pEglGetOutputLayersEXT(eglDisplay(), outputAttribs, &outputLayer, 1, &numLayers); - if (numLayers == 0) { - qCCritical(KWIN_DRM) << "No EGL output layers found"; - return false; - } - - pEglStreamConsumerOutputEXT(eglDisplay(), stream, outputLayer); - EGLint streamProducerAttribs[] = { - EGL_WIDTH, drmOutput->pixelSize().width(), - EGL_HEIGHT, drmOutput->pixelSize().height(), - EGL_NONE - }; - EGLSurface eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDisplay(), config(), stream, - streamProducerAttribs); - if (eglSurface == EGL_NO_SURFACE) { - qCCritical(KWIN_DRM) << "Failed to create EGL surface for output"; - return false; - } - - if (o.eglSurface != EGL_NO_SURFACE) { - if (surface() == o.eglSurface) { - setSurface(eglSurface); + if (isPrimary()) { + EGLAttrib streamAttribs[] = { + EGL_STREAM_FIFO_LENGTH_KHR, 0, // mailbox mode + EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE, + EGL_NONE + }; + EGLStreamKHR stream = pEglCreateStreamAttribNV(eglDisplay(), streamAttribs); + if (stream == EGL_NO_STREAM_KHR) { + qCCritical(KWIN_DRM) << "Failed to create EGL stream for output"; + return false; } - eglDestroySurface(eglDisplay(), o.eglSurface); - } - if (o.eglStream != EGL_NO_STREAM_KHR) { - pEglDestroyStreamKHR(eglDisplay(), o.eglStream); - } + EGLAttrib outputAttribs[3]; + if (drmOutput->primaryPlane()) { + outputAttribs[0] = EGL_DRM_PLANE_EXT; + outputAttribs[1] = drmOutput->primaryPlane()->id(); + } else { + outputAttribs[0] = EGL_DRM_CRTC_EXT; + outputAttribs[1] = drmOutput->crtc()->id(); + } + outputAttribs[2] = EGL_NONE; + EGLint numLayers; + EGLOutputLayerEXT outputLayer; + pEglGetOutputLayersEXT(eglDisplay(), outputAttribs, &outputLayer, 1, &numLayers); + if (numLayers == 0) { + qCCritical(KWIN_DRM) << "No EGL output layers found"; + return false; + } - o.eglStream = stream; - o.eglSurface = eglSurface; + pEglStreamConsumerOutputEXT(eglDisplay(), stream, outputLayer); + EGLint streamProducerAttribs[] = { + EGL_WIDTH, drmOutput->pixelSize().width(), + EGL_HEIGHT, drmOutput->pixelSize().height(), + EGL_NONE + }; + EGLSurface eglSurface = pEglCreateStreamProducerSurfaceKHR(eglDisplay(), config(), stream, + streamProducerAttribs); + if (eglSurface == EGL_NO_SURFACE) { + qCCritical(KWIN_DRM) << "Failed to create EGL surface for output"; + return false; + } + + if (o.eglSurface != EGL_NO_SURFACE) { + if (surface() == o.eglSurface) { + setSurface(eglSurface); + } + eglDestroySurface(eglDisplay(), o.eglSurface); + } + + if (o.eglStream != EGL_NO_STREAM_KHR) { + pEglDestroyStreamKHR(eglDisplay(), o.eglStream); + } + + o.eglStream = stream; + o.eglSurface = eglSurface; + } else { + QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() : drmOutput->modeSize(); + o.dumbSwapchain = QSharedPointer::create(m_gpu, size); + if (o.dumbSwapchain->isEmpty()) { + return false; + } + } return true; } -void EglStreamBackend::addOutput(DrmOutput *drmOutput) +bool EglStreamBackend::addOutput(DrmOutput *drmOutput) { Q_ASSERT(drmOutput->gpu() == m_gpu); Output o; if (!resetOutput(o, drmOutput)) { - return; + return false; + } + if (!isPrimary() && !renderingBackend()->addOutput(drmOutput)) { + return false; } connect(drmOutput, &DrmOutput::modeChanged, this, @@ -376,6 +397,7 @@ void EglStreamBackend::addOutput(DrmOutput *drmOutput) } ); m_outputs << o; + return true; } void EglStreamBackend::removeOutput(DrmOutput *drmOutput) @@ -391,6 +413,9 @@ void EglStreamBackend::removeOutput(DrmOutput *drmOutput) } cleanupOutput(*it); m_outputs.erase(it); + if (!isPrimary()) { + renderingBackend()->removeOutput(drmOutput); + } } bool EglStreamBackend::makeContextCurrent(const Output &output) @@ -446,16 +471,6 @@ bool EglStreamBackend::initBufferConfigs() return true; } -bool EglStreamBackend::presentOnOutput(EglStreamBackend::Output &o) -{ - if (!eglSwapBuffers(eglDisplay(), o.eglSurface)) { - qCCritical(KWIN_DRM, "eglSwapBuffers() failed: %x", eglGetError()); - return false; - } - - return o.output->present(o.buffer); -} - PlatformSurfaceTexture *EglStreamBackend::createPlatformSurfaceTextureInternal(SurfacePixmapInternal *pixmap) { return new BasicEGLSurfaceTextureInternal(this, pixmap); @@ -469,8 +484,12 @@ PlatformSurfaceTexture *EglStreamBackend::createPlatformSurfaceTextureWayland(Su QRegion EglStreamBackend::beginFrame(int screenId) { const Output &o = m_outputs.at(screenId); - makeContextCurrent(o); - return o.output->geometry(); + if (isPrimary()) { + makeContextCurrent(o); + return o.output->geometry(); + } else { + return renderingBackend()->beginFrameForSecondaryGpu(o.output); + } } void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, const QRegion &damagedRegion) @@ -481,18 +500,41 @@ void EglStreamBackend::endFrame(int screenId, const QRegion &renderedRegion, con Output &renderOutput = m_outputs[screenId]; DrmOutput *drmOutput = renderOutput.output; - if (!presentOnOutput(renderOutput)) { - RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(drmOutput->renderLoop()); - renderLoopPrivate->notifyFrameFailed(); - return; + bool frameFailed = false; + + QSharedPointer buffer; + if (isPrimary()) { + buffer = renderOutput.buffer; + if (!eglSwapBuffers(eglDisplay(), renderOutput.eglSurface)) { + qCCritical(KWIN_DRM, "eglSwapBuffers() failed: %x", eglGetError()); + frameFailed = true; + } + } else { + if (!renderingBackend()->swapBuffers(drmOutput)) { + qCCritical(KWIN_DRM) << "swapping buffers on render backend for" << drmOutput << "failed!"; + frameFailed = true; + } + buffer = renderOutput.dumbSwapchain->acquireBuffer(); + if (!frameFailed && !renderingBackend()->exportFramebuffer(drmOutput, buffer->data(), buffer->size(), buffer->stride())) { + qCCritical(KWIN_DRM) << "importing framebuffer from render backend for" << drmOutput << "failed!"; + frameFailed = true; + } + } + if (!frameFailed && !renderOutput.output->present(buffer)) { + frameFailed = true; } - EGLAttrib acquireAttribs[] = { - EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)drmOutput, - EGL_NONE, - }; - if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), renderOutput.eglStream, acquireAttribs)) { - qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame"; + if (frameFailed) { + RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(drmOutput->renderLoop()); + renderLoopPrivate->notifyFrameFailed(); + } else if (isPrimary()) { + EGLAttrib acquireAttribs[] = { + EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)drmOutput, + EGL_NONE, + }; + if (!pEglStreamConsumerAcquireAttribNV(eglDisplay(), renderOutput.eglStream, acquireAttribs)) { + qCWarning(KWIN_DRM) << "Failed to acquire output EGL stream frame"; + } } } diff --git a/src/plugins/platforms/drm/egl_stream_backend.h b/src/plugins/platforms/drm/egl_stream_backend.h index 73cadf2913..6d37513c7e 100644 --- a/src/plugins/platforms/drm/egl_stream_backend.h +++ b/src/plugins/platforms/drm/egl_stream_backend.h @@ -18,7 +18,8 @@ namespace KWin { class DrmOutput; -class DrmBuffer; +class DrmDumbBuffer; +class DumbSwapchain; /** * @brief OpenGL Backend using Egl with an EGLDevice. @@ -39,7 +40,7 @@ public: return m_outputs.count(); } - void addOutput(DrmOutput *output) override; + bool addOutput(DrmOutput *output) override; void removeOutput(DrmOutput *output) override; protected: @@ -61,13 +62,15 @@ private: struct Output { DrmOutput *output = nullptr; - QSharedPointer buffer; + QSharedPointer buffer; EGLSurface eglSurface = EGL_NO_SURFACE; EGLStreamKHR eglStream = EGL_NO_STREAM_KHR; + + // for operation as secondary GPU + QSharedPointer dumbSwapchain; }; bool resetOutput(Output &output, DrmOutput *drmOutput); bool makeContextCurrent(const Output &output); - bool presentOnOutput(Output &output); void cleanupOutput(const Output &output); QVector m_outputs;