1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/gl/swap_chain_presenter.h"
6
7 #include <d3d11_1.h>
8 #include <d3d11_4.h>
9
10 #include "base/debug/alias.h"
11 #include "base/debug/dump_without_crashing.h"
12 #include "base/feature_list.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/trace_event/trace_event.h"
17 #include "ui/gfx/color_space_win.h"
18 #include "ui/gfx/geometry/rect_conversions.h"
19 #include "ui/gl/dc_layer_tree.h"
20 #include "ui/gl/direct_composition_surface_win.h"
21 #include "ui/gl/gl_image_d3d.h"
22 #include "ui/gl/gl_image_dxgi.h"
23 #include "ui/gl/gl_image_memory.h"
24 #include "ui/gl/gl_switches.h"
25 #include "ui/gl/gl_utils.h"
26 #include "ui/gl/hdr_metadata_helper_win.h"
27
28 namespace gl {
29 namespace {
30
31 // Some drivers fail to correctly handle BT.709 video in overlays. This flag
32 // converts them to BT.601 in the video processor.
33 const base::Feature kFallbackBT709VideoToBT601{
34 "FallbackBT709VideoToBT601", base::FEATURE_DISABLED_BY_DEFAULT};
35
IsProtectedVideo(gfx::ProtectedVideoType protected_video_type)36 bool IsProtectedVideo(gfx::ProtectedVideoType protected_video_type) {
37 return protected_video_type != gfx::ProtectedVideoType::kClear;
38 }
39
40 class ScopedReleaseKeyedMutex {
41 public:
ScopedReleaseKeyedMutex(Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,UINT64 key)42 ScopedReleaseKeyedMutex(Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,
43 UINT64 key)
44 : keyed_mutex_(keyed_mutex), key_(key) {
45 DCHECK(keyed_mutex);
46 }
47
~ScopedReleaseKeyedMutex()48 ~ScopedReleaseKeyedMutex() {
49 HRESULT hr = keyed_mutex_->ReleaseSync(key_);
50 DCHECK(SUCCEEDED(hr));
51 }
52
53 private:
54 Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;
55 UINT64 key_ = 0;
56
57 DISALLOW_COPY_AND_ASSIGN(ScopedReleaseKeyedMutex);
58 };
59
60 // These values are persisted to logs. Entries should not be renumbered and
61 // numeric values should never be reused.
62 enum class OverlayFullScreenTypes {
63 kWindowMode,
64 kFullScreenMode,
65 kFullScreenInWidthOnly,
66 kFullScreenInHeightOnly,
67 kOverSizedFullScreen,
68 kNotAvailable,
69 kMaxValue = kNotAvailable,
70 };
71
72 enum : size_t {
73 kSwapChainImageIndex = 0,
74 kNV12ImageIndex = 0,
75 kYPlaneImageIndex = 0,
76 kUVPlaneImageIndex = 1,
77 };
78
ProtectedVideoTypeToString(gfx::ProtectedVideoType type)79 const char* ProtectedVideoTypeToString(gfx::ProtectedVideoType type) {
80 switch (type) {
81 case gfx::ProtectedVideoType::kClear:
82 return "Clear";
83 case gfx::ProtectedVideoType::kSoftwareProtected:
84 if (DirectCompositionSurfaceWin::AreOverlaysSupported())
85 return "SoftwareProtected.HasOverlaySupport";
86 else
87 return "SoftwareProtected.NoOverlaySupport";
88 case gfx::ProtectedVideoType::kHardwareProtected:
89 return "HardwareProtected";
90 }
91 }
92
CreateSurfaceHandleHelper(HANDLE * handle)93 bool CreateSurfaceHandleHelper(HANDLE* handle) {
94 using PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE =
95 HRESULT(WINAPI*)(DWORD, SECURITY_ATTRIBUTES*, HANDLE*);
96 static PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE create_surface_handle_function =
97 nullptr;
98
99 if (!create_surface_handle_function) {
100 HMODULE dcomp = ::GetModuleHandleA("dcomp.dll");
101 if (!dcomp) {
102 DLOG(ERROR) << "Failed to get handle for dcomp.dll";
103 return false;
104 }
105 create_surface_handle_function =
106 reinterpret_cast<PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE>(
107 ::GetProcAddress(dcomp, "DCompositionCreateSurfaceHandle"));
108 if (!create_surface_handle_function) {
109 DLOG(ERROR)
110 << "Failed to get address for DCompositionCreateSurfaceHandle";
111 return false;
112 }
113 }
114
115 HRESULT hr = create_surface_handle_function(COMPOSITIONOBJECT_ALL_ACCESS,
116 nullptr, handle);
117 if (FAILED(hr)) {
118 DLOG(ERROR) << "DCompositionCreateSurfaceHandle failed with error 0x"
119 << std::hex << hr;
120 return false;
121 }
122
123 return true;
124 }
125
DxgiFormatToString(DXGI_FORMAT format)126 const char* DxgiFormatToString(DXGI_FORMAT format) {
127 // Please also modify histogram enum and trace integration tests if new
128 // formats are added.
129 switch (format) {
130 case DXGI_FORMAT_R10G10B10A2_UNORM:
131 return "RGB10A2";
132 case DXGI_FORMAT_B8G8R8A8_UNORM:
133 return "BGRA";
134 case DXGI_FORMAT_YUY2:
135 return "YUY2";
136 case DXGI_FORMAT_NV12:
137 return "NV12";
138 default:
139 NOTREACHED();
140 return "UNKNOWN";
141 }
142 }
143
IsYUVSwapChainFormat(DXGI_FORMAT format)144 bool IsYUVSwapChainFormat(DXGI_FORMAT format) {
145 if (format == DXGI_FORMAT_NV12 || format == DXGI_FORMAT_YUY2)
146 return true;
147 return false;
148 }
149
BufferCount()150 UINT BufferCount() {
151 return base::FeatureList::IsEnabled(
152 features::kDCompTripleBufferVideoSwapChain)
153 ? 3u
154 : 2u;
155 }
156
157 // Transform is correct for scaling up |quad_rect| to on screen bounds, but
158 // doesn't include scaling transform from |swap_chain_size| to |quad_rect|.
159 // Since |swap_chain_size| could be equal to on screen bounds, and therefore
160 // possibly larger than |quad_rect|, this scaling could be downscaling, but
161 // only to the extent that it would cancel upscaling already in the transform.
UpdateSwapChainTransform(const gfx::Size & quad_size,const gfx::Size & swap_chain_size,gfx::Transform * transform)162 void UpdateSwapChainTransform(const gfx::Size& quad_size,
163 const gfx::Size& swap_chain_size,
164 gfx::Transform* transform) {
165 float swap_chain_scale_x = quad_size.width() * 1.0f / swap_chain_size.width();
166 float swap_chain_scale_y =
167 quad_size.height() * 1.0f / swap_chain_size.height();
168 transform->Scale(swap_chain_scale_x, swap_chain_scale_y);
169 }
170
171 } // namespace
172
173 SwapChainPresenter::PresentationHistory::PresentationHistory() = default;
174 SwapChainPresenter::PresentationHistory::~PresentationHistory() = default;
175
AddSample(DXGI_FRAME_PRESENTATION_MODE mode)176 void SwapChainPresenter::PresentationHistory::AddSample(
177 DXGI_FRAME_PRESENTATION_MODE mode) {
178 if (mode == DXGI_FRAME_PRESENTATION_MODE_COMPOSED)
179 composed_count_++;
180
181 presents_.push_back(mode);
182 if (presents_.size() > kPresentsToStore) {
183 DXGI_FRAME_PRESENTATION_MODE first_mode = presents_.front();
184 if (first_mode == DXGI_FRAME_PRESENTATION_MODE_COMPOSED)
185 composed_count_--;
186 presents_.pop_front();
187 }
188 }
189
Clear()190 void SwapChainPresenter::PresentationHistory::Clear() {
191 presents_.clear();
192 composed_count_ = 0;
193 }
194
Valid() const195 bool SwapChainPresenter::PresentationHistory::Valid() const {
196 return presents_.size() >= kPresentsToStore;
197 }
198
composed_count() const199 int SwapChainPresenter::PresentationHistory::composed_count() const {
200 return composed_count_;
201 }
202
SwapChainPresenter(DCLayerTree * layer_tree,HWND window,Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device)203 SwapChainPresenter::SwapChainPresenter(
204 DCLayerTree* layer_tree,
205 HWND window,
206 Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
207 Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device)
208 : layer_tree_(layer_tree),
209 window_(window),
210 d3d11_device_(d3d11_device),
211 dcomp_device_(dcomp_device),
212 is_on_battery_power_(true) {
213 if (base::PowerMonitor::IsInitialized()) {
214 is_on_battery_power_ = base::PowerMonitor::IsOnBatteryPower();
215 base::PowerMonitor::AddObserver(this);
216 }
217 }
218
~SwapChainPresenter()219 SwapChainPresenter::~SwapChainPresenter() {
220 base::PowerMonitor::RemoveObserver(this);
221 }
222
GetSwapChainFormat(gfx::ProtectedVideoType protected_video_type,bool content_is_hdr)223 DXGI_FORMAT SwapChainPresenter::GetSwapChainFormat(
224 gfx::ProtectedVideoType protected_video_type,
225 bool content_is_hdr) {
226 DXGI_FORMAT yuv_overlay_format =
227 DirectCompositionSurfaceWin::GetOverlayFormatUsedForSDR();
228 // TODO(crbug.com/850799): Assess power/perf impact when protected video
229 // swap chain is composited by DWM.
230
231 // Always prefer YUV swap chain for hardware protected video for now.
232 if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected)
233 return yuv_overlay_format;
234
235 // Prefer RGB10A2 swapchain when playing HDR content.
236 if (content_is_hdr)
237 return DXGI_FORMAT_R10G10B10A2_UNORM;
238
239 if (failed_to_create_yuv_swapchain_)
240 return DXGI_FORMAT_B8G8R8A8_UNORM;
241
242 // Start out as YUV.
243 if (!presentation_history_.Valid())
244 return yuv_overlay_format;
245
246 int composition_count = presentation_history_.composed_count();
247
248 // It's more efficient to use a BGRA backbuffer instead of YUV if overlays
249 // aren't being used, as otherwise DWM will use the video processor a second
250 // time to convert it to BGRA before displaying it on screen.
251
252 if (swap_chain_format_ == yuv_overlay_format) {
253 // Switch to BGRA once 3/4 of presents are composed.
254 if (composition_count >= (PresentationHistory::kPresentsToStore * 3 / 4))
255 return DXGI_FORMAT_B8G8R8A8_UNORM;
256 } else {
257 // Switch to YUV once 3/4 are using overlays (or unknown).
258 if (composition_count < (PresentationHistory::kPresentsToStore / 4))
259 return yuv_overlay_format;
260 }
261 return swap_chain_format_;
262 }
263
UploadVideoImages(GLImageMemory * y_image_memory,GLImageMemory * uv_image_memory)264 Microsoft::WRL::ComPtr<ID3D11Texture2D> SwapChainPresenter::UploadVideoImages(
265 GLImageMemory* y_image_memory,
266 GLImageMemory* uv_image_memory) {
267 gfx::Size texture_size = y_image_memory->GetSize();
268 gfx::Size uv_image_size = uv_image_memory->GetSize();
269 if (uv_image_size.height() != texture_size.height() / 2 ||
270 uv_image_size.width() != texture_size.width() / 2 ||
271 y_image_memory->format() != gfx::BufferFormat::R_8 ||
272 uv_image_memory->format() != gfx::BufferFormat::RG_88) {
273 DLOG(ERROR) << "Invalid NV12 GLImageMemory properties.";
274 return nullptr;
275 }
276
277 TRACE_EVENT1("gpu", "SwapChainPresenter::UploadVideoImages", "size",
278 texture_size.ToString());
279
280 bool use_dynamic_texture = !layer_tree_->disable_nv12_dynamic_textures();
281
282 D3D11_TEXTURE2D_DESC desc = {};
283 desc.Width = texture_size.width();
284 desc.Height = texture_size.height();
285 desc.Format = DXGI_FORMAT_NV12;
286 desc.MipLevels = 1;
287 desc.ArraySize = 1;
288 desc.Usage = use_dynamic_texture ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING;
289 // This isn't actually bound to a decoder, but dynamic textures need
290 // BindFlags to be nonzero and D3D11_BIND_DECODER also works when creating
291 // a VideoProcessorInputView.
292 desc.BindFlags = use_dynamic_texture ? D3D11_BIND_DECODER : 0;
293 desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
294 desc.MiscFlags = 0;
295 desc.SampleDesc.Count = 1;
296
297 if (!staging_texture_ || (staging_texture_size_ != texture_size)) {
298 staging_texture_.Reset();
299 copy_texture_.Reset();
300 HRESULT hr =
301 d3d11_device_->CreateTexture2D(&desc, nullptr, &staging_texture_);
302 base::UmaHistogramSparse(
303 "GPU.DirectComposition.UploadVideoImages.CreateStagingTexture", hr);
304 if (FAILED(hr)) {
305 DLOG(ERROR) << "Creating D3D11 video staging texture failed: " << std::hex
306 << hr;
307 DirectCompositionSurfaceWin::DisableOverlays();
308 return nullptr;
309 }
310 DCHECK(staging_texture_);
311 staging_texture_size_ = texture_size;
312 }
313
314 Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
315 d3d11_device_->GetImmediateContext(&context);
316 DCHECK(context);
317
318 D3D11_MAP map_type =
319 use_dynamic_texture ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE;
320 D3D11_MAPPED_SUBRESOURCE mapped_resource;
321 HRESULT hr =
322 context->Map(staging_texture_.Get(), 0, map_type, 0, &mapped_resource);
323 if (FAILED(hr)) {
324 DLOG(ERROR) << "Mapping D3D11 video staging texture failed: " << std::hex
325 << hr;
326 return nullptr;
327 }
328
329 size_t dest_stride = mapped_resource.RowPitch;
330 for (int y = 0; y < texture_size.height(); y++) {
331 const uint8_t* y_source =
332 y_image_memory->memory() + y * y_image_memory->stride();
333 uint8_t* dest =
334 reinterpret_cast<uint8_t*>(mapped_resource.pData) + dest_stride * y;
335 memcpy(dest, y_source, texture_size.width());
336 }
337
338 uint8_t* uv_dest_plane_start =
339 reinterpret_cast<uint8_t*>(mapped_resource.pData) +
340 dest_stride * texture_size.height();
341 for (int y = 0; y < uv_image_size.height(); y++) {
342 const uint8_t* uv_source =
343 uv_image_memory->memory() + y * uv_image_memory->stride();
344 uint8_t* dest = uv_dest_plane_start + dest_stride * y;
345 memcpy(dest, uv_source, texture_size.width());
346 }
347 context->Unmap(staging_texture_.Get(), 0);
348
349 if (use_dynamic_texture)
350 return staging_texture_;
351
352 if (!copy_texture_) {
353 desc.Usage = D3D11_USAGE_DEFAULT;
354 desc.BindFlags = D3D11_BIND_DECODER;
355 desc.CPUAccessFlags = 0;
356 HRESULT hr = d3d11_device_->CreateTexture2D(&desc, nullptr, ©_texture_);
357 base::UmaHistogramSparse(
358 "GPU.DirectComposition.UploadVideoImages.CreateCopyTexture", hr);
359 if (FAILED(hr)) {
360 DLOG(ERROR) << "Creating D3D11 video upload texture failed: " << std::hex
361 << hr;
362 DirectCompositionSurfaceWin::DisableOverlays();
363 return nullptr;
364 }
365 DCHECK(copy_texture_);
366 }
367 TRACE_EVENT0("gpu", "SwapChainPresenter::UploadVideoImages::CopyResource");
368 context->CopyResource(copy_texture_.Get(), staging_texture_.Get());
369 return copy_texture_;
370 }
371
GetMonitorSize()372 gfx::Size SwapChainPresenter::GetMonitorSize() {
373 if (DirectCompositionSurfaceWin::GetNumOfMonitors() == 1) {
374 // Only one monitor. Return the size of this monitor.
375 return DirectCompositionSurfaceWin::GetPrimaryMonitorSize();
376 } else {
377 gfx::Size monitor_size;
378 // Get the monitor on which the overlay is displayed.
379 MONITORINFO monitor_info;
380 monitor_info.cbSize = sizeof(monitor_info);
381 if (GetMonitorInfo(MonitorFromWindow(window_, MONITOR_DEFAULTTONEAREST),
382 &monitor_info)) {
383 monitor_size = gfx::Rect(monitor_info.rcMonitor).size();
384 }
385
386 return monitor_size;
387 }
388 }
389
AdjustSwapChainToFullScreenSizeIfNeeded(const ui::DCRendererLayerParams & params,const gfx::Rect & overlay_onscreen_rect,gfx::Size * swap_chain_size,gfx::Transform * transform,gfx::Rect * clip_rect)390 void SwapChainPresenter::AdjustSwapChainToFullScreenSizeIfNeeded(
391 const ui::DCRendererLayerParams& params,
392 const gfx::Rect& overlay_onscreen_rect,
393 gfx::Size* swap_chain_size,
394 gfx::Transform* transform,
395 gfx::Rect* clip_rect) {
396 gfx::Rect onscreen_rect = overlay_onscreen_rect;
397 if (params.is_clipped)
398 onscreen_rect.Intersect(*clip_rect);
399
400 // Because of the rounding when converting between pixels and DIPs, a
401 // fullscreen video can become slightly larger than the monitor - e.g. on
402 // a 3000x2000 monitor with a scale factor of 1.75 a 1920x1079 video can
403 // become 3002x1689.
404 // Swapchains that are bigger than the monitor won't be put into overlays,
405 // which will hurt power usage a lot. On those systems, the scaling can be
406 // adjusted very slightly so that it's less than the monitor size. This
407 // should be close to imperceptible. http://crbug.com/668278
408 constexpr int kFullScreenMargin = 5;
409
410 // The overlay must be positioned at (0, 0) in fullscreen mode.
411 if (std::abs(onscreen_rect.x()) >= kFullScreenMargin ||
412 std::abs(onscreen_rect.y()) >= kFullScreenMargin) {
413 // Not fullscreen mode.
414 return;
415 }
416
417 gfx::Size monitor_size = GetMonitorSize();
418 if (monitor_size.IsEmpty())
419 return;
420
421 // Check whether the on-screen overlay is near the full screen size.
422 // If yes, adjust the overlay size so it can fit the screen. This allows the
423 // application of fullscreen optimizations like dynamic backlighting or
424 // dynamic refresh rates (24hz/48hz). Note: The DWM optimizations works for
425 // both hardware and software overlays.
426 // If no, do nothing.
427 if (std::abs(onscreen_rect.width() - monitor_size.width()) >=
428 kFullScreenMargin ||
429 std::abs(onscreen_rect.height() - monitor_size.height()) >=
430 kFullScreenMargin) {
431 // Not fullscreen mode.
432 return;
433 }
434
435 // Adjust the clip rect.
436 if (params.is_clipped) {
437 *clip_rect = gfx::Rect(monitor_size);
438 }
439
440 // Adjust the swap chain size.
441 // The swap chain is either the size of onscreen_rect or min(onscreen_rect,
442 // content_rect). It might not need to update if it has the content size.
443 if (std::abs(swap_chain_size->width() - monitor_size.width()) <
444 kFullScreenMargin &&
445 std::abs(swap_chain_size->height() - monitor_size.height()) <
446 kFullScreenMargin) {
447 *swap_chain_size = monitor_size;
448 }
449
450 // Adjust the transform matrix.
451 auto& transform_matrix = transform->matrix();
452 float dx = -onscreen_rect.x();
453 float dy = -onscreen_rect.y();
454 transform_matrix.postTranslate(dx, dy, 0);
455
456 float scale_x = monitor_size.width() * 1.0f / swap_chain_size->width();
457 float scale_y = monitor_size.height() * 1.0f / swap_chain_size->height();
458 transform_matrix.setScale(scale_x, scale_y, 1);
459
460 #if DCHECK_IS_ON()
461 // The new transform matrix should transform the swap chain to the monitor
462 // rect.
463 gfx::Rect new_swap_chain_rect = gfx::Rect(*swap_chain_size);
464 new_swap_chain_rect.set_origin(params.quad_rect.origin());
465 gfx::RectF new_onscreen_rect(new_swap_chain_rect);
466 transform->TransformRect(&new_onscreen_rect);
467 DCHECK_EQ(gfx::ToEnclosingRect(new_onscreen_rect), gfx::Rect(monitor_size));
468 #endif
469 }
470
CalculateSwapChainSize(const ui::DCRendererLayerParams & params,gfx::Transform * transform,gfx::Rect * clip_rect)471 gfx::Size SwapChainPresenter::CalculateSwapChainSize(
472 const ui::DCRendererLayerParams& params,
473 gfx::Transform* transform,
474 gfx::Rect* clip_rect) {
475 // Swap chain size is the minimum of the on-screen size and the source size so
476 // the video processor can do the minimal amount of work and the overlay has
477 // to read the minimal amount of data. DWM is also less likely to promote a
478 // surface to an overlay if it's much larger than its area on-screen.
479 gfx::Size swap_chain_size = params.content_rect.size();
480 gfx::RectF bounds(params.quad_rect);
481 params.transform.TransformRect(&bounds);
482 gfx::Rect overlay_onscreen_rect = gfx::ToEnclosingRect(bounds);
483
484 // If transform isn't a scale or translation then swap chain can't be promoted
485 // to an overlay so avoid blitting to a large surface unnecessarily. Also,
486 // after the video rotation fix (crbug.com/904035), using rotated size for
487 // swap chain size will cause stretching since there's no squashing factor in
488 // the transform to counteract.
489 // TODO(sunnyps): Support 90/180/270 deg rotations using video context.
490 if (params.transform.IsScaleOrTranslation()) {
491 swap_chain_size = overlay_onscreen_rect.size();
492 }
493 if (DirectCompositionSurfaceWin::AreScaledOverlaysSupported() &&
494 !ShouldUseVideoProcessorScaling()) {
495 // Downscaling doesn't work on Intel display HW, and so DWM will perform an
496 // extra BLT to avoid HW downscaling. This prevents the use of hardware
497 // overlays especially for protected video.
498 swap_chain_size.SetToMin(params.content_rect.size());
499 }
500
501 // 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0
502 // subsampled formats like NV12 must have an even width and height.
503 if (swap_chain_size.width() % 2 == 1)
504 swap_chain_size.set_width(swap_chain_size.width() + 1);
505 if (swap_chain_size.height() % 2 == 1)
506 swap_chain_size.set_height(swap_chain_size.height() + 1);
507
508 // Adjust the transform matrix.
509 UpdateSwapChainTransform(params.quad_rect.size(), swap_chain_size, transform);
510
511 // In order to get the fullscreen DWM optimizations, the overlay onscreen rect
512 // must fit the monitor when in fullscreen mode. Adjust |swap_chain_size|,
513 // |transform| and |clip_rect| so |overlay_onscreen_rect| is the same as the
514 // monitor rect.
515 if (transform->IsScaleOrTranslation()) {
516 AdjustSwapChainToFullScreenSizeIfNeeded(
517 params, overlay_onscreen_rect, &swap_chain_size, transform, clip_rect);
518 }
519
520 return swap_chain_size;
521 }
522
UpdateVisuals(const ui::DCRendererLayerParams & params,const gfx::Size & swap_chain_size,const gfx::Transform & transform,const gfx::Rect & clip_rect)523 void SwapChainPresenter::UpdateVisuals(const ui::DCRendererLayerParams& params,
524 const gfx::Size& swap_chain_size,
525 const gfx::Transform& transform,
526 const gfx::Rect& clip_rect) {
527 if (!content_visual_) {
528 DCHECK(!clip_visual_);
529 dcomp_device_->CreateVisual(&clip_visual_);
530 DCHECK(clip_visual_);
531 dcomp_device_->CreateVisual(&content_visual_);
532 DCHECK(content_visual_);
533 clip_visual_->AddVisual(content_visual_.Get(), FALSE, nullptr);
534 layer_tree_->SetNeedsRebuildVisualTree();
535 }
536
537 // Visual offset is applied before transform so it behaves similar to how the
538 // compositor uses transform to map quad rect in layer space to target space.
539 gfx::Point offset = params.quad_rect.origin();
540
541 if (visual_info_.offset != offset || visual_info_.transform != transform) {
542 visual_info_.offset = offset;
543 visual_info_.transform = transform;
544 layer_tree_->SetNeedsRebuildVisualTree();
545
546 content_visual_->SetOffsetX(offset.x());
547 content_visual_->SetOffsetY(offset.y());
548
549 Microsoft::WRL::ComPtr<IDCompositionMatrixTransform> dcomp_transform;
550 dcomp_device_->CreateMatrixTransform(&dcomp_transform);
551 DCHECK(dcomp_transform);
552 // SkMatrix44 is column-major, but D2D_MATRIX_3x2_F is row-major.
553 D2D_MATRIX_3X2_F d2d_matrix = {
554 {{transform.matrix().get(0, 0), transform.matrix().get(1, 0),
555 transform.matrix().get(0, 1), transform.matrix().get(1, 1),
556 transform.matrix().get(0, 3), transform.matrix().get(1, 3)}}};
557 dcomp_transform->SetMatrix(d2d_matrix);
558 content_visual_->SetTransform(dcomp_transform.Get());
559 }
560
561 if (visual_info_.is_clipped != params.is_clipped ||
562 visual_info_.clip_rect != clip_rect) {
563 visual_info_.is_clipped = params.is_clipped;
564 visual_info_.clip_rect = clip_rect;
565 layer_tree_->SetNeedsRebuildVisualTree();
566 // DirectComposition clips happen in the pre-transform visual space, while
567 // cc/ clips happen post-transform. So the clip needs to go on a separate
568 // parent visual that's untransformed.
569 if (params.is_clipped) {
570 Microsoft::WRL::ComPtr<IDCompositionRectangleClip> clip;
571 dcomp_device_->CreateRectangleClip(&clip);
572 DCHECK(clip);
573 clip->SetLeft(clip_rect.x());
574 clip->SetRight(clip_rect.right());
575 clip->SetBottom(clip_rect.bottom());
576 clip->SetTop(clip_rect.y());
577 clip_visual_->SetClip(clip.Get());
578 } else {
579 clip_visual_->SetClip(nullptr);
580 }
581 }
582
583 if (visual_info_.z_order != params.z_order) {
584 visual_info_.z_order = params.z_order;
585 layer_tree_->SetNeedsRebuildVisualTree();
586 }
587 }
588
TryPresentToDecodeSwapChain(GLImageDXGI * nv12_image,const gfx::Rect & content_rect,const gfx::Size & swap_chain_size)589 bool SwapChainPresenter::TryPresentToDecodeSwapChain(
590 GLImageDXGI* nv12_image,
591 const gfx::Rect& content_rect,
592 const gfx::Size& swap_chain_size) {
593 if (ShouldUseVideoProcessorScaling())
594 return false;
595
596 auto not_used_reason = DecodeSwapChainNotUsedReason::kFailedToPresent;
597
598 bool nv12_supported =
599 (DXGI_FORMAT_NV12 ==
600 DirectCompositionSurfaceWin::GetOverlayFormatUsedForSDR());
601 // TODO(sunnyps): Try using decode swap chain for uploaded video images.
602 if (nv12_image && nv12_supported && !failed_to_present_decode_swapchain_) {
603 D3D11_TEXTURE2D_DESC texture_desc = {};
604 nv12_image->texture()->GetDesc(&texture_desc);
605
606 bool is_decoder_texture = texture_desc.BindFlags & D3D11_BIND_DECODER;
607
608 // Decode swap chains do not support shared resources.
609 // TODO(sunnyps): Find a workaround for when the decoder moves to its own
610 // thread and D3D device. See https://crbug.com/911847
611 bool is_shared_texture =
612 texture_desc.MiscFlags &
613 (D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX |
614 D3D11_RESOURCE_MISC_SHARED_NTHANDLE);
615
616 // DXVA decoder (or rather MFT) sometimes gives texture arrays with one
617 // element, which constitutes most of decode swap chain creation failures.
618 bool is_unitary_texture_array = texture_desc.ArraySize <= 1;
619
620 // Rotated videos are not promoted to overlays. We plan to implement
621 // rotation using video processor instead of via direct composition. Also
622 // check for skew and any downscaling specified to direct composition.
623 bool is_overlay_supported_transform =
624 visual_info_.transform.IsPositiveScaleOrTranslation();
625
626 // Downscaled video isn't promoted to hardware overlays. We prefer to
627 // blit into the smaller size so that it can be promoted to a hardware
628 // overlay.
629 float swap_chain_scale_x =
630 swap_chain_size.width() * 1.0f / content_rect.width();
631 float swap_chain_scale_y =
632 swap_chain_size.height() * 1.0f / content_rect.height();
633
634 is_overlay_supported_transform = is_overlay_supported_transform &&
635 (swap_chain_scale_x >= 1.0f) &&
636 (swap_chain_scale_y >= 1.0f);
637
638 if (is_decoder_texture && !is_shared_texture && !is_unitary_texture_array &&
639 is_overlay_supported_transform) {
640 if (PresentToDecodeSwapChain(nv12_image, content_rect, swap_chain_size))
641 return true;
642 ReleaseSwapChainResources();
643 failed_to_present_decode_swapchain_ = true;
644 not_used_reason = DecodeSwapChainNotUsedReason::kFailedToPresent;
645 DLOG(ERROR)
646 << "Present to decode swap chain failed - falling back to blit";
647 } else if (!is_decoder_texture) {
648 not_used_reason = DecodeSwapChainNotUsedReason::kNonDecoderTexture;
649 } else if (is_shared_texture) {
650 not_used_reason = DecodeSwapChainNotUsedReason::kSharedTexture;
651 } else if (is_unitary_texture_array) {
652 not_used_reason = DecodeSwapChainNotUsedReason::kUnitaryTextureArray;
653 } else if (!is_overlay_supported_transform) {
654 not_used_reason = DecodeSwapChainNotUsedReason::kIncompatibleTransform;
655 }
656 } else if (!nv12_image) {
657 not_used_reason = DecodeSwapChainNotUsedReason::kSoftwareFrame;
658 } else if (!nv12_supported) {
659 not_used_reason = DecodeSwapChainNotUsedReason::kNv12NotSupported;
660 } else if (failed_to_present_decode_swapchain_) {
661 not_used_reason = DecodeSwapChainNotUsedReason::kFailedToPresent;
662 }
663
664 UMA_HISTOGRAM_ENUMERATION(
665 "GPU.DirectComposition.DecodeSwapChainNotUsedReason", not_used_reason);
666 return false;
667 }
668
PresentToDecodeSwapChain(GLImageDXGI * nv12_image,const gfx::Rect & content_rect,const gfx::Size & swap_chain_size)669 bool SwapChainPresenter::PresentToDecodeSwapChain(
670 GLImageDXGI* nv12_image,
671 const gfx::Rect& content_rect,
672 const gfx::Size& swap_chain_size) {
673 DCHECK(!swap_chain_size.IsEmpty());
674
675 TRACE_EVENT2("gpu", "SwapChainPresenter::PresentToDecodeSwapChain",
676 "content_rect", content_rect.ToString(), "swap_chain_size",
677 swap_chain_size.ToString());
678
679 Microsoft::WRL::ComPtr<IDXGIResource> decode_resource;
680 nv12_image->texture().As(&decode_resource);
681 DCHECK(decode_resource);
682
683 if (!decode_swap_chain_ || decode_resource_ != decode_resource) {
684 TRACE_EVENT0(
685 "gpu",
686 "SwapChainPresenter::PresentToDecodeSwapChain::CreateDecodeSwapChain");
687 ReleaseSwapChainResources();
688
689 decode_resource_ = decode_resource;
690
691 HANDLE handle = INVALID_HANDLE_VALUE;
692 if (!CreateSurfaceHandleHelper(&handle))
693 return false;
694 swap_chain_handle_.Set(handle);
695
696 Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
697 d3d11_device_.As(&dxgi_device);
698 DCHECK(dxgi_device);
699 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
700 dxgi_device->GetAdapter(&dxgi_adapter);
701 DCHECK(dxgi_adapter);
702 Microsoft::WRL::ComPtr<IDXGIFactoryMedia> media_factory;
703 dxgi_adapter->GetParent(IID_PPV_ARGS(&media_factory));
704 DCHECK(media_factory);
705
706 DXGI_DECODE_SWAP_CHAIN_DESC desc = {};
707 // Set the DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO flag to mark this surface
708 // as a candidate for full screen video optimizations. If the surface
709 // does not qualify as fullscreen by DWM's logic then the flag will have
710 // no effects.
711 desc.Flags = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
712 HRESULT hr =
713 media_factory->CreateDecodeSwapChainForCompositionSurfaceHandle(
714 d3d11_device_.Get(), swap_chain_handle_.Get(), &desc,
715 decode_resource_.Get(), nullptr, &decode_swap_chain_);
716 base::UmaHistogramSparse(
717 "GPU.DirectComposition.DecodeSwapChainCreationResult", hr);
718 if (FAILED(hr)) {
719 DLOG(ERROR) << "CreateDecodeSwapChainForCompositionSurfaceHandle failed "
720 "with error 0x"
721 << std::hex << hr;
722 return false;
723 }
724 DCHECK(decode_swap_chain_);
725 SetSwapChainPresentDuration();
726
727 Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;
728 dcomp_device_.As(&desktop_device);
729 DCHECK(desktop_device);
730
731 hr = desktop_device->CreateSurfaceFromHandle(swap_chain_handle_.Get(),
732 &decode_surface_);
733 if (FAILED(hr)) {
734 DLOG(ERROR) << "CreateSurfaceFromHandle failed with error 0x" << std::hex
735 << hr;
736 return false;
737 }
738 DCHECK(decode_surface_);
739
740 content_visual_->SetContent(decode_surface_.Get());
741 layer_tree_->SetNeedsRebuildVisualTree();
742 } else if (last_presented_images_[kNV12ImageIndex] == nv12_image &&
743 swap_chain_size_ == swap_chain_size) {
744 // Early out if we're presenting the same image again.
745 return true;
746 }
747
748 RECT source_rect = content_rect.ToRECT();
749 decode_swap_chain_->SetSourceRect(&source_rect);
750
751 decode_swap_chain_->SetDestSize(swap_chain_size.width(),
752 swap_chain_size.height());
753 RECT target_rect = gfx::Rect(swap_chain_size).ToRECT();
754 decode_swap_chain_->SetTargetRect(&target_rect);
755
756 gfx::ColorSpace color_space = nv12_image->color_space();
757 if (!color_space.IsValid())
758 color_space = gfx::ColorSpace::CreateREC709();
759
760 // TODO(sunnyps): Move this to gfx::ColorSpaceWin helper where we can access
761 // internal color space state and do a better job.
762 // Common color spaces have primaries and transfer function similar to BT 709
763 // and there are no other choices anyway.
764 int color_space_flags = DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_BT709;
765 // Proper Rec 709 and 601 have limited or nominal color range.
766 if (color_space == gfx::ColorSpace::CreateREC709() ||
767 color_space == gfx::ColorSpace::CreateREC601()) {
768 color_space_flags |= DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_NOMINAL_RANGE;
769 }
770 // xvYCC allows colors outside nominal range to encode negative colors that
771 // allows for a wider gamut.
772 if (color_space.FullRangeEncodedValues()) {
773 color_space_flags |= DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_xvYCC;
774 }
775 decode_swap_chain_->SetColorSpace(
776 static_cast<DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS>(color_space_flags));
777
778 UINT present_flags = DXGI_PRESENT_USE_DURATION;
779 HRESULT hr =
780 decode_swap_chain_->PresentBuffer(nv12_image->level(), 1, present_flags);
781 // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
782 // that the window is occluded and we can stop rendering.
783 if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
784 DLOG(ERROR) << "PresentBuffer failed with error 0x" << std::hex << hr;
785 return false;
786 }
787
788 last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
789 last_presented_images_[kNV12ImageIndex] = nv12_image;
790 swap_chain_size_ = swap_chain_size;
791 if (swap_chain_format_ == DXGI_FORMAT_NV12) {
792 frames_since_color_space_change_++;
793 } else {
794 UMA_HISTOGRAM_COUNTS_1000(
795 "GPU.DirectComposition.FramesSinceColorSpaceChange",
796 frames_since_color_space_change_);
797 frames_since_color_space_change_ = 0;
798 }
799 RecordPresentationStatistics();
800 return true;
801 }
802
PresentToSwapChain(const ui::DCRendererLayerParams & params)803 bool SwapChainPresenter::PresentToSwapChain(
804 const ui::DCRendererLayerParams& params) {
805 GLImageDXGI* image_dxgi =
806 GLImageDXGI::FromGLImage(params.images[kNV12ImageIndex].get());
807 GLImageMemory* y_image_memory =
808 GLImageMemory::FromGLImage(params.images[kYPlaneImageIndex].get());
809 GLImageMemory* uv_image_memory =
810 GLImageMemory::FromGLImage(params.images[kUVPlaneImageIndex].get());
811 GLImageD3D* swap_chain_image =
812 GLImageD3D::FromGLImage(params.images[kSwapChainImageIndex].get());
813
814 if (!image_dxgi && (!y_image_memory || !uv_image_memory) &&
815 !swap_chain_image) {
816 DLOG(ERROR) << "Video GLImages are missing";
817 ReleaseSwapChainResources();
818 // We don't treat this as an error because this could mean that the client
819 // sent us invalid overlay candidates which we weren't able to detect prior
820 // to this. This would cause incorrect rendering, but not a failure loop.
821 return true;
822 }
823
824 if (image_dxgi && !image_dxgi->texture()) {
825 // We can't proceed if |image_dxgi| has no underlying d3d11 texture. It's
826 // unclear how we get into this state, but we do observe crashes due to it.
827 // Just stop here instead, and render incorrectly.
828 // https://crbug.com/1077645
829 DLOG(ERROR) << "Video NV12 texture is missing";
830 ReleaseSwapChainResources();
831 return true;
832 }
833
834 std::string image_type = "software video frame";
835 if (image_dxgi)
836 image_type = "hardware video frame";
837 if (swap_chain_image)
838 image_type = "swap chain";
839
840 gfx::Transform transform = params.transform;
841 gfx::Rect clip_rect = params.clip_rect;
842 gfx::Size swap_chain_size;
843 if (swap_chain_image) {
844 swap_chain_size = swap_chain_image->GetSize();
845 // |transform| now scales from |swap_chain_size| to on screen bounds.
846 UpdateSwapChainTransform(params.quad_rect.size(), swap_chain_size,
847 &transform);
848 } else {
849 swap_chain_size = CalculateSwapChainSize(params, &transform, &clip_rect);
850 }
851
852 TRACE_EVENT2("gpu", "SwapChainPresenter::PresentToSwapChain", "image_type",
853 image_type, "swap_chain_size", swap_chain_size.ToString());
854
855 bool content_is_hdr = image_dxgi && image_dxgi->color_space().IsHDR();
856 // Do not create a swap chain if swap chain size will be empty.
857 if (swap_chain_size.IsEmpty()) {
858 swap_chain_size_ = swap_chain_size;
859 if (swap_chain_) {
860 last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
861 ReleaseSwapChainResources();
862 content_visual_->SetContent(nullptr);
863 layer_tree_->SetNeedsRebuildVisualTree();
864 }
865 return true;
866 }
867
868 UpdateVisuals(params, swap_chain_size, transform, clip_rect);
869
870 // Swap chain image already has a swap chain that's presented by the client
871 // e.g. for webgl/canvas low-latency/desynchronized mode.
872 if (swap_chain_image) {
873 content_visual_->SetContent(swap_chain_image->swap_chain().Get());
874 if (last_presented_images_[kSwapChainImageIndex] != swap_chain_image) {
875 last_presented_images_ = params.images;
876 ReleaseSwapChainResources();
877 layer_tree_->SetNeedsRebuildVisualTree();
878 }
879 return true;
880 }
881
882 if (TryPresentToDecodeSwapChain(image_dxgi, params.content_rect,
883 swap_chain_size)) {
884 return true;
885 }
886
887 bool swap_chain_resized = swap_chain_size_ != swap_chain_size;
888
889 DXGI_FORMAT swap_chain_format =
890 GetSwapChainFormat(params.protected_video_type, content_is_hdr);
891 bool swap_chain_format_changed = swap_chain_format != swap_chain_format_;
892 bool toggle_protected_video =
893 protected_video_type_ != params.protected_video_type;
894
895 // Try reallocating swap chain if resizing fails.
896 if (!swap_chain_ || swap_chain_resized || swap_chain_format_changed ||
897 toggle_protected_video) {
898 if (!ReallocateSwapChain(swap_chain_size, swap_chain_format,
899 params.protected_video_type)) {
900 ReleaseSwapChainResources();
901 return false;
902 }
903 content_visual_->SetContent(swap_chain_.Get());
904 layer_tree_->SetNeedsRebuildVisualTree();
905 } else if (last_presented_images_ == params.images) {
906 // The swap chain is presenting the same images as last swap, which means
907 // that the images were never returned to the video decoder and should
908 // have the same contents as last time. It shouldn't need to be redrawn.
909 return true;
910 }
911 last_presented_images_ = params.images;
912
913 Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
914 UINT input_level;
915 Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
916 if (image_dxgi) {
917 input_texture = image_dxgi->texture();
918 input_level = static_cast<UINT>(image_dxgi->level());
919 // Keyed mutex may not exist.
920 keyed_mutex = image_dxgi->keyed_mutex();
921 staging_texture_.Reset();
922 copy_texture_.Reset();
923 } else {
924 DCHECK(y_image_memory);
925 DCHECK(uv_image_memory);
926 input_texture = UploadVideoImages(y_image_memory, uv_image_memory);
927 input_level = 0;
928 }
929
930 if (!input_texture) {
931 DLOG(ERROR) << "Video image has no texture";
932 return false;
933 }
934
935 // TODO(sunnyps): Use correct color space for uploaded video frames.
936 gfx::ColorSpace src_color_space = gfx::ColorSpace::CreateREC709();
937 if (image_dxgi && image_dxgi->color_space().IsValid())
938 src_color_space = image_dxgi->color_space();
939
940 base::Optional<DXGI_HDR_METADATA_HDR10> stream_metadata;
941 if (params.hdr_metadata.IsValid()) {
942 stream_metadata =
943 gl::HDRMetadataHelperWin::HDRMetadataToDXGI(params.hdr_metadata);
944 }
945
946 if (!VideoProcessorBlt(input_texture, input_level, keyed_mutex,
947 params.content_rect, src_color_space, content_is_hdr,
948 stream_metadata)) {
949 return false;
950 }
951
952 HRESULT hr, device_removed_reason;
953 if (first_present_) {
954 first_present_ = false;
955 UINT flags = DXGI_PRESENT_USE_DURATION;
956 hr = swap_chain_->Present(0, flags);
957 // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
958 // that the window is occluded and we can stop rendering.
959 if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
960 DLOG(ERROR) << "Present failed with error 0x" << std::hex << hr;
961 return false;
962 }
963
964 // DirectComposition can display black for a swap chain between the first
965 // and second time it's presented to - maybe the first Present can get
966 // lost somehow and it shows the wrong buffer. In that case copy the
967 // buffers so both have the correct contents, which seems to help. The
968 // first Present() after this needs to have SyncInterval > 0, or else the
969 // workaround doesn't help.
970 Microsoft::WRL::ComPtr<ID3D11Texture2D> dest_texture;
971 swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dest_texture));
972 DCHECK(dest_texture);
973 Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture;
974 hr = swap_chain_->GetBuffer(1, IID_PPV_ARGS(&src_texture));
975 DCHECK(src_texture);
976 Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
977 d3d11_device_->GetImmediateContext(&context);
978 DCHECK(context);
979 context->CopyResource(dest_texture.Get(), src_texture.Get());
980
981 // Additionally wait for the GPU to finish executing its commands, or
982 // there still may be a black flicker when presenting expensive content
983 // (e.g. 4k video).
984 Microsoft::WRL::ComPtr<IDXGIDevice2> dxgi_device2;
985 d3d11_device_.As(&dxgi_device2);
986 DCHECK(dxgi_device2);
987 base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
988 base::WaitableEvent::InitialState::NOT_SIGNALED);
989 hr = dxgi_device2->EnqueueSetEvent(event.handle());
990 if (SUCCEEDED(hr)) {
991 event.Wait();
992 } else {
993 device_removed_reason = d3d11_device_->GetDeviceRemovedReason();
994 base::debug::Alias(&hr);
995 base::debug::Alias(&device_removed_reason);
996 base::debug::DumpWithoutCrashing();
997 }
998 }
999 const bool use_swap_chain_tearing =
1000 DirectCompositionSurfaceWin::AllowTearing();
1001 UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;
1002 flags |= DXGI_PRESENT_USE_DURATION;
1003 UINT interval = use_swap_chain_tearing ? 0 : 1;
1004 // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
1005 // that the window is occluded and we can stop rendering.
1006 hr = swap_chain_->Present(interval, flags);
1007 if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
1008 DLOG(ERROR) << "Present failed with error 0x" << std::hex << hr;
1009 return false;
1010 }
1011 frames_since_color_space_change_++;
1012 RecordPresentationStatistics();
1013 return true;
1014 }
1015
SetFrameRate(float frame_rate)1016 void SwapChainPresenter::SetFrameRate(float frame_rate) {
1017 frame_rate_ = frame_rate;
1018 SetSwapChainPresentDuration();
1019 }
1020
RecordPresentationStatistics()1021 void SwapChainPresenter::RecordPresentationStatistics() {
1022 base::UmaHistogramSparse("GPU.DirectComposition.SwapChainFormat3",
1023 swap_chain_format_);
1024
1025 VideoPresentationMode presentation_mode;
1026 if (decode_swap_chain_) {
1027 presentation_mode = VideoPresentationMode::kZeroCopyDecodeSwapChain;
1028 } else if (staging_texture_) {
1029 presentation_mode = VideoPresentationMode::kUploadAndVideoProcessorBlit;
1030 } else {
1031 presentation_mode = VideoPresentationMode::kBindAndVideoProcessorBlit;
1032 }
1033 UMA_HISTOGRAM_ENUMERATION("GPU.DirectComposition.VideoPresentationMode",
1034 presentation_mode);
1035
1036 UMA_HISTOGRAM_BOOLEAN("GPU.DirectComposition.DecodeSwapChainUsed",
1037 !!decode_swap_chain_);
1038
1039 TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("gpu.service"),
1040 "SwapChain::Present", TRACE_EVENT_SCOPE_THREAD,
1041 "PixelFormat", DxgiFormatToString(swap_chain_format_),
1042 "ZeroCopy", !!decode_swap_chain_);
1043 Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media =
1044 GetSwapChainMedia();
1045 if (swap_chain_media) {
1046 DXGI_FRAME_STATISTICS_MEDIA stats = {};
1047 // GetFrameStatisticsMedia fails with DXGI_ERROR_FRAME_STATISTICS_DISJOINT
1048 // sometimes, which means an event (such as power cycle) interrupted the
1049 // gathering of presentation statistics. In this situation, calling the
1050 // function again succeeds but returns with CompositionMode = NONE.
1051 // Waiting for the DXGI adapter to finish presenting before calling the
1052 // function doesn't get rid of the failure.
1053 HRESULT hr = swap_chain_media->GetFrameStatisticsMedia(&stats);
1054 int mode = -1;
1055 if (SUCCEEDED(hr)) {
1056 base::UmaHistogramSparse("GPU.DirectComposition.CompositionMode",
1057 stats.CompositionMode);
1058 if (frame_rate_ != 0) {
1059 // [1ms, 10s] covers the fps between [0.1hz, 1000hz].
1060 base::UmaHistogramTimes("GPU.DirectComposition.ApprovedPresentDuration",
1061 base::TimeDelta::FromMilliseconds(
1062 stats.ApprovedPresentDuration / 10000));
1063 }
1064 presentation_history_.AddSample(stats.CompositionMode);
1065 mode = stats.CompositionMode;
1066 }
1067 // Record CompositionMode as -1 if GetFrameStatisticsMedia() fails.
1068 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("gpu.service"),
1069 "GetFrameStatisticsMedia", TRACE_EVENT_SCOPE_THREAD,
1070 "CompositionMode", mode);
1071 }
1072 }
1073
VideoProcessorBlt(Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture,UINT input_level,Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,const gfx::Rect & content_rect,const gfx::ColorSpace & src_color_space,bool content_is_hdr,base::Optional<DXGI_HDR_METADATA_HDR10> stream_hdr_metadata)1074 bool SwapChainPresenter::VideoProcessorBlt(
1075 Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture,
1076 UINT input_level,
1077 Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,
1078 const gfx::Rect& content_rect,
1079 const gfx::ColorSpace& src_color_space,
1080 bool content_is_hdr,
1081 base::Optional<DXGI_HDR_METADATA_HDR10> stream_hdr_metadata) {
1082 TRACE_EVENT2("gpu", "SwapChainPresenter::VideoProcessorBlt", "content_rect",
1083 content_rect.ToString(), "swap_chain_size",
1084 swap_chain_size_.ToString());
1085
1086 // TODO(sunnyps): Ensure output color space for YUV swap chains is Rec709 or
1087 // Rec601 so that the conversion from gfx::ColorSpace to DXGI_COLOR_SPACE
1088 // doesn't need a |force_yuv| parameter (and the associated plumbing).
1089 gfx::ColorSpace output_color_space = IsYUVSwapChainFormat(swap_chain_format_)
1090 ? src_color_space
1091 : gfx::ColorSpace::CreateSRGB();
1092 if (base::FeatureList::IsEnabled(kFallbackBT709VideoToBT601) &&
1093 (output_color_space == gfx::ColorSpace::CreateREC709())) {
1094 output_color_space = gfx::ColorSpace::CreateREC601();
1095 }
1096 if (content_is_hdr)
1097 output_color_space = gfx::ColorSpace::CreateHDR10();
1098
1099 if (!layer_tree_->InitializeVideoProcessor(
1100 content_rect.size(), swap_chain_size_, src_color_space,
1101 output_color_space, swap_chain_,
1102 IsYUVSwapChainFormat(swap_chain_format_))) {
1103 return false;
1104 }
1105
1106 Microsoft::WRL::ComPtr<ID3D11VideoContext> video_context =
1107 layer_tree_->video_context();
1108 Microsoft::WRL::ComPtr<ID3D11VideoProcessor> video_processor =
1109 layer_tree_->video_processor();
1110 Microsoft::WRL::ComPtr<ID3D11VideoContext2> context2;
1111 base::Optional<DXGI_HDR_METADATA_HDR10> display_metadata =
1112 layer_tree_->GetHDRMetadataHelper()->GetDisplayMetadata();
1113 if (display_metadata.has_value() && SUCCEEDED(video_context.As(&context2))) {
1114 if (stream_hdr_metadata.has_value()) {
1115 context2->VideoProcessorSetStreamHDRMetaData(
1116 video_processor.Get(), 0, DXGI_HDR_METADATA_TYPE_HDR10,
1117 sizeof(DXGI_HDR_METADATA_HDR10), &(*stream_hdr_metadata));
1118 }
1119
1120 context2->VideoProcessorSetOutputHDRMetaData(
1121 video_processor.Get(), DXGI_HDR_METADATA_TYPE_HDR10,
1122 sizeof(DXGI_HDR_METADATA_HDR10), &(*display_metadata));
1123 }
1124
1125 {
1126 base::Optional<ScopedReleaseKeyedMutex> release_keyed_mutex;
1127 if (keyed_mutex) {
1128 // The producer may still be using this texture for a short period of
1129 // time, so wait long enough to hopefully avoid glitches. For example,
1130 // all levels of the texture share the same keyed mutex, so if the
1131 // hardware decoder acquired the mutex to decode into a different array
1132 // level then it still may block here temporarily.
1133 const int kMaxSyncTimeMs = 1000;
1134 HRESULT hr = keyed_mutex->AcquireSync(0, kMaxSyncTimeMs);
1135 if (FAILED(hr)) {
1136 DLOG(ERROR) << "Error acquiring keyed mutex: " << std::hex << hr;
1137 return false;
1138 }
1139 release_keyed_mutex.emplace(keyed_mutex, 0);
1140 }
1141
1142 Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device =
1143 layer_tree_->video_device();
1144 Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>
1145 video_processor_enumerator = layer_tree_->video_processor_enumerator();
1146
1147 D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = {};
1148 input_desc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
1149 input_desc.Texture2D.ArraySlice = input_level;
1150
1151 Microsoft::WRL::ComPtr<ID3D11VideoProcessorInputView> input_view;
1152 HRESULT hr = video_device->CreateVideoProcessorInputView(
1153 input_texture.Get(), video_processor_enumerator.Get(), &input_desc,
1154 &input_view);
1155 if (FAILED(hr)) {
1156 DLOG(ERROR) << "CreateVideoProcessorInputView failed with error 0x"
1157 << std::hex << hr;
1158 return false;
1159 }
1160
1161 D3D11_VIDEO_PROCESSOR_STREAM stream = {};
1162 stream.Enable = true;
1163 stream.OutputIndex = 0;
1164 stream.InputFrameOrField = 0;
1165 stream.PastFrames = 0;
1166 stream.FutureFrames = 0;
1167 stream.pInputSurface = input_view.Get();
1168 RECT dest_rect = gfx::Rect(swap_chain_size_).ToRECT();
1169 video_context->VideoProcessorSetOutputTargetRect(video_processor.Get(),
1170 TRUE, &dest_rect);
1171 video_context->VideoProcessorSetStreamDestRect(video_processor.Get(), 0,
1172 TRUE, &dest_rect);
1173 RECT source_rect = content_rect.ToRECT();
1174 video_context->VideoProcessorSetStreamSourceRect(video_processor.Get(), 0,
1175 TRUE, &source_rect);
1176
1177 if (!output_view_) {
1178 Microsoft::WRL::ComPtr<ID3D11Texture2D> swap_chain_buffer;
1179 swap_chain_->GetBuffer(0, IID_PPV_ARGS(&swap_chain_buffer));
1180
1181 D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc = {};
1182 output_desc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
1183 output_desc.Texture2D.MipSlice = 0;
1184
1185 hr = video_device->CreateVideoProcessorOutputView(
1186 swap_chain_buffer.Get(), video_processor_enumerator.Get(),
1187 &output_desc, &output_view_);
1188 if (FAILED(hr)) {
1189 DLOG(ERROR) << "CreateVideoProcessorOutputView failed with error 0x"
1190 << std::hex << hr;
1191 return false;
1192 }
1193 DCHECK(output_view_);
1194 }
1195
1196 hr = video_context->VideoProcessorBlt(video_processor.Get(),
1197 output_view_.Get(), 0, 1, &stream);
1198 if (FAILED(hr)) {
1199 DLOG(ERROR) << "VideoProcessorBlt failed with error 0x" << std::hex << hr;
1200 return false;
1201 }
1202 }
1203
1204 return true;
1205 }
1206
ReleaseSwapChainResources()1207 void SwapChainPresenter::ReleaseSwapChainResources() {
1208 output_view_.Reset();
1209 swap_chain_.Reset();
1210 decode_surface_.Reset();
1211 decode_swap_chain_.Reset();
1212 decode_resource_.Reset();
1213 swap_chain_handle_.Close();
1214 staging_texture_.Reset();
1215 }
1216
ReallocateSwapChain(const gfx::Size & swap_chain_size,DXGI_FORMAT swap_chain_format,gfx::ProtectedVideoType protected_video_type)1217 bool SwapChainPresenter::ReallocateSwapChain(
1218 const gfx::Size& swap_chain_size,
1219 DXGI_FORMAT swap_chain_format,
1220 gfx::ProtectedVideoType protected_video_type) {
1221 bool use_yuv_swap_chain = IsYUVSwapChainFormat(swap_chain_format);
1222
1223 TRACE_EVENT2("gpu", "SwapChainPresenter::ReallocateSwapChain", "size",
1224 swap_chain_size.ToString(), "yuv", use_yuv_swap_chain);
1225
1226 DCHECK(!swap_chain_size.IsEmpty());
1227 swap_chain_size_ = swap_chain_size;
1228
1229 protected_video_type_ = protected_video_type;
1230
1231 if (swap_chain_format_ != swap_chain_format) {
1232 UMA_HISTOGRAM_COUNTS_1000(
1233 "GPU.DirectComposition.FramesSinceColorSpaceChange",
1234 frames_since_color_space_change_);
1235 frames_since_color_space_change_ = 0;
1236 }
1237
1238 ReleaseSwapChainResources();
1239
1240 Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
1241 d3d11_device_.As(&dxgi_device);
1242 DCHECK(dxgi_device);
1243 Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
1244 dxgi_device->GetAdapter(&dxgi_adapter);
1245 DCHECK(dxgi_adapter);
1246 Microsoft::WRL::ComPtr<IDXGIFactoryMedia> media_factory;
1247 dxgi_adapter->GetParent(IID_PPV_ARGS(&media_factory));
1248 DCHECK(media_factory);
1249
1250 // The composition surface handle is only used to create YUV swap chains since
1251 // CreateSwapChainForComposition can't do that.
1252 HANDLE handle = INVALID_HANDLE_VALUE;
1253 if (!CreateSurfaceHandleHelper(&handle))
1254 return false;
1255 swap_chain_handle_.Set(handle);
1256
1257 first_present_ = true;
1258
1259 DXGI_SWAP_CHAIN_DESC1 desc = {};
1260 desc.Width = swap_chain_size_.width();
1261 desc.Height = swap_chain_size_.height();
1262 desc.Format = swap_chain_format;
1263 desc.Stereo = FALSE;
1264 desc.SampleDesc.Count = 1;
1265 desc.BufferCount = BufferCount();
1266 desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
1267 desc.Scaling = DXGI_SCALING_STRETCH;
1268 desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
1269 desc.Flags =
1270 DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO | DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
1271 if (DirectCompositionSurfaceWin::AllowTearing())
1272 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
1273 if (IsProtectedVideo(protected_video_type))
1274 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY;
1275 if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected)
1276 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED;
1277 desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
1278
1279 const std::string kSwapChainCreationResultByFormatUmaPrefix =
1280 "GPU.DirectComposition.SwapChainCreationResult2.";
1281
1282 const std::string kSwapChainCreationResultByVideoTypeUmaPrefix =
1283 "GPU.DirectComposition.SwapChainCreationResult3.";
1284 const std::string protected_video_type_string =
1285 ProtectedVideoTypeToString(protected_video_type);
1286
1287 if (use_yuv_swap_chain) {
1288 TRACE_EVENT1("gpu", "SwapChainPresenter::ReallocateSwapChain::YUV",
1289 "format", DxgiFormatToString(swap_chain_format));
1290 HRESULT hr = media_factory->CreateSwapChainForCompositionSurfaceHandle(
1291 d3d11_device_.Get(), swap_chain_handle_.Get(), &desc, nullptr,
1292 &swap_chain_);
1293 failed_to_create_yuv_swapchain_ = FAILED(hr);
1294
1295 base::UmaHistogramSparse(kSwapChainCreationResultByFormatUmaPrefix +
1296 DxgiFormatToString(swap_chain_format),
1297 hr);
1298 base::UmaHistogramSparse(kSwapChainCreationResultByVideoTypeUmaPrefix +
1299 protected_video_type_string,
1300 hr);
1301
1302 if (failed_to_create_yuv_swapchain_) {
1303 DLOG(ERROR) << "Failed to create "
1304 << DxgiFormatToString(swap_chain_format)
1305 << " swap chain of size " << swap_chain_size.ToString()
1306 << " with error 0x" << std::hex << hr
1307 << "\nFalling back to BGRA";
1308 use_yuv_swap_chain = false;
1309 swap_chain_format = DXGI_FORMAT_B8G8R8A8_UNORM;
1310 }
1311 }
1312 if (!use_yuv_swap_chain) {
1313 std::ostringstream trace_event_stream;
1314 trace_event_stream << "SwapChainPresenter::ReallocateSwapChain::"
1315 << DxgiFormatToString(swap_chain_format);
1316 TRACE_EVENT0("gpu", trace_event_stream.str().c_str());
1317
1318 desc.Format = swap_chain_format;
1319 desc.Flags = 0;
1320 if (IsProtectedVideo(protected_video_type))
1321 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY;
1322 if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected)
1323 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED;
1324 if (DirectCompositionSurfaceWin::AllowTearing())
1325 desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
1326
1327 HRESULT hr = media_factory->CreateSwapChainForCompositionSurfaceHandle(
1328 d3d11_device_.Get(), swap_chain_handle_.Get(), &desc, nullptr,
1329 &swap_chain_);
1330
1331 base::UmaHistogramSparse(kSwapChainCreationResultByFormatUmaPrefix +
1332 DxgiFormatToString(swap_chain_format),
1333 hr);
1334 base::UmaHistogramSparse(kSwapChainCreationResultByVideoTypeUmaPrefix +
1335 protected_video_type_string,
1336 hr);
1337
1338 if (FAILED(hr)) {
1339 // Disable overlay support so dc_layer_overlay will stop sending down
1340 // overlay frames here and uses GL Composition instead.
1341 DirectCompositionSurfaceWin::DisableOverlays();
1342 DLOG(ERROR) << "Failed to create "
1343 << DxgiFormatToString(swap_chain_format)
1344 << " swap chain of size " << swap_chain_size.ToString()
1345 << " with error 0x" << std::hex << hr
1346 << ". Disable overlay swap chains";
1347 return false;
1348 }
1349 }
1350 swap_chain_format_ = swap_chain_format;
1351 SetSwapChainPresentDuration();
1352 return true;
1353 }
1354
OnPowerStateChange(bool on_battery_power)1355 void SwapChainPresenter::OnPowerStateChange(bool on_battery_power) {
1356 is_on_battery_power_ = on_battery_power;
1357 }
1358
ShouldUseVideoProcessorScaling()1359 bool SwapChainPresenter::ShouldUseVideoProcessorScaling() {
1360 return (!is_on_battery_power_ && !layer_tree_->disable_vp_scaling());
1361 }
1362
SetSwapChainPresentDuration()1363 void SwapChainPresenter::SetSwapChainPresentDuration() {
1364 Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media =
1365 GetSwapChainMedia();
1366 if (swap_chain_media) {
1367 UINT duration_100ns = FrameRateToPresentDuration(frame_rate_);
1368 UINT requested_duration = 0u;
1369 if (duration_100ns > 0) {
1370 UINT smaller_duration = 0u, larger_duration = 0u;
1371 HRESULT hr = swap_chain_media->CheckPresentDurationSupport(
1372 duration_100ns, &smaller_duration, &larger_duration);
1373 if (FAILED(hr)) {
1374 DLOG(ERROR) << "CheckPresentDurationSupport failed with error "
1375 << std::hex << hr;
1376 return;
1377 }
1378 constexpr UINT kDurationThreshold = 1000u;
1379 // Smaller duration should be used to avoid frame loss. However, we want
1380 // to take into consideration the larger duration is the same as the
1381 // requested duration but was slightly different due to frame rate
1382 // estimation errors.
1383 if (larger_duration > 0 &&
1384 larger_duration - duration_100ns < kDurationThreshold) {
1385 requested_duration = larger_duration;
1386 } else if (smaller_duration > 0) {
1387 requested_duration = smaller_duration;
1388 }
1389 }
1390 HRESULT hr = swap_chain_media->SetPresentDuration(requested_duration);
1391 if (FAILED(hr)) {
1392 DLOG(ERROR) << "SetPresentDuration failed with error " << std::hex << hr;
1393 }
1394 }
1395 }
1396
1397 Microsoft::WRL::ComPtr<IDXGISwapChainMedia>
GetSwapChainMedia() const1398 SwapChainPresenter::GetSwapChainMedia() const {
1399 Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media;
1400 HRESULT hr = 0;
1401 if (decode_swap_chain_) {
1402 hr = decode_swap_chain_.As(&swap_chain_media);
1403 } else {
1404 DCHECK(swap_chain_);
1405 hr = swap_chain_.As(&swap_chain_media);
1406 }
1407 if (SUCCEEDED(hr))
1408 return swap_chain_media;
1409 return nullptr;
1410 }
1411
1412 } // namespace gl
1413