Sunshine master
Self-hosted game stream host for Moonlight.
pipewire.cpp
Go to the documentation of this file.
1
5// standard includes
6#include <fstream>
7
8// lib includes
9#include <gio/gio.h>
10#include <gio/gunixfdlist.h>
11#include <libdrm/drm_fourcc.h>
12#include <pipewire/pipewire.h>
13#include <spa/param/video/format-utils.h>
14#include <spa/param/video/type-info.h>
15#include <spa/pod/builder.h>
16
17// local includes
18#include "cuda.h"
19#include "graphics.h"
20#include "src/main.h"
21#include "src/platform/common.h"
22#include "src/video.h"
23#include "vaapi.h"
24#include "vulkan_encode.h"
25#include "wayland.h"
26
27#if !PW_CHECK_VERSION(1, 6, 0)
28constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14;
29#endif
30
31#if PW_CHECK_VERSION(0, 3, 75)
32// Runtime linked library version checks are available. Check for pipewire 0.3.64 which documented object serial support and deprecated node id.
33const bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = pw_check_library_version(0, 3, 64);
34#elifdef PW_KEY_TARGET_OBJECT
35// Runtime linked library version checks are UNAVAILABLE but necessary PW_KEY_TARGET_OBJECT for object serial support is available.
36constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = true;
37#else
38// Pipewire object serials are unsupported without PW_KEY_TARGET_OBJECT (we define it here so compilation won't break but don't use it).
44 #define PW_KEY_TARGET_OBJECT "target.object"
45#endif
46
47namespace {
48 // Buffer and limit constants
49 constexpr int SPA_POD_BUFFER_SIZE = 4096;
50 constexpr int MAX_PARAMS = 200;
51 constexpr int MAX_DMABUF_FORMATS = 200;
52 constexpr int MAX_DMABUF_MODIFIERS = 200;
53} // namespace
54
55using namespace std::literals;
56
57namespace pipewire {
61 struct format_map_t {
62 uint64_t fourcc;
63 int32_t pw_format;
64 };
65
66 static constexpr std::array<format_map_t, 7> format_map = {{
67 {DRM_FORMAT_XBGR2101010, SPA_VIDEO_FORMAT_xBGR_210LE},
68 {DRM_FORMAT_BGRA1010102, SPA_VIDEO_FORMAT_ARGB_210LE},
69 {DRM_FORMAT_RGBA1010102, SPA_VIDEO_FORMAT_ABGR_210LE},
70 {DRM_FORMAT_ABGR2101010, SPA_VIDEO_FORMAT_RGBA_102LE},
71 {DRM_FORMAT_ARGB2101010, SPA_VIDEO_FORMAT_BGRA_102LE},
72 {DRM_FORMAT_ARGB8888, SPA_VIDEO_FORMAT_BGRA},
73 {DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx},
74 }};
75
80 std::atomic<int> negotiated_width {0};
81 std::atomic<int> negotiated_height {0};
82 std::atomic<int> color_primaries {0};
83 std::atomic<int> transfer_function {0};
84 std::atomic<bool> stream_dead {false};
85 pw_stream_state previous_state;
86 pw_stream_state current_state;
87 std::string err_msg;
88 };
89
94 struct pw_stream *stream;
95 struct spa_hook stream_listener;
96 struct spa_video_info format;
97 struct pw_buffer *current_buffer;
98 uint64_t drm_format;
99 std::shared_ptr<shared_state_t> shared;
100 std::mutex frame_mutex;
101 std::condition_variable frame_cv;
102 size_t local_stride = 0;
103 bool frame_ready = false;
104 // Two distinct memory pools
105 std::vector<uint8_t> buffer_a;
106 std::vector<uint8_t> buffer_b;
107 // Points to the buffer currently owned by fill_img
108 std::vector<uint8_t> *front_buffer;
109 // Points to the buffer currently being written by on_process
110 std::vector<uint8_t> *back_buffer;
111
115 };
116
121 int32_t format;
122 uint64_t *modifiers;
124 };
125
130 ~img_descriptor_t() override {
131 if (data) {
132 delete[] data;
133 data = nullptr;
134 }
135 }
136 };
137
142 public:
143 pipewire_t():
144 loop(pw_thread_loop_new("Pipewire thread", nullptr)) {
145 BOOST_LOG(debug) << "[pipewire] Start PW thread loop"sv;
146 pw_thread_loop_start(loop);
147 }
148
149 ~pipewire_t() {
150 BOOST_LOG(debug) << "[pipewire] Destroying pipewire_t"sv;
151 pw_thread_loop_lock(loop);
152
153 // Lock the frame mutex to stop fill_img
154 BOOST_LOG(debug) << "[pipewire] Stop fill_img"sv;
155 {
156 std::scoped_lock lock(stream_data.frame_mutex);
157 stream_data.frame_ready = false;
158 stream_data.current_buffer = nullptr;
159 }
160
161 // Release pipewire stream
162 if (stream_data.stream) {
163 BOOST_LOG(debug) << "[pipewire] Disconnect stream"sv;
164 pw_stream_disconnect(stream_data.stream);
165 BOOST_LOG(debug) << "[pipewire] Destroy stream"sv;
166 pw_stream_destroy(stream_data.stream);
167 stream_data.stream = nullptr;
168 }
169 // Release pipewire core
170 if (core) {
171 BOOST_LOG(debug) << "[pipewire] Disconnect PW core"sv;
172 pw_core_disconnect(core);
173 core = nullptr;
174 }
175 // Release pipewire context
176 if (context) {
177 BOOST_LOG(debug) << "[pipewire] Destroy PW context"sv;
178 pw_context_destroy(context);
179 context = nullptr;
180 }
181 // Release pipewire file descriptor
182 if (fd >= 0) {
183 BOOST_LOG(debug) << "[pipewire] Close pipewire_fd"sv;
184 close(fd);
185 }
186 // Release pipewire thread loop
187 BOOST_LOG(debug) << "[pipewire] Stop PW thread loop"sv;
188 pw_thread_loop_unlock(loop);
189 pw_thread_loop_stop(loop);
190 BOOST_LOG(debug) << "[pipewire] Destroy PW thread loop"sv;
191 pw_thread_loop_destroy(loop);
192 }
193
199 std::mutex &frame_mutex() {
200 return stream_data.frame_mutex;
201 }
202
208 std::condition_variable &frame_cv() {
209 return stream_data.frame_cv;
210 }
211
217 bool is_frame_ready() const {
218 return stream_data.frame_ready;
219 }
220
226 void set_frame_ready(bool ready) {
227 stream_data.frame_ready = ready;
228 }
229
239 int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr<shared_state_t> shared_state) {
240 fd = stream_fd;
241 node = stream_node;
242 object_serial = stream_object_serial;
243 stream_data.shared = std::move(shared_state);
244
245 pw_thread_loop_lock(loop);
246 BOOST_LOG(debug) << "[pipewire] Setup PW context"sv;
247 context = pw_context_new(pw_thread_loop_get_loop(loop), nullptr, 0);
248 if (context) {
249 BOOST_LOG(debug) << "[pipewire] Connect PW context to fd"sv;
250 if (fd >= 0) {
251 core = pw_context_connect_fd(context, fd, nullptr, 0);
252 } else {
253 core = pw_context_connect(context, nullptr, 0);
254 }
255 if (core) {
256 pw_core_add_listener(core, &core_listener, &core_events, nullptr);
257 } else {
258 BOOST_LOG(debug) << "[pipewire] Failed to connect to PW core. Error: "sv << errno << "(" << strerror(errno) << ")"sv;
259 return -1;
260 }
261 } else {
262 BOOST_LOG(debug) << "[pipewire] Failed to setup PW context. Error: "sv << errno << "(" << strerror(errno) << ")"sv;
263 return -1;
264 }
265
266 pw_thread_loop_unlock(loop);
267 return 0;
268 }
269
282 int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
283 pw_thread_loop_lock(loop);
284 int result = 0;
285 if (!stream_data.stream) {
286 if (!core) {
287 BOOST_LOG(debug) << "[pipewire] PW core not available. Cannot ensure stream."sv;
288 pw_thread_loop_unlock(loop);
289 return -1;
290 }
291
292 struct pw_properties *props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Screen", nullptr);
293
294 BOOST_LOG(debug) << "[pipewire] Create PW stream"sv;
295 stream_data.stream = pw_stream_new(core, "Sunshine Video Capture", props);
296 pw_stream_add_listener(stream_data.stream, &stream_data.stream_listener, &stream_events, &stream_data);
297
298 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
299 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
300
301 int n_params = 0;
302 std::array<const struct spa_pod *, MAX_PARAMS> params;
303
304 // Add preferred parameters for DMA-BUF with modifiers
305 // Use DMA-BUF for VAAPI, or for CUDA when the display GPU is NVIDIA (pure NVIDIA system).
306 // On hybrid GPU systems (Intel+NVIDIA), DMA-BUFs come from the Intel GPU and cannot
307 // be imported into CUDA, so we fall back to memory buffers in that case.
308 bool use_dmabuf = n_dmabuf_infos > 0 && (mem_type == platf::mem_type_e::vaapi ||
309 mem_type == platf::mem_type_e::vulkan ||
310 (mem_type == platf::mem_type_e::cuda && display_is_nvidia));
311 if (use_dmabuf) {
312 for (int i = 0; i < n_dmabuf_infos; i++) {
313 auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, dmabuf_infos[i].format, dmabuf_infos[i].modifiers, dmabuf_infos[i].n_modifiers);
314 params[n_params] = format_param;
315 n_params++;
316 }
317 }
318
319 // Add fallback for memptr
320 for (const auto &fmt : format_map) {
321 auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, fmt.pw_format, nullptr, 0);
322 params[n_params] = format_param;
323 n_params++;
324 }
325
326 // Connection via pipewire object serial if it is supported and the serial is valid (lower 32-bits != SPA_ID_INVALID, see also PW_KEY_OBJECT_SERIAL docs)
327 if (SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL && (object_serial & SPA_ID_INVALID) != SPA_ID_INVALID) {
328 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, object_serial);
329 BOOST_LOG(debug) << "[pipewire] Connect PW stream - fd: "sv << fd << " object serial: "sv << object_serial;
330 result = pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, (enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params);
331 if (result < 0) {
332 // Unset object serial for retry with node id
333 pw_properties_set(props, PW_KEY_TARGET_OBJECT, nullptr);
334 }
335 } else {
336 result = -1; // Mark failed so we try to connect via node id
337 }
338 // Connection via legacy (and deprecated) pipewire node id
339 if (result < 0) {
340 BOOST_LOG(debug) << "[pipewire] Connect PW stream - fd: "sv << fd << " node: "sv << node;
341 result = pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, node, (enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params);
342 }
343 }
344
345 pw_thread_loop_unlock(loop);
346 return result;
347 }
348
354 static void close_img_fds(egl::img_descriptor_t *img_descriptor) {
355 for (int &fd : img_descriptor->sd.fds) {
356 if (fd >= 0) {
357 close(fd);
358 fd = -1;
359 }
360 }
361 }
362
369 static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf) {
370 img_descriptor->frame_timestamp = std::chrono::steady_clock::now();
371
372 struct spa_meta_header *h = static_cast<struct spa_meta_header *>(
373 spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h))
374 );
375 if (h) {
376 img_descriptor->seq = h->seq;
377 img_descriptor->pts = h->pts;
378 }
379
380 if (buf->n_datas > 0) {
381 img_descriptor->pw_flags = buf->datas[0].chunk->flags;
382 }
383
384 struct spa_meta_region *damage = static_cast<struct spa_meta_region *>(
385 spa_buffer_find_meta_data(buf, SPA_META_VideoDamage, sizeof(*damage))
386 );
387 img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional<bool>(true) : std::nullopt;
388 }
389
397 static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d) {
398 img_descriptor->sd.width = d.format.info.raw.size.width;
399 img_descriptor->sd.height = d.format.info.raw.size.height;
400 img_descriptor->sd.modifier = d.format.info.raw.modifier;
401 img_descriptor->sd.fourcc = d.drm_format;
402 for (int i = 0; i < MIN(buf->n_datas, 4); i++) {
403 img_descriptor->sd.fds[i] = dup(buf->datas[i].fd);
404 img_descriptor->sd.pitches[i] = buf->datas[i].chunk->stride;
405 img_descriptor->sd.offsets[i] = buf->datas[i].chunk->offset;
406 }
407 }
408
415 pw_thread_loop_lock(loop);
416 std::scoped_lock lock(stream_data.frame_mutex);
417
418 if (stream_data.shared && stream_data.shared->stream_dead.load()) {
419 img->data = nullptr;
420 close_img_fds(static_cast<egl::img_descriptor_t *>(img));
421 pw_thread_loop_unlock(loop);
422 return;
423 }
424
425 if (!stream_data.current_buffer) {
426 img->data = nullptr;
427 pw_thread_loop_unlock(loop);
428 return;
429 }
430
431 struct spa_buffer *buf = stream_data.current_buffer->buffer;
432 if (buf->datas[0].chunk->size != 0) {
433 auto *img_descriptor = static_cast<egl::img_descriptor_t *>(img);
434 fill_img_metadata(img_descriptor, buf);
435 if (buf->datas[0].type == SPA_DATA_DmaBuf) {
436 fill_img_dmabuf(img_descriptor, buf, stream_data);
437 } else {
438 img->data = stream_data.front_buffer->data();
439 img->row_pitch = stream_data.local_stride;
440 }
441 }
442
443 pw_thread_loop_unlock(loop);
444 }
445
451 void set_negotiate_maxframerate(bool negotiate_maxframerate) {
452 negotiate_maxframerate_ = negotiate_maxframerate;
453 }
454
455 private:
456 struct pw_thread_loop *loop;
457 struct pw_context *context;
458 struct pw_core *core;
459 struct spa_hook core_listener;
460 struct stream_data_t stream_data;
461 int fd;
462 uint32_t node;
463 uint64_t object_serial;
464 bool negotiate_maxframerate_ = true;
465
466 struct spa_pod *build_format_parameter(struct spa_pod_builder *b, uint32_t width, uint32_t height, uint32_t refresh_rate, int32_t format, uint64_t *modifiers, int n_modifiers) {
467 struct spa_pod_frame object_frame;
468 struct spa_pod_frame modifier_frame;
469 std::array<struct spa_rectangle, 3> sizes;
470 std::array<struct spa_fraction, 3> framerates;
471
472 sizes[0] = SPA_RECTANGLE(width, height); // Preferred
473 sizes[1] = SPA_RECTANGLE(1, 1);
474 sizes[2] = SPA_RECTANGLE(8192, 4096);
475
476 framerates[0] = SPA_FRACTION(0, 1); // default; we only want variable rate, thus bypassing compositor pacing
477 framerates[1] = SPA_FRACTION(0, 1); // min
478 framerates[2] = SPA_FRACTION(0, 1); // max
479
480 spa_pod_builder_push_object(b, &object_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
481 spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
482 spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
483 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
484 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&sizes[0], &sizes[1], &sizes[2]), 0);
485 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[0]), 0);
486 if (negotiate_maxframerate_) {
487 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&framerates[0], &framerates[1], &framerates[2]), 0);
488 }
489
490 if (format == SPA_VIDEO_FORMAT_xBGR_210LE) {
491 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_Id(SPA_VIDEO_COLOR_PRIMARIES_BT2020), 0);
492 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_transferFunction, SPA_POD_Id(SPA_VIDEO_TRANSFER_SMPTE2084), 0);
493 }
494
495 if (n_modifiers) {
496 spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
497 spa_pod_builder_push_choice(b, &modifier_frame, SPA_CHOICE_Enum, 0);
498
499 // Preferred value, we pick the first modifier be the preferred one
500 spa_pod_builder_long(b, modifiers[0]);
501 for (uint32_t i = 0; i < n_modifiers; i++) {
502 spa_pod_builder_long(b, modifiers[i]);
503 }
504
505 spa_pod_builder_pop(b, &modifier_frame);
506 }
507
508 return static_cast<struct spa_pod *>(spa_pod_builder_pop(b, &object_frame));
509 }
510
511 static void on_core_info_cb([[maybe_unused]] void *user_data, const struct pw_core_info *pw_info) {
512 BOOST_LOG(info) << "[pipewire] Connected to pipewire version "sv << pw_info->version;
513 }
514
515 static void on_core_error_cb([[maybe_unused]] void *user_data, const uint32_t id, const int seq, [[maybe_unused]] int res, const char *message) {
516 BOOST_LOG(info) << "[pipewire] Pipewire Error, id:"sv << id << " seq:"sv << seq << " message: "sv << message;
517 }
518
519 constexpr static const struct pw_core_events core_events = {
520 .version = PW_VERSION_CORE_EVENTS,
521 .info = on_core_info_cb,
522 .error = on_core_error_cb,
523 };
524
525 static void on_stream_state_changed(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *err_msg) {
526 if (err_msg != nullptr) {
527 BOOST_LOG(info) << "[pipewire] PipeWire stream error '" << err_msg << "' on state: " << pw_stream_state_as_string(old)
528 << " -> " << pw_stream_state_as_string(state);
529 } else {
530 BOOST_LOG(info) << "[pipewire] PipeWire stream state: " << pw_stream_state_as_string(old)
531 << " -> " << pw_stream_state_as_string(state);
532 }
533
534 auto *d = static_cast<stream_data_t *>(user_data);
535
536 switch (state) {
537 case PW_STREAM_STATE_PAUSED:
538 if (d->shared && old == PW_STREAM_STATE_STREAMING) {
539 {
540 std::scoped_lock lock(d->frame_mutex);
541 d->frame_ready = false;
542 d->current_buffer = nullptr;
543 d->shared->stream_dead.store(true);
544 d->shared->current_state = state;
545 d->shared->previous_state = old;
546 d->shared->err_msg = "";
547 }
548 d->frame_cv.notify_all();
549 }
550 break;
551 case PW_STREAM_STATE_ERROR:
552 {
553 std::scoped_lock lock(d->frame_mutex);
554 d->shared->current_state = state;
555 d->shared->previous_state = old;
556 d->shared->err_msg = std::string(err_msg);
557 }
558 [[fallthrough]];
559 case PW_STREAM_STATE_UNCONNECTED:
560 if (d->shared) {
561 d->shared->stream_dead.store(true);
562 d->frame_cv.notify_all();
563 }
564 break;
565 default:
566 break;
567 }
568 }
569
570 static void on_process(void *user_data) {
571 const auto d = static_cast<struct stream_data_t *>(user_data);
572 struct pw_buffer *b = nullptr;
573
574 // 1. Drain the queue: Always grab the most recent buffer
575 while (struct pw_buffer *aux = pw_stream_dequeue_buffer(d->stream)) {
576 if (b) {
577 pw_stream_queue_buffer(d->stream, b); // Return the older, unused buffer
578 }
579 b = aux;
580 }
581
582 if (!b) {
583 return;
584 }
585
586 // 2. Fast Path: DMA-BUF
587 if (b->buffer->datas[0].type == SPA_DATA_DmaBuf) {
588 std::scoped_lock lock(d->frame_mutex);
589 if (d->current_buffer) {
590 pw_stream_queue_buffer(d->stream, d->current_buffer);
591 }
592 d->current_buffer = b;
593 d->frame_ready = true;
594 }
595 // 3. Optimized Path: Software/MemPtr
596 else if (b->buffer->datas[0].data != nullptr) {
597 size_t size = b->buffer->datas[0].chunk->size;
598
599 // Perform the copy to the BACK buffer while NOT holding the lock
600 if (d->back_buffer->size() < size) {
601 d->back_buffer->resize(size);
602 }
603 std::memcpy(d->back_buffer->data(), b->buffer->datas[0].data, size);
604
605 {
606 // Lock only for the pointer swap and state update
607 std::scoped_lock lock(d->frame_mutex);
608 std::swap(d->front_buffer, d->back_buffer);
609
610 d->local_stride = b->buffer->datas[0].chunk->stride;
611 d->frame_ready = true;
612 d->current_buffer = b;
613 }
614
615 // Release the PW buffer immediately after copy
616 pw_stream_queue_buffer(d->stream, b);
617 }
618
619 d->frame_cv.notify_one();
620 }
621
622 static void on_param_changed(void *user_data, uint32_t id, const struct spa_pod *param) {
623 const auto d = static_cast<struct stream_data_t *>(user_data);
624
625 d->current_buffer = nullptr;
626
627 if (param == nullptr || id != SPA_PARAM_Format) {
628 return;
629 }
630 if (spa_format_parse(param, &d->format.media_type, &d->format.media_subtype) < 0) {
631 return;
632 }
633 if (d->format.media_type != SPA_MEDIA_TYPE_video || d->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
634 return;
635 }
636 if (spa_format_video_raw_parse(param, &d->format.info.raw) < 0) {
637 return;
638 }
639
640 BOOST_LOG(info) << "[pipewire] Video format: "sv << d->format.info.raw.format;
641 BOOST_LOG(info) << "[pipewire] Size: "sv << d->format.info.raw.size.width << "x"sv << d->format.info.raw.size.height;
642 BOOST_LOG(info) << "[pipewire] Color primaries: "sv << d->format.info.raw.color_primaries;
643 BOOST_LOG(info) << "[pipewire] Transfer function: "sv << d->format.info.raw.transfer_function;
644 if (d->format.info.raw.max_framerate.num == 0 && d->format.info.raw.max_framerate.denom == 1) {
645 BOOST_LOG(info) << "[pipewire] Framerate (from compositor): 0/1 (variable rate capture)";
646 } else {
647 BOOST_LOG(info) << "[pipewire] Framerate (from compositor): "sv << d->format.info.raw.framerate.num << "/"sv << d->format.info.raw.framerate.denom;
648 BOOST_LOG(info) << "[pipewire] Framerate (from compositor, max): "sv << d->format.info.raw.max_framerate.num << "/"sv << d->format.info.raw.max_framerate.denom;
649 }
650
651 int physical_w = d->format.info.raw.size.width;
652 int physical_h = d->format.info.raw.size.height;
653
654 if (d->shared) {
655 int old_w = d->shared->negotiated_width.load();
656 int old_h = d->shared->negotiated_height.load();
657 int old_color_primaries = d->shared->color_primaries.load();
658 int old_transfer_function = d->shared->transfer_function.load();
659
660 if (physical_w != old_w || physical_h != old_h) {
661 d->shared->negotiated_width.store(physical_w);
662 d->shared->negotiated_height.store(physical_h);
663 }
664
665 if (d->format.info.raw.color_primaries != old_color_primaries || d->format.info.raw.transfer_function != old_transfer_function) {
666 d->shared->color_primaries.store(d->format.info.raw.color_primaries);
667 d->shared->transfer_function.store(d->format.info.raw.transfer_function);
668 }
669 }
670
671 uint64_t drm_format = 0;
672 for (const auto &fmt : format_map) {
673 if (fmt.pw_format == d->format.info.raw.format) {
674 drm_format = fmt.fourcc;
675 }
676 }
677 d->drm_format = drm_format;
678
679 uint32_t buffer_types = 0;
680 if (spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier) != nullptr && d->drm_format) {
681 BOOST_LOG(info) << "[pipewire] using DMA-BUF buffers"sv;
682 buffer_types |= 1 << SPA_DATA_DmaBuf;
683 } else {
684 BOOST_LOG(info) << "[pipewire] using memory buffers"sv;
685 buffer_types |= 1 << SPA_DATA_MemPtr;
686 }
687
688 // Ack the buffer type and metadata
689 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
690 std::array<const struct spa_pod *, 3> params;
691 int n_params = 0;
692 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
693 auto buffer_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types)));
694 params[n_params] = buffer_param;
695 n_params++;
696 auto meta_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))));
697 params[n_params] = meta_param;
698 n_params++;
699 int videoDamageRegionCount = 16;
700 auto damage_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * videoDamageRegionCount, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * videoDamageRegionCount)));
701 params[n_params] = damage_param;
702 n_params++;
703
704 pw_stream_update_params(d->stream, params.data(), n_params);
705 }
706
707 constexpr static const struct pw_stream_events stream_events = {
708 .version = PW_VERSION_STREAM_EVENTS,
709 .state_changed = on_stream_state_changed,
710 .param_changed = on_param_changed,
711 .process = on_process,
712 };
713 };
714
719 public:
727 // Initialize pipewire to load necessary modules
728 pw_init(nullptr, nullptr);
729
730 // Check if we have a matching hwdevice_type
731 switch (hwdevice_type) {
732 using enum platf::mem_type_e;
733 case system:
734 case vaapi:
735 case cuda:
736 case vulkan:
737 return true;
738 default:
739 return false;
740 }
741 }
742
751 virtual int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_objectserial) = 0;
752
757 // Query outputs directly using wayland wl::monitors()
758 if (logical_height <= 0 || logical_width <= 0 || env_logical_height <= 0 || env_logical_width <= 0 || env_height <= 0 || env_width <= 0) {
759 int desktop_width = 0;
760 int desktop_height = 0;
761 int desktop_logical_width = 0;
762 int desktop_logical_height = 0;
763 for (const auto &monitor : wl::monitors()) {
764 BOOST_LOG(debug) << "[pipewire] Found output: '"sv << monitor->name << "' offset: "sv << monitor->viewport.offset_x << 'x' << monitor->viewport.offset_y << " resolution: "sv << monitor->viewport.width << 'x' << monitor->viewport.height << " logical resolution: "sv << monitor->viewport.logical_width << 'x' << monitor->viewport.logical_height;
765 // If logical_width and logical_height are not valid try to update them to correct values by matching to monitor
766 // position/dimension or position/logical dimensions here since we're iterating for maximum environment size anyway
767 if ((logical_width <= 0 || logical_height <= 0) && monitor->viewport.offset_x == offset_x && monitor->viewport.offset_y == offset_y && ((monitor->viewport.width == width && monitor->viewport.height == height) || (monitor->viewport.logical_width == width && monitor->viewport.logical_height == height))) {
768 this->logical_width = monitor->viewport.logical_width;
769 this->logical_height = monitor->viewport.logical_height;
770 BOOST_LOG(debug) << "[pipewire] Set logical resolution: "sv << logical_width << 'x' << logical_height;
771 }
772 // Update desktop dimensions to setup maximum environment size over all screens
773 desktop_width = std::max(desktop_width, monitor->viewport.offset_x + monitor->viewport.width);
774 desktop_height = std::max(desktop_height, monitor->viewport.offset_y + monitor->viewport.height);
775 // Update desktop logical dimensions to setup maximum logical environment size over all screens
776 desktop_logical_width = std::max(desktop_logical_width, monitor->viewport.offset_x + monitor->viewport.logical_width);
777 desktop_logical_height = std::max(desktop_logical_height, monitor->viewport.offset_y + monitor->viewport.logical_height);
778 }
779 if (env_height <= 0 || env_width <= 0) {
780 this->env_width = desktop_width;
781 this->env_height = desktop_height;
782 BOOST_LOG(debug) << "[pipewire] Set desktop resolution: "sv << env_width << 'x' << env_height;
783 }
784 if (env_logical_height <= 0 || env_logical_width <= 0) {
785 this->env_logical_width = desktop_logical_width;
786 this->env_logical_height = desktop_logical_height;
787 BOOST_LOG(debug) << "[pipewire] Set desktop logical resolution: "sv << env_logical_width << 'x' << env_logical_height;
788 }
789 }
790 }
791
800 int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
801 // calculate frame interval we should capture at
802 framerate = config.framerate;
803 delay = ::video::capture_frame_interval(config);
804 const AVRational fps = ::video::framerate_to_rational(config);
805 if (fps.den != 1) {
806 BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "/" << fps.den << ", approx. " << av_q2d(fps) << " fps]";
807 } else {
808 BOOST_LOG(info) << "[pipewire] Requested frame rate [" << fps.num << "fps]";
809 }
810 mem_type = hwdevice_type;
811
812 if (get_dmabuf_modifiers() < 0) {
813 return -1;
814 }
815
816 int pipewire_fd = -1;
817 auto pipewire_node = PW_ID_ANY; // Default for invalid stream from pipewire docs
818 uint64_t pipewire_object_serial = SPA_ID_INVALID; // Default for invalid stream from pipewire docs for PW_KEY_OBJECT_SERIAL
819 // Fetch stream info
820 if (configure_stream(display_name, pipewire_fd, pipewire_node, pipewire_object_serial) < 0 || (pipewire_node == PW_ID_ANY && (pipewire_object_serial & SPA_ID_INVALID) == SPA_ID_INVALID)) {
821 BOOST_LOG(error) << "[pipewire] Could not find display with name: '"sv << display_name << "'";
822 return -1;
823 }
824 BOOST_LOG(info) << "[pipewire] Streaming display '"sv << display_name << "' offset: "sv << offset_x << "x"sv << offset_y << " resolution: "sv << width << "x"sv << height;
825
826 // Verify or update display parameters for streaming to ensure absolute touch inputs work as expected
828
829 framerate = config.framerate;
830
831 if (!shared_state) {
832 shared_state = std::make_shared<shared_state_t>();
833 } else {
834 shared_state->stream_dead.store(false);
835 shared_state->negotiated_width.store(0);
836 shared_state->negotiated_height.store(0);
837 shared_state->color_primaries.store(0);
838 shared_state->transfer_function.store(0);
839 }
840
841 if (pipewire.init(pipewire_fd, pipewire_node, pipewire_object_serial, shared_state) < 0) {
842 BOOST_LOG(error) << "[pipewire] Failed to init pipewire. pipewire_t::init() failed.";
843 return -1;
844 }
845
846 // Start PipeWire now so format negotiation can proceed before capture start
847 if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
848 BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. pipewire_t::init() failed.";
849 return -1;
850 }
851
852 // Wait for pipewire negotiation to finish so we have the proper negotiated dimensions
853 int timeout_ms = 1500;
854 int negotiated_w = 0;
855 int negotiated_h = 0;
856 while (timeout_ms > 0) {
857 negotiated_w = shared_state->negotiated_width.load();
858 negotiated_h = shared_state->negotiated_height.load();
859 if (negotiated_w > 0 && negotiated_h > 0) {
860 break;
861 }
862 std::this_thread::sleep_for(std::chrono::milliseconds(10));
863 timeout_ms -= 10;
864 }
865 // Set width and height to the values negotiated by pipewire
866 if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) {
867 width = negotiated_w;
868 height = negotiated_h;
869 BOOST_LOG(info) << "[pipewire] Using negotiated Resolution: "sv << width << "x" << height;
870
871 // Reset and update display parameters for negotiated resolution
872 env_width = 0;
873 env_height = 0;
874 logical_height = 0;
875 logical_width = 0;
879 }
880
881 return 0;
882 }
883
893 platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool show_cursor) {
894 // FIXME: show_cursor is ignored
895 auto deadline = std::chrono::steady_clock::now() + timeout;
896 int retries = 0;
897
898 while (std::chrono::steady_clock::now() < deadline) {
899 if (!wait_for_frame(deadline)) {
900 return platf::capture_e::timeout;
901 }
902
903 if (!pull_free_image_cb(img_out)) {
904 return platf::capture_e::interrupted;
905 }
906
907 auto *img_egl = static_cast<egl::img_descriptor_t *>(img_out.get());
908 img_egl->reset();
909 pipewire.fill_img(img_egl);
910
911 // Check if we got valid data (either DMA-BUF fd or memory pointer), then filter duplicates
912 if ((img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr) && !is_buffer_redundant(img_egl)) {
913 // Update frame metadata
914 update_metadata(img_egl, retries);
915 return platf::capture_e::ok;
916 }
917
918 // No valid frame yet, or it was a duplicate
919 retries++;
920 }
921 return platf::capture_e::timeout;
922 }
923
929 std::shared_ptr<platf::img_t> alloc_img() override {
930 // Note: this img_t type is also used for memory buffers
931 auto img = std::make_shared<img_descriptor_t>();
932
933 img->width = width;
934 img->height = height;
935 img->pixel_pitch = 4;
936 img->row_pitch = img->pixel_pitch * width;
937 img->sequence = 0;
938 img->serial = std::numeric_limits<decltype(img->serial)>::max();
939 img->data = nullptr;
940 std::fill_n(img->sd.fds, 4, -1);
941
942 return img;
943 }
944
951 virtual bool check_stream_dead(platf::capture_e &out_status) {
952 return false; // Return to default stream dead handling.
953 }
954
955 platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
956 auto next_frame = std::chrono::steady_clock::now();
957
958 if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
959 BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. capture() failed with error.";
960 return platf::capture_e::error;
961 }
963
964 while (true) {
965 // Check if PipeWire signaled a dead stream
966 if (shared_state->stream_dead.exchange(false)) {
967 // Additional custom error-handling for subclasses on stream dead event
968 if (platf::capture_e status; check_stream_dead(status)) {
969 return status;
970 }
971 // Re-init the capture if the stream is dead for any other reason
972 BOOST_LOG(warning) << "[pipewire] PipeWire stream disconnected. Forcing session reset."sv;
973 return platf::capture_e::reinit;
974 }
975
976 // Advance to (or catch up with) next delay interval
977 auto now = std::chrono::steady_clock::now();
978 while (next_frame < now) {
979 next_frame += delay;
980 }
981
982 if (next_frame > now) {
983 std::this_thread::sleep_until(next_frame);
986 }
987
988 std::shared_ptr<platf::img_t> img_out;
989 switch (const auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor)) {
990 case platf::capture_e::reinit:
991 case platf::capture_e::error:
992 case platf::capture_e::interrupted:
993 pipewire.frame_cv().notify_all();
994 return status;
995 case platf::capture_e::timeout:
996 if (!pull_free_image_cb(img_out)) {
997 // Detect if shutdown is pending
998 BOOST_LOG(debug) << "[pipewire] PipeWire: timeout -> shutdown pending -> interrupt nudge";
999 pipewire.frame_cv().notify_all();
1000 return platf::capture_e::interrupted;
1001 }
1002 if (!push_captured_image_cb(std::move(img_out), false)) {
1003 BOOST_LOG(debug) << "[pipewire] PipeWire: timeout -> !push_captured_image_cb -> ok";
1004 return platf::capture_e::ok;
1005 }
1006 break;
1007 case platf::capture_e::ok:
1008 if (!push_captured_image_cb(std::move(img_out), true)) {
1009 BOOST_LOG(debug) << "[pipewire] PipeWire: ok -> !push_captured_image_cb -> ok";
1010 return platf::capture_e::ok;
1011 }
1012 break;
1013 default:
1014 BOOST_LOG(error) << "[pipewire] Unrecognized capture status ["sv << std::to_underlying(status) << ']';
1015 return status;
1016 }
1017 }
1018
1019 return platf::capture_e::ok;
1020 }
1021
1028 std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
1029#ifdef SUNSHINE_BUILD_VAAPI
1030 if (mem_type == platf::mem_type_e::vaapi) {
1031 return va::make_avcodec_encode_device(width, height, n_dmabuf_infos > 0);
1032 }
1033#endif
1034
1035#ifdef SUNSHINE_BUILD_VULKAN
1036 if (mem_type == platf::mem_type_e::vulkan && n_dmabuf_infos > 0) {
1037 return vk::make_avcodec_encode_device_vram(width, height, 0, 0);
1038 }
1039#endif
1040
1041#ifdef SUNSHINE_BUILD_CUDA
1042 if (mem_type == platf::mem_type_e::cuda) {
1043 if (display_is_nvidia && n_dmabuf_infos > 0) {
1044 // Display GPU is NVIDIA - can use DMA-BUF directly
1045 return cuda::make_avcodec_gl_encode_device(width, height, 0, 0);
1046 } else {
1047 // Hybrid system (Intel display + NVIDIA encode) - use memory buffer path
1048 // DMA-BUFs from Intel GPU cannot be imported into CUDA
1049 return cuda::make_avcodec_encode_device(width, height, false);
1050 }
1051 }
1052#endif
1053
1054 return std::make_unique<platf::avcodec_encode_device_t>();
1055 }
1056
1063 int dummy_img(platf::img_t *img) override {
1064 // Empty images are recognized as dummies by the zero sequence number
1065 return 0;
1066 }
1067
1073 bool is_hdr() override {
1074 int color_primaries = shared_state->color_primaries.load();
1075 int transfer_function = shared_state->transfer_function.load();
1076
1077 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
1078 return true;
1079 }
1080
1081 return false;
1082 }
1083
1090 bool get_hdr_metadata(SS_HDR_METADATA &metadata) override {
1091 int color_primaries = shared_state->color_primaries.load();
1092 int transfer_function = shared_state->transfer_function.load();
1093
1094 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
1095 // Report Rec 2020 primaries
1096 metadata.displayPrimaries[0].x = 0.708f * 50000;
1097 metadata.displayPrimaries[0].y = 0.292f * 50000;
1098 metadata.displayPrimaries[1].x = 0.170f * 50000;
1099 metadata.displayPrimaries[1].y = 0.797f * 50000;
1100 metadata.displayPrimaries[2].x = 0.131f * 50000;
1101 metadata.displayPrimaries[2].y = 0.046f * 50000;
1102 metadata.whitePoint.x = 0.3127f * 50000;
1103 metadata.whitePoint.y = 0.3290f * 50000;
1104
1105 // This is according to HDR10+ standards, should probably be based on actual data
1106 metadata.maxDisplayLuminance = 4000;
1107 metadata.minDisplayLuminance = 1;
1108
1109 // These are content-specific metadata parameters that this interface doesn't give us
1110 metadata.maxContentLightLevel = 0;
1111 metadata.maxFrameAverageLightLevel = 0;
1112 metadata.maxFullFrameLuminance = 0;
1113
1114 return true;
1115 }
1116
1117 return false;
1118 }
1119
1120 private:
1121 bool is_buffer_redundant(const egl::img_descriptor_t *img) {
1122 // Check for corrupted frame
1123 if (img->pw_flags.has_value() && (img->pw_flags.value() & SPA_CHUNK_FLAG_CORRUPTED)) {
1124 return true;
1125 }
1126
1127 // If PTS is identical, only drop if damage metadata confirms no change
1128 if (img->pts.has_value() && last_pts.has_value() && img->pts.value() == last_pts.value()) {
1129 return img->pw_damage.has_value() && !img->pw_damage.value();
1130 }
1131
1132 return false;
1133 }
1134
1135 void update_metadata(egl::img_descriptor_t *img, int retries) {
1136 last_seq = img->seq;
1137 last_pts = img->pts;
1138 img->sequence = ++sequence;
1139
1140 if (retries > 0) {
1141 BOOST_LOG(debug) << "[pipewire] Processed frame after " << retries << " redundant events."sv;
1142 }
1143 }
1144
1145 bool wait_for_frame(std::chrono::steady_clock::time_point deadline) {
1146 std::unique_lock<std::mutex> lock(pipewire.frame_mutex());
1147
1148 bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] {
1149 return pipewire.is_frame_ready() || shared_state->stream_dead.load();
1150 });
1151
1152 if (success) {
1154 return true;
1155 }
1156 return false;
1157 }
1158
1159 static bool pw_format_supported(uint64_t fourcc, std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats) {
1160 for (const auto &drm_format : dmabuf_formats) {
1161 if (drm_format == fourcc) {
1162 return true;
1163 }
1164 }
1165 return false;
1166 }
1167
1168 void query_dmabuf_formats(EGLDisplay egl_display) {
1169 EGLint num_dmabuf_formats = 0;
1170 std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats = {0};
1171 eglQueryDmaBufFormatsEXT(egl_display, MAX_DMABUF_FORMATS, dmabuf_formats.data(), &num_dmabuf_formats);
1172
1173 if (num_dmabuf_formats > MAX_DMABUF_FORMATS) {
1174 BOOST_LOG(warning) << "[pipewire] Some DMA-BUF formats are being ignored"sv;
1175 }
1176
1177 for (const auto &fmt : format_map) {
1178 if (n_dmabuf_infos >= MAX_DMABUF_FORMATS) {
1179 break;
1180 }
1181
1182 if (!pw_format_supported(fmt.fourcc, dmabuf_formats)) {
1183 continue;
1184 }
1185
1186 EGLint num_modifiers = 0;
1187 std::array<EGLuint64KHR, MAX_DMABUF_MODIFIERS> mods = {0};
1188 eglQueryDmaBufModifiersEXT(egl_display, fmt.fourcc, MAX_DMABUF_MODIFIERS, mods.data(), nullptr, &num_modifiers);
1189
1190 if (num_modifiers > MAX_DMABUF_MODIFIERS) {
1191 BOOST_LOG(warning) << "[pipewire] Some DMA-BUF modifiers are being ignored"sv;
1192 }
1193
1194 dmabuf_infos[n_dmabuf_infos].format = fmt.pw_format;
1195 dmabuf_infos[n_dmabuf_infos].n_modifiers = MIN(num_modifiers, MAX_DMABUF_MODIFIERS);
1196 dmabuf_infos[n_dmabuf_infos].modifiers =
1197 static_cast<uint64_t *>(g_memdup2(mods.data(), sizeof(uint64_t) * dmabuf_infos[n_dmabuf_infos].n_modifiers));
1198 ++n_dmabuf_infos;
1199 }
1200 }
1201
1202 int get_dmabuf_modifiers() {
1203 if (wl_display.init() < 0) {
1204 return -1;
1205 }
1206
1207 auto egl_display = egl::make_display(wl_display.get());
1208 if (!egl_display) {
1209 return -1;
1210 }
1211
1212 // Detect if this is a pure NVIDIA system (not hybrid Intel+NVIDIA)
1213 // On hybrid systems, the wayland compositor typically runs on Intel,
1214 // so DMA-BUFs from portal will come from Intel and cannot be imported into CUDA.
1215 // Check if Intel GPU exists - if so, assume hybrid system and disable CUDA DMA-BUF.
1216 bool has_intel_gpu = std::ifstream("/sys/class/drm/card0/device/vendor").good() ||
1217 std::ifstream("/sys/class/drm/card1/device/vendor").good();
1218 if (has_intel_gpu) {
1219 // Read vendor IDs to check for Intel (0x8086)
1220 auto check_intel = [](const std::string &path) {
1221 if (std::ifstream f(path); f.good()) {
1222 std::string vendor;
1223 f >> vendor;
1224 return vendor == "0x8086";
1225 }
1226 return false;
1227 };
1228 bool intel_present = check_intel("/sys/class/drm/card0/device/vendor") ||
1229 check_intel("/sys/class/drm/card1/device/vendor");
1230 if (intel_present) {
1231 BOOST_LOG(info) << "[pipewire] Hybrid GPU system detected (Intel + discrete) - CUDA will use memory buffers"sv;
1232 display_is_nvidia = false;
1233 } else {
1234 // No Intel GPU found, check if NVIDIA is present
1235 const char *vendor = eglQueryString(egl_display.get(), EGL_VENDOR);
1236 if (vendor && std::string_view(vendor).contains("NVIDIA")) {
1237 BOOST_LOG(info) << "[pipewire] Pure NVIDIA system - DMA-BUF will be enabled for CUDA"sv;
1238 display_is_nvidia = true;
1239 }
1240 }
1241 }
1242
1243 if (eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT) {
1244 query_dmabuf_formats(egl_display.get());
1245 }
1246
1247 return 0;
1248 }
1249
1250 platf::mem_type_e mem_type;
1251 wl::display_t wl_display;
1252 std::array<struct dmabuf_format_info_t, MAX_DMABUF_FORMATS> dmabuf_infos;
1253 int n_dmabuf_infos;
1254 bool display_is_nvidia = false; // Track if display GPU is NVIDIA
1255 std::chrono::nanoseconds delay;
1256 std::optional<std::uint64_t> last_pts {};
1257 std::optional<std::uint64_t> last_seq {};
1258 std::uint64_t sequence {};
1259 uint32_t framerate;
1260
1261 protected:
1262 // Allow subclasses to access for pipewire requirements setup and stream dead checks
1264 std::shared_ptr<shared_state_t> shared_state;
1265 };
1266} // namespace pipewire
Captured image descriptor shared by EGL conversion paths.
Definition graphics.h:603
std::optional< bool > pw_damage
Whether PipeWire damage tracking should be used.
Definition graphics.h:633
std::optional< uint32_t > pw_flags
PipeWire frame flags reported with the buffer.
Definition graphics.h:634
std::optional< uint64_t > seq
PipeWire frame sequence number.
Definition graphics.h:632
std::uint64_t sequence
Monotonic value used to detect when GL resources must be recreated.
Definition graphics.h:625
surface_descriptor_t sd
DMA-BUF surface descriptor for the captured image.
Definition graphics.h:622
void reset()
Reset the object to its initial empty state.
Definition graphics.h:612
std::optional< uint64_t > pts
PipeWire presentation timestamp.
Definition graphics.h:631
void reset()
Reset the object to its initial empty state.
Definition logging.h:260
void second_point_now_and_log()
Store the current time as the second timestamp and log the elapsed interval.
Definition logging.h:251
void first_point(const std::chrono::steady_clock::time_point &point)
Store the first timestamp for a measured interval.
Definition logging.h:222
Display capture backend that consumes frames from a PipeWire stream.
Definition pipewire.cpp:718
bool is_hdr() override
Report whether the active display mode is HDR.
Definition pipewire.cpp:1073
virtual void verify_and_update_display_parameters()
Verify and update display parameters for logical dimensions, desktop dimensions and logical desktop d...
Definition pipewire.cpp:756
std::shared_ptr< platf::img_t > alloc_img() override
Allocate an image buffer compatible with this display backend.
Definition pipewire.cpp:929
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr< platf::img_t > &img_out, std::chrono::milliseconds timeout, bool show_cursor)
Capture a display frame into the provided image object.
Definition pipewire.cpp:893
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override
Capture a frame.
Definition pipewire.cpp:955
bool get_hdr_metadata(SS_HDR_METADATA &metadata) override
Read HDR metadata for the active display mode.
Definition pipewire.cpp:1090
pipewire_t pipewire
Pipewire.
Definition pipewire.cpp:1263
int dummy_img(platf::img_t *img) override
Populate a fallback image when real capture data is unavailable.
Definition pipewire.cpp:1063
std::shared_ptr< shared_state_t > shared_state
Shared state.
Definition pipewire.cpp:1264
virtual bool check_stream_dead(platf::capture_e &out_status)
Check stream dead.
Definition pipewire.cpp:951
static bool init_pipewire_and_check_hwdevice_type(platf::mem_type_e hwdevice_type)
Initialize pipewire and check hwdevice type.
Definition pipewire.cpp:726
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override
Create AVCodec encode device.
Definition pipewire.cpp:1028
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config)
Initialize the PipeWire display backend for a selected stream.
Definition pipewire.cpp:800
virtual int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_objectserial)=0
Configure the pipewire stream.
PipeWire core, context, and stream setup used for screencast capture.
Definition pipewire.cpp:141
void fill_img(platf::img_t *img)
Copy the latest PipeWire frame into Sunshine's image buffer.
Definition pipewire.cpp:414
std::condition_variable & frame_cv()
Return the condition variable signaled when frame state changes.
Definition pipewire.cpp:208
bool is_frame_ready() const
Check whether frame ready.
Definition pipewire.cpp:217
static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d)
Populate a Sunshine image descriptor from PipeWire DMA-BUF planes.
Definition pipewire.cpp:397
void set_negotiate_maxframerate(bool negotiate_maxframerate)
Set negotiate maxframerate.
Definition pipewire.cpp:451
void set_frame_ready(bool ready)
Set frame ready.
Definition pipewire.cpp:226
std::mutex & frame_mutex()
Return the mutex protecting PipeWire frame state.
Definition pipewire.cpp:199
int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr< shared_state_t > shared_state)
Initialize PipeWire core objects and optional stream negotiation.
Definition pipewire.cpp:239
int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia)
Create the PipeWire stream if it is not already active.
Definition pipewire.cpp:282
static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf)
Copy PipeWire metadata into the Sunshine image descriptor.
Definition pipewire.cpp:369
static void close_img_fds(egl::img_descriptor_t *img_descriptor)
Close img fds.
Definition pipewire.cpp:354
Abstract display capture backend used by the streaming pipeline.
Definition common.h:658
std::function< bool(std::shared_ptr< img_t > &&img, bool frame_captured)> push_captured_image_cb_t
Callback for when a new image is ready. When display has a new image ready or a timeout occurs,...
Definition common.h:667
int env_height
Height of the full capture environment in physical pixels.
Definition common.h:765
int env_logical_height
Height of the full capture environment after display scaling.
Definition common.h:767
std::function< bool(std::shared_ptr< img_t > &img_out)> pull_free_image_cb_t
Get free image from pool. Calls must be synchronized. Blocks until there is free image in the pool or...
Definition common.h:676
logging::time_delta_periodic_logger sleep_overshoot_logger
Periodic logger for capture sleep overshoot measurements.
Definition common.h:775
int offset_y
Vertical capture offset in physical pixels.
Definition common.h:763
int width
Width of the captured display in physical pixels.
Definition common.h:768
int logical_height
Height of the captured display after display scaling.
Definition common.h:771
int env_width
Width of the full capture environment in physical pixels.
Definition common.h:764
int height
Height of the captured display in physical pixels.
Definition common.h:769
int offset_x
Horizontal capture offset in physical pixels.
Definition common.h:762
int env_logical_width
Width of the full capture environment after display scaling.
Definition common.h:766
int logical_width
Width of the captured display after display scaling.
Definition common.h:770
Wayland display connection used to dispatch capture events.
Definition wayland.h:380
int init(const char *display_name=nullptr)
Connect to the requested Wayland display.
Definition wayland.cpp:55
display_internal_t::pointer get()
Return the native Wayland display pointer.
Definition wayland.h:411
Declarations for common platform specific utilities.
mem_type_e
Enumerates supported mem type options.
Definition common.h:303
pix_fmt_e
Enumerates supported pix fmt options.
Definition common.h:316
capture_e
Enumerates supported capture options.
Definition common.h:647
Definitions for CUDA implementation.
display_t make_display(std::variant< gbm::gbm_t::pointer, wl_display *, _XDisplay * > native_display)
Open and initialize the display connection used for capture.
Definition graphics.cpp:344
Declarations for graphics related functions.
int close(int __fd)
Release the native resource held by the RAII wrapper.
bl::sources::severity_logger< int > debug
Follow what is happening.
Definition logging.cpp:41
bl::sources::severity_logger< int > error
Recoverable errors.
Definition logging.cpp:44
bl::sources::severity_logger< int > info
Should be informed about.
Definition logging.cpp:42
bl::sources::severity_logger< int > warning
Strange events.
Definition logging.cpp:43
Declarations for the main entry point for Sunshine.
std::vector< std::unique_ptr< monitor_t > > monitors(const char *display_name)
Refresh the monitor list reported by the display server.
Definition wayland.cpp:559
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084
Protocol or platform constant for spa video transfer smpte2084.
Definition pipewire.cpp:28
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL
Whether PipeWire object serials should be used for matching.
Definition pipewire.cpp:39
#define PW_KEY_TARGET_OBJECT
Macro for PW KEY TARGET OBJECT.
Definition pipewire.cpp:44
state_e state(session_t &session)
Platform handle returned from stream setup.
Definition stream.cpp:2133
std::uint32_t fourcc
DRM fourcc pixel format for the buffer.
Definition graphics.h:500
int fds[4]
DMA-BUF file descriptors for up to four planes.
Definition graphics.h:499
std::uint32_t pitches[4]
Row stride in bytes for each DMA-BUF plane.
Definition graphics.h:502
std::uint64_t modifier
DRM format modifier describing the buffer layout.
Definition graphics.h:501
int height
Frame or display height in pixels.
Definition graphics.h:498
int width
Frame or display width in pixels.
Definition graphics.h:497
std::uint32_t offsets[4]
Byte offset to the first pixel for each DMA-BUF plane.
Definition graphics.h:503
DMA-BUF format and modifier list advertised by PipeWire.
Definition pipewire.cpp:120
int32_t format
PipeWire SPA video format being advertised.
Definition pipewire.cpp:121
int n_modifiers
Number of entries in modifiers.
Definition pipewire.cpp:123
uint64_t * modifiers
DRM format modifiers supported for the format.
Definition pipewire.cpp:122
PipeWire SPA format mapped to Sunshine pixel format.
Definition pipewire.cpp:61
uint64_t fourcc
DRM fourcc pixel format.
Definition pipewire.cpp:62
int32_t pw_format
Matching PipeWire SPA video format.
Definition pipewire.cpp:63
Pipewire image assembled for encoding.
Definition pipewire.cpp:129
PipeWire capture state shared with callback threads.
Definition pipewire.cpp:79
std::atomic< int > negotiated_height
Height negotiated with PipeWire for the stream.
Definition pipewire.cpp:81
std::atomic< bool > stream_dead
Whether the PipeWire stream has been destroyed.
Definition pipewire.cpp:84
std::atomic< int > color_primaries
PipeWire color-primaries metadata for the stream.
Definition pipewire.cpp:82
pw_stream_state previous_state
Previous PipeWire stream state reported by callbacks.
Definition pipewire.cpp:85
std::atomic< int > transfer_function
PipeWire transfer-function metadata for the stream.
Definition pipewire.cpp:83
pw_stream_state current_state
Current PipeWire stream state reported by callbacks.
Definition pipewire.cpp:86
std::atomic< int > negotiated_width
Width negotiated with PipeWire for the stream.
Definition pipewire.cpp:80
std::string err_msg
Last PipeWire error message reported by the stream.
Definition pipewire.cpp:87
PipeWire stream handle, format, and shared state pointer.
Definition pipewire.cpp:93
struct spa_hook stream_listener
Hook registering callbacks on the PipeWire stream.
Definition pipewire.cpp:95
std::condition_variable frame_cv
Signals arrival or release of a PipeWire frame.
Definition pipewire.cpp:101
std::mutex frame_mutex
Synchronizes access to the current PipeWire frame.
Definition pipewire.cpp:100
struct spa_video_info format
Negotiated PipeWire video format.
Definition pipewire.cpp:96
std::shared_ptr< shared_state_t > shared
State shared between PipeWire callbacks and the capture backend.
Definition pipewire.cpp:99
struct pw_stream * stream
PipeWire stream handle used for screencast frames.
Definition pipewire.cpp:94
std::vector< uint8_t > buffer_a
First staging buffer used for CPU-copy PipeWire frames.
Definition pipewire.cpp:105
std::vector< uint8_t > buffer_b
Second staging buffer used for CPU-copy PipeWire frames.
Definition pipewire.cpp:106
std::vector< uint8_t > * front_buffer
Staging buffer currently readable by fill_img.
Definition pipewire.cpp:108
std::vector< uint8_t > * back_buffer
Staging buffer currently writable by PipeWire callbacks.
Definition pipewire.cpp:110
uint64_t drm_format
DRM format.
Definition pipewire.cpp:98
size_t local_stride
Local stride.
Definition pipewire.cpp:102
struct pw_buffer * current_buffer
PipeWire buffer currently exposed to the capture thread.
Definition pipewire.cpp:97
bool frame_ready
Whether a PipeWire frame is ready to consume.
Definition pipewire.cpp:103
Captured frame buffer shared between capture and encode stages.
Definition common.h:502
std::int32_t row_pitch
Bytes between consecutive image rows.
Definition common.h:515
std::uint8_t * data
Pointer to the captured image buffer.
Definition common.h:511
std::optional< std::chrono::steady_clock::time_point > frame_timestamp
Capture timestamp associated with the frame.
Definition common.h:517
auto lock(const std::weak_ptr< void > &wp)
Acquire the underlying lock or keyed mutex.
Definition thread_safe.h:778
Declarations for VA-API hardware accelerated capture.
Declarations for video.
Declarations for FFmpeg Vulkan Video encoder.
Declarations for Wayland capture.