Skip to content

Commit c2b4823

Browse files
dkorkmazturkRytoEX
authored andcommitted
linux-pipewire: Add explicit sync support
It is possible for OBS to receive incomplete frames from the compositor if the graphics drivers in use do not support implicit synchronization. The compositors usually try to workaround this issue by waiting all render operations to complete before sharing frames with OBS. However, this has performance implications for the compositors. Similarly, OBS should also make sure that all the rendering operations sourcing the received buffer are complete before returning the respective pw_buffer back to the compositor, or it would risk the image it is working on getting overwritten without some type of synchronization. This change leverages PipeWire's ability to share DRM syncobj fds to implement an explicit synchronization solution to solve the problem described above. The usage of these DRM syncobjs is negotiated with the compositor that OBS is running on. So OBS can still work even if the compositor does not support explicit synchronization. Signed-off-by: Doğukan Korkmaztürk <[email protected]>
1 parent 68ca91b commit c2b4823

File tree

1 file changed

+111
-5
lines changed

1 file changed

+111
-5
lines changed

plugins/linux-pipewire/pipewire.c

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ struct _obs_pipewire_stream {
137137
struct spa_fraction fraction;
138138
bool set;
139139
} framerate;
140+
141+
struct {
142+
int acquire_syncobj_fd;
143+
int release_syncobj_fd;
144+
uint64_t acquire_point;
145+
uint64_t release_point;
146+
bool set;
147+
} sync;
140148
};
141149

142150
/* auxiliary methods */
@@ -545,6 +553,20 @@ static void renegotiate_format(void *data, uint64_t expirations)
545553

546554
/* ------------------------------------------------- */
547555

556+
static void return_unused_pw_buffer(struct pw_stream *stream, struct pw_buffer *b)
557+
{
558+
#if PW_CHECK_VERSION(1, 2, 0)
559+
struct spa_buffer *buffer = b->buffer;
560+
struct spa_data *last_data = &buffer->datas[buffer->n_datas - 1];
561+
struct spa_meta_sync_timeline *synctimeline =
562+
spa_buffer_find_meta_data(buffer, SPA_META_SyncTimeline, sizeof(struct spa_meta_sync_timeline));
563+
564+
if (synctimeline && (last_data->type == SPA_DATA_SyncObj))
565+
gs_sync_signal_syncobj_timeline_point(last_data->fd, synctimeline->release_point);
566+
#endif
567+
pw_stream_queue_buffer(stream, b);
568+
}
569+
548570
static inline struct pw_buffer *find_latest_buffer(struct pw_stream *stream)
549571
{
550572
struct pw_buffer *b;
@@ -556,13 +578,27 @@ static inline struct pw_buffer *find_latest_buffer(struct pw_stream *stream)
556578
if (!aux)
557579
break;
558580
if (b)
559-
pw_stream_queue_buffer(stream, b);
581+
return_unused_pw_buffer(stream, b);
560582
b = aux;
561583
}
562584

563585
return b;
564586
}
565587

588+
static uint32_t get_spa_buffer_plane_count(const struct spa_buffer *buffer)
589+
{
590+
uint32_t plane_count = 0;
591+
592+
while (plane_count < buffer->n_datas) {
593+
if (buffer->datas[plane_count].type == SPA_DATA_DmaBuf)
594+
plane_count++;
595+
else
596+
break;
597+
}
598+
599+
return plane_count;
600+
}
601+
566602
static enum video_colorspace video_colorspace_from_spa_color_matrix(enum spa_video_color_matrix matrix)
567603
{
568604
switch (matrix) {
@@ -681,7 +717,7 @@ static void process_video_sync(obs_pipewire_stream *obs_pw_stream)
681717
header = spa_buffer_find_meta_data(buffer, SPA_META_Header, sizeof(*header));
682718
if (header && (header->flags & SPA_META_HEADER_FLAG_CORRUPTED) > 0) {
683719
blog(LOG_ERROR, "[pipewire] buffer is corrupt");
684-
pw_stream_queue_buffer(obs_pw_stream->stream, b);
720+
return_unused_pw_buffer(obs_pw_stream->stream, b);
685721
return;
686722
}
687723

@@ -695,13 +731,17 @@ static void process_video_sync(obs_pipewire_stream *obs_pw_stream)
695731
goto read_metadata;
696732

697733
if (buffer->datas[0].type == SPA_DATA_DmaBuf) {
698-
uint32_t planes = buffer->n_datas;
734+
uint32_t planes = get_spa_buffer_plane_count(buffer);
699735
uint32_t *offsets = alloca(sizeof(uint32_t) * planes);
700736
uint32_t *strides = alloca(sizeof(uint32_t) * planes);
701737
uint64_t *modifiers = alloca(sizeof(uint64_t) * planes);
702738
int *fds = alloca(sizeof(int) * planes);
703739
bool use_modifiers;
704740
bool corrupt = false;
741+
#if PW_CHECK_VERSION(1, 2, 0)
742+
struct spa_meta_sync_timeline *synctimeline =
743+
spa_buffer_find_meta_data(buffer, SPA_META_SyncTimeline, sizeof(struct spa_meta_sync_timeline));
744+
#endif
705745

706746
#ifdef DEBUG_PIPEWIRE
707747
blog(LOG_DEBUG, "[pipewire] DMA-BUF info: fd:%ld, stride:%d, offset:%u, size:%dx%d",
@@ -724,6 +764,25 @@ static void process_video_sync(obs_pipewire_stream *obs_pw_stream)
724764
corrupt |= (buffer->datas[plane].chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) > 0;
725765
}
726766

767+
#if PW_CHECK_VERSION(1, 2, 0)
768+
if (synctimeline && (buffer->n_datas == (planes + 2))) {
769+
assert(buffer->datas[planes].type == SPA_DATA_SyncObj);
770+
assert(buffer->datas[planes + 1].type == SPA_DATA_SyncObj);
771+
772+
obs_pw_stream->sync.acquire_syncobj_fd = buffer->datas[planes].fd;
773+
obs_pw_stream->sync.acquire_point = synctimeline->acquire_point;
774+
775+
obs_pw_stream->sync.release_syncobj_fd = buffer->datas[planes + 1].fd;
776+
obs_pw_stream->sync.release_point = synctimeline->release_point;
777+
778+
obs_pw_stream->sync.set = true;
779+
} else {
780+
obs_pw_stream->sync.set = false;
781+
}
782+
#else
783+
obs_pw_stream->sync.set = false;
784+
#endif
785+
727786
if (corrupt) {
728787
blog(LOG_DEBUG, "[pipewire] buffer contains corrupted data");
729788
goto read_metadata;
@@ -861,13 +920,16 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
861920
obs_pipewire_stream *obs_pw_stream = user_data;
862921
obs_pipewire *obs_pw = obs_pw_stream->obs_pw;
863922
struct spa_pod_builder pod_builder;
864-
const struct spa_pod *params[5];
923+
const struct spa_pod *params[7];
865924
const char *format_name;
866925
uint32_t n_params = 0;
867926
uint32_t buffer_types;
868927
uint32_t output_flags;
869928
uint8_t params_buffer[1024];
870929
int result;
930+
#if PW_CHECK_VERSION(1, 2, 0)
931+
bool supports_explicit_sync = false;
932+
#endif
871933

872934
if (!param || id != SPA_PARAM_Format)
873935
return;
@@ -887,8 +949,14 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
887949
buffer_types = 1 << SPA_DATA_MemPtr;
888950
bool has_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier) != NULL;
889951
if ((has_modifier || check_pw_version(&obs_pw->server_version, 0, 3, 24)) &&
890-
(output_flags & OBS_SOURCE_ASYNC_VIDEO) != OBS_SOURCE_ASYNC_VIDEO)
952+
(output_flags & OBS_SOURCE_ASYNC_VIDEO) != OBS_SOURCE_ASYNC_VIDEO) {
891953
buffer_types |= 1 << SPA_DATA_DmaBuf;
954+
#if PW_CHECK_VERSION(1, 2, 0)
955+
obs_enter_graphics();
956+
supports_explicit_sync = gs_query_sync_capabilities();
957+
obs_leave_graphics();
958+
#endif
959+
}
892960

893961
blog(LOG_INFO, "[pipewire] Negotiated format:");
894962

@@ -921,9 +989,33 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
921989
CURSOR_META_SIZE(1024, 1024)));
922990

923991
/* Buffer options */
992+
#if PW_CHECK_VERSION(1, 2, 0)
993+
if (supports_explicit_sync) {
994+
struct spa_pod_frame dmabuf_explicit_sync_frame;
995+
996+
spa_pod_builder_push_object(&pod_builder, &dmabuf_explicit_sync_frame, SPA_TYPE_OBJECT_ParamBuffers,
997+
SPA_PARAM_Buffers);
998+
spa_pod_builder_add(&pod_builder, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(1 << SPA_DATA_DmaBuf), 0);
999+
spa_pod_builder_prop(&pod_builder, SPA_PARAM_BUFFERS_metaType, SPA_POD_PROP_FLAG_MANDATORY);
1000+
spa_pod_builder_int(&pod_builder, 1 << SPA_META_SyncTimeline);
1001+
1002+
params[n_params++] = spa_pod_builder_pop(&pod_builder, &dmabuf_explicit_sync_frame);
1003+
}
1004+
#endif
1005+
9241006
params[n_params++] = spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
9251007
SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types));
9261008

1009+
/* Sync timeline */
1010+
#if PW_CHECK_VERSION(1, 2, 0)
1011+
if (supports_explicit_sync) {
1012+
params[n_params++] = spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
1013+
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_SyncTimeline),
1014+
SPA_PARAM_META_size,
1015+
SPA_POD_Int(sizeof(struct spa_meta_sync_timeline)));
1016+
}
1017+
#endif
1018+
9271019
/* Meta header */
9281020
params[n_params++] = spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
9291021
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
@@ -1211,6 +1303,13 @@ void obs_pipewire_stream_video_render(obs_pipewire_stream *obs_pw_stream, gs_eff
12111303
if (!obs_pw_stream->texture)
12121304
return;
12131305

1306+
if (obs_pw_stream->sync.set) {
1307+
gs_sync_t *acquire_sync = gs_sync_create_from_syncobj_timeline_point(
1308+
obs_pw_stream->sync.acquire_syncobj_fd, obs_pw_stream->sync.acquire_point);
1309+
gs_sync_wait(acquire_sync);
1310+
gs_sync_destroy(acquire_sync);
1311+
}
1312+
12141313
image = gs_effect_get_param_by_name(effect, "image");
12151314
gs_effect_set_texture(image, obs_pw_stream->texture);
12161315

@@ -1253,6 +1352,13 @@ void obs_pipewire_stream_video_render(obs_pipewire_stream *obs_pw_stream, gs_eff
12531352
}
12541353

12551354
gs_blend_state_pop();
1355+
1356+
if (obs_pw_stream->sync.set) {
1357+
gs_sync_t *release_sync = gs_sync_create();
1358+
gs_sync_export_syncobj_timeline_point(release_sync, obs_pw_stream->sync.release_syncobj_fd,
1359+
obs_pw_stream->sync.release_point);
1360+
gs_sync_destroy(release_sync);
1361+
}
12561362
}
12571363

12581364
void obs_pipewire_stream_set_cursor_visible(obs_pipewire_stream *obs_pw_stream, bool cursor_visible)

0 commit comments

Comments
 (0)