From 3af1744c9b7a6ada830c7f80389f3f81f7ea9a22 Mon Sep 17 00:00:00 2001 From: Brad Richardson Date: Mon, 9 Jan 2023 08:24:21 -0500 Subject: [PATCH 01/51] FFmpeg updates with QSV-enabled builds (#730) --- .github/workflows/CI.yml | 1 + CMakeLists.txt | 2 +- docs/source/building/windows.rst | 3 ++- third-party/ffmpeg-linux-aarch64 | 2 +- third-party/ffmpeg-linux-x86_64 | 2 +- third-party/ffmpeg-macos-aarch64 | 2 +- third-party/ffmpeg-macos-x86_64 | 2 +- third-party/ffmpeg-windows-x86_64 | 2 +- 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 970191bc..5cff4180 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -886,6 +886,7 @@ jobs: mingw-w64-x86_64-boost mingw-w64-x86_64-cmake mingw-w64-x86_64-curl + mingw-w64-x86_64-libmfx mingw-w64-x86_64-nsis mingw-w64-x86_64-openssl mingw-w64-x86_64-opus diff --git a/CMakeLists.txt b/CMakeLists.txt index b1970b1d..2305eab8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,7 +388,7 @@ set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) # Pre-compiled binaries if(WIN32) set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-windows-x86_64") - set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid) + set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid mfx) elseif(APPLE) if (CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-macos-aarch64") diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 82b0dfe6..0cfb53a4 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -16,7 +16,8 @@ Install dependencies: pacman -S base-devel cmake diffutils gcc git make mingw-w64-x86_64-binutils \ mingw-w64-x86_64-boost mingw-w64-x86_64-cmake mingw-w64-x86_64-curl \ - mingw-w64-x86_64-openssl mingw-w64-x86_64-opus mingw-w64-x86_64-toolchain + mingw-w64-x86_64-libmfx mingw-w64-x86_64-openssl mingw-w64-x86_64-opus \ + mingw-w64-x86_64-toolchain npm dependencies ---------------- diff --git a/third-party/ffmpeg-linux-aarch64 b/third-party/ffmpeg-linux-aarch64 index 0341a8fe..1f42e213 160000 --- a/third-party/ffmpeg-linux-aarch64 +++ b/third-party/ffmpeg-linux-aarch64 @@ -1 +1 @@ -Subproject commit 0341a8fe5a90cd3ea297de3af479dae336370993 +Subproject commit 1f42e213d3b560448379bcb17caf74f517e9b73d diff --git a/third-party/ffmpeg-linux-x86_64 b/third-party/ffmpeg-linux-x86_64 index 999e6746..08a8ae61 160000 --- a/third-party/ffmpeg-linux-x86_64 +++ b/third-party/ffmpeg-linux-x86_64 @@ -1 +1 @@ -Subproject commit 999e6746bdf3f4ce2e5eda2e6f8d6ea92cb5372a +Subproject commit 08a8ae6100814e7cbe51eec01c9c9274411730ae diff --git a/third-party/ffmpeg-macos-aarch64 b/third-party/ffmpeg-macos-aarch64 index 3507f2d5..17142b97 160000 --- a/third-party/ffmpeg-macos-aarch64 +++ b/third-party/ffmpeg-macos-aarch64 @@ -1 +1 @@ -Subproject commit 3507f2d5793a1809e522e7fcf98bad5758653f43 +Subproject commit 17142b9797cde8f29d08cafa6cec9e3923ce1e05 diff --git a/third-party/ffmpeg-macos-x86_64 b/third-party/ffmpeg-macos-x86_64 index 01421e5a..924d4cde 160000 --- a/third-party/ffmpeg-macos-x86_64 +++ b/third-party/ffmpeg-macos-x86_64 @@ -1 +1 @@ -Subproject commit 01421e5a1435d0428d335ea1c19a9e0720180cbe +Subproject commit 924d4cde9b88aa53aff3b28c671cd8e390a95b0d diff --git a/third-party/ffmpeg-windows-x86_64 b/third-party/ffmpeg-windows-x86_64 index e0ba0df1..daf81fde 160000 --- a/third-party/ffmpeg-windows-x86_64 +++ b/third-party/ffmpeg-windows-x86_64 @@ -1 +1 @@ -Subproject commit e0ba0df13687bd6355564bfd0e1dc690856abde3 +Subproject commit daf81fde766f8d0fa05a1c2f072d5d494c3a5ffa From 6ff236727f2db102abf9911188907dd811680aab Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:07:27 -0500 Subject: [PATCH 02/51] Bump ffmpeg (#693) Co-authored-by: LizardByte-bot <108553330+RetroArcher-bot@users.noreply.github.com> --- third-party/ffmpeg-linux-aarch64 | 2 +- third-party/ffmpeg-linux-x86_64 | 2 +- third-party/ffmpeg-macos-aarch64 | 2 +- third-party/ffmpeg-macos-x86_64 | 2 +- third-party/ffmpeg-windows-x86_64 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/third-party/ffmpeg-linux-aarch64 b/third-party/ffmpeg-linux-aarch64 index 1f42e213..0038ca37 160000 --- a/third-party/ffmpeg-linux-aarch64 +++ b/third-party/ffmpeg-linux-aarch64 @@ -1 +1 @@ -Subproject commit 1f42e213d3b560448379bcb17caf74f517e9b73d +Subproject commit 0038ca370852c3dcedf4400ab9760bd9a791ed53 diff --git a/third-party/ffmpeg-linux-x86_64 b/third-party/ffmpeg-linux-x86_64 index 08a8ae61..7ab578f9 160000 --- a/third-party/ffmpeg-linux-x86_64 +++ b/third-party/ffmpeg-linux-x86_64 @@ -1 +1 @@ -Subproject commit 08a8ae6100814e7cbe51eec01c9c9274411730ae +Subproject commit 7ab578f9808e71eedc7a99d0515960b716d65eb7 diff --git a/third-party/ffmpeg-macos-aarch64 b/third-party/ffmpeg-macos-aarch64 index 17142b97..5962a242 160000 --- a/third-party/ffmpeg-macos-aarch64 +++ b/third-party/ffmpeg-macos-aarch64 @@ -1 +1 @@ -Subproject commit 17142b9797cde8f29d08cafa6cec9e3923ce1e05 +Subproject commit 5962a2422268739e106ddecb2d6c1d7ef7440192 diff --git a/third-party/ffmpeg-macos-x86_64 b/third-party/ffmpeg-macos-x86_64 index 924d4cde..e227b640 160000 --- a/third-party/ffmpeg-macos-x86_64 +++ b/third-party/ffmpeg-macos-x86_64 @@ -1 +1 @@ -Subproject commit 924d4cde9b88aa53aff3b28c671cd8e390a95b0d +Subproject commit e227b64019c23e644b66209f2716b5e96a7bd36a diff --git a/third-party/ffmpeg-windows-x86_64 b/third-party/ffmpeg-windows-x86_64 index daf81fde..4643082e 160000 --- a/third-party/ffmpeg-windows-x86_64 +++ b/third-party/ffmpeg-windows-x86_64 @@ -1 +1 @@ -Subproject commit daf81fde766f8d0fa05a1c2f072d5d494c3a5ffa +Subproject commit 4643082e9b3e6ee8cfe11289e89457ba1deaf168 From f210b89dd77e2b77f36ed10752962333549b0780 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:53:39 -0500 Subject: [PATCH 03/51] Bump third-party/nv-codec-headers from `b550d40` to `2055784` (#704) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/nv-codec-headers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/nv-codec-headers b/third-party/nv-codec-headers index b550d404..2055784e 160000 --- a/third-party/nv-codec-headers +++ b/third-party/nv-codec-headers @@ -1 +1 @@ -Subproject commit b550d4042f1ac0990efa1fa9f0f0c08fb6b24446 +Subproject commit 2055784e5d5bfb3df78d4d3645f345f19062dce2 From 7dbd5b41b2f4107c18eadfa35cf3746c64c9d0eb Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 10 Jan 2023 09:26:42 -0500 Subject: [PATCH 04/51] Fix typo in `Shortcuts` section (#744) --- docs/source/about/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index f6c574f1..5a73851d 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -190,7 +190,7 @@ Shortcuts All shortcuts start with ``CTRL + ALT + SHIFT``, just like Moonlight - ``CTRL + ALT + SHIFT + N`` - Hide/Unhide the cursor (This may be useful for Remote Desktop Mode for Moonlight) -- ``CTRL + ALT + SHIFT + F1/F13`` - Switch to different monitor for Streaming +- ``CTRL + ALT + SHIFT + F1/F12`` - Switch to different monitor for Streaming Application List ---------------- From 5477f58f18707eeb8fb4dc943826b399133c6bf9 Mon Sep 17 00:00:00 2001 From: Tony Langhammer Date: Tue, 10 Jan 2023 19:25:16 +0100 Subject: [PATCH 05/51] Move the check for UPnP enabled up to avoid error messages (#748) --- src/upnp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/upnp.cpp b/src/upnp.cpp index 36c06921..0dbdb361 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -91,6 +91,10 @@ static std::string_view status_string(int status) { } std::unique_ptr start() { + if(!config::sunshine.flags[config::flag::UPNP]) { + return nullptr; + } + int err {}; device_t device { upnpDiscover(2000, nullptr, nullptr, 0, IPv4, 2, &err) }; @@ -128,10 +132,6 @@ std::unique_ptr start() { } } - if(!config::sunshine.flags[config::flag::UPNP]) { - return nullptr; - } - auto rtsp = std::to_string(map_port(stream::RTSP_SETUP_PORT)); auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT)); auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT)); From 44ad28ebf4a17779e483971e80c260c74e972244 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 10 Jan 2023 13:52:15 -0600 Subject: [PATCH 06/51] Fix a reference leak of hw_frames_ctx and prepare for QSV (#736) --- src/platform/common.h | 9 ++- src/platform/linux/cuda.cpp | 17 ++--- src/platform/linux/vaapi.cpp | 11 ++-- src/platform/macos/nv12_zero_device.cpp | 2 +- src/platform/macos/nv12_zero_device.h | 2 +- src/platform/windows/display_vram.cpp | 87 +++++++++++++++---------- src/video.cpp | 33 +++++----- 7 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index fe074c5b..468df863 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -17,6 +17,8 @@ struct sockaddr; struct AVFrame; +struct AVBufferRef; +struct AVHWFramesContext; // Forward declarations of boost classes to avoid having to include boost headers // here, which results in issues with Windows.h and WinSock2.h include order. @@ -196,13 +198,18 @@ struct hwdevice_t { /** * implementations must take ownership of 'frame' */ - virtual int set_frame(AVFrame *frame) { + virtual int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { BOOST_LOG(error) << "Illegal call to hwdevice_t::set_frame(). Did you forget to override it?"; return -1; }; virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {}; + /** + * Implementations may set parameters during initialization of the hwframes context + */ + virtual void init_hwframes(AVHWFramesContext *frames) {}; + virtual ~hwdevice_t() = default; }; diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 963d7c35..9e3c95b4 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -94,20 +94,21 @@ public: return 0; } - int set_frame(AVFrame *frame) override { + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; - auto hwframe_ctx = (AVHWFramesContext *)frame->hw_frames_ctx->data; + auto hwframe_ctx = (AVHWFramesContext *)hw_frames_ctx->data; if(hwframe_ctx->sw_format != AV_PIX_FMT_NV12) { BOOST_LOG(error) << "cuda::cuda_t doesn't support any format other than AV_PIX_FMT_NV12"sv; return -1; } - if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { - BOOST_LOG(error) << "Couldn't get hwframe for NVENC"sv; - - return -1; + if(!frame->buf[0]) { + if(av_hwframe_get_buffer(hw_frames_ctx, frame, 0)) { + BOOST_LOG(error) << "Couldn't get hwframe for NVENC"sv; + return -1; + } } auto cuda_ctx = (AVCUDADeviceContext *)hwframe_ctx->device_ctx->hwctx; @@ -180,8 +181,8 @@ public: return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get()); } - int set_frame(AVFrame *frame) { - if(cuda_t::set_frame(frame)) { + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { + if(cuda_t::set_frame(frame, hw_frames_ctx)) { return -1; } diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 9dcb2dfe..07f0d323 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -313,14 +313,15 @@ public: return 0; } - int set_frame(AVFrame *frame) override { + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; - if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { - BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; - - return -1; + if(!frame->buf[0]) { + if(av_hwframe_get_buffer(hw_frames_ctx, frame, 0)) { + BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; + return -1; + } } va::DRMPRIMESurfaceDescriptor prime; diff --git a/src/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp index 1af0e058..71e58307 100644 --- a/src/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -53,7 +53,7 @@ int nv12_zero_device::convert(platf::img_t &img) { return result > 0 ? 0 : -1; } -int nv12_zero_device::set_frame(AVFrame *frame) { +int nv12_zero_device::set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { this->frame = frame; av_frame.reset(frame); diff --git a/src/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h index 3b74ebcc..1863fb0f 100644 --- a/src/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -20,7 +20,7 @@ public: int init(void *display, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn); int convert(img_t &img); - int set_frame(AVFrame *frame); + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx); void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); }; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 4c0e8fa4..72ad6b7b 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -392,17 +392,63 @@ public: this->color_matrix = std::move(color_matrix); } - int set_frame(AVFrame *frame) { + void init_hwframes(AVHWFramesContext *frames) override { + // We may be called with a QSV or D3D11VA context + if(frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) { + auto d3d11_frames = (AVD3D11VAFramesContext *)frames->hwctx; + + // The encoder requires textures with D3D11_BIND_RENDER_TARGET set + d3d11_frames->BindFlags = D3D11_BIND_RENDER_TARGET; + d3d11_frames->MiscFlags = 0; + } + + // We require a single texture + frames->initial_pool_size = 1; + } + + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; + // Populate this frame with a hardware buffer if one isn't there already + if(!frame->buf[0]) { + auto err = av_hwframe_get_buffer(hw_frames_ctx, frame, 0); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; + BOOST_LOG(error) << "Failed to get hwframe buffer: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + return -1; + } + } + + // If this is a frame from a derived context, we'll need to map it to D3D11 + ID3D11Texture2D *frame_texture; + if(frame->format != AV_PIX_FMT_D3D11) { + frame_t d3d11_frame { av_frame_alloc() }; + + d3d11_frame->format = AV_PIX_FMT_D3D11; + + auto err = av_hwframe_map(d3d11_frame.get(), frame, AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; + BOOST_LOG(error) << "Failed to map D3D11 frame: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + return -1; + } + + // Get the texture from the mapped frame + frame_texture = (ID3D11Texture2D *)d3d11_frame->data[0]; + } + else { + // Otherwise, we can just use the texture inside the original frame + frame_texture = (ID3D11Texture2D *)frame->data[0]; + } + auto out_width = frame->width; auto out_height = frame->height; float in_width = img.display->width; float in_height = img.display->height; - // // Ensure aspect ratio is maintained + // Ensure aspect ratio is maintained auto scalar = std::fminf(out_width / in_width, out_height / in_height); auto out_width_f = in_width * scalar; auto out_height_f = in_height * scalar; @@ -414,21 +460,9 @@ public: outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; - D3D11_TEXTURE2D_DESC t {}; - t.Width = out_width; - t.Height = out_height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_DEFAULT; - t.Format = format; - t.BindFlags = D3D11_BIND_RENDER_TARGET; - - auto status = device->CreateTexture2D(&t, nullptr, &img.encoder_texture); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } + // The underlying frame pool owns the texture, so we must reference it for ourselves + frame_texture->AddRef(); + img.encoder_texture.reset(frame_texture); img.width = out_width; img.height = out_height; @@ -449,7 +483,7 @@ public: D3D11_RTV_DIMENSION_TEXTURE2D }; - status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_Y_rt); + auto status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_Y_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -463,23 +497,6 @@ public: return -1; } - // Need to have something refcounted - if(!frame->buf[0]) { - frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); - } - - auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data; - desc->texture = (ID3D11Texture2D *)img.data; - desc->index = 0; - - frame->data[0] = img.data; - frame->data[1] = 0; - - frame->linesize[0] = img.row_pitch; - - frame->height = img.height; - frame->width = img.width; - return 0; } diff --git a/src/video.cpp b/src/video.cpp index 1fd50e7e..cd13b0d2 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -71,7 +71,7 @@ util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_c util::Either vaapi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); util::Either cuda_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); -int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); +int hwframe_ctx(ctx_t &ctx, platf::hwdevice_t *hwdevice, buffer_t &hwdevice_ctx, AVPixelFormat format); class swdevice_t : public platf::hwdevice_t { public: @@ -116,17 +116,16 @@ public: return 0; } - int set_frame(AVFrame *frame) { + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) { this->frame = frame; // If it's a hwframe, allocate buffers for hardware - if(frame->hw_frames_ctx) { + if(hw_frames_ctx) { hw_frame.reset(frame); - if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) return -1; + if(av_hwframe_get_buffer(hw_frames_ctx, frame, 0)) return -1; } - - if(!frame->hw_frames_ctx) { + else { sw_frame.reset(frame); } @@ -181,9 +180,9 @@ public: return 0; } - int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format) { + int init(int in_width, int in_height, AVFrame *frame, AVPixelFormat format, bool hardware) { // If the device used is hardware, yet the image resides on main memory - if(frame->hw_frames_ctx) { + if(hardware) { sw_frame.reset(av_frame_alloc()); sw_frame->width = frame->width; @@ -981,7 +980,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & } hwdevice_ctx = std::move(buf_or_error.left()); - if(hwframe_ctx(ctx, hwdevice_ctx, sw_fmt)) { + if(hwframe_ctx(ctx, hwdevice.get(), hwdevice_ctx, sw_fmt)) { return std::nullopt; } @@ -1063,17 +1062,12 @@ std::optional make_session(const encoder_t &encoder, const config_t & frame->width = ctx->width; frame->height = ctx->height; - - if(hardware) { - frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx); - } - std::shared_ptr device; if(!hwdevice->data) { auto device_tmp = std::make_unique(); - if(device_tmp->init(width, height, frame.get(), sw_fmt)) { + if(device_tmp->init(width, height, frame.get(), sw_fmt, hardware)) { return std::nullopt; } @@ -1083,7 +1077,7 @@ std::optional make_session(const encoder_t &encoder, const config_t & device = std::move(hwdevice); } - if(device->set_frame(frame.release())) { + if(device->set_frame(frame.release(), ctx->hw_frames_ctx)) { return std::nullopt; } @@ -1812,8 +1806,8 @@ int init() { return 0; } -int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { - buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get()) }; +int hwframe_ctx(ctx_t &ctx, platf::hwdevice_t *hwdevice, buffer_t &hwdevice_ctx, AVPixelFormat format) { + buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice_ctx.get()) }; auto frame_ctx = (AVHWFramesContext *)frame_ref->data; frame_ctx->format = ctx->pix_fmt; @@ -1822,6 +1816,9 @@ int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { frame_ctx->width = ctx->width; frame_ctx->initial_pool_size = 0; + // Allow the hwdevice to modify hwframe context parameters + hwdevice->init_hwframes(frame_ctx); + if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) { return err; } From 37710624995efc2e19e9ba1e000deede8c745eff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:46:53 -0500 Subject: [PATCH 07/51] Bump sphinx from 6.1.1 to 6.1.3 (#749) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 82012203..24cbabce 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ furo==2022.12.7 m2r2==0.3.3 -Sphinx==6.1.1 +Sphinx==6.1.3 sphinx-copybutton==0.5.1 From b4058880887c4309ce6c2548f81b15e28062358f Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Tue, 10 Jan 2023 21:25:09 +0000 Subject: [PATCH 08/51] CMake: Windows: harden install by including zlib1.dll (#743) --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2305eab8..855daef4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -475,6 +475,8 @@ add_executable(sunshine ${SUNSHINE_TARGET_FILES}) if(WIN32) set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") + find_library(ZLIB ZLIB1) endif() target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) @@ -521,6 +523,9 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules" if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.html install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application) + # Hardening: include zlib1.dll (loaded via LoadLibrary() in openssl's libcrypto.a) + install(FILES "${ZLIB}" DESTINATION "." COMPONENT application) + # Adding tools install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi) install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio) From 43d47c6f3ce5b763d0b5acdb69a079114b8c5d49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:54:01 -0500 Subject: [PATCH 09/51] Bump third-party/miniupnp from `207cf44` to `014c9df` (#734) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/miniupnp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/miniupnp b/third-party/miniupnp index 207cf440..014c9df8 160000 --- a/third-party/miniupnp +++ b/third-party/miniupnp @@ -1 +1 @@ -Subproject commit 207cf440a22c075cb55fb067a850be4f9c204e6e +Subproject commit 014c9df8ee7a36e5bf85aa619062a2d4b95ec8f6 From 696a11942cdd33f1fbdacf0ed74fb61ca96ad60a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 11 Jan 2023 10:02:53 -0600 Subject: [PATCH 10/51] Enforce 10 FPS encoding frame rate floor to improve static image quality (#754) --- src/video.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index cd13b0d2..63c19c4c 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1134,14 +1134,12 @@ void encode_run( idr_events->pop(); } + // Encode at a minimum of 10 FPS to avoid image quality issues with static content if(!frame->key_frame || images->peek()) { if(auto img = images->pop(100ms)) { session->device->convert(*img); } - else if(images->running()) { - continue; - } - else { + else if(!images->running()) { break; } } From 5480d3d59d5d27b5d21ce50478cdfcf64e537cd8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 14 Jan 2023 08:45:12 -0600 Subject: [PATCH 11/51] Switch to faster nanors Reed-Solomon implementation (#753) --- .gitmodules | 4 ++++ CMakeLists.txt | 10 +++++++--- src/stream.cpp | 3 +-- third-party/nanors | 1 + 4 files changed, 13 insertions(+), 5 deletions(-) create mode 160000 third-party/nanors diff --git a/.gitmodules b/.gitmodules index b79222a2..c6b2af2c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,3 +42,7 @@ path = third-party/ffmpeg-macos-aarch64 url = https://github.com/LizardByte/build-deps branch = ffmpeg-macos-aarch64 +[submodule "third-party/nanors"] + path = third-party/nanors + url = https://github.com/sleepybishop/nanors.git + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 855daef4..b88bba2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,8 +337,8 @@ configure_file(version.h.in version.h @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES - third-party/moonlight-common-c/reedsolomon/rs.c - third-party/moonlight-common-c/reedsolomon/rs.h + third-party/nanors/rs.c + third-party/nanors/rs.h third-party/moonlight-common-c/src/Input.h third-party/moonlight-common-c/src/Rtsp.h third-party/moonlight-common-c/src/RtspParser.c @@ -385,6 +385,9 @@ set(SUNSHINE_TARGET_FILES set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties(third-party/nanors/rs.c + PROPERTIES COMPILE_FLAGS "-include deps/obl/autoshim.h -ftree-vectorize") + # Pre-compiled binaries if(WIN32) set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-windows-x86_64") @@ -424,7 +427,8 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/third-party ${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include - ${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/reedsolomon + ${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors + ${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors/deps/obl ${FFMPEG_INCLUDE_DIRS} ${PLATFORM_INCLUDE_DIRS} ) diff --git a/src/stream.cpp b/src/stream.cpp index 6b470ddb..78667aa1 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1077,8 +1077,7 @@ void audioBroadcastThread(udp::socket &sock) { // works correctly. This is possible because the data and FEC shard count is // constant and known in advance. const unsigned char parity[] = { 0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c }; - memcpy(&rs.get()->m[16], parity, sizeof(parity)); - memcpy(rs.get()->parity, parity, sizeof(parity)); + memcpy(rs.get()->p, parity, sizeof(parity)); audio_packet->rtp.header = 0x80; audio_packet->rtp.packetType = 97; diff --git a/third-party/nanors b/third-party/nanors new file mode 160000 index 00000000..395e5ada --- /dev/null +++ b/third-party/nanors @@ -0,0 +1 @@ +Subproject commit 395e5ada44dd8d5974eaf6bb6b17f23406e3ca72 From 4fc444b5b3ccf59f4dccb43344ff4ce02bcabe6d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 14 Jan 2023 15:23:49 -0600 Subject: [PATCH 12/51] Intel QuickSync support for Windows (#758) --- docs/source/about/advanced_usage.rst | 77 ++++++++++- src/config.cpp | 52 ++++++- src/config.h | 5 + src/platform/common.h | 7 + src/platform/windows/display_vram.cpp | 17 +++ src/video.cpp | 167 +++++++++++++++++++---- src_assets/common/assets/web/config.html | 34 ++++- 7 files changed, 322 insertions(+), 37 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 03e46983..98daa7fc 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -739,13 +739,14 @@ encoder .. table:: :widths: auto - ======== =========== - Value Description - ======== =========== - nvenc For Nvidia graphics cards - amdvce For AMD graphics cards - software Encoding occurs on the CPU - ======== =========== + ========= =========== + Value Description + ========= =========== + nvenc For NVIDIA graphics cards + quicksync For Intel graphics cards + amdvce For AMD graphics cards + software Encoding occurs on the CPU + ========= =========== **Default** Sunshine will use the first encoder that is available. @@ -958,6 +959,68 @@ nv_coder nv_coder = auto +qsv_preset +^^^^^^^^^^ + +**Description** + The encoder preset to use. + + .. Note:: This option only applies when using quicksync `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + veryfast fastest (lowest quality) + faster faster (lower quality) + fast fast (low quality) + medium medium (default) + slow slow (good quality) + slower slower (better quality) + veryslow slowest (best quality) + ========== =========== + +**Default** + ``medium`` + +**Example** + .. code-block:: text + + qsv_preset = medium + +qsv_coder +^^^^^^^^^ + +**Description** + The entropy encoding to use. + + .. Note:: This option only applies when using H264 with quicksync `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + auto let ffmpeg decide + cabac context adaptive binary arithmetic coding - higher quality + cavlc context adaptive variable-length coding - faster decode + ========== =========== + +**Default** + ``auto`` + +**Example** + .. code-block:: text + + qsv_coder = auto + amd_quality ^^^^^^^^^^^ diff --git a/src/config.cpp b/src/config.cpp index 5bc04159..11c393a8 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -200,6 +200,46 @@ int coder_from_view(const std::string_view &coder) { } } // namespace amd +namespace qsv { +enum preset_e : int { + veryslow = 1, + slower = 2, + slow = 3, + medium = 4, + fast = 5, + faster = 6, + veryfast = 7 +}; + +enum cavlc_e : int { + _auto = false, + enabled = true, + disabled = false +}; + +std::optional preset_from_view(const std::string_view &preset) { +#define _CONVERT_(x) \ + if(preset == #x##sv) return x + _CONVERT_(veryslow); + _CONVERT_(slower); + _CONVERT_(slow); + _CONVERT_(medium); + _CONVERT_(fast); + _CONVERT_(faster); + _CONVERT_(veryfast); +#undef _CONVERT_ + return std::nullopt; +} + +std::optional coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return disabled; + if(coder == "cavlc"sv || coder == "vlc"sv) return enabled; + return std::nullopt; +} + +} // namespace qsv + namespace vt { enum coder_e : int { @@ -254,6 +294,11 @@ video_t video { nv::_auto // coder }, // nv + { + qsv::medium, // preset + qsv::_auto, // cavlc + }, // qsv + { (int)amd::quality_h264_e::balanced, // quality (h264) (int)amd::quality_hevc_e::balanced, // quality (hevc) @@ -261,11 +306,13 @@ video_t video { (int)amd::rc_hevc_e::vbr_latency, // rate control (hevc) (int)amd::coder_e::_auto, // coder }, // amd + { 0, 0, 1, - -1 }, // vt + -1, + }, // vt {}, // encoder {}, // adapter_name @@ -761,6 +808,9 @@ void apply_config(std::unordered_map &&vars) { int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view); int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view); + int_f(vars, "qsv_preset", video.qsv.preset, qsv::preset_from_view); + int_f(vars, "qsv_coder", video.qsv.cavlc, qsv::coder_from_view); + std::string quality; string_f(vars, "amd_quality", quality); if(!quality.empty()) { diff --git a/src/config.h b/src/config.h index 7b1c705e..a95e0f85 100644 --- a/src/config.h +++ b/src/config.h @@ -28,6 +28,11 @@ struct video_t { int coder; } nv; + struct { + std::optional preset; + std::optional cavlc; + } qsv; + struct { std::optional quality_h264; std::optional quality_hevc; diff --git a/src/platform/common.h b/src/platform/common.h index 468df863..be94673d 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -210,6 +210,13 @@ struct hwdevice_t { */ virtual void init_hwframes(AVHWFramesContext *frames) {}; + /** + * Implementations may make modifications required before context derivation + */ + virtual int prepare_to_derive_context(int hw_device_type) { + return 0; + }; + virtual ~hwdevice_t() = default; }; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 72ad6b7b..4e71a6ce 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -406,6 +406,23 @@ public: frames->initial_pool_size = 1; } + int prepare_to_derive_context(int hw_device_type) override { + // QuickSync requires our device to be multithread-protected + if(hw_device_type == AV_HWDEVICE_TYPE_QSV) { + multithread_t mt; + + auto status = device->QueryInterface(IID_ID3D11Multithread, (void **)&mt); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to query ID3D11Multithread interface from device [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + mt->SetMultithreadProtected(TRUE); + } + + return 0; + } + int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override { this->hwframe.reset(frame); this->frame = frame; diff --git a/src/video.cpp b/src/video.cpp index 63c19c4c..0d0ea2da 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -63,8 +63,29 @@ enum class profile_hevc_e : int { }; } // namespace nv +namespace qsv { -platf::mem_type_e map_dev_type(AVHWDeviceType type); +enum class profile_h264_e : int { + baseline = 66, + main = 77, + high = 100, +}; + +enum class profile_hevc_e : int { + main = 1, + main_10 = 2, +}; + +enum class multiframe_mode_e : int { + mf_default = 0, + mf_disabled = 1, + mf_auto = 2, + mf_manual = 3, +}; +} // namespace qsv + + +platf::mem_type_e map_base_dev_type(AVHWDeviceType type); platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); util::Either dxgi_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); @@ -234,11 +255,13 @@ public: }; enum flag_e { - DEFAULT = 0x00, - PARALLEL_ENCODING = 0x01, - H264_ONLY = 0x02, // When HEVC is too heavy - LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* - SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P + DEFAULT = 0x00, + PARALLEL_ENCODING = 0x01, + H264_ONLY = 0x02, // When HEVC is too heavy + LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* + SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P + CBR_WITH_VBR = 0x10, // Use a VBR rate control mode to simulate CBR + RELAXED_COMPLIANCE = 0x20, // Use FF_COMPLIANCE_UNOFFICIAL compliance mode }; struct encoder_t { @@ -285,11 +308,10 @@ struct encoder_t { option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {} }; - AVHWDeviceType dev_type; + AVHWDeviceType base_dev_type, derived_dev_type; AVPixelFormat dev_pix_fmt; - AVPixelFormat static_pix_fmt; - AVPixelFormat dynamic_pix_fmt; + AVPixelFormat static_pix_fmt, dynamic_pix_fmt; struct { std::vector common_options; @@ -403,10 +425,10 @@ auto capture_thread_sync = safe::make_shared(start_c static encoder_t nvenc { "nvenc"sv, #ifdef _WIN32 - AV_HWDEVICE_TYPE_D3D11VA, + AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_D3D11, #else - AV_HWDEVICE_TYPE_CUDA, + AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_CUDA, #endif AV_PIX_FMT_NV12, AV_PIX_FMT_P010, @@ -458,9 +480,66 @@ static encoder_t nvenc { }; #ifdef _WIN32 +static encoder_t quicksync { + "quicksync"sv, + AV_HWDEVICE_TYPE_D3D11VA, + AV_HWDEVICE_TYPE_QSV, + AV_PIX_FMT_QSV, + AV_PIX_FMT_NV12, + AV_PIX_FMT_P010, + { + // Common options + { + { "preset"s, &config::video.qsv.preset }, + { "forced_idr"s, 1 }, + { "async_depth"s, 1 }, + { "low_delay_brc"s, 1 }, + { "recovery_point_sei"s, 0 }, + { "vcm"s, 1 }, + { "pic_timing_sei"s, 0 }, + { "max_dec_frame_buffering"s, 1 }, + { "mfmode"s, (int)qsv::multiframe_mode_e::mf_disabled }, + }, + // SDR-specific options + { + { "profile"s, (int)qsv::profile_hevc_e::main }, + }, + // HDR-specific options + { + { "profile"s, (int)qsv::profile_hevc_e::main_10 }, + }, + std::make_optional({ "qp"s, &config::video.qp }), + "hevc_qsv"s, + }, + { + // Common options + { + { "preset"s, &config::video.qsv.preset }, + { "cavlc"s, &config::video.qsv.cavlc }, + { "forced_idr"s, 1 }, + { "async_depth"s, 1 }, + { "low_delay_brc"s, 1 }, + { "recovery_point_sei"s, 0 }, + { "vcm"s, 1 }, + { "pic_timing_sei"s, 0 }, + { "max_dec_frame_buffering"s, 1 }, + { "mfmode"s, (int)qsv::multiframe_mode_e::mf_disabled }, + }, + // SDR-specific options + { + { "profile"s, (int)qsv::profile_h264_e::high }, + }, + {}, // HDR-specific options + std::make_optional({ "qp"s, &config::video.qp }), + "h264_qsv"s, + }, + PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE, + dxgi_make_hwdevice_ctx, +}; + static encoder_t amdvce { "amdvce"sv, - AV_HWDEVICE_TYPE_D3D11VA, + AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, { @@ -505,7 +584,7 @@ static encoder_t amdvce { static encoder_t software { "software"sv, - AV_HWDEVICE_TYPE_NONE, + AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, { @@ -543,7 +622,7 @@ static encoder_t software { #ifdef __linux__ static encoder_t vaapi { "vaapi"sv, - AV_HWDEVICE_TYPE_VAAPI, + AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_VAAPI, AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P10, { @@ -579,7 +658,7 @@ static encoder_t vaapi { #ifdef __APPLE__ static encoder_t videotoolbox { "videotoolbox"sv, - AV_HWDEVICE_TYPE_NONE, + AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, { @@ -617,6 +696,7 @@ static std::vector encoders { nvenc, #endif #ifdef _WIN32 + quicksync, amdvce, #endif #ifdef __linux__ @@ -632,7 +712,7 @@ void reset_display(std::shared_ptr &disp, AVHWDeviceType type, // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { disp.reset(); - disp = platf::display(map_dev_type(type), display_name, framerate); + disp = platf::display(map_base_dev_type(type), display_name, framerate); if(disp) { break; } @@ -664,7 +744,7 @@ void captureThread( // Get all the monitor names now, rather than at boot, to // get the most up-to-date list available monitors - auto display_names = platf::display_names(map_dev_type(encoder.dev_type)); + auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type)); int display_p = 0; if(display_names.empty()) { @@ -683,7 +763,7 @@ void captureThread( capture_ctxs.emplace_back(std::move(*capture_ctx)); } - auto disp = platf::display(map_dev_type(encoder.dev_type), display_names[display_p], capture_ctxs.front().framerate); + auto disp = platf::display(map_base_dev_type(encoder.base_dev_type), display_names[display_p], capture_ctxs.front().framerate); if(!disp) { return; } @@ -769,7 +849,7 @@ void captureThread( } while(capture_ctx_queue->running()) { - reset_display(disp, encoder.dev_type, display_names[display_p], capture_ctxs.front().framerate); + reset_display(disp, encoder.base_dev_type, display_names[display_p], capture_ctxs.front().framerate); if(disp) { break; @@ -868,7 +948,7 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::m } std::optional make_session(const encoder_t &encoder, const config_t &config, int width, int height, std::shared_ptr &&hwdevice) { - bool hardware = encoder.dev_type != AV_HWDEVICE_TYPE_NONE; + bool hardware = encoder.base_dev_type != AV_HWDEVICE_TYPE_NONE; auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc; if(!video_format[encoder_t::PASSED]) { @@ -970,16 +1050,38 @@ std::optional make_session(const encoder_t &encoder, const config_t & // Used by cbs::make_sps_hevc ctx->sw_pix_fmt = sw_fmt; - buffer_t hwdevice_ctx; if(hardware) { + buffer_t hwdevice_ctx; + ctx->pix_fmt = encoder.dev_pix_fmt; + // Create the base hwdevice context auto buf_or_error = encoder.make_hwdevice_ctx(hwdevice.get()); if(buf_or_error.has_right()) { return std::nullopt; } - hwdevice_ctx = std::move(buf_or_error.left()); + + // If this encoder requires derivation from the base, derive the desired type + if(encoder.derived_dev_type != AV_HWDEVICE_TYPE_NONE) { + buffer_t derived_hwdevice_ctx; + + // Allow the hwdevice to prepare for this type of context to be derived + if(hwdevice->prepare_to_derive_context(encoder.derived_dev_type)) { + return std::nullopt; + } + + auto err = av_hwdevice_ctx_create_derived(&derived_hwdevice_ctx, encoder.derived_dev_type, hwdevice_ctx.get(), 0); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; + BOOST_LOG(error) << "Failed to derive device context: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + + return std::nullopt; + } + + hwdevice_ctx = std::move(derived_hwdevice_ctx); + } + if(hwframe_ctx(ctx, hwdevice.get(), hwdevice_ctx, sw_fmt)) { return std::nullopt; } @@ -1026,7 +1128,18 @@ std::optional make_session(const encoder_t &encoder, const config_t & auto bitrate = config.bitrate * 1000; ctx->rc_max_rate = bitrate; ctx->bit_rate = bitrate; - ctx->rc_min_rate = bitrate; + + if(encoder.flags & CBR_WITH_VBR) { + // Ensure rc_max_bitrate != bit_rate to force VBR mode + ctx->bit_rate--; + } + else { + ctx->rc_min_rate = bitrate; + } + + if(encoder.flags & RELAXED_COMPLIANCE) { + ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; + } if(!hardware && (ctx->slices > 1 || config.videoFormat != 0)) { // Use a larger rc_buffer_size for software encoding when slices are enabled, @@ -1211,7 +1324,7 @@ encode_e encode_run_sync( encode_session_ctx_queue_t &encode_session_ctx_queue) { const auto &encoder = encoders.front(); - auto display_names = platf::display_names(map_dev_type(encoder.dev_type)); + auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type)); int display_p = 0; if(display_names.empty()) { @@ -1242,7 +1355,7 @@ encode_e encode_run_sync( int framerate = synced_session_ctxs.front()->config.framerate; while(encode_session_ctx_queue.running()) { - reset_display(disp, encoder.dev_type, display_names[display_p], framerate); + reset_display(disp, encoder.base_dev_type, display_names[display_p], framerate); if(disp) { break; } @@ -1502,7 +1615,7 @@ enum validate_flag_e { }; int validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.dev_type, config::video.output_name, config.framerate); + reset_display(disp, encoder.base_dev_type, config::video.output_name, config.framerate); if(!disp) { return -1; } @@ -1927,7 +2040,7 @@ int start_capture_sync(capture_thread_sync_ctx_t &ctx) { } void end_capture_sync(capture_thread_sync_ctx_t &ctx) {} -platf::mem_type_e map_dev_type(AVHWDeviceType type) { +platf::mem_type_e map_base_dev_type(AVHWDeviceType type) { switch(type) { case AV_HWDEVICE_TYPE_D3D11VA: return platf::mem_type_e::dxgi; diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 096233dc..1bc06507 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -523,6 +523,7 @@ + +
+
+ + +
+
+ + +
+
@@ -820,6 +844,10 @@ id: "nv", name: "NVIDIA NVENC Encoder", }, + { + id: "qsv", + name: "Intel QuickSync Encoder", + }, { id: "amd", name: "AMD AMF Encoder", @@ -855,12 +883,12 @@ } if (this.platform == "linux") { this.tabs = this.tabs.filter((el) => { - return el.id !== "amd" && el.id !== "vt"; + return el.id !== "amd" && el.id !== "qsv" && el.id !== "vt"; }); } if (this.platform == "macos") { this.tabs = this.tabs.filter((el) => { - return el.id !== "amd" && el.id !== "nv" && el.id !== "va-api"; + return el.id !== "amd" && el.id !== "nv" && el.id !== "qsv" && el.id !== "va-api"; }); } @@ -884,6 +912,8 @@ this.config.nv_tune = this.config.nv_tune || "ull"; this.config.nv_coder = this.config.nv_coder || "auto"; this.config.nv_rc = this.config.nv_rc || "cbr"; + this.config.qsv_preset = this.config.qsv_preset || "medium"; + this.config.qsv_coder = this.config.qsv_coder || "auto"; this.config.amd_coder = this.config.amd_coder || "auto" this.config.amd_quality = this.config.amd_quality || "balanced"; this.config.amd_rc = this.config.amd_rc || "vbr_latency"; From 9fe539f87da36514850cb4d230182d58c6992592 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 14 Jan 2023 19:14:55 -0600 Subject: [PATCH 13/51] Avoid redundant padding fills in display_vram_t::convert() (#763) --- src/platform/windows/display_vram.cpp | 58 ++++----------------------- 1 file changed, 8 insertions(+), 50 deletions(-) diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 4e71a6ce..22d65be3 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -300,8 +300,7 @@ blob_t compile_vertex_shader(LPCSTR file) { class hwdevice_t : public platf::hwdevice_t { public: int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; - auto back_d3d_img = (img_d3d_t *)back_img.get(); + auto &img = (img_d3d_t &)img_base; // Open the shared capture texture with our ID3D11Device if(share_img(&img_base)) { @@ -315,24 +314,9 @@ public: return -1; } - // Even though this image will never have racing updates, we must acquire the - // keyed mutex for PSSetShaderResources() to succeed. - status = back_d3d_img->encoder_mutex->AcquireSync(0, INFINITE); - if(status != S_OK) { - img.encoder_mutex->ReleaseSync(0); - BOOST_LOG(error) << "Failed to acquire back_d3d_img mutex [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - device_ctx->IASetInputLayout(input_layout.get()); - - _init_view_port(this->img.width, this->img.height); device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx->PSSetShader(convert_Y_ps.get(), nullptr, 0); - device_ctx->PSSetShaderResources(0, 1, &back_d3d_img->encoder_input_res); - device_ctx->Draw(3, 0); - device_ctx->RSSetViewports(1, &outY_view); device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res); device_ctx->Draw(3, 0); @@ -341,20 +325,13 @@ public: // before rendering on the UV part of the image. device_ctx->Flush(); - _init_view_port(this->img.width / 2, this->img.height / 2); device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0); device_ctx->PSSetShader(convert_UV_ps.get(), nullptr, 0); - device_ctx->PSSetShaderResources(0, 1, &back_d3d_img->encoder_input_res); - device_ctx->Draw(3, 0); - device_ctx->RSSetViewports(1, &outUV_view); - device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res); device_ctx->Draw(3, 0); - device_ctx->Flush(); - // Release encoder mutexes to allow capture code to reuse this image - back_d3d_img->encoder_mutex->ReleaseSync(0); + // Release encoder mutex to allow capture code to reuse this image img.encoder_mutex->ReleaseSync(0); return 0; @@ -514,6 +491,12 @@ public: return -1; } + // Clear the RTVs to ensure the aspect ratio padding is black + const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + device_ctx->ClearRenderTargetView(nv12_Y_rt.get(), y_black); + const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; + device_ctx->ClearRenderTargetView(nv12_UV_rt.get(), uv_black); + return 0; } @@ -609,14 +592,6 @@ public: img.display = std::move(display); - // Color the background black, so that the padding for keeping the aspect ratio - // is black - back_img = img.display->alloc_img(); - if(img.display->dummy_img(back_img.get()) || share_img(back_img.get())) { - BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv; - return -1; - } - blend_disable = make_blend(device.get(), false, false); if(!blend_disable) { return -1; @@ -649,20 +624,6 @@ public: } private: - void _init_view_port(float x, float y, float width, float height) { - D3D11_VIEWPORT view { - x, y, - width, height, - 0.0f, 1.0f - }; - - device_ctx->RSSetViewports(1, &view); - } - - void _init_view_port(float width, float height) { - _init_view_port(0.0f, 0.0f, width, height); - } - int share_img(platf::img_t *img_base) { auto img = (img_d3d_t *)img_base; @@ -722,9 +683,6 @@ public: // The resulting image is stored here. img_d3d_t img; - // Clear nv12 render target to black - std::shared_ptr back_img; - vs_t convert_UV_vs; ps_t convert_UV_ps; ps_t convert_Y_ps; From 3510b8636a8a01469d687681bf27bd2812ea22b5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:31:37 -0500 Subject: [PATCH 14/51] build with docker and improve CUDA compute capability support (#714) --- .docker_platforms | 1 - .dockerignore | 4 +- .github/workflows/CI.yml | 11 +- .github/workflows/ci-docker.yml | 278 ++++++++++++++---- CMakeLists.txt | 71 ++++- Dockerfile | 102 ------- docker/debian-bullseye.dockerfile | 154 ++++++++++ docker/fedora-36.dockerfile | 155 ++++++++++ docker/fedora-37.dockerfile | 155 ++++++++++ docker/ubuntu-18.04.dockerfile-todo | 208 +++++++++++++ docker/ubuntu-20.04.dockerfile | 188 ++++++++++++ docker/ubuntu-22.04.dockerfile | 154 ++++++++++ docs/source/about/installation.rst | 22 ++ docs/source/building/linux.rst | 71 +++-- packaging/linux/aur/PKGBUILD | 6 +- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 82 +++++- 16 files changed, 1456 insertions(+), 206 deletions(-) delete mode 100644 .docker_platforms delete mode 100644 Dockerfile create mode 100644 docker/debian-bullseye.dockerfile create mode 100644 docker/fedora-36.dockerfile create mode 100644 docker/fedora-37.dockerfile create mode 100644 docker/ubuntu-18.04.dockerfile-todo create mode 100644 docker/ubuntu-20.04.dockerfile create mode 100644 docker/ubuntu-22.04.dockerfile diff --git a/.docker_platforms b/.docker_platforms deleted file mode 100644 index f41242ad..00000000 --- a/.docker_platforms +++ /dev/null @@ -1 +0,0 @@ -linux/amd64,linux/arm64/v8 \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index fb14701d..2de6c8e8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,7 +6,6 @@ # ignore repo directories and files docs/ -packaging/ scripts/ tools/ crowdin.yml @@ -14,3 +13,6 @@ crowdin.yml # ignore dev directories build/ venv/ + +# ignore artifacts +artifacts/ diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5cff4180..6ae8ad79 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -363,12 +363,6 @@ jobs: fail-fast: false # false to test all, true to fail entire job if any fail matrix: include: # package these differently - - type: cpack - EXTRA_ARGS: '' - dist: 20.04 - - type: cpack - EXTRA_ARGS: '' - dist: 22.04 - type: appimage EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON' dist: 20.04 @@ -430,6 +424,7 @@ jobs: libcurl4-openssl-dev \ libdrm-dev \ libevdev-dev \ + libmfx-dev \ libnuma-dev \ libopus-dev \ libpulse-dev \ @@ -459,9 +454,9 @@ jobs: --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \ --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10 - # Install CuDA + # Install CUDA sudo wget \ - https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \ + https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run \ --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run sudo chmod a+x /root/cuda.run sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 8ff84aaa..ccc4767e 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -3,10 +3,18 @@ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in # the above-mentioned repo. -# This workflow is intended to work with all our organization Docker projects. Docker platforms/architectures should be -# listed in a file named `.docker_platforms`, comma separated list with no spaces. A readme named `DOCKER_README.md` +# This workflow is intended to work with all our organization Docker projects. A readme named `DOCKER_README.md` # will be used to update the description on Docker hub. +# custom comments in dockerfiles: + +# `# platforms: ` +# Comma separated list of platforms, i.e. `# platforms: linux/386,linux/amd64`. Docker platforms can alternatively +# be listed in a file named `.docker_platforms`. +# `# artifacts: ` +# `true` to build in two steps, stopping at `artifacts` build stage and extracting the image from there to the +# GitHub runner. + name: CI Docker on: @@ -22,56 +30,55 @@ concurrency: cancel-in-progress: true jobs: - check_dockerfile: - name: Check Dockerfile + check_dockerfiles: + name: Check Dockerfiles runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - - name: Check - id: check + - name: Find dockerfiles + id: find run: | - if [ -f "./Dockerfile" ] - then - FOUND=true - else - FOUND=false - fi + dockerfiles=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile") - echo "dockerfile=${FOUND}" >> $GITHUB_OUTPUT + echo "found dockerfiles: ${dockerfiles}" + + # do not quote to keep this as a single line + echo dockerfiles=${dockerfiles} >> $GITHUB_OUTPUT + + MATRIX_COMBINATIONS="" + for FILE in ${dockerfiles}; do + # extract tag from file name + tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(Dockerfile)/None/gm') + if [[ $tag == "None" ]]; then + MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\"}," + else + tag=$(echo $FILE | sed -r -z -e 's/(\.\/)*.*\/(.+)(\.dockerfile)/-\2/gm') + MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS {\"dockerfile\": \"$FILE\", \"tag\": \"$tag\"}," + fi + done + + # removes the last character (i.e. comma) + MATRIX_COMBINATIONS=${MATRIX_COMBINATIONS::-1} + + # setup matrix for later jobs + matrix=$(( + echo "{ \"include\": [$MATRIX_COMBINATIONS] }" + ) | jq -c .) + + echo $matrix + echo $matrix | jq . + echo "matrix=$matrix" >> $GITHUB_OUTPUT outputs: - dockerfile: ${{ steps.check.outputs.dockerfile }} - - lint_dockerfile: - name: Lint Dockerfile - needs: [check_dockerfile] - if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Hadolint - id: hadolint - uses: hadolint/hadolint-action@v3.0.0 - with: - dockerfile: ./Dockerfile - ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059 - output-file: ./hadolint.log - verbose: true - - - name: Log - if: failure() - run: | - echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY - cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY + dockerfiles: ${{ steps.find.outputs.dockerfiles }} + matrix: ${{ steps.find.outputs.matrix }} check_changelog: name: Check Changelog - needs: [check_dockerfile] - if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} + needs: [check_dockerfiles] + if: ${{ needs.check_dockerfiles.outputs.dockerfiles }} runs-on: ubuntu-latest steps: - name: Checkout @@ -87,15 +94,99 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} outputs: next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} + next_version_bare: ${{ steps.verify_changelog.outputs.changelog_parser_version_bare }} + last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }} + release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} + + setup_release: + name: Setup Release + needs: check_changelog + runs-on: ubuntu-latest + steps: + - name: Set release details + id: release_details + env: + RELEASE_BODY: ${{ needs.check_changelog.outputs.release_body }} + run: | + # determine to create a release or not + if [[ $GITHUB_EVENT_NAME == "push" ]]; then + RELEASE=true + else + RELEASE=false + fi + + # set the release tag + COMMIT=${{ github.sha }} + if [[ $GITHUB_REF == refs/heads/master ]]; then + TAG="${{ needs.check_changelog.outputs.next_version }}" + RELEASE_NAME="${{ needs.check_changelog.outputs.next_version }}" + RELEASE_BODY="$RELEASE_BODY" + PRE_RELEASE="false" + elif [[ $GITHUB_REF == refs/heads/nightly ]]; then + TAG="nightly-dev" + RELEASE_NAME="nightly" + RELEASE_BODY="automated nightly release - $(date -u +'%Y-%m-%dT%H:%M:%SZ') - ${COMMIT}" + PRE_RELEASE="true" + fi + + echo "create_release=${RELEASE}" >> $GITHUB_OUTPUT + echo "release_tag=${TAG}" >> $GITHUB_OUTPUT + echo "release_commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT + echo "pre_release=${PRE_RELEASE}" >> $GITHUB_OUTPUT + + # this is stupid but works for multiline strings + echo "RELEASE_BODY<> $GITHUB_ENV + echo "$RELEASE_BODY" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + outputs: + create_release: ${{ steps.release_details.outputs.create_release }} + release_tag: ${{ steps.release_details.outputs.release_tag }} + release_commit: ${{ steps.release_details.outputs.release_commit }} + release_name: ${{ steps.release_details.outputs.release_name }} + release_body: ${{ env.RELEASE_BODY }} + pre_release: ${{ steps.release_details.outputs.pre_release }} + + lint_dockerfile: + needs: [check_dockerfiles] + if: ${{ needs.check_dockerfiles.outputs.dockerfiles }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }} + name: Lint Dockerfile${{ matrix.tag }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Hadolint + id: hadolint + uses: hadolint/hadolint-action@v3.0.0 + with: + dockerfile: ${{ matrix.dockerfile }} + ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059 + output-file: ./hadolint.log + verbose: true + + - name: Log + if: failure() + run: | + echo "Hadolint outcome: ${{ steps.hadolint.outcome }}" >> $GITHUB_STEP_SUMMARY + cat "./hadolint.log" >> $GITHUB_STEP_SUMMARY docker: - name: Docker - needs: [check_dockerfile, check_changelog] - if: ${{ needs.check_dockerfile.outputs.dockerfile == 'true' }} + needs: [check_dockerfiles, check_changelog, setup_release] + if: ${{ needs.check_dockerfiles.outputs.dockerfiles }} runs-on: ubuntu-latest permissions: packages: write contents: write + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.check_dockerfiles.outputs.matrix) }} + name: Docker${{ matrix.tag }} steps: - name: Checkout @@ -106,7 +197,7 @@ jobs: - name: Prepare id: prepare env: - NEXT_VERSION: ${{ needs.check_changelog.outputs.next_version }} + NV: ${{ needs.check_changelog.outputs.next_version }} run: | # get branch name BRANCH=${GITHUB_HEAD_REF} @@ -129,27 +220,49 @@ jobs: BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]') COMMIT=${{ github.sha }} - TAGS="${BASE_TAG}:${COMMIT:0:7},ghcr.io/${BASE_TAG}:${COMMIT:0:7}" + TAGS="${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${COMMIT:0:7}${{ matrix.tag }}" if [[ $GITHUB_REF == refs/heads/master ]]; then - TAGS="${TAGS},${BASE_TAG}:latest,ghcr.io/${BASE_TAG}:latest" - TAGS="${TAGS},${BASE_TAG}:master,ghcr.io/${BASE_TAG}:master" + TAGS="${TAGS},${BASE_TAG}:latest${{ matrix.tag }},ghcr.io/${BASE_TAG}:latest${{ matrix.tag }}" + TAGS="${TAGS},${BASE_TAG}:master${{ matrix.tag }},ghcr.io/${BASE_TAG}:master${{ matrix.tag }}" elif [[ $GITHUB_REF == refs/heads/nightly ]]; then - TAGS="${TAGS},${BASE_TAG}:nightly,ghcr.io/${BASE_TAG}:nightly" + TAGS="${TAGS},${BASE_TAG}:nightly${{ matrix.tag }},ghcr.io/${BASE_TAG}:nightly${{ matrix.tag }}" else - TAGS="${TAGS},${BASE_TAG}:test,ghcr.io/${BASE_TAG}:test" + TAGS="${TAGS},${BASE_TAG}:test${{ matrix.tag }},ghcr.io/${BASE_TAG}:test${{ matrix.tag }}" fi - if [[ ${NEXT_VERSION} != "" ]]; then - TAGS="${TAGS},${BASE_TAG}:${NEXT_VERSION},ghcr.io/${BASE_TAG}:${NEXT_VERSION}" + if [[ ${NV} != "" ]]; then + TAGS="${TAGS},${BASE_TAG}:${NV}${{ matrix.tag }},ghcr.io/${BASE_TAG}:${NV}${{ matrix.tag }}" fi - # read the platforms from `.docker_platforms` - PLATFORMS=$(<.docker_platforms) + # parse custom directives out of dockerfile + # try to get the platforms from the dockerfile custom directive, i.e. `# platforms: xxx,yyy` + while read -r line; do + if [[ $line == "# platforms: "* && $PLATFORMS == "" ]]; then + # echo the line and use `sed` to remove the custom directive + PLATFORMS=$(echo -e "$line" | sed 's/# platforms: //') + elif [[ $line == "# artifacts: "* && $ARTIFACTS == "" ]]; then + # echo the line and use `sed` to remove the custom directive + ARTIFACTS=$(echo -e "$line" | sed 's/# artifacts: //') + elif [[ $PLATFORMS != "" && $ARTIFACTS != "" ]]; then + # break while loop once all custom directives are found + break + fi + done <"${{ matrix.dockerfile }}" + # if PLATFORMS is blank, fall back to the legacy method of reading from the `.docker_platforms` file + if [[ $PLATFORMS == "" ]]; then + # read the platforms from `.docker_platforms` + PLATFORMS=$(<.docker_platforms) + fi + # if PLATFORMS is still blank, fall back to `linux/amd64` + if [[ $PLATFORMS == "" ]]; then + PLATFORMS="linux/amd64" + fi echo "branch=${BRANCH}" >> $GITHUB_OUTPUT echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "artifacts=${ARTIFACTS}" >> $GITHUB_OUTPUT echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT echo "push=${PUSH}" >> $GITHUB_OUTPUT echo "tags=${TAGS}" >> $GITHUB_OUTPUT @@ -165,9 +278,9 @@ jobs: uses: actions/cache@v3 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} + key: Docker-buildx${{ matrix.tag }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-buildx- + Docker-buildx${{ matrix.tag }}- - name: Log in to Docker Hub if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets @@ -184,11 +297,32 @@ jobs: username: ${{ secrets.GH_BOT_NAME }} password: ${{ secrets.GH_BOT_TOKEN }} - - name: Build and push + - name: Build artifacts + if: ${{ steps.prepare.outputs.artifacts == 'true' }} + id: build_artifacts uses: docker/build-push-action@v3 with: context: ./ - file: ./Dockerfile + file: ${{ matrix.dockerfile }} + target: artifacts + outputs: type=local,dest=artifacts + push: false + platforms: ${{ steps.prepare.outputs.platforms }} + build-args: | + BRANCH=${{ steps.prepare.outputs.branch }} + BUILD_DATE=${{ steps.prepare.outputs.build_date }} + BUILD_VERSION=${{ needs.check_changelog.outputs.next_version }} + COMMIT=${{ steps.prepare.outputs.commit }} + tags: ${{ steps.prepare.outputs.tags }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Build and push + id: build + uses: docker/build-push-action@v3 + with: + context: ./ + file: ${{ matrix.dockerfile }} push: ${{ steps.prepare.outputs.push }} platforms: ${{ steps.prepare.outputs.platforms }} build-args: | @@ -200,6 +334,36 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache + - name: Arrange Artifacts + if: ${{ steps.prepare.outputs.artifacts == 'true' }} + working-directory: artifacts + run: | + # artifacts will be in sub directories named after the docker target platform, e.g. `linux_amd64` + # so move files to the artifacts directory + # https://unix.stackexchange.com/a/52816 + find ./ -type f -exec mv -t ./ -n '{}' + + + - name: Upload Artifacts + if: ${{ steps.prepare.outputs.artifacts == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: sunshine${{ matrix.tag }} + path: artifacts/ + + - name: Create/Update GitHub Release + if: ${{ needs.setup_release.outputs.create_release == 'true' && steps.prepare.outputs.artifacts == 'true' }} + uses: ncipollo/release-action@v1 + with: + name: ${{ needs.setup_release.outputs.release_name }} + tag: ${{ needs.setup_release.outputs.release_tag }} + commit: ${{ needs.setup_release.outputs.release_commit }} + artifacts: "*artifacts/*" + token: ${{ secrets.GH_BOT_TOKEN }} + allowUpdates: true + body: ${{ needs.setup_release.outputs.release_body }} + discussionCategory: announcements + prerelease: ${{ needs.setup_release.outputs.pre_release }} + - name: Update Docker Hub Description if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: peter-evans/dockerhub-description@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index b88bba2a..a764b77a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.18) +# `CMAKE_CUDA_ARCHITECTURES` requires 3.18 project(Sunshine VERSION 0.17.0 DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight." @@ -64,7 +65,7 @@ include_directories(third-party/miniupnp/miniupnpc/include) find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) find_package(PkgConfig REQUIRED) -pkg_check_modules (CURL REQUIRED libcurl) +pkg_check_modules(CURL REQUIRED libcurl) if(NOT APPLE) set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103 @@ -197,12 +198,70 @@ else() check_language(CUDA) if(CMAKE_CUDA_COMPILER) - if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) - set(CMAKE_CUDA_ARCHITECTURES 35) - endif() - set(CUDA_FOUND ON) enable_language(CUDA) + + message(STATUS "CUDA Compiler Version: ${CMAKE_CUDA_COMPILER_VERSION}") + set(CMAKE_CUDA_ARCHITECTURES "") + + # https://tech.amikelive.com/node-930/cuda-compatibility-of-nvidia-display-gpu-drivers/ + if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 6.5) + list(APPEND CMAKE_CUDA_ARCHITECTURES 10) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_10,code=sm_10") + elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 6.5) + list(APPEND CMAKE_CUDA_ARCHITECTURES 50 52) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_50,code=sm_50") + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_52,code=sm_52") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 7.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 11) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_11,code=sm_11") + elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER 7.6) + list(APPEND CMAKE_CUDA_ARCHITECTURES 60 61 62) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_60,code=sm_60") + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_61,code=sm_61") + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_62,code=sm_62") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 9.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 20) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_20,code=sm_20") + elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 70) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_70,code=sm_70") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 75) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_75,code=sm_75") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 11.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 30) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_30,code=sm_30") + elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 80) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_80,code=sm_80") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.1) + list(APPEND CMAKE_CUDA_ARCHITECTURES 86) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_86,code=sm_86") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.8) + list(APPEND CMAKE_CUDA_ARCHITECTURES 90) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_90,code=sm_90") + endif() + + if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12.0) + list(APPEND CMAKE_CUDA_ARCHITECTURES 35) + # set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -gencode arch=compute_35,code=sm_35") + endif() + + # message(STATUS "CUDA NVCC Flags: ${CUDA_NVCC_FLAGS}") + message(STATUS "CUDA Architectures: ${CMAKE_CUDA_ARCHITECTURES}") endif() endif() if(${SUNSHINE_ENABLE_DRM}) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9ff9d0d9..00000000 --- a/Dockerfile +++ /dev/null @@ -1,102 +0,0 @@ -FROM ubuntu:22.04 AS sunshine-base - -ARG DEBIAN_FRONTEND=noninteractive - -FROM sunshine-base as sunshine-build - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends \ - build-essential=12.9* \ - cmake=3.22.1* \ - libavdevice-dev=7:4.4.* \ - libboost-filesystem-dev=1.74.0* \ - libboost-log-dev=1.74.0* \ - libboost-thread-dev=1.74.0* \ - libboost-program-options-dev=1.74.0* \ - libcap-dev=1:2.44* \ - libcurl4-openssl-dev=7.81.0* \ - libdrm-dev=2.4.110* \ - libevdev-dev=1.12.1* \ - libnuma-dev=2.0.14* \ - libopus-dev=1.3.1* \ - libpulse-dev=1:15.99.1* \ - libssl-dev=3.0.2* \ - libva-dev=2.14.0* \ - libvdpau-dev=1.4* \ - libwayland-dev=1.20.0* \ - libx11-dev=2:1.7.5* \ - libxcb-shm0-dev=1.14* \ - libxcb-xfixes0-dev=1.14* \ - libxcb1-dev=1.14* \ - libxfixes-dev=1:6.0.0* \ - libxrandr-dev=2:1.5.2* \ - libxtst-dev=2:1.2.3* \ - nodejs=12.22.9* \ - npm=8.5.1* \ - nvidia-cuda-dev=11.5.1* \ - nvidia-cuda-toolkit=11.5.1* \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# copy repository -WORKDIR /root/sunshine-build/ -COPY . . - -# setup npm and dependencies -RUN npm install - -# setup build directory -WORKDIR /root/sunshine-build/build - -# cmake and cpack -RUN cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DSUNSHINE_ASSETS_DIR=share/sunshine \ - -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ - -DSUNSHINE_ENABLE_WAYLAND=ON \ - -DSUNSHINE_ENABLE_X11=ON \ - -DSUNSHINE_ENABLE_DRM=ON \ - -DSUNSHINE_ENABLE_CUDA=ON \ - /root/sunshine-build \ - && make -j "$(nproc)" \ - && cpack -G DEB - -FROM sunshine-base as sunshine - -# copy deb from builder -COPY --from=sunshine-build /root/sunshine-build/build/cpack_artifacts/Sunshine.deb /sunshine.deb - -# install sunshine -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends /sunshine.deb \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# network setup -EXPOSE 47984-47990/tcp -EXPOSE 48010 -EXPOSE 47998-48000/udp - -# setup user -ARG PGID=1000 -ENV PGID=${PGID} -ARG PUID=1000 -ENV PUID=${PUID} -ENV TZ="UTC" -ARG UNAME=lizard -ENV UNAME=${UNAME} - -ENV HOME=/home/$UNAME - -RUN groupadd -f -g "${PGID}" "${UNAME}" && \ - useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" && \ - mkdir -p ${HOME}/.config/sunshine && \ - ln -s ${HOME}/.config/sunshine /config && \ - chown -R ${UNAME} ${HOME} - -USER ${UNAME} -WORKDIR ${HOME} - -# entrypoint -ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/debian-bullseye.dockerfile b/docker/debian-bullseye.dockerfile new file mode 100644 index 00000000..fd0c3753 --- /dev/null +++ b/docker/debian-bullseye.dockerfile @@ -0,0 +1,154 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=debian +ARG TAG=bullseye +FROM ${BASE}:${TAG} AS sunshine-base + +ENV DEBIAN_FRONTEND=noninteractive + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +RUN <<_DEPS +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends \ + build-essential=12.9* \ + cmake=3.18.4* \ + libavdevice-dev=7:4.3.* \ + libboost-filesystem-dev=1.74.0* \ + libboost-log-dev=1.74.0* \ + libboost-program-options-dev=1.74.0* \ + libboost-thread-dev=1.74.0* \ + libcap-dev=1:2.44* \ + libcurl4-openssl-dev=7.74.0* \ + libdrm-dev=2.4.104* \ + libevdev-dev=1.11.0* \ + libnuma-dev=2.0.12* \ + libopus-dev=1.3.1* \ + libpulse-dev=14.2* \ + libssl-dev=1.1.1* \ + libva-dev=2.10.0* \ + libvdpau-dev=1.4* \ + libwayland-dev=1.18.0* \ + libx11-dev=2:1.7.2* \ + libxcb-shm0-dev=1.14* \ + libxcb-xfixes0-dev=1.14* \ + libxcb1-dev=1.14* \ + libxfixes-dev=1:5.0.3* \ + libxrandr-dev=2:1.5.1* \ + libxtst-dev=2:1.2.3* \ + nodejs=12.22* \ + npm=7.5.2* \ + wget=1.21* +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + libmfx-dev=21.1.0* +fi +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="11.8.0" +ENV CUDA_BUILD="520.61.05" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G DEB +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.deb /sunshine.deb + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends /sunshine.deb +apt-get clean +rm -rf /var/lib/apt/lists/* +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/fedora-36.dockerfile b/docker/fedora-36.dockerfile new file mode 100644 index 00000000..4090382f --- /dev/null +++ b/docker/fedora-36.dockerfile @@ -0,0 +1,155 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=fedora +ARG TAG=36 +FROM ${BASE}:${TAG} AS sunshine-base + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +# hadolint ignore=DL3041 +RUN <<_DEPS +#!/bin/bash +dnf -y update +dnf -y group install "Development Tools" +dnf -y install \ + boost-devel-1.76.0* \ + boost-static-1.76.0* \ + cmake-3.22.2* \ + gcc-12.0.1* \ + gcc-c++-12.0.1* \ + libcap-devel-2.48* \ + libcurl-devel-7.82.0* \ + libdrm-devel-2.4.110* \ + libevdev-devel-1.12.0* \ + libva-devel-2.14.0* \ + libvdpau-devel-1.5* \ + libX11-devel-1.7.3* \ + libxcb-devel-1.13.1* \ + libXcursor-devel-1.2.0* \ + libXfixes-devel-6.0.0* \ + libXi-devel-1.8* \ + libXinerama-devel-1.1.4* \ + libXrandr-devel-1.5.2* \ + libXtst-devel-1.2.3* \ + mesa-libGL-devel-22.0.1* \ + npm-8.3.1* \ + numactl-devel-2.0.14* \ + openssl-devel-3.0.2* \ + opus-devel-1.3.1* \ + pulseaudio-libs-devel-15.0* \ + rpm-build-4.17.0* \ + wget-1.21.3* \ + which-2.21* +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + # libmfx-devel is not listed for fedora 36/37 + https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm +fi +dnf clean all +rm -rf /var/cache/yum +_DEPS + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="12.0.0" +ENV CUDA_BUILD="525.60.13" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G RPM +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.rpm /sunshine.rpm + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +dnf -y update +dnf -y install /sunshine.rpm +dnf clean all +rm -rf /var/cache/yum +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile new file mode 100644 index 00000000..2a49dc3e --- /dev/null +++ b/docker/fedora-37.dockerfile @@ -0,0 +1,155 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=fedora +ARG TAG=37 +FROM ${BASE}:${TAG} AS sunshine-base + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +# hadolint ignore=DL3041 +RUN <<_DEPS +#!/bin/bash +dnf -y update +dnf -y group install "Development Tools" +dnf -y install \ + boost-devel-1.78.0* \ + boost-static-1.78.0* \ + cmake-3.24.1* \ + gcc-12.2.1* \ + gcc-c++-12.2.1* \ + libcap-devel-2.48* \ + libcurl-devel-7.85.0* \ + libdrm-devel-2.4.112* \ + libevdev-devel-1.13.0* \ + libva-devel-2.15.0* \ + libvdpau-devel-1.5* \ + libX11-devel-1.8.1* \ + libxcb-devel-1.13.1* \ + libXcursor-devel-1.2.1* \ + libXfixes-devel-6.0.0* \ + libXi-devel-1.8* \ + libXinerama-devel-1.1.4* \ + libXrandr-devel-1.5.2* \ + libXtst-devel-1.2.3* \ + mesa-libGL-devel-22.2.2* \ + npm-8.15.0* \ + numactl-devel-2.0.14* \ + openssl-devel-3.0.5* \ + opus-devel-1.3.1* \ + pulseaudio-libs-devel-16.1* \ + rpm-build-4.18.0* \ + wget-1.21.3* \ + which-2.21* +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + # libmfx-devel is not listed for fedora 36/37 + https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm +fi +dnf clean all +rm -rf /var/cache/yum +_DEPS + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="12.0.0" +ENV CUDA_BUILD="525.60.13" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G RPM +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.rpm /sunshine-${BASE}-${TAG}-${TARGETARCH}.rpm + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.rpm /sunshine.rpm + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +dnf -y update +dnf -y install /sunshine.rpm +dnf clean all +rm -rf /var/cache/yum +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/ubuntu-18.04.dockerfile-todo b/docker/ubuntu-18.04.dockerfile-todo new file mode 100644 index 00000000..2425aaac --- /dev/null +++ b/docker/ubuntu-18.04.dockerfile-todo @@ -0,0 +1,208 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=ubuntu +ARG TAG=18.04 +FROM ${BASE}:${TAG} AS sunshine-base + +ENV DEBIAN_FRONTEND=noninteractive + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +RUN <<_DEPS +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends \ + software-properties-common=0.96.24.32.18 +add-apt-repository ppa:ubuntu-toolchain-r/test +apt-get install -y --no-install-recommends \ + bison=2:3.0.4* \ + build-essential=12.4* \ + gcc-10=10.3.0* \ + g++-10=10.3.0* \ + libavdevice-dev=7:3.4.* \ + libcap-dev=1:2.25* \ + libcurl-openssl1.0-dev=7.58.0* \ + libdrm-dev=2.4.101* \ + libevdev-dev=1.5.8* \ + libnuma-dev=2.0.11* \ + libopus-dev=1.1.2* \ + libpulse-dev=1:11.1* \ + libssl1.0-dev=1.0.2* \ + libva-dev=2.1.0* \ + libvdpau-dev=1.1.1* \ + libwayland-dev=1.16.0* \ + libx11-dev=2:1.6.4* \ + libxcb-shm0-dev=1.13* \ + libxcb-xfixes0-dev=1.13* \ + libxcb1-dev=1.13* \ + libxfixes-dev=1:5.0.3* \ + libxrandr-dev=2:1.5.1* \ + libxtst-dev=2:1.2.3* \ + npm=3.5.2* \ + node-gyp=3.6.2* \ + nodejs-dev=8.10.0* \ + wget=1.19.4* +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +# Update gcc alias +# https://stackoverflow.com/a/70653945/11214013 +RUN <<_GCC_ALIAS +#!/bin/bash +update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-10 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-10 \ + --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \ + --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10 +_GCC_ALIAS + +# install boost +# cannot install boost for aarch64 using ppa:savoury1/boost-defaults-1.71 +# otherwise add the repository and the following packages +# libboost-filesystem1.71-dev=1.71.0* \ +# libboost-log1.71-dev=1.71.0* \ +# libboost-program-options1.71-dev=1.71.0* \ +# libboost-regex1.71-dev=1.71.0* \ +# libboost-thread1.71-dev=1.71.0* \ +WORKDIR /build/tmp +RUN <<_INSTALL_BOOST +url="https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.bz2" +wget "${url}" --progress=bar:force:noscroll -q --show-progress -O ./boost.tar.bz2 +tar --bzip2 -xf boost.tar.bz2 --directory /build +mv /build/boost_*/ /build/boost +ls -a /build/boost +cd /build/boost +./bootstrap.sh --with-libraries=system,thread,log,program_options && \ +./b2 install variant=release link=static,shared runtime-link=shared -j "$(nproc)" +_INSTALL_BOOST + +# install cmake +# sunshine requires cmake >= 3.18 +WORKDIR /build/cmake +# https://cmake.org/download/ +ENV CMAKE_VERSION="3.25.1" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CMAKE +#!/bin/bash +cmake_prefix="https://github.com/Kitware/CMake/releases/download/v" +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + cmake_arch="x86_64" +elif [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cmake_arch="aarch64" +fi +url="${cmake_prefix}${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.sh" +echo "cmake url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cmake.sh +sh ./cmake.sh --prefix=/usr/local --skip-license +cmake --version +_INSTALL_CMAKE + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="11.8.0" +ENV CUDA_BUILD="520.61.05" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# todo - install libmfx +# https://github.com/Intel-Media-SDK/MediaSDK + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G DEB +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.deb /sunshine.deb + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends /sunshine.deb +apt-get clean +rm -rf /var/lib/apt/lists/* +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/ubuntu-20.04.dockerfile b/docker/ubuntu-20.04.dockerfile new file mode 100644 index 00000000..8aeca6dd --- /dev/null +++ b/docker/ubuntu-20.04.dockerfile @@ -0,0 +1,188 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=ubuntu +ARG TAG=20.04 +FROM ${BASE}:${TAG} AS sunshine-base + +ENV DEBIAN_FRONTEND=noninteractive + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +RUN <<_DEPS +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends \ + build-essential=12.8* \ + gcc-10=10.3.0* \ + g++-10=10.3.0* \ + libavdevice-dev=7:4.2.* \ + libboost-filesystem-dev=1.71.0* \ + libboost-log-dev=1.71.0* \ + libboost-program-options-dev=1.71.0* \ + libboost-thread-dev=1.71.0* \ + libcap-dev=1:2.32* \ + libcurl4-openssl-dev=7.68.0* \ + libdrm-dev=2.4.107* \ + libevdev-dev=1.9.0* \ + libnuma-dev=2.0.12* \ + libopus-dev=1.3.1* \ + libpulse-dev=1:13.99.1* \ + libssl-dev=1.1.1* \ + libva-dev=2.7.0* \ + libvdpau-dev=1.3* \ + libwayland-dev=1.18.0* \ + libx11-dev=2:1.6.9* \ + libxcb-shm0-dev=1.14* \ + libxcb-xfixes0-dev=1.14* \ + libxcb1-dev=1.14* \ + libxfixes-dev=1:5.0.3* \ + libxrandr-dev=2:1.5.2* \ + libxtst-dev=2:1.2.3* \ + nodejs=10.19.0* \ + npm=6.14.4* \ + wget=1.20.3* +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + libmfx-dev=20.1.0* +fi +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +# Update gcc alias +# https://stackoverflow.com/a/70653945/11214013 +RUN <<_GCC_ALIAS +#!/bin/bash +update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-10 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-10 \ + --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \ + --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10 +_GCC_ALIAS + +# install cmake +# sunshine requires cmake >= 3.18 +WORKDIR /build/cmake +# https://cmake.org/download/ +ENV CMAKE_VERSION="3.25.1" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CMAKE +#!/bin/bash +cmake_prefix="https://github.com/Kitware/CMake/releases/download/v" +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + cmake_arch="x86_64" +elif [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cmake_arch="aarch64" +fi +url="${cmake_prefix}${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.sh" +echo "cmake url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cmake.sh +sh ./cmake.sh --prefix=/usr/local --skip-license +cmake --version +_INSTALL_CMAKE + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="11.8.0" +ENV CUDA_BUILD="520.61.05" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G DEB +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.deb /sunshine.deb + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends /sunshine.deb +apt-get clean +rm -rf /var/lib/apt/lists/* +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile new file mode 100644 index 00000000..18053816 --- /dev/null +++ b/docker/ubuntu-22.04.dockerfile @@ -0,0 +1,154 @@ +# artifacts: true +# platforms: linux/amd64,linux/arm64/v8 +ARG BASE=ubuntu +ARG TAG=22.04 +FROM ${BASE}:${TAG} AS sunshine-base + +ENV DEBIAN_FRONTEND=noninteractive + +FROM sunshine-base as sunshine-build + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +RUN <<_DEPS +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends \ + build-essential=12.9* \ + cmake=3.22.1* \ + libavdevice-dev=7:4.4.* \ + libboost-filesystem-dev=1.74.0* \ + libboost-log-dev=1.74.0* \ + libboost-program-options-dev=1.74.0* \ + libboost-thread-dev=1.74.0* \ + libcap-dev=1:2.44* \ + libcurl4-openssl-dev=7.81.0* \ + libdrm-dev=2.4.110* \ + libevdev-dev=1.12.1* \ + libnuma-dev=2.0.14* \ + libopus-dev=1.3.1* \ + libpulse-dev=1:15.99.1* \ + libssl-dev=3.0.2* \ + libva-dev=2.14.0* \ + libvdpau-dev=1.4* \ + libwayland-dev=1.20.0* \ + libx11-dev=2:1.7.5* \ + libxcb-shm0-dev=1.14* \ + libxcb-xfixes0-dev=1.14* \ + libxcb1-dev=1.14* \ + libxfixes-dev=1:6.0.0* \ + libxrandr-dev=2:1.5.2* \ + libxtst-dev=2:1.2.3* \ + nodejs=12.22.9* \ + npm=8.5.1* \ + wget=1.21.2* +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + libmfx-dev=22.3.0* +fi +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="11.8.0" +ENV CUDA_BUILD="520.61.05" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA + +# copy repository +WORKDIR /build/sunshine/ +COPY .. . + +# setup npm dependencies +RUN npm install + +# setup build directory +WORKDIR /build/sunshine/build + +# cmake and cpack +RUN <<_MAKE +#!/bin/bash +cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DSUNSHINE_ASSETS_DIR=share/sunshine \ + -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DSUNSHINE_ENABLE_WAYLAND=ON \ + -DSUNSHINE_ENABLE_X11=ON \ + -DSUNSHINE_ENABLE_DRM=ON \ + -DSUNSHINE_ENABLE_CUDA=ON \ + /build/sunshine +make -j "$(nproc)" +cpack -G DEB +_MAKE + +FROM scratch AS artifacts +ARG BASE +ARG TAG +ARG TARGETARCH +COPY --from=sunshine-build /build/sunshine/build/cpack_artifacts/Sunshine.deb /sunshine-${BASE}-${TAG}-${TARGETARCH}.deb + +FROM sunshine-base as sunshine + +# copy deb from builder +COPY --from=artifacts /sunshine*.deb /sunshine.deb + +# install sunshine +RUN <<_INSTALL_SUNSHINE +#!/bin/bash +apt-get update -y +apt-get install -y --no-install-recommends /sunshine.deb +apt-get clean +rm -rf /var/lib/apt/lists/* +_INSTALL_SUNSHINE + +# network setup +EXPOSE 47984-47990/tcp +EXPOSE 48010 +EXPOSE 47998-48000/udp + +# setup user +ARG PGID=1000 +ENV PGID=${PGID} +ARG PUID=1000 +ENV PUID=${PUID} +ENV TZ="UTC" +ARG UNAME=lizard +ENV UNAME=${UNAME} + +ENV HOME=/home/$UNAME + +# setup user +RUN <<_SETUP_USER +groupadd -f -g "${PGID}" "${UNAME}" +useradd -lm -d ${HOME} -s /bin/bash -g "${PGID}" -G input -u "${PUID}" "${UNAME}" +mkdir -p ${HOME}/.config/sunshine +ln -s ${HOME}/.config/sunshine /config +chown -R ${UNAME} ${HOME} +_SETUP_USER + +USER ${UNAME} +WORKDIR ${HOME} + +# entrypoint +ENTRYPOINT ["/usr/bin/sunshine"] diff --git a/docs/source/about/installation.rst b/docs/source/about/installation.rst index aa64a189..e195c490 100644 --- a/docs/source/about/installation.rst +++ b/docs/source/about/installation.rst @@ -23,6 +23,28 @@ Linux ----- Follow the instructions for your preferred package type below. +**CUDA Compatibility** + +CUDA is used for NVFBC capture. + +.. Tip:: See `CUDA GPUS `_ to cross reference Compute Capability to your GPU. + +.. table:: + :widths: auto + + =========================================== ============== ============== ================================ + Package CUDA Version Min Driver CUDA Compute Capabilities + =========================================== ============== ============== ================================ + https://aur.archlinux.org/packages/sunshine User dependent User dependent User dependent + sunshine.AppImage 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35 + sunshine_{arch}.flatpak 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35 + sunshine-debian-bullseye-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35 + sunshine-fedora-36-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90 + sunshine-fedora-37-{arch}.rpm 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90 + sunshine-ubuntu-20.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35 + sunshine-ubuntu-22.04-{arch}.deb 11.8.0 450.80.02 50;52;60;61;62;70;75;80;86;90;35 + =========================================== ============== ============== ================================ + AppImage ^^^^^^^^ According to AppImageLint the supported distro matrix of the AppImage is below. diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index 2ccdd7c0..36f48d27 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -17,11 +17,13 @@ Install Requirements libavdevice-dev \ libboost-filesystem-dev \ libboost-log-dev \ - libboost-thread-dev \ libboost-program-options-dev \ + libboost-thread-dev \ libcap-dev \ # KMS + libcurl4-openssl-dev \ libdrm-dev \ # KMS libevdev-dev \ + libmfx-dev \ # x86_64 only libnuma-dev \ libopus-dev \ libpulse-dev \ @@ -41,25 +43,24 @@ Install Requirements nvidia-cuda-dev \ # Cuda, NvFBC nvidia-cuda-toolkit # Cuda, NvFBC -Fedora 36 -^^^^^^^^^ +Fedora 36, 37 +^^^^^^^^^^^^^ End of Life: TBD -Install Repositories - .. code-block:: bash - - sudo dnf update && \ - sudo dnf group install "Development Tools" && \ - sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm - Install Requirements .. code-block:: bash + sudo dnf update && \ + sudo dnf group install "Development Tools" && \ sudo dnf install \ boost-devel \ - boost-static.x86_64 \ + boost-static \ cmake \ + gcc \ gcc-c++ \ + libcap-devel \ + libcurl-devel \ + libdrm-devel \ libevdev-devel \ libva-devel \ libvdpau-devel \ @@ -67,18 +68,21 @@ Install Requirements libxcb-devel \ # X11 libXcursor-devel \ # X11 libXfixes-devel \ # X11 - libXinerama-devel \ # X11 libXi-devel \ # X11 + libXinerama-devel \ # X11 libXrandr-devel \ # X11 libXtst-devel \ # X11 mesa-libGL-devel \ - nodejs \ npm \ numactl-devel \ openssl-devel \ opus-devel \ pulseaudio-libs-devel \ - rpm-build # if you want to build an RPM binary package + rpm-build \ # if you want to build an RPM binary package + wget \ # necessary for cuda install with `run` file + which \ # necessary for cuda install with `run` file + # libmfx-devel is not listed for fedora, this is for x86_64 only + https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm Ubuntu 20.04 ^^^^^^^^^^^^ @@ -99,6 +103,7 @@ Install Requirements libcap-dev \ # KMS libdrm-dev \ # KMS libevdev-dev \ + libmfx-dev \ # x86_64 only libnuma-dev \ libopus-dev \ libpulse-dev \ @@ -115,18 +120,17 @@ Install Requirements libxtst-dev \ # X11 nodejs \ npm \ - wget + wget # necessary for cuda install with `run` file Update gcc alias .. code-block:: bash - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 - -Install CuDA - .. code-block:: bash - - wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run - ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run + update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-10 100 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-10 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-10 \ + --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \ + --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10 Ubuntu 22.04 ^^^^^^^^^^^^ @@ -146,6 +150,7 @@ Install Requirements libcap-dev \ # KMS libdrm-dev \ # KMS libevdev-dev \ + libmfx-dev \ # x86_64 only libnuma-dev \ libopus-dev \ libpulse-dev \ @@ -160,8 +165,26 @@ Install Requirements libxtst-dev \ # X11 nodejs \ npm \ - nvidia-cuda-dev \ # Cuda, NvFBC - nvidia-cuda-toolkit # Cuda, NvFBC + nvidia-cuda-dev \ # CUDA, NvFBC + nvidia-cuda-toolkit # CUDA, NvFBC + +CUDA +---- +If the version of CUDA available from your distro is not adequate, manually install CUDA. + +.. Tip:: The version of CUDA you use will determine compatibility with various GPU generations. + See `CUDA compatibility `_ for more info. + + Select the appropriate run file based on your desired CUDA version and architecture according to + `CUDA Toolkit Archive `_. + +.. code-block:: bash + + wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \ + --progress=bar:force:noscroll -q --show-progress -O ./cuda.run + chmod a+x ./cuda.run + ./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm + rm ./cuda.run npm dependencies ---------------- diff --git a/packaging/linux/aur/PKGBUILD b/packaging/linux/aur/PKGBUILD index f8dfbc19..60ac7fed 100644 --- a/packaging/linux/aur/PKGBUILD +++ b/packaging/linux/aur/PKGBUILD @@ -9,9 +9,11 @@ arch=('x86_64' 'i686') url=@PROJECT_HOMEPAGE_URL@ license=('GPL3') -depends=('avahi' 'boost-libs' 'curl' 'libevdev' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev') +depends=('avahi' 'boost-libs' 'curl' 'libevdev' 'libmfx' 'libpulse' 'libva' 'libvdpau' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'numactl' 'openssl' 'opus' 'udev') makedepends=('boost' 'cmake' 'git' 'make' 'nodejs' 'npm') -optdepends=('cuda' 'libcap' 'libdrm') +optdepends=('cuda: NvFBC capture support' + 'libcap' + 'libdrm') provides=(@SUNSHINE_AUR_PROVIDES@) conflicts=(@SUNSHINE_AUR_CONFLICTS@) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index a27e4e7a..1bd9926e 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -40,6 +40,12 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0.orig.tar.xz sha256: 2467be4af625b5ae4b3c93fc7af196a09eba39c11a7338cd9e8b356fa44d2f45 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/b/boost1.74/boost1.74_1.74.0-17ubuntu1.debian.tar.xz + sha256: 22e623d98c84eb3fec57e19ea371157a5bc8225ba4c5907f7e5155072317a31d + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done - name: avahi disabled: false @@ -74,6 +80,13 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8.orig.tar.gz sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/a/avahi/avahi_0.8-6ubuntu1.debian.tar.xz + sha256: ebf1dfe5e853b6bc6843e3bd784cb6af632041f305abd0e5415114f80c1dcea4 + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + - autoreconf -ivf - name: libevdev disabled: false @@ -87,6 +100,59 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg.orig.tar.xz sha256: a882e13ef1dd6bd227318080cabf60fe5af3c06471259d3acfc9dbfb202351a7 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/libe/libevdev/libevdev_1.13.0+dfsg-1.debian.tar.xz + sha256: d33c56acbbfff2dc540e45c57a38d92210b5e7fd0947ac47fbe48183468aad74 + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + + - name: libdrm + # libdrm-intel is needed for libmfx-dev + disabled: false + buildsystem: meson + only-arches: + - x86_64 + sources: + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110.orig.tar.xz + sha256: eecee4c4b47ed6d6ce1a9be3d6d92102548ea35e442282216d47d05293cf9737 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110-1ubuntu1.debian.tar.xz + sha256: 464b9553861f39beddfaee6b8924734b02a0febfae3968e4ca1360f2972bba8b + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + + - name: libmfx-dev + disabled: false + buildsystem: cmake + config-opts: + - -DENABLE_OPENCL=ON + - -DENABLE_X11_DRI3=ON + - -DENABLE_WAYLAND=ON + - -DENABLE_ITT=OFF + - -DENABLE_TEXTLOG=OFF + - -DENABLE_STAT=OFF + - -DBUILD_ALL=OFF + - -DBUILD_RUNTIME=ON + - -DBUILD_SAMPLES=OFF + - -DBUILD_TESTS=OFF + - -DBUILD_TOOLS=OFF + - -DUSE_SYSTEM_GTEST=OFF + - -DMFX_ENABLE_KERNELS=ON + only-arches: + - x86_64 + sources: + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/universe/i/intel-mediasdk/intel-mediasdk_22.3.0.orig.tar.gz + sha256: e1e74229f409e969b70c2b35b1955068de3d40db85ecc42bd6ff501468bc76d7 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/universe/i/intel-mediasdk/intel-mediasdk_22.3.0-1.debian.tar.xz + sha256: 024d98d2f63443d2765a90cfe997d104e7b897694889f199ca8fb4d9ffdcf1dc + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done - name: cuda disabled: false @@ -100,20 +166,20 @@ modules: - chmod u+x ./cuda.run - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR - - rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2 + - rm -r $FLATPAK_DEST/cuda/nsight-systems-* - rm ./cuda.run sources: - type: file only-arches: - x86_64 - url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run - sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a + url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run + sha256: 9223c4af3aebe4a7bbed9abd9b163b03a1b34b855fbc2b4a0d1b706ac09a5a16 dest-filename: cuda.run - type: file only-arches: - aarch64 - url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length - sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8 + url: https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux_sbsa.run # yamllint disable-line rule:line-length + sha256: e6e9a8d31163c9776b5e313fd7590877c5684e1ecddee741154f95704d4ed27c dest-filename: cuda.run - name: numactl @@ -124,6 +190,12 @@ modules: - type: archive url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14.orig.tar.gz sha256: 1ee27abd07ff6ba140aaf9bc6379b37825e54496e01d6f7343330cf1a4487035 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14-3ubuntu2.debian.tar.xz + sha256: 49089e5be5367f6367f8b0389d1d523944432607783b53f0605705792e1015ee + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done cleanup: - "/bin" From f44ae4cc8dddc0f7fae40af500716917ccbaa18e Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Mon, 16 Jan 2023 03:20:10 +0000 Subject: [PATCH 15/51] nvenc: add nvidia preset migration guide to docs (#685) --- docs/source/about/advanced_usage.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 98daa7fc..ee1776c6 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -844,7 +844,8 @@ nv_preset **Description** The encoder preset to use. - .. Note:: This option only applies when using nvenc `encoder`_. + .. Note:: This option only applies when using nvenc `encoder`_. For more information on the presets, see + `nvenc preset migration guide `_. **Choices** From e88dec22ee7800fa0df3feeeb289ac420e433719 Mon Sep 17 00:00:00 2001 From: Brad Richardson Date: Mon, 16 Jan 2023 07:31:03 -0500 Subject: [PATCH 16/51] Link mfx on Linux (#781) --- CMakeLists.txt | 3 ++- docker/fedora-36.dockerfile | 4 +--- docker/fedora-37.dockerfile | 4 +--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a764b77a..db58471d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -458,12 +458,13 @@ elseif(APPLE) set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-macos-x86_64") endif() else() + set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 vdpau X11) if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-linux-aarch64") else() set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/ffmpeg-linux-x86_64") + list(APPEND FFMPEG_PLATFORM_LIBRARIES mfx) endif() - set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 vdpau X11) endif() set(FFMPEG_INCLUDE_DIRS ${FFMPEG_PREPARED_BINARIES}/include) diff --git a/docker/fedora-36.dockerfile b/docker/fedora-36.dockerfile index 4090382f..f566db75 100644 --- a/docker/fedora-36.dockerfile +++ b/docker/fedora-36.dockerfile @@ -46,9 +46,7 @@ dnf -y install \ wget-1.21.3* \ which-2.21* if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then - apt-get install -y --no-install-recommends \ - # libmfx-devel is not listed for fedora 36/37 - https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm + dnf -y install intel-mediasdk-devel-22.3.0* fi dnf clean all rm -rf /var/cache/yum diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile index 2a49dc3e..7312e95f 100644 --- a/docker/fedora-37.dockerfile +++ b/docker/fedora-37.dockerfile @@ -46,9 +46,7 @@ dnf -y install \ wget-1.21.3* \ which-2.21* if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then - apt-get install -y --no-install-recommends \ - # libmfx-devel is not listed for fedora 36/37 - https://kojipkgs.fedoraproject.org//packages/libmfx/1.25/4.el8/x86_64/libmfx-devel-1.25-4.el8.x86_64.rpm + dnf -y install intel-mediasdk-devel-22.4.4* fi dnf clean all rm -rf /var/cache/yum From 42f6634e85729dc30f3da094aa45592d568fcfc1 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Mon, 16 Jan 2023 12:52:09 -0500 Subject: [PATCH 17/51] Bump ffmpeg (#769) --- third-party/ffmpeg-linux-aarch64 | 2 +- third-party/ffmpeg-linux-x86_64 | 2 +- third-party/ffmpeg-macos-aarch64 | 2 +- third-party/ffmpeg-macos-x86_64 | 2 +- third-party/ffmpeg-windows-x86_64 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/third-party/ffmpeg-linux-aarch64 b/third-party/ffmpeg-linux-aarch64 index 0038ca37..12dfcbe5 160000 --- a/third-party/ffmpeg-linux-aarch64 +++ b/third-party/ffmpeg-linux-aarch64 @@ -1 +1 @@ -Subproject commit 0038ca370852c3dcedf4400ab9760bd9a791ed53 +Subproject commit 12dfcbe5f3b2af81c4333116c8a3125a7d6f5c0b diff --git a/third-party/ffmpeg-linux-x86_64 b/third-party/ffmpeg-linux-x86_64 index 7ab578f9..09011c45 160000 --- a/third-party/ffmpeg-linux-x86_64 +++ b/third-party/ffmpeg-linux-x86_64 @@ -1 +1 @@ -Subproject commit 7ab578f9808e71eedc7a99d0515960b716d65eb7 +Subproject commit 09011c45a76903e7c7fea28133315298e00db1cf diff --git a/third-party/ffmpeg-macos-aarch64 b/third-party/ffmpeg-macos-aarch64 index 5962a242..9bc91958 160000 --- a/third-party/ffmpeg-macos-aarch64 +++ b/third-party/ffmpeg-macos-aarch64 @@ -1 +1 @@ -Subproject commit 5962a2422268739e106ddecb2d6c1d7ef7440192 +Subproject commit 9bc919589b8d3ed475bca14b4d749da210ea3efd diff --git a/third-party/ffmpeg-macos-x86_64 b/third-party/ffmpeg-macos-x86_64 index e227b640..4f572c8d 160000 --- a/third-party/ffmpeg-macos-x86_64 +++ b/third-party/ffmpeg-macos-x86_64 @@ -1 +1 @@ -Subproject commit e227b64019c23e644b66209f2716b5e96a7bd36a +Subproject commit 4f572c8d1408a8220d0f3464ead2f199a7d69abb diff --git a/third-party/ffmpeg-windows-x86_64 b/third-party/ffmpeg-windows-x86_64 index 4643082e..843be4fa 160000 --- a/third-party/ffmpeg-windows-x86_64 +++ b/third-party/ffmpeg-windows-x86_64 @@ -1 +1 @@ -Subproject commit 4643082e9b3e6ee8cfe11289e89457ba1deaf168 +Subproject commit 843be4fa79fda6a633977edece4ec35b73d884b4 From fa14b6ead7d2b7b11d466e4444631d9480aef492 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 16 Jan 2023 17:17:04 -0600 Subject: [PATCH 18/51] Network performance optimizations (#771) --- src/platform/common.h | 22 +++ src/platform/linux/misc.cpp | 211 +++++++++++++++++++++++++++ src/platform/macos/misc.cpp | 12 ++ src/platform/windows/misc.cpp | 260 ++++++++++++++++++++++++++++++++++ src/rtsp.cpp | 4 + src/stream.cpp | 40 +++++- src/stream.h | 2 + 7 files changed, 549 insertions(+), 2 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index be94673d..b9558484 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -23,6 +23,11 @@ struct AVHWFramesContext; // Forward declarations of boost classes to avoid having to include boost headers // here, which results in issues with Windows.h and WinSock2.h include order. namespace boost { +namespace asio { +namespace ip { +class address; +} // namespace ip +} // namespace asio namespace filesystem { class path; } @@ -335,6 +340,23 @@ void streaming_will_stop(); bool restart_supported(); bool restart(); +struct batched_send_info_t { + const char *buffer; + size_t block_size; + size_t block_count; + + std::uintptr_t native_socket; + boost::asio::ip::address &target_address; + uint16_t target_port; +}; +bool send_batch(batched_send_info_t &send_info); + +enum class qos_data_type_e : int { + audio, + video +}; +std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type); + input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y); diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 35e68b96..be606228 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include "src/main.h" #include "src/platform/common.h" +#include #include #ifdef __GNUC__ @@ -175,6 +177,215 @@ bool restart() { return false; } +bool send_batch(batched_send_info_t &send_info) { + auto sockfd = (int)send_info.native_socket; + + // Convert the target address into a sockaddr + struct sockaddr_in saddr_v4 = {}; + struct sockaddr_in6 saddr_v6 = {}; + struct sockaddr *addr; + socklen_t addr_len; + if(send_info.target_address.is_v6()) { + auto address_v6 = send_info.target_address.to_v6(); + + saddr_v6.sin6_family = AF_INET6; + saddr_v6.sin6_port = htons(send_info.target_port); + saddr_v6.sin6_scope_id = address_v6.scope_id(); + + auto addr_bytes = address_v6.to_bytes(); + memcpy(&saddr_v6.sin6_addr, addr_bytes.data(), sizeof(saddr_v6.sin6_addr)); + + addr = (struct sockaddr *)&saddr_v6; + addr_len = sizeof(saddr_v6); + } + else { + auto address_v4 = send_info.target_address.to_v4(); + + saddr_v4.sin_family = AF_INET; + saddr_v4.sin_port = htons(send_info.target_port); + + auto addr_bytes = address_v4.to_bytes(); + memcpy(&saddr_v4.sin_addr, addr_bytes.data(), sizeof(saddr_v4.sin_addr)); + + addr = (struct sockaddr *)&saddr_v4; + addr_len = sizeof(saddr_v4); + } + +#ifdef UDP_SEGMENT + { + struct msghdr msg = {}; + struct iovec iov = {}; + union { + char buf[CMSG_SPACE(sizeof(uint16_t))]; + struct cmsghdr alignment; + } cmbuf; + + // UDP GSO on Linux currently only supports sending 64K or 64 segments at a time + size_t seg_index = 0; + const size_t seg_max = 65536 / 1500; + while(seg_index < send_info.block_count) { + iov.iov_base = (void *)&send_info.buffer[seg_index * send_info.block_size]; + iov.iov_len = send_info.block_size * std::min(send_info.block_count - seg_index, seg_max); + + msg.msg_name = addr; + msg.msg_namelen = addr_len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + // We should not use GSO if the data is <= one full block size + if(iov.iov_len > send_info.block_size) { + msg.msg_control = cmbuf.buf; + msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + + // Enable GSO to perform segmentation of our buffer for us + auto cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *((uint16_t *)CMSG_DATA(cm)) = send_info.block_size; + } + else { + msg.msg_control = nullptr; + msg.msg_controllen = 0; + } + + // This will fail if GSO is not available, so we will fall back to non-GSO if + // it's the first sendmsg() call. On subsequent calls, we will treat errors as + // actual failures and return to the caller. + auto bytes_sent = sendmsg(sockfd, &msg, 0); + if(bytes_sent < 0) { + // If there's no send buffer space, wait for some to be available + if(errno == EAGAIN) { + struct pollfd pfd; + + pfd.fd = sockfd; + pfd.events = POLLOUT; + + if(poll(&pfd, 1, -1) != 1) { + BOOST_LOG(warning) << "poll() failed: "sv << errno; + break; + } + + // Try to send again + continue; + } + + break; + } + + seg_index += bytes_sent / send_info.block_size; + } + + // If we sent something, return the status and don't fall back to the non-GSO path. + if(seg_index != 0) { + return seg_index >= send_info.block_count; + } + } +#endif + + { + // If GSO is not supported, use sendmmsg() instead. + struct mmsghdr msgs[send_info.block_count]; + struct iovec iovs[send_info.block_count]; + for(size_t i = 0; i < send_info.block_count; i++) { + iovs[i] = {}; + iovs[i].iov_base = (void *)&send_info.buffer[i * send_info.block_size]; + iovs[i].iov_len = send_info.block_size; + + msgs[i] = {}; + msgs[i].msg_hdr.msg_name = addr; + msgs[i].msg_hdr.msg_namelen = addr_len; + msgs[i].msg_hdr.msg_iov = &iovs[i]; + msgs[i].msg_hdr.msg_iovlen = 1; + } + + // Call sendmmsg() until all messages are sent + size_t blocks_sent = 0; + while(blocks_sent < send_info.block_count) { + int msgs_sent = sendmmsg(sockfd, &msgs[blocks_sent], send_info.block_count - blocks_sent, 0); + if(msgs_sent < 0) { + // If there's no send buffer space, wait for some to be available + if(errno == EAGAIN) { + struct pollfd pfd; + + pfd.fd = sockfd; + pfd.events = POLLOUT; + + if(poll(&pfd, 1, -1) != 1) { + BOOST_LOG(warning) << "poll() failed: "sv << errno; + break; + } + + // Try to send again + continue; + } + + BOOST_LOG(warning) << "sendmmsg() failed: "sv << errno; + return false; + } + + blocks_sent += msgs_sent; + } + + return true; + } +} + +class qos_t : public deinit_t { +public: + qos_t(int sockfd, int level, int option) : sockfd(sockfd), level(level), option(option) {} + + virtual ~qos_t() { + int reset_val = -1; + if(setsockopt(sockfd, level, option, &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset IP TOS: "sv << errno; + } + } + +private: + int sockfd; + int level; + int option; +}; + +std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + int sockfd = (int)native_socket; + + int level; + int option; + if(address.is_v6()) { + level = SOL_IPV6; + option = IPV6_TCLASS; + } + else { + level = SOL_IP; + option = IP_TOS; + } + + // The specific DSCP values here are chosen to be consistent with Windows + int dscp; + switch(data_type) { + case qos_data_type_e::video: + dscp = 40; + break; + case qos_data_type_e::audio: + dscp = 56; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int)data_type; + return nullptr; + } + + // Shift to put the DSCP value in the correct position in the TOS field + dscp <<= 2; + + if(setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) < 0) { + return nullptr; + } + + return std::make_unique(sockfd, level, option); +} + namespace source { enum source_e : std::size_t { #ifdef SUNSHINE_BUILD_CUDA diff --git a/src/platform/macos/misc.cpp b/src/platform/macos/misc.cpp index eb99de78..199471e9 100644 --- a/src/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -153,6 +153,18 @@ bool restart() { return false; } +bool send_batch(batched_send_info_t &send_info) { + // Fall back to unbatched send calls + return false; +} + +std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + // Unimplemented + // + // NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely! + return nullptr; +} + } // namespace platf namespace dyn { diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 9b6867da..9c1307b1 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -4,6 +4,7 @@ #include #include +#include #include // prevent clang format from "optimizing" the header include order @@ -16,12 +17,27 @@ #include #include #include +#include // clang-format on #include "src/main.h" #include "src/platform/common.h" #include "src/utility.h" +// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK +#ifndef UDP_SEND_MSG_SIZE +#define UDP_SEND_MSG_SIZE 2 +#endif + +// MinGW headers are missing qWAVE stuff +typedef UINT32 QOS_FLOWID, *PQOS_FLOWID; +#define QOS_NON_ADAPTIVE_FLOW 0x00000002 +#include + +#ifndef WLAN_API_MAKE_VERSION +#define WLAN_API_MAKE_VERSION(_major, _minor) (((DWORD)(_minor)) << 16 | (_major)) +#endif + namespace bp = boost::process; using namespace std::literals; @@ -31,6 +47,20 @@ using adapteraddrs_t = util::c_ptr; bool enabled_mouse_keys = false; MOUSEKEYS previous_mouse_keys_state; +HANDLE qos_handle = nullptr; + +decltype(QOSCreateHandle) *fn_QOSCreateHandle = nullptr; +decltype(QOSAddSocketToFlow) *fn_QOSAddSocketToFlow = nullptr; +decltype(QOSRemoveSocketFromFlow) *fn_QOSRemoveSocketFromFlow = nullptr; + +HANDLE wlan_handle = nullptr; + +decltype(WlanOpenHandle) *fn_WlanOpenHandle = nullptr; +decltype(WlanCloseHandle) *fn_WlanCloseHandle = nullptr; +decltype(WlanFreeMemory) *fn_WlanFreeMemory = nullptr; +decltype(WlanEnumInterfaces) *fn_WlanEnumInterfaces = nullptr; +decltype(WlanSetInterface) *fn_WlanSetInterface = nullptr; + std::filesystem::path appdata() { WCHAR sunshine_path[MAX_PATH]; GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_path)); @@ -507,6 +537,35 @@ void adjust_thread_priority(thread_priority_e priority) { } void streaming_will_start() { + static std::once_flag load_wlanapi_once_flag; + std::call_once(load_wlanapi_once_flag, []() { + // wlanapi.dll is not installed by default on Windows Server, so we load it dynamically + HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if(!wlanapi) { + BOOST_LOG(debug) << "wlanapi.dll is not available on this OS"sv; + return; + } + + fn_WlanOpenHandle = (decltype(fn_WlanOpenHandle))GetProcAddress(wlanapi, "WlanOpenHandle"); + fn_WlanCloseHandle = (decltype(fn_WlanCloseHandle))GetProcAddress(wlanapi, "WlanCloseHandle"); + fn_WlanFreeMemory = (decltype(fn_WlanFreeMemory))GetProcAddress(wlanapi, "WlanFreeMemory"); + fn_WlanEnumInterfaces = (decltype(fn_WlanEnumInterfaces))GetProcAddress(wlanapi, "WlanEnumInterfaces"); + fn_WlanSetInterface = (decltype(fn_WlanSetInterface))GetProcAddress(wlanapi, "WlanSetInterface"); + + if(!fn_WlanOpenHandle || !fn_WlanCloseHandle || !fn_WlanFreeMemory || !fn_WlanEnumInterfaces || !fn_WlanSetInterface) { + BOOST_LOG(error) << "wlanapi.dll is missing exports?"sv; + + fn_WlanOpenHandle = nullptr; + fn_WlanCloseHandle = nullptr; + fn_WlanFreeMemory = nullptr; + fn_WlanEnumInterfaces = nullptr; + fn_WlanSetInterface = nullptr; + + FreeLibrary(wlanapi); + return; + } + }); + // Enable MMCSS scheduling for DWM DwmEnableMMCSS(true); @@ -516,6 +575,39 @@ void streaming_will_start() { // Promote ourselves to high priority class SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + // Enable low latency mode on all connected WLAN NICs if wlanapi.dll is available + if(fn_WlanOpenHandle) { + DWORD negotiated_version; + + if(fn_WlanOpenHandle(WLAN_API_MAKE_VERSION(2, 0), nullptr, &negotiated_version, &wlan_handle) == ERROR_SUCCESS) { + PWLAN_INTERFACE_INFO_LIST wlan_interface_list; + + if(fn_WlanEnumInterfaces(wlan_handle, nullptr, &wlan_interface_list) == ERROR_SUCCESS) { + for(DWORD i = 0; i < wlan_interface_list->dwNumberOfItems; i++) { + if(wlan_interface_list->InterfaceInfo[i].isState == wlan_interface_state_connected) { + // Enable media streaming mode for 802.11 wireless interfaces to reduce latency and + // unneccessary background scanning operations that cause packet loss and jitter. + // + // https://docs.microsoft.com/en-us/windows-hardware/drivers/network/oid-wdi-set-connection-quality + // https://docs.microsoft.com/en-us/previous-versions/windows/hardware/wireless/native-802-11-media-streaming + BOOL value = TRUE; + auto error = fn_WlanSetInterface(wlan_handle, &wlan_interface_list->InterfaceInfo[i].InterfaceGuid, + wlan_intf_opcode_media_streaming_mode, sizeof(value), &value, nullptr); + if(error == ERROR_SUCCESS) { + BOOST_LOG(info) << "WLAN interface "sv << i << " is now in low latency mode"sv; + } + } + } + + fn_WlanFreeMemory(wlan_interface_list); + } + else { + fn_WlanCloseHandle(wlan_handle, nullptr); + wlan_handle = NULL; + } + } + } + // If there is no mouse connected, enable Mouse Keys to force the cursor to appear if(!GetSystemMetrics(SM_MOUSEPRESENT)) { BOOST_LOG(info) << "A mouse was not detected. Sunshine will enable Mouse Keys while streaming to force the mouse cursor to appear."; @@ -556,6 +648,12 @@ void streaming_will_stop() { // Disable MMCSS scheduling for DWM DwmEnableMMCSS(false); + // Closing our WLAN client handle will undo our optimizations + if(wlan_handle != nullptr) { + fn_WlanCloseHandle(wlan_handle, nullptr); + wlan_handle = nullptr; + } + // Restore Mouse Keys back to the previous settings if we turned it on if(enabled_mouse_keys) { enabled_mouse_keys = false; @@ -578,4 +676,166 @@ bool restart() { return true; } +SOCKADDR_IN to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) { + SOCKADDR_IN saddr_v4 = {}; + + saddr_v4.sin_family = AF_INET; + saddr_v4.sin_port = htons(port); + + auto addr_bytes = address.to_bytes(); + memcpy(&saddr_v4.sin_addr, addr_bytes.data(), sizeof(saddr_v4.sin_addr)); + + return saddr_v4; +} + +SOCKADDR_IN6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) { + SOCKADDR_IN6 saddr_v6 = {}; + + saddr_v6.sin6_family = AF_INET6; + saddr_v6.sin6_port = htons(port); + saddr_v6.sin6_scope_id = address.scope_id(); + + auto addr_bytes = address.to_bytes(); + memcpy(&saddr_v6.sin6_addr, addr_bytes.data(), sizeof(saddr_v6.sin6_addr)); + + return saddr_v6; +} + +// Use UDP segmentation offload if it is supported by the OS. If the NIC is capable, this will use +// hardware acceleration to reduce CPU usage. Support for USO was introduced in Windows 10 20H1. +bool send_batch(batched_send_info_t &send_info) { + WSAMSG msg; + + // Convert the target address into a SOCKADDR + SOCKADDR_IN saddr_v4; + SOCKADDR_IN6 saddr_v6; + if(send_info.target_address.is_v6()) { + saddr_v6 = to_sockaddr(send_info.target_address.to_v6(), send_info.target_port); + + msg.name = (PSOCKADDR)&saddr_v6; + msg.namelen = sizeof(saddr_v6); + } + else { + saddr_v4 = to_sockaddr(send_info.target_address.to_v4(), send_info.target_port); + + msg.name = (PSOCKADDR)&saddr_v4; + msg.namelen = sizeof(saddr_v4); + } + + WSABUF buf; + buf.buf = (char *)send_info.buffer; + buf.len = send_info.block_size * send_info.block_count; + + msg.lpBuffers = &buf; + msg.dwBufferCount = 1; + msg.dwFlags = 0; + + char cmbuf[WSA_CMSG_SPACE(sizeof(DWORD))]; + msg.Control.buf = cmbuf; + msg.Control.len = 0; + + if(send_info.block_count > 1) { + msg.Control.len += WSA_CMSG_SPACE(sizeof(DWORD)); + + auto cm = WSA_CMSG_FIRSTHDR(&msg); + cm->cmsg_level = IPPROTO_UDP; + cm->cmsg_type = UDP_SEND_MSG_SIZE; + cm->cmsg_len = WSA_CMSG_LEN(sizeof(DWORD)); + *((DWORD *)WSA_CMSG_DATA(cm)) = send_info.block_size; + } + + // If USO is not supported, this will fail and the caller will fall back to unbatched sends. + DWORD bytes_sent; + return WSASendMsg((SOCKET)send_info.native_socket, &msg, 1, &bytes_sent, nullptr, nullptr) != SOCKET_ERROR; +} + +class qos_t : public deinit_t { +public: + qos_t(QOS_FLOWID flow_id) : flow_id(flow_id) {} + + virtual ~qos_t() { + if(!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET)NULL, flow_id, 0)) { + auto winerr = GetLastError(); + BOOST_LOG(warning) << "QOSRemoveSocketFromFlow() failed: "sv << winerr; + } + } + +private: + QOS_FLOWID flow_id; +}; + +std::unique_ptr enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + SOCKADDR_IN saddr_v4; + SOCKADDR_IN6 saddr_v6; + PSOCKADDR dest_addr; + + static std::once_flag load_qwave_once_flag; + std::call_once(load_qwave_once_flag, []() { + // qWAVE is not installed by default on Windows Server, so we load it dynamically + HMODULE qwave = LoadLibraryExA("qwave.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if(!qwave) { + BOOST_LOG(debug) << "qwave.dll is not available on this OS"sv; + return; + } + + fn_QOSCreateHandle = (decltype(fn_QOSCreateHandle))GetProcAddress(qwave, "QOSCreateHandle"); + fn_QOSAddSocketToFlow = (decltype(fn_QOSAddSocketToFlow))GetProcAddress(qwave, "QOSAddSocketToFlow"); + fn_QOSRemoveSocketFromFlow = (decltype(fn_QOSRemoveSocketFromFlow))GetProcAddress(qwave, "QOSRemoveSocketFromFlow"); + + if(!fn_QOSCreateHandle || !fn_QOSAddSocketToFlow || !fn_QOSRemoveSocketFromFlow) { + BOOST_LOG(error) << "qwave.dll is missing exports?"sv; + + fn_QOSCreateHandle = nullptr; + fn_QOSAddSocketToFlow = nullptr; + fn_QOSRemoveSocketFromFlow = nullptr; + + FreeLibrary(qwave); + return; + } + + QOS_VERSION qos_version { 1, 0 }; + if(!fn_QOSCreateHandle(&qos_version, &qos_handle)) { + auto winerr = GetLastError(); + BOOST_LOG(warning) << "QOSCreateHandle() failed: "sv << winerr; + return; + } + }); + + // If qWAVE is unavailable, just return + if(!fn_QOSAddSocketToFlow || !qos_handle) { + return nullptr; + } + + if(address.is_v6()) { + saddr_v6 = to_sockaddr(address.to_v6(), port); + dest_addr = (PSOCKADDR)&saddr_v6; + } + else { + saddr_v4 = to_sockaddr(address.to_v4(), port); + dest_addr = (PSOCKADDR)&saddr_v4; + } + + QOS_TRAFFIC_TYPE traffic_type; + switch(data_type) { + case qos_data_type_e::audio: + traffic_type = QOSTrafficTypeVoice; + break; + case qos_data_type_e::video: + traffic_type = QOSTrafficTypeAudioVideo; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int)data_type; + return nullptr; + } + + QOS_FLOWID flow_id = 0; + if(!fn_QOSAddSocketToFlow(qos_handle, (SOCKET)native_socket, dest_addr, traffic_type, QOS_NON_ADAPTIVE_FLOW, &flow_id)) { + auto winerr = GetLastError(); + BOOST_LOG(warning) << "QOSAddSocketToFlow() failed: "sv << winerr; + return nullptr; + } + + return std::make_unique(flow_id); +} + } // namespace platf \ No newline at end of file diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 9f2a7bbd..791d6888 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -613,6 +613,8 @@ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { args.try_emplace("x-nv-general.useReliableUdp"sv, "1"sv); args.try_emplace("x-nv-vqos[0].fec.minRequiredFecPackets"sv, "0"sv); args.try_emplace("x-nv-general.featureFlags"sv, "135"sv); + args.try_emplace("x-nv-vqos[0].qosTrafficType"sv, "5"sv); + args.try_emplace("x-nv-aqos.qosTrafficType"sv, "4"sv); config_t config; @@ -629,6 +631,8 @@ void cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv)); config.minRequiredFecPackets = util::from_view(args.at("x-nv-vqos[0].fec.minRequiredFecPackets"sv)); config.featureFlags = util::from_view(args.at("x-nv-general.featureFlags"sv)); + config.audioQosType = util::from_view(args.at("x-nv-aqos.qosTrafficType"sv)); + config.videoQosType = util::from_view(args.at("x-nv-vqos[0].qosTrafficType"sv)); config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv)); config.monitor.width = util::from_view(args.at("x-nv-video[0].clientViewportWd"sv)); diff --git a/src/stream.cpp b/src/stream.cpp index 78667aa1..67ad8a07 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -287,6 +287,7 @@ struct session_t { int lowseq; udp::endpoint peer; safe::mail_raw_t::event_t idr_events; + std::unique_ptr qos; } video; struct { @@ -302,6 +303,7 @@ struct session_t { util::buffer_t shards_p; audio_fec_packet_t fec_packet; + std::unique_ptr qos; } audio; struct { @@ -762,7 +764,10 @@ void controlBroadcastThread(control_server_t *server) { if(session->state.load(std::memory_order_acquire) == session::state_e::STOPPING) { pos = server->_map_addr_session->erase(pos); - enet_peer_disconnect_now(session->control.peer, 0); + if(session->control.peer) { + enet_peer_disconnect_now(session->control.peer, 0); + } + session->controlEnd.raise(true); continue; } @@ -1036,8 +1041,25 @@ void videoBroadcastThread(udp::socket &sock) { inspect->packet.multiFecBlocks = (blockIndex << 4) | lastBlockIndex; inspect->packet.frameIndex = av_packet->pts; + } - sock.send_to(asio::buffer(shards[x]), session->video.peer); + auto peer_address = session->video.peer.address(); + auto batch_info = platf::batched_send_info_t { + shards.shards.begin(), + shards.blocksize, + shards.nr_shards, + (uintptr_t)sock.native_handle(), + peer_address, + session->video.peer.port(), + }; + + // Use a batched send if it's supported on this platform + if(!platf::send_batch(batch_info)) { + // Batched send is not available, so send each packet individually + BOOST_LOG(verbose) << "Falling back to unbatched send"sv; + for(auto x = 0; x < shards.size(); ++x) { + sock.send_to(asio::buffer(shards[x]), session->video.peer); + } } if(av_packet->flags & AV_PKT_FLAG_KEY) { @@ -1318,6 +1340,13 @@ void videoThread(session_t *session) { return; } + // Enable QoS tagging on video traffic if requested by the client + if(session->config.videoQosType) { + auto address = session->video.peer.address(); + session->video.qos = std::move(platf::enable_socket_qos(ref->video_sock.native_handle(), address, + session->video.peer.port(), platf::qos_data_type_e::video)); + } + BOOST_LOG(debug) << "Start capturing Video"sv; video::capture(session->mail, session->config.monitor, session); } @@ -1335,6 +1364,13 @@ void audioThread(session_t *session) { return; } + // Enable QoS tagging on audio traffic if requested by the client + if(session->config.audioQosType) { + auto address = session->audio.peer.address(); + session->audio.qos = std::move(platf::enable_socket_qos(ref->audio_sock.native_handle(), address, + session->audio.peer.port(), platf::qos_data_type_e::audio)); + } + BOOST_LOG(debug) << "Start capturing Audio"sv; audio::capture(session->mail, session->config.audio, session); } diff --git a/src/stream.h b/src/stream.h index 8e054aa9..02b30413 100644 --- a/src/stream.h +++ b/src/stream.h @@ -23,6 +23,8 @@ struct config_t { int minRequiredFecPackets; int featureFlags; int controlProtocolType; + int audioQosType; + int videoQosType; std::optional gcmap; }; From 4ef97c755a745f666f4d65c74080c7a90464e150 Mon Sep 17 00:00:00 2001 From: KuleRucket Date: Tue, 17 Jan 2023 04:19:26 +0100 Subject: [PATCH 19/51] Fix multi-character character constant compiler warning (#765) Co-authored-by: KuleRucket --- src/platform/linux/wlgrab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 20d89572..ce596e8c 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -167,7 +167,7 @@ public: int w, h; gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &h); - BOOST_LOG(debug) << "width and height: w "sv << w << ' h ' << h; + BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h; gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data); gl::ctx.BindTexture(GL_TEXTURE_2D, 0); From 7f6383833c58b149f65a73d16b568c2949f2bb3d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 17 Jan 2023 08:09:15 -0600 Subject: [PATCH 20/51] Replace frame capture busy loop with waitable timer (#778) --- src/platform/windows/display.h | 13 +++--- src/platform/windows/display_base.cpp | 57 +++++++++++++++++++++++++++ src/platform/windows/display_ram.cpp | 31 --------------- src/platform/windows/display_vram.cpp | 31 --------------- 4 files changed, 63 insertions(+), 69 deletions(-) diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index a788df51..f52f1b31 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -116,6 +116,7 @@ public: class display_base_t : public display_t { public: int init(int framerate, const std::string &display_name); + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; std::chrono::nanoseconds delay; @@ -147,15 +148,14 @@ protected: const char *dxgi_format_to_string(DXGI_FORMAT format); - virtual int complete_img(img_t *img, bool dummy) = 0; - virtual std::vector get_supported_sdr_capture_formats() = 0; + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0; + virtual int complete_img(img_t *img, bool dummy) = 0; + virtual std::vector get_supported_sdr_capture_formats() = 0; }; class display_ram_t : public display_base_t { public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); - + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; std::shared_ptr alloc_img() override; int dummy_img(img_t *img) override; @@ -171,8 +171,7 @@ public: class display_vram_t : public display_base_t, public std::enable_shared_from_this { public: - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; - capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible); + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; std::shared_ptr alloc_img() override; int dummy_img(img_t *img_base) override; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 9f8c2705..6f518f7c 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -79,6 +79,63 @@ duplication_t::~duplication_t() { release_frame(); } +capture_e display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + // Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+) + HANDLE timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + if(!timer) { + timer = CreateWaitableTimerEx(nullptr, nullptr, 0, TIMER_ALL_ACCESS); + if(!timer) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to create timer: "sv << winerr; + return capture_e::error; + } + } + + auto close_timer = util::fail_guard([timer]() { + CloseHandle(timer); + }); + + while(img) { + auto wait_time_us = std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now()).count(); + + // If the wait time is between 1 us and 1 second, wait the specified time + // and offset the next frame time from the exact current frame time target. + if(wait_time_us > 0 && wait_time_us < 1000000) { + LARGE_INTEGER due_time { .QuadPart = -10LL * wait_time_us }; + SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false); + WaitForSingleObject(timer, INFINITE); + next_frame += delay; + } + else { + // If the wait time is negative (meaning the frame is past due) or the + // computed wait time is beyond a second (meaning possible clock issues), + // just capture the frame now and resynchronize the frame interval with + // the current time. + next_frame = std::chrono::steady_clock::now() + delay; + } + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + img = snapshot_cb(img, false); + break; + case platf::capture_e::ok: + img = snapshot_cb(img, true); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; +} + int display_base_t::init(int framerate, const std::string &display_name) { std::once_flag windows_cpp_once_flag; diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index c5ce6dc3..29ebcefb 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -165,37 +165,6 @@ void blend_cursor(const cursor_t &cursor, img_t &img) { } } -capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - img = snapshot_cb(img, false); - std::this_thread::sleep_for(1ms); - break; - case platf::capture_e::ok: - img = snapshot_cb(img, true); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { auto img = (img_t *)img_base; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 22d65be3..f3203353 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -698,37 +698,6 @@ public: device_ctx_t device_ctx; }; -capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) { - auto next_frame = std::chrono::steady_clock::now(); - - while(img) { - auto now = std::chrono::steady_clock::now(); - while(next_frame > now) { - now = std::chrono::steady_clock::now(); - } - next_frame = now + delay; - - auto status = snapshot(img.get(), 1000ms, *cursor); - switch(status) { - case platf::capture_e::reinit: - case platf::capture_e::error: - return status; - case platf::capture_e::timeout: - img = snapshot_cb(img, false); - std::this_thread::sleep_for(1ms); - break; - case platf::capture_e::ok: - img = snapshot_cb(img, true); - break; - default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; - return status; - } - } - - return capture_e::ok; -} - bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) { // This cursor image may not be used if(cursor_img.size() == 0) { From 1fbbab9df771c02d9c2d78318d9ed213dc16e35e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 17 Jan 2023 18:12:14 -0600 Subject: [PATCH 21/51] QSV performance optimization and HEVC hang fix (#786) --- src/video.cpp | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 0d0ea2da..a3cf5b6a 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -75,13 +75,6 @@ enum class profile_hevc_e : int { main = 1, main_10 = 2, }; - -enum class multiframe_mode_e : int { - mf_default = 0, - mf_disabled = 1, - mf_auto = 2, - mf_manual = 3, -}; } // namespace qsv @@ -262,6 +255,7 @@ enum flag_e { SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P CBR_WITH_VBR = 0x10, // Use a VBR rate control mode to simulate CBR RELAXED_COMPLIANCE = 0x20, // Use FF_COMPLIANCE_UNOFFICIAL compliance mode + NO_RC_BUF_LIMIT = 0x40, // Don't set rc_buffer_size }; struct encoder_t { @@ -494,11 +488,9 @@ static encoder_t quicksync { { "forced_idr"s, 1 }, { "async_depth"s, 1 }, { "low_delay_brc"s, 1 }, + { "low_power"s, 1 }, { "recovery_point_sei"s, 0 }, - { "vcm"s, 1 }, { "pic_timing_sei"s, 0 }, - { "max_dec_frame_buffering"s, 1 }, - { "mfmode"s, (int)qsv::multiframe_mode_e::mf_disabled }, }, // SDR-specific options { @@ -519,11 +511,11 @@ static encoder_t quicksync { { "forced_idr"s, 1 }, { "async_depth"s, 1 }, { "low_delay_brc"s, 1 }, + { "low_power"s, 1 }, { "recovery_point_sei"s, 0 }, { "vcm"s, 1 }, { "pic_timing_sei"s, 0 }, { "max_dec_frame_buffering"s, 1 }, - { "mfmode"s, (int)qsv::multiframe_mode_e::mf_disabled }, }, // SDR-specific options { @@ -533,7 +525,7 @@ static encoder_t quicksync { std::make_optional({ "qp"s, &config::video.qp }), "h264_qsv"s, }, - PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE, + PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT, dxgi_make_hwdevice_ctx, }; @@ -1141,15 +1133,17 @@ std::optional make_session(const encoder_t &encoder, const config_t & ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; } - if(!hardware && (ctx->slices > 1 || config.videoFormat != 0)) { - // Use a larger rc_buffer_size for software encoding when slices are enabled, - // because libx264 can severely degrade quality if the buffer is too small. - // libx265 encounters this issue more frequently, so always scale the - // buffer by 1.5x for software HEVC encoding. - ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15); - } - else { - ctx->rc_buffer_size = bitrate / config.framerate; + if(!(encoder.flags & NO_RC_BUF_LIMIT)) { + if(!hardware && (ctx->slices > 1 || config.videoFormat != 0)) { + // Use a larger rc_buffer_size for software encoding when slices are enabled, + // because libx264 can severely degrade quality if the buffer is too small. + // libx265 encounters this issue more frequently, so always scale the + // buffer by 1.5x for software HEVC encoding. + ctx->rc_buffer_size = bitrate / ((config.framerate * 10) / 15); + } + else { + ctx->rc_buffer_size = bitrate / config.framerate; + } } } else if(video_format.qp) { From 49fc618682b51ed52622eab2f98c1239ebd130bd Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 17 Jan 2023 23:55:44 -0600 Subject: [PATCH 22/51] Add support for hybrid graphics systems (NVIDIA Optimus) (#782) --- CMakeLists.txt | 3 + src/platform/windows/display_base.cpp | 110 +++++++++++++++++- tools/CMakeLists.txt | 9 ++ tools/ddprobe.cpp | 160 ++++++++++++++++++++++++++ 4 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 tools/ddprobe.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index db58471d..68aa994c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -595,6 +595,9 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio) install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc) + # Mandatory tools + install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application) + # scripts install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/firewall/" DESTINATION "scripts" diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 6f518f7c..6fe9f9f7 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -6,6 +6,12 @@ #include #include +#include + +// We have to include boost/process.hpp before display.h due to WinSock.h, +// but that prevents the definition of NTSTATUS so we must define it ourself. +typedef long NTSTATUS; + #include "display.h" #include "misc.h" #include "src/config.h" @@ -16,6 +22,8 @@ namespace platf { using namespace std::literals; } namespace platf::dxgi { +namespace bp = boost::process; + capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) { auto capture_status = release_frame(); if(capture_status != capture_e::ok) { @@ -136,6 +144,96 @@ capture_e display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<: return capture_e::ok; } +bool set_gpu_preference_on_self(int preference) { + // The GPU preferences key uses app path as the value name. + WCHAR sunshine_path[MAX_PATH]; + GetModuleFileNameW(NULL, sunshine_path, ARRAYSIZE(sunshine_path)); + + WCHAR value_data[128]; + swprintf_s(value_data, L"GpuPreference=%d;", preference); + + auto status = RegSetKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\DirectX\\UserGpuPreferences", + sunshine_path, + REG_SZ, + value_data, + (wcslen(value_data) + 1) * sizeof(WCHAR)); + if(status != ERROR_SUCCESS) { + BOOST_LOG(error) << "Failed to set GPU preference: "sv << status; + return false; + } + + BOOST_LOG(info) << "Set GPU preference: "sv << preference; + return true; +} + +// On hybrid graphics systems, Windows will change the order of GPUs reported by +// DXGI in accordance with the user's GPU preference. If the selected GPU is a +// render-only device with no displays, DXGI will add virtual outputs to the +// that device to avoid confusing applications. While this works properly for most +// applications, it breaks the Desktop Duplication API because DXGI doesn't proxy +// the virtual DXGIOutput to the real GPU it is attached to. When trying to call +// DuplicateOutput() on one of these virtual outputs, it fails with DXGI_ERROR_UNSUPPORTED +// (even if you try sneaky stuff like passing the ID3D11Device for the iGPU and the +// virtual DXGIOutput from the dGPU). Because the GPU preference is once-per-process, +// we spawn a helper tool to probe for us before we set our own GPU preference. +bool probe_for_gpu_preference(const std::string &display_name) { + // If we've already been through here, there's nothing to do this time. + static bool set_gpu_preference = false; + if(set_gpu_preference) { + return true; + } + + std::string cmd = "tools\\ddprobe.exe"; + + // We start at 1 because 0 is automatic selection which can be overridden by + // the GPU driver control panel options. Since ddprobe.exe can have different + // GPU driver overrides than Sunshine.exe, we want to avoid a scenario where + // autoselection might work for ddprobe.exe but not for us. + for(int i = 1; i < 5; i++) { + // Run the probe tool + // + // Arg format: [GPU preference] [Display name] + // + // Exit codes: + // < 0 -> Error performing the probe + // 0 -> Probe failed (DD API doesn't work with that GPU preference) + // 1 -> Probe successful (DD API works) + int result; + try { + result = bp::system(cmd, std::to_string(i), display_name, bp::std_out > bp::null, bp::std_err > bp::null); + } + catch(bp::process_error &e) { + BOOST_LOG(error) << "Failed to start ddprobe.exe: "sv << e.what(); + return false; + } + + BOOST_LOG(debug) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] returned: "sv << result; + + if(result > 0) { + // We found a working GPU preference, so set ourselves to use that. + if(set_gpu_preference_on_self(i)) { + set_gpu_preference = true; + return true; + } + else { + return false; + } + } + else if(result == 0) { + // This configuration didn't work, so continue testing others + continue; + } + else { + BOOST_LOG(error) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] failed: "sv << result; + } + } + + // If none of the manual options worked, we'll try autoselection as a last-ditch effort + set_gpu_preference_on_self(0); + return false; +} + int display_base_t::init(int framerate, const std::string &display_name) { std::once_flag windows_cpp_once_flag; @@ -164,6 +262,11 @@ int display_base_t::init(int framerate, const std::string &display_name) { HRESULT status; + // We must set the GPU preference before calling any DXGI APIs! + if(!probe_for_gpu_preference(display_name)) { + BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; + } + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; @@ -577,10 +680,15 @@ std::vector display_names(mem_type_e) { std::wstring_convert, wchar_t> converter; + // We must set the GPU preference before calling any DXGI APIs! + if(!dxgi::probe_for_gpu_preference("")) { + BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; + } + dxgi::factory1_t factory; status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; + BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; return {}; } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a66bbb2c..c2121a7d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -27,3 +27,12 @@ target_link_libraries(sunshinesvc wtsapi32 ${PLATFORM_LIBRARIES}) target_compile_options(sunshinesvc PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) + +add_executable(ddprobe ddprobe.cpp) +set_target_properties(ddprobe PROPERTIES CXX_STANDARD 17) +target_link_libraries(ddprobe + ${CMAKE_THREAD_LIBS_INIT} + dxgi + d3d11 + ${PLATFORM_LIBRARIES}) +target_compile_options(ddprobe PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp new file mode 100644 index 00000000..90628c20 --- /dev/null +++ b/tools/ddprobe.cpp @@ -0,0 +1,160 @@ +#include +#include + +#include +#include +#include +#include + +#include "src/utility.h" + +using namespace std::literals; +namespace dxgi { +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; +using output1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using dup_t = util::safe_ptr>; + +} // namespace dxgi + +bool set_gpu_preference(int preference) { + // The GPU preferences key uses app path as the value name. + WCHAR executable_path[MAX_PATH]; + GetModuleFileNameW(NULL, executable_path, ARRAYSIZE(executable_path)); + + WCHAR value_data[128]; + swprintf_s(value_data, L"GpuPreference=%d;", preference); + + auto status = RegSetKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\DirectX\\UserGpuPreferences", + executable_path, + REG_SZ, + value_data, + (wcslen(value_data) + 1) * sizeof(WCHAR)); + if(status != ERROR_SUCCESS) { + std::cout << "Failed to set GPU preference: "sv << std::endl; + return false; + } + + return true; +} + +bool test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { + D3D_FEATURE_LEVEL featureLevels[] { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + dxgi::device_t device; + auto status = D3D11CreateDevice( + adapter.get(), + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), + D3D11_SDK_VERSION, + &device, + nullptr, + nullptr); + if(FAILED(status)) { + std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; + return false; + } + + dxgi::output1_t output1; + status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1); + if(FAILED(status)) { + std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl; + return false; + } + + // Check if we can use the Desktop Duplication API on this output + for(int x = 0; x < 2; ++x) { + dxgi::dup_t dup; + status = output1->DuplicateOutput((IUnknown *)device.get(), &dup); + if(SUCCEEDED(status)) { + return true; + } + Sleep(200); + } + + return false; +} + +int main(int argc, char *argv[]) { + HRESULT status; + + // Display name may be omitted + if(argc != 2 && argc != 3) { + std::cout << "ddprobe.exe [GPU preference value] [display name]"sv << std::endl; + return -1; + } + + std::wstring display_name; + if(argc == 3) { + std::wstring_convert, wchar_t> converter; + display_name = converter.from_bytes(argv[2]); + } + + // We must set the GPU preference before making any DXGI/D3D calls + if(!set_gpu_preference(atoi(argv[1]))) { + return -2; + } + + // Remove the GPU preference when we're done + auto reset_gpu = util::fail_guard([]() { + WCHAR tool_path[MAX_PATH]; + GetModuleFileNameW(NULL, tool_path, ARRAYSIZE(tool_path)); + + RegDeleteKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\DirectX\\UserGpuPreferences", + tool_path); + }); + + dxgi::factory1_t factory; + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); + if(FAILED(status)) { + std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; + return -3; + } + + dxgi::adapter_t::pointer adapter_p {}; + for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { + dxgi::adapter_t adapter { adapter_p }; + + dxgi::output_t::pointer output_p {}; + for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { + dxgi::output_t output { output_p }; + + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + + // If a display name was specified and this one doesn't match, skip it + if(!display_name.empty() && desc.DeviceName != display_name) { + continue; + } + + // If this display is not part of the desktop, we definitely can't capture it + if(!desc.AttachedToDesktop) { + continue; + } + + // We found the matching output. Test it and return the result. + return test_dxgi_duplication(adapter, output) ? 1 : 0; + } + } + + return 0; +} From c4c0413f9e6b711855705019b9858aa8c16b12e9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 18 Jan 2023 07:47:00 -0600 Subject: [PATCH 23/51] Fix streaming to multiple clients from hardware encoder on Windows (#798) --- src/platform/windows/display.h | 2 + src/platform/windows/display_vram.cpp | 86 +++++++++++++++++---------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index f52f1b31..2a3b6cc9 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -195,6 +195,8 @@ public: gpu_cursor_t cursor_xor; texture2d_t last_frame_copy; + + std::atomic next_image_id; }; } // namespace platf::dxgi diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index f3203353..9b1af4ac 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -101,16 +101,16 @@ struct img_d3d_t : public platf::img_t { render_target_t capture_rt; keyed_mutex_t capture_mutex; - // These objects are owned by the hwdevice_t's ID3D11Device - texture2d_t encoder_texture; - shader_res_t encoder_input_res; - keyed_mutex_t encoder_mutex; - // This is the shared handle used by hwdevice_t to open capture_texture HANDLE encoder_texture_handle = {}; + // Set to true if the image corresponds to a dummy texture used prior to + // the first successful capture of a desktop frame bool dummy = false; + // Unique identifier for this image + uint32_t id = 0; + virtual ~img_d3d_t() override { if(encoder_texture_handle) { CloseHandle(encoder_texture_handle); @@ -300,15 +300,16 @@ blob_t compile_vertex_shader(LPCSTR file) { class hwdevice_t : public platf::hwdevice_t { public: int convert(platf::img_t &img_base) override { - auto &img = (img_d3d_t &)img_base; + auto &img = (img_d3d_t &)img_base; + auto &img_ctx = img_ctx_map[img.id]; // Open the shared capture texture with our ID3D11Device - if(share_img(&img_base)) { + if(initialize_image_context(img, img_ctx)) { return -1; } // Acquire encoder mutex to synchronize with capture code - auto status = img.encoder_mutex->AcquireSync(0, INFINITE); + auto status = img_ctx.encoder_mutex->AcquireSync(0, INFINITE); if(status != S_OK) { BOOST_LOG(error) << "Failed to acquire encoder mutex [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -318,7 +319,7 @@ public: device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); device_ctx->PSSetShader(convert_Y_ps.get(), nullptr, 0); device_ctx->RSSetViewports(1, &outY_view); - device_ctx->PSSetShaderResources(0, 1, &img.encoder_input_res); + device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res); device_ctx->Draw(3, 0); // Artifacts start appearing on the rendered image if Sunshine doesn't flush @@ -332,7 +333,7 @@ public: device_ctx->Draw(3, 0); // Release encoder mutex to allow capture code to reuse this image - img.encoder_mutex->ReleaseSync(0); + img_ctx.encoder_mutex->ReleaseSync(0); return 0; } @@ -439,8 +440,8 @@ public: auto out_width = frame->width; auto out_height = frame->height; - float in_width = img.display->width; - float in_height = img.display->height; + float in_width = display->width; + float in_height = display->height; // Ensure aspect ratio is maintained auto scalar = std::fminf(out_width / in_width, out_height / in_height); @@ -456,13 +457,7 @@ public: // The underlying frame pool owns the texture, so we must reference it for ourselves frame_texture->AddRef(); - img.encoder_texture.reset(frame_texture); - - img.width = out_width; - img.height = out_height; - img.data = (std::uint8_t *)img.encoder_texture.get(); - img.row_pitch = out_width * 4; - img.pixel_pitch = 4; + hwframe_texture.reset(frame_texture); float info_in[16 / sizeof(float)] { 1.0f / (float)out_width_f }; //aligned to 16-byte info_scene = make_buffer(device.get(), info_in); @@ -477,7 +472,7 @@ public: D3D11_RTV_DIMENSION_TEXTURE2D }; - auto status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_Y_rt); + auto status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_Y_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -485,7 +480,7 @@ public: nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM; - status = device->CreateRenderTargetView(img.encoder_texture.get(), &nv12_rt_desc, &nv12_UV_rt); + status = device->CreateRenderTargetView(hwframe_texture.get(), &nv12_rt_desc, &nv12_UV_rt); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; return -1; @@ -590,7 +585,7 @@ public: convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), &input_layout); - img.display = std::move(display); + this->display = std::move(display); blend_disable = make_blend(device.get(), false, false); if(!blend_disable) { @@ -624,14 +619,33 @@ public: } private: - int share_img(platf::img_t *img_base) { - auto img = (img_d3d_t *)img_base; + struct encoder_img_ctx_t { + // Used to determine if the underlying texture changes. + // Not safe for actual use by the encoder! + texture2d_t::pointer capture_texture_p; + texture2d_t encoder_texture; + shader_res_t encoder_input_res; + keyed_mutex_t encoder_mutex; + + void reset() { + capture_texture_p = nullptr; + encoder_texture.reset(); + encoder_input_res.reset(); + encoder_mutex.reset(); + } + }; + + int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) { // If we've already opened the shared texture, we're done - if(img->encoder_texture) { + if(img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) { return 0; } + // Reset this image context in case it was used before with a different texture. + // Textures can change when transitioning from a dummy image to a real image. + img_ctx.reset(); + device1_t device1; auto status = device->QueryInterface(__uuidof(ID3D11Device1), (void **)&device1); if(FAILED(status)) { @@ -640,26 +654,27 @@ private: } // Open a handle to the shared texture - status = device1->OpenSharedResource1(img->encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img->encoder_texture); + status = device1->OpenSharedResource1(img.encoder_texture_handle, __uuidof(ID3D11Texture2D), (void **)&img_ctx.encoder_texture); if(FAILED(status)) { BOOST_LOG(error) << "Failed to open shared image texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } // Get the keyed mutex to synchronize with the capture code - status = img->encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img->encoder_mutex); + status = img_ctx.encoder_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)&img_ctx.encoder_mutex); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIKeyedMutex [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } // Create the SRV for the encoder texture - status = device->CreateShaderResourceView(img->encoder_texture.get(), nullptr, &img->encoder_input_res); + status = device->CreateShaderResourceView(img_ctx.encoder_texture.get(), nullptr, &img_ctx.encoder_input_res); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create shader resource view for encoding [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } + img_ctx.capture_texture_p = img.capture_texture.get(); return 0; } @@ -680,8 +695,15 @@ public: render_target_t nv12_UV_rt; // The image referenced by hwframe - // The resulting image is stored here. - img_d3d_t img; + texture2d_t hwframe_texture; + + // d3d_img_t::id -> encoder_img_ctx_t + // These store the encoder textures for each img_t that passes through + // convert(). We can't store them in the img_t itself because it is shared + // amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices). + std::map img_ctx_map; + + std::shared_ptr display; vs_t convert_UV_vs; ps_t convert_UV_ps; @@ -984,6 +1006,7 @@ std::shared_ptr display_vram_t::alloc_img() { img->width = width; img->height = height; img->display = shared_from_this(); + img->id = next_image_id++; return img; } @@ -1007,9 +1030,6 @@ int display_vram_t::complete_img(platf::img_t *img_base, bool dummy) { img->capture_texture.reset(); img->capture_rt.reset(); img->capture_mutex.reset(); - img->encoder_texture.reset(); - img->encoder_input_res.reset(); - img->encoder_mutex.reset(); img->data = nullptr; if(img->encoder_texture_handle) { CloseHandle(img->encoder_texture_handle); From c81aa99c381a3ab144f49369735924b74e00516c Mon Sep 17 00:00:00 2001 From: Lukas Senionis Date: Thu, 19 Jan 2023 08:40:12 +0200 Subject: [PATCH 24/51] Fix child process spawning on linux (#773) --- src/platform/common.h | 3 ++- src/platform/linux/misc.cpp | 18 ++++++++++++++---- src/platform/macos/misc.cpp | 18 ++++++++++++++---- src/platform/windows/misc.cpp | 5 ++++- src/process.cpp | 6 ++---- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index b9558484..89fdfe5c 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -33,6 +33,7 @@ class path; } namespace process { class child; +class group; template class basic_environment; typedef basic_environment environment; @@ -323,7 +324,7 @@ std::shared_ptr display(mem_type_e hwdevice_type, const std::string & // A list of names of displays accepted as display_name with the mem_type_e std::vector display_names(mem_type_e hwdevice_type); -boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec); +boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group); enum class thread_priority_e : int { low, diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index be606228..b5660fe9 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -145,13 +145,23 @@ std::string get_mac_address(const std::string_view &address) { return "00:00:00:00:00:00"s; } -bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) { +bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv; - if(!file) { - return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); + if(!group) { + if(!file) { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); + } + else { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec); + } } else { - return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec); + if(!file) { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group); + } + else { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group); + } } } diff --git a/src/platform/macos/misc.cpp b/src/platform/macos/misc.cpp index 199471e9..cb9c0c52 100644 --- a/src/platform/macos/misc.cpp +++ b/src/platform/macos/misc.cpp @@ -121,13 +121,23 @@ std::string get_mac_address(const std::string_view &address) { return "00:00:00:00:00:00"s; } -bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) { +bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv; - if(!file) { - return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); + if(!group) { + if(!file) { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec); + } + else { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec); + } } else { - return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec); + if(!file) { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec, *group); + } + else { + return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec, *group); + } } } diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 9c1307b1..c89b0f2c 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -375,7 +375,7 @@ void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) { HeapFree(GetProcessHeap(), 0, list); } -bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) { +bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { HANDLE shell_token = duplicate_shell_token(); if(!shell_token) { // This can happen if the shell has crashed. Fail the launch rather than risking launching with @@ -490,6 +490,9 @@ bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &work // Since we are always spawning a process with a less privileged token than ourselves, // bp::child() should have no problem opening it with any access rights it wants. auto child = bp::child((bp::pid_t)process_info.dwProcessId); + if(group) { + group->add(child); + } // Only close handles after bp::child() has opened the process. If the process terminates // quickly, the PID could be reused if we close the process handle. diff --git a/src/process.cpp b/src/process.cpp index 21e633ae..3d3bd3e7 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -150,7 +150,7 @@ int proc_t::execute(int app_id) { find_working_directory(cmd, _env) : boost::filesystem::path(proc.working_dir); BOOST_LOG(info) << "Spawning ["sv << cmd << "] in ["sv << working_dir << ']'; - auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec); + auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec, nullptr); if(ec) { BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message(); } @@ -168,13 +168,11 @@ int proc_t::execute(int app_id) { find_working_directory(proc.cmd, _env) : boost::filesystem::path(proc.working_dir); BOOST_LOG(info) << "Executing: ["sv << proc.cmd << "] in ["sv << working_dir << ']'; - _process = platf::run_unprivileged(proc.cmd, working_dir, _env, _pipe.get(), ec); + _process = platf::run_unprivileged(proc.cmd, working_dir, _env, _pipe.get(), ec, &_process_handle); if(ec) { BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message(); return -1; } - - _process_handle.add(_process); } fg.disable(); From bfd1e81a25118799d3a8f1bea0ed28739e7ba4c0 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Thu, 19 Jan 2023 12:35:16 -0500 Subject: [PATCH 25/51] ci: update global workflows (#802) --- .github/dependabot.yml | 10 +++++----- .github/workflows/autoupdate.yml | 2 +- .github/workflows/issues-stale.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2b757b97..8c9df6e3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,7 +9,7 @@ updates: directory: "/" schedule: interval: "daily" - time: "00:00" + time: "08:00" target-branch: "nightly" open-pull-requests-limit: 10 @@ -17,7 +17,7 @@ updates: directory: "/" schedule: interval: "daily" - time: "00:00" + time: "08:30" target-branch: "nightly" open-pull-requests-limit: 10 @@ -25,7 +25,7 @@ updates: directory: "/" schedule: interval: "daily" - time: "00:00" + time: "09:00" target-branch: "nightly" open-pull-requests-limit: 10 @@ -33,7 +33,7 @@ updates: directory: "/" schedule: interval: "daily" - time: "00:00" + time: "09:30" target-branch: "nightly" open-pull-requests-limit: 10 @@ -41,6 +41,6 @@ updates: directory: "/" schedule: interval: "daily" - time: "00:00" + time: "10:00" target-branch: "nightly" open-pull-requests-limit: 10 diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 65d80dc3..1a03d784 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -41,6 +41,6 @@ jobs: runs-on: ubuntu-latest steps: - name: rebase - uses: "bbeesley/gha-auto-dependabot-rebase@v1.2.0" + uses: "bbeesley/gha-auto-dependabot-rebase@v1.3.18" env: GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index cc0e3ae8..5fe1d773 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -9,7 +9,7 @@ name: Stale Issues / PRs on: schedule: - - cron: '00 00 * * *' + - cron: '00 10 * * *' jobs: stale: From f213aae7b85db33100156184f564eb805dfedbc3 Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Thu, 19 Jan 2023 17:08:47 -0500 Subject: [PATCH 26/51] ci: update global docker (#803) --- .github/workflows/ci-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index ccc4767e..f67f312a 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -163,7 +163,7 @@ jobs: - name: Hadolint id: hadolint - uses: hadolint/hadolint-action@v3.0.0 + uses: hadolint/hadolint-action@v3.1.0 with: dockerfile: ${{ matrix.dockerfile }} ignore: DL3008,DL3013,DL3016,DL3018,DL3028,DL3059 From 7242202291fcc97d7edeb5a1010eba62ce58a512 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 20 Jan 2023 00:18:30 -0600 Subject: [PATCH 27/51] CMake: Default to Release build and stop hardcoding optimization flags (#777) --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68aa994c..463bbf69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,11 @@ option(SUNSHINE_CONFIGURE_FLATPAK "Configuration specific for Flatpak." OFF) option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF) option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) +endif() + if(${SUNSHINE_CONFIGURE_APPIMAGE}) configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY) elseif(${SUNSHINE_CONFIGURE_AUR}) @@ -495,13 +500,11 @@ include_directories( string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE) if("${BUILD_TYPE}" STREQUAL "XDEBUG") - list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3) if(WIN32) set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) - list(APPEND SUNSHINE_COMPILE_OPTIONS -O3) endif() # setup assets directory From da390c37dbb1edc30736d791356e272410ae4345 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 20 Jan 2023 22:12:16 -0600 Subject: [PATCH 28/51] Improve reliability of hybrid graphics detection (#801) --- src/platform/windows/display_base.cpp | 81 +++++++++++++++++++++------ tools/ddprobe.cpp | 37 +++++------- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 6fe9f9f7..5a54aa43 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -191,15 +191,10 @@ bool probe_for_gpu_preference(const std::string &display_name) { // GPU driver overrides than Sunshine.exe, we want to avoid a scenario where // autoselection might work for ddprobe.exe but not for us. for(int i = 1; i < 5; i++) { - // Run the probe tool + // Run the probe tool. It returns the status of DuplicateOutput(). // // Arg format: [GPU preference] [Display name] - // - // Exit codes: - // < 0 -> Error performing the probe - // 0 -> Probe failed (DD API doesn't work with that GPU preference) - // 1 -> Probe successful (DD API works) - int result; + HRESULT result; try { result = bp::system(cmd, std::to_string(i), display_name, bp::std_out > bp::null, bp::std_err > bp::null); } @@ -208,9 +203,12 @@ bool probe_for_gpu_preference(const std::string &display_name) { return false; } - BOOST_LOG(debug) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] returned: "sv << result; + BOOST_LOG(info) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] returned: 0x"sv << util::hex(result).to_string_view(); - if(result > 0) { + // E_ACCESSDENIED can happen at the login screen. If we get this error, + // we know capture would have been supported, because DXGI_ERROR_UNSUPPORTED + // would have been raised first if it wasn't. + if(result == S_OK || result == E_ACCESSDENIED) { // We found a working GPU preference, so set ourselves to use that. if(set_gpu_preference_on_self(i)) { set_gpu_preference = true; @@ -220,17 +218,61 @@ bool probe_for_gpu_preference(const std::string &display_name) { return false; } } - else if(result == 0) { + else { // This configuration didn't work, so continue testing others continue; } - else { - BOOST_LOG(error) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] failed: "sv << result; - } } - // If none of the manual options worked, we'll try autoselection as a last-ditch effort - set_gpu_preference_on_self(0); + // If none of the manual options worked, leave the GPU preference alone + return false; +} + +bool test_dxgi_duplication(adapter_t &adapter, output_t &output) { + D3D_FEATURE_LEVEL featureLevels[] { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + device_t device; + auto status = D3D11CreateDevice( + adapter.get(), + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + D3D11_CREATE_DEVICE_FLAGS, + featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), + D3D11_SDK_VERSION, + &device, + nullptr, + nullptr); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']'; + return false; + } + + output1_t output1; + status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; + return false; + } + + // Check if we can use the Desktop Duplication API on this output + for(int x = 0; x < 2; ++x) { + dup_t dup; + status = output1->DuplicateOutput((IUnknown *)device.get(), &dup); + if(SUCCEEDED(status)) { + return true; + } + Sleep(200); + } + + BOOST_LOG(error) << "DuplicateOutput() test failed [0x"sv << util::hex(status).to_string_view() << ']'; return false; } @@ -300,7 +342,7 @@ int display_base_t::init(int framerate, const std::string &display_name) { continue; } - if(desc.AttachedToDesktop) { + if(desc.AttachedToDesktop && test_dxgi_duplication(adapter_tmp, output_tmp)) { output = std::move(output_tmp); offset_x = desc.DesktopCoordinates.left; @@ -681,7 +723,7 @@ std::vector display_names(mem_type_e) { std::wstring_convert, wchar_t> converter; // We must set the GPU preference before calling any DXGI APIs! - if(!dxgi::probe_for_gpu_preference("")) { + if(!dxgi::probe_for_gpu_preference(config::video.output_name)) { BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; } @@ -727,7 +769,10 @@ std::vector display_names(mem_type_e) { << " Resolution : "sv << width << 'x' << height << std::endl << std::endl; - display_names.emplace_back(std::move(device_name)); + // Don't include the display in the list if we can't actually capture it + if(desc.AttachedToDesktop && dxgi::test_dxgi_duplication(adapter, output)) { + display_names.emplace_back(std::move(device_name)); + } } } diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 90628c20..bf5613b3 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -24,7 +24,7 @@ using dup_t = util::safe_ptrQueryInterface(IID_IDXGIOutput1, (void **)&output1); if(FAILED(status)) { std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl; - return false; + return status; } - // Check if we can use the Desktop Duplication API on this output - for(int x = 0; x < 2; ++x) { - dxgi::dup_t dup; - status = output1->DuplicateOutput((IUnknown *)device.get(), &dup); - if(SUCCEEDED(status)) { - return true; - } - Sleep(200); - } - - return false; + // Return the result of DuplicateOutput() to Sunshine + dxgi::dup_t dup; + return output1->DuplicateOutput((IUnknown *)device.get(), &dup); } int main(int argc, char *argv[]) { @@ -109,8 +101,9 @@ int main(int argc, char *argv[]) { } // We must set the GPU preference before making any DXGI/D3D calls - if(!set_gpu_preference(atoi(argv[1]))) { - return -2; + status = set_gpu_preference(atoi(argv[1])); + if(status != ERROR_SUCCESS) { + return status; } // Remove the GPU preference when we're done @@ -127,7 +120,7 @@ int main(int argc, char *argv[]) { status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory); if(FAILED(status)) { std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; - return -3; + return status; } dxgi::adapter_t::pointer adapter_p {}; @@ -152,7 +145,7 @@ int main(int argc, char *argv[]) { } // We found the matching output. Test it and return the result. - return test_dxgi_duplication(adapter, output) ? 1 : 0; + return test_dxgi_duplication(adapter, output); } } From 4b642f6e0186c14df41dc14e806d6f12f37b05c6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 21 Jan 2023 17:42:08 -0600 Subject: [PATCH 29/51] Implement horizontal scrolling and Sunshine detection for Moonlight (#793) --- src/input.cpp | 18 ++++++++++++++++++ src/nvhttp.cpp | 3 ++- src/platform/common.h | 1 + src/platform/linux/input.cpp | 13 +++++++++++++ src/platform/macos/input.cpp | 4 ++++ src/platform/windows/input.cpp | 12 ++++++++++++ src/stream.cpp | 3 +-- third-party/moonlight-common-c | 2 +- 8 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index 2238c316..6cdd6e6f 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -188,12 +188,20 @@ void print(PNV_SCROLL_PACKET packet) { << "--end mouse scroll packet--"sv; } +void print(PSS_HSCROLL_PACKET packet) { + BOOST_LOG(debug) + << "--begin mouse hscroll packet--"sv << std::endl + << "scrollAmount ["sv << util::endian::big(packet->scrollAmount) << ']' << std::endl + << "--end mouse hscroll packet--"sv; +} + void print(PNV_KEYBOARD_PACKET packet) { BOOST_LOG(debug) << "--begin keyboard packet--"sv << std::endl << "keyAction ["sv << util::hex(packet->header.magic).to_string_view() << ']' << std::endl << "keyCode ["sv << util::hex(packet->keyCode).to_string_view() << ']' << std::endl << "modifiers ["sv << util::hex(packet->modifiers).to_string_view() << ']' << std::endl + << "flags ["sv << util::hex(packet->flags).to_string_view() << ']' << std::endl << "--end keyboard packet--"sv; } @@ -238,6 +246,9 @@ void print(void *payload) { case SCROLL_MAGIC_GEN5: print((PNV_SCROLL_PACKET)payload); break; + case SS_HSCROLL_MAGIC: + print((PSS_HSCROLL_PACKET)payload); + break; case KEY_DOWN_EVENT_MAGIC: case KEY_UP_EVENT_MAGIC: print((PNV_KEYBOARD_PACKET)payload); @@ -459,6 +470,10 @@ void passthrough(PNV_SCROLL_PACKET packet) { platf::scroll(platf_input, util::endian::big(packet->scrollAmt1)); } +void passthrough(PSS_HSCROLL_PACKET packet) { + platf::hscroll(platf_input, util::endian::big(packet->scrollAmount)); +} + void passthrough(PNV_UNICODE_PACKET packet) { auto size = util::endian::big(packet->header.size) - sizeof(packet->header.magic); platf::unicode(platf_input, packet->text, size); @@ -621,6 +636,9 @@ void passthrough_helper(std::shared_ptr input, std::vectormouse_input.get(); + if(!mouse) { + return; + } + + libevdev_uinput_write_event(mouse, EV_REL, REL_HWHEEL, distance); + libevdev_uinput_write_event(mouse, EV_REL, REL_HWHEEL_HI_RES, high_res_distance); + libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); +} + static keycode_t keysym(std::uint16_t modcode) { if(modcode <= keycodes.size()) { return keycodes[modcode]; diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index f1970944..ff59f4fb 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -417,6 +417,10 @@ void scroll(input_t &input, int high_res_distance) { CFRelease(upEvent); } +void hscroll(input_t &input, int high_res_distance) { + // Unimplemented +} + input_t input() { input_t result { new macos_input_t() }; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 6b737b0f..483bfd81 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -303,6 +303,18 @@ void scroll(input_t &input, int distance) { send_input(i); } +void hscroll(input_t &input, int distance) { + INPUT i {}; + + i.type = INPUT_MOUSE; + auto &mi = i.mi; + + mi.dwFlags = MOUSEEVENTF_HWHEEL; + mi.mouseData = distance; + + send_input(i); +} + void keyboard(input_t &input, uint16_t modcode, bool release) { auto raw = (input_raw_t *)input.get(); diff --git a/src/stream.cpp b/src/stream.cpp index 67ad8a07..0bded57a 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -399,13 +399,12 @@ session_t *control_server_t::get_session(const net::peer_t peer) { void control_server_t::call(std::uint16_t type, session_t *session, const std::string_view &payload) { auto cb = _map_type_cb.find(type); if(cb == std::end(_map_type_cb)) { - BOOST_LOG(warning) + BOOST_LOG(debug) << "type [Unknown] { "sv << util::hex(type).to_string_view() << " }"sv << std::endl << "---data---"sv << std::endl << util::hex_vec(payload) << std::endl << "---end data---"sv; } - else { cb->second(session, payload); } diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index ef9ad529..07beb0f0 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit ef9ad529a493d699724d84a189cc1899afcc2d72 +Subproject commit 07beb0f0a520106c49fda7664369b29e6938ea6e From 3f202be09a605da64aa7a23f53a89558185e51a5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 22 Jan 2023 09:53:42 -0500 Subject: [PATCH 30/51] docker build dependent on event type (#812) --- .github/workflows/ci-docker.yml | 19 +++++++++++++++++-- docker/debian-bullseye.dockerfile | 2 ++ docker/fedora-36.dockerfile | 2 ++ docker/fedora-37.dockerfile | 2 ++ docker/ubuntu-18.04.dockerfile-todo | 2 ++ docker/ubuntu-20.04.dockerfile | 2 ++ docker/ubuntu-22.04.dockerfile | 2 ++ 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index f67f312a..48f9c8cb 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -11,6 +11,9 @@ # `# platforms: ` # Comma separated list of platforms, i.e. `# platforms: linux/386,linux/amd64`. Docker platforms can alternatively # be listed in a file named `.docker_platforms`. +# `# platforms_pr: ` +# Comma separated list of platforms to run for PR events, i.e. `# platforms_pr: linux/amd64`. This will take +# precedence over the `# platforms: ` directive. # `# artifacts: ` # `true` to build in two steps, stopping at `artifacts` build stage and extracting the image from there to the # GitHub runner. @@ -202,8 +205,7 @@ jobs: # get branch name BRANCH=${GITHUB_HEAD_REF} - if [ -z "$BRANCH" ] - then + if [ -z "$BRANCH" ]; then echo "This is a PUSH event" BRANCH=${{ github.ref_name }} fi @@ -237,6 +239,19 @@ jobs: # parse custom directives out of dockerfile # try to get the platforms from the dockerfile custom directive, i.e. `# platforms: xxx,yyy` + # directives for PR event, i.e. not push event + if [[ ${PUSH} == "false" ]]; then + while read -r line; do + if [[ $line == "# platforms_pr: "* && $PLATFORMS == "" ]]; then + # echo the line and use `sed` to remove the custom directive + PLATFORMS=$(echo -e "$line" | sed 's/# platforms_pr: //') + elif [[ $PLATFORMS != "" ]]; then + # break while loop once all custom "PR" event directives are found + break + fi + done <"${{ matrix.dockerfile }}" + fi + # directives for all events... above directives will not be parsed if they were already found while read -r line; do if [[ $line == "# platforms: "* && $PLATFORMS == "" ]]; then # echo the line and use `sed` to remove the custom directive diff --git a/docker/debian-bullseye.dockerfile b/docker/debian-bullseye.dockerfile index fd0c3753..893409f8 100644 --- a/docker/debian-bullseye.dockerfile +++ b/docker/debian-bullseye.dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=debian ARG TAG=bullseye FROM ${BASE}:${TAG} AS sunshine-base diff --git a/docker/fedora-36.dockerfile b/docker/fedora-36.dockerfile index f566db75..30c215f8 100644 --- a/docker/fedora-36.dockerfile +++ b/docker/fedora-36.dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=fedora ARG TAG=36 FROM ${BASE}:${TAG} AS sunshine-base diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile index 7312e95f..f71837a1 100644 --- a/docker/fedora-37.dockerfile +++ b/docker/fedora-37.dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=fedora ARG TAG=37 FROM ${BASE}:${TAG} AS sunshine-base diff --git a/docker/ubuntu-18.04.dockerfile-todo b/docker/ubuntu-18.04.dockerfile-todo index 2425aaac..5e905fbd 100644 --- a/docker/ubuntu-18.04.dockerfile-todo +++ b/docker/ubuntu-18.04.dockerfile-todo @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=ubuntu ARG TAG=18.04 FROM ${BASE}:${TAG} AS sunshine-base diff --git a/docker/ubuntu-20.04.dockerfile b/docker/ubuntu-20.04.dockerfile index 8aeca6dd..0a5d0907 100644 --- a/docker/ubuntu-20.04.dockerfile +++ b/docker/ubuntu-20.04.dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=ubuntu ARG TAG=20.04 FROM ${BASE}:${TAG} AS sunshine-base diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index 18053816..d2a85be4 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -1,5 +1,7 @@ +# syntax=docker/dockerfile:1.4 # artifacts: true # platforms: linux/amd64,linux/arm64/v8 +# platforms_pr: linux/amd64 ARG BASE=ubuntu ARG TAG=22.04 FROM ${BASE}:${TAG} AS sunshine-base From c8c80807da0f3bf3c036a1bb571646f69d87a052 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 22 Jan 2023 11:44:36 -0500 Subject: [PATCH 31/51] fix aur nightly publishing (#813) --- .github/workflows/CI.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6ae8ad79..a7d39583 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -163,9 +163,10 @@ jobs: cmake - name: Configure PKGBUILD files + id: prepare run: | # variables for manifest - echo "aur_publish=false" >> $GITHUB_ENV + aur_publish=false aur_pkg=sunshine-dev sub_version="" conflicts="'sunshine'" @@ -174,22 +175,20 @@ jobs: branch=${GITHUB_HEAD_REF} # check the branch variable - if [ -z "$branch" ] - then + if [ -z "$branch" ]; then echo "This is a PUSH event" commit=${{ github.sha }} clone_url=${{ github.event.repository.clone_url }} if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then + echo "This is a main release event" + aur_publish=true aur_pkg=sunshine conflicts="" provides="" - - echo "aur_publish=true" >> $GITHUB_ENV elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then + echo "This is a nightly release event" sub_version=".r${commit}" - - echo "aur_publish=false" >> $GITHUB_ENV fi else echo "This is a PR event" @@ -201,7 +200,8 @@ jobs: echo "Commit: ${commit}" echo "Clone URL: ${clone_url}" - echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV + echo "aur_publish=${aur_publish}" >> $GITHUB_OUTPUT + echo "aur_pkg=${aur_pkg}" >> $GITHUB_OUTPUT mkdir -p artifacts mkdir -p build @@ -237,10 +237,10 @@ jobs: path: artifacts/ - name: Publish AUR package - if: ${{ env.aur_publish == 'true' }} + if: ${{ steps.prepare.outputs.aur_publish == 'true' }} uses: KSXGitHub/github-actions-deploy-aur@v2.6.0 with: - pkgname: ${{ env.aur_pkg }} + pkgname: ${{ steps.prepare.outputs.aur_pkg }} pkgbuild: ./artifacts/PKGBUILD assets: | ./artifacts/* From 9df6283da474462e90ce6a5a355cd77eda96a5f7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 22 Jan 2023 12:24:25 -0500 Subject: [PATCH 32/51] cd: fix aur publish conditions (#814) --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a7d39583..e5148e4d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -180,13 +180,13 @@ jobs: commit=${{ github.sha }} clone_url=${{ github.event.repository.clone_url }} - if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then + if [[ ${{ github.ref == 'refs/heads/master' }} == 'true' ]]; then echo "This is a main release event" aur_publish=true aur_pkg=sunshine conflicts="" provides="" - elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then + elif [[ ${{ github.ref == 'refs/heads/nightly' }} == 'false' ]]; then echo "This is a nightly release event" sub_version=".r${commit}" fi From 9d6d59aa55ba51bc60d9f9fdd201b12f7ae36bf3 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 22 Jan 2023 13:23:24 -0500 Subject: [PATCH 33/51] cd: fix aur publish conditions (#815) --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e5148e4d..09d15d48 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -180,13 +180,13 @@ jobs: commit=${{ github.sha }} clone_url=${{ github.event.repository.clone_url }} - if [[ ${{ github.ref == 'refs/heads/master' }} == 'true' ]]; then + if [[ ${{ github.ref == 'refs/heads/master' }} == true ]]; then echo "This is a main release event" aur_publish=true aur_pkg=sunshine conflicts="" provides="" - elif [[ ${{ github.ref == 'refs/heads/nightly' }} == 'false' ]]; then + elif [[ ${{ github.ref == 'refs/heads/nightly' }} == true ]]; then echo "This is a nightly release event" sub_version=".r${commit}" fi From fa6c279efcea4fb33111cf8e689157aeb15b20bc Mon Sep 17 00:00:00 2001 From: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Sun, 22 Jan 2023 17:13:33 -0500 Subject: [PATCH 34/51] ci: update global workflows (#816) --- .github/workflows/autoupdate.yml | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 1a03d784..83f4e161 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -9,7 +9,7 @@ # It uses an action that auto-updates pull requests branches, when changes are pushed to their destination branch. # Auto-updating to the latest destination branch works only in the context of upstream repo and not forks. -# Dependabot PRs are updated by an action that comments `@depdenabot rebase` on dependabot PRs. +# Dependabot PRs are updated by an action that comments `@depdenabot rebase` on dependabot PRs. (disabled) name: autoupdate @@ -34,13 +34,18 @@ jobs: PR_READY_STATE: "all" MERGE_CONFLICT_ACTION: "fail" - dependabot-rebase: - name: Dependabot Rebase - if: >- - startsWith(github.repository, 'LizardByte/') - runs-on: ubuntu-latest - steps: - - name: rebase - uses: "bbeesley/gha-auto-dependabot-rebase@v1.3.18" - env: - GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} +# Disabled due to: +# - no major version tag, resulting in constant nagging to update this action +# - additionally, the code is sketchy, 16k+ lines of code? +# https://github.com/bbeesley/gha-auto-dependabot-rebase/blob/main/dist/main.cjs +# +# dependabot-rebase: +# name: Dependabot Rebase +# if: >- +# startsWith(github.repository, 'LizardByte/') +# runs-on: ubuntu-latest +# steps: +# - name: rebase +# uses: "bbeesley/gha-auto-dependabot-rebase@v1.3.18" +# env: +# GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} From 9181028bcffa3efe6c7780e8ffdb6b7dcba56e42 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 23 Jan 2023 20:54:08 -0600 Subject: [PATCH 35/51] HDR prep work (#808) --- src/main.h | 1 + src/platform/common.h | 20 +++++- src/platform/linux/cuda.cpp | 11 ++-- src/platform/linux/kmsgrab.cpp | 19 +++--- src/platform/linux/misc.cpp | 18 +++--- src/platform/linux/wlgrab.cpp | 15 ++--- src/platform/linux/x11grab.cpp | 15 ++--- src/platform/macos/display.mm | 9 ++- src/platform/windows/display.h | 15 +++-- src/platform/windows/display_base.cpp | 76 +++++++++++++++++++--- src/platform/windows/display_ram.cpp | 11 +++- src/platform/windows/display_vram.cpp | 10 ++- src/stream.cpp | 50 +++++++++++++++ src/video.cpp | 91 ++++++++++++++++++++++----- src/video.h | 10 +++ 15 files changed, 294 insertions(+), 77 deletions(-) diff --git a/src/main.h b/src/main.h index 6169fb98..a684ee6d 100644 --- a/src/main.h +++ b/src/main.h @@ -51,6 +51,7 @@ MAIL(switch_display); MAIL(touch_port); MAIL(idr); MAIL(rumble); +MAIL(hdr); #undef MAIL } // namespace mail #endif // SUNSHINE_MAIN_H diff --git a/src/platform/common.h b/src/platform/common.h index 1d91c2f7..48c7ba23 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -15,6 +15,10 @@ #include "src/thread_safe.h" #include "src/utility.h" +extern "C" { +#include +} + struct sockaddr; struct AVFrame; struct AVBufferRef; @@ -39,6 +43,9 @@ class basic_environment; typedef basic_environment environment; } // namespace process } // namespace boost +namespace video { +struct config_t; +} namespace platf { constexpr auto MAX_GAMEPADS = 32; @@ -270,6 +277,15 @@ public: return std::make_shared(); } + virtual bool is_hdr() { + return false; + } + + virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) { + std::memset(&metadata, 0, sizeof(metadata)); + return false; + } + virtual ~display_t() = default; // Offsets for when streaming a specific monitor. By default, they are 0. @@ -315,11 +331,11 @@ std::unique_ptr audio_control(); * If display_name is empty --> Use the first monitor that's compatible you can find * If you require to use this parameter in a seperate thread --> make a copy of it. * - * framerate --> The peak number of images per second + * config --> Stream configuration * * Returns display_t based on hwdevice_type */ -std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); // A list of names of displays accepted as display_name with the mem_type_e std::vector display_names(mem_type_e hwdevice_type); diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 9e3c95b4..845046d7 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -13,6 +13,7 @@ extern "C" { #include "graphics.h" #include "src/main.h" #include "src/utility.h" +#include "src/video.h" #include "wayland.h" #define SUNSHINE_STRINGVIEW_HELPER(x) x##sv @@ -414,7 +415,7 @@ public: class display_t : public platf::display_t { public: - int init(const std::string_view &display_name, int framerate) { + int init(const std::string_view &display_name, const ::video::config_t &config) { auto handle = handle_t::make(); if(!handle) { return -1; @@ -444,14 +445,14 @@ public: } } - delay = std::chrono::nanoseconds { 1s } / framerate; + delay = std::chrono::nanoseconds { 1s } / config.framerate; capture_params = NVFBC_CREATE_CAPTURE_SESSION_PARAMS { NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER }; capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA; capture_params.bDisableAutoModesetRecovery = nv_bool(true); - capture_params.dwSamplingRateMs = 1000 /* ms */ / framerate; + capture_params.dwSamplingRateMs = 1000 /* ms */ / config.framerate; if(streamedMonitor != -1) { auto &output = status_params->outputs[streamedMonitor]; @@ -663,7 +664,7 @@ public: } // namespace cuda namespace platf { -std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if(hwdevice_type != mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv; return nullptr; @@ -671,7 +672,7 @@ std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::st auto display = std::make_shared(); - if(display->init(display_name, framerate)) { + if(display->init(display_name, config)) { return nullptr; } diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 2c84baea..214387b9 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -12,6 +12,7 @@ #include "src/platform/common.h" #include "src/round_robin.h" #include "src/utility.h" +#include "src/video.h" // Cursor rendering support through x11 #include "graphics.h" @@ -444,8 +445,8 @@ class display_t : public platf::display_t { public: display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {} - int init(const std::string &display_name, int framerate) { - delay = std::chrono::nanoseconds { 1s } / framerate; + int init(const std::string &display_name, const ::video::config_t &config) { + delay = std::chrono::nanoseconds { 1s } / config.framerate; int monitor_index = util::from_view(display_name); int monitor = 0; @@ -632,13 +633,13 @@ class display_ram_t : public display_t { public: display_ram_t(mem_type_e mem_type) : display_t(mem_type) {} - int init(const std::string &display_name, int framerate) { + int init(const std::string &display_name, const ::video::config_t &config) { if(!gbm::create_device) { BOOST_LOG(warning) << "libgbm not initialized"sv; return -1; } - if(display_t::init(display_name, framerate)) { + if(display_t::init(display_name, config)) { return -1; } @@ -852,8 +853,8 @@ public: return capture_e::ok; } - int init(const std::string &display_name, int framerate) { - if(display_t::init(display_name, framerate)) { + int init(const std::string &display_name, const ::video::config_t &config) { + if(display_t::init(display_name, config)) { return -1; } @@ -872,11 +873,11 @@ public: } // namespace kms -std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if(hwdevice_type == mem_type_e::vaapi) { auto disp = std::make_shared(hwdevice_type); - if(!disp->init(display_name, framerate)) { + if(!disp->init(display_name, config)) { return disp; } @@ -885,7 +886,7 @@ std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::stri auto disp = std::make_shared(hwdevice_type); - if(disp->init(display_name, framerate)) { + if(disp->init(display_name, config)) { return nullptr; } diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index b5660fe9..081fb534 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -418,7 +418,7 @@ static std::bitset sources; #ifdef SUNSHINE_BUILD_CUDA std::vector nvfbc_display_names(); -std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); bool verify_nvfbc() { return !nvfbc_display_names().empty(); @@ -427,7 +427,7 @@ bool verify_nvfbc() { #ifdef SUNSHINE_BUILD_WAYLAND std::vector wl_display_names(); -std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); bool verify_wl() { return window_system == window_system_e::WAYLAND && !wl_display_names().empty(); @@ -436,7 +436,7 @@ bool verify_wl() { #ifdef SUNSHINE_BUILD_DRM std::vector kms_display_names(); -std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); bool verify_kms() { return !kms_display_names().empty(); @@ -445,7 +445,7 @@ bool verify_kms() { #ifdef SUNSHINE_BUILD_X11 std::vector x11_display_names(); -std::shared_ptr x11_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::shared_ptr x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); bool verify_x11() { return window_system == window_system_e::X11 && !x11_display_names().empty(); @@ -469,29 +469,29 @@ std::vector display_names(mem_type_e hwdevice_type) { return {}; } -std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { #ifdef SUNSHINE_BUILD_CUDA if(sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) { BOOST_LOG(info) << "Screencasting with NvFBC"sv; - return nvfbc_display(hwdevice_type, display_name, framerate); + return nvfbc_display(hwdevice_type, display_name, config); } #endif #ifdef SUNSHINE_BUILD_WAYLAND if(sources[source::WAYLAND]) { BOOST_LOG(info) << "Screencasting with Wayland's protocol"sv; - return wl_display(hwdevice_type, display_name, framerate); + return wl_display(hwdevice_type, display_name, config); } #endif #ifdef SUNSHINE_BUILD_DRM if(sources[source::KMS]) { BOOST_LOG(info) << "Screencasting with KMS"sv; - return kms_display(hwdevice_type, display_name, framerate); + return kms_display(hwdevice_type, display_name, config); } #endif #ifdef SUNSHINE_BUILD_X11 if(sources[source::X11]) { BOOST_LOG(info) << "Screencasting with X11"sv; - return x11_display(hwdevice_type, display_name, framerate); + return x11_display(hwdevice_type, display_name, config); } #endif diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index ce596e8c..ea4adc8d 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -1,6 +1,7 @@ #include "src/platform/common.h" #include "src/main.h" +#include "src/video.h" #include "vaapi.h" #include "wayland.h" @@ -18,8 +19,8 @@ struct img_t : public platf::img_t { class wlr_t : public platf::display_t { public: - int init(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { - delay = std::chrono::nanoseconds { 1s } / framerate; + int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { + delay = std::chrono::nanoseconds { 1s } / config.framerate; mem_type = hwdevice_type; if(display.init()) { @@ -175,8 +176,8 @@ public: return platf::capture_e::ok; } - int init(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { - if(wlr_t::init(hwdevice_type, display_name, framerate)) { + int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { + if(wlr_t::init(hwdevice_type, display_name, config)) { return -1; } @@ -307,7 +308,7 @@ public: } // namespace wl namespace platf { -std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; return nullptr; @@ -315,7 +316,7 @@ std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::strin if(hwdevice_type == platf::mem_type_e::vaapi) { auto wlr = std::make_shared(); - if(wlr->init(hwdevice_type, display_name, framerate)) { + if(wlr->init(hwdevice_type, display_name, config)) { return nullptr; } @@ -323,7 +324,7 @@ std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::strin } auto wlr = std::make_shared(); - if(wlr->init(hwdevice_type, display_name, framerate)) { + if(wlr->init(hwdevice_type, display_name, config)) { return nullptr; } diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index 1f8f5b73..e18de69d 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -19,6 +19,7 @@ #include "src/config.h" #include "src/main.h" #include "src/task_pool.h" +#include "src/video.h" #include "cuda.h" #include "graphics.h" @@ -382,13 +383,13 @@ struct x11_attr_t : public display_t { x11::InitThreads(); } - int init(const std::string &display_name, int framerate) { + int init(const std::string &display_name, const ::video::config_t &config) { if(!xdisplay) { BOOST_LOG(error) << "Could not open X11 display"sv; return -1; } - delay = std::chrono::nanoseconds { 1s } / framerate; + delay = std::chrono::nanoseconds { 1s } / config.framerate; xwindow = DefaultRootWindow(xdisplay.get()); @@ -641,8 +642,8 @@ struct shm_attr_t : public x11_attr_t { return 0; } - int init(const std::string &display_name, int framerate) { - if(x11_attr_t::init(display_name, framerate)) { + int init(const std::string &display_name, const ::video::config_t &config) { + if(x11_attr_t::init(display_name, config)) { return 1; } @@ -685,7 +686,7 @@ struct shm_attr_t : public x11_attr_t { } }; -std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv; return nullptr; @@ -700,7 +701,7 @@ std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const st // Attempt to use shared memory X11 to avoid copying the frame auto shm_disp = std::make_shared(hwdevice_type); - auto status = shm_disp->init(display_name, framerate); + auto status = shm_disp->init(display_name, config); if(status > 0) { // x11_attr_t::init() failed, don't bother trying again. return nullptr; @@ -712,7 +713,7 @@ std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const st // Fallback auto x11_disp = std::make_shared(hwdevice_type); - if(x11_disp->init(display_name, framerate)) { + if(x11_disp->init(display_name, config)) { return nullptr; } diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index a53ac0e6..c818235e 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -6,6 +6,11 @@ #include "src/config.h" #include "src/main.h" +// Avoid conflict between AVFoundation and libavutil both defining AVMediaType +#define AVMediaType AVMediaType_FFmpeg +#include "src/video.h" +#undef AVMediaType + namespace fs = std::filesystem; namespace platf { @@ -147,7 +152,7 @@ struct av_display_t : public display_t { } }; -std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if(hwdevice_type != platf::mem_type_e::system) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; return nullptr; @@ -168,7 +173,7 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::s } } - display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:framerate]; + display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate]; if(!display->av_capture) { BOOST_LOG(error) << "Video setup failed."sv; diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index 2a3b6cc9..a72c94bd 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "src/platform/common.h" #include "src/utility.h" @@ -37,6 +37,7 @@ using adapter_t = util::safe_ptr>; using output1_t = util::safe_ptr>; using output5_t = util::safe_ptr>; +using output6_t = util::safe_ptr>; using dup_t = util::safe_ptr>; using texture2d_t = util::safe_ptr>; using texture1d_t = util::safe_ptr>; @@ -115,7 +116,7 @@ public: class display_base_t : public display_t { public: - int init(int framerate, const std::string &display_name); + int init(const ::video::config_t &config, const std::string &display_name); capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override; std::chrono::nanoseconds delay; @@ -141,6 +142,9 @@ public: typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); + virtual bool is_hdr() override; + virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override; + protected: int get_pixel_pitch() { return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; @@ -151,6 +155,7 @@ protected: virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0; virtual int complete_img(img_t *img, bool dummy) = 0; virtual std::vector get_supported_sdr_capture_formats() = 0; + virtual std::vector get_supported_hdr_capture_formats() = 0; }; class display_ram_t : public display_base_t { @@ -161,8 +166,9 @@ public: int dummy_img(img_t *img) override; int complete_img(img_t *img, bool dummy) override; std::vector get_supported_sdr_capture_formats() override; + std::vector get_supported_hdr_capture_formats() override; - int init(int framerate, const std::string &display_name); + int init(const ::video::config_t &config, const std::string &display_name); cursor_t cursor; D3D11_MAPPED_SUBRESOURCE img_info; @@ -177,8 +183,9 @@ public: int dummy_img(img_t *img_base) override; int complete_img(img_t *img_base, bool dummy) override; std::vector get_supported_sdr_capture_formats() override; + std::vector get_supported_hdr_capture_formats() override; - int init(int framerate, const std::string &display_name); + int init(const ::video::config_t &config, const std::string &display_name); std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 5a54aa43..5706f86c 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -17,6 +17,7 @@ typedef long NTSTATUS; #include "src/config.h" #include "src/main.h" #include "src/platform/common.h" +#include "src/video.h" namespace platf { using namespace std::literals; @@ -106,10 +107,16 @@ capture_e display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<: }); while(img) { - auto wait_time_us = std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now()).count(); + // This will return false if the HDR state changes or for any number of other + // display or GPU changes. We should reinit to examine the updated state of + // the display subsystem. It is recommended to call this once per frame. + if(!factory->IsCurrent()) { + return platf::capture_e::reinit; + } // If the wait time is between 1 us and 1 second, wait the specified time // and offset the next frame time from the exact current frame time target. + auto wait_time_us = std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now()).count(); if(wait_time_us > 0 && wait_time_us < 1000000) { LARGE_INTEGER due_time { .QuadPart = -10LL * wait_time_us }; SetWaitableTimer(timer, &due_time, 0, nullptr, nullptr, false); @@ -276,7 +283,7 @@ bool test_dxgi_duplication(adapter_t &adapter, output_t &output) { return false; } -int display_base_t::init(int framerate, const std::string &display_name) { +int display_base_t::init(const ::video::config_t &config, const std::string &display_name) { std::once_flag windows_cpp_once_flag; std::call_once(windows_cpp_once_flag, []() { @@ -296,7 +303,7 @@ int display_base_t::init(int framerate, const std::string &display_name) { // Ensure we can duplicate the current display syncThreadDesktop(); - delay = std::chrono::nanoseconds { 1s } / framerate; + delay = std::chrono::nanoseconds { 1s } / config.framerate; // Get rectangle of full desktop for absolute mouse coordinates env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); @@ -421,7 +428,7 @@ int display_base_t::init(int framerate, const std::string &display_name) { << "Virtual Desktop : "sv << env_width << 'x' << env_height; // Enable DwmFlush() only if the current refresh rate can match the client framerate. - auto refresh_rate = framerate; + auto refresh_rate = config.framerate; DWM_TIMING_INFO timing_info; timing_info.cbSize = sizeof(timing_info); @@ -433,7 +440,7 @@ int display_base_t::init(int framerate, const std::string &display_name) { refresh_rate = std::round((double)timing_info.rateRefresh.uiNumerator / (double)timing_info.rateRefresh.uiDenominator); } - dup.use_dwmflush = config::video.dwmflush && !(framerate > refresh_rate) ? true : false; + dup.use_dwmflush = config::video.dwmflush && !(config.framerate > refresh_rate) ? true : false; // Bump up thread priority { @@ -502,7 +509,7 @@ int display_base_t::init(int framerate, const std::string &display_name) { status = output->QueryInterface(IID_IDXGIOutput5, (void **)&output5); if(SUCCEEDED(status)) { // Ask the display implementation which formats it supports - auto supported_formats = get_supported_sdr_capture_formats(); + auto supported_formats = config.dynamicRange ? get_supported_hdr_capture_formats() : get_supported_sdr_capture_formats(); if(supported_formats.empty()) { BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv; return -1; @@ -562,6 +569,57 @@ int display_base_t::init(int framerate, const std::string &display_name) { return 0; } +bool display_base_t::is_hdr() { + dxgi::output6_t output6 {}; + + auto status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to query IDXGIOutput6 from the output"sv; + return false; + } + + DXGI_OUTPUT_DESC1 desc1; + output6->GetDesc1(&desc1); + + return desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; +} + +bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) { + dxgi::output6_t output6 {}; + + std::memset(&metadata, 0, sizeof(metadata)); + + auto status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6); + if(FAILED(status)) { + BOOST_LOG(warning) << "Failed to query IDXGIOutput6 from the output"sv; + return false; + } + + DXGI_OUTPUT_DESC1 desc1; + output6->GetDesc1(&desc1); + + metadata.displayPrimaries[0].x = desc1.RedPrimary[0] * 50000; + metadata.displayPrimaries[0].y = desc1.RedPrimary[1] * 50000; + metadata.displayPrimaries[1].x = desc1.GreenPrimary[0] * 50000; + metadata.displayPrimaries[1].y = desc1.GreenPrimary[1] * 50000; + metadata.displayPrimaries[2].x = desc1.BluePrimary[0] * 50000; + metadata.displayPrimaries[2].y = desc1.BluePrimary[1] * 50000; + + metadata.whitePoint.x = desc1.WhitePoint[0] * 50000; + metadata.whitePoint.y = desc1.WhitePoint[1] * 50000; + + metadata.maxDisplayLuminance = desc1.MaxLuminance; + metadata.minDisplayLuminance = desc1.MinLuminance * 10000; + + // These are content-specific metadata parameters that this interface doesn't give us + metadata.maxContentLightLevel = 0; + metadata.maxFrameAverageLightLevel = 0; + + metadata.maxFullFrameLuminance = desc1.MaxFullFrameLuminance; + + return true; +} + const char *format_str[] = { "DXGI_FORMAT_UNKNOWN", "DXGI_FORMAT_R32G32B32A32_TYPELESS", @@ -694,18 +752,18 @@ const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) { } // namespace platf::dxgi namespace platf { -std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { if(hwdevice_type == mem_type_e::dxgi) { auto disp = std::make_shared(); - if(!disp->init(framerate, display_name)) { + if(!disp->init(config, display_name)) { return disp; } } else if(hwdevice_type == mem_type_e::system) { auto disp = std::make_shared(); - if(!disp->init(framerate, display_name)) { + if(!disp->init(config, display_name)) { return disp; } } diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index 29ebcefb..c3f64bdb 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -349,11 +349,16 @@ int display_ram_t::dummy_img(platf::img_t *img) { } std::vector display_ram_t::get_supported_sdr_capture_formats() { - return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM }; + return { DXGI_FORMAT_B8G8R8A8_UNORM }; } -int display_ram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { +std::vector display_ram_t::get_supported_hdr_capture_formats() { + // HDR is unsupported + return {}; +} + +int display_ram_t::init(const ::video::config_t &config, const std::string &display_name) { + if(display_base_t::init(config, display_name)) { return -1; } diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 9b1af4ac..b50f2c24 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -952,8 +952,8 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec return capture_e::ok; } -int display_vram_t::init(int framerate, const std::string &display_name) { - if(display_base_t::init(framerate, display_name)) { +int display_vram_t::init(const ::video::config_t &config, const std::string &display_name) { + if(display_base_t::init(config, display_name)) { return -1; } @@ -1104,7 +1104,11 @@ int display_vram_t::dummy_img(platf::img_t *img_base) { } std::vector display_vram_t::get_supported_sdr_capture_formats() { - return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM }; + return { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM }; +} + +std::vector display_vram_t::get_supported_hdr_capture_formats() { + return { DXGI_FORMAT_R10G10B10A2_UNORM }; } std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { diff --git a/src/stream.cpp b/src/stream.cpp index 0bded57a..78e7a3d0 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -33,6 +33,7 @@ extern "C" { #define IDX_PERIODIC_PING 8 #define IDX_REQUEST_IDR_FRAME 9 #define IDX_ENCRYPTED 10 +#define IDX_HDR_MODE 11 static const short packetTypes[] = { 0x0305, // Start A @@ -46,6 +47,7 @@ static const short packetTypes[] = { 0x0200, // Periodic Ping 0x0302, // IDR frame 0x0001, // fully encrypted + 0x010e, // HDR mode }; namespace asio = boost::asio; @@ -131,6 +133,15 @@ struct control_rumble_t { std::uint16_t highfreq; }; +struct control_hdr_mode_t { + control_header_v2 header; + + std::uint8_t enabled; + + // Sunshine protocol extension + SS_HDR_METADATA metadata; +}; + typedef struct control_encrypted_t { std::uint16_t encryptedHeaderType; // Always LE 0x0001 std::uint16_t length; // sizeof(seq) + 16 byte tag + secondary header and data @@ -314,6 +325,7 @@ struct session_t { std::uint8_t seq; platf::rumble_queue_t rumble_queue; + safe::mail_raw_t::event_t hdr_queue; } control; safe::mail_raw_t::event_t shutdown_event; @@ -607,6 +619,36 @@ int send_rumble(session_t *session, std::uint16_t id, std::uint16_t lowfreq, std return 0; } +int send_hdr_mode(session_t *session, video::hdr_info_t hdr_info) { + if(!session->control.peer) { + BOOST_LOG(warning) << "Couldn't send HDR mode, still waiting for PING from Moonlight"sv; + // Still waiting for PING from Moonlight + return -1; + } + + control_hdr_mode_t plaintext {}; + plaintext.header.type = packetTypes[IDX_HDR_MODE]; + plaintext.header.payloadLength = sizeof(control_hdr_mode_t) - sizeof(control_header_v2); + + plaintext.enabled = hdr_info->enabled; + plaintext.metadata = hdr_info->metadata; + + std::array + encrypted_payload; + + auto payload = encode_control(session, util::view(plaintext), encrypted_payload); + if(session->broadcast_ref->control_server.send(payload, session->control.peer)) { + TUPLE_2D(port, addr, platf::from_sockaddr_ex((sockaddr *)&session->control.peer->address.address)); + BOOST_LOG(warning) << "Couldn't send HDR mode to ["sv << addr << ':' << port << ']'; + + return -1; + } + + BOOST_LOG(debug) << "Sent HDR mode: " << hdr_info->enabled; + return 0; +} + void controlBroadcastThread(control_server_t *server) { server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) { BOOST_LOG(verbose) << "type [IDX_START_A]"sv; @@ -778,6 +820,13 @@ void controlBroadcastThread(control_server_t *server) { send_rumble(session, rumble->id, rumble->lowfreq, rumble->highfreq); } + auto &hdr_queue = session->control.hdr_queue; + while(hdr_queue->peek()) { + auto hdr_info = hdr_queue->pop(); + + send_hdr_mode(session, std::move(hdr_info)); + } + ++pos; }) } @@ -1513,6 +1562,7 @@ std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypt session->config = config; session->control.rumble_queue = mail->queue(mail::rumble); + session->control.hdr_queue = mail->event(mail::hdr); session->control.iv = iv; session->control.cipher = crypto::cipher::gcm_t { gcm_key, false diff --git a/src/video.cpp b/src/video.cpp index a3cf5b6a..91f9bfba 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -5,6 +5,7 @@ #include extern "C" { +#include #include } @@ -372,6 +373,7 @@ struct sync_session_ctx_t { safe::mail_raw_t::event_t shutdown_event; safe::mail_raw_t::queue_t packets; safe::mail_raw_t::event_t idr_events; + safe::mail_raw_t::event_t hdr_events; safe::mail_raw_t::event_t touch_port_events; config_t config; @@ -391,7 +393,7 @@ using encode_e = platf::capture_e; struct capture_ctx_t { img_event_t images; - int framerate; + config_t config; }; struct capture_thread_async_ctx_t { @@ -700,11 +702,11 @@ static std::vector encoders { software }; -void reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, int framerate) { +void reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) { // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { disp.reset(); - disp = platf::display(map_base_dev_type(type), display_name, framerate); + disp = platf::display(map_base_dev_type(type), display_name, config); if(disp) { break; } @@ -755,7 +757,7 @@ void captureThread( capture_ctxs.emplace_back(std::move(*capture_ctx)); } - auto disp = platf::display(map_base_dev_type(encoder.base_dev_type), display_names[display_p], capture_ctxs.front().framerate); + auto disp = platf::display(map_base_dev_type(encoder.base_dev_type), display_names[display_p], capture_ctxs.front().config); if(!disp) { return; } @@ -841,7 +843,7 @@ void captureThread( } while(capture_ctx_queue->running()) { - reset_display(disp, encoder.base_dev_type, display_names[display_p], capture_ctxs.front().framerate); + reset_display(disp, encoder.base_dev_type, display_names[display_p], capture_ctxs.front().config); if(disp) { break; @@ -939,7 +941,7 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, safe::m return 0; } -std::optional make_session(const encoder_t &encoder, const config_t &config, int width, int height, std::shared_ptr &&hwdevice) { +std::optional make_session(platf::display_t *disp, const encoder_t &encoder, const config_t &config, int width, int height, std::shared_ptr &&hwdevice) { bool hardware = encoder.base_dev_type != AV_HWDEVICE_TYPE_NONE; auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc; @@ -1037,6 +1039,15 @@ std::optional make_session(const encoder_t &encoder, const config_t & } else { sw_fmt = encoder.dynamic_pix_fmt; + + // When HDR is active, that overrides the colorspace the client requested + if(disp->is_hdr()) { + BOOST_LOG(info) << "HDR color coding override [SMPTE ST 2084 PQ]"sv; + ctx->color_primaries = AVCOL_PRI_BT2020; + ctx->color_trc = AVCOL_TRC_SMPTE2084; + ctx->colorspace = AVCOL_SPC_BT2020_NCL; + sws_color_space = SWS_CS_BT2020; + } } // Used by cbs::make_sps_hevc @@ -1169,6 +1180,37 @@ std::optional make_session(const encoder_t &encoder, const config_t & frame->width = ctx->width; frame->height = ctx->height; + // Attach HDR metadata to the AVFrame + if(config.dynamicRange && disp->is_hdr()) { + SS_HDR_METADATA hdr_metadata; + if(disp->get_hdr_metadata(hdr_metadata)) { + auto mdm = av_mastering_display_metadata_create_side_data(frame.get()); + + mdm->display_primaries[0][0] = av_make_q(hdr_metadata.displayPrimaries[0].x, 50000); + mdm->display_primaries[0][1] = av_make_q(hdr_metadata.displayPrimaries[0].y, 50000); + mdm->display_primaries[1][0] = av_make_q(hdr_metadata.displayPrimaries[1].x, 50000); + mdm->display_primaries[1][1] = av_make_q(hdr_metadata.displayPrimaries[1].y, 50000); + mdm->display_primaries[2][0] = av_make_q(hdr_metadata.displayPrimaries[2].x, 50000); + mdm->display_primaries[2][1] = av_make_q(hdr_metadata.displayPrimaries[2].y, 50000); + + mdm->white_point[0] = av_make_q(hdr_metadata.whitePoint.x, 50000); + mdm->white_point[1] = av_make_q(hdr_metadata.whitePoint.y, 50000); + + mdm->min_luminance = av_make_q(hdr_metadata.minDisplayLuminance, 10000); + mdm->max_luminance = av_make_q(hdr_metadata.maxDisplayLuminance, 1); + + mdm->has_luminance = hdr_metadata.maxDisplayLuminance != 0 ? 1 : 0; + mdm->has_primaries = hdr_metadata.displayPrimaries[0].x != 0 ? 1 : 0; + + if(hdr_metadata.maxContentLightLevel != 0 || hdr_metadata.maxFrameAverageLightLevel != 0) { + auto clm = av_content_light_metadata_create_side_data(frame.get()); + + clm->MaxCLL = hdr_metadata.maxContentLightLevel; + clm->MaxFALL = hdr_metadata.maxFrameAverageLightLevel; + } + } + } + std::shared_ptr device; if(!hwdevice->data) { @@ -1212,13 +1254,13 @@ void encode_run( safe::mail_t mail, img_event_t images, config_t config, - int width, int height, + std::shared_ptr disp, std::shared_ptr &&hwdevice, safe::signal_t &reinit_event, const encoder_t &encoder, void *channel_data) { - auto session = make_session(encoder, config, width, height, std::move(hwdevice)); + auto session = make_session(disp.get(), encoder, config, disp->width, disp->height, std::move(hwdevice)); if(!session) { return; } @@ -1303,7 +1345,15 @@ std::optional make_synced_session(platf::display_t *disp, const // absolute mouse coordinates require that the dimensions of the screen are known ctx.touch_port_events->raise(make_port(disp, ctx.config)); - auto session = make_session(encoder, ctx.config, img.width, img.height, std::move(hwdevice)); + // Update client with our current HDR display state + hdr_info_t hdr_info = std::make_unique(false); + if(ctx.config.dynamicRange && disp->is_hdr()) { + disp->get_hdr_metadata(hdr_info->metadata); + hdr_info->enabled = true; + } + ctx.hdr_events->raise(std::move(hdr_info)); + + auto session = make_session(disp, encoder, ctx.config, img.width, img.height, std::move(hwdevice)); if(!session) { return std::nullopt; } @@ -1346,10 +1396,8 @@ encode_e encode_run_sync( synced_session_ctxs.emplace_back(std::make_unique(std::move(*ctx))); } - int framerate = synced_session_ctxs.front()->config.framerate; - while(encode_session_ctx_queue.running()) { - reset_display(disp, encoder.base_dev_type, display_names[display_p], framerate); + reset_display(disp, encoder.base_dev_type, display_names[display_p], synced_session_ctxs.front()->config); if(disp) { break; } @@ -1509,8 +1557,7 @@ void capture_async( return; } - ref->capture_ctx_queue->raise(capture_ctx_t { - images, config.framerate }); + ref->capture_ctx_queue->raise(capture_ctx_t { images, config }); if(!ref->capture_ctx_queue->running()) { return; @@ -1519,6 +1566,7 @@ void capture_async( int frame_nr = 1; auto touch_port_event = mail->event(mail::touch_port); + auto hdr_event = mail->event(mail::hdr); // Encoding takes place on this thread platf::adjust_thread_priority(platf::thread_priority_e::high); @@ -1557,10 +1605,18 @@ void capture_async( // absolute mouse coordinates require that the dimensions of the screen are known touch_port_event->raise(make_port(display.get(), config)); + // Update client with our current HDR display state + hdr_info_t hdr_info = std::make_unique(false); + if(config.dynamicRange && display->is_hdr()) { + display->get_hdr_metadata(hdr_info->metadata); + hdr_info->enabled = true; + } + hdr_event->raise(std::move(hdr_info)); + encode_run( frame_nr, mail, images, - config, display->width, display->height, + config, display, std::move(hwdevice), ref->reinit_event, *ref->encoder_p, channel_data); @@ -1592,6 +1648,7 @@ void capture( mail->event(mail::shutdown), mail::man->queue(mail::video_packets), std::move(idr_events), + mail->event(mail::hdr), mail->event(mail::touch_port), config, 1, @@ -1609,7 +1666,7 @@ enum validate_flag_e { }; int validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.base_dev_type, config::video.output_name, config.framerate); + reset_display(disp, encoder.base_dev_type, config::video.output_name, config); if(!disp) { return -1; } @@ -1620,7 +1677,7 @@ int validate_config(std::shared_ptr &disp, const encoder_t &en return -1; } - auto session = make_session(encoder, config, disp->width, disp->height, std::move(hwdevice)); + auto session = make_session(disp.get(), encoder, config, disp->width, disp->height, std::move(hwdevice)); if(!session) { return -1; } diff --git a/src/video.h b/src/video.h index 3d99f855..3d48af14 100644 --- a/src/video.h +++ b/src/video.h @@ -48,6 +48,16 @@ struct packet_raw_t { using packet_t = std::unique_ptr; +struct hdr_info_raw_t { + explicit hdr_info_raw_t(bool enabled) : enabled { enabled }, metadata {} {}; + explicit hdr_info_raw_t(bool enabled, const SS_HDR_METADATA &metadata) : enabled { enabled }, metadata { metadata } {}; + + bool enabled; + SS_HDR_METADATA metadata; +}; + +using hdr_info_t = std::unique_ptr; + struct config_t { int width; int height; From d625dda4eaeffd91ea282eb2b253040a759a3bc4 Mon Sep 17 00:00:00 2001 From: Jari Date: Tue, 24 Jan 2023 20:47:52 -0600 Subject: [PATCH 36/51] Steam Deck Gamingmode fix (#711) --- src/platform/linux/kmsgrab.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 214387b9..6c0642a6 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -294,6 +294,18 @@ public: return false; } + std::uint32_t get_panel_orientation(std::uint32_t plane_id) { + auto props = plane_props(plane_id); + for(auto &[prop, val] : props) { + if(prop->name == "rotation"sv) { + return val; + } + } + + BOOST_LOG(error) << "Failed to determine panel orientation, defaulting to landscape."; + return DRM_MODE_ROTATE_0; + } + connector_interal_t connector(std::uint32_t id) { return drmModeGetConnector(fd.el, id); } @@ -531,11 +543,25 @@ public: if(monitor != std::end(pos->crtc_to_monitor)) { auto &viewport = monitor->second.viewport; - width = viewport.width; - height = viewport.height; + width = viewport.width; + height = viewport.height; + + switch(card.get_panel_orientation(plane->plane_id)) { + case DRM_MODE_ROTATE_270: + BOOST_LOG(debug) << "Detected panel orientation at 90, swapping width and height."; + width = viewport.height; + height = viewport.width; + break; + case DRM_MODE_ROTATE_90: + case DRM_MODE_ROTATE_180: + BOOST_LOG(warning) << "Panel orientation is unsupported, screen capture may not work correctly."; + break; + } + offset_x = viewport.offset_x; offset_y = viewport.offset_y; } + // This code path shouldn't happend, but it's there just in case. // crtc_to_monitor is part of the guesswork after all. else { From 35b660851c5f0b1ce9c31c54501a14c1deaeedf0 Mon Sep 17 00:00:00 2001 From: Jari Date: Wed, 25 Jan 2023 15:26:41 -0600 Subject: [PATCH 37/51] Fix SimpleWeb arbitrary file read vulnurability (#829) --- src/confighttp.cpp | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 1276c597..ecf7c39b 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -246,23 +246,40 @@ void getSunshineLogoImage(resp_https_t response, req_https_t request) { response->write(SimpleWeb::StatusCode::success_ok, in, headers); } +bool isChildPath(fs::path const &base, fs::path const &query) { + auto relPath = fs::relative(base, query); + return *(relPath.begin()) != fs::path(".."); +} + void getNodeModules(resp_https_t response, req_https_t request) { print_req(request); + fs::path webDirPath(WEB_DIR); + fs::path nodeModulesPath(webDirPath / "node_modules"); - SimpleWeb::CaseInsensitiveMultimap headers; - if(boost::algorithm::iends_with(request->path, ".ttf") == 1) { - std::ifstream in((WEB_DIR + request->path).c_str(), std::ios::binary); - headers.emplace("Content-Type", "font/ttf"); - response->write(SimpleWeb::StatusCode::success_ok, in, headers); + // .relative_path is needed to shed any leading slash that might exist in the request path + auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path()); + + // Don't do anything if file does not exist or is outside the node_modules directory + if(!isChildPath(filePath, nodeModulesPath)) { + BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the node_modules folder"; + response->write(SimpleWeb::StatusCode::client_error_bad_request, "Bad Request"); } - else if(boost::algorithm::iends_with(request->path, ".woff2") == 1) { - std::ifstream in((WEB_DIR + request->path).c_str(), std::ios::binary); - headers.emplace("Content-Type", "font/woff2"); - response->write(SimpleWeb::StatusCode::success_ok, in, headers); + else if(!fs::exists(filePath)) { + response->write(SimpleWeb::StatusCode::client_error_not_found); } else { - std::string content = read_file((WEB_DIR + request->path).c_str()); - response->write(content); + auto relPath = fs::relative(filePath, webDirPath); + if(relPath.extension() == ".ttf" or relPath.extension() == ".woff2") { + // Fonts are read differntly + SimpleWeb::CaseInsensitiveMultimap headers; + std::ifstream in((filePath).c_str(), std::ios::binary); + headers.emplace("Content-Type", "font/" + filePath.extension().string().substr(1)); + response->write(SimpleWeb::StatusCode::success_ok, in, headers); + } + else { + std::string content = read_file((filePath.string()).c_str()); + response->write(content); + } } } From 3bb784b3794ff5f67fa59eb6fb31c615e0c9ca0b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:00:21 -0500 Subject: [PATCH 38/51] update docker tag information (#785) --- DOCKER_README.md | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 71338e0a..63639527 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -1,17 +1,43 @@ # Docker +## Important note +Starting with v0.18.0, tag names have changed. You may no longer use `latest`, `master`, `vX.X.X`. + ## Build your own containers This image provides a method for you to easily use the latest Sunshine release in your own docker projects. It is not intended to use as a standalone container at this point, and should be considered experimental. ```dockerfile -FROM lizardbyte/sunshine +ARG SUNSHINE_VERSION=latest +ARG SUNSHINE_OS=ubuntu-22.04 +FROM lizardbyte/sunshine:${SUNSHINE_VERSION}-${SUNSHINE_OS} # install Steam, Wayland, etc. ENTRYPOINT steam && sunshine ``` +### SUNSHINE_VERSION +- `latest`, `master`, `vX.X.X` +- `nightly` +- commit hash + +### SUNSHINE_OS +Sunshine images are available, based on the following base images. + +- `debian-bullseye` +- `fedora-36` +- `fedora-37` +- `ubuntu-20.04` +- `ubuntu-22.04` + +### Tags +You must combine the `SUNSHINE_VERSION` and `SUNSHINE_OS` to determine the tag to pull. The format should be +`-`. For example, `latest-ubuntu-22.04`. + +See all our available tags on [docker hub](https://hub.docker.com/r/lizardbyte/sunshine/tags) or +[ghcr](https://github.com/LizardByte/Sunshine/pkgs/container/sunshine/versions) for more info. + ## Where used This is a list of docker projects using Sunshine. Something missing? Let us know about it! @@ -97,12 +123,12 @@ If you want to change the PUID or PGID after the image has been built, it will r ## Supported Architectures -Specifying `lizardbyte/sunshine:latest` or `ghcr.io/lizardbyte/sunshine:latest` should retrieve the correct -image for your architecture. +Specifying `lizardbyte/sunshine:latest-` or `ghcr.io/lizardbyte/sunshine:latest-` should +retrieve the correct image for your architecture. -The architectures supported by this image are: +The architectures supported by these images are: -| Architecture | Available | -|:------------:|:---------:| -| x86-64 | ✅ | -| arm64 | ✅ | +| Architecture | Available | +|:---------------:|:---------:| +| amd64 / x86_64 | ✅ | +| arm64 / aarch64 | ✅ | From 5a60090ddcb700622b1b7f624496a7c5b9d23175 Mon Sep 17 00:00:00 2001 From: Aaron Rumpler <80610725+Aaron-Rumpler@users.noreply.github.com> Date: Thu, 26 Jan 2023 12:16:03 +1300 Subject: [PATCH 39/51] Rename 'Steam BigPicture' to 'Steam Big Picture' in apps.json (#827) --- src_assets/linux/assets/apps.json | 2 +- src_assets/macos/assets/apps.json | 2 +- src_assets/windows/assets/apps.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src_assets/linux/assets/apps.json b/src_assets/linux/assets/apps.json index 5331dcf8..04d98b62 100644 --- a/src_assets/linux/assets/apps.json +++ b/src_assets/linux/assets/apps.json @@ -18,7 +18,7 @@ ] }, { - "name": "Steam BigPicture", + "name": "Steam Big Picture", "detached": [ "setsid steam steam://open/bigpicture" ], diff --git a/src_assets/macos/assets/apps.json b/src_assets/macos/assets/apps.json index 88559542..dcb8637c 100644 --- a/src_assets/macos/assets/apps.json +++ b/src_assets/macos/assets/apps.json @@ -8,7 +8,7 @@ "image-path": "desktop.png" }, { - "name": "Steam BigPicture", + "name": "Steam Big Picture", "detached": [ "open steam://open/bigpicture" ], diff --git a/src_assets/windows/assets/apps.json b/src_assets/windows/assets/apps.json index 08d2386d..72a56a7e 100644 --- a/src_assets/windows/assets/apps.json +++ b/src_assets/windows/assets/apps.json @@ -8,7 +8,7 @@ "image-path": "desktop.png" }, { - "name": "Steam BigPicture", + "name": "Steam Big Picture", "detached": [ "steam steam://open/bigpicture" ], From 1f2ad8da005b941adaad2ba457aa9ef7c1d99f89 Mon Sep 17 00:00:00 2001 From: Jari Date: Wed, 25 Jan 2023 18:14:59 -0600 Subject: [PATCH 40/51] Scrub basic auth header from logs (#834) --- src/confighttp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index ecf7c39b..e86ca9c1 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -56,7 +56,7 @@ void print_req(const req_https_t &request) { BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; for(auto &[name, val] : request->header) { - BOOST_LOG(debug) << name << " -- " << val; + BOOST_LOG(debug) << name << " -- " << (name == "Authorization" ? "CREDENTIALS REDACTED" : val); } BOOST_LOG(debug) << " [--] "sv; From bcd5188ac538469e8bacd75e071b34a0d6dd4c18 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 25 Jan 2023 19:48:55 -0600 Subject: [PATCH 41/51] Implement HDR support for Windows (#825) --- docs/source/about/usage.rst | 12 ++ src/platform/windows/display.h | 1 + src/platform/windows/display_base.cpp | 73 ++++++++++++ src/platform/windows/display_vram.cpp | 112 ++++++++++++++---- src/stream.cpp | 5 +- src/video.cpp | 70 +++++------ .../shaders/directx/ConvertUVPS_PQ.hlsl | 69 +++++++++++ .../assets/shaders/directx/ConvertYPS_PQ.hlsl | 62 ++++++++++ .../assets/shaders/directx/ScenePS_NW.hlsl | 22 ++++ 9 files changed, 369 insertions(+), 57 deletions(-) create mode 100644 src_assets/windows/assets/shaders/directx/ConvertUVPS_PQ.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index 5a73851d..e48aa1fc 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -257,6 +257,18 @@ Considerations instead it simply starts a stream. - For the Linux flatpak you must prepend commands with ``flatpak-spawn --host``. +HDR Support +----------- +Streaming HDR content is supported for Windows hosts with NVIDIA, AMD, or Intel GPUs that support encoding HEVC Main 10. +You must have an HDR-capable display or EDID emulator dongle connected to your host PC to activate HDR in Windows. + +- Ensure you enable the HDR option in your Moonlight client settings, otherwise the stream will be SDR. +- A good HDR experience relies on proper HDR display calibration both in Windows and in game. HDR calibration can differ significantly between client and host displays. +- We recommend calibrating the display by streaming the Windows HDR Calibration app to your client device and saving an HDR calibration profile to use while streaming. +- You may also need to tune the brightness slider or HDR calibration options in game to the different HDR brightness capabilities of your client's display. +- Older games that use NVIDIA-specific NVAPI HDR rather than native Windows 10 OS HDR support may not display in HDR. +- Some GPUs can produce lower image quality or encoding performance when streaming in HDR compared to SDR. + Tutorials --------- Tutorial videos are available `here `_. diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index a72c94bd..dac853fc 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -151,6 +151,7 @@ protected: } const char *dxgi_format_to_string(DXGI_FORMAT format); + const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type); virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0; virtual int complete_img(img_t *img, bool dummy) = 0; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 5706f86c..39be1112 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -563,6 +563,25 @@ int display_base_t::init(const ::video::config_t &config, const std::string &dis BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']'; BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']'; + dxgi::output6_t output6 {}; + status = output->QueryInterface(IID_IDXGIOutput6, (void **)&output6); + if(SUCCEEDED(status)) { + DXGI_OUTPUT_DESC1 desc1; + output6->GetDesc1(&desc1); + + BOOST_LOG(info) + << std::endl + << "Colorspace : "sv << colorspace_to_string(desc1.ColorSpace) << std::endl + << "Bits Per Color : "sv << desc1.BitsPerColor << std::endl + << "Red Primary : ["sv << desc1.RedPrimary[0] << ',' << desc1.RedPrimary[1] << ']' << std::endl + << "Green Primary : ["sv << desc1.GreenPrimary[0] << ',' << desc1.GreenPrimary[1] << ']' << std::endl + << "Blue Primary : ["sv << desc1.BluePrimary[0] << ',' << desc1.BluePrimary[1] << ']' << std::endl + << "White Point : ["sv << desc1.WhitePoint[0] << ',' << desc1.WhitePoint[1] << ']' << std::endl + << "Min Luminance : "sv << desc1.MinLuminance << " nits"sv << std::endl + << "Max Luminance : "sv << desc1.MaxLuminance << " nits"sv << std::endl + << "Max Full Luminance : "sv << desc1.MaxFullFrameLuminance << " nits"sv; + } + // Capture format will be determined from the first call to AcquireNextFrame() capture_format = DXGI_FORMAT_UNKNOWN; @@ -598,6 +617,23 @@ bool display_base_t::get_hdr_metadata(SS_HDR_METADATA &metadata) { DXGI_OUTPUT_DESC1 desc1; output6->GetDesc1(&desc1); + // The primaries reported here seem to correspond to scRGB (Rec. 709) + // which we then convert to Rec 2020 in our scRGB FP16 -> PQ shader + // prior to encoding. It's not clear to me if we're supposed to report + // the primaries of the original colorspace or the one we've converted + // it to, but let's just report Rec 2020 primaries and D65 white level + // to avoid confusing clients by reporting Rec 709 primaries with a + // Rec 2020 colorspace. It seems like most clients ignore the primaries + // in the metadata anyway (luminance range is most important). + desc1.RedPrimary[0] = 0.708f; + desc1.RedPrimary[1] = 0.292f; + desc1.GreenPrimary[0] = 0.170f; + desc1.GreenPrimary[1] = 0.797f; + desc1.BluePrimary[0] = 0.131f; + desc1.BluePrimary[1] = 0.046f; + desc1.WhitePoint[0] = 0.3127f; + desc1.WhitePoint[1] = 0.3290f; + metadata.displayPrimaries[0].x = desc1.RedPrimary[0] * 50000; metadata.displayPrimaries[0].y = desc1.RedPrimary[1] * 50000; metadata.displayPrimaries[1].x = desc1.GreenPrimary[0] * 50000; @@ -749,6 +785,43 @@ const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) { return format_str[format]; } +const char *display_base_t::colorspace_to_string(DXGI_COLOR_SPACE_TYPE type) { + const char *type_str[] = { + "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709", + "DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709", + "DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709", + "DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020", + "DXGI_COLOR_SPACE_RESERVED", + "DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601", + "DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709", + "DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020", + "DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020", + "DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020", + "DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020", + "DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020", + "DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020", + "DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P709", + "DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020", + "DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020", + }; + + if(type < ARRAYSIZE(type_str)) { + return type_str[type]; + } + else { + return "UNKNOWN"; + } +} + } // namespace platf::dxgi namespace platf { diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index b50f2c24..ab63974b 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -89,9 +89,12 @@ blend_t make_blend(device_t::pointer device, bool enable, bool invert) { blob_t convert_UV_vs_hlsl; blob_t convert_UV_ps_hlsl; +blob_t convert_UV_PQ_ps_hlsl; blob_t scene_vs_hlsl; blob_t convert_Y_ps_hlsl; +blob_t convert_Y_PQ_ps_hlsl; blob_t scene_ps_hlsl; +blob_t scene_NW_ps_hlsl; struct img_d3d_t : public platf::img_t { std::shared_ptr display; @@ -546,28 +549,39 @@ public: return -1; } - status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); - if(status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - status = device->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); if(status) { BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + // If the display is in HDR and we're streaming HDR, we'll be converting scRGB to SMPTE 2084 PQ. + // NB: We can consume scRGB in SDR with our regular shaders because it behaves like UNORM input. + if(format == DXGI_FORMAT_P010 && display->is_hdr()) { + status = device->CreatePixelShader(convert_Y_PQ_ps_hlsl->GetBufferPointer(), convert_Y_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreatePixelShader(convert_UV_PQ_ps_hlsl->GetBufferPointer(), convert_UV_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + } + else { + status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); + if(status) { + BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } } color_matrix = make_buffer(device.get(), ::video::colors[0]); @@ -708,7 +722,6 @@ public: vs_t convert_UV_vs; ps_t convert_UV_ps; ps_t convert_Y_ps; - ps_t scene_ps; vs_t scene_vs; D3D11_VIEWPORT outY_view; @@ -978,10 +991,32 @@ int display_vram_t::init(const ::video::config_t &config, const std::string &dis return -1; } - status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); - if(status) { - BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + if(config.dynamicRange && is_hdr()) { + // This shader will normalize scRGB white levels to a user-defined white level + status = device->CreatePixelShader(scene_NW_ps_hlsl->GetBufferPointer(), scene_NW_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // Use a 300 nit target for the mouse cursor. We should really get + // the user's SDR white level in nits, but there is no API that + // provides that information to Win32 apps. + float sdr_multiplier_data[16 / sizeof(float)] { 300.0f / 80.f }; // aligned to 16-byte + auto sdr_multiplier = make_buffer(device.get(), sdr_multiplier_data); + if(!sdr_multiplier) { + BOOST_LOG(warning) << "Failed to create SDR multiplier"sv; + return -1; + } + + device_ctx->PSSetConstantBuffers(0, 1, &sdr_multiplier); + } + else { + status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps); + if(status) { + BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } } blend_alpha = make_blend(device.get(), true, false); @@ -1108,7 +1143,25 @@ std::vector display_vram_t::get_supported_sdr_capture_formats() { } std::vector display_vram_t::get_supported_hdr_capture_formats() { - return { DXGI_FORMAT_R10G10B10A2_UNORM }; + return { + // scRGB FP16 is the desired format for HDR content. This will also handle + // 10-bit SDR displays with the increased precision of FP16 vs 8-bit UNORMs. + DXGI_FORMAT_R16G16B16A16_FLOAT, + + // DXGI_FORMAT_R10G10B10A2_UNORM seems like it might give us frames already + // converted to SMPTE 2084 PQ, however it seems to actually just clamp the + // scRGB FP16 values that DWM is using when the desktop format is scRGB FP16. + // + // If there is a case where the desktop format is really SMPTE 2084 PQ, it + // might make sense to support capturing it without conversion to scRGB, + // but we avoid it for now. + + // We include the 8-bit modes too for when the display is in SDR mode, + // while the client stream is HDR-capable. These UNORM formats behave + // like a degenerate case of scRGB FP16 with values between 0.0f-1.0f. + DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM, + }; } std::shared_ptr display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) { @@ -1144,11 +1197,21 @@ int init() { return -1; } + convert_Y_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS_PQ.hlsl"); + if(!convert_Y_PQ_ps_hlsl) { + return -1; + } + convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl"); if(!convert_UV_ps_hlsl) { return -1; } + convert_UV_PQ_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS_PQ.hlsl"); + if(!convert_UV_PQ_ps_hlsl) { + return -1; + } + convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl"); if(!convert_UV_vs_hlsl) { return -1; @@ -1158,6 +1221,11 @@ int init() { if(!scene_ps_hlsl) { return -1; } + + scene_NW_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS_NW.hlsl"); + if(!scene_NW_ps_hlsl) { + return -1; + } BOOST_LOG(info) << "Compiled shaders"sv; return 0; diff --git a/src/stream.cpp b/src/stream.cpp index 78e7a3d0..c78fbbd2 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -820,8 +820,11 @@ void controlBroadcastThread(control_server_t *server) { send_rumble(session, rumble->id, rumble->lowfreq, rumble->highfreq); } + // Unlike rumble which we send as best-effort, HDR state messages are critical + // for proper functioning of some clients. We must wait to pop entries from + // the queue until we're sure we have a peer to send them to. auto &hdr_queue = session->control.hdr_queue; - while(hdr_queue->peek()) { + while(session->control.peer && hdr_queue->peek()) { auto hdr_info = hdr_queue->pop(); send_hdr_mode(session, std::move(hdr_info)); diff --git a/src/video.cpp b/src/video.cpp index 91f9bfba..8fddb8f4 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1002,35 +1002,46 @@ std::optional make_session(platf::display_t *disp, const encoder_t &e ctx->color_range = (config.encoderCscMode & 0x1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; int sws_color_space; - switch(config.encoderCscMode >> 1) { - case 0: - default: - // Rec. 601 - BOOST_LOG(info) << "Color coding [Rec. 601]"sv; - ctx->color_primaries = AVCOL_PRI_SMPTE170M; - ctx->color_trc = AVCOL_TRC_SMPTE170M; - ctx->colorspace = AVCOL_SPC_SMPTE170M; - sws_color_space = SWS_CS_SMPTE170M; - break; - - case 1: - // Rec. 709 - BOOST_LOG(info) << "Color coding [Rec. 709]"sv; - ctx->color_primaries = AVCOL_PRI_BT709; - ctx->color_trc = AVCOL_TRC_BT709; - ctx->colorspace = AVCOL_SPC_BT709; - sws_color_space = SWS_CS_ITU709; - break; - - case 2: - // Rec. 2020 - BOOST_LOG(info) << "Color coding [Rec. 2020]"sv; + if(config.dynamicRange && disp->is_hdr()) { + // When HDR is active, that overrides the colorspace the client requested + BOOST_LOG(info) << "HDR color coding [Rec. 2020 + SMPTE 2084 PQ]"sv; ctx->color_primaries = AVCOL_PRI_BT2020; - ctx->color_trc = AVCOL_TRC_BT2020_10; + ctx->color_trc = AVCOL_TRC_SMPTE2084; ctx->colorspace = AVCOL_SPC_BT2020_NCL; sws_color_space = SWS_CS_BT2020; - break; } + else { + switch(config.encoderCscMode >> 1) { + case 0: + default: + // Rec. 601 + BOOST_LOG(info) << "SDR color coding [Rec. 601]"sv; + ctx->color_primaries = AVCOL_PRI_SMPTE170M; + ctx->color_trc = AVCOL_TRC_SMPTE170M; + ctx->colorspace = AVCOL_SPC_SMPTE170M; + sws_color_space = SWS_CS_SMPTE170M; + break; + + case 1: + // Rec. 709 + BOOST_LOG(info) << "SDR color coding [Rec. 709]"sv; + ctx->color_primaries = AVCOL_PRI_BT709; + ctx->color_trc = AVCOL_TRC_BT709; + ctx->colorspace = AVCOL_SPC_BT709; + sws_color_space = SWS_CS_ITU709; + break; + + case 2: + // Rec. 2020 + BOOST_LOG(info) << "SDR color coding [Rec. 2020]"sv; + ctx->color_primaries = AVCOL_PRI_BT2020; + ctx->color_trc = AVCOL_TRC_BT2020_10; + ctx->colorspace = AVCOL_SPC_BT2020_NCL; + sws_color_space = SWS_CS_BT2020; + break; + } + } + BOOST_LOG(info) << "Color range: ["sv << ((config.encoderCscMode & 0x1) ? "JPEG"sv : "MPEG"sv) << ']'; AVPixelFormat sw_fmt; @@ -1039,15 +1050,6 @@ std::optional make_session(platf::display_t *disp, const encoder_t &e } else { sw_fmt = encoder.dynamic_pix_fmt; - - // When HDR is active, that overrides the colorspace the client requested - if(disp->is_hdr()) { - BOOST_LOG(info) << "HDR color coding override [SMPTE ST 2084 PQ]"sv; - ctx->color_primaries = AVCOL_PRI_BT2020; - ctx->color_trc = AVCOL_TRC_SMPTE2084; - ctx->colorspace = AVCOL_SPC_BT2020_NCL; - sws_color_space = SWS_CS_BT2020; - } } // Used by cbs::make_sps_hevc diff --git a/src_assets/windows/assets/shaders/directx/ConvertUVPS_PQ.hlsl b/src_assets/windows/assets/shaders/directx/ConvertUVPS_PQ.hlsl new file mode 100644 index 00000000..3ae0fb1d --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/ConvertUVPS_PQ.hlsl @@ -0,0 +1,69 @@ +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +struct FragTexWide { + float3 uuv : TEXCOORD0; +}; + +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; + +float3 NitsToPQ(float3 L) +{ + // Constants from SMPTE 2084 PQ + static const float m1 = 2610.0 / 4096.0 / 4; + static const float m2 = 2523.0 / 4096.0 * 128; + static const float c1 = 3424.0 / 4096.0; + static const float c2 = 2413.0 / 4096.0 * 32; + static const float c3 = 2392.0 / 4096.0 * 32; + + float3 Lp = pow(saturate(L / 10000.0), m1); + return pow((c1 + c2 * Lp) / (1 + c3 * Lp), m2); +} + +float3 Rec709toRec2020(float3 rec709) +{ + static const float3x3 ConvMat = + { + 0.627402, 0.329292, 0.043306, + 0.069095, 0.919544, 0.011360, + 0.016394, 0.088028, 0.895578 + }; + return mul(ConvMat, rec709); +} + +float3 scRGBTo2100PQ(float3 rgb) +{ + // Convert from Rec 709 primaries (used by scRGB) to Rec 2020 primaries (used by Rec 2100) + rgb = Rec709toRec2020(rgb); + + // 1.0f is defined as 80 nits in the scRGB colorspace + rgb *= 80; + + // Apply the PQ transfer function on the raw color values in nits + return NitsToPQ(rgb); +} + +//-------------------------------------------------------------------------------------- +// Pixel Shader +//-------------------------------------------------------------------------------------- +float2 main_ps(FragTexWide input) : SV_Target +{ + float3 rgb_left = scRGBTo2100PQ(image.Sample(def_sampler, input.uuv.xz).rgb); + float3 rgb_right = scRGBTo2100PQ(image.Sample(def_sampler, input.uuv.yz).rgb); + float3 rgb = (rgb_left + rgb_right) * 0.5; + + float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; + float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + + u = u * range_uv.x + range_uv.y; + v = v * range_uv.x + range_uv.y; + + return float2(u, v); +} diff --git a/src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl b/src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl new file mode 100644 index 00000000..cc3054e8 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/ConvertYPS_PQ.hlsl @@ -0,0 +1,62 @@ +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +cbuffer ColorMatrix : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +float3 NitsToPQ(float3 L) +{ + // Constants from SMPTE 2084 PQ + static const float m1 = 2610.0 / 4096.0 / 4; + static const float m2 = 2523.0 / 4096.0 * 128; + static const float c1 = 3424.0 / 4096.0; + static const float c2 = 2413.0 / 4096.0 * 32; + static const float c3 = 2392.0 / 4096.0 * 32; + + float3 Lp = pow(saturate(L / 10000.0), m1); + return pow((c1 + c2 * Lp) / (1 + c3 * Lp), m2); +} + +float3 Rec709toRec2020(float3 rec709) +{ + static const float3x3 ConvMat = + { + 0.627402, 0.329292, 0.043306, + 0.069095, 0.919544, 0.011360, + 0.016394, 0.088028, 0.895578 + }; + return mul(ConvMat, rec709); +} + +float3 scRGBTo2100PQ(float3 rgb) +{ + // Convert from Rec 709 primaries (used by scRGB) to Rec 2020 primaries (used by Rec 2100) + rgb = Rec709toRec2020(rgb); + + // 1.0f is defined as 80 nits in the scRGB colorspace + rgb *= 80; + + // Apply the PQ transfer function on the raw color values in nits + return NitsToPQ(rgb); +} + +float main_ps(PS_INPUT frag_in) : SV_Target +{ + float3 rgb = scRGBTo2100PQ(image.Sample(def_sampler, frag_in.tex, 0).rgb); + + float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; + + return y * range_y.x + range_y.y; +} diff --git a/src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl b/src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl new file mode 100644 index 00000000..9088b767 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/ScenePS_NW.hlsl @@ -0,0 +1,22 @@ +Texture2D image : register(t0); + +SamplerState def_sampler : register(s0); + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float2 tex : TEXCOORD; +}; + +cbuffer SdrScaling : register(b0) { + float scale_factor; +}; + +float4 main_ps(PS_INPUT frag_in) : SV_Target +{ + float4 rgba = image.Sample(def_sampler, frag_in.tex, 0); + + rgba.rgb = rgba.rgb * scale_factor; + + return rgba; +} From d33e3a2882f51d120314f6a262e049225dad2f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=81=D1=82=D0=B0=D0=B4=D0=B8=D0=BD=20=D0=94?= =?UTF-8?q?=D0=B0=D0=BC=D1=8F=D0=BD=D0=BE=D0=B2?= Date: Thu, 26 Jan 2023 16:10:13 +0200 Subject: [PATCH 42/51] Restart the systemd service in case of a crash (#830) --- docs/source/about/usage.rst | 4 ++++ sunshine.service.in | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst index e48aa1fc..7861e523 100644 --- a/docs/source/about/usage.rst +++ b/docs/source/about/usage.rst @@ -87,9 +87,13 @@ Sunshine needs access to `uinput` to create mouse and gamepad events. [Unit] Description=Sunshine self-hosted game stream host for Moonlight. + StartLimitIntervalSec=500 + StartLimitBurst=5 [Service] ExecStart= + Restart=on-failure + RestartSec=5s #Flatpak Only #ExecStop=flatpak kill dev.lizardbyte.sunshine diff --git a/sunshine.service.in b/sunshine.service.in index 157b3a4d..965b7871 100644 --- a/sunshine.service.in +++ b/sunshine.service.in @@ -1,8 +1,12 @@ [Unit] Description=@PROJECT_DESCRIPTION@ +StartLimitIntervalSec=500 +StartLimitBurst=5 [Service] ExecStart=@SUNSHINE_EXECUTABLE_PATH@ +Restart=on-failure +RestartSec=5s [Install] WantedBy=graphical-session.target From 0698210ce19575f8684076a3bc73ded6347d9e25 Mon Sep 17 00:00:00 2001 From: manteuffel723 <40317957+manteuffel723@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:23:44 +0100 Subject: [PATCH 43/51] Fix Error: Couldn't import RGB Image: 00003002 and 00003004 (#761) --- src/platform/linux/kmsgrab.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 6c0642a6..240ce696 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -610,9 +610,12 @@ public: for(int y = 0; y < 4; ++y) { if(!fb->handles[y]) { - // It's not clear wheter there could still be valid handles left. + // setting sd->fds[y] to a negative value indicates that sd->offsets[y] and sd->pitches[y] + // are uninitialized and contain invalid values. + sd->fds[y] = -1; + // It's not clear whether there could still be valid handles left. // So, continue anyway. - // TODO: Is this redundent? + // TODO: Is this redundant? continue; } From 8b507d2b30c2f498d2f9471bdfb6ce8135c439b2 Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Thu, 26 Jan 2023 17:12:49 +0000 Subject: [PATCH 44/51] Fix image leak of hardware parallel encoder on display reinit (#823) --- src/video.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 8fddb8f4..a86055c6 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1574,6 +1574,12 @@ void capture_async( platf::adjust_thread_priority(platf::thread_priority_e::high); while(!shutdown_event->peek() && images->running()) { + // Free images that weren't consumed by the encoder before it quit. + // This is critical to allow the display_t to be freed correctly. + while(images->peek()) { + images->pop(); + } + // Wait for the main capture event when the display is being reinitialized if(ref->reinit_event.peek()) { std::this_thread::sleep_for(100ms); @@ -1622,12 +1628,6 @@ void capture_async( std::move(hwdevice), ref->reinit_event, *ref->encoder_p, channel_data); - - // Free images that weren't consumed by the encoder before it quit. - // This is critical to allow the display_t to be freed correctly. - while(images->peek()) { - images->pop(); - } } } From a3e2c4fa31a528e4a31d33dee9d4a1bfbbfcaf16 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 27 Jan 2023 09:21:05 -0600 Subject: [PATCH 45/51] Free AVFrame before calling eglTerminate() (#842) --- src/platform/linux/vaapi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 07f0d323..8f97db27 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -386,12 +386,14 @@ public: va::display_t::pointer va_display; file_t file; - frame_t hwframe; - gbm::gbm_t gbm; egl::display_t display; egl::ctx_t ctx; + // This must be destroyed before display_t to ensure the GPU + // driver is still loaded when vaDestroySurfaces() is called. + frame_t hwframe; + egl::sws_t sws; egl::nv12_t nv12; From 142f06714f82ab22a2c06bec1d2d5ca0ce758261 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 27 Jan 2023 19:16:32 -0600 Subject: [PATCH 46/51] Windows installer fixes (#843) --- CMakeLists.txt | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 463bbf69..df339906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -633,15 +633,17 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Install service SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} - ExecWait '\\\"$SYSDIR\\\\cmd.exe\\\" /c \\\"start https://sunshinestream.readthedocs.io/\\\"' - ExecWait 'icacls \\\"$INSTDIR\\\" /reset' - ExecWait '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"' - ExecWait 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' - ExecWait '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"' - ExecWait '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"' - MessageBox MB_YESNO|MB_ICONQUESTION 'Do you want to add/update ViGEmBus (virtual controller support)?' \ - IDNO NoController - ExecWait '\\\"$SYSDIR\\\\cmd.exe\\\" /c \\\"start https://github.com/ViGEm/ViGEmBus/releases/latest\\\"'; \ + IfSilent +2 0 + ExecShell 'open' 'https://sunshinestream.readthedocs.io/' + nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"' + nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"' + MessageBox MB_YESNO|MB_ICONQUESTION \ + 'Do you want to install ViGEmBus? This is REQUIRED for gamepad support while streaming.' \ + /SD IDNO IDNO NoController + ExecShell 'open' 'https://github.com/ViGEm/ViGEmBus/releases/latest'; \ skipped if no NoController: ") @@ -650,8 +652,8 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h # Uninstall service set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS} - ExecWait '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"' - ExecWait '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"' MessageBox MB_YESNO|MB_ICONQUESTION \ 'Do you want to remove $INSTDIR (this includes the configuration, cover images, and settings)?' \ /SD IDNO IDNO NoDelete @@ -667,12 +669,12 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") set(CPACK_NSIS_CREATE_ICONS_EXTRA "${CPACK_NSIS_CREATE_ICONS_EXTRA} - CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' \ + CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk' \ '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe' ") set(CPACK_NSIS_DELETE_ICONS_EXTRA "${CPACK_NSIS_DELETE_ICONS_EXTRA} - Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME}.lnk' + Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk' ") # Checking for previous installed versions From 9155e39e9130f66393d5f0235c1c4c27441f9582 Mon Sep 17 00:00:00 2001 From: istori1 <107304850+istori1@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:21:16 -0400 Subject: [PATCH 47/51] Flatpak build improvements (#784) --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index 1bd9926e..1ad23b3e 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -107,24 +107,7 @@ modules: commands: - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done - - name: libdrm - # libdrm-intel is needed for libmfx-dev - disabled: false - buildsystem: meson - only-arches: - - x86_64 - sources: - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110.orig.tar.xz - sha256: eecee4c4b47ed6d6ce1a9be3d6d92102548ea35e442282216d47d05293cf9737 - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110-1ubuntu1.debian.tar.xz - sha256: 464b9553861f39beddfaee6b8924734b02a0febfae3968e4ca1360f2972bba8b - - type: shell - commands: - - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done - - - name: libmfx-dev + - name: intel-mediasdk disabled: false buildsystem: cmake config-opts: @@ -153,7 +136,39 @@ modules: - type: shell commands: - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + modules: + - name: libdrm + disabled: false + buildsystem: meson + sources: + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110.orig.tar.xz + sha256: eecee4c4b47ed6d6ce1a9be3d6d92102548ea35e442282216d47d05293cf9737 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/libd/libdrm/libdrm_2.4.110-1ubuntu1.debian.tar.xz + sha256: 464b9553861f39beddfaee6b8924734b02a0febfae3968e4ca1360f2972bba8b + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + - name: numactl + buildsystem: autotools + make-args: + - install + sources: + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14.orig.tar.gz + sha256: 1ee27abd07ff6ba140aaf9bc6379b37825e54496e01d6f7343330cf1a4487035 + - type: archive + url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14-3ubuntu2.debian.tar.xz + sha256: 49089e5be5367f6367f8b0389d1d523944432607783b53f0605705792e1015ee + - type: shell + commands: + - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done + cleanup: + - "/bin" + + # Caching is configured until here, not including CUDA - name: cuda disabled: false buildsystem: simple @@ -161,7 +176,7 @@ modules: - x86_64 - aarch64 cleanup: - - '*' + - "*" build-commands: - chmod u+x ./cuda.run - ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm @@ -182,23 +197,6 @@ modules: sha256: e6e9a8d31163c9776b5e313fd7590877c5684e1ecddee741154f95704d4ed27c dest-filename: cuda.run - - name: numactl - buildsystem: autotools - make-args: - - install - sources: - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14.orig.tar.gz - sha256: 1ee27abd07ff6ba140aaf9bc6379b37825e54496e01d6f7343330cf1a4487035 - - type: archive - url: http://archive.ubuntu.com/ubuntu/pool/main/n/numactl/numactl_2.0.14-3ubuntu2.debian.tar.xz - sha256: 49089e5be5367f6367f8b0389d1d523944432607783b53f0605705792e1015ee - - type: shell - commands: - - for n in $(cat patches/series); do if [[ $n != "#"* ]]; then patch -Np1 -i "patches/$n" -d .; fi; done - cleanup: - - "/bin" - - name: sunshine disabled: false buildsystem: cmake @@ -228,9 +226,9 @@ modules: - -DSUNSHINE_CONFIGURE_FLATPAK=ON sources: - type: git - url: '@GITHUB_CLONE_URL@' - branch: '@GITHUB_BRANCH@' - commit: '@GITHUB_COMMIT@' + url: "@GITHUB_CLONE_URL@" + branch: "@GITHUB_BRANCH@" + commit: "@GITHUB_COMMIT@" post-install: # use `sed` to update apps.json with prefixes required for flatpak # -r (regex) From 592f3a70a37ac626bd6f1e189ae551b5234d0806 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 28 Jan 2023 23:15:37 -0600 Subject: [PATCH 48/51] Display initialization and frame ownership fixes (#850) --- src/video.cpp | 53 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index a86055c6..9389ee3d 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -711,6 +711,7 @@ void reset_display(std::shared_ptr &disp, AVHWDeviceType type, break; } + // The capture code depends on us to sleep between failures std::this_thread::sleep_for(200ms); } } @@ -839,16 +840,32 @@ void captureThread( // Wait for the other shared_ptr's of display to be destroyed. // New displays will only be created in this thread. while(display_wp->use_count() != 1) { - std::this_thread::sleep_for(100ms); + // Free images that weren't consumed by the encoders. These can reference the display and prevent + // the ref count from reaching 1. We do this here rather than on the encoder thread to avoid race + // conditions where the encoding loop might free a good frame after reinitializing if we capture + // a new frame here before the encoder has finished reinitializing. + KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), { + if(!capture_ctx->images->running()) { + capture_ctx = capture_ctxs.erase(capture_ctx); + continue; + } + + while(capture_ctx->images->peek()) { + capture_ctx->images->pop(); + } + + ++capture_ctx; + }); + + std::this_thread::sleep_for(20ms); } while(capture_ctx_queue->running()) { + // reset_display() will sleep between retries reset_display(disp, encoder.base_dev_type, display_names[display_p], capture_ctxs.front().config); - if(disp) { break; } - std::this_thread::sleep_for(200ms); } if(!disp) { return; @@ -1273,6 +1290,13 @@ void encode_run( auto packets = mail::man->queue(mail::video_packets); auto idr_events = mail->event(mail::idr); + // Load a dummy image into the AVFrame to ensure we have something to encode + // even if we time out waiting on the first frame. + auto dummy_img = disp->alloc_img(); + if(!dummy_img || disp->dummy_img(dummy_img.get()) || session->device->convert(*dummy_img)) { + return; + } + while(true) { if(shutdown_event->peek() || reinit_event.peek() || !images->running()) { break; @@ -1288,7 +1312,10 @@ void encode_run( // Encode at a minimum of 10 FPS to avoid image quality issues with static content if(!frame->key_frame || images->peek()) { if(auto img = images->pop(100ms)) { - session->device->convert(*img); + if(session->device->convert(*img)) { + BOOST_LOG(error) << "Could not convert image"sv; + return; + } } else if(!images->running()) { break; @@ -1399,12 +1426,11 @@ encode_e encode_run_sync( } while(encode_session_ctx_queue.running()) { + // reset_display() will sleep between retries reset_display(disp, encoder.base_dev_type, display_names[display_p], synced_session_ctxs.front()->config); if(disp) { break; } - - std::this_thread::sleep_for(200ms); } if(!disp) { @@ -1574,15 +1600,9 @@ void capture_async( platf::adjust_thread_priority(platf::thread_priority_e::high); while(!shutdown_event->peek() && images->running()) { - // Free images that weren't consumed by the encoder before it quit. - // This is critical to allow the display_t to be freed correctly. - while(images->peek()) { - images->pop(); - } - // Wait for the main capture event when the display is being reinitialized if(ref->reinit_event.peek()) { - std::this_thread::sleep_for(100ms); + std::this_thread::sleep_for(20ms); continue; } // Wait for the display to be ready @@ -1603,13 +1623,6 @@ void capture_async( return; } - auto dummy_img = display->alloc_img(); - if(!dummy_img || display->dummy_img(dummy_img.get())) { - return; - } - - images->raise(std::move(dummy_img)); - // absolute mouse coordinates require that the dimensions of the screen are known touch_port_event->raise(make_port(display.get(), config)); From 6cc13b8ef6a8550753912f2acd1559ee53106f55 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 29 Jan 2023 17:00:14 -0500 Subject: [PATCH 49/51] docs: update changelog for v0.18.0 (#836) --- CHANGELOG.md | 28 +++++++++++++++++++++++++++ CMakeLists.txt | 2 +- docs/source/gamestream/gamestream.rst | 1 - 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2521b68..6275b74d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,31 @@ # Changelog +Attention, this release contains critical security fixes. Please update as soon as possible. Additionally, we are +encouraging users to change your Sunshine password, especially if you expose the web UI (i.e. port 47790 by default) +to the internet, or have ever uploaded your logs with verbose output to a public resource. + +## [0.18.0] - 2023-01-25 +### Added +- (Windows) Add support for Intel QuickSync +- (Linux) Added aarch64 deb and rpm packages +- (Windows) Add support for hybrid graphics systems, such as laptops with both integrated and discrete GPUs +- (Linux) Add support for streaming from Steam Deck Gaming Mode +- (Windows) Add HDR support, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/usage.html#hdr-support +### Fixed +- (Network) Refactor code for UPnP port forwarding +- (Video) Enforce 10 FPS encoding frame rate minimum to improve static image quality +- (Linux) deb and rpm packages are now specific to destination distro and version +- (Docs) Add nvidia/nvenc preset migration guide +- (Network) Performance optimizations +- (Video/Windows) Fix streaming to multiple clients from hardware encoder +- (Linux) Fix child process spawning +- (Security) Fix security vulnerability in implementation of SimpleWebServer +- (Misc) Rename "Steam BigPicture" to "Steam Big Picture" in default apps.json +- (Security) Scrub basic authorization header from logs +- (Linux) The systemd service will now restart in the event of a crash +- (Video/KMS/Linux) Fixed error: "couldn't import RGB Image: 00003002 and 00003004" +- (Video/Windows) Fix stream freezing triggered by the resolution changed +- (Installer/Windows) Fixes silent installation and other miscellaneous improvements +- (CPU) Significantly improved CPU usage ## [0.17.0] - 2023-01-08 If you are running Sunshine as a service on Windows, we are strongly urging you to update to v0.17.0 as soon as @@ -272,3 +299,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri [0.15.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.15.0 [0.16.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.16.0 [0.17.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.17.0 +[0.18.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.18.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index df339906..62b72fcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.18) # `CMAKE_CUDA_ARCHITECTURES` requires 3.18 -project(Sunshine VERSION 0.17.0 +project(Sunshine VERSION 0.18.0 DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight." HOMEPAGE_URL "https://app.lizardbyte.dev") diff --git a/docs/source/gamestream/gamestream.rst b/docs/source/gamestream/gamestream.rst index c5c8d68d..59ba6e1d 100644 --- a/docs/source/gamestream/gamestream.rst +++ b/docs/source/gamestream/gamestream.rst @@ -16,6 +16,5 @@ Limitations ----------- Sunshine does have some limitations, as compared to Nvidia GameStream. -- HDR support is limited and currently HDR is converted to SDR. - Automatic game/application list. - Changing game settings automatically, to optimize streaming. From df6e15f1f707ffe70fd56e91f5088fa1cb8978e5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 29 Jan 2023 17:42:53 -0500 Subject: [PATCH 50/51] changelog: fix v0.18.0 formatting (#857) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6275b74d..e0251dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog + +## [0.18.0] - 2023-01-29 Attention, this release contains critical security fixes. Please update as soon as possible. Additionally, we are encouraging users to change your Sunshine password, especially if you expose the web UI (i.e. port 47790 by default) to the internet, or have ever uploaded your logs with verbose output to a public resource. -## [0.18.0] - 2023-01-25 ### Added - (Windows) Add support for Intel QuickSync - (Linux) Added aarch64 deb and rpm packages From 98d61b16f6e58e652b594a063a2929af790bcb7b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 29 Jan 2023 18:38:47 -0500 Subject: [PATCH 51/51] changelog: fix parsing issue with single quotes (#858) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0251dc7..9110aa67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ to the internet, or have ever uploaded your logs with verbose output to a public - (Misc) Rename "Steam BigPicture" to "Steam Big Picture" in default apps.json - (Security) Scrub basic authorization header from logs - (Linux) The systemd service will now restart in the event of a crash -- (Video/KMS/Linux) Fixed error: "couldn't import RGB Image: 00003002 and 00003004" +- (Video/KMS/Linux) Fixed error: `couldn't import RGB Image: 00003002 and 00003004` - (Video/Windows) Fix stream freezing triggered by the resolution changed - (Installer/Windows) Fixes silent installation and other miscellaneous improvements - (CPU) Significantly improved CPU usage