diff --git a/src/screencaststream.h b/src/screencaststream.h --- a/src/screencaststream.h +++ b/src/screencaststream.h @@ -21,6 +21,8 @@ #ifndef SCREEN_CAST_STREAM_H #define SCREEN_CAST_STREAM_H +#include "waylandintegration.h" + #include #include @@ -70,7 +72,7 @@ void removeStream(); public Q_SLOTS: - bool recordFrame(uint8_t *screenData); + bool recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride); Q_SIGNALS: void streamReady(uint nodeId); diff --git a/src/screencaststream.cpp b/src/screencaststream.cpp --- a/src/screencaststream.cpp +++ b/src/screencaststream.cpp @@ -19,7 +19,6 @@ */ #include "screencaststream.h" -#include "waylandintegration.h" #include #include @@ -499,27 +498,79 @@ return stream; } -bool ScreenCastStream::recordFrame(uint8_t *screenData) +bool ScreenCastStream::recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride) { struct pw_buffer *buffer; struct spa_buffer *spa_buffer; uint8_t *data = nullptr; if (!(buffer = pw_stream_dequeue_buffer(pwStream))) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: couldn't obtain PipeWire buffer"; return false; } spa_buffer = buffer->buffer; if (!(data = (uint8_t *) spa_buffer->datas[0].data)) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: invalid buffer data"; return false; } - memcpy(data, screenData, BITS_PER_PIXEL * videoFormat.size.height * videoFormat.size.width * sizeof(uint8_t)); + const quint32 destStride = SPA_ROUND_UP_N(videoFormat.size.width * BITS_PER_PIXEL, 4); + const quint32 destSize = BITS_PER_PIXEL * width * height * sizeof(uint8_t); + const quint32 srcSize = spa_buffer->datas[0].maxsize; + + if (destSize != srcSize || stride != destStride) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: different stride"; + return false; + } + + // bind context to render thread + eglMakeCurrent(WaylandIntegration::egl().display, EGL_NO_SURFACE, EGL_NO_SURFACE, WaylandIntegration::egl().context); + + // create EGL image from imported BO + EGLImageKHR image = eglCreateImageKHR(WaylandIntegration::egl().display, nullptr, EGL_NATIVE_PIXMAP_KHR, bo, nullptr); + + if (image == EGL_NO_IMAGE_KHR) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: Error creating EGLImageKHR - " << WaylandIntegration::formatGLError(glGetError()); + return false; + } + + // create GL 2D texture for framebuffer + GLuint texture; + glGenTextures(1, &texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, texture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + + // bind framebuffer to copy pixels from + GLuint framebuffer; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: glCheckFramebufferStatus failed - " << WaylandIntegration::formatGLError(glGetError()); + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &framebuffer); + eglDestroyImageKHR(WaylandIntegration::egl().display, image); + return false; + } + + glViewport(0, 0, width, height); + + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &framebuffer); + eglDestroyImageKHR(WaylandIntegration::egl().display, image); spa_buffer->datas[0].chunk->offset = 0; spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - spa_buffer->datas[0].chunk->stride = SPA_ROUND_UP_N (videoFormat.size.width * BITS_PER_PIXEL, 4); + spa_buffer->datas[0].chunk->stride = destStride; pw_stream_queue_buffer(pwStream, buffer); return true; diff --git a/src/waylandintegration.h b/src/waylandintegration.h --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -26,10 +26,20 @@ #include #include +#include + +#include +#include namespace WaylandIntegration { +struct EGLStruct { + QList extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; +}; + class WaylandOutput { public: @@ -77,6 +87,7 @@ Q_SIGNALS: void newBuffer(uint8_t *screenData); }; + const char * formatGLError(GLenum err); void authenticate(); void init(); @@ -96,11 +107,12 @@ void requestKeyboardKeycode(int keycode, bool state); + EGLStruct egl(); + QMap screens(); QVariant streams(); WaylandIntegration *waylandIntegration(); - } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -116,6 +116,11 @@ globalWaylandIntegration->requestKeyboardKeycode(keycode, state); } +WaylandIntegration::EGLStruct WaylandIntegration::egl() +{ + return globalWaylandIntegration->egl(); +} + QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); @@ -131,7 +136,7 @@ return globalWaylandIntegration; } -static const char * formatGLError(GLenum err) +const char * WaylandIntegration::formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: @@ -391,6 +396,11 @@ } } +WaylandIntegration::EGLStruct WaylandIntegration::WaylandIntegrationPrivate::egl() +{ + return m_egl; +} + QMap WaylandIntegration::WaylandIntegrationPrivate::screens() { return m_outputMap; @@ -591,58 +601,12 @@ return; } - // bind context to render thread - eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context); - - // create EGL image from imported BO - EGLImageKHR image = eglCreateImageKHR(m_egl.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); - - // We can already close gbm handle - gbm_bo_destroy(imported); - close(gbmHandle); - - if (image == EGL_NO_IMAGE_KHR) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Error creating EGLImageKHR - " << formatGLError(glGetError()); - return; - } - - // create GL 2D texture for framebuffer - GLuint texture; - glGenTextures(1, &texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, texture); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - - // bind framebuffer to copy pixels from - GLuint framebuffer; - glGenFramebuffers(1, &framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: glCheckFramebufferStatus failed - " << formatGLError(glGetError()); - glDeleteTextures(1, &texture); - glDeleteFramebuffers(1, &framebuffer); - eglDestroyImageKHR(m_egl.display, image); - return; - } - - auto capture = new QImage(QSize(width, height), QImage::Format_RGBA8888); - glViewport(0, 0, width, height); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, capture->bits()); - - if (m_stream->recordFrame(capture->bits())) { + if (m_stream->recordFrame(imported, width, height, stride)) { m_lastFrameTime = QDateTime::currentDateTime(); } - glDeleteTextures(1, &texture); - glDeleteFramebuffers(1, &framebuffer); - eglDestroyImageKHR(m_egl.display, image); - - delete capture; + gbm_bo_destroy(imported); + close(gbmHandle); } void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry() diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h --- a/src/waylandintegration_p.h +++ b/src/waylandintegration_p.h @@ -26,11 +26,6 @@ #include #include -#include - -#include -#include - class ScreenCastStream; namespace KWayland { @@ -81,6 +76,7 @@ void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta); void requestKeyboardKeycode(int keycode, bool state); + EGLStruct egl(); QMap screens(); QVariant streams(); @@ -116,11 +112,8 @@ qint32 m_drmFd = 0; // for GBM buffer mmap gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval - struct { - QList extensions; - EGLDisplay display = EGL_NO_DISPLAY; - EGLContext context = EGL_NO_CONTEXT; - } m_egl; + + EGLStruct m_egl; }; }