diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 7643d1d9efc..ca59d2f009d 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/windows.cmake b/cmake/targets/windows.cmake index b7f8fbcfe7a..341d7c2e74e 100644 --- a/cmake/targets/windows.cmake +++ b/cmake/targets/windows.cmake @@ -3,5 +3,4 @@ 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/src/platform/windows/display.h b/src/platform/windows/display.h index 3e035490394..e35d923f10f 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 @@ -169,6 +182,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; @@ -236,32 +250,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 @@ -271,18 +283,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 @@ -292,6 +308,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; @@ -301,59 +320,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; @@ -369,64 +335,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 3d178dafb02..21d808241b8 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -25,91 +25,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(); @@ -318,7 +233,7 @@ namespace platf::dxgi { return status; } - status = release_snapshot(); + status = dup.release_frame(); if (status != platf::capture_e::ok) { return status; } @@ -759,7 +674,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)) { @@ -1028,47 +1017,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; - } - } - 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; } } + else if (hwdevice_type == mem_type_e::system) { + auto disp = std::make_shared(); - 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; - } + 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 1105c674c30..96347d82297 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(); @@ -389,8 +385,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 532b80d62c2..d7effe7157e 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 1df60fcd8e0..00000000000 --- a/src/platform/windows/display_wgc.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/** - * @file src/platform/windows/display_wgc.cpp - * @brief Definitions for WinRT Windows.Graphics.Capture API - */ -#include - -#include "display.h" - -#include "misc.h" -#include "src/logging.h" - -#include -#include -#include -#include - -namespace platf { - using namespace std::literals; -} - -namespace winrt { - using namespace Windows::Foundation; - using namespace Windows::Foundation::Metadata; - 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; - } - - /** - * @brief Initialize the Windows.Graphics.Capture backend. - * @return 0 on success, -1 on failure. - */ - 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 { - if (winrt::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired")) { - capture_session.IsBorderRequired(false); - } - else { - BOOST_LOG(warning) << "Can't disable colored border around capture area on this version of Windows"; - } - } - 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); - } - } - - /** - * @brief 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; - } - - /** - * @brief 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 whether to capture the cursor - */ - 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_assets/common/assets/web/configs/tabs/Advanced.vue b/src_assets/common/assets/web/configs/tabs/Advanced.vue index bd11adf2892..9704c91b2e4 100644 --- a/src_assets/common/assets/web/configs/tabs/Advanced.vue +++ b/src_assets/common/assets/web/configs/tabs/Advanced.vue @@ -59,22 +59,14 @@ const config = ref(props.config) -
+
{{ $t('config.capture_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 12844fcf874..15c4015fc05 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/tools/ddprobe.cpp b/tools/ddprobe.cpp index bfabb503598..f89c085aaae 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -9,10 +9,13 @@ #include #include #include +#include #include "src/utility.h" using namespace std::literals; +using Microsoft::WRL::ComPtr; + namespace dxgi { template void @@ -70,72 +73,80 @@ syncThreadDesktop() { } /** - * @brief Determines whether a given frame is entirely black by checking every pixel. + * @brief Checks whether a given frame is entirely dark by evaluating the RGB values of each pixel. * - * This function checks if the provided frame is entirely black by inspecting each pixel in both the x and y dimensions. It inspects the RGB channels of each pixel and compares them against a specified black threshold. If any pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all pixels are below the threshold, the function returns `true`. + * This function determines if the provided frame is completely dark by analyzing the RGB values of every pixel. + * It iterates over all pixels in the frame and compares each pixel's RGB channels to a defined darkness threshold. + * If any pixel's RGB values exceed this threshold, the function concludes that the frame is not entirely dark and returns `false`. + * If all pixels are below the threshold, indicating a completely dark frame, the function returns `true`. * - * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure that contains the mapped subresource data of the frame to be analyzed. - * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure that describes the texture properties, including width and height. - * @param blackThreshold A floating-point value representing the threshold above which a pixel's RGB channels are considered non-black. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. - * @return bool Returns `true` if the frame is determined to be black, otherwise returns `false`. + * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure containing the mapped subresource data of the frame to be analyzed. + * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure describing the texture properties, including width and height. + * @param darknessThreshold A floating-point value representing the threshold above which a pixel's RGB values are considered non-dark. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. + * @return bool Returns `true` if the frame is determined to be entirely dark, otherwise returns `false`. */ bool -isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float blackThreshold = 0.1f) { +is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float darknessThreshold = 0.1f) { const uint8_t *pixels = static_cast(mappedResource.pData); - const int bytesPerPixel = 4; // Assuming RGBA format + const int bytesPerPixel = 4; // (8 bits per channel, excluding alpha). Factoring HDR is not needed because it doesn't cause black levels to raise enough to be a concern. const int stride = mappedResource.RowPitch; const int width = frameDesc.Width; const int height = frameDesc.Height; - // Convert the threshold to an integer value for comparison - const int threshold = static_cast(blackThreshold * 255); + // Convert the darkness threshold to an integer value for comparison + const int threshold = static_cast(darknessThreshold * 255); - // Loop through every pixel + // Iterate over each pixel in the frame for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { const uint8_t *pixel = pixels + y * stride + x * bytesPerPixel; - // Check if any channel (R, G, B) is significantly above black + // Check if any RGB channel exceeds the darkness threshold if (pixel[0] > threshold || pixel[1] > threshold || pixel[2] > threshold) { - return false; + // Frame is not dark + return true; } } } - return true; + // Frame is entirely dark + return false; } /** - * @brief Attempts to capture and verify the contents of up to 10 consecutive frames from a DXGI output duplication. + * @brief Captures and verifies the contents of up to 10 consecutive frames from a DXGI output duplication. * - * This function tries to acquire the next frame from a provided DXGI output duplication object (`dup`) and inspects its content to determine if the frame is not completely black. If a non-black frame is found within the 10 attempts, the function returns `S_OK`. If all 10 frames are black, it returns `S_FALSE`, indicating that the capture might not be functioning properly. In case of any failure during the process, the appropriate `HRESULT` error code is returned. + * This function attempts to acquire and analyze up to 10 frames from a DXGI output duplication object (`dup`). + * It checks if each frame is non-empty (not entirely dark) by using the `is_valid_frame` function. + * If any non-empty frame is found, the function returns `S_OK`. + * If all 10 frames are empty, it returns `S_FALSE`, suggesting potential issues with the capture process. + * If any error occurs during the frame acquisition or analysis process, the corresponding `HRESULT` error code is returned. * * @param dup A reference to the DXGI output duplication object (`dxgi::dup_t&`) used to acquire frames. - * @param device A pointer to the ID3D11Device interface that represents the device associated with the Direct3D context. - * @return HRESULT Returns `S_OK` if a non-black frame is captured successfully, `S_FALSE` if all frames are black, or an error code if a failure occurs during the process. - * - * Possible return values: - * - `S_OK`: A non-black frame was captured, indicating capture was successful. - * - `S_FALSE`: All 10 frames were black, indicating potential capture issues. - * - `E_FAIL`, `DXGI_ERROR_*`, or other DirectX HRESULT error codes in case of specific failures during frame capture, texture creation, or resource mapping. + * @param device A ComPtr to the ID3D11Device interface representing the device associated with the Direct3D context. + * @return HRESULT Returns `S_OK` if a non-empty frame is captured successfully, `S_FALSE` if all frames are empty, or an error code if any failure occurs during the process. */ HRESULT -test_frame_capture(dxgi::dup_t &dup, ID3D11Device *device) { +test_frame_capture(dxgi::dup_t &dup, ComPtr device) { for (int i = 0; i < 10; ++i) { - std::cout << "Attempting to acquire the next frame (" << i + 1 << "/10)..." << std::endl; - IDXGIResource *frameResource = nullptr; + std::cout << "Attempting to acquire frame " << (i + 1) << " of 10..." << std::endl; + + ComPtr frameResource; DXGI_OUTDUPL_FRAME_INFO frameInfo; HRESULT status = dup->AcquireNextFrame(500, &frameInfo, &frameResource); + auto release_frame = util::fail_guard([&dup]() { + dup->ReleaseFrame(); + }); + if (FAILED(status)) { - std::cout << "Failed to acquire next frame [0x" << std::hex << status << "]" << std::endl; + std::cout << "Error: Failed to acquire next frame [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } std::cout << "Frame acquired successfully." << std::endl; - ID3D11Texture2D *frameTexture = nullptr; - status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), (void **) &frameTexture); - frameResource->Release(); + ComPtr frameTexture; + status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(frameTexture.GetAddressOf())); if (FAILED(status)) { - std::cout << "Failed to query texture interface from frame resource [0x" << std::hex << status << "]" << std::endl; + std::cout << "Error: Failed to query texture interface from frame resource [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } @@ -146,51 +157,37 @@ test_frame_capture(dxgi::dup_t &dup, ID3D11Device *device) { frameDesc.BindFlags = 0; frameDesc.MiscFlags = 0; - ID3D11Texture2D *stagingTexture = nullptr; + ComPtr stagingTexture; status = device->CreateTexture2D(&frameDesc, nullptr, &stagingTexture); if (FAILED(status)) { - std::cout << "Failed to create staging texture [0x" << std::hex << status << "]" << std::endl; - frameTexture->Release(); + std::cout << "Error: Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } - ID3D11DeviceContext *context = nullptr; + ComPtr context; device->GetImmediateContext(&context); - context->CopyResource(stagingTexture, frameTexture); - frameTexture->Release(); + context->CopyResource(stagingTexture.Get(), frameTexture.Get()); D3D11_MAPPED_SUBRESOURCE mappedResource; - status = context->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mappedResource); - if (SUCCEEDED(status)) { - std::cout << "Verifying if frame is not empty" << std::endl; - - if (!isFrameBlack(mappedResource, frameDesc)) { - std::cout << "Frame " << i + 1 << " is not empty!" << std::endl; - context->Unmap(stagingTexture, 0); - stagingTexture->Release(); - context->Release(); - dup->ReleaseFrame(); - return S_OK; - } - context->Unmap(stagingTexture, 0); - } - else { - std::cout << "Failed to map the staging texture for inspection [0x" << std::hex << status << "]" << std::endl; - stagingTexture->Release(); - context->Release(); + status = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mappedResource); + if (FAILED(status)) { + std::cout << "Error: Failed to map the staging texture for inspection [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } - stagingTexture->Release(); - context->Release(); - std::cout << "Releasing the frame..." << std::endl; - dup->ReleaseFrame(); + if (is_valid_frame(mappedResource, frameDesc)) { + std::cout << "Frame " << (i + 1) << " is non-empty (contains visible content)." << std::endl; + context->Unmap(stagingTexture.Get(), 0); + return S_OK; + } + + std::cout << "Frame " << (i + 1) << " is empty (no visible content)." << std::endl; + context->Unmap(stagingTexture.Get(), 0); } - // If all frames are black, then we can assume the capture isn't working properly. + // All frames were empty, indicating potential capture issues. return S_FALSE; } - HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { D3D_FEATURE_LEVEL featureLevels[] = { @@ -232,11 +229,12 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { // Attempt to duplicate the output dxgi::dup_t dup; - HRESULT result = output1->DuplicateOutput(static_cast(device.get()), &dup); + ComPtr device_ptr(device.get()); + HRESULT result = output1->DuplicateOutput(device_ptr.Get(), &dup); if (SUCCEEDED(result)) { // If duplication is successful, test frame capture - HRESULT captureResult = test_frame_capture(dup, device.get()); + HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); if (SUCCEEDED(captureResult)) { return S_OK; }