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