DirectX capturer may crash after switching shared screen

The root cause is because the offset used in DxgiOutputDuplicator should always
depend on the position of the monitor in the system instead of the offset in the
target frame. Otherwise, once switching between two monitors with different
screen size, the updated region in the context would base on the old monitor,
and cause the copied regions to be out of the source DesktopFrame.
This issue also impacts the SpreadContextChange() function, the updated region
stores in the Context should also only depend on the position of the monitor.

BUG=chromium:706797

Review-Url: https://codereview.webrtc.org/2801433002
Cr-Commit-Position: refs/heads/master@{#17548}
diff --git a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc
index 04f4664..fb8dacb 100644
--- a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc
+++ b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc
@@ -57,12 +57,6 @@
   return Rotation::CLOCK_WISE_0;
 }
 
-// Translates |rect| with the reverse of |offset|
-DesktopRect ReverseTranslate(DesktopRect rect, DesktopVector offset) {
-  rect.Translate(-offset.x(), -offset.y());
-  return rect;
-}
-
 }  // namespace
 
 DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device,
@@ -132,8 +126,7 @@
   }
 
   rotation_ = DxgiRotationToRotation(desc_.Rotation);
-  unrotated_size_ =
-      RotateSize(desktop_rect_.size(), ReverseRotation(rotation_));
+  unrotated_size_ = RotateSize(desktop_size(), ReverseRotation(rotation_));
 
   return true;
 }
@@ -157,7 +150,7 @@
   RTC_DCHECK(texture_);
   RTC_DCHECK(target);
   if (!DesktopRect::MakeSize(target->size())
-           .ContainsRect(TranslatedDesktopRect(offset))) {
+           .ContainsRect(GetTranslatedDesktopRect(offset))) {
     // target size is not large enough to cover current output region.
     return false;
   }
@@ -175,37 +168,43 @@
 
   // We need to merge updated region with the one from context, but only spread
   // updated region from current frame. So keeps a copy of updated region from
-  // context here.
+  // context here. The |updated_region| always starts from (0, 0).
   DesktopRegion updated_region;
   updated_region.Swap(&context->updated_region);
   if (error.Error() == S_OK &&
       frame_info.AccumulatedFrames > 0 &&
       resource) {
-    DetectUpdatedRegion(frame_info, offset, &context->updated_region);
+    DetectUpdatedRegion(frame_info, &context->updated_region);
+    SpreadContextChange(context);
     if (!texture_->CopyFrom(frame_info, resource.Get())) {
       return false;
     }
-    SpreadContextChange(context);
     updated_region.AddRegion(context->updated_region);
+    // TODO(zijiehe): Figure out why clearing context->updated_region() here
+    // triggers screen flickering?
 
     const DesktopFrame& source = texture_->AsDesktopFrame();
     if (rotation_ != Rotation::CLOCK_WISE_0) {
       for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
            it.Advance()) {
-        const DesktopRect source_rect =
-            RotateRect(ReverseTranslate(it.rect(), offset),
-                       desktop_rect().size(), ReverseRotation(rotation_));
+        // The |updated_region| returned by Windows is rotated, but the |source|
+        // frame is not. So we need to rotate it reversely.
+        const DesktopRect source_rect = RotateRect(
+            it.rect(), desktop_size(), ReverseRotation(rotation_));
         RotateDesktopFrame(source, source_rect, rotation_, offset, target);
       }
     } else {
       for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
            it.Advance()) {
-        target->CopyPixelsFrom(
-            source, ReverseTranslate(it.rect(), offset).top_left(), it.rect());
+        // The DesktopRect in |target|, starts from offset.
+        DesktopRect dest_rect = it.rect();
+        dest_rect.Translate(offset);
+        target->CopyPixelsFrom(source, it.rect().top_left(), dest_rect);
       }
     }
     last_frame_ = target->Share();
     last_frame_offset_ = offset;
+    updated_region.Translate(offset.x(), offset.y());
     target->mutable_updated_region()->AddRegion(updated_region);
     num_frames_captured_++;
     return texture_->Release() && ReleaseFrame();
@@ -216,8 +215,15 @@
     // export last frame to the target.
     for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
          it.Advance()) {
-      target->CopyPixelsFrom(*last_frame_, it.rect().top_left(), it.rect());
+      // The DesktopRect in |source|, starts from last_frame_offset_.
+      DesktopRect source_rect = it.rect();
+      // The DesktopRect in |target|, starts from offset.
+      DesktopRect target_rect = source_rect;
+      source_rect.Translate(last_frame_offset_);
+      target_rect.Translate(offset);
+      target->CopyPixelsFrom(*last_frame_, source_rect.top_left(), target_rect);
     }
+    updated_region.Translate(offset.x(), offset.y());
     target->mutable_updated_region()->AddRegion(updated_region);
   } else {
     // If we were at the very first frame, and capturing failed, the
@@ -229,23 +235,26 @@
   return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame();
 }
 
-DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect(DesktopVector offset) {
-  DesktopRect result(DesktopRect::MakeSize(desktop_rect_.size()));
+DesktopRect DxgiOutputDuplicator::GetTranslatedDesktopRect(
+    DesktopVector offset) const {
+  DesktopRect result(DesktopRect::MakeSize(desktop_size()));
   result.Translate(offset);
   return result;
 }
 
+DesktopRect DxgiOutputDuplicator::GetUntranslatedDesktopRect() const {
+  return DesktopRect::MakeSize(desktop_size());
+}
+
 void DxgiOutputDuplicator::DetectUpdatedRegion(
     const DXGI_OUTDUPL_FRAME_INFO& frame_info,
-    DesktopVector offset,
     DesktopRegion* updated_region) {
   if (DoDetectUpdatedRegion(frame_info, updated_region)) {
-    updated_region->Translate(offset.x(), offset.y());
     // Make sure even a region returned by Windows API is out of the scope of
     // desktop_rect_, we still won't export it to the target DesktopFrame.
-    updated_region->IntersectWith(TranslatedDesktopRect(offset));
+    updated_region->IntersectWith(GetUntranslatedDesktopRect());
   } else {
-    updated_region->SetRect(TranslatedDesktopRect(offset));
+    updated_region->SetRect(GetUntranslatedDesktopRect());
   }
 }
 
@@ -340,7 +349,7 @@
 void DxgiOutputDuplicator::Setup(Context* context) {
   RTC_DCHECK(context->updated_region.is_empty());
   // Always copy entire monitor during the first Duplicate() function call.
-  context->updated_region.AddRect(desktop_rect_);
+  context->updated_region.AddRect(GetUntranslatedDesktopRect());
   RTC_DCHECK(std::find(contexts_.begin(), contexts_.end(), context) ==
              contexts_.end());
   contexts_.push_back(context);
@@ -361,6 +370,10 @@
   }
 }
 
+DesktopSize DxgiOutputDuplicator::desktop_size() const {
+  return desktop_rect_.size();
+}
+
 int64_t DxgiOutputDuplicator::num_frames_captured() const {
 #if !defined(NDEBUG)
   RTC_DCHECK_EQ(!!last_frame_, num_frames_captured_ > 0);
diff --git a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h
index 2ac0a75..2e85bf7 100644
--- a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h
+++ b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h
@@ -36,8 +36,8 @@
  public:
   struct Context {
     // The updated region DxgiOutputDuplicator::DetectUpdatedRegion() output
-    // during last Duplicate() function call. It's a DesktopRegion translated by
-    // offset of each DxgiOutputDuplicator instance.
+    // during last Duplicate() function call. It's always relative to the
+    // (0, 0).
     DesktopRegion updated_region;
   };
 
@@ -80,11 +80,9 @@
   int64_t num_frames_captured() const;
 
  private:
-  // Detects updated region translated by offset from IDXGIOutput1. This
-  // function will set the |updated_region| as entire DesktopRect starts from
-  // offset if it failed to execute Windows APIs.
+  // Calls DoDetectUpdatedRegion(). If it fails, this function sets the
+  // |updated_region| as entire UntranslatedDesktopRect().
   void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info,
-                           DesktopVector offset,
                            DesktopRegion* updated_region);
 
   // Returns untranslated updated region, which are directly returned by Windows
@@ -98,14 +96,21 @@
   // Returns false if system does not support IDXGIOutputDuplication.
   bool DuplicateOutput();
 
-  // Returns a DesktopRect with the same size of desktop_size_, but translated
+  // Returns a DesktopRect with the same size of desktop_size(), but translated
   // by offset.
-  DesktopRect TranslatedDesktopRect(DesktopVector offset);
+  DesktopRect GetTranslatedDesktopRect(DesktopVector offset) const;
+
+  // Returns a DesktopRect with the same size of desktop_size(), but starts from
+  // (0, 0).
+  DesktopRect GetUntranslatedDesktopRect() const;
 
   // Spreads changes from |context| to other registered Context(s) in
   // contexts_.
   void SpreadContextChange(const Context* const context);
 
+  // Returns the size of desktop rectangle current instance representing.
+  DesktopSize desktop_size() const;
+
   const D3dDevice device_;
   const Microsoft::WRL::ComPtr<IDXGIOutput1> output_;
   const DesktopRect desktop_rect_;