// 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 "media/gpu/windows/supported_profile_helpers.h"
#include <algorithm>
#include <memory>
#include "base/feature_list.h"
#include "base/trace_event/trace_event.h"
#include "base/win/windows_version.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "media/gpu/windows/av1_guids.h"
namespace media {
namespace {
class D3DVideoDeviceWrapper {
public:
virtual ~D3DVideoDeviceWrapper() = default;
virtual std::vector<GUID> GetVideoDecodeProfileGuids() = 0;
virtual bool IsResolutionSupported(const GUID& profile,
const gfx::Size& resolution,
DXGI_FORMAT format) = 0;
};
class D3D11VideoDeviceWrapper : public D3DVideoDeviceWrapper {
public:
explicit D3D11VideoDeviceWrapper(ComD3D11VideoDevice video_device)
: video_device_(video_device) {
CHECK(video_device);
}
~D3D11VideoDeviceWrapper() override = default;
std::vector<GUID> GetVideoDecodeProfileGuids() override {
std::vector<GUID> result;
UINT profile_count = video_device_->GetVideoDecoderProfileCount();
for (UINT i = 0; i < profile_count; i++) {
GUID profile_id;
if (SUCCEEDED(video_device_->GetVideoDecoderProfile(i, &profile_id))) {
result.push_back(profile_id);
}
}
return result;
}
bool IsResolutionSupported(const GUID& profile,
const gfx::Size& resolution,
DXGI_FORMAT format) override {
D3D11_VIDEO_DECODER_DESC desc = {
profile, // Guid
static_cast<UINT>(resolution.width()), // SampleWidth
static_cast<UINT>(resolution.height()), // SampleHeight
format // OutputFormat
};
UINT config_count;
return SUCCEEDED(video_device_->GetVideoDecoderConfigCount(
&desc, &config_count)) &&
config_count > 0;
}
private:
ComD3D11VideoDevice video_device_;
};
class D3D12VideoDeviceWrapper : public D3DVideoDeviceWrapper {
public:
explicit D3D12VideoDeviceWrapper(ComD3D12VideoDevice video_device)
: video_device_(video_device) {
CHECK(video_device);
}
~D3D12VideoDeviceWrapper() override = default;
std::vector<GUID> GetVideoDecodeProfileGuids() override {
std::vector<GUID> result;
D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILE_COUNT profile_count{};
HRESULT hr = video_device_->CheckFeatureSupport(
D3D12_FEATURE_VIDEO_DECODE_PROFILE_COUNT, &profile_count,
sizeof(profile_count));
if (FAILED(hr)) {
return {};
}
result.resize(profile_count.ProfileCount);
D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILES profiles{
.ProfileCount = profile_count.ProfileCount, .pProfiles = result.data()};
hr = video_device_->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_PROFILES,
&profiles, sizeof(profiles));
if (FAILED(hr)) {
return {};
}
return result;
}
bool IsResolutionSupported(const GUID& profile,
const gfx::Size& resolution,
DXGI_FORMAT format) override {
D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT support{
.Configuration = {profile},
.Width = static_cast<UINT>(resolution.width()),
.Height = static_cast<UINT>(resolution.height()),
.DecodeFormat = format};
return SUCCEEDED(video_device_->CheckFeatureSupport(
D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &support,
sizeof(support))) &&
support.SupportFlags == D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED;
}
private:
ComD3D12VideoDevice video_device_;
};
// Windows Media Foundation H.264 decoding does not support decoding videos
// with any dimension smaller than 48 pixels:
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd797815
//
// TODO(dalecurtis): These values are too low. We should only be using
// hardware decode for videos above ~360p, see http://crbug.com/684792.
constexpr gfx::Size kMinResolution(64, 64);
SupportedResolutionRange GetResolutionsForGUID(
D3DVideoDeviceWrapper* video_device_wrapper,
const GUID& decoder_guid,
const std::vector<gfx::Size>& resolutions_to_test,
DXGI_FORMAT format = DXGI_FORMAT_NV12,
const gfx::Size& min_resolution = kMinResolution) {
SupportedResolutionRange result;
// Verify input is in ascending order by height.
DCHECK(std::is_sorted(resolutions_to_test.begin(), resolutions_to_test.end(),
[](const gfx::Size& a, const gfx::Size& b) {
return a.height() < b.height();
}));
for (const auto& res : resolutions_to_test) {
// We've chosen the least expensive test for identifying if a given
// resolution is supported. Actually creating the VideoDecoder instance only
// fails ~0.4% of the time and the outcome is that we will offer support and
// then immediately fall back to software; e.g., playback still works. Since
// these calls can take hundreds of milliseconds to complete and are often
// executed during startup, this seems a reasonably trade off.
//
// See the deprecated histograms Media.DXVAVDA.GetDecoderConfigStatus which
// succeeds 100% of the time and Media.DXVAVDA.CreateDecoderStatus which
// only succeeds 99.6% of the time (in a 28 day aggregation).
if (!video_device_wrapper->IsResolutionSupported(decoder_guid, res,
format)) {
break;
}
result.max_landscape_resolution = res;
}
// The max supported portrait resolution should be just be a w/h flip of the
// max supported landscape resolution.
const gfx::Size flipped(result.max_landscape_resolution.height(),
result.max_landscape_resolution.width());
if (flipped == result.max_landscape_resolution ||
video_device_wrapper->IsResolutionSupported(decoder_guid, flipped,
format)) {
result.max_portrait_resolution = flipped;
}
if (!result.max_landscape_resolution.IsEmpty())
result.min_resolution = min_resolution;
return result;
}
} // namespace
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
GUID GetHEVCRangeExtensionPrivateGUID(uint8_t bitdepth,
VideoChromaSampling chroma_sampling) {
if (bitdepth == 8) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXVA_ModeHEVC_VLD_Main_Intel;
} else if (chroma_sampling == VideoChromaSampling::k422) {
// For D3D11/D3D12, 8b/10b-422 HEVC will share 10b-422 GUID no matter
// it is defined by Intel or DXVA spec(as part of Windows SDK).
return DXVA_ModeHEVC_VLD_Main422_10_Intel;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXVA_ModeHEVC_VLD_Main444_Intel;
}
} else if (bitdepth == 10) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXVA_ModeHEVC_VLD_Main10_Intel;
} else if (chroma_sampling == VideoChromaSampling::k422) {
return DXVA_ModeHEVC_VLD_Main422_10_Intel;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXVA_ModeHEVC_VLD_Main444_10_Intel;
}
} else if (bitdepth == 12) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXVA_ModeHEVC_VLD_Main12_Intel;
} else if (chroma_sampling == VideoChromaSampling::k422) {
return DXVA_ModeHEVC_VLD_Main422_12_Intel;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXVA_ModeHEVC_VLD_Main444_12_Intel;
}
}
return {};
}
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
DXGI_FORMAT GetOutputDXGIFormat(uint8_t bitdepth,
VideoChromaSampling chroma_sampling) {
if (bitdepth == 8) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXGI_FORMAT_NV12;
} else if (chroma_sampling == VideoChromaSampling::k422) {
return DXGI_FORMAT_YUY2;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXGI_FORMAT_AYUV;
}
} else if (bitdepth == 10) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXGI_FORMAT_P010;
} else if (chroma_sampling == VideoChromaSampling::k422) {
return DXGI_FORMAT_Y210;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXGI_FORMAT_Y410;
}
} else if (bitdepth == 12 || bitdepth == 16) {
if (chroma_sampling == VideoChromaSampling::k420) {
return DXGI_FORMAT_P016;
} else if (chroma_sampling == VideoChromaSampling::k422) {
return DXGI_FORMAT_Y216;
} else if (chroma_sampling == VideoChromaSampling::k444) {
return DXGI_FORMAT_Y416;
}
}
return {};
}
namespace {
SupportedResolutionRangeMap GetSupportedD3DVideoDecoderResolutions(
D3DVideoDeviceWrapper* video_device_wrapper,
const gpu::GpuDriverBugWorkarounds& workarounds) {
TRACE_EVENT0("gpu,startup", "GetSupportedD3DVideoDecoderResolutions");
SupportedResolutionRangeMap supported_resolutions;
// We always insert support for H.264 regardless of the tests below. It's old
// enough to be ubiquitous.
//
// On Windows 7 the maximum resolution supported by media foundation is
// 1920 x 1088. We use 1088 to account for 16x16 macro-blocks.
constexpr gfx::Size kDefaultMaxH264Resolution(1920, 1088);
SupportedResolutionRange h264_profile;
h264_profile.min_resolution = kMinResolution;
h264_profile.max_landscape_resolution = kDefaultMaxH264Resolution;
// We don't have a way to map DXVA support to specific H.264 profiles, so just
// mark all the common ones with the same level of support.
constexpr VideoCodecProfile kSupportedH264Profiles[] = {
H264PROFILE_BASELINE, H264PROFILE_MAIN, H264PROFILE_HIGH};
for (const auto profile : kSupportedH264Profiles)
supported_resolutions[profile] = h264_profile;
if (!video_device_wrapper) {
return supported_resolutions;
}
// To detect if a driver supports the desired resolutions, we try and create
// a DXVA decoder instance for that resolution and profile. If that succeeds
// we assume that the driver supports decoding for that resolution.
// Legacy AMD drivers with UVD3 or earlier and some Intel GPU's crash while
// creating surfaces larger than 1920 x 1088.
const std::vector<gfx::Size> kModernResolutions = {
gfx::Size(4096, 2160), gfx::Size(4096, 2304), gfx::Size(7680, 4320),
gfx::Size(8192, 4320), gfx::Size(8192, 8192)};
// Enumerate supported video profiles and look for the known profile for each
// codec. We first look through the the decoder profiles so we don't run N
// resolution tests for a profile that's unsupported.
for (const GUID& profile_id :
video_device_wrapper->GetVideoDecodeProfileGuids()) {
if (profile_id == D3D11_DECODER_PROFILE_H264_VLD_NOFGT) {
const auto result = GetResolutionsForGUID(
video_device_wrapper, profile_id,
{gfx::Size(2560, 1440), gfx::Size(3840, 2160), gfx::Size(4096, 2160),
gfx::Size(4096, 2304), gfx::Size(4096, 4096)});
// Unlike the other codecs, H.264 support is assumed up to 1080p, even if
// our initial queries fail. If they fail, we use the defaults set above.
if (!result.max_landscape_resolution.IsEmpty()) {
for (const auto profile : kSupportedH264Profiles)
supported_resolutions[profile] = result;
}
continue;
}
// Note: Each bit depth of AV1 uses a different DXGI_FORMAT, here we only
// test for the 8-bit one (NV12).
if (!workarounds.disable_accelerated_av1_decode) {
if (profile_id == DXVA_ModeAV1_VLD_Profile0) {
supported_resolutions[AV1PROFILE_PROFILE_MAIN] = GetResolutionsForGUID(
video_device_wrapper, profile_id, kModernResolutions);
continue;
}
if (profile_id == DXVA_ModeAV1_VLD_Profile1) {
supported_resolutions[AV1PROFILE_PROFILE_HIGH] = GetResolutionsForGUID(
video_device_wrapper, profile_id, kModernResolutions);
continue;
}
if (profile_id == DXVA_ModeAV1_VLD_Profile2) {
// TODO(dalecurtis): 12-bit profile 2 support is complicated. Ideally,
// we should test DXVA_ModeAV1_VLD_12bit_Profile2 and
// DXVA_ModeAV1_VLD_12bit_Profile2_420 when the bit depth of the content
// is 12-bit. However we don't know the bit depth or pixel format until
// too late. In these cases we'll end up initializing the decoder and
// failing on the first decode (which will trigger software fallback).
supported_resolutions[AV1PROFILE_PROFILE_PRO] = GetResolutionsForGUID(
video_device_wrapper, profile_id, kModernResolutions);
continue;
}
}
if (!workarounds.disable_accelerated_vp9_decode) {
if (profile_id == D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0) {
supported_resolutions[VP9PROFILE_PROFILE0] = GetResolutionsForGUID(
video_device_wrapper, profile_id, kModernResolutions);
continue;
}
// RS3 has issues with VP9.2 decoding. See https://crbug.com/937108.
if (!workarounds.disable_accelerated_vp9_profile2_decode &&
profile_id == D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2 &&
base::win::GetVersion() != base::win::Version::WIN10_RS3) {
supported_resolutions[VP9PROFILE_PROFILE2] =
GetResolutionsForGUID(video_device_wrapper, profile_id,
kModernResolutions, DXGI_FORMAT_P010);
continue;
}
}
if (!workarounds.disable_accelerated_vp8_decode &&
profile_id == D3D11_DECODER_PROFILE_VP8_VLD &&
base::FeatureList::IsEnabled(kMediaFoundationVP8Decoding)) {
// VP8 decoding is cheap on modern devices compared to other codecs, so
// much so that hardware decoding performance is actually worse at low
// resolutions than software decoding. See https://crbug.com/1136495.
constexpr gfx::Size kMinVp8Resolution = gfx::Size(640, 480);
supported_resolutions[VP8PROFILE_ANY] = GetResolutionsForGUID(
video_device_wrapper, profile_id,
{gfx::Size(4096, 2160), gfx::Size(4096, 2304), gfx::Size(4096, 4096)},
DXGI_FORMAT_NV12, kMinVp8Resolution);
continue;
}
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
if (!workarounds.disable_accelerated_hevc_decode &&
base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport)) {
// Per DirectX Video Acceleration Specification for High Efficiency Video
// Coding - 7.4, DXVA_ModeHEVC_VLD_Main GUID can be used for both main and
// main still picture profile.
if (profile_id == D3D11_DECODER_PROFILE_HEVC_VLD_MAIN) {
auto supported_resolution = GetResolutionsForGUID(
video_device_wrapper, profile_id, kModernResolutions);
supported_resolutions[HEVCPROFILE_MAIN] = supported_resolution;
supported_resolutions[HEVCPROFILE_MAIN_STILL_PICTURE] =
supported_resolution;
continue;
}
// For range extensions only test main10_422 with P010, and apply
// the same resolution range to main420 & main10_YUV420. Ideally we
// should be also testing against NV12 & Y210 for YUV422, and Y410 for
// YUV444 8/10/12 bit.
if (profile_id == DXVA_ModeHEVC_VLD_Main422_10_Intel) {
supported_resolutions[HEVCPROFILE_REXT] =
GetResolutionsForGUID(video_device_wrapper, profile_id,
kModernResolutions, DXGI_FORMAT_P010);
continue;
}
if (profile_id == D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10) {
supported_resolutions[HEVCPROFILE_MAIN10] =
GetResolutionsForGUID(video_device_wrapper, profile_id,
kModernResolutions, DXGI_FORMAT_P010);
continue;
}
}
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
}
return supported_resolutions;
}
} // namespace
SupportedResolutionRangeMap GetSupportedD3D11VideoDecoderResolutions(
ComD3D11Device device,
const gpu::GpuDriverBugWorkarounds& workarounds) {
ComD3D11VideoDevice video_device;
std::unique_ptr<D3D11VideoDeviceWrapper> video_device_wrapper;
if (device && SUCCEEDED(device.As(&video_device))) {
video_device_wrapper =
std::make_unique<D3D11VideoDeviceWrapper>(video_device);
}
return GetSupportedD3DVideoDecoderResolutions(video_device_wrapper.get(),
workarounds);
}
SupportedResolutionRangeMap GetSupportedD3D12VideoDecoderResolutions(
ComD3D12Device device,
const gpu::GpuDriverBugWorkarounds& workarounds) {
ComD3D12VideoDevice video_device;
std::unique_ptr<D3D12VideoDeviceWrapper> video_device_wrapper;
if (device && SUCCEEDED(device.As(&video_device))) {
video_device_wrapper =
std::make_unique<D3D12VideoDeviceWrapper>(video_device);
}
return GetSupportedD3DVideoDecoderResolutions(video_device_wrapper.get(),
workarounds);
}
} // namespace media