tor-browser

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

dxgi_output_duplicator.cc (17410B)


      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_output_duplicator.h"
     12 
     13 #include <dxgi.h>
     14 #include <dxgiformat.h>
     15 #include <unknwn.h>
     16 #include <windows.h>
     17 
     18 #include <algorithm>
     19 #include <cstdint>
     20 #include <cstring>
     21 #include <optional>
     22 
     23 #include "modules/desktop_capture/desktop_frame.h"
     24 #include "modules/desktop_capture/desktop_frame_rotation.h"
     25 #include "modules/desktop_capture/desktop_geometry.h"
     26 #include "modules/desktop_capture/desktop_region.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/desktop_capture_utils.h"
     30 #include "modules/desktop_capture/win/dxgi_texture_mapping.h"
     31 #include "modules/desktop_capture/win/dxgi_texture_staging.h"
     32 #include "rtc_base/checks.h"
     33 #include "rtc_base/logging.h"
     34 #include "rtc_base/string_utils.h"
     35 #include "rtc_base/win32.h"
     36 #include "system_wrappers/include/metrics.h"
     37 
     38 namespace webrtc {
     39 
     40 using Microsoft::WRL::ComPtr;
     41 
     42 namespace {
     43 
     44 // Timeout for AcquireNextFrame() call.
     45 // DxgiDuplicatorController leverages external components to do the capture
     46 // scheduling. So here DxgiOutputDuplicator does not need to actively wait for a
     47 // new frame.
     48 const int kAcquireTimeoutMs = 0;
     49 
     50 DesktopRect RECTToDesktopRect(const RECT& rect) {
     51  return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
     52 }
     53 
     54 Rotation DxgiRotationToRotation(DXGI_MODE_ROTATION rotation) {
     55  switch (rotation) {
     56    case DXGI_MODE_ROTATION_IDENTITY:
     57    case DXGI_MODE_ROTATION_UNSPECIFIED:
     58      return Rotation::CLOCK_WISE_0;
     59    case DXGI_MODE_ROTATION_ROTATE90:
     60      return Rotation::CLOCK_WISE_90;
     61    case DXGI_MODE_ROTATION_ROTATE180:
     62      return Rotation::CLOCK_WISE_180;
     63    case DXGI_MODE_ROTATION_ROTATE270:
     64      return Rotation::CLOCK_WISE_270;
     65  }
     66 
     67  RTC_DCHECK_NOTREACHED();
     68  return Rotation::CLOCK_WISE_0;
     69 }
     70 
     71 }  // namespace
     72 
     73 DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device,
     74                                           const ComPtr<IDXGIOutput1>& output,
     75                                           const DXGI_OUTPUT_DESC& desc)
     76    : device_(device),
     77      output_(output),
     78      device_name_(ToUtf8(desc.DeviceName)),
     79      desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)),
     80      monitor_(desc.Monitor) {
     81  RTC_DCHECK(output_);
     82  RTC_DCHECK(!desktop_rect_.is_empty());
     83  RTC_DCHECK_GT(desktop_rect_.width(), 0);
     84  RTC_DCHECK_GT(desktop_rect_.height(), 0);
     85 }
     86 
     87 DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) =
     88    default;
     89 
     90 DxgiOutputDuplicator::~DxgiOutputDuplicator() {
     91  if (duplication_) {
     92    duplication_->ReleaseFrame();
     93  }
     94  texture_.reset();
     95 }
     96 
     97 bool DxgiOutputDuplicator::Initialize() {
     98  if (DuplicateOutput()) {
     99    if (desc_.DesktopImageInSystemMemory) {
    100      texture_.reset(new DxgiTextureMapping(duplication_.Get()));
    101    } else {
    102      texture_.reset(new DxgiTextureStaging(device_));
    103    }
    104    return true;
    105  } else {
    106    duplication_.Reset();
    107    return false;
    108  }
    109 }
    110 
    111 bool DxgiOutputDuplicator::DuplicateOutput() {
    112  RTC_DCHECK(!duplication_);
    113  _com_error error =
    114      output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()),
    115                               duplication_.GetAddressOf());
    116  if (error.Error() != S_OK || !duplication_) {
    117    RTC_LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1: "
    118                        << desktop_capture::utils::ComErrorToString(error);
    119    return false;
    120  }
    121 
    122  memset(&desc_, 0, sizeof(desc_));
    123  duplication_->GetDesc(&desc_);
    124 
    125  // DXGI_FORMAT_R16G16B16A16_FLOAT is returned for HDR monitor,
    126  // DXGI_FORMAT_B8G8R8A8_UNORM for others.
    127  if ((desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) &&
    128      (desc_.ModeDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT)) {
    129    RTC_LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8, 16 bit)"
    130                      << "which is required by downstream components"
    131                      << "format is " << desc_.ModeDesc.Format;
    132    return false;
    133  }
    134 
    135  if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() ||
    136      static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) {
    137    RTC_LOG(LS_ERROR)
    138        << "IDXGIDuplicateOutput does not return a same size as its "
    139        << "IDXGIOutput1, size returned by IDXGIDuplicateOutput is "
    140        << desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height
    141        << ", size returned by IDXGIOutput1 is " << desktop_rect_.width()
    142        << " x " << desktop_rect_.height();
    143    return false;
    144  }
    145 
    146  rotation_ = DxgiRotationToRotation(desc_.Rotation);
    147  unrotated_size_ = RotateSize(desktop_size(), ReverseRotation(rotation_));
    148 
    149  return true;
    150 }
    151 
    152 bool DxgiOutputDuplicator::ReleaseFrame() {
    153  RTC_DCHECK(duplication_);
    154  _com_error error = duplication_->ReleaseFrame();
    155  if (error.Error() != S_OK) {
    156    RTC_LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication: "
    157                      << desktop_capture::utils::ComErrorToString(error);
    158    return false;
    159  }
    160  return true;
    161 }
    162 
    163 bool DxgiOutputDuplicator::ContainsMouseCursor(
    164    const DXGI_OUTDUPL_FRAME_INFO& frame_info) {
    165  // The DXGI_OUTDUPL_POINTER_POSITION structure that describes the most recent
    166  // mouse position is only valid if the LastMouseUpdateTime member is a non-
    167  // zero value.
    168  if (frame_info.LastMouseUpdateTime.QuadPart == 0)
    169    return false;
    170 
    171  // Ignore cases when the mouse shape has changed and not the position.
    172  const bool new_pointer_shape = (frame_info.PointerShapeBufferSize != 0);
    173  if (new_pointer_shape)
    174    return false;
    175 
    176  // The mouse cursor has moved and we can now query if the mouse pointer is
    177  // drawn onto the desktop image or not to decide if we must draw the mouse
    178  // pointer shape onto the desktop image (always done by default currently).
    179  // Either the mouse pointer is already drawn onto the desktop image that
    180  // IDXGIOutputDuplication::AcquireNextFrame provides or the mouse pointer is
    181  // separate from the desktop image. If the mouse pointer is drawn onto the
    182  // desktop image, the pointer position data that is reported by
    183  // AcquireNextFrame indicates that a separate pointer isn’t visible, hence
    184  // `frame_info.PointerPosition.Visible` is false.
    185  const bool cursor_embedded_in_frame = !frame_info.PointerPosition.Visible;
    186  RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.Win.DirectXCursorEmbedded",
    187                        cursor_embedded_in_frame);
    188  return cursor_embedded_in_frame;
    189 }
    190 
    191 bool DxgiOutputDuplicator::Duplicate(Context* context,
    192                                     DesktopVector offset,
    193                                     SharedDesktopFrame* target) {
    194  RTC_DCHECK(duplication_);
    195  RTC_DCHECK(texture_);
    196  RTC_DCHECK(target);
    197  if (!DesktopRect::MakeSize(target->size())
    198           .ContainsRect(GetTranslatedDesktopRect(offset))) {
    199    // target size is not large enough to cover current output region.
    200    return false;
    201  }
    202 
    203  DXGI_OUTDUPL_FRAME_INFO frame_info;
    204  memset(&frame_info, 0, sizeof(frame_info));
    205  ComPtr<IDXGIResource> resource;
    206  _com_error error = duplication_->AcquireNextFrame(
    207      kAcquireTimeoutMs, &frame_info, resource.GetAddressOf());
    208  if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) {
    209    RTC_LOG(LS_ERROR) << "Failed to capture frame: "
    210                      << desktop_capture::utils::ComErrorToString(error);
    211    return false;
    212  }
    213 
    214  const bool cursor_embedded_in_frame = ContainsMouseCursor(frame_info);
    215 
    216  // We need to merge updated region with the one from context, but only spread
    217  // updated region from current frame. So keeps a copy of updated region from
    218  // context here. The `updated_region` always starts from (0, 0).
    219  DesktopRegion updated_region;
    220  updated_region.Swap(&context->updated_region);
    221  if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0 && resource) {
    222    DetectUpdatedRegion(frame_info, &context->updated_region);
    223    SpreadContextChange(context);
    224    if (!texture_->CopyFrom(frame_info, resource.Get())) {
    225      return false;
    226    }
    227    updated_region.AddRegion(context->updated_region);
    228    // TODO(zijiehe): Figure out why clearing context->updated_region() here
    229    // triggers screen flickering?
    230 
    231    const DesktopFrame& source = texture_->AsDesktopFrame();
    232    if (rotation_ != Rotation::CLOCK_WISE_0) {
    233      for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
    234           it.Advance()) {
    235        // The `updated_region` returned by Windows is rotated, but the `source`
    236        // frame is not. So we need to rotate it reversely.
    237        const DesktopRect source_rect =
    238            RotateRect(it.rect(), desktop_size(), ReverseRotation(rotation_));
    239        RotateDesktopFrame(source, source_rect, rotation_, offset, target);
    240      }
    241    } else {
    242      for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
    243           it.Advance()) {
    244        // The DesktopRect in `target`, starts from offset.
    245        DesktopRect dest_rect = it.rect();
    246        dest_rect.Translate(offset);
    247        target->CopyPixelsFrom(source, it.rect().top_left(), dest_rect);
    248      }
    249    }
    250    last_frame_ = target->Share();
    251    last_frame_offset_ = offset;
    252    updated_region.Translate(offset.x(), offset.y());
    253    target->mutable_updated_region()->AddRegion(updated_region);
    254    target->set_may_contain_cursor(cursor_embedded_in_frame);
    255    num_frames_captured_++;
    256    return texture_->Release() && ReleaseFrame();
    257  }
    258 
    259  if (last_frame_) {
    260    // No change since last frame or AcquireNextFrame() timed out, we will
    261    // export last frame to the target.
    262    for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
    263         it.Advance()) {
    264      // The DesktopRect in `source`, starts from last_frame_offset_.
    265      DesktopRect source_rect = it.rect();
    266      // The DesktopRect in `target`, starts from offset.
    267      DesktopRect target_rect = source_rect;
    268      source_rect.Translate(last_frame_offset_);
    269      target_rect.Translate(offset);
    270      target->CopyPixelsFrom(*last_frame_, source_rect.top_left(), target_rect);
    271    }
    272    updated_region.Translate(offset.x(), offset.y());
    273    target->mutable_updated_region()->AddRegion(updated_region);
    274    target->set_may_contain_cursor(cursor_embedded_in_frame);
    275  } else {
    276    // If we were at the very first frame, and capturing failed, the
    277    // context->updated_region should be kept unchanged for next attempt.
    278    context->updated_region.Swap(&updated_region);
    279  }
    280  // If AcquireNextFrame() failed with timeout error, we do not need to release
    281  // the frame.
    282  return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame();
    283 }
    284 
    285 DesktopRect DxgiOutputDuplicator::GetTranslatedDesktopRect(
    286    DesktopVector offset) const {
    287  DesktopRect result(DesktopRect::MakeSize(desktop_size()));
    288  result.Translate(offset);
    289  return result;
    290 }
    291 
    292 DesktopRect DxgiOutputDuplicator::GetUntranslatedDesktopRect() const {
    293  return DesktopRect::MakeSize(desktop_size());
    294 }
    295 
    296 void DxgiOutputDuplicator::DetectUpdatedRegion(
    297    const DXGI_OUTDUPL_FRAME_INFO& frame_info,
    298    DesktopRegion* updated_region) {
    299  if (DoDetectUpdatedRegion(frame_info, updated_region)) {
    300    // Make sure even a region returned by Windows API is out of the scope of
    301    // desktop_rect_, we still won't export it to the target DesktopFrame.
    302    updated_region->IntersectWith(GetUntranslatedDesktopRect());
    303  } else {
    304    updated_region->SetRect(GetUntranslatedDesktopRect());
    305  }
    306 }
    307 
    308 bool DxgiOutputDuplicator::DoDetectUpdatedRegion(
    309    const DXGI_OUTDUPL_FRAME_INFO& frame_info,
    310    DesktopRegion* updated_region) {
    311  RTC_DCHECK(updated_region);
    312  updated_region->Clear();
    313  if (frame_info.TotalMetadataBufferSize == 0) {
    314    // This should not happen, since frame_info.AccumulatedFrames > 0.
    315    RTC_LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, "
    316                      << "but TotalMetadataBufferSize == 0";
    317    return false;
    318  }
    319 
    320  if (metadata_.size() < frame_info.TotalMetadataBufferSize) {
    321    metadata_.clear();  // Avoid data copy
    322    metadata_.resize(frame_info.TotalMetadataBufferSize);
    323  }
    324 
    325  UINT buff_size = 0;
    326  DXGI_OUTDUPL_MOVE_RECT* move_rects =
    327      reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata_.data());
    328  size_t move_rects_count = 0;
    329  _com_error error = duplication_->GetFrameMoveRects(
    330      static_cast<UINT>(metadata_.size()), move_rects, &buff_size);
    331  if (error.Error() != S_OK) {
    332    RTC_LOG(LS_ERROR) << "Failed to get move rectangles: "
    333                      << desktop_capture::utils::ComErrorToString(error);
    334    return false;
    335  }
    336  move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
    337 
    338  RECT* dirty_rects = reinterpret_cast<RECT*>(metadata_.data() + buff_size);
    339  size_t dirty_rects_count = 0;
    340  error = duplication_->GetFrameDirtyRects(
    341      static_cast<UINT>(metadata_.size()) - buff_size, dirty_rects, &buff_size);
    342  if (error.Error() != S_OK) {
    343    RTC_LOG(LS_ERROR) << "Failed to get dirty rectangles: "
    344                      << desktop_capture::utils::ComErrorToString(error);
    345    return false;
    346  }
    347  dirty_rects_count = buff_size / sizeof(RECT);
    348 
    349  while (move_rects_count > 0) {
    350    // DirectX capturer API may randomly return unmoved move_rects, which should
    351    // be skipped to avoid unnecessary wasting of differing and encoding
    352    // resources.
    353    // By using testing application it2me_standalone_host_main, this check
    354    // reduces average capture time by 0.375% (4.07 -> 4.055), and average
    355    // encode time by 0.313% (8.042 -> 8.016) without other impacts.
    356    if (move_rects->SourcePoint.x != move_rects->DestinationRect.left ||
    357        move_rects->SourcePoint.y != move_rects->DestinationRect.top) {
    358      updated_region->AddRect(
    359          RotateRect(DesktopRect::MakeXYWH(move_rects->SourcePoint.x,
    360                                           move_rects->SourcePoint.y,
    361                                           move_rects->DestinationRect.right -
    362                                               move_rects->DestinationRect.left,
    363                                           move_rects->DestinationRect.bottom -
    364                                               move_rects->DestinationRect.top),
    365                     unrotated_size_, rotation_));
    366      updated_region->AddRect(
    367          RotateRect(DesktopRect::MakeLTRB(move_rects->DestinationRect.left,
    368                                           move_rects->DestinationRect.top,
    369                                           move_rects->DestinationRect.right,
    370                                           move_rects->DestinationRect.bottom),
    371                     unrotated_size_, rotation_));
    372    } else {
    373      RTC_LOG(LS_INFO) << "Unmoved move_rect detected, ["
    374                       << move_rects->DestinationRect.left << ", "
    375                       << move_rects->DestinationRect.top << "] - ["
    376                       << move_rects->DestinationRect.right << ", "
    377                       << move_rects->DestinationRect.bottom << "].";
    378    }
    379    move_rects++;
    380    move_rects_count--;
    381  }
    382 
    383  while (dirty_rects_count > 0) {
    384    updated_region->AddRect(RotateRect(
    385        DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top,
    386                              dirty_rects->right, dirty_rects->bottom),
    387        unrotated_size_, rotation_));
    388    dirty_rects++;
    389    dirty_rects_count--;
    390  }
    391 
    392  return true;
    393 }
    394 
    395 void DxgiOutputDuplicator::Setup(Context* context) {
    396  RTC_DCHECK(context->updated_region.is_empty());
    397  // Always copy entire monitor during the first Duplicate() function call.
    398  context->updated_region.AddRect(GetUntranslatedDesktopRect());
    399  RTC_DCHECK(std::find(contexts_.begin(), contexts_.end(), context) ==
    400             contexts_.end());
    401  contexts_.push_back(context);
    402 }
    403 
    404 void DxgiOutputDuplicator::Unregister(const Context* const context) {
    405  auto it = std::find(contexts_.begin(), contexts_.end(), context);
    406  RTC_DCHECK(it != contexts_.end());
    407  contexts_.erase(it);
    408 }
    409 
    410 void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) {
    411  for (Context* dest : contexts_) {
    412    RTC_DCHECK(dest);
    413    if (dest != source) {
    414      dest->updated_region.AddRegion(source->updated_region);
    415    }
    416  }
    417 }
    418 
    419 DesktopSize DxgiOutputDuplicator::desktop_size() const {
    420  return desktop_rect_.size();
    421 }
    422 
    423 int64_t DxgiOutputDuplicator::num_frames_captured() const {
    424 #if !defined(NDEBUG)
    425  RTC_DCHECK_EQ(!!last_frame_, num_frames_captured_ > 0);
    426 #endif
    427  return num_frames_captured_;
    428 }
    429 
    430 std::optional<float> DxgiOutputDuplicator::device_scale_factor() const {
    431  DEVICE_SCALE_FACTOR device_scale_factor = DEVICE_SCALE_FACTOR_INVALID;
    432  HRESULT hr = GetScaleFactorForMonitor(monitor_, &device_scale_factor);
    433  if (FAILED(hr)) {
    434    RTC_LOG(LS_ERROR) << "Failed to get scale factor for monitor: " << hr;
    435    return std::nullopt;
    436  }
    437  RTC_DCHECK(device_scale_factor != DEVICE_SCALE_FACTOR_INVALID);
    438  return static_cast<float>(device_scale_factor) / 100.0f;
    439 }
    440 
    441 void DxgiOutputDuplicator::TranslateRect(const DesktopVector& position) {
    442  desktop_rect_.Translate(position);
    443  RTC_DCHECK_GE(desktop_rect_.left(), 0);
    444  RTC_DCHECK_GE(desktop_rect_.top(), 0);
    445 }
    446 
    447 }  // namespace webrtc