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