OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
| 3 * |
| 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ |
| 10 |
| 11 #include "webrtc/modules/desktop_capture/win/dxgi_output_duplicator.h" |
| 12 |
| 13 #include <string.h> |
| 14 |
| 15 #include <unknwn.h> |
| 16 #include <DXGIFormat.h> |
| 17 #include <Windows.h> |
| 18 |
| 19 #include "webrtc/base/checks.h" |
| 20 #include "webrtc/base/logging.h" |
| 21 #include "webrtc/modules/desktop_capture/win/dxgi_texture_mapping.h" |
| 22 #include "webrtc/modules/desktop_capture/win/dxgi_texture_staging.h" |
| 23 |
| 24 namespace webrtc { |
| 25 |
| 26 using Microsoft::WRL::ComPtr; |
| 27 |
| 28 namespace { |
| 29 |
| 30 // Timeout for AcquireNextFrame() call. |
| 31 const int kAcquireTimeoutMs = 10; |
| 32 |
| 33 DesktopRect RECTToDesktopRect(const RECT& rect) { |
| 34 return DesktopRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom); |
| 35 } |
| 36 |
| 37 } // namespace |
| 38 |
| 39 DxgiOutputDuplicator::DxgiOutputDuplicator(const D3dDevice& device, |
| 40 const ComPtr<IDXGIOutput1>& output, |
| 41 const DXGI_OUTPUT_DESC& desc) |
| 42 : device_(device), |
| 43 output_(output), |
| 44 desktop_rect_(RECTToDesktopRect(desc.DesktopCoordinates)) { |
| 45 RTC_DCHECK(output_); |
| 46 RTC_DCHECK(!desktop_rect_.is_empty()); |
| 47 RTC_DCHECK(desktop_rect_.left() >= 0 && desktop_rect_.top() >= 0); |
| 48 } |
| 49 |
| 50 DxgiOutputDuplicator::DxgiOutputDuplicator(DxgiOutputDuplicator&& other) = |
| 51 default; |
| 52 |
| 53 DxgiOutputDuplicator::~DxgiOutputDuplicator() { |
| 54 if (duplication_) { |
| 55 duplication_->ReleaseFrame(); |
| 56 } |
| 57 texture_.reset(); |
| 58 } |
| 59 |
| 60 bool DxgiOutputDuplicator::Initialize() { |
| 61 if (DuplicateOutput()) { |
| 62 if (desc_.DesktopImageInSystemMemory) { |
| 63 texture_.reset(new DxgiTextureMapping(desktop_rect_, duplication_.Get())); |
| 64 } else { |
| 65 texture_.reset(new DxgiTextureStaging(desktop_rect_, device_)); |
| 66 } |
| 67 return true; |
| 68 } else { |
| 69 duplication_.Reset(); |
| 70 return false; |
| 71 } |
| 72 } |
| 73 |
| 74 bool DxgiOutputDuplicator::DuplicateOutput() { |
| 75 RTC_DCHECK(!duplication_); |
| 76 _com_error error = |
| 77 output_->DuplicateOutput(static_cast<IUnknown*>(device_.d3d_device()), |
| 78 duplication_.GetAddressOf()); |
| 79 if (error.Error() != S_OK || !duplication_) { |
| 80 LOG(LS_WARNING) << "Failed to duplicate output from IDXGIOutput1, error " |
| 81 << error.ErrorMessage() << ", with code " << error.Error(); |
| 82 return false; |
| 83 } |
| 84 |
| 85 memset(&desc_, 0, sizeof(desc_)); |
| 86 duplication_->GetDesc(&desc_); |
| 87 if (desc_.ModeDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { |
| 88 LOG(LS_ERROR) << "IDXGIDuplicateOutput does not use RGBA (8 bit) " |
| 89 "format, which is required by downstream components, " |
| 90 "format is " |
| 91 << desc_.ModeDesc.Format; |
| 92 return false; |
| 93 } |
| 94 |
| 95 if (static_cast<int>(desc_.ModeDesc.Width) != desktop_rect_.width() || |
| 96 static_cast<int>(desc_.ModeDesc.Height) != desktop_rect_.height()) { |
| 97 LOG(LS_ERROR) << "IDXGIDuplicateOutput does not return a same size as its " |
| 98 "IDXGIOutput1, size returned by IDXGIDuplicateOutput is " |
| 99 << desc_.ModeDesc.Width << " x " << desc_.ModeDesc.Height |
| 100 << ", size returned by IDXGIOutput1 is " |
| 101 << desktop_rect_.width() << " x " << desktop_rect_.height(); |
| 102 return false; |
| 103 } |
| 104 |
| 105 return true; |
| 106 } |
| 107 |
| 108 bool DxgiOutputDuplicator::ReleaseFrame() { |
| 109 RTC_DCHECK(duplication_); |
| 110 _com_error error = duplication_->ReleaseFrame(); |
| 111 if (error.Error() != S_OK) { |
| 112 LOG(LS_ERROR) << "Failed to release frame from IDXGIOutputDuplication, " |
| 113 "error" |
| 114 << error.ErrorMessage() << ", code " << error.Error(); |
| 115 return false; |
| 116 } |
| 117 return true; |
| 118 } |
| 119 |
| 120 bool DxgiOutputDuplicator::Duplicate(Context* context, |
| 121 const DesktopFrame* last_frame, |
| 122 const DesktopVector offset, |
| 123 DesktopFrame* target) { |
| 124 RTC_DCHECK(duplication_); |
| 125 RTC_DCHECK(texture_); |
| 126 RTC_DCHECK(target); |
| 127 DXGI_OUTDUPL_FRAME_INFO frame_info; |
| 128 memset(&frame_info, 0, sizeof(frame_info)); |
| 129 ComPtr<IDXGIResource> resource; |
| 130 _com_error error = duplication_->AcquireNextFrame( |
| 131 kAcquireTimeoutMs, &frame_info, resource.GetAddressOf()); |
| 132 if (error.Error() != S_OK && error.Error() != DXGI_ERROR_WAIT_TIMEOUT) { |
| 133 LOG(LS_ERROR) << "Failed to capture frame, error " << error.ErrorMessage() |
| 134 << ", code " << error.Error(); |
| 135 return false; |
| 136 } |
| 137 |
| 138 // We need to merge updated region with the one from last frame, since current |
| 139 // frame contains the content one frame before. Note, this is for double |
| 140 // buffering implementation, as what we have in ScreenCapturerWinDirectx. If |
| 141 // a consumer uses single buffering, we should clear context->updated_region |
| 142 // after it has been merged to updated_region. |
| 143 DesktopRegion updated_region = context->updated_region; |
| 144 if (error.Error() == S_OK && frame_info.AccumulatedFrames > 0) { |
| 145 DetectUpdatedRegion(frame_info, offset, &context->updated_region); |
| 146 SpreadContextChange(context); |
| 147 updated_region.AddRegion(context->updated_region); |
| 148 if (!texture_->CopyFrom(frame_info, resource.Get(), updated_region)) { |
| 149 return false; |
| 150 } |
| 151 |
| 152 const DesktopFrame& source = texture_->AsDesktopFrame(); |
| 153 DesktopRect target_rect(DesktopRect::MakeSize(target->size())); |
| 154 for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); |
| 155 it.Advance()) { |
| 156 if (!target_rect.ContainsRect(it.rect())) { |
| 157 // target size is not large enough to copy the pixel from texture. |
| 158 return false; |
| 159 } |
| 160 target->CopyPixelsFrom(source, it.rect().top_left().subtract(offset), |
| 161 it.rect()); |
| 162 } |
| 163 target->mutable_updated_region()->AddRegion(updated_region); |
| 164 return texture_->Release() && ReleaseFrame(); |
| 165 } |
| 166 |
| 167 if (last_frame != nullptr) { |
| 168 // DxgiOutputDuplicatorContainer::Duplicate() makes sure target size and |
| 169 // last frame size are consistent. |
| 170 RTC_DCHECK(target->size().equals(last_frame->size())); |
| 171 // No change since last frame or AcquireNextFrame() timed out, we will |
| 172 // export last frame to the target. |
| 173 context->updated_region.Clear(); |
| 174 for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd(); |
| 175 it.Advance()) { |
| 176 target->CopyPixelsFrom(*last_frame, it.rect().top_left(), it.rect()); |
| 177 } |
| 178 target->mutable_updated_region()->AddRegion(updated_region); |
| 179 } |
| 180 // If AcquireNextFrame() failed with timeout error, we do not need to release |
| 181 // the frame. |
| 182 return error.Error() == DXGI_ERROR_WAIT_TIMEOUT || ReleaseFrame(); |
| 183 } |
| 184 |
| 185 DesktopRect DxgiOutputDuplicator::TranslatedDesktopRect( |
| 186 const DesktopVector offset) { |
| 187 DesktopRect result(DesktopRect::MakeSize(desktop_rect_.size())); |
| 188 result.Translate(offset); |
| 189 return result; |
| 190 } |
| 191 |
| 192 void DxgiOutputDuplicator::DetectUpdatedRegion( |
| 193 const DXGI_OUTDUPL_FRAME_INFO& frame_info, |
| 194 const DesktopVector offset, |
| 195 DesktopRegion* updated_region) { |
| 196 if (DoDetectUpdatedRegion(frame_info, updated_region)) { |
| 197 updated_region->Translate(offset.x(), offset.y()); |
| 198 // Make sure even a region returned by Windows API is out of the scope of |
| 199 // desktop_rect_, we still won't export it to the target DesktopFrame. |
| 200 updated_region->IntersectWith(TranslatedDesktopRect(offset)); |
| 201 } else { |
| 202 updated_region->SetRect(TranslatedDesktopRect(offset)); |
| 203 } |
| 204 } |
| 205 |
| 206 bool DxgiOutputDuplicator::DoDetectUpdatedRegion( |
| 207 const DXGI_OUTDUPL_FRAME_INFO& frame_info, |
| 208 DesktopRegion* updated_region) { |
| 209 RTC_DCHECK(updated_region); |
| 210 updated_region->Clear(); |
| 211 if (frame_info.TotalMetadataBufferSize == 0) { |
| 212 // This should not happen, since frame_info.AccumulatedFrames > 0. |
| 213 LOG(LS_ERROR) << "frame_info.AccumulatedFrames > 0, " |
| 214 "but TotalMetadataBufferSize == 0"; |
| 215 return false; |
| 216 } |
| 217 |
| 218 if (metadata.capacity() < frame_info.TotalMetadataBufferSize) { |
| 219 metadata.clear(); // Avoid data copy |
| 220 metadata.reserve(frame_info.TotalMetadataBufferSize); |
| 221 } |
| 222 |
| 223 UINT buff_size = 0; |
| 224 DXGI_OUTDUPL_MOVE_RECT* move_rects = |
| 225 reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(metadata.data()); |
| 226 size_t move_rects_count = 0; |
| 227 _com_error error = _com_error(duplication_->GetFrameMoveRects( |
| 228 static_cast<UINT>(metadata.capacity()), move_rects, &buff_size)); |
| 229 if (error.Error() != S_OK) { |
| 230 LOG(LS_ERROR) << "Failed to get move rectangles, error " |
| 231 << error.ErrorMessage() << ", code " << error.Error(); |
| 232 return false; |
| 233 } |
| 234 move_rects_count = buff_size / sizeof(DXGI_OUTDUPL_MOVE_RECT); |
| 235 |
| 236 RECT* dirty_rects = reinterpret_cast<RECT*>(metadata.data() + buff_size); |
| 237 size_t dirty_rects_count = 0; |
| 238 error = _com_error(duplication_->GetFrameDirtyRects( |
| 239 static_cast<UINT>(metadata.capacity()) - buff_size, dirty_rects, |
| 240 &buff_size)); |
| 241 if (error.Error() != S_OK) { |
| 242 LOG(LS_ERROR) << "Failed to get dirty rectangles, error " |
| 243 << error.ErrorMessage() << ", code " << error.Error(); |
| 244 return false; |
| 245 } |
| 246 dirty_rects_count = buff_size / sizeof(RECT); |
| 247 |
| 248 while (move_rects_count > 0) { |
| 249 updated_region->AddRect(DesktopRect::MakeXYWH( |
| 250 move_rects->SourcePoint.x, move_rects->SourcePoint.y, |
| 251 move_rects->DestinationRect.right - move_rects->DestinationRect.left, |
| 252 move_rects->DestinationRect.bottom - move_rects->DestinationRect.top)); |
| 253 updated_region->AddRect(DesktopRect::MakeLTRB( |
| 254 move_rects->DestinationRect.left, move_rects->DestinationRect.top, |
| 255 move_rects->DestinationRect.right, move_rects->DestinationRect.bottom)); |
| 256 move_rects++; |
| 257 move_rects_count--; |
| 258 } |
| 259 |
| 260 while (dirty_rects_count > 0) { |
| 261 updated_region->AddRect( |
| 262 DesktopRect::MakeLTRB(dirty_rects->left, dirty_rects->top, |
| 263 dirty_rects->right, dirty_rects->bottom)); |
| 264 dirty_rects++; |
| 265 dirty_rects_count--; |
| 266 } |
| 267 |
| 268 return true; |
| 269 } |
| 270 |
| 271 void DxgiOutputDuplicator::Setup(Context* context) { |
| 272 RTC_DCHECK(context->updated_region.is_empty()); |
| 273 // Always copy entire monitor during the first Duplicate() function call. |
| 274 context->updated_region.AddRect(desktop_rect_); |
| 275 for (size_t i = 0; i < contexts_.size(); i++) { |
| 276 if (contexts_[i] == nullptr) { |
| 277 contexts_[i] = context; |
| 278 return; |
| 279 } |
| 280 } |
| 281 |
| 282 contexts_.push_back(context); |
| 283 } |
| 284 |
| 285 void DxgiOutputDuplicator::Unregister(const Context* const context) { |
| 286 for (size_t i = 0; i < contexts_.size(); i++) { |
| 287 if (contexts_[i] == context) { |
| 288 contexts_[i] = nullptr; |
| 289 return; |
| 290 } |
| 291 } |
| 292 |
| 293 RTC_NOTREACHED(); |
| 294 } |
| 295 |
| 296 void DxgiOutputDuplicator::SpreadContextChange(const Context* const source) { |
| 297 for (Context* dest : contexts_) { |
| 298 if (dest != source) { |
| 299 dest->updated_region.AddRegion(source->updated_region); |
| 300 } |
| 301 } |
| 302 } |
| 303 |
| 304 } // namespace webrtc |
OLD | NEW |