From 7a3e5c938016e7fe3ecefbc4519bf81efdae82eb Mon Sep 17 00:00:00 2001 From: Koji AGAWA Date: Tue, 15 Jul 2025 15:30:49 +0900 Subject: [PATCH] feat(pipewire): add support for source texture output in PipeWire --- src/main.cpp | 23 +++++++++++++++++++++++ src/main.hpp | 9 +++++++++ src/pipewire.cpp | 12 +++++++++++- src/steamcompmgr.cpp | 40 ++++++++++++++++++++++++++++------------ 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cdb35c3b25..ea969598b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -150,6 +150,10 @@ const struct option *gamescope_options = (struct option[]){ // Steam Deck options { "mura-map", required_argument, nullptr, 0 }, + // PipeWire options + { "pipewire-source", required_argument, nullptr, 0 }, + { "pipewire-width", required_argument, nullptr, 0 }, + { "pipewire-height", required_argument, nullptr, 0 }, {} // keep last }; @@ -209,6 +213,9 @@ const char usage[] = " --framerate-limit Set a simple framerate limit. Used as a divisor of the refresh rate, rounds down eg 60 / 59 -> 60fps, 60 / 25 -> 30fps. Default: 0, disabled.\n" " --mangoapp Launch with the mangoapp (mangohud) performance overlay enabled. You should use this instead of using mangohud on the game or gamescope.\n" " --adaptive-sync Enable adaptive sync if available (variable rate refresh)\n" + " --pipewire-source Select PipeWire source: 'output' (default) or 'source' (unscaled source texture).\n" + " --pipewire-width Fixed width for PipeWire capture (overrides output width).\n" + " --pipewire-height Fixed height for PipeWire capture (overrides output height).\n" "\n" "Nested mode options:\n" " -o, --nested-unfocused-refresh game refresh rate when unfocused\n" @@ -680,6 +687,9 @@ static void UpdateCompatEnvVars() int g_nPreferredOutputWidth = 0; int g_nPreferredOutputHeight = 0; bool g_bExposeWayland = false; +PipeWireSourceMode g_ePipewireSourceMode = PipeWireSourceMode::Output; +int g_nPipewireWidth = 0; +int g_nPipewireHeight = 0; const char *g_sOutputName = nullptr; bool g_bDebugLayers = false; bool g_bForceDisableColorMgmt = false; @@ -821,6 +831,19 @@ int main(int argc, char **argv) } } + } else if (strcmp(opt_name, "pipewire-source") == 0) { + if (strcmp(optarg, "source") == 0) { + g_ePipewireSourceMode = PipeWireSourceMode::Source; + } else if (strcmp(optarg, "output") == 0) { + g_ePipewireSourceMode = PipeWireSourceMode::Output; + } else { + fprintf( stderr, "gamescope: invalid pipewire source mode: %s\n", optarg ); + return 1; + } + } else if (strcmp(opt_name, "pipewire-width") == 0) { + g_nPipewireWidth = atoi(optarg); + } else if (strcmp(opt_name, "pipewire-height") == 0) { + g_nPipewireHeight = atoi(optarg); } break; case '?': diff --git a/src/main.hpp b/src/main.hpp index 2e6fb833af..05452682f6 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -73,3 +73,12 @@ extern int g_nXWaylandCount; extern uint32_t g_preferVendorID; extern uint32_t g_preferDeviceID; +enum class PipeWireSourceMode { + Output, + Source +}; + +extern PipeWireSourceMode g_ePipewireSourceMode; +extern int g_nPipewireWidth; +extern int g_nPipewireHeight; + diff --git a/src/pipewire.cpp b/src/pipewire.cpp index 1f93cb8a55..b1eb53ec15 100644 --- a/src/pipewire.cpp +++ b/src/pipewire.cpp @@ -52,7 +52,7 @@ static void destroy_buffer(struct pipewire_buffer *buffer) { break; // nothing to do default: assert(false); // unreachable - } + } // If out_buffer == buffer, then set it to nullptr. // We don't care about the result. @@ -71,6 +71,12 @@ void pipewire_destroy_buffer(struct pipewire_buffer *buffer) static void calculate_capture_size() { + if (g_nPipewireWidth > 0 && g_nPipewireHeight > 0) { + s_nCaptureWidth = g_nPipewireWidth; + s_nCaptureHeight = g_nPipewireHeight; + return; + } + s_nCaptureWidth = s_nOutputWidth; s_nCaptureHeight = s_nOutputHeight; @@ -280,6 +286,10 @@ static void dispatch_nudge(struct pipewire_state *state, int fd) s_nOutputHeight = g_nOutputHeight; calculate_capture_size(); } + + if (g_nPipewireWidth > 0 && g_nPipewireHeight > 0) { + calculate_capture_size(); + } if (s_nCaptureWidth != state->video_info.size.width || s_nCaptureHeight != state->video_info.size.height) { pwr_log.debugf("renegotiating stream params (size: %dx%d)", s_nCaptureWidth, s_nCaptureHeight); diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index b4ff430b8d..2a52f2c556 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -2023,6 +2023,7 @@ namespace PaintWindowFlag static const uint32_t NoScale = 1u << 4; static const uint32_t NoFilter = 1u << 5; static const uint32_t CoverageMode = 1u << 6; + static const uint32_t PipeWire = 1u << 7; } using PaintWindowFlags = uint32_t; @@ -2073,12 +2074,22 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win layer->filter = ( flags & PaintWindowFlag::NoFilter ) ? GamescopeUpscaleFilter::LINEAR : g_upscaleFilter; - layer->tex = lastCommit->GetTexture( layer->filter, g_upscaleScaler, layer->colorspace ); + // Use source texture for PipeWire when source mode is enabled + bool bUseSourceTexture = ( flags & PaintWindowFlag::PipeWire ) && ( g_ePipewireSourceMode == PipeWireSourceMode::Source ); + layer->tex = bUseSourceTexture ? lastCommit->vulkanTex : lastCommit->GetTexture( layer->filter, g_upscaleScaler, layer->colorspace ); if ( flags & PaintWindowFlag::NoScale ) { - sourceWidth = currentOutputWidth; - sourceHeight = currentOutputHeight; + if ( flags & PaintWindowFlag::PipeWire && g_ePipewireSourceMode == PipeWireSourceMode::Source ) + { + sourceWidth = layer->tex->width(); + sourceHeight = layer->tex->height(); + } + else + { + sourceWidth = currentOutputWidth; + sourceHeight = currentOutputHeight; + } } else { @@ -2108,22 +2119,27 @@ paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win if ( fit ) { // If we have an override window, try to fit it in as long as it won't make our scale go below 1.0. - sourceWidth = std::max( sourceWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, currentOutputWidth ) ); - sourceHeight = std::max( sourceHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, currentOutputHeight ) ); + uint32_t outputWidth = (flags & PaintWindowFlag::PipeWire && g_nPipewireWidth > 0) ? g_nPipewireWidth : currentOutputWidth; + uint32_t outputHeight = (flags & PaintWindowFlag::PipeWire && g_nPipewireHeight > 0) ? g_nPipewireHeight : currentOutputHeight; + sourceWidth = std::max( sourceWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, outputWidth ) ); + sourceHeight = std::max( sourceHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, outputHeight ) ); - baseWidth = std::max( baseWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, currentOutputWidth ) ); - baseHeight = std::max( baseHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, currentOutputHeight ) ); + baseWidth = std::max( baseWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, outputWidth ) ); + baseHeight = std::max( baseHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, outputHeight ) ); } } bool offset = ( ( w->GetGeometry().nX || w->GetGeometry().nY ) && w != scaleW ); - if (sourceWidth != (int32_t)currentOutputWidth || sourceHeight != (int32_t)currentOutputHeight || offset || globalScaleRatio != 1.0f) + uint32_t targetOutputWidth = (flags & PaintWindowFlag::PipeWire && g_nPipewireWidth > 0) ? g_nPipewireWidth : currentOutputWidth; + uint32_t targetOutputHeight = (flags & PaintWindowFlag::PipeWire && g_nPipewireHeight > 0) ? g_nPipewireHeight : currentOutputHeight; + + if (sourceWidth != (int32_t)targetOutputWidth || sourceHeight != (int32_t)targetOutputHeight || offset || globalScaleRatio != 1.0f) { calc_scale_factor(currentScaleRatio_x, currentScaleRatio_y, sourceWidth, sourceHeight); - drawXOffset = ((int)currentOutputWidth - (int)sourceWidth * currentScaleRatio_x) / 2.0f; - drawYOffset = ((int)currentOutputHeight - (int)sourceHeight * currentScaleRatio_y) / 2.0f; + drawXOffset = ((int)targetOutputWidth - (int)sourceWidth * currentScaleRatio_x) / 2.0f; + drawYOffset = ((int)targetOutputHeight - (int)sourceHeight * currentScaleRatio_y) / 2.0f; if ( w != scaleW ) { @@ -2360,10 +2376,10 @@ static void paint_pipewire() currentOutputHeight = uHeight; // Paint the windows we have onto the Pipewire stream. - paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow ); + paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::PipeWire, 1.0f, pFocus->overrideWindow ); if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient ) - paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); + paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter | PaintWindowFlag::PipeWire, 1.0f, pFocus->overrideWindow ); gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr() ? vulkan_acquire_screenshot_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 )