blob: e79138f39ae587dc05254651e89fbd863861f919 [file] [log] [blame]
// 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.
#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 "media/base/win/mf_helpers.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_tree.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gl_image_d3d.h"
#include "ui/gl/gl_image_dxgi.h"
#include "ui/gl/gl_image_memory.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);
bool IsProtectedVideo(gfx::ProtectedVideoType protected_video_type) {
return protected_video_type != gfx::ProtectedVideoType::kClear;
}
class ScopedReleaseKeyedMutex {
public:
ScopedReleaseKeyedMutex(Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,
UINT64 key)
: keyed_mutex_(keyed_mutex), key_(key) {
DCHECK(keyed_mutex);
}
ScopedReleaseKeyedMutex(const ScopedReleaseKeyedMutex&) = delete;
ScopedReleaseKeyedMutex& operator=(const ScopedReleaseKeyedMutex&) = delete;
~ScopedReleaseKeyedMutex() {
HRESULT hr = keyed_mutex_->ReleaseSync(key_);
DCHECK(SUCCEEDED(hr));
}
private:
Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;
UINT64 key_ = 0;
};
// 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();
return "UNKNOWN";
}
}
bool IsYUVSwapChainFormat(DXGI_FORMAT format) {
if (format == DXGI_FORMAT_NV12 || format == DXGI_FORMAT_YUY2)
return true;
return false;
}
UINT BufferCount() {
return 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::Size& 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;
};
void ToggleIntelVpSuperResolution(ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool is_on_battery_power) {
TRACE_EVENT1("gpu", "ToggleIntelVpSuperResolution", "on",
!is_on_battery_power);
IntelVpeExt ext = {};
UINT param = 0;
ext.param = &param;
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;
}
ext.function = kIntelVpeFnMode;
param = is_on_battery_power ? kIntelVpeModeNone : kIntelVpeModePreproc;
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;
}
ext.function = kIntelVpeFnScaling;
param = is_on_battery_power ? kIntelVpeScalingDefault
: kIntelVpeScalingSuperResolution;
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;
}
}
void ToggleNvidiaVpSuperResolution(ID3D11VideoContext* video_context,
ID3D11VideoProcessor* video_processor,
bool is_on_battery_power) {
TRACE_EVENT1("gpu", "ToggleNvidiaVpSuperResolution", "on",
!is_on_battery_power);
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,
is_on_battery_power ? 0 : 1u};
HRESULT hr = video_context->VideoProcessorSetStreamExtension(
video_processor, 0, &kNvidiaPPEInterfaceGUID,
sizeof(stream_extension_info), &stream_extension_info);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorSetStreamExtension failed with error 0x"
<< std::hex << hr;
return;
}
}
bool IsWithinMargin(int i, int j) {
constexpr int kFullScreenMargin = 10;
return (std::abs(i - j) < kFullScreenMargin);
}
} // 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,
HWND window,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device)
: layer_tree_(layer_tree),
window_(window),
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::UploadVideoImages(
GLImageMemory* y_image_memory,
GLImageMemory* uv_image_memory) {
gfx::Size texture_size = y_image_memory->GetSize();
gfx::Size uv_image_size = uv_image_memory->GetSize();
if (uv_image_size.height() != texture_size.height() / 2 ||
uv_image_size.width() != texture_size.width() / 2 ||
y_image_memory->format() != gfx::BufferFormat::R_8 ||
uv_image_memory->format() != gfx::BufferFormat::RG_88) {
DLOG(ERROR) << "Invalid NV12 GLImageMemory properties.";
return nullptr;
}
TRACE_EVENT1("gpu", "SwapChainPresenter::UploadVideoImages", "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 = media::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;
for (int y = 0; y < texture_size.height(); y++) {
const uint8_t* y_source =
y_image_memory->memory() + y * y_image_memory->stride();
uint8_t* dest =
reinterpret_cast<uint8_t*>(mapped_resource.pData) + dest_stride * y;
memcpy(dest, y_source, texture_size.width());
}
uint8_t* uv_dest_plane_start =
reinterpret_cast<uint8_t*>(mapped_resource.pData) +
dest_stride * texture_size.height();
for (int y = 0; y < uv_image_size.height(); y++) {
const uint8_t* uv_source =
uv_image_memory->memory() + y * uv_image_memory->stride();
uint8_t* dest = uv_dest_plane_start + dest_stride * y;
memcpy(dest, uv_source, 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, &copy_texture_);
if (FAILED(hr)) {
DLOG(ERROR) << "Creating D3D11 video upload texture failed: " << std::hex
<< hr;
DisableDirectCompositionOverlays();
return nullptr;
}
DCHECK(copy_texture_);
hr = media::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() {
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(window_, MONITOR_DEFAULTTONEAREST),
&monitor_info)) {
monitor_size = gfx::Rect(monitor_info.rcMonitor).size();
}
return monitor_size;
}
}
void SwapChainPresenter::AdjustTargetToOptimalSizeIfNeeded(
const ui::DCRendererLayerParams& params,
const gfx::Rect& overlay_onscreen_rect,
gfx::Size* swap_chain_size,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
// 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();
bool size_adjusted = AdjustTargetToFullScreenSizeIfNeeded(
monitor_size, params, overlay_onscreen_rect, swap_chain_size,
visual_transform, visual_clip_rect);
if (!size_adjusted && params.is_video_fullscreen_letterboxing) {
AdjustTargetForFullScreenLetterboxing(
monitor_size, params, overlay_onscreen_rect, swap_chain_size,
visual_transform, visual_clip_rect);
}
}
bool SwapChainPresenter::AdjustTargetToFullScreenSizeIfNeeded(
const gfx::Size& monitor_size,
const ui::DCRendererLayerParams& params,
const gfx::Rect& overlay_onscreen_rect,
gfx::Size* swap_chain_size,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
if (monitor_size.IsEmpty())
return false;
gfx::Rect clipped_onscreen_rect = overlay_onscreen_rect;
if (params.clip_rect.has_value())
clipped_onscreen_rect.Intersect(*visual_clip_rect);
// 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) ||
!IsWithinMargin(clipped_onscreen_rect.y(), 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::Rect(monitor_size);
}
// Adjust the swap chain size.
// The swap chain is either the size of overlay_onscreen_rect or
// min(overlay_onscreen_rect, content_rect). It might not need to update if it
// has the content size.
if (IsWithinMargin(swap_chain_size->width(), monitor_size.width()) &&
IsWithinMargin(swap_chain_size->height(), monitor_size.height())) {
*swap_chain_size = monitor_size;
}
// Adjust the transform matrix.
float scale_x = monitor_size.width() * 1.0f / swap_chain_size->width();
float scale_y = monitor_size.height() * 1.0f / swap_chain_size->height();
visual_transform->MakeIdentity();
visual_transform->Scale(scale_x, scale_y);
// Origin is probably (0,0) all the time. If not, adjust the origin.
if (!params.quad_rect.origin().IsOrigin()) {
auto new_origin = visual_transform->MapPoint(params.quad_rect.origin());
visual_transform->PostTranslate(-new_origin.OffsetFromOrigin());
}
// The new transform matrix should transform the swap chain to the monitor
// rect.
DCHECK_EQ(visual_transform->MapRect(
gfx::Rect(params.quad_rect.origin(), *swap_chain_size)),
gfx::Rect(monitor_size));
return true;
}
void SwapChainPresenter::AdjustTargetForFullScreenLetterboxing(
const gfx::Size& monitor_size,
const ui::DCRendererLayerParams& params,
const gfx::Rect& overlay_onscreen_rect,
gfx::Size* swap_chain_size,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
if (!base::FeatureList::IsEnabled(
features::kDirectCompositionLetterboxVideoOptimization)) {
return;
}
if (monitor_size.IsEmpty())
return;
gfx::Rect 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);
bool is_onscreen_rect_y_near_0 = IsWithinMargin(clipped_onscreen_rect.y(), 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 + 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 + clipped_onscreen_rect.width(),
monitor_size.width())) {
// Not fullscreen letterboxing mode.
return;
}
// Adjust the onscreen rect to touch two screen borders, and also make sure
// the onscreen rect be right in the center.
// At the same time, make sure the origin position for clipped_onscreen_rect
// with round-up integer so that no extra blank bar shows up.
if (is_onscreen_rect_x_near_0) {
clipped_onscreen_rect.set_x(0);
clipped_onscreen_rect.set_width(monitor_size.width());
int new_y = (monitor_size.height() - clipped_onscreen_rect.height()) / 2;
if (new_y < clipped_onscreen_rect.y()) {
// If clipped_onscreen_rect needs to be moved up by n lines, we add n
// lines to the video onscreen rect height.
clipped_onscreen_rect.set_height(clipped_onscreen_rect.height() +
clipped_onscreen_rect.y() - new_y);
clipped_onscreen_rect.set_y(new_y);
} else if (new_y > clipped_onscreen_rect.y()) {
// If clipped_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.
clipped_onscreen_rect.set_height(monitor_size.height() -
clipped_onscreen_rect.y() * 2);
}
// Make clipped_onscreen_rect height even.
if (clipped_onscreen_rect.height() % 2 == 1)
clipped_onscreen_rect.set_height(clipped_onscreen_rect.height() + 1);
}
if (is_onscreen_rect_y_near_0) {
clipped_onscreen_rect.set_y(0);
clipped_onscreen_rect.set_height(monitor_size.height());
int new_x = (monitor_size.width() - clipped_onscreen_rect.width()) / 2;
if (new_x < clipped_onscreen_rect.x()) {
// If clipped_onscreen_rect needs to be moved left by n lines, we add n
// lines to the video onscreen rect width.
clipped_onscreen_rect.set_width(clipped_onscreen_rect.width() +
clipped_onscreen_rect.x() - new_x);
clipped_onscreen_rect.set_x(new_x);
} else if (new_x > clipped_onscreen_rect.x()) {
// If clipped_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.
clipped_onscreen_rect.set_width(monitor_size.width() -
clipped_onscreen_rect.x() * 2);
}
// Make clipped_onscreen_rect width even.
if (clipped_onscreen_rect.width() % 2 == 1)
clipped_onscreen_rect.set_width(clipped_onscreen_rect.width() + 1);
}
// Adjust the clip rect.
if (params.clip_rect.has_value())
*visual_clip_rect = clipped_onscreen_rect;
// Swap chain size has been updated before. Do not update it if it is not
// necessary.
if (!IsWithinMargin(swap_chain_size->width(),
clipped_onscreen_rect.width()) ||
!IsWithinMargin(swap_chain_size->height(),
clipped_onscreen_rect.height())) {
*swap_chain_size = clipped_onscreen_rect.size();
}
// Adjust the transform matrix.
float scale_x =
clipped_onscreen_rect.width() * 1.0f / swap_chain_size->width();
float scale_y =
clipped_onscreen_rect.height() * 1.0f / swap_chain_size->height();
visual_transform->set_rc(0, 3, clipped_onscreen_rect.x());
visual_transform->set_rc(1, 3, clipped_onscreen_rect.y());
visual_transform->set_rc(0, 0, scale_x);
visual_transform->set_rc(1, 1, scale_y);
#if DCHECK_IS_ON()
{
// The new transform matrix should transform the swap chain correctly
gfx::Rect new_swap_chain_rect(params.quad_rect.origin(), *swap_chain_size);
gfx::Rect result_rect = visual_transform->MapRect(new_swap_chain_rect);
gfx::Rect new_clipped_onscreen_rect = clipped_onscreen_rect;
gfx::Transform new_visual_transform = *visual_transform;
base::debug::Alias(&new_swap_chain_rect);
base::debug::Alias(&result_rect);
base::debug::Alias(&new_clipped_onscreen_rect);
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.
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());
if (IsWithinMargin(clipped_onscreen_rect.x(), 0)) {
DCHECK_EQ(result_rect.x(), 0);
DCHECK_EQ(result_rect.width(), monitor_size.width());
}
if (IsWithinMargin(clipped_onscreen_rect.y(), 0)) {
DCHECK_EQ(result_rect.y(), 0);
DCHECK_EQ(result_rect.height(), monitor_size.height());
}
}
#endif
}
gfx::Size SwapChainPresenter::CalculateSwapChainSize(
const ui::DCRendererLayerParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
// 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::Size swap_chain_size = params.content_rect.size();
if (swap_chain_size.IsEmpty())
return gfx::Size();
if (params.quad_rect.IsEmpty())
return gfx::Size();
gfx::Rect overlay_onscreen_rect = params.transform.MapRect(params.quad_rect);
// 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.
if (params.transform.IsScaleOrTranslation()) {
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.
if (swap_chain_size.width() % 2 == 1)
swap_chain_size.set_width(swap_chain_size.width() + 1);
if (swap_chain_size.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);
}
return 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) {
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)) {
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) {
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);
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;
HRESULT 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();
decode_swap_chain_->SetSourceRect(&source_rect);
decode_swap_chain_->SetDestSize(swap_chain_size.width(),
swap_chain_size.height());
RECT target_rect = gfx::Rect(swap_chain_size).ToRECT();
decode_swap_chain_->SetTargetRect(&target_rect);
// 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;
}
decode_swap_chain_->SetColorSpace(
static_cast<DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS>(color_space_flags));
UINT present_flags = DXGI_PRESENT_USE_DURATION;
HRESULT 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;
swap_chain_format_ = DXGI_FORMAT_NV12;
RecordPresentationStatistics();
return true;
}
bool SwapChainPresenter::PresentToSwapChain(
const ui::DCRendererLayerParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
*visual_transform = params.transform;
*visual_clip_rect = params.clip_rect.value_or(gfx::Rect());
if (params.dcomp_surface_proxy)
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();
GLImageDXGI* image_dxgi =
GLImageDXGI::FromGLImage(params.images[kNV12ImageIndex].get());
GLImageD3D* image_d3d =
GLImageD3D::FromGLImage(params.images[kNV12ImageIndex].get());
GLImageMemory* y_image_memory =
GLImageMemory::FromGLImage(params.images[kYPlaneImageIndex].get());
GLImageMemory* uv_image_memory =
GLImageMemory::FromGLImage(params.images[kUVPlaneImageIndex].get());
GLImageD3D* swap_chain_image =
GLImageD3D::FromGLImage(params.images[kSwapChainImageIndex].get());
if (swap_chain_image && !swap_chain_image->swap_chain())
swap_chain_image = nullptr;
if (!image_dxgi && !image_d3d && (!y_image_memory || !uv_image_memory) &&
!swap_chain_image) {
DLOG(ERROR) << "Video GLImages are missing";
ReleaseSwapChainResources();
// We don't treat this as an error because this could mean that the client
// sent us invalid overlay candidates which we weren't able to detect prior
// to this. This would cause incorrect rendering, but not a failure loop.
return true;
}
if ((image_dxgi && !image_dxgi->texture()) ||
(image_d3d && !image_d3d->texture())) {
// We can't proceed if |image_dxgi| 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;
}
std::string image_type;
if (swap_chain_image) {
image_type = "swap chain";
} else if (image_d3d || image_dxgi) {
image_type = "hardware video frame";
} else {
image_type = "software video frame";
}
gfx::Size swap_chain_size;
if (swap_chain_image) {
swap_chain_size = swap_chain_image->GetSize();
// |visual_transform| now scales from |swap_chain_size| to on screen bounds.
UpdateSwapChainTransform(params.quad_rect.size(), swap_chain_size,
visual_transform);
} else {
swap_chain_size =
CalculateSwapChainSize(params, visual_transform, visual_clip_rect);
}
TRACE_EVENT2("gpu", "SwapChainPresenter::PresentToSwapChain", "image_type",
image_type, "swap_chain_size", swap_chain_size.ToString());
Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
unsigned input_level = 0u;
Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
gfx::ColorSpace input_color_space;
if (image_dxgi) {
input_texture = image_dxgi->texture();
input_level = image_dxgi->level();
// Keyed mutex may not exist.
keyed_mutex = image_dxgi->keyed_mutex();
input_color_space = image_dxgi->color_space();
} else if (image_d3d) {
input_texture = image_d3d->texture();
input_level = image_d3d->array_slice();
input_color_space = image_d3d->color_space();
}
if (!input_color_space.IsValid())
input_color_space = gfx::ColorSpace::CreateREC709();
bool content_is_hdr = input_color_space.IsHDR();
// Do not create a swap chain if swap chain size will be empty.
if (swap_chain_size.IsEmpty()) {
swap_chain_size_ = swap_chain_size;
if (swap_chain_) {
ReleaseSwapChainResources();
content_.Reset();
}
return true;
}
// Swap chain image already has a swap chain that's presented by the client
// e.g. for webgl/canvas low-latency/desynchronized mode.
if (swap_chain_image) {
DCHECK(swap_chain_image->swap_chain());
if (last_presented_images_ != params.images) {
ReleaseSwapChainResources();
last_presented_images_ = params.images;
}
content_ = swap_chain_image->swap_chain().Get();
return true;
}
bool swap_chain_resized = swap_chain_size_ != swap_chain_size;
bool use_hdr_swap_chain = content_is_hdr && params.hdr_metadata.IsValid();
DXGI_FORMAT swap_chain_format =
GetSwapChainFormat(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.protected_video_type;
if (swap_chain_ && !swap_chain_resized && !swap_chain_format_changed &&
!toggle_protected_video && last_presented_images_ == params.images) {
// 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.
return true;
}
if (TryPresentToDecodeSwapChain(input_texture, input_level, input_color_space,
params.content_rect, swap_chain_size,
swap_chain_format, params.transform)) {
last_presented_images_ = params.images;
return true;
}
// Try reallocating swap chain if resizing fails.
if (!swap_chain_ || swap_chain_resized || swap_chain_format_changed ||
toggle_protected_video) {
if (!ReallocateSwapChain(swap_chain_size, swap_chain_format,
params.protected_video_type)) {
ReleaseSwapChainResources();
return false;
}
content_ = swap_chain_.Get();
}
if (input_texture) {
staging_texture_.Reset();
copy_texture_.Reset();
} else {
DCHECK(y_image_memory);
DCHECK(uv_image_memory);
input_texture = UploadVideoImages(y_image_memory, uv_image_memory);
input_level = 0;
}
if (!input_texture) {
DLOG(ERROR) << "Video image has no texture";
return false;
}
absl::optional<DXGI_HDR_METADATA_HDR10> stream_metadata;
if (params.hdr_metadata.IsValid()) {
stream_metadata =
gl::HDRMetadataHelperWin::HDRMetadataToDXGI(params.hdr_metadata);
}
if (!VideoProcessorBlt(input_texture, input_level, keyed_mutex,
params.content_rect, input_color_space, content_is_hdr,
stream_metadata)) {
return false;
}
HRESULT hr, device_removed_reason;
if (first_present_) {
first_present_ = false;
UINT flags = DXGI_PRESENT_USE_DURATION;
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;
}
// 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 both 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.
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;
}
// 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;
}
last_presented_images_ = params.images;
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);
if (frame_rate_ != 0) {
// [1ms, 10s] covers the fps between [0.1hz, 1000hz].
base::UmaHistogramTimes(
"GPU.DirectComposition.ApprovedPresentDuration",
base::Milliseconds(stats.ApprovedPresentDuration / 10000));
}
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(
const ui::DCRendererLayerParams& params,
gfx::Transform* visual_transform,
gfx::Rect* visual_clip_rect) {
// TODO(crbug.com/999747): Include an early out path in case the same dcomp
// surface is being presented.
ReleaseSwapChainResources();
auto dcomp_surface_proxy = params.dcomp_surface_proxy;
dcomp_surface_proxy->SetParentWindow(layer_tree_->window());
// Apply fullscreen rounding and transform to video and notify DCOMPTexture.
gfx::Rect overlay_onscreen_rect = params.quad_rect;
gfx::Size on_screen_size = overlay_onscreen_rect.size();
AdjustTargetToOptimalSizeIfNeeded(params, overlay_onscreen_rect,
&on_screen_size, visual_transform,
visual_clip_rect);
dcomp_surface_proxy->SetRect(visual_transform->MapRect(
gfx::Rect(params.quad_rect.origin(), on_screen_size)));
dcomp_surface_proxy->SetProtectedVideoType(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.
if (dcomp_surface_proxy->GetSize() == 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/999747): 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);
// 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 << ")";
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();
// 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;
content_.Reset();
}
}
bool SwapChainPresenter::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,
absl::optional<DXGI_HDR_METADATA_HDR10> stream_hdr_metadata) {
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 =
is_yuv_swapchain ? src_color_space : gfx::ColorSpace::CreateSRGB();
if (base::FeatureList::IsEnabled(kFallbackBT709VideoToBT601) &&
(output_color_space == gfx::ColorSpace::CreateREC709())) {
output_color_space = gfx::ColorSpace::CreateREC601();
}
if (content_is_hdr) {
output_color_space = gfx::ColorSpace::CreateHDR10();
}
VideoProcessorWrapper* video_processor_wrapper =
layer_tree_->InitializeVideoProcessor(
content_rect.size(), swap_chain_size_, output_color_space.IsHDR());
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;
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);
if (SUCCEEDED(swap_chain3->SetColorSpace1(output_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;
absl::optional<DXGI_HDR_METADATA_HDR10> display_metadata =
layer_tree_->GetHDRMetadataHelper()->GetDisplayMetadata();
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));
}
{
absl::optional<ScopedReleaseKeyedMutex> release_keyed_mutex;
if (keyed_mutex) {
// The producer may still be using this texture for a short period of
// time, so wait long enough to hopefully avoid glitches. For example,
// all levels of the texture share the same keyed mutex, so if the
// hardware decoder acquired the mutex to decode into a different array
// level then it still may block here temporarily.
const int kMaxSyncTimeMs = 1000;
HRESULT hr = keyed_mutex->AcquireSync(0, kMaxSyncTimeMs);
if (FAILED(hr)) {
DLOG(ERROR) << "Error acquiring keyed mutex: " << std::hex << hr;
return false;
}
release_keyed_mutex.emplace(keyed_mutex, 0);
}
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 (!layer_tree_->disable_vp_super_resolution()) {
if (gpu_vendor_id_ == 0x8086 &&
base::FeatureList::IsEnabled(features::kIntelVpSuperResolution)) {
ToggleIntelVpSuperResolution(video_context.Get(), video_processor.Get(),
is_on_battery_power_);
}
if (gpu_vendor_id_ == 0x10de &&
base::FeatureList::IsEnabled(features::kNvidiaVpSuperResolution)) {
ToggleNvidiaVpSuperResolution(
video_context.Get(), video_processor.Get(), is_on_battery_power_);
}
}
hr = video_context->VideoProcessorBlt(video_processor.Get(),
output_view_.Get(), 0, 1, &stream);
if (FAILED(hr)) {
DLOG(ERROR) << "VideoProcessorBlt failed with error 0x" << std::hex << hr;
return false;
}
}
return true;
}
void SwapChainPresenter::ReleaseSwapChainResources() {
last_presented_images_ = ui::DCRendererLayerParams::OverlayImages();
output_view_.Reset();
swap_chain_.Reset();
decode_surface_.Reset();
decode_swap_chain_.Reset();
decode_resource_.Reset();
swap_chain_handle_.Close();
staging_texture_.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);
DCHECK(!swap_chain_size.IsEmpty());
swap_chain_size_ = swap_chain_size;
swap_chain_protected_video_type_ = protected_video_type;
gpu_vendor_id_ = 0;
ReleaseSwapChainResources();
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 = BufferCount();
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 (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) {
std::ostringstream trace_event_stream;
trace_event_stream << "SwapChainPresenter::ReallocateSwapChain::"
<< DxgiFormatToString(swap_chain_format);
TRACE_EVENT0("gpu", trace_event_stream.str().c_str());
desc.Format = swap_chain_format;
desc.Flags = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
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;
if (DirectCompositionSwapChainTearingEnabled())
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
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;
}
}
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 (FAILED(hr)) {
DLOG(ERROR) << "Failed to get adapter desc with error 0x" << std::hex << hr;
} else {
gpu_vendor_id_ = adapter_desc.VendorId;
}
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 = 0;
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;
}
} // namespace gl