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, &copy_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