diff --git a/app/cli/commandlineparser.cpp b/app/cli/commandlineparser.cpp index 0e50a75ba..49cc85f7b 100644 --- a/app/cli/commandlineparser.cpp +++ b/app/cli/commandlineparser.cpp @@ -353,6 +353,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference parser.addFlagOption("1440", "2560x1440 resolution"); parser.addFlagOption("4K", "3840x2160 resolution"); parser.addValueOption("resolution", "custom x resolution"); + parser.addToggleOption("ignore-aspect-ratio", "ignore aspect ratio"); parser.addToggleOption("vsync", "V-Sync"); parser.addValueOption("fps", "FPS"); parser.addValueOption("bitrate", "bitrate in Kbps"); @@ -442,6 +443,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference preferences->windowMode = mapValue(m_WindowModeMap, parser.getChoiceOptionValue("display-mode")); } + // Resolve --ignore-aspect-ratio + preferences->ignoreAspectRatio = parser.getToggleOptionValue("ignore-aspect-ratio", preferences->ignoreAspectRatio); + // Resolve --vsync and --no-vsync options preferences->enableVsync = parser.getToggleOptionValue("vsync", preferences->enableVsync); diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index 30b9ed787..38974da6f 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -787,6 +787,23 @@ Flickable { ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.") } + CheckBox { + id: ignoreAspectRatioCheck + width: parent.width + hoverEnabled: true + text: qsTr("Stretch presentation") + font.pointSize: 12 + checked: StreamingPreferences.ignoreAspectRatio + onCheckedChanged: { + StreamingPreferences.ignoreAspectRatio = checked + } + + ToolTip.delay: 1000 + ToolTip.timeout: 12000 + ToolTip.visible: hovered + ToolTip.text: qsTr("Ignores both client and host PC aspect ratios, which is required for displaying Half-SBS (Side-By-Side) 3D signals to AR/XR devices that only support Full-SBS (usually 1920x1080 per eye, meaning a total resolution of 3840x1080)") + } + CheckBox { id: vsyncCheck width: parent.width diff --git a/app/settings/streamingpreferences.cpp b/app/settings/streamingpreferences.cpp index bb34f6269..044b2772d 100644 --- a/app/settings/streamingpreferences.cpp +++ b/app/settings/streamingpreferences.cpp @@ -17,6 +17,7 @@ #define SER_BITRATE "bitrate" #define SER_UNLOCK_BITRATE "unlockbitrate" #define SER_FULLSCREEN "fullscreen" +#define SER_IGNORE_ASPECT_RATIO "ignoreaspectratio" #define SER_VSYNC "vsync" #define SER_GAMEOPTS "gameopts" #define SER_HOSTAUDIO "hostaudio" @@ -122,6 +123,7 @@ void StreamingPreferences::reload() enableYUV444 = settings.value(SER_YUV444, false).toBool(); bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps, enableYUV444)).toInt(); unlockBitrate = settings.value(SER_UNLOCK_BITRATE, false).toBool(); + ignoreAspectRatio = settings.value(SER_IGNORE_ASPECT_RATIO, true).toBool(); enableVsync = settings.value(SER_VSYNC, true).toBool(); gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool(); playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool(); @@ -312,6 +314,7 @@ void StreamingPreferences::save() settings.setValue(SER_FPS, fps); settings.setValue(SER_BITRATE, bitrateKbps); settings.setValue(SER_UNLOCK_BITRATE, unlockBitrate); + settings.setValue(SER_IGNORE_ASPECT_RATIO, ignoreAspectRatio); settings.setValue(SER_VSYNC, enableVsync); settings.setValue(SER_GAMEOPTS, gameOptimizations); settings.setValue(SER_HOSTAUDIO, playAudioOnHost); diff --git a/app/settings/streamingpreferences.h b/app/settings/streamingpreferences.h index 3ca216fff..0c7a1e191 100644 --- a/app/settings/streamingpreferences.h +++ b/app/settings/streamingpreferences.h @@ -110,6 +110,7 @@ class StreamingPreferences : public QObject Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged) Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged) Q_PROPERTY(bool unlockBitrate MEMBER unlockBitrate NOTIFY unlockBitrateChanged) + Q_PROPERTY(bool ignoreAspectRatio MEMBER ignoreAspectRatio NOTIFY ignoreAspectRatioChanged) Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged) Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged) Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged) @@ -149,6 +150,7 @@ class StreamingPreferences : public QObject int fps; int bitrateKbps; bool unlockBitrate; + bool ignoreAspectRatio; bool enableVsync; bool gameOptimizations; bool playAudioOnHost; @@ -185,6 +187,7 @@ class StreamingPreferences : public QObject void displayModeChanged(); void bitrateChanged(); void unlockBitrateChanged(); + void ignoreAspectRatioChanged(); void enableVsyncChanged(); void gameOptimizationsChanged(); void playAudioOnHostChanged(); diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 6da51f16d..b490b870c 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -261,7 +261,8 @@ void Session::clSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, SDL_Window* window, int videoFormat, int width, int height, - int frameRate, bool enableVsync, bool enableFramePacing, bool testOnly, IVideoDecoder*& chosenDecoder) + int frameRate, bool enableVsync, bool enableFramePacing, + bool ignoreAspectRatio, bool testOnly, IVideoDecoder*& chosenDecoder) { DECODER_PARAMETERS params; @@ -277,6 +278,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, params.window = window; params.enableVsync = enableVsync; params.enableFramePacing = enableFramePacing; + params.ignoreAspectRatio = ignoreAspectRatio; params.testOnly = testOnly; params.vds = vds; @@ -381,7 +383,7 @@ void Session::getDecoderInfo(SDL_Window* window, // Try an HEVC Main10 decoder first to see if we have HDR support if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE, window, VIDEO_FORMAT_H265_MAIN10, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { isHardwareAccelerated = decoder->isHardwareAccelerated(); isFullScreenOnly = decoder->isAlwaysFullScreen(); isHdrSupported = decoder->isHdrSupported(); @@ -394,7 +396,7 @@ void Session::getDecoderInfo(SDL_Window* window, // Try an AV1 Main10 decoder next to see if we have HDR support if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE, window, VIDEO_FORMAT_AV1_MAIN10, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { // If we've got a working AV1 Main 10-bit decoder, we'll enable the HDR checkbox // but we will still continue probing to get other attributes for HEVC or H.264 // decoders. See the AV1 comment at the top of the function for more info. @@ -406,10 +408,10 @@ void Session::getDecoderInfo(SDL_Window* window, // that supports HDR rendering with software decoded frames. if (chooseDecoder(StreamingPreferences::VDS_FORCE_SOFTWARE, window, VIDEO_FORMAT_H265_MAIN10, 1920, 1080, 60, - false, false, true, decoder) || + false, false, false, true, decoder) || chooseDecoder(StreamingPreferences::VDS_FORCE_SOFTWARE, window, VIDEO_FORMAT_AV1_MAIN10, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { isHdrSupported = decoder->isHdrSupported(); delete decoder; } @@ -423,7 +425,7 @@ void Session::getDecoderInfo(SDL_Window* window, // Try a regular hardware accelerated HEVC decoder now if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE, window, VIDEO_FORMAT_H265, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { isHardwareAccelerated = decoder->isHardwareAccelerated(); isFullScreenOnly = decoder->isAlwaysFullScreen(); maxResolution = decoder->getDecoderMaxResolution(); @@ -436,7 +438,7 @@ void Session::getDecoderInfo(SDL_Window* window, #if 0 // See AV1 comment at the top of this function if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE, window, VIDEO_FORMAT_AV1_MAIN8, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { isHardwareAccelerated = decoder->isHardwareAccelerated(); isFullScreenOnly = decoder->isAlwaysFullScreen(); maxResolution = decoder->getDecoderMaxResolution(); @@ -450,7 +452,7 @@ void Session::getDecoderInfo(SDL_Window* window, // This will fall back to software decoding, so it should always work. if (chooseDecoder(StreamingPreferences::VDS_AUTO, window, VIDEO_FORMAT_H264, 1920, 1080, 60, - false, false, true, decoder)) { + false, false, false, true, decoder)) { isHardwareAccelerated = decoder->isHardwareAccelerated(); isFullScreenOnly = decoder->isAlwaysFullScreen(); maxResolution = decoder->getDecoderMaxResolution(); @@ -470,7 +472,7 @@ Session::getDecoderAvailability(SDL_Window* window, { IVideoDecoder* decoder; - if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, false, false, true, decoder)) { + if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, false, false, false, true, decoder)) { return DecoderAvailability::None; } @@ -491,7 +493,7 @@ bool Session::populateDecoderProperties(SDL_Window* window) m_StreamConfig.width, m_StreamConfig.height, m_StreamConfig.fps, - false, false, true, decoder)) { + false, false, false, true, decoder)) { return false; } @@ -2219,6 +2221,7 @@ void Session::execInternal() m_ActiveVideoHeight, m_ActiveVideoFrameRate, enableVsync, enableVsync && m_Preferences->framePacing, + m_Preferences->ignoreAspectRatio, false, s_ActiveSession->m_VideoDecoder)) { SDL_AtomicUnlock(&m_DecoderLock); diff --git a/app/streaming/session.h b/app/streaming/session.h index 95858437e..d8e12753a 100644 --- a/app/streaming/session.h +++ b/app/streaming/session.h @@ -187,7 +187,7 @@ class Session : public QObject bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, SDL_Window* window, int videoFormat, int width, int height, int frameRate, bool enableVsync, bool enableFramePacing, - bool testOnly, + bool ignoreAspectRatio, bool testOnly, IVideoDecoder*& chosenDecoder); static diff --git a/app/streaming/video/decoder.h b/app/streaming/video/decoder.h index c77928237..c715cc516 100644 --- a/app/streaming/video/decoder.h +++ b/app/streaming/video/decoder.h @@ -42,6 +42,7 @@ typedef struct _DECODER_PARAMETERS { int frameRate; bool enableVsync; bool enableFramePacing; + bool ignoreAspectRatio; bool testOnly; } DECODER_PARAMETERS, *PDECODER_PARAMETERS; diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp index 201eb7acc..c127368aa 100644 --- a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp +++ b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp @@ -1401,7 +1401,10 @@ bool D3D11VARenderer::setupRenderingResources() dst.x = dst.y = 0; dst.w = m_DisplayWidth; dst.h = m_DisplayHeight; - StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + + if (!m_DecoderParams.ignoreAspectRatio) { + StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + } // Convert screen space to normalized device coordinates SDL_FRect renderRect; diff --git a/app/streaming/video/ffmpeg-renderers/plvk.cpp b/app/streaming/video/ffmpeg-renderers/plvk.cpp index 4546be7c5..3359f8c58 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.cpp +++ b/app/streaming/video/ffmpeg-renderers/plvk.cpp @@ -418,6 +418,9 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params) return false; } + // Ignores aspect ratio to fill the entire screen + m_IgnoreAspectRatio = params->ignoreAspectRatio; + VkPresentModeKHR presentMode; if (params->enableVsync) { // FIFO mode improves frame pacing compared with Mailbox, especially for @@ -841,7 +844,9 @@ void PlVkRenderer::renderFrame(AVFrame *frame) dst.h = targetFrame.crop.y1 - targetFrame.crop.y0; // Scale the video to the surface size while preserving the aspect ratio - StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + if (!m_IgnoreAspectRatio) { + StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + } targetFrame.crop.x0 = dst.x; targetFrame.crop.y0 = dst.y; diff --git a/app/streaming/video/ffmpeg-renderers/plvk.h b/app/streaming/video/ffmpeg-renderers/plvk.h index d5214c200..c1367eb08 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.h +++ b/app/streaming/video/ffmpeg-renderers/plvk.h @@ -66,6 +66,8 @@ class PlVkRenderer : public IFFmpegRenderer { // Pending swapchain state shared between waitToRender(), renderFrame(), and cleanupRenderContext() pl_swapchain_frame m_SwapchainFrame = {}; bool m_HasPendingSwapchainFrame = false; + + bool m_IgnoreAspectRatio = false; // Overlay state SDL_SpinLock m_OverlayLock = 0; diff --git a/app/streaming/video/ffmpeg-renderers/vt_metal.mm b/app/streaming/video/ffmpeg-renderers/vt_metal.mm index 61f15a7f9..08ec26b7b 100644 --- a/app/streaming/video/ffmpeg-renderers/vt_metal.mm +++ b/app/streaming/video/ffmpeg-renderers/vt_metal.mm @@ -137,7 +137,8 @@ m_LastDrawableHeight(-1), m_PresentationMutex(SDL_CreateMutex()), m_PresentationCond(SDL_CreateCond()), - m_PendingPresentationCount(0) + m_PendingPresentationCount(0), + m_IgnoreAspectRatio(false) { } @@ -260,7 +261,10 @@ bool updateVideoRegionSizeForFrame(AVFrame* frame) dst.x = dst.y = 0; dst.w = drawableWidth; dst.h = drawableHeight; - StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + + if (!m_IgnoreAspectRatio) { + StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + } // Convert screen space to normalized device coordinates SDL_FRect renderRect; @@ -750,6 +754,9 @@ virtual bool initialize(PDECODER_PARAMETERS params) override // Allow tearing if V-Sync is off (also requires direct display path) m_MetalLayer.displaySyncEnabled = params->enableVsync; + // Ignores aspect ratio to fill the entire screen + m_IgnoreAspectRatio = params->ignoreAspectRatio; + // Create the Metal texture cache for our CVPixelBuffers CFStringRef keys[1] = { kCVMetalTextureUsage }; NSUInteger values[1] = { MTLTextureUsageShaderRead }; @@ -939,6 +946,7 @@ bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) override SDL_mutex* m_PresentationMutex; SDL_cond* m_PresentationCond; int m_PendingPresentationCount; + bool m_IgnoreAspectRatio; }; IFFmpegRenderer* VTMetalRendererFactory::createRenderer(bool hwAccel) {