tor-browser

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

dxgi_duplicator_controller.cc (17901B)


      1 /*
      2 *  Copyright (c) 2016 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/dxgi_duplicator_controller.h"
     12 
     13 #include <windows.h>
     14 
     15 #include <algorithm>
     16 #include <cstddef>
     17 #include <cstdint>
     18 #include <memory>
     19 #include <optional>
     20 #include <string>
     21 #include <utility>
     22 #include <vector>
     23 
     24 #include "api/scoped_refptr.h"
     25 #include "modules/desktop_capture/desktop_frame.h"
     26 #include "modules/desktop_capture/desktop_geometry.h"
     27 #include "modules/desktop_capture/shared_desktop_frame.h"
     28 #include "modules/desktop_capture/win/d3d_device.h"
     29 #include "modules/desktop_capture/win/dxgi_adapter_duplicator.h"
     30 #include "modules/desktop_capture/win/dxgi_frame.h"
     31 #include "rtc_base/checks.h"
     32 #include "rtc_base/logging.h"
     33 #include "rtc_base/synchronization/mutex.h"
     34 #include "rtc_base/thread.h"
     35 #include "rtc_base/time_utils.h"
     36 
     37 namespace webrtc {
     38 
     39 namespace {
     40 
     41 constexpr DWORD kInvalidSessionId = 0xFFFFFFFF;
     42 
     43 DWORD GetCurrentSessionId() {
     44  DWORD session_id = kInvalidSessionId;
     45  if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) {
     46    RTC_LOG(LS_WARNING)
     47        << "Failed to retrieve current session Id, current binary "
     48           "may not have required priviledge.";
     49  }
     50  return session_id;
     51 }
     52 
     53 bool IsConsoleSession() {
     54  return WTSGetActiveConsoleSessionId() == GetCurrentSessionId();
     55 }
     56 
     57 }  // namespace
     58 
     59 // static
     60 std::string DxgiDuplicatorController::ResultName(
     61    DxgiDuplicatorController::Result result) {
     62  switch (result) {
     63    case Result::SUCCEEDED:
     64      return "Succeeded";
     65    case Result::UNSUPPORTED_SESSION:
     66      return "Unsupported session";
     67    case Result::FRAME_PREPARE_FAILED:
     68      return "Frame preparation failed";
     69    case Result::INITIALIZATION_FAILED:
     70      return "Initialization failed";
     71    case Result::DUPLICATION_FAILED:
     72      return "Duplication failed";
     73    case Result::INVALID_MONITOR_ID:
     74      return "Invalid monitor id";
     75    default:
     76      return "Unknown error";
     77  }
     78 }
     79 
     80 // static
     81 webrtc::scoped_refptr<DxgiDuplicatorController>
     82 DxgiDuplicatorController::Instance() {
     83  // The static instance won't be deleted to ensure it can be used by other
     84  // threads even during program exiting.
     85  static DxgiDuplicatorController* instance = new DxgiDuplicatorController();
     86  return scoped_refptr<DxgiDuplicatorController>(instance);
     87 }
     88 
     89 // static
     90 bool DxgiDuplicatorController::IsCurrentSessionSupported() {
     91  DWORD current_session_id = GetCurrentSessionId();
     92  return current_session_id != kInvalidSessionId && current_session_id != 0;
     93 }
     94 
     95 DxgiDuplicatorController::DxgiDuplicatorController() : refcount_(0) {}
     96 
     97 void DxgiDuplicatorController::AddRef() {
     98  int refcount = (++refcount_);
     99  RTC_DCHECK(refcount > 0);
    100 }
    101 
    102 void DxgiDuplicatorController::Release() {
    103  int refcount = (--refcount_);
    104  RTC_DCHECK(refcount >= 0);
    105  if (refcount == 0) {
    106    RTC_LOG(LS_WARNING) << "Count of references reaches zero, "
    107                           "DxgiDuplicatorController will be unloaded.";
    108    Unload();
    109  }
    110 }
    111 
    112 bool DxgiDuplicatorController::IsSupported() {
    113  MutexLock lock(&mutex_);
    114  return Initialize();
    115 }
    116 
    117 bool DxgiDuplicatorController::RetrieveD3dInfo(D3dInfo* info) {
    118  bool result = false;
    119  {
    120    MutexLock lock(&mutex_);
    121    result = Initialize();
    122    *info = d3d_info_;
    123  }
    124  if (!result) {
    125    RTC_LOG(LS_WARNING) << "Failed to initialize DXGI components, the D3dInfo "
    126                           "retrieved may not accurate or out of date.";
    127  }
    128  return result;
    129 }
    130 
    131 DxgiDuplicatorController::Result DxgiDuplicatorController::Duplicate(
    132    DxgiFrame* frame) {
    133  return DoDuplicate(frame, -1);
    134 }
    135 
    136 DxgiDuplicatorController::Result DxgiDuplicatorController::DuplicateMonitor(
    137    DxgiFrame* frame,
    138    int monitor_id) {
    139  RTC_DCHECK_GE(monitor_id, 0);
    140  return DoDuplicate(frame, monitor_id);
    141 }
    142 
    143 DesktopVector DxgiDuplicatorController::system_dpi() {
    144  MutexLock lock(&mutex_);
    145  if (Initialize()) {
    146    return system_dpi_;
    147  }
    148  return DesktopVector();
    149 }
    150 
    151 int DxgiDuplicatorController::ScreenCount() {
    152  MutexLock lock(&mutex_);
    153  if (Initialize()) {
    154    return ScreenCountUnlocked();
    155  }
    156  return 0;
    157 }
    158 
    159 bool DxgiDuplicatorController::GetDeviceNames(
    160    std::vector<std::string>* output) {
    161  MutexLock lock(&mutex_);
    162  if (Initialize()) {
    163    GetDeviceNamesUnlocked(output);
    164    return true;
    165  }
    166  return false;
    167 }
    168 
    169 DxgiDuplicatorController::Result DxgiDuplicatorController::DoDuplicate(
    170    DxgiFrame* frame,
    171    int monitor_id) {
    172  RTC_DCHECK(frame);
    173  MutexLock lock(&mutex_);
    174 
    175  // The dxgi components and APIs do not update the screen resolution without
    176  // a reinitialization. So we use the GetDC() function to retrieve the screen
    177  // resolution to decide whether dxgi components need to be reinitialized.
    178  // If the screen resolution changed, it's very likely the next Duplicate()
    179  // function call will fail because of a missing monitor or the frame size is
    180  // not enough to store the output. So we reinitialize dxgi components in-place
    181  // to avoid a capture failure.
    182  // But there is no guarantee GetDC() function returns the same resolution as
    183  // dxgi APIs, we still rely on dxgi components to return the output frame
    184  // size.
    185  // TODO(zijiehe): Confirm whether IDXGIOutput::GetDesc() and
    186  // IDXGIOutputDuplication::GetDesc() can detect the resolution change without
    187  // reinitialization.
    188  if (display_configuration_monitor_.IsChanged(frame->source_id_)) {
    189    Deinitialize();
    190  }
    191 
    192  if (!Initialize()) {
    193    if (succeeded_duplications_ == 0 && !IsCurrentSessionSupported()) {
    194      RTC_LOG(LS_WARNING) << "Current binary is running in session 0. DXGI "
    195                             "components cannot be initialized.";
    196      return Result::UNSUPPORTED_SESSION;
    197    }
    198 
    199    // Cannot initialize COM components now, display mode may be changing.
    200    return Result::INITIALIZATION_FAILED;
    201  }
    202 
    203  if (!frame->Prepare(SelectedDesktopSize(monitor_id), monitor_id)) {
    204    return Result::FRAME_PREPARE_FAILED;
    205  }
    206 
    207  frame->frame()->mutable_updated_region()->Clear();
    208  frame->frame()->set_device_scale_factor(GetDeviceScaleFactor(monitor_id));
    209 
    210  if (DoDuplicateUnlocked(frame->context(), monitor_id, frame->frame())) {
    211    succeeded_duplications_++;
    212    return Result::SUCCEEDED;
    213  }
    214  if (monitor_id >= ScreenCountUnlocked()) {
    215    // It's a user error to provide a `monitor_id` larger than screen count. We
    216    // do not need to deinitialize.
    217    return Result::INVALID_MONITOR_ID;
    218  }
    219 
    220  // If the `monitor_id` is valid, but DoDuplicateUnlocked() failed, something
    221  // must be wrong from capturer APIs. We should Deinitialize().
    222  Deinitialize();
    223  return Result::DUPLICATION_FAILED;
    224 }
    225 
    226 void DxgiDuplicatorController::Unload() {
    227  MutexLock lock(&mutex_);
    228  Deinitialize();
    229 }
    230 
    231 void DxgiDuplicatorController::Unregister(const Context* const context) {
    232  MutexLock lock(&mutex_);
    233  if (ContextExpired(context)) {
    234    // The Context has not been setup after a recent initialization, so it
    235    // should not been registered in duplicators.
    236    return;
    237  }
    238  for (size_t i = 0; i < duplicators_.size(); i++) {
    239    duplicators_[i].Unregister(&context->contexts[i]);
    240  }
    241 }
    242 
    243 bool DxgiDuplicatorController::Initialize() {
    244  if (!duplicators_.empty()) {
    245    return true;
    246  }
    247 
    248  if (DoInitialize()) {
    249    return true;
    250  }
    251  Deinitialize();
    252  return false;
    253 }
    254 
    255 bool DxgiDuplicatorController::DoInitialize() {
    256  RTC_DCHECK(desktop_rect_.is_empty());
    257  RTC_DCHECK(duplicators_.empty());
    258 
    259  d3d_info_.min_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
    260  d3d_info_.max_feature_level = static_cast<D3D_FEATURE_LEVEL>(0);
    261 
    262  std::vector<D3dDevice> devices = D3dDevice::EnumDevices();
    263  if (devices.empty()) {
    264    RTC_LOG(LS_WARNING) << "No D3dDevice found.";
    265    return false;
    266  }
    267 
    268  for (size_t i = 0; i < devices.size(); i++) {
    269    D3D_FEATURE_LEVEL feature_level =
    270        devices[i].d3d_device()->GetFeatureLevel();
    271    if (d3d_info_.max_feature_level == 0 ||
    272        feature_level > d3d_info_.max_feature_level) {
    273      d3d_info_.max_feature_level = feature_level;
    274    }
    275    if (d3d_info_.min_feature_level == 0 ||
    276        feature_level < d3d_info_.min_feature_level) {
    277      d3d_info_.min_feature_level = feature_level;
    278    }
    279 
    280    DxgiAdapterDuplicator duplicator(devices[i]);
    281    // There may be several video cards on the system, some of them may not
    282    // support IDXGOutputDuplication. But they should not impact others from
    283    // taking effect, so we should continually try other adapters. This usually
    284    // happens when a non-official virtual adapter is installed on the system.
    285    if (!duplicator.Initialize()) {
    286      RTC_LOG(LS_WARNING) << "Failed to initialize DxgiAdapterDuplicator on "
    287                             "adapter "
    288                          << i;
    289      continue;
    290    }
    291    RTC_DCHECK(!duplicator.desktop_rect().is_empty());
    292    duplicators_.push_back(std::move(duplicator));
    293 
    294    desktop_rect_.UnionWith(duplicators_.back().desktop_rect());
    295  }
    296  TranslateRect();
    297 
    298  HDC hdc = GetDC(nullptr);
    299  // Use old DPI value if failed.
    300  if (hdc) {
    301    system_dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX),
    302                    GetDeviceCaps(hdc, LOGPIXELSY));
    303    ReleaseDC(nullptr, hdc);
    304  }
    305 
    306  identity_++;
    307 
    308  if (duplicators_.empty()) {
    309    RTC_LOG(LS_WARNING)
    310        << "Cannot initialize any DxgiAdapterDuplicator instance.";
    311  }
    312 
    313  return !duplicators_.empty();
    314 }
    315 
    316 void DxgiDuplicatorController::Deinitialize() {
    317  desktop_rect_ = DesktopRect();
    318  duplicators_.clear();
    319  display_configuration_monitor_.Reset();
    320 }
    321 
    322 bool DxgiDuplicatorController::ContextExpired(
    323    const Context* const context) const {
    324  RTC_DCHECK(context);
    325  return context->controller_id != identity_ ||
    326         context->contexts.size() != duplicators_.size();
    327 }
    328 
    329 void DxgiDuplicatorController::Setup(Context* context) {
    330  if (ContextExpired(context)) {
    331    RTC_DCHECK(context);
    332    context->contexts.clear();
    333    context->contexts.resize(duplicators_.size());
    334    for (size_t i = 0; i < duplicators_.size(); i++) {
    335      duplicators_[i].Setup(&context->contexts[i]);
    336    }
    337    context->controller_id = identity_;
    338  }
    339 }
    340 
    341 bool DxgiDuplicatorController::DoDuplicateUnlocked(Context* context,
    342                                                   int monitor_id,
    343                                                   SharedDesktopFrame* target) {
    344  Setup(context);
    345 
    346  if (!EnsureFrameCaptured(context, monitor_id, target)) {
    347    return false;
    348  }
    349 
    350  bool result = false;
    351  if (monitor_id < 0) {
    352    // Capture entire screen.
    353    result = DoDuplicateAll(context, target);
    354  } else {
    355    result = DoDuplicateOne(context, monitor_id, target);
    356  }
    357 
    358  if (result) {
    359    target->set_dpi(system_dpi_);
    360    return true;
    361  }
    362 
    363  return false;
    364 }
    365 
    366 bool DxgiDuplicatorController::DoDuplicateAll(Context* context,
    367                                              SharedDesktopFrame* target) {
    368  for (size_t i = 0; i < duplicators_.size(); i++) {
    369    if (!duplicators_[i].Duplicate(&context->contexts[i], target)) {
    370      return false;
    371    }
    372  }
    373  return true;
    374 }
    375 
    376 bool DxgiDuplicatorController::DoDuplicateOne(Context* context,
    377                                              int monitor_id,
    378                                              SharedDesktopFrame* target) {
    379  RTC_DCHECK(monitor_id >= 0);
    380  for (size_t i = 0; i < duplicators_.size() && i < context->contexts.size();
    381       i++) {
    382    if (monitor_id >= duplicators_[i].screen_count()) {
    383      monitor_id -= duplicators_[i].screen_count();
    384    } else {
    385      if (duplicators_[i].DuplicateMonitor(&context->contexts[i], monitor_id,
    386                                           target)) {
    387        target->set_top_left(duplicators_[i].ScreenRect(monitor_id).top_left());
    388        return true;
    389      }
    390      return false;
    391    }
    392  }
    393  return false;
    394 }
    395 
    396 int64_t DxgiDuplicatorController::GetNumFramesCaptured(int monitor_id) const {
    397  int64_t min = INT64_MAX;
    398  if (monitor_id < 0) {
    399    for (const auto& duplicator : duplicators_) {
    400      min = std::min(min, duplicator.GetNumFramesCaptured(monitor_id));
    401    }
    402    return min;
    403  }
    404  for (const auto& duplicator : duplicators_) {
    405    if (monitor_id >= duplicator.screen_count()) {
    406      monitor_id -= duplicator.screen_count();
    407    } else {
    408      return duplicator.GetNumFramesCaptured(monitor_id);
    409    }
    410  }
    411  return min;
    412 }
    413 
    414 DesktopSize DxgiDuplicatorController::desktop_size() const {
    415  return desktop_rect_.size();
    416 }
    417 
    418 std::optional<float> DxgiDuplicatorController::GetDeviceScaleFactor(
    419    int monitor_id) const {
    420  if (monitor_id < 0) {
    421    return std::nullopt;
    422  }
    423  for (const auto& duplicator : duplicators_) {
    424    if (monitor_id >= duplicator.screen_count()) {
    425      monitor_id -= duplicator.screen_count();
    426    } else {
    427      return duplicator.GetDeviceScaleFactor(monitor_id);
    428    }
    429  }
    430  return std::nullopt;
    431 }
    432 
    433 DesktopRect DxgiDuplicatorController::ScreenRect(int id) const {
    434  RTC_DCHECK(id >= 0);
    435  for (size_t i = 0; i < duplicators_.size(); i++) {
    436    if (id >= duplicators_[i].screen_count()) {
    437      id -= duplicators_[i].screen_count();
    438    } else {
    439      return duplicators_[i].ScreenRect(id);
    440    }
    441  }
    442  return DesktopRect();
    443 }
    444 
    445 int DxgiDuplicatorController::ScreenCountUnlocked() const {
    446  int result = 0;
    447  for (auto& duplicator : duplicators_) {
    448    result += duplicator.screen_count();
    449  }
    450  return result;
    451 }
    452 
    453 void DxgiDuplicatorController::GetDeviceNamesUnlocked(
    454    std::vector<std::string>* output) const {
    455  RTC_DCHECK(output);
    456  for (auto& duplicator : duplicators_) {
    457    for (int i = 0; i < duplicator.screen_count(); i++) {
    458      output->push_back(duplicator.GetDeviceName(i));
    459    }
    460  }
    461 }
    462 
    463 DesktopSize DxgiDuplicatorController::SelectedDesktopSize(
    464    int monitor_id) const {
    465  if (monitor_id < 0) {
    466    return desktop_size();
    467  }
    468 
    469  return ScreenRect(monitor_id).size();
    470 }
    471 
    472 bool DxgiDuplicatorController::EnsureFrameCaptured(Context* context,
    473                                                   int monitor_id,
    474                                                   SharedDesktopFrame* target) {
    475  // On a modern system, the FPS / monitor refresh rate is usually larger than
    476  // or equal to 60. So 17 milliseconds is enough to capture at least one frame.
    477  const int64_t ms_per_frame = 17;
    478  // Skip frames to ensure a full frame refresh has occurred and the DXGI
    479  // machinery is producing frames before this function returns.
    480  int64_t frames_to_skip = 1;
    481  // The total time out milliseconds for this function. If we cannot get enough
    482  // frames during this time interval, this function returns false, and cause
    483  // the DXGI components to be reinitialized. This usually should not happen
    484  // unless the system is switching display mode when this function is being
    485  // called. 500 milliseconds should be enough for ~30 frames.
    486  const int64_t timeout_ms = 500;
    487 
    488  if (GetNumFramesCaptured(monitor_id) == 0 && !IsConsoleSession()) {
    489    // When capturing a console session, waiting for a single frame is
    490    // sufficient to ensure that DXGI output duplication is working. When the
    491    // session is not attached to the console, it has been observed that DXGI
    492    // may produce up to 4 frames (typically 1-2 though) before stopping. When
    493    // this condition occurs, no errors are returned from the output duplication
    494    // API, it simply appears that nothing is changing on the screen. Thus for
    495    // detached sessions, we need to capture a few extra frames before we can be
    496    // confident that output duplication was initialized properly.
    497    frames_to_skip = 5;
    498  }
    499 
    500  if (GetNumFramesCaptured(monitor_id) >= frames_to_skip) {
    501    return true;
    502  }
    503 
    504  std::unique_ptr<SharedDesktopFrame> fallback_frame;
    505  SharedDesktopFrame* shared_frame = nullptr;
    506  DesktopSize selected_size = SelectedDesktopSize(monitor_id);
    507  if (target->size().width() >= selected_size.width() &&
    508      target->size().height() >= selected_size.height()) {
    509    // `target` is large enough to cover the currently captured screen,
    510    // we do not need to use `fallback_frame`.
    511    shared_frame = target;
    512  } else {
    513    fallback_frame = SharedDesktopFrame::Wrap(
    514        std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(selected_size)));
    515    shared_frame = fallback_frame.get();
    516  }
    517 
    518  const int64_t start_ms = TimeMillis();
    519  while (GetNumFramesCaptured(monitor_id) < frames_to_skip) {
    520    if (monitor_id < 0) {
    521      if (!DoDuplicateAll(context, shared_frame)) {
    522        return false;
    523      }
    524    } else {
    525      if (!DoDuplicateOne(context, monitor_id, shared_frame)) {
    526        return false;
    527      }
    528    }
    529 
    530    // Calling DoDuplicateAll() may change the number of frames captured.
    531    if (GetNumFramesCaptured(monitor_id) >= frames_to_skip) {
    532      break;
    533    }
    534 
    535    if (TimeMillis() - start_ms > timeout_ms) {
    536      RTC_LOG(LS_ERROR) << "Failed to capture " << frames_to_skip
    537                        << " frames "
    538                           "within "
    539                        << timeout_ms << " milliseconds.";
    540      return false;
    541    }
    542 
    543    // Sleep `ms_per_frame` before attempting to capture the next frame to
    544    // ensure the video adapter has time to update the screen.
    545    Thread::SleepMs(ms_per_frame);
    546  }
    547  // When capturing multiple monitors, we need to update the captured region to
    548  // prevent flickering by re-setting context. See
    549  // https://crbug.com/webrtc/15718 for details.
    550  if (shared_frame != target) {
    551    context->Reset();
    552    Setup(context);
    553  }
    554  return true;
    555 }
    556 
    557 void DxgiDuplicatorController::TranslateRect() {
    558  const DesktopVector position =
    559      DesktopVector().subtract(desktop_rect_.top_left());
    560  desktop_rect_.Translate(position);
    561  for (auto& duplicator : duplicators_) {
    562    duplicator.TranslateRect(position);
    563  }
    564 }
    565 
    566 }  // namespace webrtc