Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(852)

Side by Side Diff: webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc

Issue 1845113002: DirectX based screen capturer logic (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 "webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h"
12
13 #include <assert.h>
14 #include <string.h>
15
16 #include <comdef.h>
17 #include <wincodec.h>
18 #include <DXGI.h>
19
20 #include "webrtc/modules/desktop_capture/desktop_frame_win.h"
21 #include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
22 #include "webrtc/system_wrappers/include/logging.h"
23 #include "webrtc/system_wrappers/include/tick_util.h"
24
25 namespace webrtc {
26
27 using Microsoft::WRL::ComPtr;
28 using rtc::scoped_ptr;
Sergey Ulanov 2016/03/31 18:41:16 scoped_ptr<> is used in only one place, so you can
Hzj_jie 2016/04/05 23:15:17 Done.
29 using std::unique_ptr;
Sergey Ulanov 2016/03/31 18:41:17 unique_ptr is not used anywhere in this file.
Hzj_jie 2016/04/05 23:15:17 Done.
30
31 bool ScreenCapturerWinDirectX::kInitialized { false };
Sergey Ulanov 2016/03/31 18:41:16 All of these are not really constants, so you shou
Sergey Ulanov 2016/03/31 18:41:16 = false. C++11 style initialization syntax is not
Hzj_jie 2016/04/05 23:15:17 Done.
Hzj_jie 2016/04/05 23:15:18 Yes, I have also had the same feeling, but logical
32 bool ScreenCapturerWinDirectX::kInitializeResult { false };
33 ID3D11Device* ScreenCapturerWinDirectX::kD3D11Device { nullptr };
34 ID3D11DeviceContext* ScreenCapturerWinDirectX::kD3D11Context { nullptr };
35 IDXGIOutput1* ScreenCapturerWinDirectX::kDXGIOutput1 { nullptr };
36 ComPtr<IDXGIOutputDuplication>
Sergey Ulanov 2016/03/31 18:41:16 Static variables are allowed only for POD types. S
Hzj_jie 2016/04/05 23:15:18 According to MSDN http://shortn/_geEQFIizM3, one a
Sergey Ulanov 2016/04/08 21:22:26 All global variables _must_ be POD. It doesn't mat
37 ScreenCapturerWinDirectX::kDXGIOutputDuplication {};
38 DesktopSize ScreenCapturerWinDirectX::kDesktopSize {};
39 ComPtr<ID3D11Texture2D> ScreenCapturerWinDirectX::kStage {};
40 ComPtr<IDXGISurface> ScreenCapturerWinDirectX::kSurface {};
41 std::vector<BYTE> ScreenCapturerWinDirectX::kMetaDataBuffer {};
42 CriticalSectionWrapper ScreenCapturerWinDirectX::kInitializeLock {};
43 CriticalSectionWrapper ScreenCapturerWinDirectX::kDuplicationLock {};
44 CriticalSectionWrapper ScreenCapturerWinDirectX::kAcquireLock {};
45
46 bool ScreenCapturerWinDirectX::Initialize() {
47 if (!kInitialized) {
48 CriticalSectionScoped lock(&kInitializeLock);
49 if (!kInitialized) {
50 kInitializeResult = DoInitialize();
51 kInitialized = true;
52 if (kInitializeResult) {
53 return true;
54 }
55
56 // Clean up if DirectX cannot work on the system.
57 if (kDXGIOutputDuplication) {
58 kDXGIOutputDuplication.Reset();
59 }
60
61 if (kDXGIOutput1 != nullptr) {
62 kDXGIOutput1->Release();
Sergey Ulanov 2016/03/31 18:41:15 Why do you need this? Doesn't ComPtr<> release the
Hzj_jie 2016/04/05 23:15:18 Yes, ComPtr does, but this instance is a pure poin
63 kDXGIOutput1 = nullptr;
64 }
65
66 if (kD3D11Context != nullptr) {
67 kD3D11Context->Release();
68 kD3D11Context = nullptr;
69 }
70
71 if (kD3D11Device != nullptr) {
72 kD3D11Device->Release();
73 kD3D11Device = nullptr;
74 }
75
76 return false;
77 }
78 }
79
80 return kInitializeResult;
81 }
82
83 bool ScreenCapturerWinDirectX::DoInitialize() {
84 D3D_FEATURE_LEVEL feature_level;
85 _com_error err(D3D11CreateDevice(nullptr,
86 D3D_DRIVER_TYPE_HARDWARE,
87 nullptr,
88 D3D11_CREATE_DEVICE_SINGLETHREADED,
Sergey Ulanov 2016/03/31 18:41:17 DirectX may be used from other threads in chrome,
Hzj_jie 2016/04/05 23:15:17 We always have only one thread to access an ID3D11
89 nullptr,
90 0,
91 D3D11_SDK_VERSION,
92 &kD3D11Device,
93 &feature_level,
94 &kD3D11Context));
95 if (err.Error() != S_OK ||
96 kD3D11Device == nullptr ||
97 kD3D11Context == nullptr) {
98 LOG(LS_WARNING) << "D3D11CreateDeivce returns error " << err.ErrorMessage()
99 << " with code " << err.Error();
100 return false;
101 }
102
103 if (feature_level < D3D_FEATURE_LEVEL_11_0) {
104 LOG(LS_WARNING) << "D3D11CreateDevice returns an instance without DirectX "
105 "11 support, level " << feature_level;
106 return false;
107 }
108
109 ComPtr<IDXGIDevice> device;
110 err = _com_error(kD3D11Device->QueryInterface(
111 __uuidof(IDXGIDevice),
112 reinterpret_cast<void**>(device.GetAddressOf())));
113 if (err.Error() != S_OK || !device) {
114 LOG(LS_WARNING) << "ID3D11Device is not an implementation of IDXGIDevice, "
115 "this usually means the system does not support DirectX "
116 "11";
117 return false;
118 }
119
120 ComPtr<IDXGIAdapter> adapter;
121 err = _com_error(device->GetAdapter(adapter.GetAddressOf()));
122 if (err.Error() != S_OK || !adapter) {
123 LOG(LS_WARNING) << "Failed to get an IDXGIAdapter implementation from "
124 "IDXGIDevice.";
125 return false;
126 }
127
128 ComPtr<IDXGIOutput> output;
129 for (int i = 0;; i++) {
Sergey Ulanov 2016/03/31 18:41:16 while() loop would be more readable here. E.g. see
Hzj_jie 2016/04/05 23:15:17 The scenario is a little bit different, we are loo
130 err = _com_error(adapter->EnumOutputs(i, output.GetAddressOf()));
131 if (err.Error() == DXGI_ERROR_NOT_FOUND) {
132 LOG(LS_WARNING) << "No output detected.";
133 return false;
134 } else if (err.Error() == S_OK && output) {
Sergey Ulanov 2016/03/31 18:41:15 what if err.Error() is any error other than DXGI_
Sergey Ulanov 2016/03/31 18:41:16 no else after return please: https://www.chromium.
Hzj_jie 2016/04/05 23:15:18 I do not see a statement in MSDN to say this funct
135 DXGI_OUTPUT_DESC desc;
136 err = _com_error(output->GetDesc(&desc));
137 if (err.Error() == S_OK) {
138 if (desc.AttachedToDesktop) {
139 // Current output instance is the device attached to desktop.
140 break;
141 }
142 } else {
143 LOG(LS_WARNING) << "Failed to get output description of device " << i
144 << ", ignore.";
145 }
146 }
147 }
148
149 assert(output);
150 err = _com_error(output.CopyTo(__uuidof(IDXGIOutput1),
151 reinterpret_cast<void**>(&kDXGIOutput1)));
152 if (err.Error() != S_OK || kDXGIOutput1 == nullptr) {
153 LOG(LS_WARNING) << "Failed to convert IDXGIOutput to IDXGIOutput1, this "
154 "usually means the system does not support DirectX 11";
155 return false;
156 }
157
158 return DuplicateOutput();
159 }
160
161 bool ScreenCapturerWinDirectX::DuplicateOutput() {
162 assert(kDXGIOutput1 != nullptr);
163 // We are updating the instance.
164 CriticalSectionScoped lock(&kDuplicationLock);
165 // Make sure nobody is using current instance.
166 CriticalSectionScoped lock2(&kAcquireLock);
167 if (kDXGIOutputDuplication) {
168 kDXGIOutputDuplication.Reset();
169 }
170 _com_error err(kDXGIOutput1->DuplicateOutput(
171 static_cast<IUnknown*>(kD3D11Device),
172 kDXGIOutputDuplication.GetAddressOf()));
173 if (err.Error() != S_OK || !kDXGIOutputDuplication) {
174 LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error "
175 << err.ErrorMessage() << ", with code " << err.Error();
176 return false;
177 }
178
179 DXGI_OUTDUPL_DESC desc;
180 kDXGIOutputDuplication->GetDesc(&desc);
181 kDesktopSize.set(desc.ModeDesc.Width, desc.ModeDesc.Height);
182 kStage.Reset();
183 kSurface.Reset();
184 return true;
185 }
186
187 ScreenCapturerWinDirectX::ScreenCapturerWinDirectX(
188 const DesktopCaptureOptions& options) :
189 callback_(nullptr),
190 set_thread_execution_state_failed_(false) {
191 assert(kInitialized && kInitializeResult);
192 }
193
194 ScreenCapturerWinDirectX::~ScreenCapturerWinDirectX() {}
195
196 void ScreenCapturerWinDirectX::Start(Callback* callback) {
197 assert(callback_ == nullptr);
Sergey Ulanov 2016/03/31 18:41:16 Here and everywhere else please use RTC_DCHECK() i
Hzj_jie 2016/04/05 23:15:18 Done.
198 assert(callback != nullptr);
199
200 callback_ = callback;
201 }
202
203 // We do not need to allocate memory in this class.
204 void ScreenCapturerWinDirectX::SetSharedMemoryFactory(
205 rtc::scoped_ptr<SharedMemoryFactory> shared_memory_factory) {}
Sergey Ulanov 2016/03/31 18:41:16 We actually don't want to ignore this call. On win
Hzj_jie 2016/04/05 23:15:18 Yes, done.
206
207 bool ScreenCapturerWinDirectX::CreateTexture(ID3D11Texture2D* texture) {
208 assert(texture != nullptr);
209 D3D11_TEXTURE2D_DESC desc;
210 texture->GetDesc(&desc);
211 desc.Usage = D3D11_USAGE_STAGING;
212 desc.BindFlags = 0;
213 desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
214 desc.MiscFlags = 0;
215 if (kStage) {
216 {
217 ComPtr<IUnknown> left;
218 ComPtr<IUnknown> right;
219 assert(SUCCEEDED(kStage.CopyTo(
Sergey Ulanov 2016/03/31 18:41:16 Don't put any statements with side-effects inside
Hzj_jie 2016/04/05 23:15:18 Done.
220 __uuidof(IUnknown),
221 reinterpret_cast<void**>(left.GetAddressOf()))));
222 assert(SUCCEEDED(kSurface.CopyTo(
223 __uuidof(IUnknown),
224 reinterpret_cast<void**>(right.GetAddressOf()))));
225 assert(left.Get() == right.Get());
226 }
227 _com_error err(kSurface->Unmap()); // This buffer should be used already.
228 if (err.Error() == S_OK) {
229 D3D11_TEXTURE2D_DESC orgi_desc;
230 kStage->GetDesc(&orgi_desc);
231 if (memcmp(&desc, &orgi_desc, sizeof(D3D11_TEXTURE2D_DESC)) == 0) {
Sergey Ulanov 2016/03/31 18:41:16 You don't need this check. memcmp() is not expecte
Hzj_jie 2016/04/05 23:15:18 This logic is to check whether current buffer (sta
232 return true;
233 }
234 } else {
235 // Let's recreate kSurface later.
236 LOG(LS_ERROR) << "Failed to unmap surface, error " << err.ErrorMessage()
237 << ", code " << err.Error();
238 }
239 kStage.Reset();
240 kSurface.Reset();
241 }
242
243 _com_error err = _com_error(kD3D11Device->CreateTexture2D(
244 &desc,
245 nullptr,
246 kStage.GetAddressOf()));
247 if (err.Error() != S_OK || !kStage) {
248 LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage, "
249 "error " << err.ErrorMessage()
250 << ", code " << err.Error();
251 return false;
252 }
253
254 err = _com_error(kStage.CopyTo(
255 __uuidof(IDXGISurface),
256 reinterpret_cast<void**>(kSurface.GetAddressOf())));
257 if (err.Error() != S_OK || !kSurface) {
258 LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface, "
259 "error " << err.ErrorMessage()
260 << ", code " << err.Error();
261 return false;
262 }
263
264 return true;
265 }
266
267 bool ScreenCapturerWinDirectX::DetectUpdatedRegion(
268 const DXGI_OUTDUPL_FRAME_INFO& frame_info,
Sergey Ulanov 2016/03/31 18:41:16 incorrect indentation. Please use clang-format: ht
Hzj_jie 2016/04/05 23:15:17 Done.
269 DesktopFrame* frame) {
270 assert(kDXGIOutputDuplication);
271 assert(frame != nullptr);
272 DesktopRegion& updated_region = *frame->mutable_updated_region();
273 updated_region.Clear();
274 if (frame_info.TotalMetadataBufferSize == 0) {
275 // This should not happen, since frame_info.AccumulatedFrames > 0.
276 LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, "
277 "but TotalMetadataBufferSize == 0";
278 return false;
279 }
280
281 if (kMetaDataBuffer.size() < frame_info.TotalMetadataBufferSize) {
282 kMetaDataBuffer.clear(); // Avoid data copy
283 kMetaDataBuffer.reserve(frame_info.TotalMetadataBufferSize);
284 }
285
286 UINT buff_size = 0;
287 DXGI_OUTDUPL_MOVE_RECT* move_rects = nullptr;
288 size_t move_rects_count = 0;
289 RECT* dirty_rects = nullptr;
290 size_t dirty_rects_count = 0;
291 for (int i = 0; i < 2; i++) {
Sergey Ulanov 2016/03/31 18:41:16 This looks strange. You have a loop that iterates
Hzj_jie 2016/04/05 23:15:17 To share most of the logic below. I agree it looks
292 _com_error err(S_OK);
293 if (i == 0) {
294 move_rects =
295 reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(kMetaDataBuffer.data());
296 err = _com_error(kDXGIOutputDuplication->GetFrameMoveRects(
297 kMetaDataBuffer.capacity(),
298 move_rects,
299 &buff_size));
300 } else {
301 dirty_rects =
302 reinterpret_cast<RECT*>(kMetaDataBuffer.data() + buff_size);
303 err = _com_error(kDXGIOutputDuplication->GetFrameDirtyRects(
304 kMetaDataBuffer.capacity() - buff_size,
305 dirty_rects,
306 &buff_size));
307 }
308 if (err.Error() != S_OK) {
309 if (err.Error() == DXGI_ERROR_ACCESS_LOST) {
310 if (!DuplicateOutput()) {
311 LOG(LS_ERROR) << "Failed to regenerate an IDXGIOutputDuplication.";
312 }
313 } else {
314 LOG(LS_ERROR) << "Failed to get " << (i == 0 ? "move" : "dirty")
315 << " rectangles, error " << err.ErrorMessage()
316 << ", code " << err.Error();
317 }
318 // Send whole desktop as we cannot get dirty or move rectangles.
319 return false;
320 }
321 if (i == 0) {
322 move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT);
323 } else {
324 dirty_rects_count = buff_size / sizeof(RECT);
325 }
326 }
327
328 while (move_rects_count > 0) {
329 updated_region.AddRect(DesktopRect::MakeXYWH(
330 move_rects->SourcePoint.x,
331 move_rects->SourcePoint.y,
332 move_rects->DestinationRect.right - move_rects->DestinationRect.left,
333 move_rects->DestinationRect.bottom - move_rects->DestinationRect.top));
334 updated_region.AddRect(DesktopRect::MakeLTRB(
335 move_rects->DestinationRect.left,
336 move_rects->DestinationRect.top,
337 move_rects->DestinationRect.right,
338 move_rects->DestinationRect.bottom));
339 move_rects++;
340 move_rects_count--;
341 }
342
343 while (dirty_rects_count > 0) {
344 updated_region.AddRect(DesktopRect::MakeLTRB(
345 dirty_rects->left,
346 dirty_rects->top,
347 dirty_rects->right,
348 dirty_rects->bottom));
349 dirty_rects++;
350 dirty_rects_count--;
351 }
352
353 return true;
354 }
355
356 bool ScreenCapturerWinDirectX::ProcessFrame(
357 const DXGI_OUTDUPL_FRAME_INFO& frame_info,
358 IDXGIResource* resource,
359 DesktopFrame** frame) {
360 assert(resource != nullptr);
361 assert(frame != nullptr);
362 assert(frame_info.AccumulatedFrames > 0);
363
364 ComPtr<ID3D11Texture2D> texture;
365 _com_error err = _com_error(resource->QueryInterface(
366 __uuidof(ID3D11Texture2D),
367 reinterpret_cast<void**>(texture.GetAddressOf())));
368 if (err.Error() != S_OK || !texture) {
369 LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, "
370 "error " << err.ErrorMessage() << ", code "
371 << err.Error();
372 return false;
373 }
374
375 // AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to
376 // make a copy.
377 if (!CreateTexture(texture.Get())) {
378 return false;
379 }
380
381 kD3D11Context->CopyResource(static_cast<ID3D11Resource*>(kStage.Get()),
382 static_cast<ID3D11Resource*>(texture.Get()));
383
384 DXGI_MAPPED_RECT rect;
385 err = _com_error(kSurface->Map(&rect, DXGI_MAP_READ));
386 if (err.Error() != S_OK) {
387 LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap, error "
388 << err.ErrorMessage() << ", code " << err.Error();
389 return false;
390 }
391
392 *frame = new DesktopFrameWinDXGI(kDesktopSize, kSurface, rect);
393 // kSurface->Unmap will be called next time we capture an image to avoid
394 // memory copy.
395 if (!DetectUpdatedRegion(frame_info, *frame)) {
396 (*frame)->mutable_updated_region()->Clear();
397 (*frame)->mutable_updated_region()->AddRect(
398 DesktopRect::MakeSize(kDesktopSize));
399 }
400 return true;
401 }
402
403 void ScreenCapturerWinDirectX::Capture(const DesktopRegion& region) {
404 if (!kDXGIOutputDuplication) {
405 // Receive a capture request when application is shutting down.
406 CallbackError();
407 return;
408 }
409
410 assert(callback_ != nullptr);
411 TickTime capture_start_time = TickTime::Now();
412
413 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
414 if (!set_thread_execution_state_failed_) {
415 set_thread_execution_state_failed_ = true;
416 LOG(LS_WARNING) << "Failed to make system & display power assertion: "
417 << GetLastError();
418 }
419 }
420
421 DXGI_OUTDUPL_FRAME_INFO frame_info = { 0 };
422 ComPtr<IDXGIResource> resource = nullptr;
423 CriticalSectionScoped lock(&kAcquireLock);
424 _com_error err(kDXGIOutputDuplication->AcquireNextFrame(
425 kAcquireTimeout,
426 &frame_info,
427 resource.GetAddressOf()));
428 if (err.Error() == DXGI_ERROR_ACCESS_LOST) {
429 if (DuplicateOutput()) {
430 CallbackUnchanged();
431 } else {
432 LOG(LS_ERROR) << "Failed to regenerate an IDXGIOutputDuplication";
433 CallbackError();
434 }
435 return;
436 } else if (err.Error() == DXGI_ERROR_WAIT_TIMEOUT) {
Sergey Ulanov 2016/03/31 18:41:17 here and below: no else after return please
Hzj_jie 2016/04/05 23:15:18 Done.
437 // Nothing changed.
438 CallbackUnchanged();
439 return;
440 } else if (err.Error() != S_OK) {
441 CallbackError();
442 return;
443 } else {
444 if (frame_info.AccumulatedFrames > 0) {
445 // Everything looks good so far, build CaptureFrame.
446 DesktopFrame* frame = nullptr;
447 bool result = ProcessFrame(frame_info, resource.Get(), &frame);
448 kDXGIOutputDuplication->ReleaseFrame();
449 if (result) {
450 assert(frame != nullptr);
451 frame->set_capture_time_ms(
452 (TickTime::Now() - capture_start_time).Milliseconds());
453 callback_->OnCaptureCompleted(frame);
454 } else {
455 assert(frame == nullptr);
456 CallbackError();
457 }
458 } else {
459 // Only mouse cursor moved, ignore.
460 CallbackUnchanged();
461 kDXGIOutputDuplication->ReleaseFrame();
462 }
463 }
464 }
465
466 bool ScreenCapturerWinDirectX::GetScreenList(ScreenList* screens) {
467 assert(screens != nullptr);
468 assert(screens->size() == 0);
469 screens->push_back(Screen { 0 });
470 return true;
471 }
472
473 bool ScreenCapturerWinDirectX::SelectScreen(ScreenId id) {
474 return id == 0 || id == kFullDesktopScreenId;
475 }
476
477 void ScreenCapturerWinDirectX::CallbackUnchanged() {
478 callback_->OnCaptureCompleted(new DesktopFrameWinDXGI(kDesktopSize));
Sergey Ulanov 2016/03/31 18:41:16 When nothing is changed we want to emit a frame th
Hzj_jie 2016/04/05 23:15:18 Done.
479 }
480
481 void ScreenCapturerWinDirectX::CallbackError() {
482 callback_->OnCaptureCompleted(nullptr);
483 }
484
485 } // namespace webrtc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698