Commit f6e55970 authored by Jan Grulich's avatar Jan Grulich

Avoid copying buffer twice

Summary:
Previously we copied frames first to a temporary QImage and then to PipeWire buffer. This shouldn't be
necessary as we can copy frames directly to PipeWire buffers which should be much more effecient.

BUG: 419209

Reviewers: #plasma, zzag

Reviewed By: #plasma, zzag

Subscribers: meven, zzag, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D28272
parent 75a8fa72
......@@ -19,7 +19,6 @@
*/
#include "screencaststream.h"
#include "waylandintegration.h"
#include <limits.h>
#include <math.h>
......@@ -499,27 +498,79 @@ pw_stream *ScreenCastStream::createStream()
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;
......
......@@ -21,6 +21,8 @@
#ifndef SCREEN_CAST_STREAM_H
#define SCREEN_CAST_STREAM_H
#include "waylandintegration.h"
#include <QObject>
#include <QSize>
......@@ -70,7 +72,7 @@ public:
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);
......
......@@ -116,6 +116,11 @@ void WaylandIntegration::requestKeyboardKeycode(int keycode, bool state)
globalWaylandIntegration->requestKeyboardKeycode(keycode, state);
}
WaylandIntegration::EGLStruct WaylandIntegration::egl()
{
return globalWaylandIntegration->egl();
}
QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::screens()
{
return globalWaylandIntegration->screens();
......@@ -131,7 +136,7 @@ WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration(
return globalWaylandIntegration;
}
static const char * formatGLError(GLenum err)
const char * WaylandIntegration::formatGLError(GLenum err)
{
switch(err) {
case GL_NO_ERROR:
......@@ -391,6 +396,11 @@ void WaylandIntegration::WaylandIntegrationPrivate::requestKeyboardKeycode(int k
}
}
WaylandIntegration::EGLStruct WaylandIntegration::WaylandIntegrationPrivate::egl()
{
return m_egl;
}
QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::WaylandIntegrationPrivate::screens()
{
return m_outputMap;
......@@ -591,58 +601,12 @@ void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland
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()
......
......@@ -26,10 +26,20 @@
#include <QSize>
#include <QVariant>
#include <gbm.h>
#include <epoxy/egl.h>
#include <epoxy/gl.h>
namespace WaylandIntegration
{
struct EGLStruct {
QList<QByteArray> extensions;
EGLDisplay display = EGL_NO_DISPLAY;
EGLContext context = EGL_NO_CONTEXT;
};
class WaylandOutput
{
public:
......@@ -77,6 +87,7 @@ class WaylandIntegration : public QObject
Q_SIGNALS:
void newBuffer(uint8_t *screenData);
};
const char * formatGLError(GLenum err);
void authenticate();
void init();
......@@ -96,11 +107,12 @@ Q_SIGNALS:
void requestKeyboardKeycode(int keycode, bool state);
EGLStruct egl();
QMap<quint32, WaylandOutput> screens();
QVariant streams();
WaylandIntegration *waylandIntegration();
}
#endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H
......
......@@ -26,11 +26,6 @@
#include <QDateTime>
#include <QMap>
#include <gbm.h>
#include <epoxy/egl.h>
#include <epoxy/gl.h>
class ScreenCastStream;
namespace KWayland {
......@@ -81,6 +76,7 @@ public:
void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta);
void requestKeyboardKeycode(int keycode, bool state);
EGLStruct egl();
QMap<quint32, WaylandOutput> screens();
QVariant streams();
......@@ -116,11 +112,8 @@ private:
qint32 m_drmFd = 0; // for GBM buffer mmap
gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval
struct {
QList<QByteArray> extensions;
EGLDisplay display = EGL_NO_DISPLAY;
EGLContext context = EGL_NO_CONTEXT;
} m_egl;
EGLStruct m_egl;
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment