diff --git a/src/Apps/gamescopestream.cpp b/src/Apps/gamescopestream.cpp index b5ae7d9d43..ee9ac90883 100644 --- a/src/Apps/gamescopestream.cpp +++ b/src/Apps/gamescopestream.cpp @@ -2,7 +2,7 @@ // Gracefully butchered from https://docs.pipewire.org/spa_2examples_2adapter-control_8c-example.html // by Wim Taymans under MIT /////////////////////////////////////////////////////////////////////////////////////////////////////// - + #include #include #include @@ -11,19 +11,19 @@ #include #include #include - + #include #include #include #include - + #include - + #define DEFAULT_WIDTH 1280 #define DEFAULT_HEIGHT 720 - + #define MAX_BUFFERS 64 - + #include #include #include @@ -37,159 +37,147 @@ #include "log.hpp" static LogScope s_StreamLog( "stream" ); - + void spa_gamescopestream_log( struct spa_debug_context *ctx, const char *fmt, ... ) { - va_list args; - va_start( args, fmt ); - s_StreamLog.vlogf( LOG_DEBUG, fmt, args ); - va_end( args ); + va_list args; + va_start( args, fmt ); + s_StreamLog.vlogf( LOG_DEBUG, fmt, args ); + va_end( args ); } struct spa_debug_context s_SpaDebugContext = { - .log = spa_gamescopestream_log, + .log = spa_gamescopestream_log, }; struct pw_version { - int major; - int minor; - int micro; + int major; + int minor; + int micro; }; -static uint32_t spa_format_to_drm(uint32_t spa_format) -{ - switch (spa_format) - { - case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; - default: - case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888; - } -} - struct data { - const char *path; - - wl_display *pDisplay = nullptr; - wl_compositor *pCompositor = nullptr; - zwp_linux_dmabuf_v1 *pLinuxDmabuf = nullptr; - libdecor *pDecor = nullptr; - libdecor_frame *pFrame = nullptr; - wl_surface *pSurface = nullptr; - wl_buffer *pWaylandBuffer = nullptr; - - struct pw_main_loop *loop; - struct spa_source *reneg; - - struct pw_stream *stream; - struct spa_hook stream_listener; - - struct spa_video_info format; - int32_t stride; - struct spa_rectangle size; - - bool needs_decor_commit; - - uint32_t appid; - - std::unordered_map> m_FormatModifiers; - - int counter; + const char *path; + + wl_display *pDisplay = nullptr; + wl_compositor *pCompositor = nullptr; + zwp_linux_dmabuf_v1 *pLinuxDmabuf = nullptr; + libdecor *pDecor = nullptr; + libdecor_frame *pFrame = nullptr; + wl_surface *pSurface = nullptr; + wl_buffer *pWaylandBuffer = nullptr; + + struct pw_main_loop *loop; + struct spa_source *reneg; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + struct spa_video_info format; + + bool needs_decor_commit; + + uint32_t appid; + + std::unordered_map> m_FormatModifiers; + + int counter; }; - + static void handle_events( struct data *pData ) { - wl_display_flush( pData->pDisplay ); - - if ( wl_display_prepare_read( pData->pDisplay ) == 0 ) - { - int nRet = 0; - pollfd pollfd = - { - .fd = wl_display_get_fd( pData->pDisplay ), - .events = POLLIN, - }; - - do - { - nRet = poll( &pollfd, 1, 0 ); - } while ( nRet < 0 && ( errno == EINTR || errno == EAGAIN ) ); - - if ( nRet > 0 ) - wl_display_read_events( pData->pDisplay ); - else - wl_display_cancel_read( pData->pDisplay ); - } - - wl_display_dispatch_pending( pData->pDisplay ); + wl_display_flush( pData->pDisplay ); + + if ( wl_display_prepare_read( pData->pDisplay ) == 0 ) + { + int nRet = 0; + pollfd pollfd = + { + .fd = wl_display_get_fd( pData->pDisplay ), + .events = POLLIN, + }; + + do + { + nRet = poll( &pollfd, 1, 0 ); + } while ( nRet < 0 && ( errno == EINTR || errno == EAGAIN ) ); + + if ( nRet > 0 ) + wl_display_read_events( pData->pDisplay ); + else + wl_display_cancel_read( pData->pDisplay ); + } + + wl_display_dispatch_pending( pData->pDisplay ); } - + static struct spa_pod *build_format(struct data *data, struct spa_pod_builder *b, enum spa_video_format format, uint64_t *modifiers, int modifier_count) { - struct spa_pod_frame f[3]; - int i, c; - - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); - spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); - /* format */ - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); - /* modifiers */ - if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { - // we only support implicit modifiers, use shortpath to skip fixation phase - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); - spa_pod_builder_long(b, modifiers[0]); - } else if (modifier_count > 0) { - // build an enumeration of modifiers - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); - // modifiers from the array - for (i = 0, c = 0; i < modifier_count; i++) { - spa_pod_builder_long(b, modifiers[i]); - if (c++ == 0) - spa_pod_builder_long(b, modifiers[i]); - } - spa_pod_builder_pop(b, &f[1]); - } - - spa_rectangle default_size = SPA_RECTANGLE(DEFAULT_WIDTH, DEFAULT_HEIGHT); - spa_rectangle min_size = SPA_RECTANGLE(1,1); - spa_rectangle max_size = SPA_RECTANGLE(65535, 65535); - - spa_fraction frac1 = SPA_FRACTION(25,1); - spa_fraction frac2 = SPA_FRACTION(0,1); - spa_fraction frac3 = SPA_FRACTION(30,1); - - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, - SPA_POD_CHOICE_RANGE_Rectangle( &default_size, &min_size, &max_size), - 0); - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, - SPA_POD_CHOICE_RANGE_Fraction( - &frac1, - &frac2, - &frac3), - 0); - - if (data->appid) - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_Long(uint64_t(data->appid)), 0); - - return (spa_pod *)spa_pod_builder_pop(b, &f[0]); + struct spa_pod_frame f[3]; + int i, c; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* format */ + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + /* modifiers */ + if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { + // we only support implicit modifiers, use shortpath to skip fixation phase + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(b, modifiers[0]); + } else if (modifier_count > 0) { + // build an enumeration of modifiers + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + // modifiers from the array + for (i = 0, c = 0; i < modifier_count; i++) { + spa_pod_builder_long(b, modifiers[i]); + if (c++ == 0) + spa_pod_builder_long(b, modifiers[i]); + } + spa_pod_builder_pop(b, &f[1]); + } + + spa_rectangle default_size = SPA_RECTANGLE(DEFAULT_WIDTH, DEFAULT_HEIGHT); + spa_rectangle min_size = SPA_RECTANGLE(1,1); + spa_rectangle max_size = SPA_RECTANGLE(65535, 65535); + + spa_fraction frac1 = SPA_FRACTION(25,1); + spa_fraction frac2 = SPA_FRACTION(0,1); + spa_fraction frac3 = SPA_FRACTION(30,1); + + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle( &default_size, &min_size, &max_size), + 0); + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, + SPA_POD_CHOICE_RANGE_Fraction( + &frac1, + &frac2, + &frac3), + 0); + + if (data->appid) + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_Long(uint64_t(data->appid)), 0); + + return (spa_pod *)spa_pod_builder_pop(b, &f[0]); } void commit_libdecor( struct data *data, libdecor_configuration *pConfiguration ) { - uint32_t uWidth = data->format.info.raw.size.width; - uint32_t uHeight = data->format.info.raw.size.height; - uWidth = uWidth ? uWidth : 1280; - uHeight = uHeight ? uHeight : 720; + uint32_t uWidth = data->format.info.raw.size.width; + uint32_t uHeight = data->format.info.raw.size.height; + uWidth = uWidth ? uWidth : 1280; + uHeight = uHeight ? uHeight : 720; - libdecor_state *pState = libdecor_state_new( uWidth, uHeight ); - libdecor_frame_commit( data->pFrame, pState, pConfiguration ); - libdecor_state_free( pState ); + libdecor_state *pState = libdecor_state_new( uWidth, uHeight ); + libdecor_frame_commit( data->pFrame, pState, pConfiguration ); + libdecor_state_free( pState ); - data->needs_decor_commit = false; + data->needs_decor_commit = false; } - + /* our data processing function is in general: * * struct pw_buffer *b; @@ -202,118 +190,118 @@ void commit_libdecor( struct data *data, libdecor_configuration *pConfiguration static void on_process(void *_data) { - struct data *data = (struct data *)_data; - struct pw_stream *stream = data->stream; - struct pw_buffer *b; - struct spa_buffer *buf; - - b = nullptr; - /* dequeue and queue old buffers, use the last available - * buffer */ - while (true) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(stream)) == nullptr) - break; - if (b) - pw_stream_queue_buffer(stream, b); - b = t; - } - if (b == nullptr) { - pw_log_warn("out of buffers: %m"); - return; - } - - buf = b->buffer; - - pw_log_info("new buffer %p", buf); - - handle_events(data); - - zwp_linux_buffer_params_v1 *pBufferParams = zwp_linux_dmabuf_v1_create_params( data->pLinuxDmabuf ); - if ( !pBufferParams ) - { - pw_stream_queue_buffer(stream, b); - return; - } - - for ( uint32_t i = 0; i < buf->n_datas; i++ ) - { - zwp_linux_buffer_params_v1_add( - pBufferParams, - buf->datas[i].fd, - i, - buf->datas[i].chunk->offset, - buf->datas[i].chunk->stride, - data->format.info.raw.modifier >> 32, - data->format.info.raw.modifier & 0xffffffff); - } - - uint32_t uDrmFormat = spa_format_to_drm(data->format.info.raw.format); - - wl_buffer *pImportedBuffer = zwp_linux_buffer_params_v1_create_immed( - pBufferParams, - data->format.info.raw.size.width, - data->format.info.raw.size.height, - uDrmFormat, - 0u ); - - assert( pImportedBuffer ); - - struct StreamBuffer - { - struct data *pData = nullptr; - wl_buffer *pWaylandBuffer = nullptr; - pw_buffer *pPipewireBuffer = nullptr; - }; - - StreamBuffer *pStreamBuffer = new StreamBuffer - { - .pData = data, - .pWaylandBuffer = pImportedBuffer, - .pPipewireBuffer = b, - }; - static constexpr wl_buffer_listener s_BufferListener = - { - .release = []( void *pData, wl_buffer *pBuffer ) - { - StreamBuffer *pStreamBuffer = ( StreamBuffer * )pData; - pw_stream_queue_buffer( pStreamBuffer->pData->stream, pStreamBuffer->pPipewireBuffer ); - wl_buffer_destroy( pStreamBuffer->pWaylandBuffer ); - delete pStreamBuffer; - }, - }; - wl_buffer_add_listener( pImportedBuffer, &s_BufferListener, pStreamBuffer ); - - wl_surface_attach( data->pSurface, pImportedBuffer, 0, 0 ); - wl_surface_damage( data->pSurface, 0, 0, INT32_MAX, INT32_MAX ); - wl_surface_set_buffer_scale( data->pSurface, 1 ); - - if (data->needs_decor_commit) - commit_libdecor( data, nullptr ); - wl_surface_commit( data->pSurface ); - - wl_display_flush( data->pDisplay ); + struct data *data = (struct data *)_data; + struct pw_stream *stream = data->stream; + struct pw_buffer *b; + struct spa_buffer *buf; + + b = nullptr; + /* dequeue and queue old buffers, use the last available + * buffer */ + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(stream)) == nullptr) + break; + if (b) + pw_stream_queue_buffer(stream, b); + b = t; + } + if (b == nullptr) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + + pw_log_info("new buffer %p", buf); + + handle_events(data); + + zwp_linux_buffer_params_v1 *pBufferParams = zwp_linux_dmabuf_v1_create_params( data->pLinuxDmabuf ); + if ( !pBufferParams ) + { + pw_stream_queue_buffer(stream, b); + return; + } + + for ( uint32_t i = 0; i < buf->n_datas; i++ ) + { + zwp_linux_buffer_params_v1_add( + pBufferParams, + buf->datas[i].fd, + i, + buf->datas[i].chunk->offset, + buf->datas[i].chunk->stride, + data->format.info.raw.modifier >> 32, + data->format.info.raw.modifier & 0xffffffff); + } + + uint32_t uDrmFormat = spa_format_to_drm(data->format.info.raw.format); + + wl_buffer *pImportedBuffer = zwp_linux_buffer_params_v1_create_immed( + pBufferParams, + data->format.info.raw.size.width, + data->format.info.raw.size.height, + uDrmFormat, + 0u ); + + assert( pImportedBuffer ); + + struct StreamBuffer + { + struct data *pData = nullptr; + wl_buffer *pWaylandBuffer = nullptr; + pw_buffer *pPipewireBuffer = nullptr; + }; + + StreamBuffer *pStreamBuffer = new StreamBuffer + { + .pData = data, + .pWaylandBuffer = pImportedBuffer, + .pPipewireBuffer = b, + }; + static constexpr wl_buffer_listener s_BufferListener = + { + .release = []( void *pData, wl_buffer *pBuffer ) + { + StreamBuffer *pStreamBuffer = ( StreamBuffer * )pData; + pw_stream_queue_buffer( pStreamBuffer->pData->stream, pStreamBuffer->pPipewireBuffer ); + wl_buffer_destroy( pStreamBuffer->pWaylandBuffer ); + delete pStreamBuffer; + }, + }; + wl_buffer_add_listener( pImportedBuffer, &s_BufferListener, pStreamBuffer ); + + wl_surface_attach( data->pSurface, pImportedBuffer, 0, 0 ); + wl_surface_damage( data->pSurface, 0, 0, INT32_MAX, INT32_MAX ); + wl_surface_set_buffer_scale( data->pSurface, 1 ); + + if (data->needs_decor_commit) + commit_libdecor( data, nullptr ); + wl_surface_commit( data->pSurface ); + + wl_display_flush( data->pDisplay ); } - + static void on_stream_state_changed(void *_data, enum pw_stream_state old, - enum pw_stream_state state, const char *error) + enum pw_stream_state state, const char *error) { - struct data *data = (struct data *)_data; - s_StreamLog.debugf( "stream state: \"%s\"", pw_stream_state_as_string(state) ); - if ( error ) - s_StreamLog.errorf( "error: \"%s\"", error ); - switch (state) { - case PW_STREAM_STATE_UNCONNECTED: - pw_main_loop_quit(data->loop); - break; - case PW_STREAM_STATE_PAUSED: - break; - case PW_STREAM_STATE_STREAMING: - default: - break; - } + struct data *data = (struct data *)_data; + s_StreamLog.debugf( "stream state: \"%s\"", pw_stream_state_as_string(state) ); + if ( error ) + s_StreamLog.errorf( "error: \"%s\"", error ); + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + pw_main_loop_quit(data->loop); + break; + case PW_STREAM_STATE_PAUSED: + break; + case PW_STREAM_STATE_STREAMING: + default: + break; + } } - + /* Be notified when the stream param changes. We're only looking at the * format changes. * @@ -328,269 +316,257 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { - struct data *data = (struct data *)_data; - struct pw_stream *stream = data->stream; - uint8_t params_buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - const struct spa_pod *params[1]; - - /* nullptr means to clear the format */ - if (param == nullptr || id != SPA_PARAM_Format) - return; - - s_StreamLog.debugf( "got format:" ); - spa_debugc_format(&s_SpaDebugContext, 2, nullptr, param); - - if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) - return; - - if (data->format.media_type != SPA_MEDIA_TYPE_video || - data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return; - - /* call a helper function to parse the format for us. */ - spa_format_video_raw_parse(param, &data->format.info.raw); - data->size = data->format.info.raw.size; - - uint32_t drm_format = spa_format_to_drm(data->format.info.raw.format); - if (drm_format == DRM_FORMAT_INVALID) { - pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); - return; - } - if (data->size.width == 0 || data->size.height == 0) { - pw_stream_set_error(stream, -EINVAL, "invalid size"); - return; - } - - data->stride = SPA_ROUND_UP_N( data->size.width * 4, 4 ); - - /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, - * number, stride etc of the buffers */ - params[0] = (const struct spa_pod *) spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), - SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream; + uint8_t params_buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); + const struct spa_pod *params[1]; + + /* nullptr means to clear the format */ + if (param == nullptr || id != SPA_PARAM_Format) + return; + + s_StreamLog.debugf( "got format:" ); + spa_debugc_format(&s_SpaDebugContext, 2, nullptr, param); + + if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) + return; + + if (data->format.media_type != SPA_MEDIA_TYPE_video || + data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + /* call a helper function to parse the format for us. */ + spa_format_video_raw_parse(param, &data->format.info.raw); + + uint32_t drm_format = spa_format_to_drm(data->format.info.raw.format); + if (drm_format == DRM_FORMAT_INVALID) { + pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); + return; + } + + /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, + * number, stride etc of the buffers */ + params[0] = (const struct spa_pod *) spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<m_FormatModifiers.contains(DRM_FORMAT_XRGB8888)) - params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, data->m_FormatModifiers[DRM_FORMAT_XRGB8888].data(), uint32_t( data->m_FormatModifiers[DRM_FORMAT_XRGB8888].size() ) ); - params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, nullptr, 0 ); - - for (int i=0; i < n_params; i++) - spa_debugc_format(&s_SpaDebugContext, 2, NULL, params[i]); - - return n_params; + int n_params = 0; + + if (data->m_FormatModifiers.contains(DRM_FORMAT_XRGB8888)) + params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, data->m_FormatModifiers[DRM_FORMAT_XRGB8888].data(), uint32_t( data->m_FormatModifiers[DRM_FORMAT_XRGB8888].size() ) ); + params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, nullptr, 0 ); + + for (int i=0; i < n_params; i++) + spa_debugc_format(&s_SpaDebugContext, 2, NULL, params[i]); + + return n_params; } - + static void reneg_format(void *_data, uint64_t expiration) { - struct data *data = (struct data*) _data; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *params[2]; - uint32_t n_params; - - if (data->format.info.raw.format == 0) - return; - - s_StreamLog.debugf( "renegotiate formats:" ); - n_params = build_formats(data, &b, params); - - pw_stream_update_params(data->stream, params, n_params); + struct data *data = (struct data*) _data; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[2]; + uint32_t n_params; + + if (data->format.info.raw.format == 0) + return; + + s_StreamLog.debugf( "renegotiate formats:" ); + n_params = build_formats(data, &b, params); + + pw_stream_update_params(data->stream, params, n_params); } - + static void do_quit(void *userdata, int signal_number) { - struct data *data = (struct data *)userdata; - pw_main_loop_quit(data->loop); + struct data *data = (struct data *)userdata; + pw_main_loop_quit(data->loop); } - + int main(int argc, char *argv[]) { - struct data data = { 0, }; - const struct spa_pod *params[2]; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - struct pw_properties *props; - int res, n_params; - - pw_init(&argc, &argv); - - /* create a main loop */ - data.loop = pw_main_loop_new(nullptr); - - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); - - /* create a simple stream, the simple stream manages to core and remote - * objects for you if you don't need to deal with them - * - * If you plan to autoconnect your stream, you need to provide at least - * media, category and role properties - * - * Pass your events and a user_data pointer as the last arguments. This - * will inform you about the stream state. The most important event - * you need to listen to is the process event where you need to consume - * the data provided to you. - */ - props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - nullptr), - data.appid = argc > 1 ? atoi(argv[1]) : 0; - data.path = argc > 2 ? argv[2] : "gamescope"; - if (data.path) - /* Set stream target if given on command line */ - pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); - - data.stream = pw_stream_new_simple( - pw_main_loop_get_loop(data.loop), - "video-play-fixate", - props, - &stream_events, - &data); - - // - - if ( !( data.pDisplay = wl_display_connect( nullptr ) ) ) - return -1; - - wl_registry *pRegistry; - if ( !( pRegistry = wl_display_get_registry( data.pDisplay ) ) ) - return -1; - - static constexpr wl_registry_listener s_RegistryListener = - { - .global = [] ( void *pUserData, wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) - { - struct data *pData = (struct data *)pUserData; - if ( !strcmp( pInterface, wl_compositor_interface.name ) && uVersion >= 4u ) - { - pData->pCompositor = (wl_compositor *)wl_registry_bind( pRegistry, uName, &wl_compositor_interface, 4u ); - } - else if ( !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && uVersion >= 3 ) - { - pData->pLinuxDmabuf = (zwp_linux_dmabuf_v1 *)wl_registry_bind( pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); - static constexpr zwp_linux_dmabuf_v1_listener s_Listener = - { - .format = WAYLAND_NULL(), // Formats are also advertised by the modifier event, ignore them here. - .modifier = [] ( void *pUserData, zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) - { - uint64_t ulModifier = ( uint64_t( uModifierHi ) << 32 ) | uModifierLo; - - struct data *pData = (struct data *)pUserData; - if ( ulModifier != DRM_FORMAT_MOD_INVALID ) - pData->m_FormatModifiers[ uFormat ].emplace_back( ulModifier ); - }, - }; - zwp_linux_dmabuf_v1_add_listener( pData->pLinuxDmabuf, &s_Listener, pData ); - } - }, - .global_remove = WAYLAND_NULL(), - }; - - wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)&data ); - wl_display_roundtrip( data.pDisplay ); - - if ( !data.pCompositor || !data.pLinuxDmabuf ) - return -1; - - // Grab stuff from any extra bindings/listeners we set up, eg. format/modifiers. - wl_display_roundtrip( data.pDisplay ); - - wl_registry_destroy( pRegistry ); - pRegistry = nullptr; - - static libdecor_interface s_LibDecorInterface = - { - .error = []( libdecor *pContext, libdecor_error eError, const char *pMessage ) - { - s_StreamLog.errorf( "libdecor: %s", pMessage ); - }, - }; - data.pDecor = libdecor_new( data.pDisplay, &s_LibDecorInterface ); - if ( !data.pDecor ) - return -1; - - static libdecor_frame_interface s_LibDecorFrameInterface - { - .configure = []( libdecor_frame *pFrame, libdecor_configuration *pConfiguration, void *pUserData ) - { - struct data *pData = (struct data *)pUserData; - commit_libdecor( pData, pConfiguration ); - }, - .close = []( libdecor_frame *pFrame, void *pUserData ) - { - raise( SIGTERM ); - }, - .commit = []( libdecor_frame *pFrame, void *pUserData ) - { - struct data *pData = (struct data *)pUserData; - pData->needs_decor_commit = true; - }, - .dismiss_popup = []( libdecor_frame *pFrame, const char *pSeatName, void *pUserData ) - { - }, - }; - data.pSurface = wl_compositor_create_surface( data.pCompositor ); - data.pFrame = libdecor_decorate( data.pDecor, data.pSurface, &s_LibDecorFrameInterface, &data ); - libdecor_frame_set_title( data.pFrame, "Gamescope Pipewire Stream" ); - libdecor_frame_set_app_id( data.pFrame, "gamescopestream" ); - libdecor_frame_map( data.pFrame ); - wl_surface_commit( data.pSurface ); - wl_display_roundtrip( data.pDisplay ); - - // - - /* build the extra parameters to connect with. To connect, we can provide - * a list of supported formats. We use a builder that writes the param - * object to the stack. */ - s_StreamLog.debugf( "supported formats:" ); - n_params = build_formats(&data, &b, params); - - /* now connect the stream, we need a direction (input/output), - * an optional target node to connect to, some flags and parameters - */ - if ((res = pw_stream_connect(data.stream, - PW_DIRECTION_INPUT, - PW_ID_ANY, - pw_stream_flags( - PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ - PW_STREAM_FLAG_MAP_BUFFERS), /* mmap the buffer data for us */ - params, n_params)) /* extra parameters, see above */ < 0) { - s_StreamLog.errorf( "can't connect: %s\n", spa_strerror(res) ); - return -1; - } - - data.reneg = pw_loop_add_event(pw_main_loop_get_loop(data.loop), reneg_format, &data); - - /* do things until we quit the mainloop */ - pw_main_loop_run(data.loop); - - pw_stream_destroy(data.stream); - pw_main_loop_destroy(data.loop); - - // TODO: cleanup wayland - - pw_deinit(); - - return 0; + struct data data = { 0, }; + const struct spa_pod *params[2]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; + int res, n_params; + + pw_init(&argc, &argv); + + /* create a main loop */ + data.loop = pw_main_loop_new(nullptr); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* create a simple stream, the simple stream manages to core and remote + * objects for you if you don't need to deal with them + * + * If you plan to autoconnect your stream, you need to provide at least + * media, category and role properties + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the stream state. The most important event + * you need to listen to is the process event where you need to consume + * the data provided to you. + */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + nullptr), + data.appid = argc > 1 ? atoi(argv[1]) : 0; + data.path = argc > 2 ? argv[2] : "gamescope"; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + + data.stream = pw_stream_new_simple( + pw_main_loop_get_loop(data.loop), + "video-play-fixate", + props, + &stream_events, + &data); + + // + + if ( !( data.pDisplay = wl_display_connect( nullptr ) ) ) + return -1; + + wl_registry *pRegistry; + if ( !( pRegistry = wl_display_get_registry( data.pDisplay ) ) ) + return -1; + + static constexpr wl_registry_listener s_RegistryListener = + { + .global = [] ( void *pUserData, wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + { + struct data *pData = (struct data *)pUserData; + if ( !strcmp( pInterface, wl_compositor_interface.name ) && uVersion >= 4u ) + { + pData->pCompositor = (wl_compositor *)wl_registry_bind( pRegistry, uName, &wl_compositor_interface, 4u ); + } + else if ( !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && uVersion >= 3 ) + { + pData->pLinuxDmabuf = (zwp_linux_dmabuf_v1 *)wl_registry_bind( pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); + static constexpr zwp_linux_dmabuf_v1_listener s_Listener = + { + .format = WAYLAND_NULL(), // Formats are also advertised by the modifier event, ignore them here. + .modifier = [] ( void *pUserData, zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) + { + uint64_t ulModifier = ( uint64_t( uModifierHi ) << 32 ) | uModifierLo; + + struct data *pData = (struct data *)pUserData; + if ( ulModifier != DRM_FORMAT_MOD_INVALID ) + pData->m_FormatModifiers[ uFormat ].emplace_back( ulModifier ); + }, + }; + zwp_linux_dmabuf_v1_add_listener( pData->pLinuxDmabuf, &s_Listener, pData ); + } + }, + .global_remove = WAYLAND_NULL(), + }; + + wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)&data ); + wl_display_roundtrip( data.pDisplay ); + + if ( !data.pCompositor || !data.pLinuxDmabuf ) + return -1; + + // Grab stuff from any extra bindings/listeners we set up, eg. format/modifiers. + wl_display_roundtrip( data.pDisplay ); + + wl_registry_destroy( pRegistry ); + pRegistry = nullptr; + + static libdecor_interface s_LibDecorInterface = + { + .error = []( libdecor *pContext, libdecor_error eError, const char *pMessage ) + { + s_StreamLog.errorf( "libdecor: %s", pMessage ); + }, + }; + data.pDecor = libdecor_new( data.pDisplay, &s_LibDecorInterface ); + if ( !data.pDecor ) + return -1; + + static libdecor_frame_interface s_LibDecorFrameInterface + { + .configure = []( libdecor_frame *pFrame, libdecor_configuration *pConfiguration, void *pUserData ) + { + struct data *pData = (struct data *)pUserData; + commit_libdecor( pData, pConfiguration ); + }, + .close = []( libdecor_frame *pFrame, void *pUserData ) + { + raise( SIGTERM ); + }, + .commit = []( libdecor_frame *pFrame, void *pUserData ) + { + struct data *pData = (struct data *)pUserData; + pData->needs_decor_commit = true; + }, + .dismiss_popup = []( libdecor_frame *pFrame, const char *pSeatName, void *pUserData ) + { + }, + }; + data.pSurface = wl_compositor_create_surface( data.pCompositor ); + data.pFrame = libdecor_decorate( data.pDecor, data.pSurface, &s_LibDecorFrameInterface, &data ); + libdecor_frame_set_title( data.pFrame, "Gamescope Pipewire Stream" ); + libdecor_frame_set_app_id( data.pFrame, "gamescopestream" ); + libdecor_frame_map( data.pFrame ); + wl_surface_commit( data.pSurface ); + wl_display_roundtrip( data.pDisplay ); + + // + + /* build the extra parameters to connect with. To connect, we can provide + * a list of supported formats. We use a builder that writes the param + * object to the stack. */ + s_StreamLog.debugf( "supported formats:" ); + n_params = build_formats(&data, &b, params); + + /* now connect the stream, we need a direction (input/output), + * an optional target node to connect to, some flags and parameters + */ + if ((res = pw_stream_connect(data.stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT, /* try to automatically connect this stream */ + params, n_params)) /* extra parameters, see above */ < 0) { + s_StreamLog.errorf( "can't connect: %s\n", spa_strerror(res) ); + return -1; + } + + data.reneg = pw_loop_add_event(pw_main_loop_get_loop(data.loop), reneg_format, &data); + + /* do things until we quit the mainloop */ + pw_main_loop_run(data.loop); + + pw_stream_destroy(data.stream); + pw_main_loop_destroy(data.loop); + + // TODO: cleanup wayland + + pw_deinit(); + + return 0; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1397028e7f..62e28c48cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1030,7 +1030,7 @@ int main(int argc, char **argv) setenv("WAYLAND_DISPLAY", wlserver_get_wl_display_name(), 1); #if HAVE_PIPEWIRE - if ( !init_pipewire() ) + if ( !pipewire_init() ) { fprintf( stderr, "Warning: failed to setup PipeWire, screen capture won't be available\n" ); } diff --git a/src/pipewire.cpp b/src/pipewire.cpp index 1f93cb8a55..73c8543f1b 100644 --- a/src/pipewire.cpp +++ b/src/pipewire.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -19,7 +18,6 @@ static LogScope pwr_log("pipewire"); static struct pipewire_state pipewire_state = { .stream_node_id = SPA_ID_INVALID }; -static int nudgePipe[2] = { -1, -1 }; // Pending buffer for PipeWire → steamcompmgr static std::atomic out_buffer; @@ -39,28 +37,15 @@ static void destroy_buffer(struct pipewire_buffer *buffer) { switch (buffer->type) { case SPA_DATA_MemFd: - { - off_t size = buffer->shm.stride * buffer->video_info.size.height; - if (buffer->video_info.format == SPA_VIDEO_FORMAT_NV12) { - size += buffer->shm.stride * ((buffer->video_info.size.height + 1) / 2); - } - munmap(buffer->shm.data, size); + munmap(buffer->shm.data, buffer->shm.size); close(buffer->shm.fd); break; - } case SPA_DATA_DmaBuf: break; // nothing to do default: assert(false); // unreachable } - // If out_buffer == buffer, then set it to nullptr. - // We don't care about the result. - struct pipewire_buffer *buffer1 = buffer; - out_buffer.compare_exchange_strong(buffer1, nullptr); - struct pipewire_buffer *buffer2 = buffer; - in_buffer.compare_exchange_strong(buffer2, nullptr); - delete buffer; } @@ -89,14 +74,14 @@ static void calculate_capture_size() } } -static void build_format_params(struct spa_pod_builder *builder, spa_video_format format, std::vector ¶ms) { +static const struct spa_pod *build_format_params(struct spa_pod_builder *builder, const enum spa_video_format format, const std::span& modifiers) +{ struct spa_rectangle size = SPA_RECTANGLE(s_nCaptureWidth, s_nCaptureHeight); struct spa_rectangle min_requested_size = { 0, 0 }; struct spa_rectangle max_requested_size = { UINT32_MAX, UINT32_MAX }; struct spa_fraction framerate = SPA_FRACTION(0, 1); - uint64_t modifier = DRM_FORMAT_MOD_LINEAR; - struct spa_pod_frame obj_frame, choice_frame; + struct spa_pod_frame obj_frame; spa_pod_builder_push_object(builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), @@ -110,48 +95,30 @@ static void build_format_params(struct spa_pod_builder *builder, spa_video_forma if (format == SPA_VIDEO_FORMAT_NV12) { spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT709), + SPA_VIDEO_COLOR_MATRIX_BT709, + SPA_VIDEO_COLOR_MATRIX_BT709, + SPA_VIDEO_COLOR_MATRIX_BT601), SPA_FORMAT_VIDEO_colorRange, SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_COLOR_RANGE_16_235, SPA_VIDEO_COLOR_RANGE_16_235, SPA_VIDEO_COLOR_RANGE_0_255), 0); } - spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); - spa_pod_builder_push_choice(builder, &choice_frame, SPA_CHOICE_Enum, 0); - spa_pod_builder_long(builder, modifier); // default - spa_pod_builder_long(builder, modifier); - spa_pod_builder_pop(builder, &choice_frame); - params.push_back((const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame)); - - spa_pod_builder_push_object(builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, - 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_Id(format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate), - SPA_FORMAT_VIDEO_requested_size, SPA_POD_CHOICE_RANGE_Rectangle( &min_requested_size, &min_requested_size, &max_requested_size ), - SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), - 0); - if (format == SPA_VIDEO_FORMAT_NV12) { - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT709), - SPA_FORMAT_VIDEO_colorRange, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_0_255), - 0); + if (modifiers.size() == 1) { + // Pre-fixate if there is only one modifier + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(builder, modifiers[0]); + } else if (modifiers.size() > 0) { + struct spa_pod_frame choice_frame; + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(builder, &choice_frame, SPA_CHOICE_Enum, 0); + spa_pod_builder_long(builder, modifiers[0]); // default, but should be ignored because of FLAG_DONT_FIXATE + for (const uint64_t modifier : modifiers) { + spa_pod_builder_long(builder, modifier); + } + spa_pod_builder_pop(builder, &choice_frame); } - params.push_back((const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame)); - -// for (auto& param : params) -// spa_debug_format(2, nullptr, param); + return (const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame); } @@ -159,43 +126,65 @@ static std::vector build_format_params(struct spa_pod_bu { std::vector params; - build_format_params(builder, SPA_VIDEO_FORMAT_BGRx, params); - build_format_params(builder, SPA_VIDEO_FORMAT_NV12, params); + for (const enum spa_video_format format : { + SPA_VIDEO_FORMAT_BGRx, + SPA_VIDEO_FORMAT_NV12, + }) { + // TODO: Get supported modifiers for format + std::vector modifiers = { DRM_FORMAT_MOD_LINEAR }; + params.push_back(build_format_params(builder, format, modifiers)); + params.push_back(build_format_params(builder, format, {})); + } +// for (auto& param : params) +// spa_debug_format(2, nullptr, param); return params; } -static void request_buffer(struct pipewire_state *state) +static struct pipewire_buffer *dequeue_buffer(struct pipewire_state *state) { struct pw_buffer *pw_buffer = pw_stream_dequeue_buffer(state->stream); if (!pw_buffer) { - pwr_log.errorf("warning: out of buffers"); - return; + pwr_log.warnf("out of buffers"); + return nullptr; } - struct pipewire_buffer *buffer = (struct pipewire_buffer *) pw_buffer->user_data; + // Past this exchange, the PipeWire thread shares the buffer with the steamcompmgr thread buffer->copying = true; - - // Past this exchange, the PipeWire thread shares the buffer with the - // steamcompmgr thread - struct pipewire_buffer *old = out_buffer.exchange(buffer); - assert(old == nullptr); + return buffer; } -static void copy_buffer(struct pipewire_state *state, struct pipewire_buffer *buffer) +static void stream_handle_process(void *data) { + struct pipewire_state *state = (struct pipewire_state *) data; + + if (out_buffer == nullptr) { + out_buffer = dequeue_buffer(state); + } + + struct pipewire_buffer *buffer = in_buffer.exchange(nullptr); + if (buffer == nullptr) { + // Nothing was submitted + return; + } + // We now completely own the buffer, it's no longer shared with the steamcompmgr thread. + buffer->copying = false; + + if (buffer->IsStale()) { + destroy_buffer(buffer); + return; + } + gamescope::OwningRc &tex = buffer->texture; assert(tex != nullptr); struct pw_buffer *pw_buffer = buffer->buffer; struct spa_buffer *spa_buffer = pw_buffer->buffer; - bool needs_reneg = buffer->video_info.size.width != tex->width() || buffer->video_info.size.height != tex->height(); - struct spa_meta_header *header = (struct spa_meta_header *) spa_buffer_find_meta_data(spa_buffer, SPA_META_Header, sizeof(*header)); if (header != nullptr) { - header->pts = -1; - header->flags = needs_reneg ? SPA_META_HEADER_FLAG_CORRUPTED : 0; + header->pts = buffer->pts; + header->flags = 0; header->seq = state->seq++; header->dts_offset = 0; } @@ -206,19 +195,16 @@ static void copy_buffer(struct pipewire_state *state, struct pipewire_buffer *bu } struct spa_chunk *chunk = spa_buffer->datas[0].chunk; - chunk->flags = needs_reneg ? SPA_CHUNK_FLAG_CORRUPTED : 0; + chunk->flags = 0; struct wlr_dmabuf_attributes dmabuf; switch (buffer->type) { case SPA_DATA_MemFd: chunk->offset = 0; - chunk->size = state->video_info.size.height * buffer->shm.stride; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - chunk->size += ((state->video_info.size.height + 1)/2 * buffer->shm.stride); - } + chunk->size = buffer->shm.size; chunk->stride = buffer->shm.stride; - if (!needs_reneg) { + { uint8_t *pMappedData = tex->mappedData(); if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { @@ -262,10 +248,17 @@ static void copy_buffer(struct pipewire_state *state, struct pipewire_buffer *bu default: assert(false); // unreachable } + + int ret = pw_stream_queue_buffer(state->stream, pw_buffer); + if (ret < 0) { + pwr_log.errorf("pw_stream_queue_buffer failed"); + } } -static void dispatch_nudge(struct pipewire_state *state, int fd) +static void on_nudge(void *data, int fd, uint32_t mask) { + struct pipewire_state *state = (struct pipewire_state *) data; + while (true) { static char buf[1024]; if (read(fd, buf, sizeof(buf)) < 0) { @@ -292,24 +285,7 @@ static void dispatch_nudge(struct pipewire_state *state, int fd) } } - struct pipewire_buffer *buffer = in_buffer.exchange(nullptr); - if (buffer != nullptr) { - // We now completely own the buffer, it's no longer shared with the - // steamcompmgr thread. - - buffer->copying = false; - - if (buffer->buffer != nullptr) { - copy_buffer(state, buffer); - - int ret = pw_stream_queue_buffer(state->stream, buffer->buffer); - if (ret < 0) { - pwr_log.errorf("pw_stream_queue_buffer failed"); - } - } else { - destroy_buffer(buffer); - } - } + pw_stream_trigger_process(state->stream); } static void stream_handle_state_changed(void *data, enum pw_stream_state old_stream_state, enum pw_stream_state stream_state, const char *error) @@ -318,21 +294,21 @@ static void stream_handle_state_changed(void *data, enum pw_stream_state old_str pwr_log.infof("stream state changed: %s", pw_stream_state_as_string(stream_state)); + state->streaming = stream_state == PW_STREAM_STATE_STREAMING; + + if (error != nullptr) { + pwr_log.errorf("stream error: %s", error); + } + switch (stream_state) { case PW_STREAM_STATE_PAUSED: - if (state->stream_node_id == SPA_ID_INVALID) { - state->stream_node_id = pw_stream_get_node_id(state->stream); - } - state->streaming = false; + state->stream_node_id = pw_stream_get_node_id(state->stream); + pwr_log.infof("stream available on node ID: %u", state->stream_node_id); state->seq = 0; break; case PW_STREAM_STATE_STREAMING: - state->streaming = true; - break; case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: - state->running = false; - break; default: break; } @@ -371,20 +347,15 @@ static void stream_handle_param_changed(void *data, uint32_t id, const struct sp uint8_t buf[1024]; struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - int buffers = 4; - int shm_size = state->shm_stride * state->video_info.size.height; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - shm_size += ((state->video_info.size.height + 1) / 2) * state->shm_stride; - } int data_type = state->dmabuf ? (1 << SPA_DATA_DmaBuf) : (1 << SPA_DATA_MemFd); const struct spa_pod *buffers_param = (const struct spa_pod *) spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, 1, 8), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 1, 8), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(shm_size), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(state->shm_stride), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(data_type)); const struct spa_pod *meta_param = (const struct spa_pod *) spa_pod_builder_add_object(&builder, @@ -403,10 +374,10 @@ static void stream_handle_param_changed(void *data, uint32_t id, const struct sp pwr_log.errorf("pw_stream_update_params failed"); } - pwr_log.debugf("format changed (size: %dx%d, requested %dx%d, format %d, stride %d, size: %d, dmabuf: %d)", + pwr_log.debugf("format changed (size: %dx%d, requested: %dx%d, format: %d, dmabuf: %d)", state->video_info.size.width, state->video_info.size.height, s_nRequestedWidth, s_nRequestedHeight, - state->video_info.format, state->shm_stride, shm_size, state->dmabuf); + state->video_info.format, state->dmabuf); } static void randname(char *buf) @@ -440,14 +411,26 @@ static int anonymous_shm_open(void) return -1; } -uint32_t spa_format_to_drm(uint32_t spa_format) +static constexpr EStreamColorspace spa_color_to_gamescope(const struct spa_video_info_raw& video_info) { - switch (spa_format) - { - case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; - default: - case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888; + switch (video_info.color_matrix) { + case SPA_VIDEO_COLOR_MATRIX_BT601: + switch (video_info.color_range) { + case SPA_VIDEO_COLOR_RANGE_16_235: return k_EStreamColorspace_BT601; + case SPA_VIDEO_COLOR_RANGE_0_255: return k_EStreamColorspace_BT601_Full; + default: break; + } + break; + case SPA_VIDEO_COLOR_MATRIX_BT709: + switch (video_info.color_range) { + case SPA_VIDEO_COLOR_RANGE_16_235: return k_EStreamColorspace_BT709; + case SPA_VIDEO_COLOR_RANGE_0_255: return k_EStreamColorspace_BT709_Full; + default: break; + } + break; + default: break; } + return k_EStreamColorspace_Unknown; } static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffer) @@ -459,42 +442,12 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe struct pipewire_buffer *buffer = new pipewire_buffer(); buffer->buffer = pw_buffer; - buffer->video_info = state->video_info; buffer->gamescope_info = state->gamescope_info; bool is_dmabuf = (spa_data->type & (1 << SPA_DATA_DmaBuf)) != 0; bool is_memfd = (spa_data->type & (1 << SPA_DATA_MemFd)) != 0; - EStreamColorspace colorspace = k_EStreamColorspace_Unknown; - switch (state->video_info.color_matrix) { - case SPA_VIDEO_COLOR_MATRIX_BT601: - switch (state->video_info.color_range) { - case SPA_VIDEO_COLOR_RANGE_16_235: - colorspace = k_EStreamColorspace_BT601; - break; - case SPA_VIDEO_COLOR_RANGE_0_255: - colorspace = k_EStreamColorspace_BT601_Full; - break; - default: - break; - } - break; - case SPA_VIDEO_COLOR_MATRIX_BT709: - switch (state->video_info.color_range) { - case SPA_VIDEO_COLOR_RANGE_16_235: - colorspace = k_EStreamColorspace_BT709; - break; - case SPA_VIDEO_COLOR_RANGE_0_255: - colorspace = k_EStreamColorspace_BT709_Full; - break; - default: - break; - } - break; - default: - break; - } - + EStreamColorspace colorspace = spa_color_to_gamescope(state->video_info); uint32_t drmFormat = spa_format_to_drm(state->video_info.format); buffer->texture = new CVulkanTexture(); @@ -544,7 +497,7 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe goto error; } - off_t size = state->shm_stride * state->video_info.size.height; + size_t size = state->shm_stride * state->video_info.size.height; if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { size += state->shm_stride * ((state->video_info.size.height + 1) / 2); } @@ -562,6 +515,7 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe } buffer->type = SPA_DATA_MemFd; + buffer->shm.size = size; buffer->shm.stride = state->shm_stride; buffer->shm.data = (uint8_t *) data; buffer->shm.fd = fd; @@ -590,8 +544,22 @@ static void stream_handle_remove_buffer(void *data, struct pw_buffer *pw_buffer) { struct pipewire_buffer *buffer = (struct pipewire_buffer *) pw_buffer->user_data; + if (buffer == nullptr) { + return; + } + pw_buffer->user_data = nullptr; buffer->buffer = nullptr; + // We want to remove any references to this buffer + struct pipewire_buffer *other = buffer; + if (out_buffer.compare_exchange_strong(other, nullptr)) { + buffer->copying = false; + } + other = buffer; + if (in_buffer.compare_exchange_strong(other, nullptr)) { + buffer->copying = false; + } + if (!buffer->copying) { destroy_buffer(buffer); } @@ -603,105 +571,64 @@ static const struct pw_stream_events stream_events = { .param_changed = stream_handle_param_changed, .add_buffer = stream_handle_add_buffer, .remove_buffer = stream_handle_remove_buffer, - .process = nullptr, -}; - -enum pipewire_event_type { - EVENT_PIPEWIRE, - EVENT_NUDGE, - EVENT_COUNT // keep last + .process = stream_handle_process, }; -static void run_pipewire(struct pipewire_state *state) +void pipewire_exit() { - pthread_setname_np( pthread_self(), "gamescope-pw" ); - - struct pollfd pollfds[] = { - [EVENT_PIPEWIRE] = { - .fd = pw_loop_get_fd(state->loop), - .events = POLLIN, - }, - [EVENT_NUDGE] = { - .fd = nudgePipe[0], - .events = POLLIN, - }, - }; - - while (state->running) { - int ret = poll(pollfds, EVENT_COUNT, -1); - if (ret < 0) { - pwr_log.errorf_errno("poll failed"); - break; - } - - if (pollfds[EVENT_PIPEWIRE].revents & POLLHUP) { - pwr_log.errorf("lost connection to server"); - break; - } - - assert(!(pollfds[EVENT_NUDGE].revents & POLLHUP)); - - if (pollfds[EVENT_PIPEWIRE].revents & POLLIN) { - ret = pw_loop_iterate(state->loop, -1); - if (ret < 0) { - pwr_log.errorf("pw_loop_iterate failed"); - break; - } - } - - if (pollfds[EVENT_NUDGE].revents & POLLIN) { - dispatch_nudge(state, nudgePipe[0]); - } - } + struct pipewire_state *state = &pipewire_state; pwr_log.infof("exiting"); + + pw_thread_loop_stop(state->loop); + pw_loop_destroy_source(pw_thread_loop_get_loop(state->loop), state->nudge_source); + close(state->nudge_fd); pw_stream_destroy(state->stream); - pw_core_disconnect(state->core); - pw_context_destroy(state->context); - pw_loop_destroy(state->loop); + pw_thread_loop_destroy(state->loop); + pw_deinit(); } -bool init_pipewire(void) +bool pipewire_init() { struct pipewire_state *state = &pipewire_state; pw_init(nullptr, nullptr); - if (pipe2(nudgePipe, O_CLOEXEC | O_NONBLOCK) != 0) { - pwr_log.errorf_errno("pipe2 failed"); - return false; - } - - state->loop = pw_loop_new(nullptr); + state->loop = pw_thread_loop_new("gamescope-pw", nullptr); if (!state->loop) { - pwr_log.errorf("pw_loop_new failed"); + pwr_log.errorf("pw_thread_loop_new failed"); return false; } + pw_thread_loop_lock(state->loop); + pw_thread_loop_start(state->loop); + struct pw_loop *loop = pw_thread_loop_get_loop(state->loop); - state->context = pw_context_new(state->loop, nullptr, 0); - if (!state->context) { - pwr_log.errorf("pw_context_new failed"); + int nudgePipe[2]; + if (pipe2(nudgePipe, O_CLOEXEC | O_NONBLOCK) != 0) { + pwr_log.errorf_errno("pipe2 failed"); return false; } + state->nudge_fd = nudgePipe[1]; - state->core = pw_context_connect(state->context, nullptr, 0); - if (!state->core) { - pwr_log.errorf("pw_context_connect failed"); + state->nudge_source = pw_loop_add_io(loop, nudgePipe[0], SPA_IO_IN, true, on_nudge, state); + if (state->nudge_source == nullptr) { + pwr_log.errorf("pw_loop_add_io failed"); return false; } - state->stream = pw_stream_new(state->core, "gamescope", + state->stream = pw_stream_new_simple( + loop, + "gamescope", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", - nullptr)); + nullptr), + &stream_events, + state); if (!state->stream) { - pwr_log.errorf("pw_stream_new failed"); + pwr_log.errorf("pw_stream_new_simple failed"); return false; } - static struct spa_hook stream_hook; - pw_stream_add_listener(state->stream, &stream_hook, &stream_events, state); - s_nRequestedWidth = 0; s_nRequestedHeight = 0; s_nOutputWidth = g_nOutputWidth; @@ -719,55 +646,45 @@ bool init_pipewire(void) return false; } - state->running = true; - while (state->stream_node_id == SPA_ID_INVALID) { - int ret = pw_loop_iterate(state->loop, -1); - if (ret < 0) { - pwr_log.errorf("pw_loop_iterate failed"); - return false; - } - } - - pwr_log.infof("stream available on node ID: %u", state->stream_node_id); - - std::thread thread(run_pipewire, state); - thread.detach(); - + pw_thread_loop_unlock(state->loop); return true; } -uint32_t get_pipewire_stream_node_id(void) +uint32_t pipewire_get_stream_node_id() { return pipewire_state.stream_node_id; } -bool pipewire_is_streaming() +struct pipewire_buffer *pipewire_dequeue_buffer() { struct pipewire_state *state = &pipewire_state; - return state->streaming; -} -struct pipewire_buffer *dequeue_pipewire_buffer(void) -{ - struct pipewire_state *state = &pipewire_state; - if (state->streaming) { - request_buffer(state); + struct pipewire_buffer *buffer = out_buffer.exchange(nullptr); + if (buffer == nullptr && state->streaming) { + pw_thread_loop_lock(state->loop); + buffer = dequeue_buffer(state); + pw_thread_loop_unlock(state->loop); } - return out_buffer.exchange(nullptr); + return buffer; } -void push_pipewire_buffer(struct pipewire_buffer *buffer) +struct pipewire_buffer *pipewire_push_buffer(struct pipewire_buffer *buffer) { + struct pipewire_state *state = &pipewire_state; + + buffer->pts = pw_stream_get_nsec(state->stream); + struct pipewire_buffer *old = in_buffer.exchange(buffer); - if ( old != nullptr ) - { - pwr_log.errorf_errno("push_pipewire_buffer: Already had a buffer?!"); - } - nudge_pipewire(); + pipewire_nudge(); + // This will be `nullptr` if the pipewire thread is keeping up, otherwise the + // compositor should reuse this old (previous) buffer + return old; } -void nudge_pipewire(void) +void pipewire_nudge() { - if (write(nudgePipe[1], "\n", 1) < 0) - pwr_log.errorf_errno("nudge_pipewire: write failed"); + struct pipewire_state *state = &pipewire_state; + + if (write(state->nudge_fd, "\n", 1) < 0) + pwr_log.errorf_errno("nudge: write failed"); } diff --git a/src/pipewire.hpp b/src/pipewire.hpp index b4d7e296d0..df18cc2514 100644 --- a/src/pipewire.hpp +++ b/src/pipewire.hpp @@ -8,17 +8,15 @@ #include "pipewire_gamescope.hpp" struct pipewire_state { - struct pw_loop *loop; - struct pw_context *context; - struct pw_core *core; - bool running; + struct pw_thread_loop *loop; + struct spa_source *nudge_source; + int nudge_fd; struct pw_stream *stream; uint32_t stream_node_id; std::atomic streaming; struct spa_video_info_raw video_info; struct spa_gamescope gamescope_info; - uint64_t focus_appid; bool dmabuf; int shm_stride; uint64_t seq; @@ -26,17 +24,18 @@ struct pipewire_state { /** * PipeWire buffers are allocated by the PipeWire thread, and are temporarily - * shared with the steamcompmgr thread (via dequeue_pipewire_buffer and - * push_pipewire_buffer) for copying. + * shared with the steamcompmgr thread (via pipewire_dequeue_buffer and + * pipewire_push_buffer) for copying. */ struct pipewire_buffer { enum spa_data_type type; // SPA_DATA_MemFd or SPA_DATA_DmaBuf - struct spa_video_info_raw video_info; struct spa_gamescope gamescope_info; gamescope::OwningRc texture; + uint64_t pts; // Only used for SPA_DATA_MemFd struct { + size_t size; int stride; uint8_t *data; int fd; @@ -55,10 +54,10 @@ struct pipewire_buffer { bool copying; }; -bool init_pipewire(void); -uint32_t get_pipewire_stream_node_id(void); -struct pipewire_buffer *dequeue_pipewire_buffer(void); -bool pipewire_is_streaming(); +bool pipewire_init(); +void pipewire_exit(); +uint32_t pipewire_get_stream_node_id(); +struct pipewire_buffer *pipewire_dequeue_buffer(); void pipewire_destroy_buffer(struct pipewire_buffer *buffer); -void push_pipewire_buffer(struct pipewire_buffer *buffer); -void nudge_pipewire(void); +struct pipewire_buffer *pipewire_push_buffer(struct pipewire_buffer *buffer); +void pipewire_nudge(); diff --git a/src/pipewire_gamescope.hpp b/src/pipewire_gamescope.hpp index ee731b47ff..6f16e18452 100644 --- a/src/pipewire_gamescope.hpp +++ b/src/pipewire_gamescope.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include enum { SPA_FORMAT_VIDEO_requested_size = 0x70000, @@ -21,24 +21,22 @@ struct spa_gamescope static inline int spa_format_video_raw_parse_with_gamescope(const struct spa_pod *format, struct spa_video_info_raw *info, spa_gamescope *gamescope_info) { + int ret = spa_format_video_raw_parse(format, info); + if (ret < 0) { + return ret; + } return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(&info->format), - SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), - SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), - SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&info->views), - SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_OPT_Id(&info->interlace_mode), - SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_POD_OPT_Fraction(&info->pixel_aspect_ratio), - SPA_FORMAT_VIDEO_multiviewMode, SPA_POD_OPT_Id(&info->multiview_mode), - SPA_FORMAT_VIDEO_multiviewFlags, SPA_POD_OPT_Id(&info->multiview_flags), - SPA_FORMAT_VIDEO_chromaSite, SPA_POD_OPT_Id(&info->chroma_site), - SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&info->color_range), - SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&info->color_matrix), - SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&info->transfer_function), - SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&info->color_primaries), SPA_FORMAT_VIDEO_requested_size, SPA_POD_OPT_Rectangle(&gamescope_info->requested_size), SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_OPT_Long(&gamescope_info->focus_appid)); } +static inline uint32_t spa_format_to_drm(const enum spa_video_format spa_format) +{ + switch (spa_format) + { + case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888; + case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; + default: return DRM_FORMAT_INVALID; + } +} diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index df013147e4..c58a577ac9 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -2009,7 +2009,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin m_drmFormat = drmFormat; VkResult res = VK_ERROR_INITIALIZATION_FAILED; - VkImageTiling tiling = (flags.bMappable || flags.bLinear) ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; + VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; VkImageUsageFlags usage = 0; VkMemoryPropertyFlags properties; @@ -2045,6 +2045,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin if ( flags.bMappable == true ) { + flags.bLinear = true; properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } else @@ -2052,13 +2053,16 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } + if ( flags.bLinear == true ) + { + tiling = VK_IMAGE_TILING_LINEAR; + } + if ( flags.bOutputImage == true ) { m_bOutputImage = true; } - m_bExternal = pDMA || flags.bExportable == true; - // Possible extensions for below wsi_image_create_info wsiImageCreateInfo = {}; VkExternalMemoryImageCreateInfo externalImageCreateInfo = {}; @@ -2193,12 +2197,6 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin .pDrmFormatModifiers = modifiers.data(), }; - externalImageCreateInfo = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, - .pNext = std::exchange(imageInfo.pNext, &externalImageCreateInfo), - .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; - imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; } @@ -2212,13 +2210,15 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin }; } - if ( pDMA != nullptr ) + if ( flags.bExportable == true || pDMA != nullptr ) { externalImageCreateInfo = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .pNext = std::exchange(imageInfo.pNext, &externalImageCreateInfo), .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, }; + + m_bExternal = true; } m_width = width; @@ -3593,7 +3593,7 @@ void vulkan_garbage_collect( void ) g_device.garbageCollect(); } -gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace) +gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, uint32_t drmFormat, EStreamColorspace colorspace) { for (auto& pScreenshotImage : g_output.pScreenshotImages) { @@ -3605,10 +3605,6 @@ gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, screenshotImageFlags.bMappable = true; screenshotImageFlags.bTransferDst = true; screenshotImageFlags.bStorage = true; - if (exportable || drmFormat == DRM_FORMAT_NV12) { - screenshotImageFlags.bExportable = true; - screenshotImageFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire - } bool bSuccess = pScreenshotImage->BInit( width, height, 1u, drmFormat, screenshotImageFlags ); pScreenshotImage->setStreamColorspace(colorspace); diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 63cc6029ac..3f7d3b3088 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -411,7 +411,7 @@ gamescope::OwningRc vulkan_create_texture_from_wlr_buffer( struc std::optional vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc pScreenshotTexture, bool partial, gamescope::Rc pOutputOverride = nullptr, bool increment = true, std::unique_ptr pInCommandBuffer = nullptr ); void vulkan_wait( uint64_t ulSeqNo, bool bReset ); gamescope::Rc vulkan_get_last_output_image( bool partial, bool defer ); -gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); +gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); void vulkan_present_to_window( void ); diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 7ccf4a32da..3a23cc2491 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -2307,7 +2307,7 @@ static void paint_pipewire() // Queue up a buffer with some metadata. if ( !s_pPipewireBuffer ) - s_pPipewireBuffer = dequeue_pipewire_buffer(); + s_pPipewireBuffer = pipewire_dequeue_buffer(); if ( !s_pPipewireBuffer || !s_pPipewireBuffer->texture ) return; @@ -2393,7 +2393,7 @@ static void paint_pipewire() paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr() - ? vulkan_acquire_screenshot_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 ) + ? vulkan_acquire_screenshot_texture( uWidth, uHeight, DRM_FORMAT_XRGB2101010 ) : gamescope::Rc{ s_pPipewireBuffer->texture }; gamescope::Rc pYUVTexture = s_pPipewireBuffer->texture->isYcbcr() ? s_pPipewireBuffer->texture : nullptr; @@ -2412,8 +2412,7 @@ static void paint_pipewire() { vulkan_wait( *oPipewireSequence, true ); - push_pipewire_buffer( s_pPipewireBuffer ); - s_pPipewireBuffer = nullptr; + s_pPipewireBuffer = pipewire_push_buffer( s_pPipewireBuffer ); } } #endif @@ -2836,7 +2835,7 @@ paint_all( global_focus_t *pFocus, bool async ) gamescope::Rc pScreenshotTexture; if ( drmCaptureFormat != DRM_FORMAT_INVALID ) - pScreenshotTexture = vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat ); + pScreenshotTexture = vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, drmCaptureFormat ); if ( pScreenshotTexture ) { @@ -6538,6 +6537,10 @@ steamcompmgr_exit(void) wlserver_lock(); wlserver_shutdown(); wlserver_unlock(false); + +#if HAVE_PIPEWIRE + pipewire_exit(); +#endif } [[noreturn]] static int @@ -8572,7 +8575,7 @@ steamcompmgr_main(int argc, char **argv) currentHDRForce = g_bForceHDRSupportDebug; #if HAVE_PIPEWIRE - nudge_pipewire(); + pipewire_nudge(); #endif } @@ -8946,8 +8949,7 @@ steamcompmgr_main(int argc, char **argv) GetVBlankTimer().ArmNextVBlank( true ); #if HAVE_PIPEWIRE - if ( pipewire_is_streaming() ) - paint_pipewire(); + paint_pipewire(); #endif } diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 1631ca780d..ffe15c7ecb 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -1054,7 +1054,7 @@ static void gamescope_pipewire_bind( struct wl_client *client, void *data, uint3 struct wl_resource *resource = wl_resource_create( client, &gamescope_pipewire_interface, version, id ); wl_resource_set_implementation( resource, &gamescope_pipewire_impl, NULL, NULL ); - gamescope_pipewire_send_stream_node( resource, get_pipewire_stream_node_id() ); + gamescope_pipewire_send_stream_node( resource, pipewire_get_stream_node_id() ); } static void create_gamescope_pipewire( void )