wgc_capturer_win.cc (17400B)
1 /* 2 * Copyright (c) 2020 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/wgc_capturer_win.h" 12 13 #include <dispatcherqueue.h> 14 #include <windows.foundation.metadata.h> 15 #include <windows.graphics.capture.h> 16 17 #include <cstdint> 18 #include <cwchar> 19 #include <map> 20 #include <memory> 21 #include <tuple> 22 #include <utility> 23 24 #include "modules/desktop_capture/desktop_capture_metrics_helper.h" 25 #include "modules/desktop_capture/desktop_capture_options.h" 26 #include "modules/desktop_capture/desktop_capture_types.h" 27 #include "modules/desktop_capture/desktop_capturer.h" 28 #include "modules/desktop_capture/desktop_frame.h" 29 #include "modules/desktop_capture/win/screen_capture_utils.h" 30 #include "modules/desktop_capture/win/wgc_capture_session.h" 31 #include "modules/desktop_capture/win/wgc_capture_source.h" 32 #include "rtc_base/checks.h" 33 #include "rtc_base/logging.h" 34 #include "rtc_base/time_utils.h" 35 #include "rtc_base/win/get_activation_factory.h" 36 #include "rtc_base/win/hstring.h" 37 #include "rtc_base/win/windows_version.h" 38 #include "system_wrappers/include/metrics.h" 39 40 namespace WGC = ABI::Windows::Graphics::Capture; 41 using Microsoft::WRL::ComPtr; 42 43 namespace webrtc { 44 45 namespace { 46 47 constexpr wchar_t kCoreMessagingDll[] = L"CoreMessaging.dll"; 48 49 constexpr wchar_t kWgcSessionType[] = 50 L"Windows.Graphics.Capture.GraphicsCaptureSession"; 51 constexpr wchar_t kApiContract[] = L"Windows.Foundation.UniversalApiContract"; 52 constexpr wchar_t kDirtyRegionMode[] = L"DirtyRegionMode"; 53 constexpr UINT16 kRequiredApiContractVersion = 8; 54 55 enum class WgcCapturerResult { 56 kSuccess = 0, 57 kNoDirect3dDevice = 1, 58 kNoSourceSelected = 2, 59 kItemCreationFailure = 3, 60 kSessionStartFailure = 4, 61 kGetFrameFailure = 5, 62 kFrameDropped = 6, 63 kCreateDispatcherQueueFailure = 7, 64 kMaxValue = kCreateDispatcherQueueFailure 65 }; 66 67 void RecordWgcCapturerResult(WgcCapturerResult error) { 68 RTC_HISTOGRAM_ENUMERATION("WebRTC.DesktopCapture.Win.WgcCapturerResult", 69 static_cast<int>(error), 70 static_cast<int>(WgcCapturerResult::kMaxValue)); 71 } 72 73 // Checks if the DirtyRegionMode property is present in GraphicsCaptureSession 74 // and logs a boolean histogram with the result. 75 // TODO(https://crbug.com/40259177): Detecting support for this property means 76 // that the WGC API supports dirty regions and it can be utilized to improve 77 // the capture performance and the existing zero-herz support. 78 void LogDirtyRegionSupport() { 79 ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics> 80 api_info_statics; 81 HRESULT hr = GetActivationFactory< 82 ABI::Windows::Foundation::Metadata::IApiInformationStatics, 83 RuntimeClass_Windows_Foundation_Metadata_ApiInformation>( 84 &api_info_statics); 85 if (FAILED(hr)) { 86 return; 87 } 88 89 HSTRING dirty_region_mode; 90 hr = CreateHstring(kDirtyRegionMode, wcslen(kDirtyRegionMode), 91 &dirty_region_mode); 92 if (FAILED(hr)) { 93 DeleteHstring(dirty_region_mode); 94 return; 95 } 96 97 HSTRING wgc_session_type; 98 hr = CreateHstring(kWgcSessionType, wcslen(kWgcSessionType), 99 &wgc_session_type); 100 if (SUCCEEDED(hr)) { 101 boolean is_dirty_region_mode_supported = 102 api_info_statics->IsPropertyPresent(wgc_session_type, dirty_region_mode, 103 &is_dirty_region_mode_supported); 104 RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.Win.WgcDirtyRegionSupport", 105 !!is_dirty_region_mode_supported); 106 } 107 DeleteHstring(dirty_region_mode); 108 DeleteHstring(wgc_session_type); 109 } 110 111 } // namespace 112 113 bool IsWgcSupported(CaptureType capture_type) { 114 if (!HasActiveDisplay()) { 115 // There is a bug in `CreateForMonitor` that causes a crash if there are no 116 // active displays. The crash was fixed in Win11, but we are still unable 117 // to capture screens without an active display. 118 if (capture_type == CaptureType::kScreen) { 119 return false; 120 } 121 // There is a bug in the DWM (Desktop Window Manager) that prevents it from 122 // providing image data if there are no displays attached. This was fixed in 123 // Windows 11. 124 if (rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN11) { 125 return false; 126 } 127 } 128 129 // A bug in the WGC API `CreateForMonitor` prevents capturing the entire 130 // virtual screen (all monitors simultaneously), this was fixed in 20H1. Since 131 // we can't assert that we won't be asked to capture the entire virtual 132 // screen, we report unsupported so we can fallback to another capturer. 133 if (capture_type == CaptureType::kScreen && 134 rtc_win::GetVersion() < rtc_win::Version::VERSION_WIN10_20H1) { 135 return false; 136 } 137 138 if (!ResolveCoreWinRTDelayload()) { 139 return false; 140 } 141 142 // We need to check if the WGC APIs are present on the system. Certain SKUs 143 // of Windows ship without these APIs. 144 ComPtr<ABI::Windows::Foundation::Metadata::IApiInformationStatics> 145 api_info_statics; 146 HRESULT hr = GetActivationFactory< 147 ABI::Windows::Foundation::Metadata::IApiInformationStatics, 148 RuntimeClass_Windows_Foundation_Metadata_ApiInformation>( 149 &api_info_statics); 150 if (FAILED(hr)) { 151 return false; 152 } 153 154 HSTRING api_contract; 155 hr = CreateHstring(kApiContract, wcslen(kApiContract), &api_contract); 156 if (FAILED(hr)) { 157 return false; 158 } 159 160 boolean is_api_present; 161 hr = api_info_statics->IsApiContractPresentByMajor( 162 api_contract, kRequiredApiContractVersion, &is_api_present); 163 DeleteHstring(api_contract); 164 if (FAILED(hr) || !is_api_present) { 165 return false; 166 } 167 168 HSTRING wgc_session_type; 169 hr = CreateHstring(kWgcSessionType, wcslen(kWgcSessionType), 170 &wgc_session_type); 171 if (FAILED(hr)) { 172 return false; 173 } 174 175 boolean is_type_present; 176 hr = api_info_statics->IsTypePresent(wgc_session_type, &is_type_present); 177 DeleteHstring(wgc_session_type); 178 if (FAILED(hr) || !is_type_present) { 179 return false; 180 } 181 182 // If the APIs are present, we need to check that they are supported. 183 ComPtr<WGC::IGraphicsCaptureSessionStatics> capture_session_statics; 184 hr = GetActivationFactory< 185 WGC::IGraphicsCaptureSessionStatics, 186 RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession>( 187 &capture_session_statics); 188 if (FAILED(hr)) { 189 return false; 190 } 191 192 boolean is_supported; 193 hr = capture_session_statics->IsSupported(&is_supported); 194 if (FAILED(hr) || !is_supported) { 195 return false; 196 } 197 198 return true; 199 } 200 201 WgcCapturerWin::WgcCapturerWin( 202 const DesktopCaptureOptions& options, 203 std::unique_ptr<WgcCaptureSourceFactory> source_factory, 204 std::unique_ptr<SourceEnumerator> source_enumerator, 205 bool allow_delayed_capturable_check) 206 : options_(options), 207 source_factory_(std::move(source_factory)), 208 source_enumerator_(std::move(source_enumerator)), 209 allow_delayed_capturable_check_(allow_delayed_capturable_check), 210 full_screen_window_detector_(options.full_screen_window_detector()) { 211 if (!core_messaging_library_) { 212 core_messaging_library_ = LoadLibraryW(kCoreMessagingDll); 213 } 214 215 if (core_messaging_library_) { 216 create_dispatcher_queue_controller_func_ = 217 reinterpret_cast<CreateDispatcherQueueControllerFunc>(GetProcAddress( 218 core_messaging_library_, "CreateDispatcherQueueController")); 219 } 220 LogDirtyRegionSupport(); 221 } 222 223 WgcCapturerWin::~WgcCapturerWin() { 224 if (core_messaging_library_) { 225 FreeLibrary(core_messaging_library_); 226 } 227 } 228 229 // static 230 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawWindowCapturer( 231 const DesktopCaptureOptions& options, 232 bool allow_delayed_capturable_check) { 233 return std::make_unique<WgcCapturerWin>( 234 options, std::make_unique<WgcWindowSourceFactory>(), 235 std::make_unique<WindowEnumerator>( 236 options.enumerate_current_process_windows()), 237 allow_delayed_capturable_check); 238 } 239 240 // static 241 std::unique_ptr<DesktopCapturer> WgcCapturerWin::CreateRawScreenCapturer( 242 const DesktopCaptureOptions& options) { 243 return std::make_unique<WgcCapturerWin>( 244 options, std::make_unique<WgcScreenSourceFactory>(), 245 std::make_unique<ScreenEnumerator>(), false); 246 } 247 248 bool WgcCapturerWin::GetSourceList(SourceList* sources) { 249 return source_enumerator_->FindAllSources(sources); 250 } 251 252 bool WgcCapturerWin::SelectSource(DesktopCapturer::SourceId id) { 253 selected_source_id_ = id; 254 255 // Use `full_screen_window_detector_` to check if there is a corresponding 256 // full screen window for the `selected_source_id_`. 257 const DesktopCapturer::SourceId full_screen_source_id = 258 full_screen_window_detector_ && 259 full_screen_window_detector_->UseHeuristicForWGC() 260 ? full_screen_window_detector_->FindFullScreenWindow(id) 261 : 0; 262 263 // `capture_id` represents the SourceId used to create the `capture_source_`, 264 // which is the module responsible for capturing the frames. 265 auto capture_id = full_screen_source_id ? full_screen_source_id : id; 266 if (capture_id != id && !fullscreen_usage_logged_) { 267 // Log the usage of FullScreenDetector only once and only if it's 268 // successful. 269 fullscreen_usage_logged_ = true; 270 LogDesktopCapturerFullscreenDetectorUsage(); 271 } 272 273 if (!capture_source_ || capture_source_->GetSourceId() != capture_id) { 274 capture_source_ = source_factory_->CreateCaptureSource(capture_id); 275 } 276 277 if (allow_delayed_capturable_check_) { 278 return true; 279 } 280 281 return capture_source_->IsCapturable(); 282 } 283 284 bool WgcCapturerWin::FocusOnSelectedSource() { 285 if (!capture_source_) { 286 return false; 287 } 288 289 return capture_source_->FocusOnSource(); 290 } 291 292 void WgcCapturerWin::Start(Callback* callback) { 293 RTC_DCHECK(!callback_); 294 RTC_DCHECK(callback); 295 RecordCapturerImpl(DesktopCapturerId::kWgcCapturerWin); 296 297 callback_ = callback; 298 299 // Create a Direct3D11 device to share amongst the WgcCaptureSessions. Many 300 // parameters are nullptr as the implemention uses defaults that work well for 301 // us. 302 HRESULT hr = D3D11CreateDevice( 303 /*adapter=*/nullptr, D3D_DRIVER_TYPE_HARDWARE, 304 /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, 305 /*feature_levels=*/nullptr, /*feature_levels_size=*/0, D3D11_SDK_VERSION, 306 &d3d11_device_, /*feature_level=*/nullptr, /*device_context=*/nullptr); 307 if (hr == DXGI_ERROR_UNSUPPORTED) { 308 // If a hardware device could not be created, use WARP which is a high speed 309 // software device. 310 hr = D3D11CreateDevice( 311 /*adapter=*/nullptr, D3D_DRIVER_TYPE_WARP, 312 /*software_rasterizer=*/nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, 313 /*feature_levels=*/nullptr, /*feature_levels_size=*/0, 314 D3D11_SDK_VERSION, &d3d11_device_, /*feature_level=*/nullptr, 315 /*device_context=*/nullptr); 316 } 317 318 if (FAILED(hr)) { 319 RTC_LOG(LS_ERROR) << "Failed to create D3D11Device: " << hr; 320 } 321 } 322 323 void WgcCapturerWin::CaptureFrame() { 324 RTC_DCHECK(callback_); 325 326 if (!capture_source_) { 327 RTC_LOG(LS_ERROR) << "Source hasn't been selected"; 328 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 329 /*frame=*/nullptr); 330 RecordWgcCapturerResult(WgcCapturerResult::kNoSourceSelected); 331 return; 332 } 333 334 if (!d3d11_device_) { 335 RTC_LOG(LS_ERROR) << "No D3D11D3evice, cannot capture."; 336 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 337 /*frame=*/nullptr); 338 RecordWgcCapturerResult(WgcCapturerResult::kNoDirect3dDevice); 339 return; 340 } 341 342 if (allow_delayed_capturable_check_ && !capture_source_->IsCapturable()) { 343 RTC_LOG(LS_ERROR) << "Source is not capturable."; 344 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 345 /*frame=*/nullptr); 346 return; 347 } 348 349 // Feed the actual list of windows into full screen window detector. 350 if (full_screen_window_detector_) { 351 full_screen_window_detector_->UpdateWindowListIfNeeded( 352 selected_source_id_, [this](DesktopCapturer::SourceList* sources) { 353 return GetSourceList(sources); 354 }); 355 SelectSource(selected_source_id_); 356 } 357 358 HRESULT hr; 359 if (!dispatcher_queue_created_) { 360 // Set the apartment type to NONE because this thread should already be COM 361 // initialized. 362 DispatcherQueueOptions options{ 363 sizeof(DispatcherQueueOptions), 364 DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT, 365 DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE}; 366 ComPtr<ABI::Windows::System::IDispatcherQueueController> queue_controller; 367 hr = create_dispatcher_queue_controller_func_(options, &queue_controller); 368 369 // If there is already a DispatcherQueue on this thread, that is fine. Its 370 // lifetime is tied to the thread's, and as long as the thread has one, even 371 // if we didn't create it, the capture session's events will be delivered on 372 // this thread. 373 if (FAILED(hr) && hr != RPC_E_WRONG_THREAD) { 374 RecordWgcCapturerResult(WgcCapturerResult::kCreateDispatcherQueueFailure); 375 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 376 /*frame=*/nullptr); 377 } else { 378 dispatcher_queue_created_ = true; 379 } 380 } 381 382 int64_t capture_start_time_nanos = TimeNanos(); 383 384 WgcCaptureSession* capture_session = nullptr; 385 std::map<SourceId, WgcCaptureSession>::iterator session_iter = 386 ongoing_captures_.find(capture_source_->GetSourceId()); 387 if (session_iter == ongoing_captures_.end()) { 388 ComPtr<WGC::IGraphicsCaptureItem> item; 389 hr = capture_source_->GetCaptureItem(&item); 390 if (FAILED(hr)) { 391 RTC_LOG(LS_ERROR) << "Failed to create a GraphicsCaptureItem: " << hr; 392 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 393 /*frame=*/nullptr); 394 RecordWgcCapturerResult(WgcCapturerResult::kItemCreationFailure); 395 return; 396 } 397 398 std::pair<std::map<SourceId, WgcCaptureSession>::iterator, bool> 399 iter_success_pair = ongoing_captures_.emplace( 400 std::piecewise_construct, 401 std::forward_as_tuple(capture_source_->GetSourceId()), 402 std::forward_as_tuple(capture_source_->GetSourceId(), d3d11_device_, 403 item, capture_source_->GetSize())); 404 RTC_DCHECK(iter_success_pair.second); 405 capture_session = &iter_success_pair.first->second; 406 } else { 407 capture_session = &session_iter->second; 408 } 409 410 if (!capture_session->IsCaptureStarted()) { 411 hr = capture_session->StartCapture(options_); 412 if (FAILED(hr)) { 413 RTC_LOG(LS_ERROR) << "Failed to start capture: " << hr; 414 ongoing_captures_.erase(capture_source_->GetSourceId()); 415 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 416 /*frame=*/nullptr); 417 RecordWgcCapturerResult(WgcCapturerResult::kSessionStartFailure); 418 return; 419 } 420 } 421 422 std::unique_ptr<DesktopFrame> frame; 423 if (!capture_session->GetFrame(&frame, 424 capture_source_->ShouldBeCapturable())) { 425 RTC_LOG(LS_ERROR) << "GetFrame failed."; 426 ongoing_captures_.erase(capture_source_->GetSourceId()); 427 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, 428 /*frame=*/nullptr); 429 RecordWgcCapturerResult(WgcCapturerResult::kGetFrameFailure); 430 return; 431 } 432 433 if (!frame) { 434 callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY, 435 /*frame=*/nullptr); 436 RecordWgcCapturerResult(WgcCapturerResult::kFrameDropped); 437 return; 438 } 439 440 int capture_time_ms = 441 (TimeNanos() - capture_start_time_nanos) / kNumNanosecsPerMillisec; 442 RTC_HISTOGRAM_COUNTS_1000("WebRTC.DesktopCapture.Win.WgcCapturerFrameTime", 443 capture_time_ms); 444 frame->set_capture_time_ms(capture_time_ms); 445 frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin); 446 frame->set_may_contain_cursor(options_.prefer_cursor_embedded()); 447 frame->set_top_left(capture_source_->GetTopLeft()); 448 RecordWgcCapturerResult(WgcCapturerResult::kSuccess); 449 callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS, 450 std::move(frame)); 451 } 452 453 bool WgcCapturerWin::IsSourceBeingCaptured(DesktopCapturer::SourceId id) { 454 std::map<DesktopCapturer::SourceId, WgcCaptureSession>::iterator 455 session_iter = ongoing_captures_.find(id); 456 if (session_iter == ongoing_captures_.end()) { 457 return false; 458 } 459 460 return session_iter->second.IsCaptureStarted(); 461 } 462 463 void WgcCapturerWin::SetUpFullScreenDetectorForTest( 464 bool use_heuristic, 465 DesktopCapturer::SourceId source_id, 466 bool fullscreen_slide_show_started_after_capture_start) { 467 if (full_screen_window_detector_) { 468 full_screen_window_detector_->SetUseHeuristicFullscreenPowerPointWindows( 469 /*use_heuristic_fullscreen_powerpoint_windows=*/true, use_heuristic); 470 full_screen_window_detector_->CreateFullScreenApplicationHandlerForTest( 471 source_id, fullscreen_slide_show_started_after_capture_start); 472 } 473 } 474 475 } // namespace webrtc