diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index d2b4eea652..cc89f0f632 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -225,6 +225,19 @@ if (is_linux || is_chromeos) { } } + pkg_config("gbm") { + packages = [ "gbm" ] + } + pkg_config("egl") { + packages = [ "egl" ] + } + pkg_config("epoxy") { + packages = [ "epoxy" ] + } + pkg_config("libdrm") { + packages = [ "libdrm" ] + } + if (!rtc_link_pipewire) { # When libpipewire is not directly linked, use stubs to allow for dlopening of # the binary. @@ -542,17 +555,25 @@ rtc_library("desktop_capture_generic") { sources += [ "linux/base_capturer_pipewire.cc", "linux/base_capturer_pipewire.h", + "linux/egl_dmabuf.cc", + "linux/egl_dmabuf.h", ] configs += [ ":pipewire_config", ":gio", ":pipewire", + ":gbm", + ":egl", + ":epoxy", + ":libdrm", ] if (!rtc_link_pipewire) { deps += [ ":pipewire_stubs" ] } + + deps += [ "../../rtc_base:sanitizer" ] } if (rtc_enable_win_wgc) { diff --git a/modules/desktop_capture/DEPS b/modules/desktop_capture/DEPS index 8c894c4430..033d318a9a 100644 --- a/modules/desktop_capture/DEPS +++ b/modules/desktop_capture/DEPS @@ -13,6 +13,9 @@ specific_include_rules = { "desktop_frame_provider\.h": [ "+sdk/objc", ], + "egl_dmabuf\.cc": [ + "+absl/strings/str_split.h", + ], "screen_capturer_mac\.mm": [ "+sdk/objc", ], diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.cc b/modules/desktop_capture/linux/base_capturer_pipewire.cc index 7212ad383e..263c8a647d 100644 --- a/modules/desktop_capture/linux/base_capturer_pipewire.cc +++ b/modules/desktop_capture/linux/base_capturer_pipewire.cc @@ -14,12 +14,13 @@ #include #include #include - #include #include #include +#include #include +#include #include #include "absl/memory/memory.h" @@ -51,65 +52,102 @@ const int kBytesPerPixel = 4; const char kPipeWireLib[] = "libpipewire-0.3.so.0"; #endif -// static -struct dma_buf_sync { - uint64_t flags; +#if !PW_CHECK_VERSION(0, 3, 29) +#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3) +#endif +#if !PW_CHECK_VERSION(0, 3, 33) +#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4) +#endif + +struct pw_version { + int major = 0; + int minor = 0; + int micro = 0; }; -#define DMA_BUF_SYNC_READ (1 << 0) -#define DMA_BUF_SYNC_START (0 << 2) -#define DMA_BUF_SYNC_END (1 << 2) -#define DMA_BUF_BASE 'b' -#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) -static void SyncDmaBuf(int fd, uint64_t start_or_end) { - struct dma_buf_sync sync = {0}; +pw_version ParsePipeWireVersion(const char* version) { + pw_version pw_version; + sscanf(version, "%d.%d.%d", &pw_version.major, &pw_version.minor, + &pw_version.micro); + return pw_version; +} - sync.flags = start_or_end | DMA_BUF_SYNC_READ; +spa_pod* BuildFormat(spa_pod_builder* builder, + uint32_t format, + const std::vector& modifiers) { + bool first = true; + spa_pod_frame frames[2]; + spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1}; + spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; - while (true) { - int ret; - ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); - if (ret == -1 && errno == EINTR) { - continue; - } else if (ret == -1) { - RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " - << g_strerror(errno); - break; + spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format, + SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, SPA_FORMAT_mediaType, + SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, + SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + + if (modifiers.size()) { + pw_version pw_version = ParsePipeWireVersion(pw_get_library_version()); + + // SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33 + if (pw_version.major >= 0 && pw_version.minor >= 3 && + pw_version.micro >= 33) { + spa_pod_builder_prop( + builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); } else { - break; + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY); } + spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0); + // modifiers from the array + for (int64_t val : modifiers) { + spa_pod_builder_long(builder, val); + // Add the first modifier twice as the very first value is the default + // option + if (first) { + spa_pod_builder_long(builder, val); + first = false; + } + } + spa_pod_builder_pop(builder, &frames[1]); } + + spa_pod_builder_add( + builder, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle( + &pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds), + 0); + + return static_cast(spa_pod_builder_pop(builder, &frames[0])); } class ScopedBuf { public: ScopedBuf() {} - ScopedBuf(unsigned char* map, int map_size, bool is_dma_buf, int fd) - : map_(map), map_size_(map_size), is_dma_buf_(is_dma_buf), fd_(fd) {} + ScopedBuf(unsigned char* map, int map_size, int fd) + : map_(map), map_size_(map_size), fd_(fd) {} ~ScopedBuf() { if (map_ != MAP_FAILED) { - if (is_dma_buf_) { - SyncDmaBuf(fd_, DMA_BUF_SYNC_END); - } munmap(map_, map_size_); } } operator bool() { return map_ != MAP_FAILED; } - void initialize(unsigned char* map, int map_size, bool is_dma_buf, int fd) { + void initialize(unsigned char* map, int map_size, int fd) { map_ = map; map_size_ = map_size; - is_dma_buf_ = is_dma_buf; fd_ = fd; } unsigned char* get() { return map_; } protected: - unsigned char* map_ = nullptr; + unsigned char* map_ = static_cast(MAP_FAILED); int map_size_; - bool is_dma_buf_; int fd_; }; @@ -234,17 +272,26 @@ void BaseCapturerPipeWire::OnStreamParamChanged(void* data, auto size = height * stride; that->desktop_size_ = DesktopSize(width, height); +#if PW_CHECK_VERSION(0, 3, 0) + that->modifier_ = that->spa_video_format_.modifier; +#endif uint8_t buffer[1024] = {}; auto builder = spa_pod_builder{buffer, sizeof(buffer)}; // Setup buffers and meta header for new format. const struct spa_pod* params[3]; + const int buffer_types = + spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier) + ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | + (1 << SPA_DATA_MemPtr) + : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); params[0] = reinterpret_cast(spa_pod_builder_add_object( &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers, - SPA_POD_CHOICE_RANGE_Int(8, 1, 32))); + SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int(buffer_types))); params[1] = reinterpret_cast(spa_pod_builder_add_object( &builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, @@ -350,6 +397,12 @@ BaseCapturerPipeWire::~BaseCapturerPipeWire() { } } +#if PW_CHECK_VERSION(0, 3, 0) +void BaseCapturerPipeWire::InitEGL() { + egl_dmabuf_ = std::make_unique(); +} +#endif + void BaseCapturerPipeWire::InitPortal() { cancellable_ = g_cancellable_new(); g_dbus_proxy_new_for_bus( @@ -419,34 +472,39 @@ void BaseCapturerPipeWire::InitPipeWire() { } pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { - spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1}; - spa_rectangle pwMaxScreenBounds = spa_rectangle{UINT32_MAX, UINT32_MAX}; - pw_properties* reuseProps = pw_properties_new_string("pipewire.client.reuse=1"); auto stream = pw_stream_new(pw_core_, "webrtc-consume-stream", reuseProps); - uint8_t buffer[1024] = {}; - const spa_pod* params[1]; - spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; + spa_pod_builder builder; + uint8_t buffer[2048] = {}; + std::vector modifiers; - params[0] = reinterpret_cast(spa_pod_builder_add_object( - &builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, - SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, - SPA_VIDEO_FORMAT_BGRA), - SPA_FORMAT_VIDEO_size, - SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds, &pwMinScreenBounds, - &pwMaxScreenBounds), - 0)); + builder = spa_pod_builder{buffer, sizeof(buffer)}; + + std::vector params; + for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { + pw_version pw_version = ParsePipeWireVersion(pw_get_library_version()); + + // Modifiers can be used with PipeWire >= 0.3.29 + if (pw_version.major >= 0 && pw_version.minor >= 3 && + pw_version.micro >= 29) { + modifiers = egl_dmabuf_->QueryDmaBufModifiers(format); + + if (!modifiers.empty()) { + params.push_back(BuildFormat(&builder, format, modifiers)); + } + } + + params.push_back(BuildFormat(&builder, format, /*modifiers=*/{})); + } pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, this); if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_, - PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) { + PW_STREAM_FLAG_AUTOCONNECT, params.data(), + params.size()) != 0) { RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; portal_init_failed_ = true; return nullptr; @@ -456,25 +514,26 @@ pw_stream* BaseCapturerPipeWire::CreateReceivingStream() { } void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { - spa_buffer* spaBuffer = buffer->buffer; + spa_buffer* spa_buffer = buffer->buffer; ScopedBuf map; + std::unique_ptr src_unique_ptr; uint8_t* src = nullptr; - if (spaBuffer->datas[0].chunk->size == 0) { + if (spa_buffer->datas[0].chunk->size == 0) { RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size."; return; } - if (spaBuffer->datas[0].type == SPA_DATA_MemFd || - spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { + std::function cleanup; + const int32_t src_stride = spa_buffer->datas[0].chunk->stride; + if (spa_buffer->datas[0].type == SPA_DATA_MemFd) { map.initialize( static_cast( mmap(nullptr, - spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, - PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0)), - spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, - spaBuffer->datas[0].type == SPA_DATA_DmaBuf, - spaBuffer->datas[0].fd); + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + PROT_READ, MAP_PRIVATE, spa_buffer->datas[0].fd, 0)), + spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, + spa_buffer->datas[0].fd); if (!map) { RTC_LOG(LS_ERROR) << "Failed to mmap the memory: " @@ -482,13 +541,25 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { return; } - if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) { - SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START); + src = SPA_MEMBER(map.get(), spa_buffer->datas[0].mapoffset, uint8_t); + } else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf) { + const uint n_planes = spa_buffer->n_datas; + int fds[n_planes]; + uint32_t offsets[n_planes]; + uint32_t strides[n_planes]; + + for (uint i = 0; i < n_planes; i++) { + fds[i] = spa_buffer->datas[i].fd; + offsets[i] = spa_buffer->datas[i].chunk->offset; + strides[i] = spa_buffer->datas[i].chunk->stride; } - src = SPA_MEMBER(map.get(), spaBuffer->datas[0].mapoffset, uint8_t); - } else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) { - src = static_cast(spaBuffer->datas[0].data); + src_unique_ptr = egl_dmabuf_->ImageFromDmaBuf( + desktop_size_, spa_video_format_.format, n_planes, fds, strides, + offsets, modifier_); + src = src_unique_ptr.get(); + } else if (spa_buffer->datas[0].type == SPA_DATA_MemPtr) { + src = static_cast(spa_buffer->datas[0].data); } if (!src) { @@ -497,7 +568,7 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { struct spa_meta_region* video_metadata = static_cast(spa_buffer_find_meta_data( - spaBuffer, SPA_META_VideoCrop, sizeof(*video_metadata))); + spa_buffer, SPA_META_VideoCrop, sizeof(*video_metadata))); // Video size from metadata is bigger than an actual video stream size. // The metadata are wrong or we should up-scale the video...in both cases @@ -513,7 +584,6 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { // Use video metadata when video size from metadata is set and smaller than // video stream size, so we need to adjust it. bool video_metadata_use = false; - const struct spa_rectangle* video_metadata_size = video_metadata ? &video_metadata->region.size : nullptr; @@ -540,7 +610,6 @@ void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { } const int32_t dst_stride = video_size_.width() * kBytesPerPixel; - const int32_t src_stride = spaBuffer->datas[0].chunk->stride; if (src_stride != (desktop_size_.width() * kBytesPerPixel)) { RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: " @@ -1001,6 +1070,9 @@ void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested( return; } +#if PW_CHECK_VERSION(0, 3, 0) + that->InitEGL(); +#endif that->InitPipeWire(); } diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.h b/modules/desktop_capture/linux/base_capturer_pipewire.h index 7ec5ea6950..8f9b61dc04 100644 --- a/modules/desktop_capture/linux/base_capturer_pipewire.h +++ b/modules/desktop_capture/linux/base_capturer_pipewire.h @@ -19,6 +19,7 @@ #include "absl/types/optional.h" #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/linux/egl_dmabuf.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/synchronization/mutex.h" @@ -88,6 +89,7 @@ class BaseCapturerPipeWire : public DesktopCapturer { guint sources_request_signal_id_ = 0; guint start_request_signal_id_ = 0; + int64_t modifier_; DesktopSize video_size_; DesktopSize desktop_size_ = {}; DesktopCaptureOptions options_ = {}; @@ -98,6 +100,9 @@ class BaseCapturerPipeWire : public DesktopCapturer { bool portal_init_failed_ = false; + std::unique_ptr egl_dmabuf_; + + void InitEGL(); void InitPortal(); void InitPipeWire(); void InitPipeWireTypes(); diff --git a/modules/desktop_capture/linux/egl_dmabuf.cc b/modules/desktop_capture/linux/egl_dmabuf.cc new file mode 100644 index 0000000000..6bc148666c --- /dev/null +++ b/modules/desktop_capture/linux/egl_dmabuf.cc @@ -0,0 +1,411 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/linux/egl_dmabuf.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/strings/str_split.h" +#include "absl/types/optional.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" + +namespace webrtc { + +typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy, + EGLint max_formats, + EGLint* formats, + EGLint* num_formats); +typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy, + EGLint format, + EGLint max_modifiers, + EGLuint64KHR* modifiers, + EGLBoolean* external_only, + EGLint* num_modifiers); +eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr; +eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr; + +static const std::string FormatGLError(GLenum err) { + switch (err) { + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_STACK_OVERFLOW: + return "GL_STACK_OVERFLOW"; + case GL_STACK_UNDERFLOW: + return "GL_STACK_UNDERFLOW"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + default: + return std::string("0x") + std::to_string(err); + } +} + +static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) { + switch (spa_format) { + case SPA_VIDEO_FORMAT_RGBA: + return DRM_FORMAT_ABGR8888; + case SPA_VIDEO_FORMAT_RGBx: + return DRM_FORMAT_XBGR8888; + case SPA_VIDEO_FORMAT_BGRA: + return DRM_FORMAT_ARGB8888; + case SPA_VIDEO_FORMAT_BGRx: + return DRM_FORMAT_XRGB8888; + default: + return DRM_FORMAT_INVALID; + } +} + +static absl::optional GetRenderNode() { + int ret, max_devices; + std::string render_node; + + max_devices = drmGetDevices2(0, nullptr, 0); + if (max_devices <= 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno=" + << -max_devices << ")"; + return absl::nullopt; + } + + std::vector devices(max_devices); + ret = drmGetDevices2(0, devices.data(), max_devices); + if (ret < 0) { + RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret; + return absl::nullopt; + } + + for (const drmDevicePtr& device : devices) { + if (device->available_nodes & (1 << DRM_NODE_RENDER)) { + render_node = device->nodes[DRM_NODE_RENDER]; + break; + } + } + + drmFreeDevices(devices.data(), ret); + return render_node; +} + +RTC_NO_SANITIZE("cfi-icall") +EglDmaBuf::EglDmaBuf() { + absl::optional render_node = GetRenderNode(); + if (!render_node) { + return; + } + + drm_fd_ = open(render_node->c_str(), O_RDWR); + + if (drm_fd_ < 0) { + RTC_LOG(LS_ERROR) << "Failed to open drm render node: " << strerror(errno); + return; + } + + gbm_device_ = gbm_create_device(drm_fd_); + + if (!gbm_device_) { + RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno); + return; + } + + // Get the list of client extensions + const char* client_extensions_cstring_no_display = + eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + std::string client_extensions_string = client_extensions_cstring_no_display; + if (!client_extensions_cstring_no_display) { + // If eglQueryString() returned NULL, the implementation doesn't support + // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. + RTC_LOG(LS_ERROR) << "No client extensions defined! " + << FormatGLError(eglGetError()); + return; + } + + for (const auto& extension : + absl::StrSplit(client_extensions_cstring_no_display, " ")) { + egl_.extensions.push_back(std::string(extension)); + } + + bool has_platform_base_ext = false; + bool has_platform_gbm_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_platform_base") { + has_platform_base_ext = true; + continue; + } else if (extension == "EGL_MESA_platform_gbm") { + has_platform_gbm_ext = true; + continue; + } + } + + if (!has_platform_base_ext || !has_platform_gbm_ext) { + RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing"; + return; + } + + // Use eglGetPlatformDisplayEXT() to get the display pointer + // if the implementation supports it. + egl_.display = + eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, gbm_device_, nullptr); + + if (egl_.display == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: " + << FormatGLError(eglGetError()); + return; + } + + EGLint major, minor; + if (eglInitialize(egl_.display, &major, &minor) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Error during eglInitialize: " + << FormatGLError(eglGetError()); + return; + } + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "bind OpenGL API failed"; + return; + } + + egl_.context = + eglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr); + + if (egl_.context == EGL_NO_CONTEXT) { + RTC_LOG(LS_ERROR) << "Couldn't create EGL context: " + << FormatGLError(eglGetError()); + return; + } + + const char* client_extensions_cstring_display = + eglQueryString(egl_.display, EGL_EXTENSIONS); + client_extensions_string = client_extensions_cstring_display; + + for (const auto& extension : absl::StrSplit(client_extensions_string, " ")) { + egl_.extensions.push_back(std::string(extension)); + } + + bool has_image_dma_buf_import_ext = false; + bool has_image_dma_buf_import_modifiers_ext = false; + + for (const auto& extension : egl_.extensions) { + if (extension == "EGL_EXT_image_dma_buf_import") { + has_image_dma_buf_import_ext = true; + continue; + } else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") { + has_image_dma_buf_import_modifiers_ext = true; + continue; + } + } + + if (has_image_dma_buf_import_ext && has_image_dma_buf_import_modifiers_ext) { + EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)eglGetProcAddress( + "eglQueryDmaBufFormatsEXT"); + EglQueryDmaBufModifiersEXT = + (eglQueryDmaBufModifiersEXT_func)eglGetProcAddress( + "eglQueryDmaBufModifiersEXT"); + } + + RTC_LOG(LS_INFO) << "Egl initialization succeeded"; + egl_initialized_ = true; +} + +EglDmaBuf::~EglDmaBuf() { + if (gbm_device_) { + gbm_device_destroy(gbm_device_); + } +} + +RTC_NO_SANITIZE("cfi-icall") +std::unique_ptr EglDmaBuf::ImageFromDmaBuf(const DesktopSize& size, + uint32_t format, + uint32_t n_planes, + const int32_t* fds, + const uint32_t* strides, + const uint32_t* offsets, + uint64_t modifier) { + std::unique_ptr src; + + if (!egl_initialized_) { + return src; + } + + if (n_planes <= 0) { + RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes"; + return src; + } + + gbm_bo* imported; + if (modifier == DRM_FORMAT_MOD_INVALID) { + gbm_import_fd_data import_info = {fds[0], + static_cast(size.width()), + static_cast(size.height()), + strides[0], GBM_BO_FORMAT_ARGB8888}; + + imported = gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD, &import_info, 0); + } else { + gbm_import_fd_modifier_data import_info = {}; + import_info.format = GBM_BO_FORMAT_ARGB8888; + import_info.width = static_cast(size.width()); + import_info.height = static_cast(size.height()); + import_info.num_fds = n_planes; + import_info.modifier = modifier; + for (uint32_t i = 0; i < n_planes; i++) { + import_info.fds[i] = fds[i]; + import_info.offsets[i] = offsets[i]; + import_info.strides[i] = strides[i]; + } + + imported = + gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD_MODIFIER, &import_info, 0); + } + + if (!imported) { + RTC_LOG(LS_ERROR) + << "Failed to process buffer: Cannot import passed GBM fd - " + << strerror(errno); + return src; + } + + // bind context to render thread + eglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context); + + // create EGL image from imported BO + EGLImageKHR image = eglCreateImageKHR( + egl_.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); + + if (image == EGL_NO_IMAGE_KHR) { + RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImageKHR - " + << FormatGLError(glGetError()); + gbm_bo_destroy(imported); + return src; + } + + // 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); + + src = std::make_unique(strides[0] * size.height()); + + GLenum gl_format = GL_BGRA; + switch (format) { + case SPA_VIDEO_FORMAT_RGBx: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_RGBA: + gl_format = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_BGRx: + gl_format = GL_BGRA; + break; + case SPA_VIDEO_FORMAT_RGB: + gl_format = GL_RGB; + break; + case SPA_VIDEO_FORMAT_BGR: + gl_format = GL_BGR; + break; + default: + gl_format = GL_BGRA; + break; + } + glGetTexImage(GL_TEXTURE_2D, 0, gl_format, GL_UNSIGNED_BYTE, src.get()); + + if (glGetError()) { + RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer."; + gbm_bo_destroy(imported); + return src; + } + + glDeleteTextures(1, &texture); + eglDestroyImageKHR(egl_.display, image); + + gbm_bo_destroy(imported); + + return src; +} + +RTC_NO_SANITIZE("cfi-icall") +std::vector EglDmaBuf::QueryDmaBufModifiers(uint32_t format) { + if (!egl_initialized_) { + return {}; + } + + // Modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we can still + // use modifier-less DMA-BUFs + if (EglQueryDmaBufFormatsEXT == nullptr || + EglQueryDmaBufModifiersEXT == nullptr) { + return {DRM_FORMAT_MOD_INVALID}; + } + + uint32_t drm_format = SpaPixelFormatToDrmFormat(format); + if (drm_format == DRM_FORMAT_INVALID) { + RTC_LOG(LS_ERROR) << "Failed to find matching DRM format."; + return {DRM_FORMAT_MOD_INVALID}; + } + + EGLint count = 0; + EGLBoolean success = + EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector formats(count); + if (!EglQueryDmaBufFormatsEXT(egl_.display, count, + reinterpret_cast(formats.data()), + &count)) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF formats."; + return {DRM_FORMAT_MOD_INVALID}; + } + + if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) { + RTC_LOG(LS_ERROR) << "Format " << drm_format + << " not supported for modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr, + nullptr, &count); + + if (!success || !count) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF modifiers."; + return {DRM_FORMAT_MOD_INVALID}; + } + + std::vector modifiers(count); + if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count, + modifiers.data(), nullptr, &count)) { + RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF modifiers."; + } + + // Support modifier-less buffers + modifiers.push_back(DRM_FORMAT_MOD_INVALID); + + return modifiers; +} + +} // namespace webrtc diff --git a/modules/desktop_capture/linux/egl_dmabuf.h b/modules/desktop_capture/linux/egl_dmabuf.h new file mode 100644 index 0000000000..bfa3a0a71c --- /dev/null +++ b/modules/desktop_capture/linux/egl_dmabuf.h @@ -0,0 +1,58 @@ +/* + * Copyright 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ + +#include +#include +#include + +#include +#include +#include + +#include "modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class EglDmaBuf { + public: + struct EGLStruct { + std::vector extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; + }; + + EglDmaBuf(); + ~EglDmaBuf(); + + std::unique_ptr ImageFromDmaBuf(const DesktopSize& size, + uint32_t format, + uint32_t n_planes, + const int32_t* fds, + const uint32_t* strides, + const uint32_t* offsets, + uint64_t modifiers); + std::vector QueryDmaBufModifiers(uint32_t format); + + bool IsEglInitialized() const { return egl_initialized_; } + + private: + bool egl_initialized_ = false; + int32_t drm_fd_ = -1; // for GBM buffer mmap + gbm_device* gbm_device_ = nullptr; // for passed GBM buffer retrieval + + EGLStruct egl_; +}; + +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_EGL_DMABUF_H_ diff --git a/modules/desktop_capture/linux/pipewire03.sigs b/modules/desktop_capture/linux/pipewire03.sigs index 78d241f40c..debe04bbdc 100644 --- a/modules/desktop_capture/linux/pipewire03.sigs +++ b/modules/desktop_capture/linux/pipewire03.sigs @@ -16,6 +16,7 @@ pw_loop * pw_loop_new(const spa_dict *props); // pipewire.h void pw_init(int *argc, char **argv[]); +const char* pw_get_library_version(); // properties.h pw_properties * pw_properties_new_string(const char *args); diff --git a/webrtc.gni b/webrtc.gni index a32e7bfc44..d2d0d15bd2 100644 --- a/webrtc.gni +++ b/webrtc.gni @@ -130,7 +130,8 @@ declare_args() { # By default it's only enabled on desktop Linux (excludes ChromeOS) and # only when using the sysroot as PipeWire is not available in older and # supported Ubuntu and Debian distributions. - rtc_use_pipewire = is_linux && use_sysroot + # TODO: remove !is_msan (https://bugs.chromium.org/p/webrtc/issues/detail?id=13137) + rtc_use_pipewire = is_linux && use_sysroot && !is_msan # Set this to link PipeWire directly instead of using the dlopen. rtc_link_pipewire = false