diff --git a/.gitignore b/.gitignore index c676cc4c..c15ad42a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ cmake-build-* *.swp *.kdev4 -.idea +.idea \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 5938843f..db296a31 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/ViGEm/ViGEmClient [submodule "pre-compiled"] path = pre-compiled - url = https://bitbucket.org/Loki-47-6F-64/pre-compiled.git + url = https://github.com/TheElixZammuto/sunshine-prebuilt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 177ce096..63f705ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ if(WIN32) ${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a ${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a ${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a + ${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a) endif() else() @@ -69,6 +70,7 @@ if(WIN32) ViGEmClient/include/ViGEm/Util.h ViGEmClient/include/ViGEm/km/BusShared.h) list(PREPEND PLATFORM_LIBRARIES + ssp winmm ksuser wsock32 diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 7a318ed4..356acd79 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -129,6 +129,7 @@ # Force a specific encoder, otherwise Sunshine will use the first encoder that is available # supported encoders: # nvenc +# amdvce # NOTE: alpha stage. The cursor is not yet displayed # software # # encoder = nvenc @@ -173,6 +174,31 @@ ########################## # nv_coder = auto +##################################### AMD ##################################### +###### presets ########### +# default +# speed +# balanced +########################## +# amd_preset = balanced +# +####### rate control ##### +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr -- variable bitrate +# cbr -- constant bitrate +# cbr_hq -- cbr high quality +# cbr_ld_hq -- cbr low delay high quality +# vbr_hq -- vbr high quality +########################## +# amd_rc = auto + +###### h264 entropy ###### +# auto -- let ffmpeg nvenc decide the entropy encoding +# cabac +# cavlc +########################## +# amd_coder = auto ############################################## # Some configurable parameters, are merely toggles for specific features diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 26878011..88cc4502 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -85,6 +85,60 @@ int coder_from_view(const std::string_view &coder) { } } +namespace amd { +enum quality_e : int { + _default = 0, + speed, + balanced, + //quality2, +}; + +enum rc_e : int { + constqp = 0x0, /**< Constant QP mode */ + vbr = 0x1, /**< Variable bitrate mode */ + cbr = 0x2, /**< Constant bitrate mode */ + cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ + cbr_hq = 0x10, /**< CBR, high quality (slower) */ + vbr_hq = 0x20 /**< VBR, high quality (slower) */ +}; + +enum coder_e : int { + _auto = 0, + cabac, + cavlc +}; + +std::optional quality_from_view(const std::string_view &quality) { +#define _CONVERT_(x) if(quality == #x##sv) return x + _CONVERT_(speed); + _CONVERT_(balanced); + //_CONVERT_(quality2); + if(quality == "default"sv) return _default; +#undef _CONVERT_ + return std::nullopt; +} + +std::optional rc_from_view(const std::string_view &rc) { +#define _CONVERT_(x) if(rc == #x##sv) return x + _CONVERT_(constqp); + _CONVERT_(vbr); + _CONVERT_(cbr); + _CONVERT_(cbr_hq); + _CONVERT_(vbr_hq); + _CONVERT_(cbr_ld_hq); +#undef _CONVERT_ + return std::nullopt; +} + +int coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + + return -1; +} +} + video_t video { 0, // crf 28, // qp @@ -103,6 +157,12 @@ video_t video { -1 }, // nv + { + amd::balanced, + std::nullopt, + -1 + }, // amd + {}, // encoder {}, // adapter_name {}, // output_name @@ -332,6 +392,9 @@ int apply_flags(const char *line) { case '1': config::sunshine.flags[config::flag::FRESH_STATE].flip(); break; + case 'p': + config::sunshine.flags[config::flag::CONST_PIN].flip(); + break; default: std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl; ret = -1; @@ -357,8 +420,13 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "sw_preset", video.sw.preset); string_f(vars, "sw_tune", video.sw.tune); int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); - int_f(vars, "nv_rc", video.nv.preset, nv::rc_from_view); + 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, "amd_quality", video.amd.quality, amd::quality_from_view); + int_f(vars, "amd_rc", video.amd.rc, amd::rc_from_view); + int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view); + string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); diff --git a/sunshine/config.h b/sunshine/config.h index 9c14b1fe..f67ac12b 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -26,6 +26,12 @@ struct video_t { int coder; } nv; + struct { + std::optional quality; + std::optional rc; + int coder; + } amd; + std::string encoder; std::string adapter_name; std::string output_name; @@ -71,7 +77,8 @@ namespace flag { enum flag_e : std::size_t { PIN_STDIN = 0, // Read PIN from stdin instead of http FRESH_STATE, // Do not load or save state - FLAG_SIZE + FLAG_SIZE, + CONST_PIN= 4 // Use "universal" pin }; } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6ce58a7e..da402706 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -360,7 +360,11 @@ void pair(std::shared_ptr> &add_cert, std::shared_ ptr->second.async_insert_pin.salt = std::move(args.at("salt"s)); - if(config::sunshine.flags[config::flag::PIN_STDIN]) { + if(config::sunshine.flags[config::flag::CONST_PIN]) { + std::string pin("6174"); + getservercert(ptr->second, tree, pin); + } + else if(config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; std::cout << "Please insert pin: "sv; diff --git a/sunshine/platform/windows/desktop.h b/sunshine/platform/windows/desktop.h new file mode 100644 index 00000000..b0b2e69b --- /dev/null +++ b/sunshine/platform/windows/desktop.h @@ -0,0 +1,18 @@ +namespace platf { + using namespace std::literals; + inline auto pairInputDesktop(){ + auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL); + if (NULL == hDesk) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } else { + BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']'; + if (!SetThreadDesktop(hDesk) ) { + auto err = GetLastError(); + BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']'; + } + CloseDesktop(hDesk); + } + return hDesk; + }; +}; \ No newline at end of file diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h index 662115e8..039e6a75 100644 --- a/sunshine/platform/windows/display.h +++ b/sunshine/platform/windows/display.h @@ -84,6 +84,19 @@ public: DXGI_FORMAT format; D3D_FEATURE_LEVEL feature_level; + + typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS + { + D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, + D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, + D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, + D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME + } + D3DKMT_SCHEDULINGPRIORITYCLASS; + + typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS); }; class display_ram_t : public display_base_t { diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp index 4b42880c..cf5c5e4f 100644 --- a/sunshine/platform/windows/display_base.cpp +++ b/sunshine/platform/windows/display_base.cpp @@ -10,6 +10,8 @@ #include "display.h" +#include "desktop.h" + namespace platf { using namespace std::literals; } @@ -90,6 +92,8 @@ int display_base_t::init() { FreeLibrary(user32); }); */ + pairInputDesktop(); + dxgi::factory1_t::pointer factory_p {}; dxgi::adapter_t::pointer adapter_p {}; dxgi::output_t::pointer output_p {}; @@ -150,8 +154,6 @@ int display_base_t::init() { } D3D_FEATURE_LEVEL featureLevels[] { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -164,7 +166,6 @@ int display_base_t::init() { status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; - return -1; } @@ -206,6 +207,36 @@ int display_base_t::init() { // Bump up thread priority { + const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY; + TOKEN_PRIVILEGES tp; + HANDLE token; + LUID val; + + if (OpenProcessToken(GetCurrentProcess(), flags, &token) && + !!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) { + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = val; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) { + BOOST_LOG(error) << "Could not set privilege to increase GPU priority"; + } + } + + CloseHandle(token); + + HMODULE gdi32 = GetModuleHandleA("GDI32"); + if (gdi32) { + PD3DKMTSetProcessSchedulingPriorityClass fn = + (PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass"); + if (fn) { + status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance."; + } + } + } + dxgi::dxgi_t::pointer dxgi_p {}; status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); dxgi::dxgi_t dxgi { dxgi_p }; diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 60120738..9b43494f 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -12,11 +12,15 @@ #include "sunshine/main.h" #include "sunshine/platform/common.h" +#include "desktop.h" + namespace platf { using namespace std::literals; using adapteraddrs_t = util::c_ptr; +volatile HDESK _lastKnownInputDesktop = NULL; + class vigem_t { public: using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; @@ -171,9 +175,15 @@ void move_mouse(input_t &input, int deltaX, int deltaY) { mi.dwFlags = MOUSEEVENTF_MOVE; mi.dx = deltaX; mi.dy = deltaY; - + +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } BOOST_LOG(warning) << "Couldn't send mouse movement input"sv; } } @@ -218,8 +228,14 @@ void button_mouse(input_t &input, int button, bool release) { return; } +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } BOOST_LOG(warning) << "Couldn't send mouse button input"sv; } } @@ -233,9 +249,15 @@ void scroll(input_t &input, int distance) { mi.dwFlags = MOUSEEVENTF_WHEEL; mi.mouseData = distance; +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send mouse scroll input"sv; } } @@ -282,9 +304,15 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { ki.dwFlags |= KEYEVENTF_KEYUP; } +retry: auto send = SendInput(1, &i, sizeof(INPUT)); if(send != 1) { - BOOST_LOG(warning) << "Couldn't send moue movement input"sv; + auto hDesk = pairInputDesktop(); + if (_lastKnownInputDesktop != hDesk) { + _lastKnownInputDesktop = hDesk; + goto retry; + } + BOOST_LOG(warning) << "Couldn't send keyboard input"sv; } } diff --git a/sunshine/video.cpp b/sunshine/video.cpp index f32cd60f..b2e23855 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -58,6 +58,20 @@ enum class profile_hevc_e : int { }; } +namespace amd { + +enum class profile_h264_e : int { + main, + high, + constrained_baseline, + constrained_high, +}; + +enum class profile_hevc_e : int { + main, +}; +} + using ctx_t = util::safe_ptr; using frame_t = util::safe_ptr; using buffer_t = util::safe_ptr; @@ -70,6 +84,8 @@ platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); void sw_img_to_frame(const platf::img_t &img, frame_t &frame); void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); +void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); +util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); @@ -284,6 +300,40 @@ static encoder_t nvenc { nv_d3d_img_to_frame, nv_d3d_make_hwdevice_ctx }; + +static encoder_t amdvce { + "amdvce"sv, + { (int)amd::profile_h264_e::high, (int)amd::profile_hevc_e::main }, + AV_HWDEVICE_TYPE_D3D11VA, + AV_PIX_FMT_D3D11, + AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, + { + { + { "header_insertion_mode"s, "idr"s }, + { "gops_per_idr"s, 30 }, + { "usage"s, "ultralowlatency"s }, + { "quality"s, &config::video.amd.quality }, + { "rc"s, &config::video.amd.rc } + }, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + "hevc_amf"s, + }, + { + { + { "usage"s, "ultralowlatency"s }, + { "quality"s, &config::video.amd.quality }, + { "rc"s, &config::video.amd.rc }, + {"log_to_dbg"s,"1"s}, + }, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + "h264_amf"s + }, + false, + true, + + amd_d3d_img_to_frame, + amd_d3d_make_hwdevice_ctx +}; #endif static encoder_t software { @@ -324,7 +374,10 @@ static std::vector encoders { #ifdef _WIN32 nvenc, #endif - software + software, +#ifdef _WIN32 + amdvce, +#endif }; void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { @@ -416,7 +469,14 @@ void captureThread( std::this_thread::sleep_for(100ms); } - reset_display(disp, encoder.dev_type); + while(capture_ctx_queue->running()) { + reset_display(disp, encoder.dev_type); + + if(disp) { + break; + } + std::this_thread::sleep_for(200ms); + } if(!disp) { return; } @@ -720,7 +780,7 @@ void encode_run( if(idr_events->peek()) { session->frame->pict_type = AV_PICTURE_TYPE_I; - + session->frame->key_frame = 1; auto event = idr_events->pop(); if(!event) { return; @@ -732,6 +792,7 @@ void encode_run( } else if(frame_nr == key_frame_nr) { session->frame->pict_type = AV_PICTURE_TYPE_I; + session->frame->key_frame = 1; } std::this_thread::sleep_until(next_frame); @@ -758,6 +819,7 @@ void encode_run( } session->frame->pict_type = AV_PICTURE_TYPE_NONE; + session->frame->key_frame = 0; } } @@ -791,7 +853,16 @@ encode_e encode_run_sync(std::vector> &synce const auto &encoder = encoders.front(); std::shared_ptr disp; - reset_display(disp, encoder.dev_type); + + while(encode_session_ctx_queue.running()) { + reset_display(disp, encoder.dev_type); + if(disp) { + break; + } + + std::this_thread::sleep_for(200ms); + } + if(!disp) { return encode_e::error; } @@ -870,6 +941,7 @@ encode_e encode_run_sync(std::vector> &synce if(ctx->idr_events->peek()) { pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + pos->session.frame->key_frame = 1; auto event = ctx->idr_events->pop(); auto end = event->second; @@ -879,6 +951,7 @@ encode_e encode_run_sync(std::vector> &synce } else if(ctx->frame_nr == ctx->key_frame_nr) { pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + pos->session.frame->key_frame = 1; } if(img_tmp) { @@ -917,6 +990,7 @@ encode_e encode_run_sync(std::vector> &synce } pos->session.frame->pict_type = AV_PICTURE_TYPE_NONE; + pos->session.frame->key_frame = 0; ++pos; }) @@ -1249,6 +1323,30 @@ void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { frame->width = img.width; } +void amd_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { + if(img.data == frame->data[0]) { + return; + } + + // 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; +} + + util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; @@ -1269,6 +1367,27 @@ util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice return ctx_buf; } + +util::Either amd_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { + buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; + auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; + + std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); + + auto device = (ID3D11Device*)hwdevice_ctx->data; + device->AddRef(); + ctx->device = device; + + auto err = av_hwdevice_ctx_init(ctx_buf.get()); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + BOOST_LOG(error) << "Failed to create FFMpeg amddech: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + + return err; + } + + return ctx_buf; +} #endif int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) {