tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

wgc_capturer_win.cc (17400B)


      1 /*
      2 *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
      3 *
      4 *  Use of this source code is governed by a BSD-style license
      5 *  that can be found in the LICENSE file in the root of the source
      6 *  tree. An additional intellectual property rights grant can be found
      7 *  in the file PATENTS.  All contributing project authors may
      8 *  be found in the AUTHORS file in the root of the source tree.
      9 */
     10 
     11 #include "modules/desktop_capture/win/wgc_capturer_win.h"
     12 
     13 #include <dispatcherqueue.h>
     14 #include <windows.foundation.metadata.h>
     15 #include <windows.graphics.capture.h>
     16 
     17 #include <cstdint>
     18 #include <cwchar>
     19 #include <map>
     20 #include <memory>
     21 #include <tuple>
     22 #include <utility>
     23 
     24 #include "modules/desktop_capture/desktop_capture_metrics_helper.h"
     25 #include "modules/desktop_capture/desktop_capture_options.h"
     26 #include "modules/desktop_capture/desktop_capture_types.h"
     27 #include "modules/desktop_capture/desktop_capturer.h"
     28 #include "modules/desktop_capture/desktop_frame.h"
     29 #include "modules/desktop_capture/win/screen_capture_utils.h"
     30 #include "modules/desktop_capture/win/wgc_capture_session.h"
     31 #include "modules/desktop_capture/win/wgc_capture_source.h"
     32 #include "rtc_base/checks.h"
     33 #include "rtc_base/logging.h"
     34 #include "rtc_base/time_utils.h"
     35 #include "rtc_base/win/get_activation_factory.h"
     36 #include "rtc_base/win/hstring.h"
     37 #include "rtc_base/win/windows_version.h"
     38 #include "system_wrappers/include/metrics.h"
     39 
     40 namespace WGC = ABI::Windows::Graphics::Capture;
     41 using Microsoft::WRL::ComPtr;
     42 
     43 namespace webrtc {
     44 
     45 namespace {
     46 
     47 constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll";
     48 
     49 constexpr wchar_t kWgcSessionType[] =
     50    L"Windows.Graphics.Capture.GraphicsCaptureSession";
     51 constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract";
     52 constexpr wchar_t kDirtyRegionMode[] = L"DirtyRegionMode";
     53 constexpr UINT16 kRequiredApiContractVersion = 8;
     54 
     55 enum class WgcCapturerResult {
     56  kSuccess = 0,
     57  kNoDirect3dDevice = 1,
     58  kNoSourceSelected = 2,
     59  kItemCreationFailure = 3,
     60  kSessionStartFailure = 4,
     61  kGetFrameFailure = 5,
     62  kFrameDropped = 6,
     63  kCreateDispatcherQueueFailure = 7,
     64  kMaxValue = kCreateDispatcherQueueFailure
     65 };
     66 
     67 void RecordWgcCapturerResult(WgcCapturerResult error) {
     68  RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult",
     69                            static_cast<int>(error),
     70                            static_cast<int>(WgcCapturerResult::kMaxValue));
     71 }
     72 
     73 // Checks if the DirtyRegionMode property is present in GraphicsCaptureSession
     74 // and logs a boolean histogram with the result.
     75 // TODO(https://crbug.com/40259177): Detecting support for this property means
     76 // that the WGC API supports dirty regions and it can be utilized to improve
     77 // the capture performance and the existing zero-herz support.
     78 void LogDirtyRegionSupport() {
     79  ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics>
     80      api_info_statics;
     81  HRESULT hr = GetActivationFactory<
     82      ABI::Windows::Foundation::Metadata::IApiInformationStatics,
     83      RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(
     84      &api_info_statics);
     85  if (FAILED(hr)) {
     86    return;
     87  }
     88 
     89  HSTRING dirty_region_mode;
     90  hr = CreateHstring(kDirtyRegionMode, wcslen(kDirtyRegionMode),
     91                     &dirty_region_mode);
     92  if (FAILED(hr)) {
     93    DeleteHstring(dirty_region_mode);
     94    return;
     95  }
     96 
     97  HSTRING wgc_session_type;
     98  hr = CreateHstring(kWgcSessionType, wcslen(kWgcSessionType),
     99                     &wgc_session_type);
    100  if (SUCCEEDED(hr)) {
    101    boolean is_dirty_region_mode_supported =
    102        api_info_statics->IsPropertyPresent(wgc_session_type, dirty_region_mode,
    103                                            &is_dirty_region_mode_supported);
    104    RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.Win.WgcDirtyRegionSupport",
    105                          !!is_dirty_region_mode_supported);
    106  }
    107  DeleteHstring(dirty_region_mode);
    108  DeleteHstring(wgc_session_type);
    109 }
    110 
    111 }  // namespace
    112 
    113 bool IsWgcSupported(CaptureType capture_type) {
    114  if (!HasActiveDisplay()) {
    115    // There is a bug in `CreateForMonitor` that causes a crash if there are no
    116    // active displays. The crash was fixed in Win11, but we are still unable
    117    // to capture screens without an active display.
    118    if (capture_type == CaptureType::kScreen) {
    119      return false;
    120    }
    121    // There is a bug in the DWM (Desktop Window Manager) that prevents it from
    122    // providing image data if there are no displays attached. This was fixed in
    123    // Windows 11.
    124    if (rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN11) {
    125      return false;
    126    }
    127  }
    128 
    129  // A bug in the WGC API `CreateForMonitor` prevents capturing the entire
    130  // virtual screen (all monitors simultaneously), this was fixed in 20H1. Since
    131  // we can't assert that we won't be asked to capture the entire virtual
    132  // screen, we report unsupported so we can fallback to another capturer.
    133  if (capture_type == CaptureType::kScreen &&
    134      rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN10_20H1) {
    135    return false;
    136  }
    137 
    138  if (!ResolveCoreWinRTDelayload()) {
    139    return false;
    140  }
    141 
    142  // We need to check if the WGC APIs are present on the system. Certain SKUs
    143  // of Windows ship without these APIs.
    144  ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics>
    145      api_info_statics;
    146  HRESULT hr = GetActivationFactory<
    147      ABI::Windows::Foundation::Metadata::IApiInformationStatics,
    148      RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(
    149      &api_info_statics);
    150  if (FAILED(hr)) {
    151    return false;
    152  }
    153 
    154  HSTRING api_contract;
    155  hr = CreateHstring(kApiContract, wcslen(kApiContract), &api_contract);
    156  if (FAILED(hr)) {
    157    return false;
    158  }
    159 
    160  boolean is_api_present;
    161  hr = api_info_statics->IsApiContractPresentByMajor(
    162      api_contract, kRequiredApiContractVersion, &is_api_present);
    163  DeleteHstring(api_contract);
    164  if (FAILED(hr) || !is_api_present) {
    165    return false;
    166  }
    167 
    168  HSTRING wgc_session_type;
    169  hr = CreateHstring(kWgcSessionType, wcslen(kWgcSessionType),
    170                     &wgc_session_type);
    171  if (FAILED(hr)) {
    172    return false;
    173  }
    174 
    175  boolean is_type_present;
    176  hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present);
    177  DeleteHstring(wgc_session_type);
    178  if (FAILED(hr) || !is_type_present) {
    179    return false;
    180  }
    181 
    182  // If the APIs are present, we need to check that they are supported.
    183  ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics;
    184  hr = GetActivationFactory<
    185      WGC::IGraphicsCaptureSessionStatics,
    186      RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>(
    187      &capture_session_statics);
    188  if (FAILED(hr)) {
    189    return false;
    190  }
    191 
    192  boolean is_supported;
    193  hr = capture_session_statics->IsSupported(&is_supported);
    194  if (FAILED(hr) || !is_supported) {
    195    return false;
    196  }
    197 
    198  return true;
    199 }
    200 
    201 WgcCapturerWin::WgcCapturerWin(
    202    const DesktopCaptureOptions& options,
    203    std::unique_ptr<WgcCaptureSourceFactory> source_factory,
    204    std::unique_ptr<SourceEnumerator> source_enumerator,
    205    bool allow_delayed_capturable_check)
    206    : options_(options),
    207      source_factory_(std::move(source_factory)),
    208      source_enumerator_(std::move(source_enumerator)),
    209      allow_delayed_capturable_check_(allow_delayed_capturable_check),
    210      full_screen_window_detector_(options.full_screen_window_detector()) {
    211  if (!core_messaging_library_) {
    212    core_messaging_library_ = LoadLibraryW(kCoreMessagingDll);
    213  }
    214 
    215  if (core_messaging_library_) {
    216    create_dispatcher_queue_controller_func_ =
    217        reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress(
    218            core_messaging_library_, "CreateDispatcherQueueController"));
    219  }
    220  LogDirtyRegionSupport();
    221 }
    222 
    223 WgcCapturerWin::~WgcCapturerWin() {
    224  if (core_messaging_library_) {
    225    FreeLibrary(core_messaging_library_);
    226  }
    227 }
    228 
    229 // static
    230 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer(
    231    const DesktopCaptureOptions& options,
    232    bool allow_delayed_capturable_check) {
    233  return std::make_unique<WgcCapturerWin>(
    234      options, std::make_unique<WgcWindowSourceFactory>(),
    235      std::make_unique<WindowEnumerator>(
    236          options.enumerate_current_process_windows()),
    237      allow_delayed_capturable_check);
    238 }
    239 
    240 // static
    241 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer(
    242    const DesktopCaptureOptions& options) {
    243  return std::make_unique<WgcCapturerWin>(
    244      options, std::make_unique<WgcScreenSourceFactory>(),
    245      std::make_unique<ScreenEnumerator>(), false);
    246 }
    247 
    248 bool WgcCapturerWin::GetSourceList(SourceList* sources) {
    249  return source_enumerator_->FindAllSources(sources);
    250 }
    251 
    252 bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) {
    253  selected_source_id_ = id;
    254 
    255  // Use `full_screen_window_detector_` to check if there is a corresponding
    256  // full screen window for the `selected_source_id_`.
    257  const DesktopCapturer::SourceId full_screen_source_id =
    258      full_screen_window_detector_ &&
    259              full_screen_window_detector_->UseHeuristicForWGC()
    260          ? full_screen_window_detector_->FindFullScreenWindow(id)
    261          : 0;
    262 
    263  // `capture_id` represents the SourceId used to create  the `capture_source_`,
    264  // which is the module responsible for capturing the frames.
    265  auto capture_id = full_screen_source_id ? full_screen_source_id : id;
    266  if (capture_id != id && !fullscreen_usage_logged_) {
    267    // Log the usage of FullScreenDetector only once and only if it's
    268    // successful.
    269    fullscreen_usage_logged_ = true;
    270    LogDesktopCapturerFullscreenDetectorUsage();
    271  }
    272 
    273  if (!capture_source_ || capture_source_->GetSourceId() != capture_id) {
    274    capture_source_ = source_factory_->CreateCaptureSource(capture_id);
    275  }
    276 
    277  if (allow_delayed_capturable_check_) {
    278    return true;
    279  }
    280 
    281  return capture_source_->IsCapturable();
    282 }
    283 
    284 bool WgcCapturerWin::FocusOnSelectedSource() {
    285  if (!capture_source_) {
    286    return false;
    287  }
    288 
    289  return capture_source_->FocusOnSource();
    290 }
    291 
    292 void WgcCapturerWin::Start(Callback* callback) {
    293  RTC_DCHECK(!callback_);
    294  RTC_DCHECK(callback);
    295  RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin);
    296 
    297  callback_ = callback;
    298 
    299  // Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many
    300  // parameters are nullptr as the implemention uses defaults that work well for
    301  // us.
    302  HRESULT hr = D3D11CreateDevice(
    303      /*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE,
    304      /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
    305      /*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION,
    306      &d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr);
    307  if (hr == DXGI_ERROR_UNSUPPORTED) {
    308    // If a hardware device could not be created, use WARP which is a high speed
    309    // software device.
    310    hr = D3D11CreateDevice(
    311        /*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP,
    312        /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
    313        /*feature_levels=*/nullptr, /*feature_levels_size=*/0,
    314        D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr,
    315        /*device_context=*/nullptr);
    316  }
    317 
    318  if (FAILED(hr)) {
    319    RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr;
    320  }
    321 }
    322 
    323 void WgcCapturerWin::CaptureFrame() {
    324  RTC_DCHECK(callback_);
    325 
    326  if (!capture_source_) {
    327    RTC_LOG(LS_ERROR) << "Source hasn't been selected";
    328    callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    329                               /*frame=*/nullptr);
    330    RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected);
    331    return;
    332  }
    333 
    334  if (!d3d11_device_) {
    335    RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture.";
    336    callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    337                               /*frame=*/nullptr);
    338    RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice);
    339    return;
    340  }
    341 
    342  if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) {
    343    RTC_LOG(LS_ERROR) << "Source is not capturable.";
    344    callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    345                               /*frame=*/nullptr);
    346    return;
    347  }
    348 
    349  // Feed the actual list of windows into full screen window detector.
    350  if (full_screen_window_detector_) {
    351    full_screen_window_detector_->UpdateWindowListIfNeeded(
    352        selected_source_id_, [this](DesktopCapturer::SourceList* sources) {
    353          return GetSourceList(sources);
    354        });
    355    SelectSource(selected_source_id_);
    356  }
    357 
    358  HRESULT hr;
    359  if (!dispatcher_queue_created_) {
    360    // Set the apartment type to NONE because this thread should already be COM
    361    // initialized.
    362    DispatcherQueueOptions options{
    363        sizeof(DispatcherQueueOptions),
    364        DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT,
    365        DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE};
    366    ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller;
    367    hr = create_dispatcher_queue_controller_func_(options, &queue_controller);
    368 
    369    // If there is already a DispatcherQueue on this thread, that is fine. Its
    370    // lifetime is tied to the thread's, and as long as the thread has one, even
    371    // if we didn't create it, the capture session's events will be delivered on
    372    // this thread.
    373    if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) {
    374      RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure);
    375      callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    376                                 /*frame=*/nullptr);
    377    } else {
    378      dispatcher_queue_created_ = true;
    379    }
    380  }
    381 
    382  int64_t capture_start_time_nanos = TimeNanos();
    383 
    384  WgcCaptureSession* capture_session = nullptr;
    385  std::map<SourceId, WgcCaptureSession>::iterator session_iter =
    386      ongoing_captures_.find(capture_source_->GetSourceId());
    387  if (session_iter == ongoing_captures_.end()) {
    388    ComPtr<WGC::IGraphicsCaptureItem> item;
    389    hr = capture_source_->GetCaptureItem(&item);
    390    if (FAILED(hr)) {
    391      RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr;
    392      callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    393                                 /*frame=*/nullptr);
    394      RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure);
    395      return;
    396    }
    397 
    398    std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool>
    399        iter_success_pair = ongoing_captures_.emplace(
    400            std::piecewise_construct,
    401            std::forward_as_tuple(capture_source_->GetSourceId()),
    402            std::forward_as_tuple(capture_source_->GetSourceId(), d3d11_device_,
    403                                  item, capture_source_->GetSize()));
    404    RTC_DCHECK(iter_success_pair.second);
    405    capture_session = &iter_success_pair.first->second;
    406  } else {
    407    capture_session = &session_iter->second;
    408  }
    409 
    410  if (!capture_session->IsCaptureStarted()) {
    411    hr = capture_session->StartCapture(options_);
    412    if (FAILED(hr)) {
    413      RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr;
    414      ongoing_captures_.erase(capture_source_->GetSourceId());
    415      callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    416                                 /*frame=*/nullptr);
    417      RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure);
    418      return;
    419    }
    420  }
    421 
    422  std::unique_ptr<DesktopFrame> frame;
    423  if (!capture_session->GetFrame(&frame,
    424                                 capture_source_->ShouldBeCapturable())) {
    425    RTC_LOG(LS_ERROR) << "GetFrame failed.";
    426    ongoing_captures_.erase(capture_source_->GetSourceId());
    427    callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT,
    428                               /*frame=*/nullptr);
    429    RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure);
    430    return;
    431  }
    432 
    433  if (!frame) {
    434    callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
    435                               /*frame=*/nullptr);
    436    RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped);
    437    return;
    438  }
    439 
    440  int capture_time_ms =
    441      (TimeNanos() - capture_start_time_nanos) / kNumNanosecsPerMillisec;
    442  RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime",
    443                            capture_time_ms);
    444  frame->set_capture_time_ms(capture_time_ms);
    445  frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin);
    446  frame->set_may_contain_cursor(options_.prefer_cursor_embedded());
    447  frame->set_top_left(capture_source_->GetTopLeft());
    448  RecordWgcCapturerResult(WgcCapturerResult::kSuccess);
    449  callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS,
    450                             std::move(frame));
    451 }
    452 
    453 bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) {
    454  std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator
    455      session_iter = ongoing_captures_.find(id);
    456  if (session_iter == ongoing_captures_.end()) {
    457    return false;
    458  }
    459 
    460  return session_iter->second.IsCaptureStarted();
    461 }
    462 
    463 void WgcCapturerWin::SetUpFullScreenDetectorForTest(
    464    bool use_heuristic,
    465    DesktopCapturer::SourceId source_id,
    466    bool fullscreen_slide_show_started_after_capture_start) {
    467  if (full_screen_window_detector_) {
    468    full_screen_window_detector_->SetUseHeuristicFullscreenPowerPointWindows(
    469        /*use_heuristic_fullscreen_powerpoint_windows=*/true, use_heuristic);
    470    full_screen_window_detector_->CreateFullScreenApplicationHandlerForTest(
    471        source_id, fullscreen_slide_show_started_after_capture_start);
    472  }
    473 }
    474 
    475 }  // namespace webrtc