text: // Copyright 2019 The Chromium Authors. All rights reserved.
  // 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 <limits>
  #include <memory>
  #include <utility>
  
  #include <d3d9.h>
  #include <dxva2api.h>
  
  #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/gpu/windows/av1_guids.h"
  
  namespace {
  
  // R600, R700, Evergreen and Cayman AMD cards. These support DXVA via UVD3
  // or earlier, and don't handle resolutions higher than 1920 x 1088 well.
  //
  // NOTE: This list must be kept in sorted order.
  constexpr uint16_t kLegacyAmdGpuList[] = {
      0x130f, 0x6700, 0x6701, 0x6702, 0x6703, 0x6704, 0x6705, 0x6706, 0x6707,
      0x6708, 0x6709, 0x6718, 0x6719, 0x671c, 0x671d, 0x671f, 0x6720, 0x6721,
      0x6722, 0x6723, 0x6724, 0x6725, 0x6726, 0x6727, 0x6728, 0x6729, 0x6738,
      0x6739, 0x673e, 0x6740, 0x6741, 0x6742, 0x6743, 0x6744, 0x6745, 0x6746,
      0x6747, 0x6748, 0x6749, 0x674a, 0x6750, 0x6751, 0x6758, 0x6759, 0x675b,
      0x675d, 0x675f, 0x6760, 0x6761, 0x6762, 0x6763, 0x6764, 0x6765, 0x6766,
      0x6767, 0x6768, 0x6770, 0x6771, 0x6772, 0x6778, 0x6779, 0x677b, 0x6798,
      0x67b1, 0x6821, 0x683d, 0x6840, 0x6841, 0x6842, 0x6843, 0x6849, 0x6850,
      0x6858, 0x6859, 0x6880, 0x6888, 0x6889, 0x688a, 0x688c, 0x688d, 0x6898,
      0x6899, 0x689b, 0x689c, 0x689d, 0x689e, 0x68a0, 0x68a1, 0x68a8, 0x68a9,
      0x68b0, 0x68b8, 0x68b9, 0x68ba, 0x68be, 0x68bf, 0x68c0, 0x68c1, 0x68c7,
      0x68c8, 0x68c9, 0x68d8, 0x68d9, 0x68da, 0x68de, 0x68e0, 0x68e1, 0x68e4,
      0x68e5, 0x68e8, 0x68e9, 0x68f1, 0x68f2, 0x68f8, 0x68f9, 0x68fa, 0x68fe,
      0x9400, 0x9401, 0x9402, 0x9403, 0x9405, 0x940a, 0x940b, 0x940f, 0x9440,
      0x9441, 0x9442, 0x9443, 0x9444, 0x9446, 0x944a, 0x944b, 0x944c, 0x944e,
      0x9450, 0x9452, 0x9456, 0x945a, 0x945b, 0x945e, 0x9460, 0x9462, 0x946a,
      0x946b, 0x947a, 0x947b, 0x9480, 0x9487, 0x9488, 0x9489, 0x948a, 0x948f,
      0x9490, 0x9491, 0x9495, 0x9498, 0x949c, 0x949e, 0x949f, 0x94a0, 0x94a1,
      0x94a3, 0x94b1, 0x94b3, 0x94b4, 0x94b5, 0x94b9, 0x94c0, 0x94c1, 0x94c3,
      0x94c4, 0x94c5, 0x94c6, 0x94c7, 0x94c8, 0x94c9, 0x94cb, 0x94cc, 0x94cd,
      0x9500, 0x9501, 0x9504, 0x9505, 0x9506, 0x9507, 0x9508, 0x9509, 0x950f,
      0x9511, 0x9515, 0x9517, 0x9519, 0x9540, 0x9541, 0x9542, 0x954e, 0x954f,
      0x9552, 0x9553, 0x9555, 0x9557, 0x955f, 0x9580, 0x9581, 0x9583, 0x9586,
      0x9587, 0x9588, 0x9589, 0x958a, 0x958b, 0x958c, 0x958d, 0x958e, 0x958f,
      0x9590, 0x9591, 0x9593, 0x9595, 0x9596, 0x9597, 0x9598, 0x9599, 0x959b,
      0x95c0, 0x95c2, 0x95c4, 0x95c5, 0x95c6, 0x95c7, 0x95c9, 0x95cc, 0x95cd,
      0x95ce, 0x95cf, 0x9610, 0x9611, 0x9612, 0x9613, 0x9614, 0x9615, 0x9616,
      0x9640, 0x9641, 0x9642, 0x9643, 0x9644, 0x9645, 0x9647, 0x9648, 0x9649,
      0x964a, 0x964b, 0x964c, 0x964e, 0x964f, 0x9710, 0x9711, 0x9712, 0x9713,
      0x9714, 0x9715, 0x9802, 0x9803, 0x9804, 0x9805, 0x9806, 0x9807, 0x9808,
      0x9809, 0x980a, 0x9830, 0x983d, 0x9850, 0x9851, 0x9874, 0x9900, 0x9901,
      0x9903, 0x9904, 0x9905, 0x9906, 0x9907, 0x9908, 0x9909, 0x990a, 0x990b,
      0x990c, 0x990d, 0x990e, 0x990f, 0x9910, 0x9913, 0x9917, 0x9918, 0x9919,
      0x9990, 0x9991, 0x9992, 0x9993, 0x9994, 0x9995, 0x9996, 0x9997, 0x9998,
      0x9999, 0x999a, 0x999b, 0x999c, 0x999d, 0x99a0, 0x99a2, 0x99a4};
  
  // Legacy Intel GPUs which have trouble even querying if resolutions higher than
  // 1920 x 1088 are supported. Updated based on crash reports.
  //
  // NOTE: This list must be kept in sorted order.
  constexpr uint16_t kLegacyIntelGpuList[] = {
      0x102, 0x106, 0x116, 0x126, 0x152, 0x156, 0x166,
      0x402, 0x406, 0x416, 0x41e, 0xa06, 0xa16, 0xf31,
  };
  
  // 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);
  
  // Certain AMD GPU drivers like R600, R700, Evergreen and Cayman and some second
  // generation Intel GPU drivers crash if we create a video device with a
  // resolution higher then 1920 x 1088. This function checks if the GPU is in
  // this list and if yes returns true.
  bool IsLegacyGPU(ID3D11Device* device) {
    DCHECK(std::is_sorted(std::begin(kLegacyAmdGpuList),
                          std::end(kLegacyAmdGpuList)));
    DCHECK(std::is_sorted(std::begin(kLegacyIntelGpuList),
                          std::end(kLegacyIntelGpuList)));
  
    constexpr int kAMDGPUId1 = 0x1002;
    constexpr int kAMDGPUId2 = 0x1022;
    constexpr int kIntelGPU = 0x8086;
  
    Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
    HRESULT hr = device->QueryInterface(IID_PPV_ARGS(&dxgi_device));
    if (FAILED(hr))
      return true;
  
    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
    hr = dxgi_device->GetAdapter(&adapter);
    if (FAILED(hr))
      return true;
  
    DXGI_ADAPTER_DESC adapter_desc = {};
    hr = adapter->GetDesc(&adapter_desc);
    if (FAILED(hr))
      return true;
  
    // All the values in the legacy gpu list are uint16_t.
    if (adapter_desc.DeviceId > std::numeric_limits<uint16_t>::max())
      return false;
  
    const uint16_t device_id = adapter_desc.DeviceId;
  
    // We check if the device is an Intel or an AMD device and whether it is in
    // the global list defined by the kLegacyAmdGpuList and kLegacyIntelGpuList
    // arrays above. If yes then the device is treated as a legacy device.
    if (adapter_desc.VendorId == kAMDGPUId1 ||
        adapter_desc.VendorId == kAMDGPUId2) {
      if (std::binary_search(std::begin(kLegacyAmdGpuList),
                             std::end(kLegacyAmdGpuList), device_id)) {
        return true;
      }
    } else if (adapter_desc.VendorId == kIntelGPU) {
      if (std::binary_search(std::begin(kLegacyIntelGpuList),
                             std::end(kLegacyIntelGpuList), device_id)) {
        return true;
      }
    }
  
    return false;
  }
  
  bool IsResolutionSupportedForDevice(const gfx::Size& resolution_to_test,
                                      const GUID& decoder_guid,
                                      ID3D11VideoDevice* video_device,
                                      DXGI_FORMAT format) {
    D3D11_VIDEO_DECODER_DESC desc = {
        decoder_guid,                                    // Guid
        static_cast<UINT>(resolution_to_test.width()),   // SampleWidth
        static_cast<UINT>(resolution_to_test.height()),  // SampleHeight
        format                                           // OutputFormat
    };
  
    // 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).
    UINT config_count;
    return SUCCEEDED(
               video_device->GetVideoDecoderConfigCount(&desc, &config_count)) &&
           config_count > 0;
  }
  
  media::SupportedResolutionRange GetResolutionsForGUID(
      ID3D11VideoDevice* video_device,
      const GUID& decoder_guid,
      const std::vector<gfx::Size>& resolutions_to_test,
      DXGI_FORMAT format = DXGI_FORMAT_NV12,
      const gfx::Size& min_resolution = kMinResolution) {
    media::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) {
      if (!IsResolutionSupportedForDevice(res, decoder_guid, video_device,
                                          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 ||
        IsResolutionSupportedForDevice(flipped, decoder_guid, video_device,
                                       format)) {
      result.max_portrait_resolution = flipped;
    }
  
    if (!result.max_landscape_resolution.IsEmpty())
      result.min_resolution = min_resolution;
  
    return result;
  }
  
  }  // namespace
  
  namespace media {
  
  SupportedResolutionRangeMap GetSupportedD3D11VideoDecoderResolutions(
      ComD3D11Device device,
      const gpu::GpuDriverBugWorkarounds& workarounds) {
    TRACE_EVENT0("gpu,startup", "GetSupportedD3D11VideoDecoderResolutions");
    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 (base::win::GetVersion() <= base::win::Version::WIN7)
      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.
    if (!device || IsLegacyGPU(device.Get()))
      return supported_resolutions;
  
    ComD3D11VideoDevice video_device;
    if (FAILED(device.As(&video_device)))
      return supported_resolutions;
  
    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.
    UINT profile_count = video_device->GetVideoDecoderProfileCount();
    for (UINT i = 0; i < profile_count; i++) {
      GUID profile_id;
      if (FAILED(video_device->GetVideoDecoderProfile(i, &profile_id)))
        continue;
  
      if (profile_id == D3D11_DECODER_PROFILE_H264_VLD_NOFGT) {
        const auto result = GetResolutionsForGUID(
            video_device.Get(), 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.Get(), profile_id, kModernResolutions);
          continue;
        }
        if (profile_id == DXVA_ModeAV1_VLD_Profile1) {
          supported_resolutions[AV1PROFILE_PROFILE_HIGH] = GetResolutionsForGUID(
              video_device.Get(), 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.Get(), profile_id, kModernResolutions);
          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.Get(), profile_id,
            {gfx::Size(4096, 2160), gfx::Size(4096, 2304), gfx::Size(4096, 4096)},
            DXGI_FORMAT_NV12, kMinVp8Resolution);
        continue;
      }
  
      if (workarounds.disable_accelerated_vp9_decode)
        continue;
  
      if (profile_id == D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0) {
        supported_resolutions[VP9PROFILE_PROFILE0] = GetResolutionsForGUID(
            video_device.Get(), 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.Get(), profile_id, kModernResolutions, DXGI_FORMAT_P010);
        continue;
      }
    }
  
    return supported_resolutions;
  }
  
  }  // namespace media
  