// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ui/gl/swap_chain_presenter.h"
#include <d3d11_1.h>
#include <d3d11_4.h>
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
#include "ui/gfx/color_space_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gl/dc_layer_overlay_image.h"
#include "ui/gl/dc_layer_tree.h"
#include "ui/gl/debug_utils.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/hdr_metadata_helper_win.h"
namespace gl {
namespace {
// When in BGRA888 overlay format, wait for this time delta before retrying
// YUV format.
constexpr base::TimeDelta kDelayForRetryingYUVFormat = base::Minutes(10);
// Some drivers fail to correctly handle BT.709 video in overlays. This flag
// converts them to BT.601 in the video processor.
BASE_FEATURE(kFallbackBT709VideoToBT601,
"FallbackBT709VideoToBT601",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kDisableVPBLTUpscale,
"DisableVPBLTUpscale",
base::FEATURE_DISABLED_BY_DEFAULT);
gfx::ColorSpace GetOutputColorSpace(const gfx::ColorSpace& input_color_space,
bool is_yuv_swapchain) {
gfx::ColorSpace output_color_space =
is_yuv_swapchain ? input_color_space : gfx::ColorSpace::CreateSRGB();
if (base::FeatureList::IsEnabled(kFallbackBT709VideoToBT601) &&
(output_color_space == gfx::ColorSpace::CreateREC709())) {
output_color_space = gfx::ColorSpace::CreateREC601();
}
if (input_color_space.IsHDR()) {
output_color_space = gfx::ColorSpace::CreateHDR10();
}
return output_color_space;
}
bool IsProtectedVideo(gfx::ProtectedVideoType protected_video_type) {
return protected_video_type != gfx::ProtectedVideoType::kClear;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class OverlayFullScreenTypes {
kWindowMode,
kFullScreenMode,
kFullScreenInWidthOnly,
kFullScreenInHeightOnly,
kOverSizedFullScreen,
kNotAvailable,
kMaxValue = kNotAvailable,
};
enum : size_t {
kSwapChainImageIndex = 0,
kNV12ImageIndex = 0,
kYPlaneImageIndex = 0,
kUVPlaneImageIndex = 1,
};
const char* ProtectedVideoTypeToString(gfx::ProtectedVideoType type) {
switch (type) {
case gfx::ProtectedVideoType::kClear:
return "Clear";
case gfx::ProtectedVideoType::kSoftwareProtected:
if (DirectCompositionOverlaysSupported())
return "SoftwareProtected.HasOverlaySupport";
else
return "SoftwareProtected.NoOverlaySupport";
case gfx::ProtectedVideoType::kHardwareProtected:
return "HardwareProtected";
}
}
bool CreateSurfaceHandleHelper(HANDLE* handle) {
using PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE =
HRESULT(WINAPI*)(DWORD, SECURITY_ATTRIBUTES*, HANDLE*);
static PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE create_surface_handle_function =
nullptr;
if (!create_surface_handle_function) {
HMODULE dcomp = ::GetModuleHandleA("dcomp.dll");
if (!dcomp) {
DLOG(ERROR) << "Failed to get handle for dcomp.dll";
return false;
}
create_surface_handle_function =
reinterpret_cast<PFN_DCOMPOSITION_CREATE_SURFACE_HANDLE>(
::GetProcAddress(dcomp, "DCompositionCreateSurfaceHandle"));
if (!create_surface_handle_function) {
DLOG(ERROR)
<< "Failed to get address for DCompositionCreateSurfaceHandle";
return false;
}
}
HRESULT hr = create_surface_handle_function(COMPOSITIONOBJECT_ALL_ACCESS,
nullptr, handle);
if (FAILED(hr)) {
DLOG(ERROR) << "DCompositionCreateSurfaceHandle failed with error 0x"
<< std::hex << hr;
return false;
}
return true;
}
const char* DxgiFormatToString(DXGI_FORMAT format) {
// Please also modify histogram enum and trace integration tests if new
// formats are added.
switch (format) {
case DXGI_FORMAT_R10G10B10A2_UNORM:
return "RGB10A2";
case DXGI_FORMAT_B8G8R8A8_UNORM:
return "BGRA";
case DXGI_FORMAT_YUY2:
return "YUY2";
case DXGI_FORMAT_NV12:
return "NV12";
default:
NOTREACHED_IN_MIGRATION();
return "UNKNOWN";
}
}
bool IsYUVSwapChainFormat(DXGI_FORMAT format) {
if (format == DXGI_FORMAT_NV12 || format == DXGI_FORMAT_YUY2)
return true;
return false;
}
UINT BufferCount(bool force_triple_buffer) {
return force_triple_buffer || base::FeatureList::IsEnabled(
features::kDCompTripleBufferVideoSwapChain)
? 3u
: 2u;
}
// Transform is correct for scaling up |quad_rect| to on screen bounds, but
// doesn't include scaling transform from |swap_chain_size| to |quad_rect|.
// Since |swap_chain_size| could be equal to on screen bounds, and therefore
// possibly larger than |quad_rect|, this scaling could be downscaling, but
// only to the extent that it would cancel upscaling already in the transform.
void UpdateSwapChainTransform(const gfx::Size& quad_size,
const gfx::SizeF& swap_chain_size,
gfx::Transform* visual_transform) {
float swap_chain_scale_x = quad_size.width() * 1.0f / swap_chain_size.width();
float swap_chain_scale_y =
quad_size.height() * 1.0f / swap_chain_size.height();
visual_transform->Scale(swap_chain_scale_x, swap_chain_scale_y);
}
const GUID GUID_INTEL_VPE_INTERFACE = {
0xedd1d4b9,
0x8659,
0x4cbc,
{0xa4, 0xd6, 0x98, 0x31, 0xa2, 0x16, 0x3a, 0xc3}};
enum : UINT {
kIntelVpeFnVersion = 0x01,
kIntelVpeFnMode = 0x20,
kIntelVpeFnScaling = 0x37,
};
enum : UINT {
kIntelVpeVersion3 = 0x0003,
};
enum : UINT {
kIntelVpeModeNone = 0x0,
kIntelVpeModePreproc = 0x01,
};
enum : UINT {
kIntelVpeScalingDefault = 0x0,
kIntelVpeScalingSuperResolution = 0x2,
};
struct IntelVpeExt {
UINT function;
raw_ptr<void> param;
};
HRESULT ToggleIntelVpSuperResolution(ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool enable) {
TRACE_EVENT1("gpu", "ToggleIntelVpSuperResolution", "on", enable);
IntelVpeExt ext = {};
UINT param = 0;
ext.param = ¶m;
ext.function = kIntelVpeFnVersion;
param = kIntelVpeVersion3;
HRESULT hr = video_context->VideoProcessorSetOutputExtension(
video_processor, &GUID_INTEL_VPE_INTERFACE, sizeof(ext), &ext);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetOutputExtension failed with error 0x"
<< std::hex << hr;
return hr;
}
ext.function = kIntelVpeFnMode;
param = enable ? kIntelVpeModePreproc : kIntelVpeModeNone;
hr = video_context->VideoProcessorSetOutputExtension(
video_processor, &GUID_INTEL_VPE_INTERFACE, sizeof(ext), &ext);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetOutputExtension failed with error 0x"
<< std::hex << hr;
return hr;
}
ext.function = kIntelVpeFnScaling;
param = enable ? kIntelVpeScalingSuperResolution : kIntelVpeScalingDefault;
hr = video_context->VideoProcessorSetStreamExtension(
video_processor, 0, &GUID_INTEL_VPE_INTERFACE, sizeof(ext), &ext);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetStreamExtension failed with error 0x"
<< std::hex << hr;
}
return hr;
}
HRESULT ToggleNvidiaVpSuperResolution(ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool enable) {
TRACE_EVENT1("gpu", "ToggleNvidiaVpSuperResolution", "on", enable);
constexpr GUID kNvidiaPPEInterfaceGUID = {
0xd43ce1b3,
0x1f4b,
0x48ac,
{0xba, 0xee, 0xc3, 0xc2, 0x53, 0x75, 0xe6, 0xf7}};
constexpr UINT kStreamExtensionVersionV1 = 0x1;
constexpr UINT kStreamExtensionMethodSuperResolution = 0x2;
struct {
UINT version;
UINT method;
UINT enable;
} stream_extension_info = {kStreamExtensionVersionV1,
kStreamExtensionMethodSuperResolution,
enable ? 1u : 0u};
HRESULT hr = video_context->VideoProcessorSetStreamExtension(
video_processor, 0, &kNvidiaPPEInterfaceGUID,
sizeof(stream_extension_info), &stream_extension_info);
base::UmaHistogramSparse(enable
? "GPU.NvidiaVpSuperResolution.On.SetStreamExt"
: "GPU.NvidiaVpSuperResolution.Off.SetStreamExt",
hr);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetStreamExtension failed with error 0x"
<< std::hex << hr;
}
return hr;
}
HRESULT ToggleVpSuperResolution(UINT gpu_vendor_id,
ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool enable) {
if (gpu_vendor_id == 0x8086 &&
base::FeatureList::IsEnabled(features::kIntelVpSuperResolution)) {
return ToggleIntelVpSuperResolution(video_context, video_processor, enable);
}
if (gpu_vendor_id == 0x10de &&
base::FeatureList::IsEnabled(features::kNvidiaVpSuperResolution)) {
return ToggleNvidiaVpSuperResolution(video_context, video_processor,
enable);
}
return E_NOTIMPL;
}
constexpr GUID kNvidiaTrueHDRInterfaceGUID = {
0xfdd62bb4,
0x620b,
0x4fd7,
{0x9a, 0xb3, 0x1e, 0x59, 0xd0, 0xd5, 0x44, 0xb3}};
bool NvidiaDriverSupportsTrueHDR(ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor) {
UINT driver_supports_true_hdr = 0;
HRESULT hr = video_context->VideoProcessorGetStreamExtension(
video_processor, 0, &kNvidiaTrueHDRInterfaceGUID,
sizeof(driver_supports_true_hdr), &driver_supports_true_hdr);
// The runtime never fails the GetStreamExtension hr unless a bad memory size
// is provided.
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorGetStreamExtension failed with error 0x"
<< std::hex << hr;
return false;
}
return (driver_supports_true_hdr == 1);
}
bool GpuDriverSupportsVpAutoHDR(UINT gpu_vendor_id,
ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor) {
if (gpu_vendor_id == 0x10de) {
return NvidiaDriverSupportsTrueHDR(video_context, video_processor);
}
return false;
}
HRESULT ToggleNvidiaVpTrueHDR(bool driver_supports_vp_auto_hdr,
ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool enable) {
TRACE_EVENT1("gpu", "ToggleNvidiaVpTrueHDR", "on", enable);
if (enable && !driver_supports_vp_auto_hdr) {
return E_NOTIMPL;
}
constexpr UINT kStreamExtensionVersionV4 = 0x4;
constexpr UINT kStreamExtensionMethodTrueHDR = 0x3;
struct {
UINT version;
UINT method;
UINT enable : 1;
UINT reserved : 31;
} stream_extension_info = {kStreamExtensionVersionV4,
kStreamExtensionMethodTrueHDR, enable ? 1u : 0u,
0u};
HRESULT hr = video_context->VideoProcessorSetStreamExtension(
video_processor, 0, &kNvidiaTrueHDRInterfaceGUID,
sizeof(stream_extension_info), &stream_extension_info);
base::UmaHistogramSparse(enable ? "GPU.NvidiaVpTrueHDR.On.SetStreamExt"
: "GPU.NvidiaVpTrueHDR.Off.SetStreamExt",
hr);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetStreamExtension failed with error 0x"
<< std::hex << hr;
}
return hr;
}
HRESULT ToggleVpAutoHDR(UINT gpu_vendor_id,
bool driver_supports_vp_auto_hdr,
ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool enable) {
if (gpu_vendor_id == 0x10de) {
return ToggleNvidiaVpTrueHDR(driver_supports_vp_auto_hdr, video_context,
video_processor, enable);
}
return E_NOTIMPL;
}
bool IsVpAutoHDREnabled(UINT gpu_vendor_id) {
if (gpu_vendor_id == 0x10de &&
base::FeatureList::IsEnabled(features::kNvidiaVpTrueHDR)) {
return true;
}
return false;
}
bool IsWithinMargin(float i, float j) {
constexpr float kFullScreenMargin = 10.0;
return (std::abs(i - j) < kFullScreenMargin);
}
// Try disabling the topmost desktop plane for a decode swap chain in the case
// of full screen. Otherwise, swap chain size is used to set destination size
// and target rectangle for the decode swap chain. In DWM, the desktop plane
// can be turned off if the letterboxing info is set up properly for YUV
// swapchains, meaning that when the size of the window and the size of the
// monitor are the same and there is no other UI component overtop of the
// video. Otherwise, set the letterboxing info with swap chain size in order
// to restore the topmost desktop plane, which happens in scenarios like
// switching to underlay.
// Returns true on successful settings.
bool TryDisableDesktopPlane(IDXGIDecodeSwapChain* decode_swap_chain,
const gfx::Size& dest_size,
const gfx::Rect& target_rect) {
// Get the original dest size in case of restoring.
uint32_t original_dest_width, original_dest_height;
HRESULT hr = decode_swap_chain->GetDestSize(&original_dest_width,
&original_dest_height);
if (FAILED(hr)) {
DLOG(ERROR) << "GetDestSize failed with error 0x" << std::hex << hr;
return false;
}
// Set the destination surface size if necessary.
if (dest_size.width() != (int)original_dest_width ||
dest_size.height() != (int)original_dest_height) {
hr = decode_swap_chain->SetDestSize(dest_size.width(), dest_size.height());
if (FAILED(hr)) {
DLOG(ERROR) << "SetDestSize failed with error 0x" << std::hex << hr;
return false;
}
}
// Get the original target rect in case of restoring.
RECT original_target_rect;
hr = decode_swap_chain->GetTargetRect(&original_target_rect);
if (FAILED(hr)) {
DLOG(ERROR) << "GetTargetRect failed with error 0x" << std::hex << hr;
decode_swap_chain->SetDestSize(original_dest_width, original_dest_height);
return false;
}
// Set the target region to the specified rectangle if necessary.
RECT target_region = target_rect.ToRECT();
if (target_region != original_target_rect) {
hr = decode_swap_chain->SetTargetRect(&target_region);
if (FAILED(hr)) {
DLOG(ERROR) << "SetTargetRect failed with error 0x" << std::hex << hr;
decode_swap_chain->SetDestSize(original_dest_width, original_dest_height);
decode_swap_chain->SetTargetRect(&original_target_rect);
return false;
}
}
return true;
}
} // namespace
SwapChainPresenter::PresentationHistory::PresentationHistory() = default;
SwapChainPresenter::PresentationHistory::~PresentationHistory() = default;
void SwapChainPresenter::PresentationHistory::AddSample(
DXGI_FRAME_PRESENTATION_MODE mode) {
if (mode == DXGI_FRAME_PRESENTATION_MODE_COMPOSED)
composed_count_++;
presents_.push_back(mode);
if (presents_.size() > kPresentsToStore) {
DXGI_FRAME_PRESENTATION_MODE first_mode = presents_.front();
if (first_mode == DXGI_FRAME_PRESENTATION_MODE_COMPOSED)
composed_count_--;
presents_.pop_front();
}
}
void SwapChainPresenter::PresentationHistory::Clear() {
presents_.clear();
composed_count_ = 0;
}
bool SwapChainPresenter::PresentationHistory::Valid() const {
return presents_.size() >= kPresentsToStore;
}
int SwapChainPresenter::PresentationHistory::composed_count() const {
return composed_count_;
}
SwapChainPresenter::SwapChainPresenter(
DCLayerTree* layer_tree,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device)
: layer_tree_(layer_tree),
swap_chain_buffer_count_(BufferCount(
layer_tree->force_dcomp_triple_buffer_video_swap_chain())),
switched_to_BGRA8888_time_tick_(base::TimeTicks::Now()),
d3d11_device_(d3d11_device),
dcomp_device_(dcomp_device),
is_on_battery_power_(
base::PowerMonitor::AddPowerStateObserverAndReturnOnBatteryState(
this)) {}
SwapChainPresenter::~SwapChainPresenter() {
base::PowerMonitor::RemovePowerStateObserver(this);
}
DXGI_FORMAT SwapChainPresenter::GetSwapChainFormat(
gfx::ProtectedVideoType protected_video_type,
bool content_is_hdr) {
// Prefer RGB10A2 swapchain when playing HDR content.
// Only use rgb10a2 overlay when the hdr monitor is available.
if (content_is_hdr && DirectCompositionSystemHDREnabled()) {
return DXGI_FORMAT_R10G10B10A2_UNORM;
}
DXGI_FORMAT yuv_overlay_format = GetDirectCompositionSDROverlayFormat();
// Always prefer YUV swap chain for hardware protected video for now.
if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected)
return yuv_overlay_format;
if (failed_to_create_yuv_swapchain_ ||
!DirectCompositionHardwareOverlaysSupported()) {
return DXGI_FORMAT_B8G8R8A8_UNORM;
}
// Start out as YUV.
if (!presentation_history_.Valid())
return yuv_overlay_format;
int composition_count = presentation_history_.composed_count();
// It's more efficient to use a BGRA backbuffer instead of YUV if overlays
// aren't being used, as otherwise DWM will use the video processor a second
// time to convert it to BGRA before displaying it on screen.
if (swap_chain_format_ == yuv_overlay_format) {
// Switch to BGRA once 3/4 of presents are composed.
if (composition_count >= (PresentationHistory::kPresentsToStore * 3 / 4)) {
switched_to_BGRA8888_time_tick_ = base::TimeTicks::Now();
return DXGI_FORMAT_B8G8R8A8_UNORM;
}
} else {
// To prevent it from switching back and forth between YUV and BGRA8888,
// Wait for at least 10 minutes before we re-try YUV. On a system that
// can promote BGRA8888 but not YUV, the format change might cause
// flickers.
base::TimeDelta time_delta =
base::TimeTicks::Now() - switched_to_BGRA8888_time_tick_;
if (time_delta >= kDelayForRetryingYUVFormat) {
presentation_history_.Clear();
return yuv_overlay_format;
}
}
return swap_chain_format_;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> SwapChainPresenter::UploadVideoImage(
const gfx::Size& texture_size,
const uint8_t* nv12_pixmap,
size_t pixmap_stride) {
if (!nv12_pixmap) {
DLOG(ERROR) << "Invalid NV12 pixmap data.";
return nullptr;
}
if (texture_size.width() % 2 != 0 || texture_size.height() % 2 != 0) {
DLOG(ERROR) << "Invalid NV12 pixmap size.";
return nullptr;
}
if (pixmap_stride < static_cast<size_t>(texture_size.width())) {
DLOG(ERROR) << "Invalid NV12 pixmap stride.";
return nullptr;
}
TRACE_EVENT1("gpu", "SwapChainPresenter::UploadVideoImage", "size",
texture_size.ToString());
bool use_dynamic_texture = !layer_tree_->disable_nv12_dynamic_textures();
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = texture_size.width();
desc.Height = texture_size.height();
desc.Format = DXGI_FORMAT_NV12;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Usage = use_dynamic_texture ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_STAGING;
// This isn't actually bound to a decoder, but dynamic textures need
// BindFlags to be nonzero and D3D11_BIND_DECODER also works when creating
// a VideoProcessorInputView.
desc.BindFlags = use_dynamic_texture ? D3D11_BIND_DECODER : 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
desc.SampleDesc.Count = 1;
if (!staging_texture_ || (staging_texture_size_ != texture_size)) {
staging_texture_.Reset();
copy_texture_.Reset();
HRESULT hr =
d3d11_device_->CreateTexture2D(&desc, nullptr, &staging_texture_);
if (FAILED(hr)) {
DLOG(ERROR) << "Creating D3D11 video staging texture failed: " << std::hex
<< hr;
DisableDirectCompositionOverlays();
return nullptr;
}
DCHECK(staging_texture_);
staging_texture_size_ = texture_size;
hr = SetDebugName(staging_texture_.Get(), "SwapChainPresenter_Staging");
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to label D3D11 texture: " << std::hex << hr;
}
}
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device_->GetImmediateContext(&context);
DCHECK(context);
D3D11_MAP map_type =
use_dynamic_texture ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_WRITE;
D3D11_MAPPED_SUBRESOURCE mapped_resource;
HRESULT hr =
context->Map(staging_texture_.Get(), 0, map_type, 0, &mapped_resource);
if (FAILED(hr)) {
DLOG(ERROR) << "Mapping D3D11 video staging texture failed: " << std::hex
<< hr;
return nullptr;
}
size_t dest_stride = mapped_resource.RowPitch;
DCHECK_GE(dest_stride, static_cast<size_t>(texture_size.width()));
for (int y = 0; y < texture_size.height(); y++) {
const uint8_t* src_row = nv12_pixmap + pixmap_stride * y;
uint8_t* dest_row =
reinterpret_cast<uint8_t*>(mapped_resource.pData) + dest_stride * y;
memcpy(dest_row, src_row, texture_size.width());
}
const uint8_t* uv_src_start =
nv12_pixmap + pixmap_stride * texture_size.height();
uint8_t* uv_dst_start = reinterpret_cast<uint8_t*>(mapped_resource.pData) +
dest_stride * texture_size.height();
for (int y = 0; y < texture_size.height() / 2; y++) {
const uint8_t* src_row = uv_src_start + pixmap_stride * y;
uint8_t* dest_row = uv_dst_start + dest_stride * y;
memcpy(dest_row, src_row, texture_size.width());
}
context->Unmap(staging_texture_.Get(), 0);
if (use_dynamic_texture)
return staging_texture_;
if (!copy_texture_) {
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_DECODER;
desc.CPUAccessFlags = 0;
hr = d3d11_device_->CreateTexture2D(&desc, nullptr, ©_texture_);
if (FAILED(hr)) {
DLOG(ERROR) << "Creating D3D11 video upload texture failed: " << std::hex
<< hr;
DisableDirectCompositionOverlays();
return nullptr;
}
DCHECK(copy_texture_);
hr = SetDebugName(copy_texture_.Get(), "SwapChainPresenter_Copy");
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to label D3D11 texture: " << std::hex << hr;
}
}
TRACE_EVENT0("gpu", "SwapChainPresenter::UploadVideoImages::CopyResource");
context->CopyResource(copy_texture_.Get(), staging_texture_.Get());
return copy_texture_;
}
gfx::Size SwapChainPresenter::GetMonitorSize() const {
if (GetDirectCompositionNumMonitors() == 1) {
// Only one monitor. Return the size of this monitor.
return GetDirectCompositionPrimaryMonitorSize();
} else {
gfx::Size monitor_size;
// Get the monitor on which the overlay is displayed.
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(monitor_info);
if (GetMonitorInfo(
MonitorFromWindow(layer_tree_->window(), MONITOR_DEFAULTTONEAREST),
&monitor_info)) {
monitor_size = gfx::Rect(monitor_info.rcMonitor).size();
}
return monitor_size;
}
}
void SwapChainPresenter::SetTargetToFullScreen(
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect,
const std::optional<gfx::Rect>& target_rect) {
if (base::FeatureList::IsEnabled(kDisableVPBLTUpscale) &&
(std::abs(visual_transform->rc(0, 0)) > 1.0f) &&
(std::abs(visual_transform->rc(1, 1)) > 1.0f) &&
target_rect.has_value()) {
// Reset the horizontal/vertical shift according to the target_rect and
// original transform, since DWM will do the positioning in case of overlay.
visual_transform->set_rc(
0, 3,
visual_transform->rc(0, 3) -
target_rect.value().x() * visual_transform->rc(0, 0));
visual_transform->set_rc(
1, 3,
visual_transform->rc(1, 3) -
target_rect.value().y() * visual_transform->rc(1, 1));
} else {
// Reset the horizontal/vertical shift according to the visual clip and
// original transform, since DWM will do the positioning in case of overlay.
visual_transform->set_rc(
0, 3,
visual_clip_rect->x() -
visual_transform->rc(0, 3) * visual_transform->rc(0, 0));
visual_transform->set_rc(
1, 3,
visual_clip_rect->y() -
visual_transform->rc(1, 3) * visual_transform->rc(1, 1));
}
// Expand the clip rect for swap chain to the whole screen.
*visual_clip_rect = gfx::Rect(GetMonitorSize());
last_desktop_plane_removed_ = true;
}
void SwapChainPresenter::AdjustTargetToOptimalSizeIfNeeded(
const DCLayerOverlayParams& params,
const gfx::RectF& overlay_onscreen_rect,
gfx::SizeF* swap_chain_size,
gfx::Transform* visual_transform,
gfx::RectF* visual_clip_rect,
std::optional<gfx::SizeF>* dest_size,
std::optional<gfx::RectF>* target_rect) const {
// First try to adjust the full screen overlay that can fit the whole
// screen. If it cannot fit the whole screen and we know it's in
// letterboxing mode, try to center the overlay and adjust only x or only y.
gfx::Size monitor_size = GetMonitorSize();
gfx::SizeF monitor_size_float(monitor_size.width(), monitor_size.height());
bool size_adjusted = AdjustTargetToFullScreenSizeIfNeeded(
monitor_size_float, params, overlay_onscreen_rect, swap_chain_size,
visual_transform, visual_clip_rect);
// Adjustment for the full screen letterboxing scenario.
if (!size_adjusted &&
params.video_params.possible_video_fullscreen_letterboxing) {
AdjustTargetForFullScreenLetterboxing(
monitor_size_float, params, overlay_onscreen_rect, swap_chain_size,
visual_transform, visual_clip_rect, dest_size, target_rect);
}
}
bool SwapChainPresenter::AdjustTargetToFullScreenSizeIfNeeded(
const gfx::SizeF& monitor_size,
const DCLayerOverlayParams& params,
const gfx::RectF& overlay_onscreen_rect,
gfx::SizeF* swap_chain_size,
gfx::Transform* visual_transform,
gfx::RectF* visual_clip_rect) const {
if (monitor_size.IsEmpty()) {
return false;
}
gfx::RectF clipped_onscreen_rect = overlay_onscreen_rect;
if (params.clip_rect.has_value()) {
clipped_onscreen_rect.Intersect(*visual_clip_rect);
}
// Skip adjustment if the current swap chain size is already correct.
if (clipped_onscreen_rect == gfx::RectF(monitor_size) &&
overlay_onscreen_rect == gfx::RectF(monitor_size)) {
return true;
}
// Because of the rounding when converting between pixels and DIPs, a
// fullscreen video can become slightly larger than the monitor - e.g. on
// a 3000x2000 monitor with a scale factor of 1.75 a 1920x1079 video can
// become 3002x1689.
// Swapchains that are bigger than the monitor won't be put into overlays,
// which will hurt power usage a lot. On those systems, the scaling can be
// adjusted very slightly so that it's less than the monitor size. This
// should be close to imperceptible. http://crbug.com/668278
// The overlay must be positioned at (0, 0) in fullscreen mode.
if (!IsWithinMargin(clipped_onscreen_rect.x(), 0.0) ||
!IsWithinMargin(clipped_onscreen_rect.y(), 0.0)) {
// Not fullscreen mode.
return false;
}
// Check whether the on-screen overlay is near the full screen size.
// If yes, adjust the overlay size so it can fit the screen. This allows the
// application of fullscreen optimizations like dynamic backlighting or
// dynamic refresh rates (24hz/48hz). Note: The DWM optimizations works for
// both hardware and software overlays.
// If no, do nothing.
if (!IsWithinMargin(clipped_onscreen_rect.width(), monitor_size.width()) ||
!IsWithinMargin(clipped_onscreen_rect.height(), monitor_size.height())) {
// Not fullscreen mode.
return false;
}
// For most video playbacks, |clip_rect| is the same as
// |overlay_onscreen_rect| or close to it. If |clipped_onscreen_rect| has the
// size of the monitor but |overlay_onscreen_rect| is much bigger than the
// monitor size, we don't get the benefit of this optimization in this case.
// We should do nothing here. e.g. |overlay_onscreen_rect| is ~7680 x 4320 and
// it's clipped to ~3840 x 2160 to fit the monitor. Check
// |overlay_onscreen_rect| only if it's different from |clipped_onscreen_rect|
// when clipping is enabled. https://crbug.com/1213035
if (params.clip_rect.has_value()) {
if (!IsWithinMargin(overlay_onscreen_rect.width(), monitor_size.width()) ||
!IsWithinMargin(overlay_onscreen_rect.height(),
monitor_size.height())) {
return false;
}
}
//
// Adjust the clip rect.
//
if (params.clip_rect.has_value()) {
*visual_clip_rect = gfx::RectF(monitor_size);
}
//
// Adjust the swap chain size if needed.
//
// Change the swap chain size so the scaling is performed by video processor.
// Make the final |visual_transform| after this function an Identity if
// possible.
// The swap chain is either the size of overlay_onscreen_rect or
// min(overlay_onscreen_rect, content_rect). The swap chain might not need to
// be updated if it's the content size.
// |visual_transform| transforms the swap chain to the on-screen rect.
// (See UpdateSwapChainTransform() in CalculateSwapChainSize().) Now update
// |visual_transform| so it still produces the same on-screen rect
// after changing the swapchain.
float scale_x;
float scale_y;
if (*swap_chain_size == overlay_onscreen_rect.size()) {
scale_x = swap_chain_size->width() * 1.0f / monitor_size.width();
scale_y = swap_chain_size->height() * 1.0f / monitor_size.height();
visual_transform->Scale(scale_x, scale_y);
*swap_chain_size = monitor_size;
}
//
// Adjust the transform matrix.
//
// Add the new scale that scales |overlay_onscreen_rect| to |monitor_size|.
// The new |visual_transform| will produce a rect of the monitor size.
scale_x = monitor_size.width() * 1.0f / overlay_onscreen_rect.width();
scale_y = monitor_size.height() * 1.0f / overlay_onscreen_rect.height();
visual_transform->Scale(scale_x, scale_y);
// Origin is probably (0,0) all the time. If not, adjust the origin.
gfx::RectF unmapped_rect =
gfx::RectF(params.quad_rect.x(), params.quad_rect.y(),
swap_chain_size->width(), swap_chain_size->height());
gfx::RectF mapped_rect = visual_transform->MapRect(unmapped_rect);
visual_transform->PostTranslate(-mapped_rect.OffsetFromOrigin());
#if DCHECK_IS_ON()
// Verify if the new transform matrix transforms the swap chain to the
// monitor rect.
gfx::RectF new_rect = visual_transform->MapRect(unmapped_rect);
if (params.clip_rect.has_value()) {
new_rect.Intersect(*visual_clip_rect);
}
// https://crbug.com/1517344: "DCHECK_EQ(new_rect, gfx::RectF(monitor_size))"
// sometimes failed in the field. But here we collect possible crashes in
// general.
base::debug::Alias(&visual_transform);
base::debug::Alias(&new_rect);
// Here we use 0.01f as the check tolerance for floating-point numbers, since
// eventually the size adjustment for overlay will be rounded to be integral.
constexpr float kTolerance = 0.01f;
bool sufficiently_equal = new_rect.ApproximatelyEqual(
gfx::RectF(monitor_size), kTolerance, kTolerance);
DCHECK(sufficiently_equal)
<< ", params.quad_rect: " << params.quad_rect.ToString()
<< ", params.content_rect: " << params.content_rect.ToString()
<< ", clipped_onscreen_rect: " << clipped_onscreen_rect.ToString()
<< ", overlay_onscreen_rect: " << overlay_onscreen_rect.ToString()
<< ", params.transform: " << params.transform.ToString()
<< ", visual_transform: " << visual_transform->ToString();
#endif
return true;
}
void SwapChainPresenter::AdjustTargetForFullScreenLetterboxing(
const gfx::SizeF& monitor_size,
const DCLayerOverlayParams& params,
const gfx::RectF& overlay_onscreen_rect,
gfx::SizeF* swap_chain_size,
gfx::Transform* visual_transform,
gfx::RectF* visual_clip_rect,
std::optional<gfx::SizeF>* dest_size,
std::optional<gfx::RectF>* target_rect) const {
if (!base::FeatureList::IsEnabled(
features::kDirectCompositionLetterboxVideoOptimization)) {
return;
}
if (monitor_size.IsEmpty()) {
return;
}
gfx::RectF clipped_onscreen_rect = overlay_onscreen_rect;
if (params.clip_rect.has_value()) {
clipped_onscreen_rect.Intersect(*visual_clip_rect);
}
bool is_onscreen_rect_x_near_0 =
IsWithinMargin(clipped_onscreen_rect.x(), 0.0);
bool is_onscreen_rect_y_near_0 =
IsWithinMargin(clipped_onscreen_rect.y(), 0.0);
if (!is_onscreen_rect_x_near_0 && !is_onscreen_rect_y_near_0) {
// Not fullscreen letterboxing mode.
return;
}
if (!IsWithinMargin(clipped_onscreen_rect.width(), monitor_size.width()) &&
!IsWithinMargin(clipped_onscreen_rect.height(), monitor_size.height())) {
// Not fullscreen letterboxing mode.
return;
}
// Scrolling down during video fullscreen letterboxing will change the
// position of the whole clipped_onscreen_rect, which makes it not cover
// the whole screen with its black bar surroundings. In this case, the
// adjustment should be stopped. (http://crbug.com/1371976)
if (is_onscreen_rect_x_near_0 &&
!IsWithinMargin(
clipped_onscreen_rect.y() * 2.0 + clipped_onscreen_rect.height(),
monitor_size.height())) {
// Not fullscreen letterboxing mode.
return;
}
if (is_onscreen_rect_y_near_0 &&
!IsWithinMargin(
clipped_onscreen_rect.x() * 2.0 + clipped_onscreen_rect.width(),
monitor_size.width())) {
// Not fullscreen letterboxing mode.
return;
}
if (params.clip_rect.has_value()) {
if (is_onscreen_rect_x_near_0 &&
!IsWithinMargin(overlay_onscreen_rect.width(), monitor_size.width())) {
// Not fullscreen letterboxing mode.
return;
}
if (is_onscreen_rect_y_near_0 &&
!IsWithinMargin(overlay_onscreen_rect.height(),
monitor_size.height())) {
// Not fullscreen letterboxing mode.
return;
}
}
//
// Adjust the on-screen rect.
//
// Make sure the on-screen rect touches both the screen borders, and the
// on-screen rect is right in the center. At the same time, make sure the
// origin position for |new_onscreen_rect| with round-up integer so that no
// extra blank bar shows up.
gfx::Rect new_onscreen_rect = gfx::ToNearestRect(clipped_onscreen_rect);
if (is_onscreen_rect_x_near_0) {
new_onscreen_rect.set_x(0);
new_onscreen_rect.set_width(monitor_size.width());
int new_y = (monitor_size.height() - new_onscreen_rect.height()) / 2;
if (new_y < new_onscreen_rect.y()) {
// If new_onscreen_rect needs to be moved up by n lines, we add n
// lines to the video onscreen rect height.
new_onscreen_rect.set_height(new_onscreen_rect.height() +
new_onscreen_rect.y() - new_y);
new_onscreen_rect.set_y(new_y);
} else if (new_y > new_onscreen_rect.y()) {
// If new_onscreen_rect needs to be moved down by n lines, we keep
// the original point of the video onscreen rect. Meanwhile, increase its
// size to make it symmetrical around the monitor center.
new_onscreen_rect.set_height(monitor_size.height() -
new_onscreen_rect.y() * 2);
}
// Make new_onscreen_rect height even.
if (new_onscreen_rect.height() % 2 == 1) {
new_onscreen_rect.set_height(new_onscreen_rect.height() + 1);
}
}
if (is_onscreen_rect_y_near_0) {
new_onscreen_rect.set_y(0);
new_onscreen_rect.set_height(monitor_size.height());
int new_x = (monitor_size.width() - new_onscreen_rect.width()) / 2;
if (new_x < new_onscreen_rect.x()) {
// If new_onscreen_rect needs to be moved left by n lines, we add n
// lines to the video onscreen rect width.
new_onscreen_rect.set_width(new_onscreen_rect.width() +
new_onscreen_rect.x() - new_x);
new_onscreen_rect.set_x(new_x);
} else if (new_x > new_onscreen_rect.x()) {
// If new_onscreen_rect needs to be moved right by n lines, we keep
// the original point of the video onscreen rect. Meanwhile, increase its
// size to make it symmetrical around the monitor center.
new_onscreen_rect.set_width(monitor_size.width() -
new_onscreen_rect.x() * 2);
}
// Make new_onscreen_rect width even.
if (new_onscreen_rect.width() % 2 == 1) {
new_onscreen_rect.set_width(new_onscreen_rect.width() + 1);
}
}
gfx::RectF new_onscreen_rect_float = gfx::RectF(new_onscreen_rect);
// Skip adjustment if the current swap chain size is already correct.
if (new_onscreen_rect_float != clipped_onscreen_rect) {
//
// Adjust the clip rect.
//
if (params.clip_rect.has_value()) {
*visual_clip_rect = new_onscreen_rect_float;
}
//
// Adjust the swap chain size if needed.
//
// The swap chain is either the size of overlay_onscreen_rect or
// min(overlay_onscreen_rect, content_rect). The swap chain might not need
// to be updated if it's the content size. After UpdateSwapChainTransform()
// in CalculateSwapChainSize(), |visual_transform| transforms the swap
// chain to the on-screen rect. Now update |visual_transform| so it still
// produces the same on-screen rect after changing the swapchain.
float scale_x;
float scale_y;
if (*swap_chain_size == overlay_onscreen_rect.size()) {
scale_x =
swap_chain_size->width() * 1.0f / new_onscreen_rect_float.width();
scale_y =
swap_chain_size->height() * 1.0f / new_onscreen_rect_float.height();
visual_transform->Scale(scale_x, scale_y);
*swap_chain_size = new_onscreen_rect_float.size();
}
//
// Adjust the transform matrix.
//
// Add the new scale that scales |overlay_onscreen_rect| to
// |new_onscreen_rect|. The new |visual_transform| will produce a new width
// or a new height of the monitor size.
scale_x =
new_onscreen_rect_float.width() * 1.0f / overlay_onscreen_rect.width();
scale_y = new_onscreen_rect_float.height() * 1.0f /
overlay_onscreen_rect.height();
visual_transform->Scale(scale_x, scale_y);
// Update the origin.
gfx::RectF unmapped_rect = gfx::RectF(
gfx::PointF(params.quad_rect.origin()), gfx::SizeF(*swap_chain_size));
gfx::RectF mapped_rect = visual_transform->MapRect(unmapped_rect);
auto offset = new_onscreen_rect_float.OffsetFromOrigin() -
mapped_rect.OffsetFromOrigin();
visual_transform->PostTranslate(offset);
}
// Full screen letterboxing overlay scenario can be optimized by DWM, like to
// turn off the topmost desktop plane to save power.
// Here the destination surface size is set to the whole monitor, while the
// target region is set to the visual clip rectangle on the screen.
if (params.z_order > 0) {
if (base::FeatureList::IsEnabled(kDisableVPBLTUpscale) &&
(std::abs(visual_transform->rc(0, 0)) > 1.0f) &&
(std::abs(visual_transform->rc(1, 1)) > 1.0f)) {
// Since DWM will perform the transform scaling on dest_size/target_rect
// when display, so the inverse scaling ratio should be applied in the
// process of calculating dest_size/target_rect than directly using
// the monitor size.
float inverse_scale_x = 1.0f / std::abs(visual_transform->rc(0, 0));
float inverse_scale_y = 1.0f / std::abs(visual_transform->rc(1, 1));
*dest_size =
gfx::ScaleSize(monitor_size, inverse_scale_x, inverse_scale_y);
*target_rect =
gfx::ScaleRect(*visual_clip_rect, inverse_scale_x, inverse_scale_y);
} else {
*dest_size = monitor_size;
*target_rect = *visual_clip_rect;
}
} else {
// For underlay scenario, keep the destination surface size and target
// region according to swap chain size.
*dest_size = *swap_chain_size;
*target_rect = gfx::RectF(*swap_chain_size);
}
#if DCHECK_IS_ON()
{
// Verify if the new transform matrix transforms the swap chain correctly.
gfx::RectF new_swap_chain_rect = gfx::RectF(
gfx::PointF(params.quad_rect.origin()), gfx::SizeF(*swap_chain_size));
gfx::RectF result_rect = visual_transform->MapRect(new_swap_chain_rect);
if (params.clip_rect.has_value()) {
result_rect.Intersect(*visual_clip_rect);
}
gfx::RectF new_onscreen_rect_local = new_onscreen_rect_float;
// TODO(crbug.com/40866962): Remove these crash keys.
gfx::Transform new_visual_transform = *visual_transform;
base::debug::Alias(&new_swap_chain_rect);
base::debug::Alias(&result_rect);
base::debug::Alias(&new_onscreen_rect_local);
base::debug::Alias(&new_visual_transform);
// https://crbug.com/1366493: "DCHECK_EQ(result_rect.x(), 0);" sometimes
// failed in the field. But here we collect possible crashes in general.
// https://crbug.com/1517344 might also be triggered similarly.
static auto* new_swap_chain_rect_key = base::debug::AllocateCrashKeyString(
"new-swap-chain-rect", base::debug::CrashKeySize::Size256);
base::debug::ScopedCrashKeyString scoped_crash_key_1(
new_swap_chain_rect_key, new_swap_chain_rect.ToString());
static auto* visual_transform_key = base::debug::AllocateCrashKeyString(
"visual-transform", base::debug::CrashKeySize::Size256);
base::debug::ScopedCrashKeyString scoped_crash_key_2(
visual_transform_key, visual_transform->ToString());
static auto* result_rect_key = base::debug::AllocateCrashKeyString(
"result-rect", base::debug::CrashKeySize::Size256);
base::debug::ScopedCrashKeyString scoped_crash_key_3(
result_rect_key, result_rect.ToString());
// Here we use 0.01f as the check tolerance for floating-point numbers,
// since eventually the size adjustment for overlay will be rounded to be
// integral.
constexpr float kTolerance = 0.01f;
if (is_onscreen_rect_x_near_0) {
DCHECK_LE(std::abs(result_rect.x()), kTolerance);
DCHECK_LE(std::abs(result_rect.width() - monitor_size.width()),
kTolerance);
}
if (is_onscreen_rect_y_near_0) {
DCHECK_LE(std::abs(result_rect.y()), kTolerance);
DCHECK_LE(std::abs(result_rect.height() - monitor_size.height()),
kTolerance);
}
}
#endif
}
gfx::Size SwapChainPresenter::CalculateSwapChainSize(
const DCLayerOverlayParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect,
std::optional<gfx::Size>* dest_size,
std::optional<gfx::Rect>* target_rect) const {
gfx::RectF visual_clip_rect_float = gfx::RectF(*visual_clip_rect);
std::optional<gfx::SizeF> dest_size_float;
std::optional<gfx::RectF> target_rect_float;
// Swap chain size is the minimum of the on-screen size and the source size so
// the video processor can do the minimal amount of work and the overlay has
// to read the minimal amount of data. DWM is also less likely to promote a
// surface to an overlay if it's much larger than its area on-screen.
gfx::SizeF swap_chain_size = params.content_rect.size();
if (swap_chain_size.IsEmpty()) {
return gfx::Size();
}
if (params.quad_rect.IsEmpty()) {
return gfx::Size();
}
gfx::RectF quad_rect_float = gfx::RectF(params.quad_rect);
gfx::RectF overlay_onscreen_rect = visual_transform->MapRect(quad_rect_float);
// If transform isn't a scale or translation then swap chain can't be promoted
// to an overlay so avoid blitting to a large surface unnecessarily. Also,
// after the video rotation fix (crbug.com/904035), using rotated size for
// swap chain size will cause stretching since there's no squashing factor in
// the transform to counteract.
// Downscaling doesn't work on Intel display HW, and so DWM will perform an
// extra BLT to avoid HW downscaling. This prevents the use of hardware
// overlays especially for protected video. Use the onscreen size (scale==1)
// for overlay can avoid this problem.
// TODO(sunnyps): Support 90/180/270 deg rotations using video context.
// On battery_power mode, set swap_chain_size to the source content size when
// the swap chain presents upscaled overlay, multi-plane overlay hardware will
// perform an upscaling operation instead of video processor(VP). Disabling VP
// upscaled BLT is more power saving as the video processor can do the minimal
// amount of work and the overlay has to read the minimal amount of data.
bool can_disable_vp_upscaling_blt =
base::FeatureList::IsEnabled(kDisableVPBLTUpscale) &&
is_on_battery_power_ && std::abs(params.transform.rc(0, 0)) > 1.0f &&
std::abs(params.transform.rc(1, 1)) > 1.0f;
if (visual_transform->IsScaleOrTranslation() &&
!can_disable_vp_upscaling_blt) {
swap_chain_size = overlay_onscreen_rect.size();
}
// 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0
// subsampled formats like NV12 must have an even width and height..
gfx::Size swap_chain_size_rounded = gfx::ToRoundedSize(swap_chain_size);
if (swap_chain_size_rounded.width() % 2 == 1) {
swap_chain_size.set_width(swap_chain_size.width() + 1);
}
if (swap_chain_size_rounded.height() % 2 == 1) {
swap_chain_size.set_height(swap_chain_size.height() + 1);
}
// Adjust the transform matrix.
UpdateSwapChainTransform(params.quad_rect.size(), swap_chain_size,
visual_transform);
// In order to get the fullscreen DWM optimizations, the overlay onscreen rect
// must fit the monitor when in non-letterboxing fullscreen mode. Adjust
// |swap_chain_size|, |visual_transform| and |visual_clip_rect| so
// |overlay_onscreen_rect| is the same as the monitor rect.
// Specially for fullscreen overlays with letterboxing effect,
// |overlay_onscreen_rect| will be placed in the center of the screen, and
// either left/right edges or top/bottom edges will touch the monitor edges.
if (visual_transform->IsScaleOrTranslation()) {
AdjustTargetToOptimalSizeIfNeeded(
params, overlay_onscreen_rect, &swap_chain_size, visual_transform,
&visual_clip_rect_float, &dest_size_float, &target_rect_float);
*visual_clip_rect = gfx::ToNearestRect(visual_clip_rect_float);
if (target_rect_float.has_value()) {
gfx::RectF temp = target_rect_float.value();
*target_rect = gfx::ToNearestRect(temp);
}
if (dest_size_float.has_value()) {
gfx::SizeF temp = dest_size_float.value();
*dest_size = gfx::ToRoundedSize(temp);
}
}
return gfx::ToRoundedSize(swap_chain_size);
}
bool SwapChainPresenter::TryPresentToDecodeSwapChain(
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
unsigned array_slice,
const gfx::ColorSpace& color_space,
const gfx::Rect& content_rect,
const gfx::Size& swap_chain_size,
DXGI_FORMAT swap_chain_format,
const gfx::Transform& transform_to_root,
const std::optional<gfx::Size> dest_size,
const std::optional<gfx::Rect> target_rect) {
if (ShouldUseVideoProcessorScaling())
return false;
bool nv12_supported =
(swap_chain_format == DXGI_FORMAT_NV12) &&
(DXGI_FORMAT_NV12 == GetDirectCompositionSDROverlayFormat());
// TODO(sunnyps): Try using decode swap chain for uploaded video images.
if (texture && nv12_supported && !failed_to_present_decode_swapchain_) {
D3D11_TEXTURE2D_DESC texture_desc = {};
texture->GetDesc(&texture_desc);
bool is_decoder_texture = (texture_desc.Format == DXGI_FORMAT_NV12) &&
(texture_desc.BindFlags & D3D11_BIND_DECODER);
// Decode swap chains do not support shared resources.
// TODO(sunnyps): Find a workaround for when the decoder moves to its own
// thread and D3D device. See https://crbug.com/911847
bool is_shared_texture =
texture_desc.MiscFlags &
(D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX |
D3D11_RESOURCE_MISC_SHARED_NTHANDLE);
// DXVA decoder (or rather MFT) sometimes gives texture arrays with one
// element, which constitutes most of decode swap chain creation failures.
bool is_unitary_texture_array = texture_desc.ArraySize <= 1;
// Rotated videos are not promoted to overlays. We plan to implement
// rotation using video processor instead of via direct composition. Also
// check for skew and any downscaling specified to direct composition.
bool compatible_transform =
transform_to_root.IsPositiveScaleOrTranslation();
// Downscaled video isn't promoted to hardware overlays. We prefer to
// blit into the smaller size so that it can be promoted to a hardware
// overlay.
float swap_chain_scale_x =
swap_chain_size.width() * 1.0f / content_rect.width();
float swap_chain_scale_y =
swap_chain_size.height() * 1.0f / content_rect.height();
if (layer_tree_->no_downscaled_overlay_promotion()) {
compatible_transform = compatible_transform &&
(swap_chain_scale_x >= 1.0f) &&
(swap_chain_scale_y >= 1.0f);
}
if (!DirectCompositionScaledOverlaysSupported()) {
compatible_transform = compatible_transform &&
(swap_chain_scale_x == 1.0f) &&
(swap_chain_scale_y == 1.0f);
}
if (is_decoder_texture && !is_shared_texture && !is_unitary_texture_array &&
compatible_transform) {
if (PresentToDecodeSwapChain(texture, array_slice, color_space,
content_rect, swap_chain_size, dest_size,
target_rect)) {
return true;
}
ReleaseSwapChainResources();
failed_to_present_decode_swapchain_ = true;
DLOG(ERROR)
<< "Present to decode swap chain failed - falling back to blit";
}
}
return false;
}
bool SwapChainPresenter::PresentToDecodeSwapChain(
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture,
unsigned array_slice,
const gfx::ColorSpace& color_space,
const gfx::Rect& content_rect,
const gfx::Size& swap_chain_size,
const std::optional<gfx::Size> dest_size,
const std::optional<gfx::Rect> target_rect) {
DCHECK(!swap_chain_size.IsEmpty());
TRACE_EVENT2("gpu", "SwapChainPresenter::PresentToDecodeSwapChain",
"content_rect", content_rect.ToString(), "swap_chain_size",
swap_chain_size.ToString());
Microsoft::WRL::ComPtr<IDXGIResource> decode_resource;
texture.As(&decode_resource);
DCHECK(decode_resource);
HRESULT hr = S_OK;
if (!decode_swap_chain_ || decode_resource_ != decode_resource) {
TRACE_EVENT0(
"gpu",
"SwapChainPresenter::PresentToDecodeSwapChain::CreateDecodeSwapChain");
ReleaseSwapChainResources();
decode_resource_ = decode_resource;
HANDLE handle = INVALID_HANDLE_VALUE;
if (!CreateSurfaceHandleHelper(&handle))
return false;
swap_chain_handle_.Set(handle);
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
d3d11_device_.As(&dxgi_device);
DCHECK(dxgi_device);
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
dxgi_device->GetAdapter(&dxgi_adapter);
DCHECK(dxgi_adapter);
Microsoft::WRL::ComPtr<IDXGIFactoryMedia> media_factory;
dxgi_adapter->GetParent(IID_PPV_ARGS(&media_factory));
DCHECK(media_factory);
DXGI_DECODE_SWAP_CHAIN_DESC desc = {};
// Set the DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO flag to mark this surface
// as a candidate for full screen video optimizations. If the surface
// does not qualify as fullscreen by DWM's logic then the flag will have
// no effects.
desc.Flags = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
hr = media_factory->CreateDecodeSwapChainForCompositionSurfaceHandle(
d3d11_device_.Get(), swap_chain_handle_.Get(), &desc,
decode_resource_.Get(), nullptr, &decode_swap_chain_);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateDecodeSwapChainForCompositionSurfaceHandle failed "
"with error 0x"
<< std::hex << hr;
return false;
}
DCHECK(decode_swap_chain_);
SetSwapChainPresentDuration();
Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;
dcomp_device_.As(&desktop_device);
DCHECK(desktop_device);
hr = desktop_device->CreateSurfaceFromHandle(swap_chain_handle_.Get(),
&decode_surface_);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateSurfaceFromHandle failed with error 0x" << std::hex
<< hr;
return false;
}
DCHECK(decode_surface_);
content_ = decode_surface_.Get();
}
RECT source_rect = content_rect.ToRECT();
hr = decode_swap_chain_->SetSourceRect(&source_rect);
if (FAILED(hr)) {
DLOG(ERROR) << "SetSourceRect failed with error 0x" << std::hex << hr;
return false;
}
gfx::Size swap_chain_dest_size =
dest_size.has_value() ? dest_size.value() : swap_chain_size;
hr = decode_swap_chain_->SetDestSize(swap_chain_dest_size.width(),
swap_chain_dest_size.height());
if (FAILED(hr)) {
DLOG(ERROR) << "SetDestSize failed with error 0x" << std::hex << hr;
return false;
}
RECT swap_chain_target_rect = target_rect.has_value()
? (target_rect.value()).ToRECT()
: gfx::Rect(swap_chain_size).ToRECT();
hr = decode_swap_chain_->SetTargetRect(&swap_chain_target_rect);
if (FAILED(hr)) {
DLOG(ERROR) << "SetTargetRect failed with error 0x" << std::hex << hr;
return false;
}
// TODO(sunnyps): Move this to gfx::ColorSpaceWin helper where we can access
// internal color space state and do a better job.
// Common color spaces have primaries and transfer function similar to BT 709
// and there are no other choices anyway.
int color_space_flags = DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_BT709;
// Proper Rec 709 and 601 have limited or nominal color range.
if (color_space == gfx::ColorSpace::CreateREC709() ||
color_space == gfx::ColorSpace::CreateREC601() ||
!color_space.IsValid()) {
color_space_flags |= DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_NOMINAL_RANGE;
}
// xvYCC allows colors outside nominal range to encode negative colors that
// allows for a wider gamut.
if (color_space.FullRangeEncodedValues()) {
color_space_flags |= DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAG_xvYCC;
}
hr = decode_swap_chain_->SetColorSpace(
static_cast<DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS>(color_space_flags));
if (FAILED(hr)) {
DLOG(ERROR) << "SetColorSpace failed with error 0x" << std::hex << hr;
return false;
}
UINT present_flags = DXGI_PRESENT_USE_DURATION;
hr = decode_swap_chain_->PresentBuffer(array_slice, 1, present_flags);
// Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
// that the window is occluded and we can stop rendering.
if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
DLOG(ERROR) << "PresentBuffer failed with error 0x" << std::hex << hr;
return false;
}
swap_chain_size_ = swap_chain_size;
content_size_ = swap_chain_size;
swap_chain_format_ = DXGI_FORMAT_NV12;
RecordPresentationStatistics();
return true;
}
bool SwapChainPresenter::PresentToSwapChain(DCLayerOverlayParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
DCHECK(params.overlay_image);
DCHECK_NE(params.overlay_image->type(),
gl::DCLayerOverlayType::kDCompVisualContent);
CHECK(gfx::IsNearestRectWithinDistance(params.content_rect, 0.01f));
gl::DCLayerOverlayType overlay_type = params.overlay_image->type();
*visual_transform = params.transform;
*visual_clip_rect = params.clip_rect.value_or(gfx::Rect());
if (overlay_type == gl::DCLayerOverlayType::kDCompSurfaceProxy) {
return PresentDCOMPSurface(params, visual_transform, visual_clip_rect);
}
// SwapChainPresenter can be reused when switching between MediaFoundation
// (MF) video content and non-MF content; in such cases, the DirectComposition
// (DCOMP) surface handle associated with the MF content needs to be cleared.
// Doing so allows a DCOMP surface to be reset on the visual when MF
// content is shown again.
ReleaseDCOMPSurfaceResourcesIfNeeded();
// Optional |dest_size| and |target_rect| are only calculated for full screen
// letterboxing in |AdjustTargetForFullScreenLetterboxing|, which is guarded
// by flag of DirectCompositionLetterboxVideoOptimization for now.
std::optional<gfx::Size> dest_size;
std::optional<gfx::Rect> target_rect;
gfx::Size swap_chain_size = CalculateSwapChainSize(
params, visual_transform, visual_clip_rect, &dest_size, &target_rect);
if (overlay_type == gl::DCLayerOverlayType::kNV12Texture &&
!params.overlay_image->nv12_texture()) {
// We can't proceed if overlay image has no underlying d3d11 texture. It's
// unclear how we get into this state, but we do observe crashes due to it.
// Just stop here instead, and render incorrectly.
// https://crbug.com/1077645
DLOG(ERROR) << "Video NV12 texture is missing";
ReleaseSwapChainResources();
return true;
}
// Do not create a swap chain if swap chain size will be empty.
if (swap_chain_size.IsEmpty()) {
ReleaseSwapChainResources();
swap_chain_size_ = swap_chain_size;
content_size_ = swap_chain_size;
return true;
}
bool swap_chain_resized = swap_chain_size_ != swap_chain_size;
gfx::ColorSpace input_color_space = params.video_params.color_space;
if (!input_color_space.IsValid()) {
input_color_space = gfx::ColorSpace::CreateREC709();
}
bool content_is_hdr = input_color_space.IsHDR();
// Enable VideoProcessor-HDR for SDR content if the monitor supports it and
// the GPU driver version is not blocked (enable_vp_auto_hdr_). The actual GPU
// driver support will be queried right after InitializeVideoProcessor() and
// is checked in ToggleVpAutoHDR().
bool use_vp_auto_hdr =
!content_is_hdr &&
DirectCompositionMonitorHDREnabled(layer_tree_->window()) &&
enable_vp_auto_hdr_ && !is_on_battery_power_;
bool use_hdr_swap_chain =
((content_is_hdr && params.video_params.hdr_metadata.IsValid()) ||
use_vp_auto_hdr);
DXGI_FORMAT swap_chain_format = GetSwapChainFormat(
params.video_params.protected_video_type, use_hdr_swap_chain);
bool swap_chain_format_changed = swap_chain_format != swap_chain_format_;
bool toggle_protected_video = swap_chain_protected_video_type_ !=
params.video_params.protected_video_type;
bool contents_changed = last_overlay_image_ != params.overlay_image;
if (swap_chain_ && !swap_chain_resized && !swap_chain_format_changed &&
!toggle_protected_video && !contents_changed) {
// The swap chain is presenting the same images as last swap, which means
// that the images were never returned to the video decoder and should
// have the same contents as last time. It shouldn't need to be redrawn.
// But the visual transform and clip rectangle for DCLayerTree update need
// to keep the same as the last presentation when desktop plane was removed.
if (last_desktop_plane_removed_) {
SetTargetToFullScreen(visual_transform, visual_clip_rect, target_rect);
}
return true;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture =
params.overlay_image->nv12_texture();
unsigned input_level = params.overlay_image->texture_array_slice();
if (TryPresentToDecodeSwapChain(input_texture, input_level, input_color_space,
gfx::ToNearestRect(params.content_rect),
swap_chain_size, swap_chain_format,
params.transform, dest_size, target_rect)) {
last_overlay_image_ = std::move(params.overlay_image);
// Only NV12 format is supported in zero copy presentation path.
if (dest_size.has_value() && target_rect.has_value() &&
params.z_order > 0) {
SetTargetToFullScreen(visual_transform, visual_clip_rect, target_rect);
} else {
last_desktop_plane_removed_ = false;
}
return true;
}
// Reallocate swap chain if contents or properties change.
if (!swap_chain_ || swap_chain_resized || swap_chain_format_changed ||
toggle_protected_video) {
if (!ReallocateSwapChain(swap_chain_size, swap_chain_format,
params.video_params.protected_video_type)) {
ReleaseSwapChainResources();
return false;
}
content_ = swap_chain_.Get();
swap_chain_size_ = swap_chain_size;
content_size_ = swap_chain_size;
}
if (input_texture) {
staging_texture_.Reset();
copy_texture_.Reset();
} else {
input_texture = UploadVideoImage(params.overlay_image->size(),
params.overlay_image->nv12_pixmap(),
params.overlay_image->pixmap_stride());
input_level = 0;
}
std::optional<DXGI_HDR_METADATA_HDR10> stream_metadata;
if (params.video_params.hdr_metadata.IsValid()) {
stream_metadata = gl::HDRMetadataHelperWin::HDRMetadataToDXGI(
params.video_params.hdr_metadata);
}
if (!VideoProcessorBlt(std::move(input_texture), input_level,
gfx::ToNearestRect(params.content_rect),
input_color_space, stream_metadata, use_vp_auto_hdr)) {
return false;
}
HRESULT hr, device_removed_reason;
if (first_present_) {
first_present_ = false;
UINT flags = DXGI_PRESENT_USE_DURATION;
// DirectComposition can display black for a swap chain between the first
// and second time it's presented to - maybe the first Present can get lost
// somehow and it shows the wrong buffer. In that case copy the buffers so
// all have the correct contents, which seems to help. The first Present()
// after this needs to have SyncInterval > 0, or else the workaround doesn't
// help.
for (size_t i = 0; i < swap_chain_buffer_count_ - 1; ++i) {
hr = swap_chain_->Present(0, flags);
// Ignore DXGI_STATUS_OCCLUDED since that's not an error but only
// indicates that the window is occluded and we can stop rendering.
if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
DLOG(ERROR) << "Present failed with error 0x" << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> dest_texture;
swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dest_texture));
DCHECK(dest_texture);
Microsoft::WRL::ComPtr<ID3D11Texture2D> src_texture;
hr = swap_chain_->GetBuffer(1, IID_PPV_ARGS(&src_texture));
DCHECK(src_texture);
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device_->GetImmediateContext(&context);
DCHECK(context);
context->CopyResource(dest_texture.Get(), src_texture.Get());
}
// Additionally wait for the GPU to finish executing its commands, or
// there still may be a black flicker when presenting expensive content
// (e.g. 4k video).
Microsoft::WRL::ComPtr<IDXGIDevice2> dxgi_device2;
d3d11_device_.As(&dxgi_device2);
DCHECK(dxgi_device2);
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
hr = dxgi_device2->EnqueueSetEvent(event.handle());
if (SUCCEEDED(hr)) {
event.Wait();
} else {
device_removed_reason = d3d11_device_->GetDeviceRemovedReason();
base::debug::Alias(&hr);
base::debug::Alias(&device_removed_reason);
base::debug::DumpWithoutCrashing();
}
}
UINT flags = DXGI_PRESENT_USE_DURATION;
UINT interval = 1;
if (DirectCompositionSwapChainTearingEnabled()) {
flags |= DXGI_PRESENT_ALLOW_TEARING;
interval = 0;
} else if (base::FeatureList::IsEnabled(
features::kDXGISwapChainPresentInterval0)) {
interval = 0;
}
// DWM can turn off the desktop plane if this is a YUV swap chain and the
// overlay candidate covers the whole screen with letterboxing.
bool is_letterboxing_overlay_ready = false;
if (IsYUVSwapChainFormat(swap_chain_format_) && dest_size.has_value() &&
target_rect.has_value()) {
// Try to QI IDXGIDecodeSwapChain and set the DXGI properties properly, in
// order to turn off the desktop plane in case of overlay.
bool succeeded = false;
Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain;
// Note that QI IDXGIDecodeSwapChain from an RGB swap chain will always
// fail.
hr = swap_chain_->QueryInterface(IID_PPV_ARGS(&decode_swap_chain));
if (SUCCEEDED(hr)) {
succeeded = TryDisableDesktopPlane(decode_swap_chain.Get(), *dest_size,
*target_rect);
} else {
DLOG(ERROR)
<< "QueryInterface for IDXGIDecodeSwapChain failed with error 0x"
<< std::hex << hr;
}
// There should be no other UI content overtop of the video, so that the
// letterboxing and positioning can be carried out by DWM. In case of
// underlay, both |dest_size| and |target_rect| are initialized according to
// swap_chain_size, thus no extra target transform and clip adjustment is
// needed as follow-ups.
if (succeeded && params.z_order > 0) {
is_letterboxing_overlay_ready = true;
}
}
// Ignore DXGI_STATUS_OCCLUDED since that's not an error but only indicates
// that the window is occluded and we can stop rendering.
hr = swap_chain_->Present(interval, flags);
if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
DLOG(ERROR) << "Present failed with error 0x" << std::hex << hr;
return false;
}
// Update |visual_transform| and |visual_clip_rect| for the full screen
// letterboxing overlay presentation.
if (is_letterboxing_overlay_ready) {
SetTargetToFullScreen(visual_transform, visual_clip_rect, target_rect);
} else {
last_desktop_plane_removed_ = false;
}
last_overlay_image_ = std::move(params.overlay_image);
RecordPresentationStatistics();
return true;
}
void SwapChainPresenter::SetFrameRate(float frame_rate) {
frame_rate_ = frame_rate;
SetSwapChainPresentDuration();
}
void SwapChainPresenter::RecordPresentationStatistics() {
base::UmaHistogramSparse("GPU.DirectComposition.SwapChainFormat3",
swap_chain_format_);
VideoPresentationMode presentation_mode;
if (decode_swap_chain_) {
presentation_mode = VideoPresentationMode::kZeroCopyDecodeSwapChain;
} else if (staging_texture_) {
presentation_mode = VideoPresentationMode::kUploadAndVideoProcessorBlit;
} else {
presentation_mode = VideoPresentationMode::kBindAndVideoProcessorBlit;
}
UMA_HISTOGRAM_ENUMERATION("GPU.DirectComposition.VideoPresentationMode",
presentation_mode);
TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("gpu.service"),
"SwapChain::Present", TRACE_EVENT_SCOPE_THREAD,
"PixelFormat", DxgiFormatToString(swap_chain_format_),
"ZeroCopy", !!decode_swap_chain_);
Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media =
GetSwapChainMedia();
if (swap_chain_media) {
DXGI_FRAME_STATISTICS_MEDIA stats = {};
// GetFrameStatisticsMedia fails with DXGI_ERROR_FRAME_STATISTICS_DISJOINT
// sometimes, which means an event (such as power cycle) interrupted the
// gathering of presentation statistics. In this situation, calling the
// function again succeeds but returns with CompositionMode = NONE.
// Waiting for the DXGI adapter to finish presenting before calling the
// function doesn't get rid of the failure.
HRESULT hr = swap_chain_media->GetFrameStatisticsMedia(&stats);
int mode = -1;
if (SUCCEEDED(hr)) {
base::UmaHistogramSparse(
"GPU.DirectComposition.CompositionMode2.VideoOrCanvas",
stats.CompositionMode);
presentation_history_.AddSample(stats.CompositionMode);
mode = stats.CompositionMode;
}
// Record CompositionMode as -1 if GetFrameStatisticsMedia() fails.
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("gpu.service"),
"GetFrameStatisticsMedia", TRACE_EVENT_SCOPE_THREAD,
"CompositionMode", mode);
}
}
bool SwapChainPresenter::PresentDCOMPSurface(DCLayerOverlayParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
auto* dcomp_surface_proxy = params.overlay_image->dcomp_surface_proxy();
last_overlay_image_ = std::move(params.overlay_image);
dcomp_surface_proxy->SetParentWindow(layer_tree_->window());
gfx::Rect mapped_rect;
// Apply fullscreen rounding and transform to video and notify DCOMPTexture.
// For the DCOMP Surface presentation path we don't create a swap chain, but
// we expect the Media Engine to use the on screen rect as its
// representation.
gfx::RectF overlay_onscreen_rect =
visual_transform->MapRect(gfx::RectF(params.quad_rect));
gfx::SizeF on_screen_size_float = overlay_onscreen_rect.size();
gfx::RectF visual_clip_rect_float = gfx::RectF(*visual_clip_rect);
std::optional<gfx::SizeF> dest_size;
std::optional<gfx::RectF> target_rect;
// In order to get the fullscreen DWM optimizations, the overlay onscreen
// rect must fit the monitor when in non-letterboxing fullscreen mode.
// Adjust |swap_chain_size|, |visual_transform| and |visual_clip_rect| so
// |overlay_onscreen_rect| is the same as the monitor rect.
// Specially for fullscreen overlays with letterboxing effect,
// |overlay_onscreen_rect| will be placed in the center of the screen, and
// either left/right edges or top/bottom edges will touch the monitor edges.
if (visual_transform->IsScaleOrTranslation()) {
AdjustTargetToOptimalSizeIfNeeded(
params, overlay_onscreen_rect, &on_screen_size_float, visual_transform,
&visual_clip_rect_float, &dest_size, &target_rect);
}
mapped_rect = visual_transform->MapRect(params.quad_rect);
// Note: do not intersect clip rect w/ mapped_rect. This will result
// in Media Foundation scaling the full video to the clipped region,
// instead of allowing clipping to a portion of the video.
dcomp_surface_proxy->SetRect(mapped_rect);
dcomp_surface_proxy->SetProtectedVideoType(
params.video_params.protected_video_type);
// If |dcomp_surface_proxy| size is {1, 1}, the texture was initialized
// without knowledge of output size; reset |content_| so it's not added to the
// visual tree.
const gfx::Size content_size = dcomp_surface_proxy->GetSize();
if (content_size == gfx::Size(1, 1)) {
// If |content_visual_| is not updated, empty the visual and clear the DComp
// surface to prevent stale content from being displayed.
ReleaseDCOMPSurfaceResourcesIfNeeded();
DVLOG(2) << __func__ << " this=" << this
<< " dcomp_surface_proxy size (1x1) path.";
return true;
}
// TODO(crbug.com/40642952): Call UpdateVisuals() here.
// Scaling is handled by the MF video renderer, so we only need the
// translation component.
gfx::Vector2dF visual_transform_offset = visual_transform->To2dTranslation();
visual_transform->MakeIdentity();
visual_transform->Translate(visual_transform_offset);
#if DCHECK_IS_ON()
TRACE_EVENT2("gpu", "PresentDCOMPSurface", "finalized transform",
visual_transform->ToString(), "finalized mapped rect",
mapped_rect.ToString());
#endif // DCHECK_IS_ON()
// This visual's content was a different DC surface.
HANDLE surface_handle = dcomp_surface_proxy->GetSurfaceHandle();
if (dcomp_surface_handle_ != surface_handle) {
DVLOG(2) << "Update visual's content. " << __func__ << "(" << this << ")";
ReleaseSwapChainResources();
Microsoft::WRL::ComPtr<IDCompositionSurface> dcomp_surface;
Microsoft::WRL::ComPtr<IDCompositionDevice> dcomp_device1;
HRESULT hr = dcomp_device_.As(&dcomp_device1);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get DCOMP device. hr=" << hr;
return false;
}
hr = dcomp_device1->CreateSurfaceFromHandle(surface_handle, &dcomp_surface);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to create DCOMP surface. hr=" << hr;
return false;
}
content_ = dcomp_surface.Get();
content_size_ = content_size;
// Don't take ownership of handle as the DCOMPSurfaceProxy instance owns it.
dcomp_surface_handle_ = surface_handle;
}
return true;
}
void SwapChainPresenter::ReleaseDCOMPSurfaceResourcesIfNeeded() {
if (dcomp_surface_handle_ != INVALID_HANDLE_VALUE) {
dcomp_surface_handle_ = INVALID_HANDLE_VALUE;
last_overlay_image_.reset();
content_.Reset();
}
}
bool SwapChainPresenter::VideoProcessorBlt(
Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture,
UINT input_level,
const gfx::Rect& content_rect,
const gfx::ColorSpace& src_color_space,
std::optional<DXGI_HDR_METADATA_HDR10> stream_hdr_metadata,
bool use_vp_auto_hdr) {
TRACE_EVENT2("gpu", "SwapChainPresenter::VideoProcessorBlt", "content_rect",
content_rect.ToString(), "swap_chain_size",
swap_chain_size_.ToString());
// TODO(sunnyps): Ensure output color space for YUV swap chains is Rec709 or
// Rec601 so that the conversion from gfx::ColorSpace to DXGI_COLOR_SPACE
// doesn't need a |force_yuv| parameter (and the associated plumbing).
bool is_yuv_swapchain = IsYUVSwapChainFormat(swap_chain_format_);
gfx::ColorSpace output_color_space =
GetOutputColorSpace(src_color_space, is_yuv_swapchain);
bool video_processor_recreated = false;
VideoProcessorWrapper* video_processor_wrapper =
layer_tree_->InitializeVideoProcessor(
content_rect.size(), swap_chain_size_, output_color_space.IsHDR(),
video_processor_recreated);
if (!video_processor_wrapper)
return false;
Microsoft::WRL::ComPtr<ID3D11VideoContext> video_context =
video_processor_wrapper->video_context;
Microsoft::WRL::ComPtr<ID3D11VideoProcessor> video_processor =
video_processor_wrapper->video_processor;
if (video_processor_recreated) {
bool supports_vp_auto_hdr = GpuDriverSupportsVpAutoHDR(
gpu_vendor_id_, video_context.Get(), video_processor.Get());
video_processor_wrapper->SetDriverSupportsVpAutoHdr(supports_vp_auto_hdr);
}
bool driver_supports_vp_auto_hdr =
video_processor_wrapper->GetDriverSupportsVpAutoHdr();
Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain3;
Microsoft::WRL::ComPtr<ID3D11VideoContext1> context1;
if (SUCCEEDED(swap_chain_.As(&swap_chain3)) &&
SUCCEEDED(video_context.As(&context1))) {
DCHECK(swap_chain3);
DCHECK(context1);
// Set input color space.
context1->VideoProcessorSetStreamColorSpace1(
video_processor.Get(), 0,
gfx::ColorSpaceWin::GetDXGIColorSpace(src_color_space));
// Set output color space.
DXGI_COLOR_SPACE_TYPE output_dxgi_color_space =
gfx::ColorSpaceWin::GetDXGIColorSpace(output_color_space,
/*force_yuv=*/is_yuv_swapchain);
DXGI_COLOR_SPACE_TYPE swap_dxgi_color_space =
use_vp_auto_hdr ? gfx::ColorSpaceWin::GetDXGIColorSpace(
gfx::ColorSpace::CreateHDR10())
: output_dxgi_color_space;
if (SUCCEEDED(swap_chain3->SetColorSpace1(swap_dxgi_color_space))) {
context1->VideoProcessorSetOutputColorSpace1(video_processor.Get(),
output_dxgi_color_space);
}
} else {
// This can't handle as many different types of color spaces, so use it
// only if ID3D11VideoContext1 isn't available.
D3D11_VIDEO_PROCESSOR_COLOR_SPACE src_d3d11_color_space =
gfx::ColorSpaceWin::GetD3D11ColorSpace(src_color_space);
video_context->VideoProcessorSetStreamColorSpace(video_processor.Get(), 0,
&src_d3d11_color_space);
D3D11_VIDEO_PROCESSOR_COLOR_SPACE output_d3d11_color_space =
gfx::ColorSpaceWin::GetD3D11ColorSpace(output_color_space);
video_context->VideoProcessorSetOutputColorSpace(video_processor.Get(),
&output_d3d11_color_space);
}
Microsoft::WRL::ComPtr<ID3D11VideoContext2> context2;
std::optional<DXGI_HDR_METADATA_HDR10> display_metadata =
layer_tree_->GetHDRMetadataHelper()->GetDisplayMetadata(
layer_tree_->window());
if (display_metadata.has_value() && SUCCEEDED(video_context.As(&context2))) {
if (stream_hdr_metadata.has_value()) {
context2->VideoProcessorSetStreamHDRMetaData(
video_processor.Get(), 0, DXGI_HDR_METADATA_TYPE_HDR10,
sizeof(DXGI_HDR_METADATA_HDR10), &(*stream_hdr_metadata));
}
context2->VideoProcessorSetOutputHDRMetaData(
video_processor.Get(), DXGI_HDR_METADATA_TYPE_HDR10,
sizeof(DXGI_HDR_METADATA_HDR10), &(*display_metadata));
}
{
Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device =
video_processor_wrapper->video_device;
Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>
video_processor_enumerator =
video_processor_wrapper->video_processor_enumerator;
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = {};
input_desc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
input_desc.Texture2D.ArraySlice = input_level;
Microsoft::WRL::ComPtr<ID3D11VideoProcessorInputView> input_view;
HRESULT hr = video_device->CreateVideoProcessorInputView(
input_texture.Get(), video_processor_enumerator.Get(), &input_desc,
&input_view);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateVideoProcessorInputView failed with error 0x"
<< std::hex << hr;
return false;
}
D3D11_VIDEO_PROCESSOR_STREAM stream = {};
stream.Enable = true;
stream.OutputIndex = 0;
stream.InputFrameOrField = 0;
stream.PastFrames = 0;
stream.FutureFrames = 0;
stream.pInputSurface = input_view.Get();
RECT dest_rect = gfx::Rect(swap_chain_size_).ToRECT();
video_context->VideoProcessorSetOutputTargetRect(video_processor.Get(),
TRUE, &dest_rect);
video_context->VideoProcessorSetStreamDestRect(video_processor.Get(), 0,
TRUE, &dest_rect);
RECT source_rect = content_rect.ToRECT();
video_context->VideoProcessorSetStreamSourceRect(video_processor.Get(), 0,
TRUE, &source_rect);
if (!output_view_) {
Microsoft::WRL::ComPtr<ID3D11Texture2D> swap_chain_buffer;
swap_chain_->GetBuffer(0, IID_PPV_ARGS(&swap_chain_buffer));
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc = {};
output_desc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
output_desc.Texture2D.MipSlice = 0;
hr = video_device->CreateVideoProcessorOutputView(
swap_chain_buffer.Get(), video_processor_enumerator.Get(),
&output_desc, &output_view_);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateVideoProcessorOutputView failed with error 0x"
<< std::hex << hr;
return false;
}
DCHECK(output_view_);
}
if (enable_vp_auto_hdr_) {
hr = ToggleVpAutoHDR(gpu_vendor_id_, driver_supports_vp_auto_hdr,
video_context.Get(), video_processor.Get(),
use_vp_auto_hdr);
if (FAILED(hr)) {
enable_vp_auto_hdr_ = false;
if (use_vp_auto_hdr) {
if (!RevertSwapChainToSDR(video_device, video_processor,
video_processor_enumerator, swap_chain3,
context1, src_color_space)) {
return false;
}
use_vp_auto_hdr = false;
}
}
}
bool use_vp_super_resolution =
enable_vp_super_resolution_ && !is_on_battery_power_;
if (enable_vp_super_resolution_) {
hr = ToggleVpSuperResolution(gpu_vendor_id_, video_context.Get(),
video_processor.Get(),
use_vp_super_resolution);
if (FAILED(hr)) {
enable_vp_super_resolution_ = false;
use_vp_super_resolution = false;
}
}
{
TRACE_EVENT0("gpu", "ID3D11VideoContext::VideoProcessorBlt");
hr = video_context->VideoProcessorBlt(video_processor.Get(),
output_view_.Get(), 0, 1, &stream);
}
base::UmaHistogramSparse(
(use_vp_auto_hdr ? "GPU.VideoProcessorBlt.VpAutoHDR.On"
: "GPU.VideoProcessorBlt.VpAutoHDR.Off"),
hr);
base::UmaHistogramSparse(
(use_vp_super_resolution
? "GPU.VideoProcessorBlt.VpSuperResolution.On"
: "GPU.VideoProcessorBlt.VpSuperResolution.Off"),
hr);
// Retry VideoProcessorBlt with VpSuperResolution off if it was on.
if (FAILED(hr) && use_vp_super_resolution) {
DLOG(ERROR) << "Retry VideoProcessorBlt with VpSuperResolution off "
"after it failed with error 0x"
<< std::hex << hr;
ToggleVpSuperResolution(gpu_vendor_id_, video_context.Get(),
video_processor.Get(), false);
{
TRACE_EVENT0("gpu", "ID3D11VideoContext::VideoProcessorBlt");
hr = video_context->VideoProcessorBlt(
video_processor.Get(), output_view_.Get(), 0, 1, &stream);
}
base::UmaHistogramSparse(
"GPU.VideoProcessorBlt.VpSuperResolution.RetryOffAfterError", hr);
// We shouldn't use VpSuperResolution if it was the reason that caused
// the VideoProcessorBlt failure.
if (SUCCEEDED(hr)) {
enable_vp_super_resolution_ = false;
}
}
if (FAILED(hr) && use_vp_auto_hdr) {
DLOG(ERROR) << "Retry VideoProcessorBlt with VpAutoHDR off "
"after it failed with error 0x"
<< std::hex << hr;
ToggleVpAutoHDR(gpu_vendor_id_, driver_supports_vp_auto_hdr,
video_context.Get(), video_processor.Get(), false);
if (!RevertSwapChainToSDR(video_device, video_processor,
video_processor_enumerator, swap_chain3,
context1, src_color_space)) {
return false;
}
{
TRACE_EVENT0("gpu", "ID3D11VideoContext::VideoProcessorBlt");
hr = video_context->VideoProcessorBlt(
video_processor.Get(), output_view_.Get(), 0, 1, &stream);
}
base::UmaHistogramSparse(
"GPU.VideoProcessorBlt.VpAutoHDR.RetryOffAfterError", hr);
// We shouldn't use VpAutoHDR if it was the reason that caused
// the VideoProcessorBlt failure.
if (SUCCEEDED(hr)) {
enable_vp_auto_hdr_ = false;
}
}
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorBlt failed with error 0x" << std::hex << hr;
// To prevent it from failing in all coming frames, disable overlay if
// VideoProcessorBlt is not implemented in the GPU driver.
if (hr == E_NOTIMPL) {
DisableDirectCompositionOverlays();
}
return false;
}
}
return true;
}
void SwapChainPresenter::ReleaseSwapChainResources() {
if (swap_chain_ || decode_swap_chain_) {
output_view_.Reset();
swap_chain_.Reset();
swap_chain_handle_.Close();
staging_texture_.Reset();
swap_chain_size_ = gfx::Size();
decode_surface_.Reset();
decode_swap_chain_.Reset();
decode_resource_.Reset();
// Only release these if we were previously using a swap chain, otherwise it
// might interfere with dcomp surface path.
content_.Reset();
content_size_ = gfx::Size();
last_overlay_image_.reset();
}
}
bool SwapChainPresenter::ReallocateSwapChain(
const gfx::Size& swap_chain_size,
DXGI_FORMAT swap_chain_format,
gfx::ProtectedVideoType protected_video_type) {
bool use_yuv_swap_chain = IsYUVSwapChainFormat(swap_chain_format);
TRACE_EVENT2("gpu", "SwapChainPresenter::ReallocateSwapChain", "size",
swap_chain_size.ToString(), "yuv", use_yuv_swap_chain);
ReleaseSwapChainResources();
DCHECK(!swap_chain_size.IsEmpty());
swap_chain_size_ = swap_chain_size;
swap_chain_protected_video_type_ = protected_video_type;
gpu_vendor_id_ = 0;
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
d3d11_device_.As(&dxgi_device);
DCHECK(dxgi_device);
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
dxgi_device->GetAdapter(&dxgi_adapter);
DCHECK(dxgi_adapter);
Microsoft::WRL::ComPtr<IDXGIFactoryMedia> media_factory;
dxgi_adapter->GetParent(IID_PPV_ARGS(&media_factory));
DCHECK(media_factory);
// The composition surface handle is only used to create YUV swap chains since
// CreateSwapChainForComposition can't do that.
HANDLE handle = INVALID_HANDLE_VALUE;
if (!CreateSurfaceHandleHelper(&handle))
return false;
swap_chain_handle_.Set(handle);
first_present_ = true;
DXGI_SWAP_CHAIN_DESC1 desc = {};
desc.Width = swap_chain_size_.width();
desc.Height = swap_chain_size_.height();
desc.Format = swap_chain_format;
desc.Stereo = FALSE;
desc.SampleDesc.Count = 1;
desc.BufferCount = swap_chain_buffer_count_;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.Flags =
DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO | DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
if (DirectCompositionSwapChainTearingEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
if (DXGIWaitableSwapChainEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
}
if (IsProtectedVideo(protected_video_type)) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY;
}
if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED;
}
desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
const std::string kSwapChainCreationResultByVideoTypeUmaPrefix =
"GPU.DirectComposition.SwapChainCreationResult3.";
const std::string protected_video_type_string =
ProtectedVideoTypeToString(protected_video_type);
if (use_yuv_swap_chain) {
TRACE_EVENT1("gpu", "SwapChainPresenter::ReallocateSwapChain::YUV",
"format", DxgiFormatToString(swap_chain_format));
HRESULT hr = media_factory->CreateSwapChainForCompositionSurfaceHandle(
d3d11_device_.Get(), swap_chain_handle_.Get(), &desc, nullptr,
&swap_chain_);
failed_to_create_yuv_swapchain_ = FAILED(hr);
base::UmaHistogramSparse(kSwapChainCreationResultByVideoTypeUmaPrefix +
protected_video_type_string,
hr);
if (failed_to_create_yuv_swapchain_) {
DLOG(ERROR) << "Failed to create "
<< DxgiFormatToString(swap_chain_format)
<< " swap chain of size " << swap_chain_size.ToString()
<< " with error 0x" << std::hex << hr
<< "\nFalling back to BGRA";
use_yuv_swap_chain = false;
swap_chain_format = DXGI_FORMAT_B8G8R8A8_UNORM;
}
}
if (!use_yuv_swap_chain) {
TRACE_EVENT1("gpu", "SwapChainPresenter::ReallocateSwapChain::BGRA",
"format", DxgiFormatToString(swap_chain_format));
desc.Format = swap_chain_format;
desc.Flags = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
if (DirectCompositionSwapChainTearingEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
if (DXGIWaitableSwapChainEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
}
if (IsProtectedVideo(protected_video_type)) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY;
}
if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED;
}
HRESULT hr = media_factory->CreateSwapChainForCompositionSurfaceHandle(
d3d11_device_.Get(), swap_chain_handle_.Get(), &desc, nullptr,
&swap_chain_);
base::UmaHistogramSparse(kSwapChainCreationResultByVideoTypeUmaPrefix +
protected_video_type_string,
hr);
if (FAILED(hr)) {
// Disable overlay support so dc_layer_overlay will stop sending down
// overlay frames here and uses GL Composition instead.
DisableDirectCompositionOverlays();
DLOG(ERROR) << "Failed to create "
<< DxgiFormatToString(swap_chain_format)
<< " swap chain of size " << swap_chain_size.ToString()
<< " with error 0x" << std::hex << hr
<< ". Disable overlay swap chains";
return false;
}
}
if (DXGIWaitableSwapChainEnabled()) {
Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain3;
if (SUCCEEDED(swap_chain_.As(&swap_chain3))) {
HRESULT hr = swap_chain3->SetMaximumFrameLatency(
GetDXGIWaitableSwapChainMaxQueuedFrames());
DCHECK(SUCCEEDED(hr)) << "SetMaximumFrameLatency failed with error "
<< logging::SystemErrorCodeToString(hr);
}
}
gl::LabelSwapChainAndBuffers(swap_chain_.Get(), "SwapChainPresenter");
swap_chain_format_ = swap_chain_format;
SetSwapChainPresentDuration();
DXGI_ADAPTER_DESC adapter_desc;
HRESULT hr = dxgi_adapter->GetDesc(&adapter_desc);
if (SUCCEEDED(hr)) {
gpu_vendor_id_ = adapter_desc.VendorId;
} else {
DLOG(ERROR) << "Failed to get adapter desc with error 0x" << std::hex << hr;
}
enable_vp_auto_hdr_ =
!layer_tree_->disable_vp_auto_hdr() && IsVpAutoHDREnabled(gpu_vendor_id_);
enable_vp_super_resolution_ = !layer_tree_->disable_vp_super_resolution();
return true;
}
void SwapChainPresenter::OnPowerStateChange(bool on_battery_power) {
is_on_battery_power_ = on_battery_power;
}
bool SwapChainPresenter::ShouldUseVideoProcessorScaling() {
return (!is_on_battery_power_ && !layer_tree_->disable_vp_scaling());
}
void SwapChainPresenter::SetSwapChainPresentDuration() {
Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media =
GetSwapChainMedia();
if (swap_chain_media) {
UINT duration_100ns = FrameRateToPresentDuration(frame_rate_);
UINT requested_duration = 0u;
if (duration_100ns > 0) {
UINT smaller_duration = 0u, larger_duration = 0u;
HRESULT hr = swap_chain_media->CheckPresentDurationSupport(
duration_100ns, &smaller_duration, &larger_duration);
if (FAILED(hr)) {
DLOG(ERROR) << "CheckPresentDurationSupport failed with error "
<< std::hex << hr;
return;
}
constexpr UINT kDurationThreshold = 1000u;
// Smaller duration should be used to avoid frame loss. However, we want
// to take into consideration the larger duration is the same as the
// requested duration but was slightly different due to frame rate
// estimation errors.
if (larger_duration > 0 &&
larger_duration - duration_100ns < kDurationThreshold) {
requested_duration = larger_duration;
} else if (smaller_duration > 0) {
requested_duration = smaller_duration;
}
}
HRESULT hr = swap_chain_media->SetPresentDuration(requested_duration);
if (FAILED(hr)) {
DLOG(ERROR) << "SetPresentDuration failed with error " << std::hex << hr;
}
}
}
Microsoft::WRL::ComPtr<IDXGISwapChainMedia>
SwapChainPresenter::GetSwapChainMedia() const {
Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media;
HRESULT hr = S_OK;
if (decode_swap_chain_) {
hr = decode_swap_chain_.As(&swap_chain_media);
} else {
DCHECK(swap_chain_);
hr = swap_chain_.As(&swap_chain_media);
}
if (SUCCEEDED(hr))
return swap_chain_media;
return nullptr;
}
bool SwapChainPresenter::RevertSwapChainToSDR(
Microsoft::WRL::ComPtr<ID3D11VideoDevice> video_device,
Microsoft::WRL::ComPtr<ID3D11VideoProcessor> video_processor,
Microsoft::WRL::ComPtr<ID3D11VideoProcessorEnumerator>
video_processor_enumerator,
Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain3,
Microsoft::WRL::ComPtr<ID3D11VideoContext1> context1,
const gfx::ColorSpace& input_color_space) {
if (!video_device || !video_processor || !video_processor_enumerator ||
!swap_chain3 || !context1) {
return false;
}
// Restore the SDR swap chain and output view
if (!ReallocateSwapChain(
gfx::Size(swap_chain_size_),
GetSwapChainFormat(swap_chain_protected_video_type_, /*hdr=*/false),
swap_chain_protected_video_type_)) {
ReleaseSwapChainResources();
return false;
}
content_ = swap_chain_.Get();
Microsoft::WRL::ComPtr<ID3D11Texture2D> swap_chain_buffer;
swap_chain_->GetBuffer(0, IID_PPV_ARGS(&swap_chain_buffer));
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc = {};
output_desc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
output_desc.Texture2D.MipSlice = 0;
HRESULT hr = video_device->CreateVideoProcessorOutputView(
swap_chain_buffer.Get(), video_processor_enumerator.Get(), &output_desc,
&output_view_);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateVideoProcessorOutputView failed with error 0x"
<< std::hex << hr;
return false;
}
DCHECK(output_view_);
// Reset the output color space for the swap chain and video processor
bool is_yuv_swapchain = IsYUVSwapChainFormat(swap_chain_format_);
gfx::ColorSpace output_color_space =
GetOutputColorSpace(input_color_space, is_yuv_swapchain);
DXGI_COLOR_SPACE_TYPE output_dxgi_color_space =
gfx::ColorSpaceWin::GetDXGIColorSpace(output_color_space,
is_yuv_swapchain);
context1->VideoProcessorSetOutputColorSpace1(video_processor.Get(),
output_dxgi_color_space);
hr = swap_chain3->SetColorSpace1(output_dxgi_color_space);
if (FAILED(hr)) {
DLOG(ERROR) << "SetColorSpace1 failed with error 0x" << std::hex << hr;
return false;
}
return true;
}
} // namespace gl