diff --git a/webrtc/modules/desktop_capture/BUILD.gn b/webrtc/modules/desktop_capture/BUILD.gn index 6caef4d719..672433e32b 100644 --- a/webrtc/modules/desktop_capture/BUILD.gn +++ b/webrtc/modules/desktop_capture/BUILD.gn @@ -72,8 +72,22 @@ source_set("desktop_capture") { "shared_memory.h", "win/cursor.cc", "win/cursor.h", + "win/d3d_device.cc", + "win/d3d_device.h", "win/desktop.cc", "win/desktop.h", + "win/dxgi_adapter_duplicator.cc", + "win/dxgi_adapter_duplicator.h", + "win/dxgi_duplicator_controller.cc", + "win/dxgi_duplicator_controller.h", + "win/dxgi_output_duplicator.cc", + "win/dxgi_output_duplicator.h", + "win/dxgi_texture.cc", + "win/dxgi_texture.h", + "win/dxgi_texture_mapping.cc", + "win/dxgi_texture_mapping.h", + "win/dxgi_texture_staging.cc", + "win/dxgi_texture_staging.h", "win/scoped_gdi_object.h", "win/scoped_thread_desktop.cc", "win/scoped_thread_desktop.h", @@ -124,7 +138,10 @@ source_set("desktop_capture") { } if (is_win) { - libs = [ "d3d11.lib" ] + libs = [ + "d3d11.lib", + "dxgi.lib", + ] } configs += [ "../..:common_config" ] diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 3963d28714..024d826d4f 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -69,8 +69,22 @@ "shared_memory.h", "win/cursor.cc", "win/cursor.h", + "win/d3d_device.cc", + "win/d3d_device.h", "win/desktop.cc", "win/desktop.h", + "win/dxgi_adapter_duplicator.cc", + "win/dxgi_adapter_duplicator.h", + "win/dxgi_duplicator_controller.cc", + "win/dxgi_duplicator_controller.h", + "win/dxgi_output_duplicator.cc", + "win/dxgi_output_duplicator.h", + "win/dxgi_texture.cc", + "win/dxgi_texture.h", + "win/dxgi_texture_mapping.cc", + "win/dxgi_texture_mapping.h", + "win/dxgi_texture_staging.cc", + "win/dxgi_texture_staging.h", "win/scoped_gdi_object.h", "win/scoped_thread_desktop.cc", "win/scoped_thread_desktop.h", @@ -137,6 +151,7 @@ 'VCLinkerTool': { 'AdditionalDependencies': [ 'd3d11.lib', + 'dxgi.lib', ], }, }, diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc index 4d7799cc68..49fcb8aafb 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -9,6 +9,7 @@ */ #include +#include #include "webrtc/modules/desktop_capture/screen_capturer.h" @@ -75,8 +76,8 @@ ACTION_P(SaveUniquePtrArg, dest) { TEST_F(ScreenCapturerTest, GetScreenListAndSelectScreen) { webrtc::ScreenCapturer::ScreenList screens; EXPECT_TRUE(capturer_->GetScreenList(&screens)); - for(webrtc::ScreenCapturer::ScreenList::iterator it = screens.begin(); - it != screens.end(); ++it) { + for (webrtc::ScreenCapturer::ScreenList::iterator it = screens.begin(); + it != screens.end(); ++it) { EXPECT_TRUE(capturer_->SelectScreen(it->id)); } } @@ -144,6 +145,40 @@ TEST_F(ScreenCapturerTest, UseMagnifier) { ASSERT_TRUE(frame); } +TEST_F(ScreenCapturerTest, UseDirectxCapturer) { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_directx_capturer(true); + capturer_.reset(ScreenCapturer::Create(options)); + + std::unique_ptr frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + ASSERT_TRUE(frame); +} + +TEST_F(ScreenCapturerTest, UseDirectxCapturerWithSharedBuffers) { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_directx_capturer(true); + capturer_.reset(ScreenCapturer::Create(options)); + + std::unique_ptr frame; + EXPECT_CALL(callback_, + OnCaptureResultPtr(DesktopCapturer::Result::SUCCESS, _)) + .WillOnce(SaveUniquePtrArg(&frame)); + + capturer_->Start(&callback_); + capturer_->SetSharedMemoryFactory( + std::unique_ptr(new FakeSharedMemoryFactory())); + capturer_->Capture(DesktopRegion()); + ASSERT_TRUE(frame); + ASSERT_TRUE(frame->shared_memory()); + EXPECT_EQ(frame->shared_memory()->id(), kTestSharedMemoryId); +} + #endif // defined(WEBRTC_WIN) } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc index 7e7fbaa38f..6a83bec1c5 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_win.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc @@ -24,7 +24,7 @@ namespace webrtc { ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { std::unique_ptr capturer; if (options.allow_directx_capturer() && - ScreenCapturerWinDirectx::Initialize()) { + ScreenCapturerWinDirectx::IsSupported()) { capturer.reset(new ScreenCapturerWinDirectx(options)); } else { capturer.reset(new ScreenCapturerWinGdi(options)); diff --git a/webrtc/modules/desktop_capture/win/d3d_device.cc b/webrtc/modules/desktop_capture/win/d3d_device.cc new file mode 100644 index 0000000000..15233e6f5e --- /dev/null +++ b/webrtc/modules/desktop_capture/win/d3d_device.cc @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/d3d_device.h" + +#include + +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +D3dDevice::D3dDevice() = default; +D3dDevice::~D3dDevice() = default; + +bool D3dDevice::Initialize(const ComPtr& adapter) { + dxgi_adapter_ = adapter; + if (!dxgi_adapter_) { + LOG(LS_WARNING) << "An empty IDXGIAdapter instance has been received."; + return false; + } + + D3D_FEATURE_LEVEL feature_level; + _com_error error = D3D11CreateDevice( + adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED, + nullptr, 0, D3D11_SDK_VERSION, d3d_device_.GetAddressOf(), &feature_level, + context_.GetAddressOf()); + if (error.Error() != S_OK || !d3d_device_ || !context_) { + LOG(LS_WARNING) << "D3D11CreateDeivce returns error " + << error.ErrorMessage() << " with code " << error.Error(); + return false; + } + + if (feature_level < D3D_FEATURE_LEVEL_11_0) { + LOG(LS_WARNING) << "D3D11CreateDevice returns an instance without DirectX " + "11 support, level " + << feature_level; + return false; + } + + error = _com_error(d3d_device_.As(&dxgi_device_)); + if (error.Error() != S_OK || !dxgi_device_) { + LOG(LS_WARNING) << "ID3D11Device is not an implementation of IDXGIDevice, " + "this usually means the system does not support DirectX " + "11"; + return false; + } + + return true; +} + +// static +std::vector D3dDevice::EnumDevices() { + ComPtr factory; + _com_error error = _com_error( + CreateDXGIFactory1(__uuidof(IDXGIFactory1), + reinterpret_cast(factory.GetAddressOf()))); + if (error.Error() != S_OK || !factory) { + return std::vector(); + } + + std::vector result; + for (int i = 0;; i++) { + ComPtr adapter; + error = _com_error(factory->EnumAdapters(i, adapter.GetAddressOf())); + if (error.Error() == S_OK) { + D3dDevice device; + if (!device.Initialize(adapter)) { + return std::vector(); + } + result.push_back(std::move(device)); + } else if (error.Error() == DXGI_ERROR_NOT_FOUND) { + break; + } else { + LOG(LS_WARNING) << "IDXGIFactory1::EnumAdapters returns an unexpected " + "error " + << error.ErrorMessage() << " with code " << error.Error(); + return std::vector(); + } + } + return result; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/d3d_device.h b/webrtc/modules/desktop_capture/win/d3d_device.h new file mode 100644 index 0000000000..94a263edcb --- /dev/null +++ b/webrtc/modules/desktop_capture/win/d3d_device.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ + +#include +#include +#include +#include + +#include + +namespace webrtc { + +// A wrapper of ID3D11Device and its corresponding context and IDXGIAdapter. +// This class represents one video card in the system. +class D3dDevice { + public: + ~D3dDevice(); + + ID3D11Device* d3d_device() const { return d3d_device_.Get(); } + + ID3D11DeviceContext* context() const { return context_.Get(); } + + IDXGIDevice* dxgi_device() const { return dxgi_device_.Get(); } + + IDXGIAdapter* dxgi_adapter() const { return dxgi_adapter_.Get(); } + + // Returns all D3dDevice instances on the system. Returns an empty vector if + // anything wrong. + static std::vector EnumDevices(); + + private: + // Instances of D3dDevice should only be created by EnumDevices() static + // function. + D3dDevice(); + + // Initialize the D3dDevice from an IDXGIAdapter. + bool Initialize(const Microsoft::WRL::ComPtr& adapter); + + Microsoft::WRL::ComPtr d3d_device_; + Microsoft::WRL::ComPtr context_; + Microsoft::WRL::ComPtr dxgi_device_; + Microsoft::WRL::ComPtr dxgi_adapter_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_D3D_DEVICE_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc b/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc new file mode 100644 index 0000000000..abe7908afe --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.cc @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h" + +#include +#include + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +namespace { + +bool IsValidRect(const RECT& rect) { + return rect.left >= 0 && rect.top >= 0 && rect.right > rect.left && + rect.bottom > rect.top; +} + +} // namespace + +DxgiAdapterDuplicator::DxgiAdapterDuplicator(const D3dDevice& device) + : device_(device) {} + +DxgiAdapterDuplicator::DxgiAdapterDuplicator(DxgiAdapterDuplicator&&) = default; + +bool DxgiAdapterDuplicator::Initialize() { + if (DoInitialize()) { + return true; + } + duplicators_.clear(); + return false; +} + +bool DxgiAdapterDuplicator::DoInitialize() { + for (int i = 0;; i++) { + ComPtr output; + _com_error error = + device_.dxgi_adapter()->EnumOutputs(i, output.GetAddressOf()); + if (error.Error() == DXGI_ERROR_NOT_FOUND) { + break; + } + + if (error.Error() != S_OK || !output) { + LOG(LS_WARNING) << "IDXGIAdapter::EnumOutputs returns an unexpected " + "result " + << error.ErrorMessage() << " with error code" + << error.Error(); + return false; + } + + DXGI_OUTPUT_DESC desc; + error = _com_error(output->GetDesc(&desc)); + if (error.Error() == S_OK) { + if (desc.AttachedToDesktop && IsValidRect(desc.DesktopCoordinates)) { + ComPtr output1; + error = _com_error(output.As(&output1)); + if (error.Error() != S_OK || !output1) { + LOG(LS_WARNING) << "Failed to convert IDXGIOutput to IDXGIOutput1, " + "this usually means the system does not support " + "DirectX 11"; + return false; + } + duplicators_.emplace_back(device_, output1, desc); + if (!duplicators_.back().Initialize()) { + return false; + } + if (desktop_rect_.is_empty()) { + desktop_rect_ = duplicators_.back().desktop_rect(); + } else { + const DesktopRect& left = desktop_rect_; + const DesktopRect& right = duplicators_.back().desktop_rect(); + desktop_rect_ = + DesktopRect::MakeLTRB(std::min(left.left(), right.left()), + std::min(left.top(), right.top()), + std::max(left.right(), right.right()), + std::max(left.bottom(), right.bottom())); + } + } + } else { + LOG(LS_WARNING) << "Failed to get output description of device " << i + << ", ignore."; + } + } + return true; +} + +void DxgiAdapterDuplicator::Setup(Context* context) { + RTC_DCHECK(context->contexts.empty()); + context->contexts.resize(duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Setup(&context->contexts[i]); + } +} + +void DxgiAdapterDuplicator::Unregister(const Context* const context) { + RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Unregister(&context->contexts[i]); + } +} + +bool DxgiAdapterDuplicator::Duplicate(Context* context, + const DesktopFrame* last_frame, + DesktopFrame* target) { + RTC_DCHECK_EQ(context->contexts.size(), duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + if (!duplicators_[i].Duplicate(&context->contexts[i], last_frame, + duplicators_[i].desktop_rect().top_left(), + target)) { + return false; + } + } + return true; +} + +bool DxgiAdapterDuplicator::DuplicateMonitor(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target) { + RTC_DCHECK(monitor_id >= 0 && + monitor_id < static_cast(duplicators_.size()) && + context->contexts.size() == duplicators_.size()); + return duplicators_[monitor_id].Duplicate( + &context->contexts[monitor_id], last_frame, DesktopVector(), target); +} + +DesktopRect DxgiAdapterDuplicator::ScreenRect(int id) const { + RTC_DCHECK(id >= 0 && id < static_cast(duplicators_.size())); + return duplicators_[id].desktop_rect(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h b/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h new file mode 100644 index 0000000000..be9fd17009 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ + +#include + +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/win/d3d_device.h" +#include "webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h" + +namespace webrtc { + +// A container of DxgiOutputDuplicators to duplicate monitors attached to a +// single video card. +class DxgiAdapterDuplicator { + public: + struct Context { + // Child DxgiOutputDuplicator::Context belongs to this + // DxgiAdapterDuplicator::Context. + std::vector contexts; + }; + + // Creates an instance of DxgiAdapterDuplicator from a D3dDevice. Only + // DxgiDuplicatorController can create an instance. + explicit DxgiAdapterDuplicator(const D3dDevice& device); + + // Move constructor, to make it possible to store instances of + // DxgiAdapterDuplicator in std::vector<>. + DxgiAdapterDuplicator(DxgiAdapterDuplicator&& other); + + // Initializes the DxgiAdapterDuplicator from a D3dDevice. + bool Initialize(); + + // Sequential calls Duplicate function of all the DxgiOutputDuplicators owned + // by this instance. + bool Duplicate(Context* context, + const DesktopFrame* last_frame, + DesktopFrame* target); + + // Captures one monitor and writes into target. |monitor_id| should be between + // [0, screen_count()). + bool DuplicateMonitor(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target); + + // Returns desktop rect covered by this DxgiAdapterDuplicator. + DesktopRect desktop_rect() const { return desktop_rect_; } + + // Returns the size of one screen owned by this DxgiAdapterDuplicator. |id| + // should be between [0, screen_count()). + DesktopRect ScreenRect(int id) const; + + // Returns the count of screens owned by this DxgiAdapterDuplicator. These + // screens can be retrieved by an interger in the range of + // [0, screen_count()). + int screen_count() const { return static_cast(duplicators_.size()); } + + private: + friend class DxgiDuplicatorController; + + bool DoInitialize(); + + void Setup(Context* context); + + void Unregister(const Context* const context); + + const D3dDevice device_; + std::vector duplicators_; + DesktopRect desktop_rect_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_ADAPTER_DUPLICATOR_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc b/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc new file mode 100644 index 0000000000..46a345846e --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.cc @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h" + +#include + +#include + +#include "webrtc/base/checks.h" + +namespace webrtc { + +DxgiDuplicatorController::Context::~Context() { + DxgiDuplicatorController::Instance()->Unregister(this); +} + +// static +DxgiDuplicatorController* DxgiDuplicatorController::Instance() { + // The static instance won't be deleted to ensure it can be used by other + // threads even during program exiting. + static DxgiDuplicatorController* instance = new DxgiDuplicatorController(); + return instance; +} + +DxgiDuplicatorController::DxgiDuplicatorController() = default; + +DxgiDuplicatorController::~DxgiDuplicatorController() { + rtc::CritScope lock(&lock_); + Deinitialize(); +} + +bool DxgiDuplicatorController::IsSupported() { + rtc::CritScope lock(&lock_); + return Initialize(); +} + +DesktopVector DxgiDuplicatorController::dpi() { + rtc::CritScope lock(&lock_); + if (Initialize()) { + return dpi_; + } + return DesktopVector(); +} + +DesktopRect DxgiDuplicatorController::desktop_rect() { + rtc::CritScope lock(&lock_); + if (Initialize()) { + return desktop_rect_; + } + return DesktopRect(); +} + +DesktopSize DxgiDuplicatorController::desktop_size() { + DesktopRect rect = desktop_rect(); + return DesktopSize(rect.right(), rect.bottom()); +} + +DesktopRect DxgiDuplicatorController::ScreenRect(int id) { + RTC_DCHECK(id >= 0); + rtc::CritScope lock(&lock_); + if (!Initialize()) { + return DesktopRect(); + } + for (size_t i = 0; i < duplicators_.size(); i++) { + if (id >= duplicators_[i].screen_count()) { + id -= duplicators_[i].screen_count(); + } else { + return duplicators_[i].ScreenRect(id); + } + } + return DesktopRect(); +} + +int DxgiDuplicatorController::ScreenCount() { + rtc::CritScope lock(&lock_); + if (!Initialize()) { + return 0; + } + int result = 0; + for (auto& duplicator : duplicators_) { + result += duplicator.screen_count(); + } + return result; +} + +void DxgiDuplicatorController::Unregister(const Context* const context) { + rtc::CritScope lock(&lock_); + if (ContextExpired(context)) { + // The Context has not been setup after a recent initialization, so it + // should not been registered in duplicators. + return; + } + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Unregister(&context->contexts_[i]); + } +} + +bool DxgiDuplicatorController::Initialize() { + if (!duplicators_.empty()) { + return true; + } + + if (DoInitialize()) { + return true; + } + Deinitialize(); + return false; +} + +bool DxgiDuplicatorController::DoInitialize() { + RTC_DCHECK(desktop_rect_.is_empty()); + RTC_DCHECK(duplicators_.empty()); + + std::vector devices = D3dDevice::EnumDevices(); + if (devices.empty()) { + return false; + } + + for (size_t i = 0; i < devices.size(); i++) { + duplicators_.emplace_back(devices[i]); + if (!duplicators_.back().Initialize()) { + return false; + } + if (desktop_rect_.is_empty()) { + desktop_rect_ = duplicators_.back().desktop_rect(); + } else { + const DesktopRect& left = desktop_rect_; + const DesktopRect& right = duplicators_.back().desktop_rect(); + desktop_rect_ = + DesktopRect::MakeLTRB(std::min(left.left(), right.left()), + std::min(left.top(), right.top()), + std::max(left.right(), right.right()), + std::max(left.bottom(), right.bottom())); + } + } + + HDC hdc = GetDC(nullptr); + // Use old DPI value if failed. + if (hdc) { + dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); + ReleaseDC(nullptr, hdc); + } + + identity_++; + return true; +} + +void DxgiDuplicatorController::Deinitialize() { + desktop_rect_ = DesktopRect(); + duplicators_.clear(); +} + +bool DxgiDuplicatorController::ContextExpired( + const Context* const context) const { + return context->identity_ != identity_ || + context->contexts_.size() != duplicators_.size(); +} + +void DxgiDuplicatorController::Setup(Context* context) { + if (ContextExpired(context)) { + context->contexts_.clear(); + context->contexts_.resize(duplicators_.size()); + for (size_t i = 0; i < duplicators_.size(); i++) { + duplicators_[i].Setup(&context->contexts_[i]); + } + context->identity_ = identity_; + } +} + +bool DxgiDuplicatorController::Duplicate(Context* context, + const DesktopFrame* last_frame, + DesktopFrame* target) { + return DoDuplicate(context, -1, last_frame, target); +} + +bool DxgiDuplicatorController::DuplicateMonitor(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target) { + RTC_DCHECK_GE(monitor_id, 0); + return DoDuplicate(context, monitor_id, last_frame, target); +} + +bool DxgiDuplicatorController::DoDuplicate(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target) { + RTC_DCHECK(target); + if (last_frame && !target->size().equals(last_frame->size())) { + return false; + } + target->mutable_updated_region()->Clear(); + rtc::CritScope lock(&lock_); + if (!Initialize()) { + // Cannot initialize COM components now, display mode may be changing. + return false; + } + Setup(context); + if (monitor_id < 0) { + // Capture entire screen. + for (size_t i = 0; i < duplicators_.size(); i++) { + if (!duplicators_[i].Duplicate(&context->contexts_[i], last_frame, + target)) { + Deinitialize(); + return false; + } + } + target->set_dpi(dpi()); + return true; + } + + // Capture one monitor. + for (size_t i = 0; i < duplicators_.size() && i < context->contexts_.size(); + i++) { + if (monitor_id >= duplicators_[i].screen_count()) { + monitor_id -= duplicators_[i].screen_count(); + } else { + if (duplicators_[i].DuplicateMonitor(&context->contexts_[i], monitor_id, + last_frame, target)) { + target->set_dpi(dpi()); + return true; + } + Deinitialize(); + return false; + } + } + // id >= ScreenCount(). This is a user error, so we do not need to + // deinitialize. + return false; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h b/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h new file mode 100644 index 0000000000..e65035d8eb --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ + +#include + +#include "webrtc/base/criticalsection.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/win/d3d_device.h" +#include "webrtc/modules/desktop_capture/win/dxgi_adapter_duplicator.h" + +namespace webrtc { + +// A controller for all the objects we need to call Windows DirectX capture APIs +// It's a singleton because, only one IDXGIOutputDuplication instance per +// monitor is allowed per application. +// +// Consumers should create a DxgiDuplicatorController::Context and keep it +// throughout their lifetime, and pass it when calling Duplicate(). Consumers +// can also call IsSupported() to determine whether the system supports DXGI +// duplicator or not. If a previous IsSupported() function call returns true, +// but a later Duplicate() returns false, this usually means the display mode is +// changing. Consumers should retry after a while. (Typically 50 milliseconds, +// but according to hardware performance, this time may vary.) +// +// This class is normally used with double buffering, e.g. as in +// ScreenCapturerWinDirectx, but it should work with consumers with one buffer, +// i.e. consumers can always send nullptr for |last_frame|. Some minor changes +// in DxgiOutputDuplicator class are nice to have to reduce size of data to copy +// (Commented in dxgi_output_duplicator.cc). But this class won't work +// with three or more buffers, the updated region merging logic will be broken +// in such scenarios. If a consumer does have this requirement, one can always +// send a new Context instance to Duplicate() function to force duplicator to +// treat it as a new consumer. +class DxgiDuplicatorController { + public: + // A context to store the status of a single consumer of + // DxgiDuplicatorController. + class Context { + public: + // Unregister this Context instance from all Dxgi duplicators during + // destructing. + ~Context(); + + private: + friend class DxgiDuplicatorController; + + // A Context will have an exactly same |identity_| as + // DxgiDuplicatorController, to ensure it has been correctly setted up after + // each DxgiDuplicatorController::Initialize(). + int identity_ = 0; + + // Child DxgiAdapterDuplicator::Context belongs to this Context. + std::vector contexts_; + }; + + // Returns the singleton instance of DxgiDuplicatorController. + static DxgiDuplicatorController* Instance(); + + // Destructs current instance. We need to make sure COM components and their + // containers are destructed in correct order. + ~DxgiDuplicatorController(); + + // Detects whether the system supports DXGI based capturer. + bool IsSupported(); + + // Captures current screen and writes into target. Since we are using double + // buffering, |last_frame|.updated_region() is used to represent the not + // updated regions in current |target| frame, which should also be copied this + // time. + // TODO(zijiehe): Windows cannot guarantee the frames returned by each + // IDXGIOutputDuplication are synchronized. But we are using a totally + // different threading model than the way Windows suggested, it's hard to + // synchronize them manually. But we should find a way to do it. + bool Duplicate(Context* context, + const DesktopFrame* last_frame, + DesktopFrame* target); + + // Captures one monitor and writes into target. |monitor_id| should >= 0. If + // |monitor_id| is greater than the total screen count of all the Duplicators, + // this function returns false. + bool DuplicateMonitor(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target); + + // Returns dpi of current system. Returns an empty DesktopVector if system + // does not support DXGI based capturer. + DesktopVector dpi(); + + // Returns entire desktop size. Returns an empty DesktopRect if system does + // not support DXGI based capturer. + DesktopRect desktop_rect(); + + // Returns a DesktopSize to cover entire desktop_rect. This may be different + // than desktop_rect().size(), since top-left screen does not need to start + // from (0, 0). + DesktopSize desktop_size(); + + // Returns the size of one screen. |monitor_id| should be >= 0. If system does + // not support DXGI based capturer, or |monitor_id| is greater than the total + // screen count of all the Duplicators, this function returns an empty + // DesktopRect. + DesktopRect ScreenRect(int id); + + // Returns the count of screens on the system. These screens can be retrieved + // by an integer in the range of [0, ScreenCount()). If system does not + // support DXGI based capturer, this function returns 0. + int ScreenCount(); + + private: + // Context calls private Unregister(Context*) function during + // destructing. + friend class Context; + + // A private constructor to ensure consumers to use + // DxgiDuplicatorController::Instance(). + DxgiDuplicatorController(); + + // Unregisters Context from this instance and all DxgiAdapterDuplicator(s) + // it owns. + void Unregister(const Context* const context); + + // All functions below should be called in |lock_| locked scope. + + // If current instance has not been initialized, executes DoInitialize + // function, and returns initialize result. Otherwise directly returns true. + bool Initialize(); + + bool DoInitialize(); + + // Clears all COM components referred by this instance. So next Duplicate() + // call will eventually initialize this instance again. + void Deinitialize(); + + // A helper function to check whether a Context has been expired. + bool ContextExpired(const Context* const context) const; + + // Updates Context if needed. + void Setup(Context* context); + + // Do the real duplication work. |monitor_id < 0| to capture entire screen. + bool DoDuplicate(Context* context, + int monitor_id, + const DesktopFrame* last_frame, + DesktopFrame* target); + + // This lock must be locked whenever accessing any of the following objects. + rtc::CriticalSection lock_; + + // A self-incremented integer to compare with the one in Context, to + // ensure a Context has been initialized after DxgiDuplicatorController. + int identity_ = 0; + DesktopRect desktop_rect_; + DesktopVector dpi_; + std::vector duplicators_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_DUPLICATOR_CONTROLLER_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc new file mode 100644 index 0000000000..7ce17c66a4 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.cc @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h" + +#include + +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h" +#include "webrtc/modules/desktop_capture/win/dxgi_texture_staging.h" + +namespace webrtc { + +using Microsoft::WRL::ComPtr; + +namespace { + +// Timeout for AcquireNextFrame() call. +const int kAcquireTimeoutMs = 10; + +DesktopRect RECTToDesktopRect(const RECT& rect) { + return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); +} + +} // namespace + +DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device, + const ComPtr& output, + const DXGI_OUTPUT_DESC& desc) + : device_(device), + output_(output), + desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) { + RTC_DCHECK(output_); + RTC_DCHECK(!desktop_rect_.is_empty()); + RTC_DCHECK(desktop_rect_.left() >= 0 && desktop_rect_.top() >= 0); +} + +DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) = + default; + +DxgiOutputDuplicator::~DxgiOutputDuplicator() { + if (duplication_) { + duplication_->ReleaseFrame(); + } + texture_.reset(); +} + +bool DxgiOutputDuplicator::Initialize() { + if (DuplicateOutput()) { + if (desc_.DesktopImageInSystemMemory) { + texture_.reset(new DxgiTextureMapping(desktop_rect_, duplication_.Get())); + } else { + texture_.reset(new DxgiTextureStaging(desktop_rect_, device_)); + } + return true; + } else { + duplication_.Reset(); + return false; + } +} + +bool DxgiOutputDuplicator::DuplicateOutput() { + RTC_DCHECK(!duplication_); + _com_error error = + output_->DuplicateOutput(static_cast(device_.d3d_device()), + duplication_.GetAddressOf()); + if (error.Error() != S_OK || !duplication_) { + LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error " + << error.ErrorMessage() << ", with code " << error.Error(); + return false; + } + + memset(&desc_, 0, sizeof(desc_)); + duplication_->GetDesc(&desc_); + if (desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { + LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " + "format, which is required by downstream components, " + "format is " + << desc_.ModeDesc.Format; + return false; + } + + if (static_cast(desc_.ModeDesc.Width) != desktop_rect_.width() || + static_cast(desc_.ModeDesc.Height) != desktop_rect_.height()) { + LOG(LS_ERROR) << "IDXGIDuplicateOutput does not return a same size as its " + "IDXGIOutput1, size returned by IDXGIDuplicateOutput is " + << desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height + << ", size returned by IDXGIOutput1 is " + << desktop_rect_.width() << " x " << desktop_rect_.height(); + return false; + } + + return true; +} + +bool DxgiOutputDuplicator::ReleaseFrame() { + RTC_DCHECK(duplication_); + _com_error error = duplication_->ReleaseFrame(); + if (error.Error() != S_OK) { + LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication, " + "error" + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + return true; +} + +bool DxgiOutputDuplicator::Duplicate(Context* context, + const DesktopFrame* last_frame, + const DesktopVector offset, + DesktopFrame* target) { + RTC_DCHECK(duplication_); + RTC_DCHECK(texture_); + RTC_DCHECK(target); + DXGI_OUTDUPL_FRAME_INFO frame_info; + memset(&frame_info, 0, sizeof(frame_info)); + ComPtr resource; + _com_error error = duplication_->AcquireNextFrame( + kAcquireTimeoutMs, &frame_info, resource.GetAddressOf()); + if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) { + LOG(LS_ERROR) << "Failed to capture frame, error " << error.ErrorMessage() + << ", code " << error.Error(); + return false; + } + + // We need to merge updated region with the one from last frame, since current + // frame contains the content one frame before. Note, this is for double + // buffering implementation, as what we have in ScreenCapturerWinDirectx. If + // a consumer uses single buffering, we should clear context->updated_region + // after it has been merged to updated_region. + DesktopRegion updated_region = context->updated_region; + if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0) { + DetectUpdatedRegion(frame_info, offset, &context->updated_region); + SpreadContextChange(context); + updated_region.AddRegion(context->updated_region); + if (!texture_->CopyFrom(frame_info, resource.Get(), updated_region)) { + return false; + } + + const DesktopFrame& source = texture_->AsDesktopFrame(); + DesktopRect target_rect(DesktopRect::MakeSize(target->size())); + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + if (!target_rect.ContainsRect(it.rect())) { + // target size is not large enough to copy the pixel from texture. + return false; + } + target->CopyPixelsFrom(source, it.rect().top_left().subtract(offset), + it.rect()); + } + target->mutable_updated_region()->AddRegion(updated_region); + return texture_->Release() && ReleaseFrame(); + } + + if (last_frame != nullptr) { + // DxgiOutputDuplicatorContainer::Duplicate() makes sure target size and + // last frame size are consistent. + RTC_DCHECK(target->size().equals(last_frame->size())); + // No change since last frame or AcquireNextFrame() timed out, we will + // export last frame to the target. + context->updated_region.Clear(); + for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); + it.Advance()) { + target->CopyPixelsFrom(*last_frame, it.rect().top_left(), it.rect()); + } + target->mutable_updated_region()->AddRegion(updated_region); + } + // If AcquireNextFrame() failed with timeout error, we do not need to release + // the frame. + return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame(); +} + +DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect( + const DesktopVector offset) { + DesktopRect result(DesktopRect::MakeSize(desktop_rect_.size())); + result.Translate(offset); + return result; +} + +void DxgiOutputDuplicator::DetectUpdatedRegion( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + const DesktopVector offset, + DesktopRegion* updated_region) { + if (DoDetectUpdatedRegion(frame_info, updated_region)) { + updated_region->Translate(offset.x(), offset.y()); + // Make sure even a region returned by Windows API is out of the scope of + // desktop_rect_, we still won't export it to the target DesktopFrame. + updated_region->IntersectWith(TranslatedDesktopRect(offset)); + } else { + updated_region->SetRect(TranslatedDesktopRect(offset)); + } +} + +bool DxgiOutputDuplicator::DoDetectUpdatedRegion( + const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region) { + RTC_DCHECK(updated_region); + updated_region->Clear(); + if (frame_info.TotalMetadataBufferSize == 0) { + // This should not happen, since frame_info.AccumulatedFrames > 0. + LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " + "but TotalMetadataBufferSize == 0"; + return false; + } + + if (metadata.capacity() < frame_info.TotalMetadataBufferSize) { + metadata.clear(); // Avoid data copy + metadata.reserve(frame_info.TotalMetadataBufferSize); + } + + UINT buff_size = 0; + DXGI_OUTDUPL_MOVE_RECT* move_rects = + reinterpret_cast(metadata.data()); + size_t move_rects_count = 0; + _com_error error = _com_error(duplication_->GetFrameMoveRects( + static_cast(metadata.capacity()), move_rects, &buff_size)); + if (error.Error() != S_OK) { + LOG(LS_ERROR) << "Failed to get move rectangles, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); + + RECT* dirty_rects = reinterpret_cast(metadata.data() + buff_size); + size_t dirty_rects_count = 0; + error = _com_error(duplication_->GetFrameDirtyRects( + static_cast(metadata.capacity()) - buff_size, dirty_rects, + &buff_size)); + if (error.Error() != S_OK) { + LOG(LS_ERROR) << "Failed to get dirty rectangles, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + dirty_rects_count = buff_size / sizeof(RECT); + + while (move_rects_count > 0) { + updated_region->AddRect(DesktopRect::MakeXYWH( + move_rects->SourcePoint.x, move_rects->SourcePoint.y, + move_rects->DestinationRect.right - move_rects->DestinationRect.left, + move_rects->DestinationRect.bottom - move_rects->DestinationRect.top)); + updated_region->AddRect(DesktopRect::MakeLTRB( + move_rects->DestinationRect.left, move_rects->DestinationRect.top, + move_rects->DestinationRect.right, move_rects->DestinationRect.bottom)); + move_rects++; + move_rects_count--; + } + + while (dirty_rects_count > 0) { + updated_region->AddRect( + DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top, + dirty_rects->right, dirty_rects->bottom)); + dirty_rects++; + dirty_rects_count--; + } + + return true; +} + +void DxgiOutputDuplicator::Setup(Context* context) { + RTC_DCHECK(context->updated_region.is_empty()); + // Always copy entire monitor during the first Duplicate() function call. + context->updated_region.AddRect(desktop_rect_); + for (size_t i = 0; i < contexts_.size(); i++) { + if (contexts_[i] == nullptr) { + contexts_[i] = context; + return; + } + } + + contexts_.push_back(context); +} + +void DxgiOutputDuplicator::Unregister(const Context* const context) { + for (size_t i = 0; i < contexts_.size(); i++) { + if (contexts_[i] == context) { + contexts_[i] = nullptr; + return; + } + } + + RTC_NOTREACHED(); +} + +void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) { + for (Context* dest : contexts_) { + if (dest != source) { + dest->updated_region.AddRegion(source->updated_region); + } + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h new file mode 100644 index 0000000000..31fb32ab9e --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ + +#include +#include +#include +#include + +#include +#include + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/thread_annotations.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/win/d3d_device.h" +#include "webrtc/modules/desktop_capture/win/dxgi_texture.h" + +namespace webrtc { + +// Duplicates the content on one IDXGIOutput, i.e. one monitor attached to one +// video card. None of functions in this class is thread-safe. +// TODO(zijiehe): Understand the meaning of rotation. +class DxgiOutputDuplicator { + public: + struct Context { + // The updated region DxgiOutputDuplicator::DetectUpdatedRegion() output + // during last Duplicate() function call. It's a DesktopRegion translated by + // offset of each DxgiOutputDuplicator instance. + DesktopRegion updated_region; + }; + + // Creates an instance of DxgiOutputDuplicator from a D3dDevice and one of its + // IDXGIOutput1. Caller must maintain the lifetime of device, to make sure it + // outlives this instance. Only DxgiAdapterDuplicator can create an instance. + DxgiOutputDuplicator(const D3dDevice& device, + const Microsoft::WRL::ComPtr& output, + const DXGI_OUTPUT_DESC& desc); + + // To allow this class to work with vector. + DxgiOutputDuplicator(DxgiOutputDuplicator&& other); + + // Destructs this instance. We need to make sure texture_ has been released + // before duplication_. + ~DxgiOutputDuplicator(); + + // Initializes duplication_ object. + bool Initialize(); + + // Copies the content of current IDXGIOutput to the |target|. To improve the + // performance, this function copies only regions merged from + // |last_frame|.updated_region and DetectUpdatedRegion(). The |offset| decides + // the + // offset in the |target| where the content should be copied to. i.e. this + // function copies the content to the rectangle of (offset.x(), offset.y()) to + // (offset.x() + desktop_rect_.width(), offset.y() + desktop_rect_.height()). + // The |last_frame| is always expected to be translated by the same offset. + // Returns false in case of a failure. + bool Duplicate(Context* context, + const DesktopFrame* last_frame, + const DesktopVector offset, + DesktopFrame* target); + + // Returns the desktop rect covered by this DxgiOutputDuplicator. + DesktopRect desktop_rect() const { return desktop_rect_; } + + private: + friend class DxgiAdapterDuplicator; + + // Detects updated region translated by offset from IDXGIOutput1. This + // function will set the |updated_region| as entire DesktopRect starts from + // offset if it failed to execute Windows APIs. + void DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + const DesktopVector offset, + DesktopRegion* updated_region); + + // Returns untranslated updated region, which are directly returned by Windows + // APIs. Returns false in case of a failure. + bool DoDetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + DesktopRegion* updated_region); + + bool ReleaseFrame(); + + // Initializes duplication_ instance. Expects duplication_ is in empty status. + // Returns false if system does not support IDXGIOutputDuplication. + bool DuplicateOutput(); + + // Returns a DesktopRect with the same size of desktop_size_, but translated + // by offset. + DesktopRect TranslatedDesktopRect(const DesktopVector offset); + + void Setup(Context* context); + + void Unregister(const Context* const context); + + // Spreads changes from |context| to other registered Context(s) in + // contexts_. + void SpreadContextChange(const Context* const context); + + const D3dDevice& device_; + const Microsoft::WRL::ComPtr output_; + const DesktopRect desktop_rect_; + Microsoft::WRL::ComPtr duplication_; + DXGI_OUTDUPL_DESC desc_; + std::vector metadata; + std::unique_ptr texture_; + + // After each AcquireNextFrame() function call, updated_region_(s) of all + // active Context(s) need to be updated. Since they have missed the + // change this time. And during next Duplicate() function call, their + // updated_region_ will be merged and copied. + std::vector contexts_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_OUTPUT_DUPLICATOR_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture.cc b/webrtc/modules/desktop_capture/win/dxgi_texture.cc new file mode 100644 index 0000000000..96c98363a0 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture.cc @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_texture.h" + +#include "webrtc/modules/desktop_capture/desktop_region.h" + +namespace webrtc { + +namespace { + +class DxgiDesktopFrame : public DesktopFrame { + public: + explicit DxgiDesktopFrame(const DxgiTexture& texture) + : DesktopFrame(texture.desktop_rect().size(), + texture.pitch(), + texture.bits(), + nullptr) {} + + virtual ~DxgiDesktopFrame() = default; +}; + +} // namespace + +DxgiTexture::DxgiTexture(const DesktopRect& desktop_rect) + : desktop_rect_(desktop_rect) {} + +const DesktopFrame& DxgiTexture::AsDesktopFrame() { + if (!frame_) { + frame_.reset(new DxgiDesktopFrame(*this)); + } + return *frame_; +} + +bool DxgiTexture::Release() { + frame_.reset(); + return DoRelease(); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture.h b/webrtc/modules/desktop_capture/win/dxgi_texture.h new file mode 100644 index 0000000000..6d6976aa1d --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ + +#include + +#include + +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_geometry.h" + +namespace webrtc { + +class DesktopRegion; + +// A texture copied or mapped from a DXGI_OUTDUPL_FRAME_INFO and IDXGIResource. +class DxgiTexture { + public: + // Creates a DxgiTexture instance, which represents the DesktopRect area of + // entire screen -- usually a monitor on the system. + explicit DxgiTexture(const DesktopRect& desktop_rect); + + virtual ~DxgiTexture() = default; + + // Copies selected regions of a frame represented by frame_info and resource. + // Returns false if anything wrong. + virtual bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource, + const DesktopRegion& region) = 0; + + const DesktopRect& desktop_rect() const { return desktop_rect_; } + + uint8_t* bits() const { return static_cast(rect_.pBits); } + + int pitch() const { return static_cast(rect_.Pitch); } + + // Releases the resource currently holds by this instance. Returns false if + // anything wrong, and this instance should be deprecated in this state. bits, + // pitch and AsDesktopFrame are only valid after a success CopyFrom() call, + // but before Release() call. + bool Release(); + + // Returns a DesktopFrame snapshot of a DxgiTexture instance. This + // DesktopFrame is used to copy a DxgiTexture content to another DesktopFrame + // only. And it should not outlive its DxgiTexture instance. + const DesktopFrame& AsDesktopFrame(); + + protected: + DXGI_MAPPED_RECT rect_ = {0}; + + private: + virtual bool DoRelease() = 0; + + const DesktopRect desktop_rect_; + std::unique_ptr frame_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc b/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc new file mode 100644 index 0000000000..573b9a053f --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.cc @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h" + +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/system_wrappers/include/logging.h" + +namespace webrtc { + +DxgiTextureMapping::DxgiTextureMapping(const DesktopRect& desktop_rect, + IDXGIOutputDuplication* duplication) + : DxgiTexture(desktop_rect), duplication_(duplication) { + RTC_DCHECK(duplication_); +} + +DxgiTextureMapping::~DxgiTextureMapping() = default; + +bool DxgiTextureMapping::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource, + const DesktopRegion& region) { + RTC_DCHECK(resource && frame_info.AccumulatedFrames > 0); + rect_ = {0}; + _com_error error = duplication_->MapDesktopSurface(&rect_); + if (error.Error() != S_OK) { + rect_ = {0}; + LOG(LS_ERROR) << "Failed to map the IDXGIOutputDuplication to a bitmap, " + "error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + + return true; +} + +bool DxgiTextureMapping::DoRelease() { + _com_error error = duplication_->UnMapDesktopSurface(); + if (error.Error() != S_OK) { + LOG(LS_ERROR) << "Failed to unmap the IDXGIOutputDuplication, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h b/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h new file mode 100644 index 0000000000..dd3f407475 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ + +#include +#include + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/win/dxgi_texture.h" + +namespace webrtc { + +// A DxgiTexture which directly maps bitmap from IDXGIResource. This class is +// used when DXGI_OUTDUPL_DESC.DesktopImageInSystemMemory is true. (This usually +// means the video card shares main memory with CPU, instead of having its own +// individual memory.) +class DxgiTextureMapping : public DxgiTexture { + public: + // Creates a DxgiTextureMapping instance. Caller must maintain the lifetime + // of input duplication to make sure it outlives this instance. + DxgiTextureMapping(const DesktopRect& desktop_rect, + IDXGIOutputDuplication* duplication); + + ~DxgiTextureMapping() override; + + bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource, + const DesktopRegion& region) override; + + bool DoRelease() override; + + private: + IDXGIOutputDuplication* const duplication_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_MAPPING_H_ diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture_staging.cc b/webrtc/modules/desktop_capture/win/dxgi_texture_staging.cc new file mode 100644 index 0000000000..7478728143 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture_staging.cc @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/dxgi_texture_staging.h" + +#include +#include +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/system_wrappers/include/logging.h" + +using Microsoft::WRL::ComPtr; + +namespace webrtc { + +DxgiTextureStaging::DxgiTextureStaging(const DesktopRect& desktop_rect, + const D3dDevice& device) + : DxgiTexture(desktop_rect), device_(device) {} + +DxgiTextureStaging::~DxgiTextureStaging() = default; + +bool DxgiTextureStaging::InitializeStage(ID3D11Texture2D* texture) { + RTC_DCHECK(texture); + D3D11_TEXTURE2D_DESC desc = {0}; + texture->GetDesc(&desc); + if (static_cast(desc.Width) != desktop_rect().width() || + static_cast(desc.Height) != desktop_rect().height()) { + LOG(LS_ERROR) << "Texture size is not consistent with current DxgiTexture."; + return false; + } + + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + desc.Usage = D3D11_USAGE_STAGING; + if (stage_) { + AssertStageAndSurfaceAreSameObject(); + D3D11_TEXTURE2D_DESC current_desc; + stage_->GetDesc(¤t_desc); + if (memcmp(&desc, ¤t_desc, sizeof(D3D11_TEXTURE2D_DESC)) == 0) { + return true; + } + + // The descriptions are not consistent, we need to create a new + // ID3D11Texture2D instance. + stage_.Reset(); + surface_.Reset(); + } else { + RTC_DCHECK(!surface_); + } + + _com_error error = _com_error(device_.d3d_device()->CreateTexture2D( + &desc, nullptr, stage_.GetAddressOf())); + if (error.Error() != S_OK || !stage_) { + LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + + error = _com_error(stage_.As(&surface_)); + if (error.Error() != S_OK || !surface_) { + LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + + return true; +} + +void DxgiTextureStaging::AssertStageAndSurfaceAreSameObject() { + ComPtr left; + ComPtr right; + bool left_result = SUCCEEDED(stage_.As(&left)); + bool right_result = SUCCEEDED(surface_.As(&right)); + RTC_DCHECK(left_result); + RTC_DCHECK(right_result); + RTC_DCHECK(left.Get() == right.Get()); +} + +bool DxgiTextureStaging::CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource, + const DesktopRegion& region) { + RTC_DCHECK(resource && frame_info.AccumulatedFrames > 0); + ComPtr texture; + _com_error error = resource->QueryInterface( + __uuidof(ID3D11Texture2D), + reinterpret_cast(texture.GetAddressOf())); + if (error.Error() != S_OK || !texture) { + LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, " + "error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + + // AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to + // copy it to a CPU accessible staging ID3D11Texture2D. + if (!InitializeStage(texture.Get())) { + return false; + } + + for (DesktopRegion::Iterator it(region); !it.IsAtEnd(); it.Advance()) { + DesktopRect rect(it.rect()); + rect.Translate(-desktop_rect().left(), -desktop_rect().top()); + D3D11_BOX box; + box.left = rect.left(); + box.top = rect.top(); + box.right = rect.right(); + box.bottom = rect.bottom(); + box.front = 0; + box.back = 1; + device_.context()->CopySubresourceRegion( + static_cast(stage_.Get()), 0, rect.left(), rect.top(), + 0, static_cast(texture.Get()), 0, &box); + } + + rect_ = {0}; + error = _com_error(surface_->Map(&rect_, DXGI_MAP_READ)); + if (error.Error() != S_OK) { + rect_ = {0}; + LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap, error " + << error.ErrorMessage() << ", code " << error.Error(); + return false; + } + + return true; +} + +bool DxgiTextureStaging::DoRelease() { + _com_error error = _com_error(surface_->Unmap()); + if (error.Error() != S_OK) { + stage_.Reset(); + surface_.Reset(); + } + // If using staging mode, we only need to recreate ID3D11Texture2D instance. + // This will happen during next CopyFrom call. So this function always returns + // true. + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/dxgi_texture_staging.h b/webrtc/modules/desktop_capture/win/dxgi_texture_staging.h new file mode 100644 index 0000000000..2da1600122 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/dxgi_texture_staging.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ + +#include +#include +#include + +#include "webrtc/modules/desktop_capture/desktop_geometry.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/win/d3d_device.h" +#include "webrtc/modules/desktop_capture/win/dxgi_texture.h" + +namespace webrtc { + +// A pair of an ID3D11Texture2D and an IDXGISurface. We need an ID3D11Texture2D +// instance to copy GPU texture to RAM, but an IDXGISurface instance to map the +// texture into a bitmap buffer. These two instances are pointing to a same +// object. +// +// An ID3D11Texture2D is created by an ID3D11Device, so a DxgiTexture cannot be +// shared between two DxgiAdapterDuplicators. +class DxgiTextureStaging : public DxgiTexture { + public: + // Creates a DxgiTextureStaging instance. Caller must maintain the lifetime + // of input device to make sure it outlives this instance. + DxgiTextureStaging(const DesktopRect& desktop_rect, const D3dDevice& device); + + ~DxgiTextureStaging() override; + + // Copies selected regions of a frame represented by frame_info and resource. + // Returns false if anything wrong. + bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, + IDXGIResource* resource, + const DesktopRegion& region) override; + + bool DoRelease() override; + + private: + // Initializes stage_ from a CPU inaccessible IDXGIResource. Returns false if + // it failed to execute Windows APIs, or the size of the texture is not + // consistent with desktop_rect. + bool InitializeStage(ID3D11Texture2D* texture); + + // Makes sure stage_ and surface_ are always pointing to a same object. + // We need an ID3D11Texture2D instance for + // ID3D11DeviceContext::CopySubresourceRegion, but an IDXGISurface for + // IDXGISurface::Map. + void AssertStageAndSurfaceAreSameObject(); + + const DesktopRect desktop_rect_; + const D3dDevice& device_; + Microsoft::WRL::ComPtr stage_; + Microsoft::WRL::ComPtr surface_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_DXGI_TEXTURE_STAGING_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc index 159e5a9195..a09935fb01 100644 --- a/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.cc @@ -10,458 +10,25 @@ #include "webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h" -#include - -#include -#include -#include -#include +#include #include "webrtc/base/checks.h" -#include "webrtc/base/criticalsection.h" -#include "webrtc/base/scoped_ref_ptr.h" +#include "webrtc/base/logging.h" #include "webrtc/base/timeutils.h" #include "webrtc/modules/desktop_capture/desktop_frame.h" -#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" -#include "webrtc/system_wrappers/include/atomic32.h" -#include "webrtc/system_wrappers/include/logging.h" namespace webrtc { using Microsoft::WRL::ComPtr; -namespace { - -// Timeout for AcquireNextFrame() call. -const int kAcquireTimeoutMs = 10; - -// Wait time between two DuplicateOutput operations, DuplicateOutput may fail if -// display mode is changing. -const int kDuplicateOutputWaitMs = 50; - -// How many times we attempt to DuplicateOutput before returning an error to -// upstream components. -const int kDuplicateOutputAttempts = 10; - -rtc::GlobalLockPod g_initialize_lock; - -// A container of all the objects we need to call Windows API. Note, one -// application can only have one IDXGIOutputDuplication instance, that's the -// reason the container is singleton. -struct DxgiContainer { - rtc::CriticalSection duplication_lock; - rtc::CriticalSection acquire_lock; - bool initialize_result GUARDED_BY(g_initialize_lock) = false; - ID3D11Device* device GUARDED_BY(g_initialize_lock) = nullptr; - ID3D11DeviceContext* context GUARDED_BY(g_initialize_lock) = nullptr; - IDXGIOutput1* output1 GUARDED_BY(g_initialize_lock) = nullptr; - ComPtr duplication - GUARDED_BY(duplication_lock); - DXGI_OUTDUPL_DESC duplication_desc; - std::vector metadata GUARDED_BY(acquire_lock); -}; - -DxgiContainer* g_container GUARDED_BY(g_initialize_lock); - -} // namespace - -// A pair of an ID3D11Texture2D and an IDXGISurface. We need an -// ID3D11Texture2D instance to copy GPU texture to RAM, but an IDXGISurface to -// map the texture into a bitmap buffer. These two instances are always -// pointing to a same object. -// This class also has two DesktopRegions, one is the updated region from -// returned from Windows API, the other is the region intersects with the -// updated region of last frame. -// -// This class is not thread safe. -class ScreenCapturerWinDirectx::Texture { - public: - // Copy a frame represented by frame_info and resource. Returns false if - // anything wrong. - bool CopyFrom(const DXGI_OUTDUPL_FRAME_INFO& frame_info, - IDXGIResource* resource, - const DesktopRegion& last_updated_region) { - if (!resource || frame_info.AccumulatedFrames == 0) { - // Nothing updated, but current data is still valid. - return false; - } - - ComPtr texture; - _com_error error = resource->QueryInterface( - __uuidof(ID3D11Texture2D), - reinterpret_cast(texture.GetAddressOf())); - if (error.Error() != S_OK || !texture) { - LOG(LS_ERROR) << "Failed to convert IDXGIResource to ID3D11Texture2D, " - "error " - << error.ErrorMessage() << ", code " << error.Error(); - return false; - } - - // AcquireNextFrame returns a CPU inaccessible IDXGIResource, so we need to - // make a copy. - if (!InitializeStage(texture.Get())) { - return false; - } - - updated_region_.Clear(); - if (!DetectUpdatedRegion(frame_info, &updated_region_)) { - updated_region_.SetRect(DesktopRect::MakeSize(size())); - } - // We need to copy changed area in both this frame and last frame, since - // currently this frame stores the bitmap of the one before last frame. - copied_region_.Clear(); - copied_region_.AddRegion(updated_region_); - copied_region_.AddRegion(last_updated_region); - copied_region_.IntersectWith(DesktopRect::MakeSize(size())); - - for (DesktopRegion::Iterator it(copied_region_); - !it.IsAtEnd(); - it.Advance()) { - D3D11_BOX box; - box.left = it.rect().left(); - box.top = it.rect().top(); - box.right = it.rect().right(); - box.bottom = it.rect().bottom(); - box.front = 0; - box.back = 1; - g_container->context->CopySubresourceRegion( - static_cast(stage_.Get()), - 0, it.rect().left(), it.rect().top(), 0, - static_cast(texture.Get()), - 0, &box); - } - - rect_ = {0}; - error = _com_error(surface_->Map(&rect_, DXGI_MAP_READ)); - if (error.Error() != S_OK) { - rect_ = {0}; - LOG(LS_ERROR) << "Failed to map the IDXGISurface to a bitmap, error " - << error.ErrorMessage() << ", code " << error.Error(); - return false; - } - - // surface_->Unmap() will be called next time we capture an image to avoid - // memory copy without shared_memory. - return true; - } - - uint8_t* bits() const { return static_cast(rect_.pBits); } - int pitch() const { return static_cast(rect_.Pitch); } - const DesktopSize& size() const { return size_; } - const DesktopVector& dpi() const { return dpi_; } - - int32_t AddRef() { - return ++ref_count_; - } - - int32_t Release() { - int32_t ref_count; - ref_count = --ref_count_; - if (ref_count == 0) { - delete this; - } - return ref_count; - } - - const DesktopRegion& updated_region() { - return updated_region_; - } - - const DesktopRegion& copied_region() { - return copied_region_; - } - - private: - // Texture should only be deleted by Release function. - ~Texture() = default; - - // Initializes stage_ from a CPU inaccessible IDXGIResource. Returns false - // if it fails to execute windows api. - bool InitializeStage(ID3D11Texture2D* texture) { - RTC_DCHECK(texture); - D3D11_TEXTURE2D_DESC desc = {0}; - texture->GetDesc(&desc); - desc.BindFlags = 0; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - desc.MiscFlags = 0; - desc.Usage = D3D11_USAGE_STAGING; - if (stage_) { - // Make sure stage_ and surface_ are always pointing to a same object. - // We need an ID3D11Texture2D instance for - // ID3D11DeviceContext::CopySubresourceRegion, but an IDXGISurface for - // IDXGISurface::Map. - { - ComPtr left; - ComPtr right; - bool left_result = SUCCEEDED(stage_.As(&left)); - bool right_result = SUCCEEDED(surface_.As(&right)); - RTC_DCHECK(left_result); - RTC_DCHECK(right_result); - RTC_DCHECK(left.Get() == right.Get()); - } - - // This buffer should be used already. - _com_error error = _com_error(surface_->Unmap()); - if (error.Error() == S_OK) { - D3D11_TEXTURE2D_DESC current_desc; - stage_->GetDesc(¤t_desc); - if (memcmp(&desc, ¤t_desc, sizeof(D3D11_TEXTURE2D_DESC)) == 0) { - return true; - } - } else { - // Let's recreate stage_ and surface_ later. - LOG(LS_ERROR) << "Failed to unmap surface, error " - << error.ErrorMessage() << ", code " << error.Error(); - } - - stage_.Reset(); - surface_.Reset(); - } else { - RTC_DCHECK(!surface_); - } - - HDC hdc = GetDC(nullptr); - // Use old DPI value if failed. - if (hdc != nullptr) { - dpi_.set(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); - ReleaseDC(nullptr, hdc); - } - - _com_error error = _com_error(g_container->device->CreateTexture2D( - &desc, nullptr, stage_.GetAddressOf())); - if (error.Error() != S_OK || !stage_) { - LOG(LS_ERROR) << "Failed to create a new ID3D11Texture2D as stage, " - "error " - << error.ErrorMessage() << ", code " << error.Error(); - return false; - } - - error = _com_error(stage_.As(&surface_)); - if (error.Error() != S_OK || !surface_) { - LOG(LS_ERROR) << "Failed to convert ID3D11Texture2D to IDXGISurface, " - "error " - << error.ErrorMessage() << ", code " << error.Error(); - return false; - } - - size_.set(desc.Width, desc.Height); - return true; - } - - ComPtr stage_; - ComPtr surface_; - DXGI_MAPPED_RECT rect_; - DesktopSize size_; - Atomic32 ref_count_; - // The updated region from Windows API. - DesktopRegion updated_region_; - // Combination of updated regions from both current frame and previous frame. - DesktopRegion copied_region_; - // The DPI of current frame. - DesktopVector dpi_; -}; - -// A DesktopFrame which does not own the data buffer, and also does not have -// shared memory. This uses in IT2ME scenario only. -class ScreenCapturerWinDirectx::DxgiDesktopFrame : public DesktopFrame { - public: - DxgiDesktopFrame( - const rtc::scoped_refptr& texture) - : DesktopFrame(texture.get()->size(), - texture.get()->pitch(), - texture.get()->bits(), - nullptr), - texture_(texture) { - set_dpi(texture->dpi()); - } - - virtual ~DxgiDesktopFrame() {} - - private: - // Keep a reference to the Texture instance to make sure we can still access - // its bytes array. - rtc::scoped_refptr texture_; -}; - -bool ScreenCapturerWinDirectx::Initialize() { - if (!g_container) { - rtc::GlobalLockScope lock(&g_initialize_lock); - if (!g_container) { - g_container = new DxgiContainer(); - g_container->initialize_result = DoInitialize(); - if (g_container->initialize_result) { - return true; - } - - // Clean up if DirectX cannot work on the system. - if (g_container->duplication) { - g_container->duplication.Reset(); - } - - if (g_container->output1) { - g_container->output1->Release(); - g_container->output1 = nullptr; - } - - if (g_container->context) { - g_container->context->Release(); - g_container->context = nullptr; - } - - if (g_container->device) { - g_container->device->Release(); - g_container->device = nullptr; - } - - return false; - } - } - - return g_container->initialize_result; -} - -bool ScreenCapturerWinDirectx::DoInitialize() { - D3D_FEATURE_LEVEL feature_level; - _com_error error = D3D11CreateDevice( - nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED, - nullptr, 0, D3D11_SDK_VERSION, &g_container->device, &feature_level, - &g_container->context); - if (error.Error() != S_OK || !g_container->device || !g_container->context) { - LOG(LS_WARNING) << "D3D11CreateDeivce returns error " - << error.ErrorMessage() << " with code " << error.Error(); - return false; - } - - if (feature_level < D3D_FEATURE_LEVEL_11_0) { - LOG(LS_WARNING) << "D3D11CreateDevice returns an instance without DirectX " - "11 support, level " - << feature_level; - return false; - } - - ComPtr device; - error = _com_error(g_container->device->QueryInterface( - __uuidof(IDXGIDevice), reinterpret_cast(device.GetAddressOf()))); - if (error.Error() != S_OK || !device) { - LOG(LS_WARNING) << "ID3D11Device is not an implementation of IDXGIDevice, " - "this usually means the system does not support DirectX " - "11"; - return false; - } - - ComPtr adapter; - error = _com_error(device->GetAdapter(adapter.GetAddressOf())); - if (error.Error() != S_OK || !adapter) { - LOG(LS_WARNING) << "Failed to get an IDXGIAdapter implementation from " - "IDXGIDevice."; - return false; - } - - ComPtr output; - for (int i = 0;; i++) { - error = _com_error(adapter->EnumOutputs(i, output.GetAddressOf())); - if (error.Error() == DXGI_ERROR_NOT_FOUND) { - LOG(LS_WARNING) << "No output detected."; - return false; - } - if (error.Error() == S_OK && output) { - DXGI_OUTPUT_DESC desc; - error = _com_error(output->GetDesc(&desc)); - if (error.Error() == S_OK) { - if (desc.AttachedToDesktop) { - // Current output instance is the device attached to desktop. - break; - } - } else { - LOG(LS_WARNING) << "Failed to get output description of device " << i - << ", ignore."; - } - } - } - - RTC_DCHECK(output); - error = _com_error(output.CopyTo( - __uuidof(IDXGIOutput1), reinterpret_cast(&g_container->output1))); - if (error.Error() != S_OK || !g_container->output1) { - LOG(LS_WARNING) << "Failed to convert IDXGIOutput to IDXGIOutput1, this " - "usually means the system does not support DirectX 11"; - return false; - } - - // When we are initializing the DXGI, retrying several times to avoid any - // temporary issue, such as display mode changing, to block us from using - // DXGI based capturer. - for (int i = 0; i < kDuplicateOutputAttempts; i++) { - if (DuplicateOutput()) { - return true; - } - Sleep(kDuplicateOutputWaitMs); - } - return false; -} - -bool ScreenCapturerWinDirectx::DuplicateOutput() { - // We are updating the instance. - rtc::CritScope lock(&g_container->duplication_lock); - // Make sure nobody is using current instance. - rtc::CritScope lock2(&g_container->acquire_lock); - if (g_container->duplication) { - return true; - } - - _com_error error = g_container->output1->DuplicateOutput( - static_cast(g_container->device), - g_container->duplication.GetAddressOf()); - if (error.Error() != S_OK || !g_container->duplication) { - g_container->duplication.Reset(); - LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error " - << error.ErrorMessage() << ", with code " - << error.Error(); - return false; - } - - memset(&g_container->duplication_desc, 0, sizeof(DXGI_OUTDUPL_DESC)); - g_container->duplication->GetDesc(&g_container->duplication_desc); - if (g_container->duplication_desc.ModeDesc.Format != - DXGI_FORMAT_B8G8R8A8_UNORM) { - g_container->duplication.Reset(); - LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " - "format, which is required by downstream components, " - "format is " - << g_container->duplication_desc.ModeDesc.Format; - return false; - } - - return true; -} - -bool ScreenCapturerWinDirectx::ForceDuplicateOutput() { - // We are updating the instance. - rtc::CritScope lock(&g_container->duplication_lock); - // Make sure nobody is using current instance. - rtc::CritScope lock2(&g_container->acquire_lock); - - if (g_container->duplication) { - g_container->duplication->ReleaseFrame(); - g_container->duplication.Reset(); - } - - return DuplicateOutput(); +bool ScreenCapturerWinDirectx::IsSupported() { + // Forward IsSupported function call to DxgiDuplicatorController. + return DxgiDuplicatorController::Instance()->IsSupported(); } ScreenCapturerWinDirectx::ScreenCapturerWinDirectx( const DesktopCaptureOptions& options) - : callback_(nullptr) { - RTC_DCHECK(g_container && g_container->initialize_result); - - // Texture instance won't change forever. - while (!surfaces_.current_frame()) { - surfaces_.ReplaceCurrentFrame(std::unique_ptr>( - new rtc::scoped_refptr(new Texture()))); - surfaces_.MoveToNextFrame(); - } -} + : callback_(nullptr) {} ScreenCapturerWinDirectx::~ScreenCapturerWinDirectx() {} @@ -477,202 +44,65 @@ void ScreenCapturerWinDirectx::SetSharedMemoryFactory( shared_memory_factory_ = std::move(shared_memory_factory); } -bool ScreenCapturerWinDirectx::HandleDetectUpdatedRegionError( - const _com_error& error, - const char* stage) { - if (error.Error() != S_OK) { - if (error.Error() == DXGI_ERROR_ACCESS_LOST) { - ForceDuplicateOutput(); - } else { - LOG(LS_ERROR) << "Failed to get " << stage << " rectangles, error " - << error.ErrorMessage() << ", code " << error.Error(); - } - // Send entire desktop as we cannot get dirty or move rectangles. - return false; +DesktopSize ScreenCapturerWinDirectx::SelectedDesktopSize() const { + if (current_screen_id == kFullDesktopScreenId) { + return DxgiDuplicatorController::Instance()->desktop_size(); } - - return true; -} - -bool ScreenCapturerWinDirectx::DetectUpdatedRegion( - const DXGI_OUTDUPL_FRAME_INFO& frame_info, - DesktopRegion* updated_region) { - RTC_DCHECK(g_container->duplication); - RTC_DCHECK(updated_region); - updated_region->Clear(); - if (frame_info.TotalMetadataBufferSize == 0) { - // This should not happen, since frame_info.AccumulatedFrames > 0. - LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " - "but TotalMetadataBufferSize == 0"; - return false; - } - - if (g_container->metadata.capacity() < frame_info.TotalMetadataBufferSize) { - g_container->metadata.clear(); // Avoid data copy - g_container->metadata.reserve(frame_info.TotalMetadataBufferSize); - } - - UINT buff_size = 0; - DXGI_OUTDUPL_MOVE_RECT* move_rects = - reinterpret_cast(g_container->metadata.data()); - size_t move_rects_count = 0; - _com_error error = _com_error(g_container->duplication->GetFrameMoveRects( - static_cast(g_container->metadata.capacity()), - move_rects, &buff_size)); - if (!HandleDetectUpdatedRegionError(error, "move")) { - return false; - } - move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); - - RECT* dirty_rects = - reinterpret_cast(g_container->metadata.data() + buff_size); - size_t dirty_rects_count = 0; - error = _com_error(g_container->duplication->GetFrameDirtyRects( - static_cast(g_container->metadata.capacity()) - buff_size, - dirty_rects, &buff_size)); - if (!HandleDetectUpdatedRegionError(error, "dirty")) { - return false; - } - dirty_rects_count = buff_size / sizeof(RECT); - - while (move_rects_count > 0) { - updated_region->AddRect(DesktopRect::MakeXYWH( - move_rects->SourcePoint.x, move_rects->SourcePoint.y, - move_rects->DestinationRect.right - move_rects->DestinationRect.left, - move_rects->DestinationRect.bottom - move_rects->DestinationRect.top)); - updated_region->AddRect(DesktopRect::MakeLTRB( - move_rects->DestinationRect.left, move_rects->DestinationRect.top, - move_rects->DestinationRect.right, move_rects->DestinationRect.bottom)); - move_rects++; - move_rects_count--; - } - - while (dirty_rects_count > 0) { - updated_region->AddRect(DesktopRect::MakeLTRB( - dirty_rects->left, dirty_rects->top, - dirty_rects->right, dirty_rects->bottom)); - dirty_rects++; - dirty_rects_count--; - } - - return true; -} - -std::unique_ptr ScreenCapturerWinDirectx::ProcessFrame( - const DXGI_OUTDUPL_FRAME_INFO& frame_info, - IDXGIResource* resource) { - RTC_DCHECK(resource); - RTC_DCHECK(frame_info.AccumulatedFrames > 0); - // We have something to update, so move to next surface. - surfaces_.MoveToNextFrame(); - if (shared_memory_factory_) { - // Make sure frames_ and surfaces_ are synchronized if we are using both. - frames_.MoveToNextFrame(); - } - RTC_DCHECK(surfaces_.current_frame()); - if (!surfaces_.current_frame()->get()->CopyFrom(frame_info, resource, - surfaces_.previous_frame()->get()->updated_region())) { - return std::unique_ptr(); - } - - std::unique_ptr result; - if (shared_memory_factory_) { - // When using shared memory, |frames_| is used to store a queue of - // SharedMemoryDesktopFrame's. - if (!frames_.current_frame() || - !frames_.current_frame()->size().equals( - surfaces_.current_frame()->get()->size())) { - // Current frame does not have a same size as last captured surface. - std::unique_ptr new_frame = - SharedMemoryDesktopFrame::Create( - surfaces_.current_frame()->get()->size(), - shared_memory_factory_.get()); - if (!new_frame) { - LOG(LS_ERROR) << "Failed to allocate a new SharedMemoryDesktopFrame"; - return std::unique_ptr(); - } - frames_.ReplaceCurrentFrame( - SharedDesktopFrame::Wrap(std::move(new_frame))); - } - result = frames_.current_frame()->Share(); - - std::unique_ptr frame( - new DxgiDesktopFrame(*surfaces_.current_frame())); - // Copy data into SharedMemory. - for (DesktopRegion::Iterator it( - surfaces_.current_frame()->get()->copied_region()); - !it.IsAtEnd(); - it.Advance()) { - result->CopyPixelsFrom(*frame, it.rect().top_left(), it.rect()); - } - result->set_dpi(frame->dpi()); - } else { - result.reset(new DxgiDesktopFrame(*surfaces_.current_frame())); - } - RTC_DCHECK(result); - *result->mutable_updated_region() = - surfaces_.current_frame()->get()->updated_region(); - return result; + return DxgiDuplicatorController::Instance() + ->ScreenRect(current_screen_id) + .size(); } void ScreenCapturerWinDirectx::Capture(const DesktopRegion& region) { RTC_DCHECK(callback_); - if (!g_container->duplication && !DuplicateOutput()) { - // Failed to initialize desktop duplication. This usually happens when - // Windows is switching display mode. Retrying later usually resolves the - // issue. - callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); - return; - } - - RTC_DCHECK(g_container->duplication); int64_t capture_start_time_nanos = rtc::TimeNanos(); - DXGI_OUTDUPL_FRAME_INFO frame_info; - memset(&frame_info, 0, sizeof(DXGI_OUTDUPL_FRAME_INFO)); - ComPtr resource; - rtc::CritScope lock(&g_container->acquire_lock); - _com_error error = g_container->duplication->AcquireNextFrame( - kAcquireTimeoutMs, &frame_info, resource.GetAddressOf()); - - if (error.Error() == DXGI_ERROR_WAIT_TIMEOUT) { - // Nothing changed. - EmitCurrentFrame(); - return; - } - - if (error.Error() != S_OK) { - LOG(LS_ERROR) << "Failed to capture frame, error " << error.ErrorMessage() - << ", code " << error.Error(); - if (ForceDuplicateOutput()) { - EmitCurrentFrame(); + frames_.MoveToNextFrame(); + if (!frames_.current_frame()) { + std::unique_ptr new_frame; + if (shared_memory_factory_) { + new_frame = SharedMemoryDesktopFrame::Create( + SelectedDesktopSize(), shared_memory_factory_.get()); } else { - callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + new_frame.reset(new BasicDesktopFrame(SelectedDesktopSize())); } - return; + if (!new_frame) { + LOG(LS_ERROR) << "Failed to allocate a new DesktopFrame."; + // This usually means we do not have enough memory or SharedMemoryFactory + // cannot work correctly. + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + frames_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(new_frame))); } - if (frame_info.AccumulatedFrames == 0) { - g_container->duplication->ReleaseFrame(); - EmitCurrentFrame(); - return; - } - - // Everything looks good so far, build next frame. - std::unique_ptr result = - ProcessFrame(frame_info, resource.Get()); - // DetectUpdatedRegion may release last g_container->duplication. But - // ForctDuplicateOutput function will always release last frame, so there is - // no potential leak. - if (g_container->duplication) { - g_container->duplication->ReleaseFrame(); - } - if (!result) { - callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); - return; + if (current_screen_id == kFullDesktopScreenId) { + if (!DxgiDuplicatorController::Instance()->Duplicate( + &context_, frames_.previous_frame(), frames_.current_frame())) { + // Screen size may be changed, so we need to reset the frames. + frames_.Reset(); + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + return; + } + } else { + if (!DxgiDuplicatorController::Instance()->DuplicateMonitor( + &context_, current_screen_id, frames_.previous_frame(), + frames_.current_frame())) { + // Screen size may be changed, so we need to reset the frames. + frames_.Reset(); + if (current_screen_id >= + DxgiDuplicatorController::Instance()->ScreenCount()) { + // Current monitor has been removed from the system. + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + } else { + callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); + } + return; + } } + std::unique_ptr result = frames_.current_frame()->Share(); result->set_capture_time_ms( (rtc::TimeNanos() - capture_start_time_nanos) / rtc::kNumNanosecsPerMillisec); @@ -680,41 +110,31 @@ void ScreenCapturerWinDirectx::Capture(const DesktopRegion& region) { } bool ScreenCapturerWinDirectx::GetScreenList(ScreenList* screens) { + int screen_count = DxgiDuplicatorController::Instance()->ScreenCount(); + for (int i = 0; i < screen_count; i++) { + screens->push_back(Screen{i}); + } return true; } bool ScreenCapturerWinDirectx::SelectScreen(ScreenId id) { - // Only full desktop capture is supported. - return id == kFullDesktopScreenId; -} - -void ScreenCapturerWinDirectx::EmitCurrentFrame() { - if (!surfaces_.current_frame()->get()->bits()) { - // At the very begining, we have not captured any frames. - callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); - return; + if (id == current_screen_id) { + return true; } - if (shared_memory_factory_) { - // If shared_memory_factory_ is provided, last frame is stored in frames_ - // queue. If there is not an existing frame (at the very begining), we can - // only return a nullptr. - if (frames_.current_frame()) { - std::unique_ptr frame = - frames_.current_frame()->Share(); - frame->mutable_updated_region()->Clear(); - callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); - } else { - callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); - } - return; + // Changing target screen may or may not impact frame size. So resetting + // frames only when a Duplicate() function call returns false. + if (id == kFullDesktopScreenId) { + current_screen_id = id; + return true; } - // If there is no shared_memory_factory_, last frame is stored in surfaces_ - // queue. - std::unique_ptr frame( - new DxgiDesktopFrame(*surfaces_.current_frame())); - callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); + int screen_count = DxgiDuplicatorController::Instance()->ScreenCount(); + if (id >= 0 && id < screen_count) { + current_screen_id = id; + return true; + } + return false; } } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h index 169151975e..119ffaecdf 100644 --- a/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h @@ -13,23 +13,14 @@ #include "webrtc/modules/desktop_capture/screen_capturer.h" -#include -#include -#include -#include -#include -#include -#include - #include #include -#include "webrtc/base/thread_annotations.h" #include "webrtc/modules/desktop_capture/desktop_capture_options.h" -#include "webrtc/modules/desktop_capture/desktop_geometry.h" #include "webrtc/modules/desktop_capture/desktop_region.h" #include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" #include "webrtc/modules/desktop_capture/shared_desktop_frame.h" +#include "webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h" namespace webrtc { @@ -37,10 +28,8 @@ namespace webrtc { // implementation won't work when ScreenCaptureFrameQueue.kQueueLength is not 2. class ScreenCapturerWinDirectx : public ScreenCapturer { public: - // Initializes DirectX related components. Returns false if any error - // happened, any instance of this class won't be able to work in such status. - // Thread safe, guarded by initialize_lock. - static bool Initialize(); + // Whether the system support DirectX based capturing. + static bool IsSupported(); explicit ScreenCapturerWinDirectx(const DesktopCaptureOptions& options); virtual ~ScreenCapturerWinDirectx(); @@ -53,48 +42,17 @@ class ScreenCapturerWinDirectx : public ScreenCapturer { bool SelectScreen(ScreenId id) override; private: - // Texture is a pair of an ID3D11Texture2D and an IDXGISurface. Refer to its - // implementation in source code for details. - class Texture; + // Returns desktop size of selected screen. + DesktopSize SelectedDesktopSize() const; - // An implementation of DesktopFrame to return data from a Texture instance. - class DxgiDesktopFrame; - - static bool DoInitialize(); - - // Initializes DxgiOutputDuplication. If current DxgiOutputDuplication - // instance is existing, this function takes no-op and returns true. Returns - // false if it fails to execute windows api. - static bool DuplicateOutput(); - - // Deprecates current DxgiOutputDuplication instance and calls DuplicateOutput - // to reinitialize it. - static bool ForceDuplicateOutput(); - - // Detects update regions in last frame, if anything wrong, returns false. - // ProcessFrame will insert a whole desktop size as updated region instead. - static bool DetectUpdatedRegion(const DXGI_OUTDUPL_FRAME_INFO& frame_info, - DesktopRegion* updated_region); - - // A helper function to handle _com_error result in DetectUpdatedRegion. - // Returns false if the _com_error shows an error. - static bool HandleDetectUpdatedRegionError(const _com_error& error, - const char* stage); - - // Processes one frame received from AcquireNextFrame function, returns a - // nullptr if anything wrong. - std::unique_ptr ProcessFrame( - const DXGI_OUTDUPL_FRAME_INFO& frame_info, - IDXGIResource* resource); - - // A shortcut to execute callback with current frame in frames. - void EmitCurrentFrame(); - - ScreenCaptureFrameQueue> surfaces_; ScreenCaptureFrameQueue frames_; std::unique_ptr shared_memory_factory_; Callback* callback_ = nullptr; + DxgiDuplicatorController::Context context_; + + ScreenId current_screen_id = kFullDesktopScreenId; + RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinDirectx); };