Changeset View
Changeset View
Standalone View
Standalone View
plugins/platforms/drm/egl_gbm_backend.cpp
Show All 19 Lines | |||||
20 | #include "egl_gbm_backend.h" | 20 | #include "egl_gbm_backend.h" | ||
21 | // kwin | 21 | // kwin | ||
22 | #include "composite.h" | 22 | #include "composite.h" | ||
23 | #include "drm_backend.h" | 23 | #include "drm_backend.h" | ||
24 | #include "drm_output.h" | 24 | #include "drm_output.h" | ||
25 | #include "gbm_surface.h" | 25 | #include "gbm_surface.h" | ||
26 | #include "logging.h" | 26 | #include "logging.h" | ||
27 | #include "options.h" | 27 | #include "options.h" | ||
28 | #include "screencaststream.h" | ||||
28 | #include "screens.h" | 29 | #include "screens.h" | ||
30 | #include "wayland_server.h" | ||||
31 | | ||||
29 | // kwin libs | 32 | // kwin libs | ||
30 | #include <kwinglplatform.h> | 33 | #include <kwinglplatform.h> | ||
31 | // Qt | 34 | // Qt | ||
32 | #include <QOpenGLContext> | 35 | #include <QOpenGLContext> | ||
36 | #include <KLocalizedString> | ||||
33 | // system | 37 | // system | ||
34 | #include <gbm.h> | 38 | #include <gbm.h> | ||
35 | 39 | | |||
36 | namespace KWin | 40 | namespace KWin | ||
37 | { | 41 | { | ||
38 | 42 | | |||
39 | EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend) | 43 | EglGbmBackend::EglGbmBackend(DrmBackend *drmBackend) | ||
40 | : AbstractEglBackend() | 44 | : AbstractEglBackend() | ||
41 | , m_backend(drmBackend) | 45 | , m_backend(drmBackend) | ||
42 | { | 46 | { | ||
43 | // Egl is always direct rendering. | 47 | // Egl is always direct rendering. | ||
44 | setIsDirectRendering(true); | 48 | setIsDirectRendering(true); | ||
45 | setSyncsToVBlank(true); | 49 | setSyncsToVBlank(true); | ||
46 | connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); | 50 | connect(m_backend, &DrmBackend::outputAdded, this, &EglGbmBackend::createOutput); | ||
47 | connect(m_backend, &DrmBackend::outputRemoved, this, &EglGbmBackend::removeOutput); | 51 | connect(m_backend, &DrmBackend::outputRemoved, this, &EglGbmBackend::removeOutput); | ||
52 | | ||||
53 | connect(waylandServer()->screencasting(), &KWaylandServer::ScreencastingInterface::outputScreencastRequested, this, &EglGbmBackend::streamingRequested); | ||||
48 | } | 54 | } | ||
49 | 55 | | |||
50 | EglGbmBackend::~EglGbmBackend() | 56 | EglGbmBackend::~EglGbmBackend() | ||
51 | { | 57 | { | ||
52 | cleanup(); | 58 | cleanup(); | ||
53 | } | 59 | } | ||
54 | 60 | | |||
55 | void EglGbmBackend::cleanupSurfaces() | 61 | void EglGbmBackend::cleanupSurfaces() | ||
▲ Show 20 Lines • Show All 156 Lines • ▼ Show 20 Line(s) | 217 | if (surface() == output.eglSurface) { | |||
212 | setSurface(eglSurface); | 218 | setSurface(eglSurface); | ||
213 | } | 219 | } | ||
214 | eglDestroySurface(eglDisplay(), output.eglSurface); | 220 | eglDestroySurface(eglDisplay(), output.eglSurface); | ||
215 | } | 221 | } | ||
216 | output.eglSurface = eglSurface; | 222 | output.eglSurface = eglSurface; | ||
217 | output.gbmSurface = gbmSurface; | 223 | output.gbmSurface = gbmSurface; | ||
218 | 224 | | |||
219 | resetFramebuffer(output); | 225 | resetFramebuffer(output); | ||
220 | return true; | 226 | return true; | ||
davidedmundson: where does stream get deleted in the case were things don't fail? | |||||
221 | } | 227 | } | ||
222 | 228 | | |||
223 | void EglGbmBackend::createOutput(DrmOutput *drmOutput) | 229 | void EglGbmBackend::createOutput(DrmOutput *drmOutput) | ||
224 | { | 230 | { | ||
225 | Output newOutput; | 231 | Output newOutput; | ||
226 | if (resetOutput(newOutput, drmOutput)) { | 232 | if (resetOutput(newOutput, drmOutput)) { | ||
227 | connect(drmOutput, &DrmOutput::modeChanged, this, | 233 | connect(drmOutput, &DrmOutput::modeChanged, this, | ||
228 | [drmOutput, this] { | 234 | [drmOutput, this] { | ||
Show All 17 Lines | 251 | { | |||
246 | auto it = std::find_if(m_outputs.begin(), m_outputs.end(), | 252 | auto it = std::find_if(m_outputs.begin(), m_outputs.end(), | ||
247 | [drmOutput] (const Output &output) { | 253 | [drmOutput] (const Output &output) { | ||
248 | return output.output == drmOutput; | 254 | return output.output == drmOutput; | ||
249 | } | 255 | } | ||
250 | ); | 256 | ); | ||
251 | if (it == m_outputs.end()) { | 257 | if (it == m_outputs.end()) { | ||
252 | return; | 258 | return; | ||
253 | } | 259 | } | ||
260 | | ||||
254 | cleanupOutput(*it); | 261 | cleanupOutput(*it); | ||
255 | m_outputs.erase(it); | 262 | m_outputs.erase(it); | ||
256 | } | 263 | } | ||
257 | 264 | | |||
258 | const float vertices[] = { | 265 | const float vertices[] = { | ||
259 | -1.0f, 1.0f, | 266 | -1.0f, 1.0f, | ||
260 | -1.0f, -1.0f, | 267 | -1.0f, -1.0f, | ||
261 | 1.0f, -1.0f, | 268 | 1.0f, -1.0f, | ||
▲ Show 20 Lines • Show All 174 Lines • ▼ Show 20 Line(s) | |||||
436 | { | 443 | { | ||
437 | Q_UNREACHABLE(); | 444 | Q_UNREACHABLE(); | ||
438 | // Not in use. This backend does per-screen rendering. | 445 | // Not in use. This backend does per-screen rendering. | ||
439 | } | 446 | } | ||
440 | 447 | | |||
441 | void EglGbmBackend::presentOnOutput(Output &output) | 448 | void EglGbmBackend::presentOnOutput(Output &output) | ||
442 | { | 449 | { | ||
443 | eglSwapBuffers(eglDisplay(), output.eglSurface); | 450 | eglSwapBuffers(eglDisplay(), output.eglSurface); | ||
444 | output.buffer = m_backend->createBuffer(output.gbmSurface); | 451 | auto surfaceBuffer = m_backend->createBuffer(output.gbmSurface); | ||
452 | output.buffer = surfaceBuffer; | ||||
445 | 453 | | |||
446 | if(m_remoteaccessManager && gbm_surface_has_free_buffers(output.gbmSurface->surface())) { | 454 | if (gbm_surface_has_free_buffers(output.gbmSurface->surface())) { | ||
447 | // GBM surface is released on page flip so | 455 | // GBM surface is released on page flip so | ||
448 | // we should pass the buffer before it's presented. | 456 | // we should pass the buffer before it's presented. | ||
449 | m_remoteaccessManager->passBuffer(output.output, output.buffer); | 457 | | ||
458 | Q_EMIT passBuffer(output.output, surfaceBuffer); | ||||
450 | } | 459 | } | ||
451 | 460 | | |||
452 | m_backend->present(output.buffer, output.output); | 461 | m_backend->present(output.buffer, output.output); | ||
453 | 462 | | |||
454 | if (supportsBufferAge()) { | 463 | if (supportsBufferAge()) { | ||
455 | eglQuerySurface(eglDisplay(), output.eglSurface, EGL_BUFFER_AGE_EXT, &output.bufferAge); | 464 | eglQuerySurface(eglDisplay(), output.eglSurface, EGL_BUFFER_AGE_EXT, &output.bufferAge); | ||
456 | } | 465 | } | ||
457 | } | 466 | } | ||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Line(s) | 568 | { | |||
560 | return false; | 569 | return false; | ||
561 | } | 570 | } | ||
562 | 571 | | |||
563 | bool EglGbmBackend::perScreenRendering() const | 572 | bool EglGbmBackend::perScreenRendering() const | ||
564 | { | 573 | { | ||
565 | return true; | 574 | return true; | ||
566 | } | 575 | } | ||
567 | 576 | | |||
577 | static void recordFrame(AbstractEglBackend* backend, ScreenCastStream* stream, gbm_bo *bo) | ||||
578 | { | ||||
579 | struct pw_buffer *buffer; | ||||
580 | struct spa_buffer *spa_buffer; | ||||
581 | | ||||
582 | auto pwStream = stream->pwStream; | ||||
583 | if (!(buffer = pw_stream_dequeue_buffer(pwStream))) { | ||||
584 | //qCWarning(KWIN_DRM) << "Failed to record frame: couldn't obtain PipeWire buffer"; | ||||
585 | return; | ||||
586 | } | ||||
587 | | ||||
588 | spa_buffer = buffer->buffer; | ||||
589 | | ||||
590 | uint8_t *data = (uint8_t *) spa_buffer->datas[0].data; | ||||
591 | if (!data) { | ||||
592 | qCWarning(KWIN_DRM) << "Failed to record frame: invalid buffer data"; | ||||
593 | pw_stream_queue_buffer(pwStream, buffer); | ||||
594 | return; | ||||
595 | } | ||||
596 | | ||||
597 | const quint32 height = gbm_bo_get_height(bo); | ||||
598 | const quint32 stride = gbm_bo_get_stride(bo); | ||||
599 | | ||||
600 | const quint32 minStride = SPA_ROUND_UP_N(stream->videoFormat.size.width * stream->bytesPerPixel(), 4); | ||||
601 | const quint32 minSrcSize = height * minStride; | ||||
602 | | ||||
603 | const quint32 srcSize = height * stride; | ||||
604 | const quint32 destSize = spa_buffer->datas[0].maxsize; | ||||
605 | | ||||
606 | // If we can fit source into the pipewire buffer, we can use stride we got from gbm_bo as the client | ||||
607 | // should be able to handle it | ||||
608 | quint32 streamStride; | ||||
609 | if (srcSize <= destSize) { | ||||
610 | streamStride = stride; | ||||
611 | // Fallback to fixed minimum stride, which should be (width * bpp) | ||||
612 | } else if (minSrcSize == destSize) { | ||||
613 | streamStride = minStride; | ||||
614 | } else { | ||||
615 | qCWarning(KWIN_DRM) << "Failed to record frame: got buffer with higher stride than we can handle"; | ||||
616 | pw_stream_queue_buffer(pwStream, buffer); | ||||
617 | return; | ||||
618 | } | ||||
619 | | ||||
620 | // bind context to render thread | ||||
621 | eglMakeCurrent(backend->eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, backend->context()); | ||||
622 | | ||||
623 | // create EGL image from imported BO | ||||
624 | EGLImageKHR image = eglCreateImageKHR(backend->eglDisplay(), nullptr, EGL_NATIVE_PIXMAP_KHR, bo, nullptr); | ||||
625 | | ||||
626 | if (image == EGL_NO_IMAGE_KHR) { | ||||
627 | qCWarning(KWIN_DRM) << "Failed to record frame: Error creating EGLImageKHR - " << glGetError(); | ||||
628 | pw_stream_queue_buffer(pwStream, buffer); | ||||
629 | return; | ||||
630 | } | ||||
631 | | ||||
632 | // create GL 2D texture for framebuffer | ||||
633 | GLuint texture; | ||||
634 | glGenTextures(1, &texture); | ||||
635 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
636 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
637 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||
638 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||
639 | glBindTexture(GL_TEXTURE_2D, texture); | ||||
640 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); | ||||
641 | | ||||
642 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); | ||||
643 | | ||||
644 | glDeleteTextures(1, &texture); | ||||
645 | eglDestroyImageKHR(backend->eglDisplay(), image); | ||||
646 | | ||||
647 | spa_buffer->datas[0].chunk->offset = 0; | ||||
648 | spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; | ||||
649 | spa_buffer->datas[0].chunk->stride = streamStride; | ||||
650 | | ||||
651 | pw_stream_queue_buffer(pwStream, buffer); | ||||
652 | } | ||||
653 | | ||||
654 | void EglGbmBackend::streamingRequested(KWaylandServer::ScreencastingStreamInterface* waylandStream, ::wl_resource *outputResource) | ||||
655 | { | ||||
656 | auto output = KWaylandServer::OutputInterface::get(outputResource); | ||||
657 | if (!output) { | ||||
658 | waylandStream->sendFailed(i18n("Invalid output")); | ||||
659 | return; | ||||
660 | } | ||||
661 | | ||||
662 | DrmOutput* drmOutput = nullptr; | ||||
663 | for (auto o : qAsConst(m_outputs)) { | ||||
664 | if (o.output->waylandOutput() == output) | ||||
665 | drmOutput = o.output; | ||||
666 | } | ||||
667 | if (!drmOutput) { | ||||
668 | waylandStream->sendFailed(i18n("Could not find output")); | ||||
669 | return; | ||||
670 | } | ||||
671 | | ||||
672 | auto stream = new ScreenCastStream(output->pixelSize(), this); | ||||
673 | if (stream->init(4)) { | ||||
674 | connect(waylandStream, &KWaylandServer::ScreencastingStreamInterface::stop, stream, &ScreenCastStream::stop); | ||||
675 | QObject::connect(stream, &ScreenCastStream::streamReady, waylandStream, [output, waylandStream] (quint32 nodeId) { | ||||
676 | waylandStream->sendCreated(nodeId, output->pixelSize()); | ||||
677 | }); | ||||
678 | connect(this, &EglGbmBackend::passBuffer, stream, [this, drmOutput, stream] (DrmOutput *output, DrmSurfaceBuffer* gbmbuf) { | ||||
679 | if (drmOutput == output) { | ||||
680 | auto bo = gbmbuf->getBo(); | ||||
681 | recordFrame(this, stream, bo); | ||||
682 | } | ||||
683 | }); | ||||
684 | connect(stream, &ScreenCastStream::stopStreaming, this, [waylandStream, stream] () { | ||||
685 | waylandStream->sendClosed(); | ||||
686 | delete stream; | ||||
687 | }); | ||||
688 | } else { | ||||
689 | waylandStream->sendFailed(stream->error()); | ||||
690 | delete stream; | ||||
691 | } | ||||
692 | } | ||||
693 | | ||||
568 | /************************************************ | 694 | /************************************************ | ||
569 | * EglTexture | 695 | * EglTexture | ||
570 | ************************************************/ | 696 | ************************************************/ | ||
571 | 697 | | |||
572 | EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend) | 698 | EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend) | ||
573 | : AbstractEglTexture(texture, backend) | 699 | : AbstractEglTexture(texture, backend) | ||
574 | { | 700 | { | ||
575 | } | 701 | } | ||
576 | 702 | | |||
577 | EglGbmTexture::~EglGbmTexture() = default; | 703 | EglGbmTexture::~EglGbmTexture() = default; | ||
578 | 704 | | |||
579 | } | 705 | } |
where does stream get deleted in the case were things don't fail?