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