diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0319c274..4bd0fa1a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -969,24 +969,29 @@ jobs: - name: Setup Dependencies Windows uses: msys2/setup-msys2@v2 with: - msystem: ucrt64 update: true install: >- + base-devel + diffutils doxygen git - mingw-w64-ucrt-x86_64-boost - mingw-w64-ucrt-x86_64-cmake - mingw-w64-ucrt-x86_64-cppwinrt - mingw-w64-ucrt-x86_64-curl - mingw-w64-ucrt-x86_64-graphviz - mingw-w64-ucrt-x86_64-miniupnpc - mingw-w64-ucrt-x86_64-nlohmann-json - mingw-w64-ucrt-x86_64-nodejs - mingw-w64-ucrt-x86_64-nsis - mingw-w64-ucrt-x86_64-onevpl - mingw-w64-ucrt-x86_64-openssl - mingw-w64-ucrt-x86_64-opus - mingw-w64-ucrt-x86_64-toolchain + 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-graphviz + mingw-w64-x86_64-miniupnpc + mingw-w64-x86_64-nlohmann-json + mingw-w64-x86_64-nodejs + mingw-w64-x86_64-nsis + mingw-w64-x86_64-onevpl + mingw-w64-x86_64-openssl + mingw-w64-x86_64-opus + mingw-w64-x86_64-toolchain + nasm + wget + yasm - name: Setup python # use this instead of msys2 python due to known issues using wheels, https://www.msys2.org/docs/python/ diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index c7024003..26e89a20 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -9,9 +9,6 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") # gcc complains about misleading indentation in some mingw includes list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation) -# see gcc bug 98723 -add_definitions(-DUSE_BOOST_REGEX) - # curl add_definitions(-DCURL_STATICLIB) include_directories(SYSTEM ${CURL_STATIC_INCLUDE_DIRS}) @@ -50,7 +47,6 @@ set(PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/platform/windows/display_base.cpp" "${CMAKE_SOURCE_DIR}/src/platform/windows/display_vram.cpp" "${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp" - "${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp" "${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp" "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h" diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index 012f17ac..34c65a5d 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -2,9 +2,6 @@ # this file will also load platform specific macros add_executable(sunshine ${SUNSHINE_TARGET_FILES}) -set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}) # platform specific target definitions if(WIN32) @@ -27,6 +24,9 @@ endif() target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) +set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) # CLion complains about unknown flags after running cmake, and cannot add symbols to the index for cuda files if(CUDA_INHERIT_COMPILE_OPTIONS) diff --git a/cmake/targets/windows.cmake b/cmake/targets/windows.cmake index ca8a6963..341d7c2e 100644 --- a/cmake/targets/windows.cmake +++ b/cmake/targets/windows.cmake @@ -1,9 +1,6 @@ # windows specific target definitions -set_target_properties(sunshine PROPERTIES - CXX_STANDARD 20 - LINK_SEARCH_START_STATIC 1) +set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1) set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") find_library(ZLIB ZLIB1) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES - Windowsapp.lib Wtsapi32.lib) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index eb81b5f8..36468fd1 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -200,7 +200,7 @@ editing the `conf` file in a text editor. Use the examples as reference. .. code-block:: text gamepad = auto - + `ds4_back_as_touchpad_click `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -378,7 +378,7 @@ editing the `conf` file in a text editor. Use the examples as reference. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Description** - When enabled, Sunshine will pass through native pen/touch events from Moonlight clients. + When enabled, Sunshine will pass through native pen/touch events from Moonlight clients. This can be useful to disable for older applications without native pen/touch support. @@ -1085,25 +1085,25 @@ keybindings **Description** Force specific screen capture method. + .. caution:: Applies to Linux only. + **Choices** .. table:: :widths: auto - ========= ======== =========== - Value Platform Description - ========= ======== =========== - nvfbc Linux Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for - NVIDIA cards. For GeForce cards it will only work with drivers patched with - `nvidia-patch `__ - or `nvlax `__. - wlr Linux Capture for wlroots based Wayland compositors via DMA-BUF. - kms Linux DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability. - See :ref:`Linux Setup `. - x11 Linux Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible. - ddx Windows Use DirectX Desktop Duplication API to capture the display. This is well-supported on Windows machines. - wgc Windows (beta feature) Use Windows.Graphics.Capture to capture the display. - ========= ======== =========== + ========= =========== + Value Description + ========= =========== + nvfbc Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for + NVIDIA cards. For GeForce cards it will only work with drivers patched with + `nvidia-patch `__ + or `nvlax `__. + wlr Capture for wlroots based Wayland compositors via DMA-BUF. + kms DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability. + See :ref:`Linux Setup `. + x11 Uses XCB. This is the slowest and most CPU intensive so should be avoided if possible. + ========= =========== **Default** Automatic. Sunshine will use the first capture method available in the order of the table above. diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 084c492f..90b3d9e3 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -3,7 +3,7 @@ Windows Requirements ------------ -First you need to install `MSYS2 `__, then startup "MSYS2 UCRT64" and execute the following +First you need to install `MSYS2 `__, then startup "MSYS2 MinGW 64-bit" and execute the following codes. Update all packages: @@ -15,22 +15,26 @@ Install dependencies: .. code-block:: bash pacman -S \ + base-devel \ + cmake \ + diffutils \ doxygen \ + gcc \ git \ - mingw-w64-ucrt-x86_64-boost \ - mingw-w64-ucrt-x86_64-cmake \ - mingw-w64-ucrt-x86_64-cppwinrt \ - mingw-w64-ucrt-x86_64-curl \ - mingw-w64-ucrt-x86_64-graphviz \ - mingw-w64-ucrt-x86_64-miniupnpc \ - mingw-w64-ucrt-x86_64-nlohmann-json \ - mingw-w64-ucrt-x86_64-nodejs \ - mingw-w64-ucrt-x86_64-nsis \ - mingw-w64-ucrt-x86_64-onevpl \ - mingw-w64-ucrt-x86_64-openssl \ - mingw-w64-ucrt-x86_64-opus \ - mingw-w64-ucrt-x86_64-rust \ - mingw-w64-ucrt-x86_64-toolchain \ + 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-graphviz \ + mingw-w64-x86_64-miniupnpc \ + mingw-w64-x86_64-nlohmann-json \ + mingw-w64-x86_64-nodejs \ + mingw-w64-x86_64-onevpl \ + mingw-w64-x86_64-openssl \ + mingw-w64-x86_64-opus \ + mingw-w64-x86_64-rust \ + mingw-w64-x86_64-toolchain \ python \ python-pip diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h index 1a3e7b58..2d480c59 100644 --- a/src/platform/windows/display.h +++ b/src/platform/windows/display.h @@ -11,9 +11,6 @@ #include #include -#include -#include - #include "src/platform/common.h" #include "src/utility.h" #include "src/video.h" @@ -156,6 +153,22 @@ namespace platf::dxgi { bool visible; }; + class duplication_t { + public: + dup_t dup; + bool has_frame {}; + std::chrono::steady_clock::time_point last_protected_content_warning_time {}; + + capture_e + next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e + reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e + release_frame(); + + ~duplication_t(); + }; + class display_base_t: public display_t { public: int @@ -172,6 +185,7 @@ namespace platf::dxgi { output_t output; device_t device; device_ctx_t device_ctx; + duplication_t dup; DXGI_RATIONAL display_refresh_rate; int display_refresh_rate_rounded; @@ -239,32 +253,30 @@ namespace platf::dxgi { virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override; - const char * - dxgi_format_to_string(DXGI_FORMAT format); - const char * - colorspace_to_string(DXGI_COLOR_SPACE_TYPE type); - virtual std::vector - get_supported_capture_formats() = 0; - protected: int get_pixel_pitch() { return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4; } + const char * + dxgi_format_to_string(DXGI_FORMAT format); + const char * + colorspace_to_string(DXGI_COLOR_SPACE_TYPE type); + virtual capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0; - virtual capture_e - release_snapshot() = 0; virtual int complete_img(img_t *img, bool dummy) = 0; + virtual std::vector + get_supported_capture_formats() = 0; }; - /** - * Display component for devices that use software encoders. - */ class display_ram_t: public display_base_t { public: + virtual capture_e + snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + std::shared_ptr alloc_img() override; int @@ -274,18 +286,22 @@ namespace platf::dxgi { std::vector get_supported_capture_formats() override; + int + init(const ::video::config_t &config, const std::string &display_name); + std::unique_ptr make_avcodec_encode_device(pix_fmt_e pix_fmt) override; + cursor_t cursor; D3D11_MAPPED_SUBRESOURCE img_info; texture2d_t texture; }; - /** - * Display component for devices that use hardware encoders. - */ class display_vram_t: public display_base_t, public std::enable_shared_from_this { public: + virtual capture_e + snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; + std::shared_ptr alloc_img() override; int @@ -295,6 +311,9 @@ namespace platf::dxgi { std::vector get_supported_capture_formats() override; + int + init(const ::video::config_t &config, const std::string &display_name); + bool is_codec_supported(std::string_view name, const ::video::config_t &config) override; @@ -304,59 +323,6 @@ namespace platf::dxgi { std::unique_ptr make_nvenc_encode_device(pix_fmt_e pix_fmt) override; - std::atomic next_image_id; - }; - - /** - * Display duplicator that uses the DirectX Desktop Duplication API. - */ - class duplication_t { - public: - dup_t dup; - bool has_frame {}; - std::chrono::steady_clock::time_point last_protected_content_warning_time {}; - - int - init(display_base_t *display, const ::video::config_t &config); - capture_e - next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); - capture_e - reset(dup_t::pointer dup_p = dup_t::pointer()); - capture_e - release_frame(); - - ~duplication_t(); - }; - - /** - * Display backend that uses DDAPI with a software encoder. - */ - class display_ddup_ram_t: public display_ram_t { - public: - int - init(const ::video::config_t &config, const std::string &display_name); - capture_e - snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; - capture_e - release_snapshot() override; - - duplication_t dup; - cursor_t cursor; - }; - - /** - * Display backend that uses DDAPI with a hardware encoder. - */ - class display_ddup_vram_t: public display_vram_t { - public: - int - init(const ::video::config_t &config, const std::string &display_name); - capture_e - snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; - capture_e - release_snapshot() override; - - duplication_t dup; sampler_state_t sampler_linear; blend_t blend_alpha; @@ -372,64 +338,7 @@ namespace platf::dxgi { texture2d_t old_surface_delayed_destruction; std::chrono::steady_clock::time_point old_surface_timestamp; std::variant> last_frame_variant; - }; - /** - * Display duplicator that uses the Windows.Graphics.Capture API. - */ - class wgc_capture_t { - winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice uwp_device { nullptr }; - winrt::Windows::Graphics::Capture::GraphicsCaptureItem item { nullptr }; - winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool frame_pool { nullptr }; - winrt::Windows::Graphics::Capture::GraphicsCaptureSession capture_session { nullptr }; - winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame produced_frame { nullptr }, consumed_frame { nullptr }; - SRWLOCK frame_lock = SRWLOCK_INIT; - CONDITION_VARIABLE frame_present_cv; - - void - on_frame_arrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &); - - public: - wgc_capture_t(); - ~wgc_capture_t(); - - int - init(display_base_t *display, const ::video::config_t &config); - capture_e - next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time); - capture_e - release_frame(); - int - set_cursor_visible(bool); - }; - - /** - * Display backend that uses Windows.Graphics.Capture with a software encoder. - */ - class display_wgc_ram_t: public display_ram_t { - wgc_capture_t dup; - - public: - int - init(const ::video::config_t &config, const std::string &display_name); - capture_e - snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; - capture_e - release_snapshot() override; - }; - - /** - * Display backend that uses Windows.Graphics.Capture with a hardware encoder. - */ - class display_wgc_vram_t: public display_vram_t { - wgc_capture_t dup; - - public: - int - init(const ::video::config_t &config, const std::string &display_name); - capture_e - snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override; - capture_e - release_snapshot() override; + std::atomic next_image_id; }; } // namespace platf::dxgi diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 485a3646..b258690e 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -26,91 +26,6 @@ namespace platf { namespace platf::dxgi { namespace bp = boost::process; - /** - * DDAPI-specific initialization goes here. - */ - int - duplication_t::init(display_base_t *display, const ::video::config_t &config) { - HRESULT status; - - // Capture format will be determined from the first call to AcquireNextFrame() - display->capture_format = DXGI_FORMAT_UNKNOWN; - - // FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD - { - // IDXGIOutput5 is optional, but can provide improved performance and wide color support - dxgi::output5_t output5 {}; - status = display->output->QueryInterface(IID_IDXGIOutput5, (void **) &output5); - if (SUCCEEDED(status)) { - // Ask the display implementation which formats it supports - auto supported_formats = display->get_supported_capture_formats(); - if (supported_formats.empty()) { - BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv; - return -1; - } - - // We try this twice, in case we still get an error on reinitialization - for (int x = 0; x < 2; ++x) { - // Ensure we can duplicate the current display - syncThreadDesktop(); - - status = output5->DuplicateOutput1((IUnknown *) display->device.get(), 0, supported_formats.size(), supported_formats.data(), &dup); - if (SUCCEEDED(status)) { - break; - } - std::this_thread::sleep_for(200ms); - } - - // We don't retry with DuplicateOutput() because we can hit this codepath when we're racing - // with mode changes and we don't want to accidentally fall back to suboptimal capture if - // we get unlucky and succeed below. - if (FAILED(status)) { - BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - } - else { - BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv; - - dxgi::output1_t output1 {}; - status = display->output->QueryInterface(IID_IDXGIOutput1, (void **) &output1); - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; - return -1; - } - - for (int x = 0; x < 2; ++x) { - // Ensure we can duplicate the current display - syncThreadDesktop(); - - status = output1->DuplicateOutput((IUnknown *) display->device.get(), &dup); - if (SUCCEEDED(status)) { - break; - } - std::this_thread::sleep_for(200ms); - } - - if (FAILED(status)) { - BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - } - } - - DXGI_OUTDUPL_DESC dup_desc; - dup->GetDesc(&dup_desc); - - BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']'; - BOOST_LOG(info) << "Desktop format ["sv << display->dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']'; - - display->display_refresh_rate = dup_desc.ModeDesc.RefreshRate; - double display_refresh_rate_decimal = (double) display->display_refresh_rate.Numerator / display->display_refresh_rate.Denominator; - BOOST_LOG(info) << "Display refresh rate [" << display_refresh_rate_decimal << "Hz]"; - BOOST_LOG(info) << "Requested frame rate [" << display->client_frame_rate << "fps]"; - display->display_refresh_rate_rounded = lround(display_refresh_rate_decimal); - return 0; - } - 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(); @@ -348,7 +263,7 @@ namespace platf::dxgi { return status; } - status = release_snapshot(); + status = dup.release_frame(); if (status != platf::capture_e::ok) { return status; } @@ -787,7 +702,81 @@ namespace platf::dxgi { } } + // FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD + { + // IDXGIOutput5 is optional, but can provide improved performance and wide color support + dxgi::output5_t output5 {}; + status = output->QueryInterface(IID_IDXGIOutput5, (void **) &output5); + if (SUCCEEDED(status)) { + // Ask the display implementation which formats it supports + auto supported_formats = get_supported_capture_formats(); + if (supported_formats.empty()) { + BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv; + return -1; + } + + // We try this twice, in case we still get an error on reinitialization + for (int x = 0; x < 2; ++x) { + // Ensure we can duplicate the current display + syncThreadDesktop(); + + status = output5->DuplicateOutput1((IUnknown *) device.get(), 0, supported_formats.size(), supported_formats.data(), &dup.dup); + if (SUCCEEDED(status)) { + break; + } + std::this_thread::sleep_for(200ms); + } + + // We don't retry with DuplicateOutput() because we can hit this codepath when we're racing + // with mode changes and we don't want to accidentally fall back to suboptimal capture if + // we get unlucky and succeed below. + if (FAILED(status)) { + BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + } + else { + BOOST_LOG(warning) << "IDXGIOutput5 is not supported by your OS. Capture performance may be reduced."sv; + + dxgi::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 -1; + } + + for (int x = 0; x < 2; ++x) { + // Ensure we can duplicate the current display + syncThreadDesktop(); + + status = output1->DuplicateOutput((IUnknown *) device.get(), &dup.dup); + if (SUCCEEDED(status)) { + break; + } + std::this_thread::sleep_for(200ms); + } + + if (FAILED(status)) { + BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + } + } + + DXGI_OUTDUPL_DESC dup_desc; + dup.dup->GetDesc(&dup_desc); + + 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) << ']'; + + display_refresh_rate = dup_desc.ModeDesc.RefreshRate; + double display_refresh_rate_decimal = (double) display_refresh_rate.Numerator / display_refresh_rate.Denominator; + BOOST_LOG(info) << "Display refresh rate [" << display_refresh_rate_decimal << "Hz]"; + display_refresh_rate_rounded = lround(display_refresh_rate_decimal); + client_frame_rate = config.framerate; + BOOST_LOG(info) << "Requested frame rate [" << client_frame_rate << "fps]"; + dxgi::output6_t output6 {}; status = output->QueryInterface(IID_IDXGIOutput6, (void **) &output6); if (SUCCEEDED(status)) { @@ -807,6 +796,9 @@ namespace platf::dxgi { << "Max Full Luminance : "sv << desc1.MaxFullFrameLuminance << " nits"sv; } + // Capture format will be determined from the first call to AcquireNextFrame() + capture_format = DXGI_FORMAT_UNKNOWN; + // Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+) timer.reset(CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS)); if (!timer) { @@ -1062,47 +1054,23 @@ namespace platf::dxgi { } // namespace platf::dxgi namespace platf { - /** - * Pick a display adapter and capture method. - * @param hwdevice_type enables possible use of hardware encoder - */ std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { - if (config::video.capture == "ddx" || config::video.capture.empty()) { - if (hwdevice_type == mem_type_e::dxgi) { - auto disp = std::make_shared(); + if (hwdevice_type == mem_type_e::dxgi) { + auto disp = std::make_shared(); - if (!disp->init(config, display_name)) { - return disp; - } + if (!disp->init(config, display_name)) { + return disp; } - else if (hwdevice_type == mem_type_e::system) { - auto disp = std::make_shared(); + } + else if (hwdevice_type == mem_type_e::system) { + auto disp = std::make_shared(); - if (!disp->init(config, display_name)) { - return disp; - } + if (!disp->init(config, display_name)) { + return disp; } } - if (config::video.capture == "wgc" || config::video.capture.empty()) { - if (hwdevice_type == mem_type_e::dxgi) { - auto disp = std::make_shared(); - - if (!disp->init(config, display_name)) { - return disp; - } - } - else if (hwdevice_type == mem_type_e::system) { - auto disp = std::make_shared(); - - if (!disp->init(config, display_name)) { - return disp; - } - } - } - - // ddx and wgc failed return nullptr; } diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index 0a8e1a8b..cbe37edf 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -177,8 +177,9 @@ namespace platf::dxgi { } capture_e - display_ddup_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { + display_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { HRESULT status; + DXGI_OUTDUPL_FRAME_INFO frame_info; resource_t::pointer res_p {}; @@ -325,11 +326,6 @@ namespace platf::dxgi { return capture_e::ok; } - capture_e - display_ddup_ram_t::release_snapshot() { - return dup.release_frame(); - } - std::shared_ptr display_ram_t::alloc_img() { auto img = std::make_shared(); @@ -386,8 +382,8 @@ namespace platf::dxgi { } int - display_ddup_ram_t::init(const ::video::config_t &config, const std::string &display_name) { - if (display_base_t::init(config, display_name) || dup.init(this, config)) { + 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 ae0e6407..4aa1800b 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -945,8 +945,9 @@ namespace platf::dxgi { } capture_e - display_ddup_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { + display_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { HRESULT status; + DXGI_OUTDUPL_FRAME_INFO frame_info; resource_t::pointer res_p {}; @@ -1328,14 +1329,9 @@ namespace platf::dxgi { return capture_e::ok; } - capture_e - display_ddup_vram_t::release_snapshot() { - return dup.release_frame(); - } - int - display_ddup_vram_t::init(const ::video::config_t &config, const std::string &display_name) { - if (display_base_t::init(config, display_name) || dup.init(this, config)) { + display_vram_t::init(const ::video::config_t &config, const std::string &display_name) { + if (display_base_t::init(config, display_name)) { return -1; } @@ -1414,80 +1410,6 @@ namespace platf::dxgi { return 0; } - /** - * Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture. - * @param pull_free_image_cb call this to get a new free image from the video subsystem. - * @param img_out the captured frame is returned here - * @param timeout how long to wait for the next frame - * @param cursor_visible - */ - capture_e - display_wgc_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { - texture2d_t src; - uint64_t frame_qpc; - dup.set_cursor_visible(cursor_visible); - auto capture_status = dup.next_frame(timeout, &src, frame_qpc); - if (capture_status != capture_e::ok) - return capture_status; - - auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc); - D3D11_TEXTURE2D_DESC desc; - src->GetDesc(&desc); - - // It's possible for our display enumeration to race with mode changes and result in - // mismatched image pool and desktop texture sizes. If this happens, just reinit again. - if (desc.Width != width_before_rotation || desc.Height != height_before_rotation) { - BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']'; - return capture_e::reinit; - } - - // It's also possible for the capture format to change on the fly. If that happens, - // reinitialize capture to try format detection again and create new images. - if (capture_format != desc.Format) { - BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']'; - return capture_e::reinit; - } - - std::shared_ptr img; - if (!pull_free_image_cb(img)) - return capture_e::interrupted; - - auto d3d_img = std::static_pointer_cast(img); - d3d_img->blank = false; // image is always ready for capture - if (complete_img(d3d_img.get(), false) == 0) { - texture_lock_helper lock_helper(d3d_img->capture_mutex.get()); - if (lock_helper.lock()) { - device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get()); - } - else { - BOOST_LOG(error) << "Failed to lock capture texture"; - return capture_e::error; - } - } - else { - return capture_e::error; - } - img_out = img; - if (img_out) { - img_out->frame_timestamp = frame_timestamp; - } - - return capture_e::ok; - } - - capture_e - display_wgc_vram_t::release_snapshot() { - return dup.release_frame(); - } - - int - display_wgc_vram_t::init(const ::video::config_t &config, const std::string &display_name) { - if (display_base_t::init(config, display_name) || dup.init(this, config)) - return -1; - - return 0; - } - std::shared_ptr display_vram_t::alloc_img() { auto img = std::make_shared(); diff --git a/src/platform/windows/display_wgc.cpp b/src/platform/windows/display_wgc.cpp deleted file mode 100644 index b77c600b..00000000 --- a/src/platform/windows/display_wgc.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/** - * @file src/platform/windows/display_wgc.cpp - * @brief WinRT Windows.Graphics.Capture API - */ -#include - -#include "display.h" - -#include "misc.h" -#include "src/logging.h" - -#include -#include -#include - -namespace platf { - using namespace std::literals; -} - -namespace winrt { - using namespace Windows::Foundation; - using namespace Windows::Graphics::Capture; - using namespace Windows::Graphics::DirectX::Direct3D11; - - extern "C" { - HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); - } - - /* Windows structures sometimes have compile-time GUIDs. GCC supports this, but in a roundabout way. - * If WINRT_IMPL_HAS_DECLSPEC_UUID is true, then the compiler supports adding this attribute to a struct. For example, Visual Studio. - * If not, then MinGW GCC has a workaround to assign a GUID to a structure. - */ - struct -#if WINRT_IMPL_HAS_DECLSPEC_UUID - __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) -#endif - IDirect3DDxgiInterfaceAccess: ::IUnknown { - virtual HRESULT __stdcall GetInterface(REFIID id, void **object) = 0; - }; -} // namespace winrt -#if !WINRT_IMPL_HAS_DECLSPEC_UUID -static constexpr GUID GUID__IDirect3DDxgiInterfaceAccess = { - 0xA9B3D012, 0x3DF2, 0x4EE3, { 0xB8, 0xD1, 0x86, 0x95, 0xF4, 0x57, 0xD3, 0xC1 } - // compare with __declspec(uuid(...)) for the struct above. -}; -template <> -constexpr auto -__mingw_uuidof() -> GUID const & { - return GUID__IDirect3DDxgiInterfaceAccess; -} -#endif - -namespace platf::dxgi { - wgc_capture_t::wgc_capture_t() { - InitializeConditionVariable(&frame_present_cv); - } - - wgc_capture_t::~wgc_capture_t() { - if (capture_session) - capture_session.Close(); - if (frame_pool) - frame_pool.Close(); - item = nullptr; - capture_session = nullptr; - frame_pool = nullptr; - } - - /** - * Initialize the Windows.Graphics.Capture backend. - * @return 0 on success - */ - int - wgc_capture_t::init(display_base_t *display, const ::video::config_t &config) { - HRESULT status; - dxgi::dxgi_t dxgi; - winrt::com_ptr<::IInspectable> d3d_comhandle; - try { - if (!winrt::GraphicsCaptureSession::IsSupported()) { - BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows!"sv; - return -1; - } - if (FAILED(status = display->device->QueryInterface(IID_IDXGIDevice, (void **) &dxgi))) { - BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - if (FAILED(status = winrt::CreateDirect3D11DeviceFromDXGIDevice(*&dxgi, d3d_comhandle.put()))) { - BOOST_LOG(error) << "Failed to query WinRT DirectX interface from device [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - } - catch (winrt::hresult_error &e) { - BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire device: [0x"sv << util::hex(e.code()).to_string_view() << ']'; - return -1; - } - - DXGI_OUTPUT_DESC output_desc; - uwp_device = d3d_comhandle.as(); - display->output->GetDesc(&output_desc); - - auto monitor_factory = winrt::get_activation_factory(); - if (monitor_factory == nullptr || - FAILED(status = monitor_factory->CreateForMonitor(output_desc.Monitor, winrt::guid_of(), winrt::put_abi(item)))) { - BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to acquire display: [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - if (config.dynamicRange) - display->capture_format = DXGI_FORMAT_R16G16B16A16_FLOAT; - else - display->capture_format = DXGI_FORMAT_B8G8R8A8_UNORM; - - try { - frame_pool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(uwp_device, static_cast(display->capture_format), 2, item.Size()); - capture_session = frame_pool.CreateCaptureSession(item); - frame_pool.FrameArrived({ this, &wgc_capture_t::on_frame_arrived }); - } - catch (winrt::hresult_error &e) { - BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to create capture session: [0x"sv << util::hex(e.code()).to_string_view() << ']'; - return -1; - } - try { - capture_session.IsBorderRequired(false); - } - catch (winrt::hresult_error &e) { - BOOST_LOG(warning) << "Screen capture may not be fully supported on this device for this release of Windows: failed to disable border around capture area: [0x"sv << util::hex(e.code()).to_string_view() << ']'; - } - try { - capture_session.StartCapture(); - } - catch (winrt::hresult_error &e) { - BOOST_LOG(error) << "Screen capture is not supported on this device for this release of Windows: failed to start capture: [0x"sv << util::hex(e.code()).to_string_view() << ']'; - return -1; - } - return 0; - } - - /** - * This function runs in a separate thread spawned by the frame pool and is a producer of frames. - * To maintain parity with the original display interface, this frame will be consumed by the capture thread. - * Acquire a read-write lock, make the produced frame available to the capture thread, then wake the capture thread. - */ - void - wgc_capture_t::on_frame_arrived(winrt::Direct3D11CaptureFramePool const &sender, winrt::IInspectable const &) { - winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame frame { nullptr }; - try { - frame = sender.TryGetNextFrame(); - } - catch (winrt::hresult_error &e) { - BOOST_LOG(warning) << "Failed to capture frame: "sv << e.code(); - return; - } - if (frame != nullptr) { - AcquireSRWLockExclusive(&frame_lock); - if (produced_frame) - produced_frame.Close(); - - produced_frame = frame; - ReleaseSRWLockExclusive(&frame_lock); - WakeConditionVariable(&frame_present_cv); - } - } - - /** - * Get the next frame from the producer thread. - * If not available, the capture thread blocks until one is, or the wait times out. - * @param timeout how long to wait for the next frame - * @param out a texture containing the frame just captured - * @param out_time the timestamp of the frame just captured - */ - capture_e - wgc_capture_t::next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time) { - // this CONSUMER runs in the capture thread - release_frame(); - - AcquireSRWLockExclusive(&frame_lock); - if (produced_frame == nullptr && SleepConditionVariableSRW(&frame_present_cv, &frame_lock, timeout.count(), 0) == 0) { - ReleaseSRWLockExclusive(&frame_lock); - if (GetLastError() == ERROR_TIMEOUT) - return capture_e::timeout; - else - return capture_e::error; - } - if (produced_frame) { - consumed_frame = produced_frame; - produced_frame = nullptr; - } - ReleaseSRWLockExclusive(&frame_lock); - if (consumed_frame == nullptr) // spurious wakeup - return capture_e::timeout; - - auto capture_access = consumed_frame.Surface().as(); - if (capture_access == nullptr) - return capture_e::error; - capture_access->GetInterface(IID_ID3D11Texture2D, (void **) out); - out_time = consumed_frame.SystemRelativeTime().count(); // raw ticks from query performance counter - return capture_e::ok; - } - - capture_e - wgc_capture_t::release_frame() { - if (consumed_frame != nullptr) { - consumed_frame.Close(); - consumed_frame = nullptr; - } - return capture_e::ok; - } - - int - wgc_capture_t::set_cursor_visible(bool x) { - try { - if (capture_session.IsCursorCaptureEnabled() != x) - capture_session.IsCursorCaptureEnabled(x); - return 0; - } - catch (winrt::hresult_error &) { - return -1; - } - } - - int - display_wgc_ram_t::init(const ::video::config_t &config, const std::string &display_name) { - if (display_base_t::init(config, display_name) || dup.init(this, config)) - return -1; - - texture.reset(); - return 0; - } - - /** - * Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture. - * @param pull_free_image_cb call this to get a new free image from the video subsystem. - * @param img_out the captured frame is returned here - * @param timeout how long to wait for the next frame - * @param cursor_visible - */ - capture_e - display_wgc_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) { - HRESULT status; - texture2d_t src; - uint64_t frame_qpc; - dup.set_cursor_visible(cursor_visible); - auto capture_status = dup.next_frame(timeout, &src, frame_qpc); - if (capture_status != capture_e::ok) - return capture_status; - - auto frame_timestamp = std::chrono::steady_clock::now() - qpc_time_difference(qpc_counter(), frame_qpc); - D3D11_TEXTURE2D_DESC desc; - src->GetDesc(&desc); - - // Create the staging texture if it doesn't exist. It should match the source in size and format. - if (texture == nullptr) { - capture_format = desc.Format; - BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']'; - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = capture_format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - auto status = device->CreateTexture2D(&t, nullptr, &texture); - - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - } - - // It's possible for our display enumeration to race with mode changes and result in - // mismatched image pool and desktop texture sizes. If this happens, just reinit again. - if (desc.Width != width || desc.Height != height) { - BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']'; - return capture_e::reinit; - } - // It's also possible for the capture format to change on the fly. If that happens, - // reinitialize capture to try format detection again and create new images. - if (capture_format != desc.Format) { - BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']'; - return capture_e::reinit; - } - - // Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - - if (!pull_free_image_cb(img_out)) { - return capture_e::interrupted; - } - auto img = (img_t *) img_out.get(); - - // Map the staging texture for CPU access (making it inaccessible for the GPU) - if (FAILED(status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info))) { - BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - - // Now that we know the capture format, we can finish creating the image - if (complete_img(img, false)) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - return capture_e::error; - } - - std::copy_n((std::uint8_t *) img_info.pData, height * img_info.RowPitch, (std::uint8_t *) img->data); - - // Unmap the staging texture to allow GPU access again - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - - if (img) { - img->frame_timestamp = frame_timestamp; - } - - return capture_e::ok; - } - - capture_e - display_wgc_ram_t::release_snapshot() { - return dup.release_frame(); - } -} // namespace platf::dxgi diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index dfc9852f..5056c2c7 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -1411,7 +1411,7 @@ namespace platf { ds4_update_state(gamepad_context_t &gamepad, const gamepad_state_t &gamepad_state) { auto &report = gamepad.report.ds4.Report; - report.wButtons = static_cast(ds4_buttons(gamepad_state)) | static_cast(ds4_dpad(gamepad_state)); + report.wButtons = ds4_buttons(gamepad_state) | ds4_dpad(gamepad_state); report.bSpecial = ds4_special_buttons(gamepad_state); report.bTriggerL = gamepad_state.lt; diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index e136de66..e7bb64e5 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1691,8 +1691,8 @@ namespace platf { } int64_t qpc_counter() { - LARGE_INTEGER performance_counter; - if (QueryPerformanceCounter(&performance_counter)) return performance_counter.QuadPart; + LARGE_INTEGER performace_counter; + if (QueryPerformanceCounter(&performace_counter)) return performace_counter.QuadPart; return 0; } diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 6bb2979f..debcdaae 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -694,16 +694,14 @@ -
+
{{ $t('config.capture_desc') }}
@@ -810,7 +808,7 @@
{{ $t('config.nvenc_latency_over_power_desc') }}
- +
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 7786a9d8..8d1be14d 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -3,7 +3,6 @@ "apply": "Apply", "auto": "Automatic", "autodetect": "Autodetect (recommended)", - "beta": "(beta)", "cancel": "Cancel", "disabled": "Disabled", "disabled_def": "Disabled (default)", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ba70a88c..e4583f9c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,6 +107,7 @@ list(REMOVE_ITEM SUNSHINE_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp) add_executable(${PROJECT_NAME} ${TEST_SOURCES} ${SUNSHINE_SOURCES}) +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) target_link_libraries(${PROJECT_NAME} ${SUNSHINE_EXTERNAL_LIBRARIES} gtest @@ -115,10 +116,5 @@ target_link_libraries(${PROJECT_NAME} target_compile_definitions(${PROJECT_NAME} PUBLIC ${SUNSHINE_DEFINITIONS} ${TEST_DEFINITIONS}) target_compile_options(${PROJECT_NAME} PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 target_link_options(${PROJECT_NAME} PRIVATE) -if(WIN32) - set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) -else() - set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) -endif() add_test(NAME ${PROJECT_NAME} COMMAND sunshine_test)