// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/config/gpu_info_collector.h"
// C system before C++ system.
#include <DirectML.h>
#include <d3d11.h>
#include <d3d11_3.h>
#include <dxgi.h>
#include <dxgi1_6.h>
#include <stddef.h>
#include <stdint.h>
#include <vulkan/vulkan.h>
#include <wrl/client.h>
#include "base/file_version_info_win.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/win/scoped_com_initializer.h"
#include "build/branding_buildflags.h"
#include "gpu/config/gpu_util.h"
#include "third_party/microsoft_dxheaders/src/include/directx/d3d12.h"
#include "third_party/microsoft_dxheaders/src/include/directx/dxcore.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/gl_display.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/gl_utils.h"
namespace gpu {
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// This should match enum D3D12FeatureLevel in
// \tools\metrics\histograms\enums.xml
enum class D3D12FeatureLevel {
kD3DFeatureLevelUnknown = 0,
kD3DFeatureLevel_12_0 = 1,
kD3DFeatureLevel_12_1 = 2,
kD3DFeatureLevel_11_0 = 3,
kD3DFeatureLevel_11_1 = 4,
kD3DFeatureLevel_12_2 = 5,
kMaxValue = kD3DFeatureLevel_12_2,
};
inline D3D12FeatureLevel ConvertToHistogramFeatureLevel(
uint32_t d3d_feature_level) {
switch (d3d_feature_level) {
case 0:
return D3D12FeatureLevel::kD3DFeatureLevelUnknown;
case D3D_FEATURE_LEVEL_12_0:
return D3D12FeatureLevel::kD3DFeatureLevel_12_0;
case D3D_FEATURE_LEVEL_12_1:
return D3D12FeatureLevel::kD3DFeatureLevel_12_1;
case D3D_FEATURE_LEVEL_12_2:
return D3D12FeatureLevel::kD3DFeatureLevel_12_2;
case D3D_FEATURE_LEVEL_11_0:
return D3D12FeatureLevel::kD3DFeatureLevel_11_0;
case D3D_FEATURE_LEVEL_11_1:
return D3D12FeatureLevel::kD3DFeatureLevel_11_1;
default:
NOTREACHED_IN_MIGRATION();
return D3D12FeatureLevel::kD3DFeatureLevelUnknown;
}
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class D3D12ShaderModel {
kUnknownOrNoD3D12Devices = 0,
kD3DShaderModel_5_1 = 1,
kD3DShaderModel_6_0 = 2,
kD3DShaderModel_6_1 = 3,
kD3DShaderModel_6_2 = 4,
kD3DShaderModel_6_3 = 5,
kD3DShaderModel_6_4 = 6,
kD3DShaderModel_6_5 = 7,
kD3DShaderModel_6_6 = 8,
kD3DShaderModel_6_7 = 9,
kMaxValue = kD3DShaderModel_6_7,
};
D3D12ShaderModel ConvertToHistogramShaderVersion(uint32_t version) {
switch (version) {
case 0:
return D3D12ShaderModel::kUnknownOrNoD3D12Devices;
case D3D_SHADER_MODEL_5_1:
return D3D12ShaderModel::kD3DShaderModel_5_1;
case D3D_SHADER_MODEL_6_0:
return D3D12ShaderModel::kD3DShaderModel_6_0;
case D3D_SHADER_MODEL_6_1:
return D3D12ShaderModel::kD3DShaderModel_6_1;
case D3D_SHADER_MODEL_6_2:
return D3D12ShaderModel::kD3DShaderModel_6_2;
case D3D_SHADER_MODEL_6_3:
return D3D12ShaderModel::kD3DShaderModel_6_3;
case D3D_SHADER_MODEL_6_4:
return D3D12ShaderModel::kD3DShaderModel_6_4;
case D3D_SHADER_MODEL_6_5:
return D3D12ShaderModel::kD3DShaderModel_6_5;
case D3D_SHADER_MODEL_6_6:
return D3D12ShaderModel::kD3DShaderModel_6_6;
case D3D_SHADER_MODEL_6_7:
return D3D12ShaderModel::kD3DShaderModel_6_7;
default:
NOTREACHED_IN_MIGRATION();
return D3D12ShaderModel::kUnknownOrNoD3D12Devices;
}
}
OverlaySupport FlagsToOverlaySupport(bool overlays_supported, UINT flags) {
if (flags & DXGI_OVERLAY_SUPPORT_FLAG_SCALING)
return OverlaySupport::kScaling;
if (flags & DXGI_OVERLAY_SUPPORT_FLAG_DIRECT)
return OverlaySupport::kDirect;
if (overlays_supported)
return OverlaySupport::kSoftware;
return OverlaySupport::kNone;
}
bool GetActiveAdapterLuid(LUID* luid) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
if (!d3d11_device)
return false;
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
if (FAILED(d3d11_device.As(&dxgi_device)))
return false;
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
if (FAILED(dxgi_device->GetAdapter(&adapter)))
return false;
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, adapter->GetDesc(&desc));
// Zero isn't a valid LUID.
if (desc.AdapterLuid.HighPart == 0 && desc.AdapterLuid.LowPart == 0)
return false;
*luid = desc.AdapterLuid;
return true;
}
} // namespace
// This has to be called after a context is created, active GPU is identified,
// and GPU driver bug workarounds are computed again. Otherwise the workaround
// |disable_direct_composition| may not be correctly applied.
// Also, this has to be called after falling back to SwiftShader decision is
// finalized because this function depends on GL is ANGLE's GLES or not.
void CollectHardwareOverlayInfo(OverlayInfo* overlay_info) {
if (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE) {
overlay_info->direct_composition = gl::DirectCompositionSupported();
overlay_info->supports_overlays = gl::DirectCompositionOverlaysSupported();
overlay_info->nv12_overlay_support = FlagsToOverlaySupport(
overlay_info->supports_overlays,
gl::GetDirectCompositionOverlaySupportFlags(DXGI_FORMAT_NV12));
overlay_info->yuy2_overlay_support = FlagsToOverlaySupport(
overlay_info->supports_overlays,
gl::GetDirectCompositionOverlaySupportFlags(DXGI_FORMAT_YUY2));
overlay_info->bgra8_overlay_support =
FlagsToOverlaySupport(overlay_info->supports_overlays,
gl::GetDirectCompositionOverlaySupportFlags(
DXGI_FORMAT_B8G8R8A8_UNORM));
overlay_info->rgb10a2_overlay_support =
FlagsToOverlaySupport(overlay_info->supports_overlays,
gl::GetDirectCompositionOverlaySupportFlags(
DXGI_FORMAT_R10G10B10A2_UNORM));
overlay_info->p010_overlay_support = FlagsToOverlaySupport(
overlay_info->supports_overlays,
gl::GetDirectCompositionOverlaySupportFlags(DXGI_FORMAT_P010));
}
}
std::string DriverVersionToString(LARGE_INTEGER driver_version) {
return base::StringPrintf("%d.%d.%d.%d", HIWORD(driver_version.HighPart),
LOWORD(driver_version.HighPart),
HIWORD(driver_version.LowPart),
LOWORD(driver_version.LowPart));
}
void CollectNPUInformation(GPUInfo* gpu_info) {
// Enumerate all dxcore adapters to retrieve the NPUs.
base::ScopedNativeLibrary dxcore_library(
base::LoadSystemLibrary(L"DXCore.dll"));
if (!dxcore_library.is_valid()) {
return;
}
using DXCoreCreateAdapterFactoryProc =
decltype(static_cast<STDMETHODIMP (*)(REFIID, void**)>(
DXCoreCreateAdapterFactory));
DXCoreCreateAdapterFactoryProc dxcore_create_adapter_factory_proc =
reinterpret_cast<DXCoreCreateAdapterFactoryProc>(
dxcore_library.GetFunctionPointer("DXCoreCreateAdapterFactory"));
if (!dxcore_create_adapter_factory_proc) {
return;
}
Microsoft::WRL::ComPtr<IDXCoreAdapterFactory> dxcore_factory;
HRESULT hr =
dxcore_create_adapter_factory_proc(IID_PPV_ARGS(&dxcore_factory));
if (FAILED(hr)) {
return;
}
// First query for NPU devices that satisfy the generic machine learning
// property. Note this must be done as a separate query from core compute
// because `CreateAdapterList()` returns the logical intersection of all
// filter properties, not union.
const std::array<GUID, 1> dx_guids_generic_ml = {
DXCORE_ADAPTER_ATTRIBUTE_D3D12_GENERIC_ML};
Microsoft::WRL::ComPtr<IDXCoreAdapterList> adapter_list;
hr = dxcore_factory->CreateAdapterList(dx_guids_generic_ml.size(),
dx_guids_generic_ml.data(),
IID_PPV_ARGS(&adapter_list));
if (FAILED(hr)) {
return;
}
uint32_t adapter_count = adapter_list->GetAdapterCount();
// If no generic ML devices were found, then retry with the core compute
// filter, getting an adapter list that only contains core-compute capable
// devices.
if (adapter_count == 0) {
adapter_list.Reset();
const std::array<GUID, 1> dx_guids_core_compute = {
DXCORE_ADAPTER_ATTRIBUTE_D3D12_CORE_COMPUTE};
hr = dxcore_factory->CreateAdapterList(dx_guids_core_compute.size(),
dx_guids_core_compute.data(),
IID_PPV_ARGS(&adapter_list));
if (FAILED(hr)) {
return;
}
adapter_count = adapter_list->GetAdapterCount();
}
for (uint32_t adapter_index = 0; adapter_index < adapter_count;
++adapter_index) {
Microsoft::WRL::ComPtr<IDXCoreAdapter> dxcore_adapter;
hr = adapter_list->GetAdapter(adapter_index, IID_PPV_ARGS(&dxcore_adapter));
if (FAILED(hr)) {
return;
}
// Because GPUs usually also have the core-compute capability, then we need
// to filter out the GPUs with `DXCORE_ADAPTER_ATTRIBUTE_D3D12_GRAPHICS` or
// `DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS` attribute to
// get the NPUs.
bool is_hardware;
if (SUCCEEDED(dxcore_adapter->GetProperty(DXCoreAdapterProperty::IsHardware,
&is_hardware)) &&
is_hardware &&
!dxcore_adapter->IsAttributeSupported(
DXCORE_ADAPTER_ATTRIBUTE_D3D12_GRAPHICS) &&
!dxcore_adapter->IsAttributeSupported(
DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS)) {
GPUInfo::GPUDevice device;
DXCoreHardwareID hardware_id;
if (SUCCEEDED(dxcore_adapter->GetProperty(
DXCoreAdapterProperty::HardwareID, &hardware_id))) {
device.vendor_id = hardware_id.vendorID;
device.device_id = hardware_id.deviceID;
}
uint64_t raw_driver_version;
if (SUCCEEDED(dxcore_adapter->GetProperty(
DXCoreAdapterProperty::DriverVersion, &raw_driver_version))) {
LARGE_INTEGER driver_version;
driver_version.QuadPart = static_cast<LONGLONG>(raw_driver_version);
device.driver_version = DriverVersionToString(driver_version);
}
LUID instance_luid;
if (SUCCEEDED(dxcore_adapter->GetProperty(
DXCoreAdapterProperty::InstanceLuid, &instance_luid))) {
device.luid =
CHROME_LUID{instance_luid.LowPart, instance_luid.HighPart};
}
gpu_info->npus.push_back(device);
}
}
}
bool CollectDriverInfoD3D(GPUInfo* gpu_info) {
TRACE_EVENT0("gpu", "CollectDriverInfoD3D");
CollectNPUInformation(gpu_info);
Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
HRESULT hr = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory));
if (FAILED(hr))
return false;
UINT i;
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
for (i = 0; SUCCEEDED(dxgi_factory->EnumAdapters(i, &dxgi_adapter)); i++) {
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, dxgi_adapter->GetDesc(&desc));
GPUInfo::GPUDevice device;
device.vendor_id = desc.VendorId;
device.device_id = desc.DeviceId;
device.sub_sys_id = desc.SubSysId;
device.revision = desc.Revision;
device.luid =
CHROME_LUID{desc.AdapterLuid.LowPart, desc.AdapterLuid.HighPart};
LARGE_INTEGER umd_version;
hr = dxgi_adapter->CheckInterfaceSupport(__uuidof(IDXGIDevice),
&umd_version);
if (SUCCEEDED(hr)) {
device.driver_version = DriverVersionToString(umd_version);
} else {
DLOG(ERROR) << "Unable to retrieve the umd version of adapter: "
<< desc.Description << " HR: " << std::hex << hr;
}
if (i == 0) {
gpu_info->gpu = device;
} else {
gpu_info->secondary_gpus.push_back(device);
}
}
Microsoft::WRL::ComPtr<IDXGIFactory6> dxgi_factory6;
if (gpu_info->GpuCount() > 1 && SUCCEEDED(dxgi_factory.As(&dxgi_factory6))) {
if (SUCCEEDED(dxgi_factory6->EnumAdapterByGpuPreference(
0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
IID_PPV_ARGS(&dxgi_adapter)))) {
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, dxgi_adapter->GetDesc(&desc));
GPUInfo::GPUDevice* device = gpu_info->FindGpuByLuid(
desc.AdapterLuid.LowPart, desc.AdapterLuid.HighPart);
DCHECK(device);
device->gpu_preference = gl::GpuPreference::kHighPerformance;
}
if (SUCCEEDED(dxgi_factory6->EnumAdapterByGpuPreference(
0, DXGI_GPU_PREFERENCE_MINIMUM_POWER,
IID_PPV_ARGS(&dxgi_adapter)))) {
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, dxgi_adapter->GetDesc(&desc));
GPUInfo::GPUDevice* device = gpu_info->FindGpuByLuid(
desc.AdapterLuid.LowPart, desc.AdapterLuid.HighPart);
DCHECK(device);
device->gpu_preference = gl::GpuPreference::kLowPower;
}
}
return i > 0;
}
// CanCreateD3D12Device returns true/false depending on whether D3D12 device
// creation should be attempted on the passed in adapter. Returns false if there
// are known driver bugs.
bool CanCreateD3D12Device(IDXGIAdapter* dxgi_adapter) {
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, dxgi_adapter->GetDesc(&desc));
// Known driver bugs are Intel-only. Expand in the future, as necessary, for
// other IHVs.
if (desc.VendorId != 0x8086)
return true;
LARGE_INTEGER umd_version;
HRESULT hr =
dxgi_adapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &umd_version);
if (FAILED(hr)) {
return false;
}
// On certain Intel drivers, the driver will crash if you call
// D3D12CreateDevice and the command line of the process is greater than 1024
// bytes. 100.9416 is the first driver to introduce the bug, while 100.9664 is
// the first driver to fix it.
if (HIWORD(umd_version.LowPart) == 100 &&
LOWORD(umd_version.LowPart) >= 9416 &&
LOWORD(umd_version.LowPart) < 9664) {
const char* command_line = GetCommandLineA();
const size_t command_line_length = strlen(command_line);
// Check for 1023 since strlen doesn't include the null terminator.
if (command_line_length > 1023) {
return false;
}
}
return true;
}
// DirectX 12 are included with Windows 10 and Server 2016.
void GetGpuSupportedDirectXVersion(uint32_t& d3d12_feature_level,
uint32_t& highest_shader_model_version,
uint32_t& directml_feature_level) {
TRACE_EVENT0("gpu", "GetGpuSupportedDirectXVersion");
// Initialize to 0 to indicated an unknown type in UMA.
d3d12_feature_level = 0;
highest_shader_model_version = 0;
directml_feature_level = 0;
base::ScopedNativeLibrary d3d12_library(
base::FilePath(FILE_PATH_LITERAL("d3d12.dll")));
if (!d3d12_library.is_valid())
return;
// The order of feature levels to attempt to create in D3D CreateDevice
const D3D_FEATURE_LEVEL feature_levels[] = {
D3D_FEATURE_LEVEL_12_2, D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
PFN_D3D12_CREATE_DEVICE d3d12_create_device_proc =
reinterpret_cast<PFN_D3D12_CREATE_DEVICE>(
d3d12_library.GetFunctionPointer("D3D12CreateDevice"));
Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device;
if (d3d12_create_device_proc) {
Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
HRESULT hr = ::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory));
if (FAILED(hr)) {
return;
}
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
hr = dxgi_factory->EnumAdapters(0, &dxgi_adapter);
if (FAILED(hr)) {
return;
}
if (!CanCreateD3D12Device(dxgi_adapter.Get())) {
return;
}
// For the default adapter only: EnumAdapters(0, ...).
// Check to see if the adapter supports Direct3D 12.
for (auto level : feature_levels) {
if (SUCCEEDED(d3d12_create_device_proc(dxgi_adapter.Get(), level,
_uuidof(ID3D12Device),
&d3d12_device))) {
d3d12_feature_level = level;
break;
}
}
// Query the maximum supported shader model version.
if (d3d12_device) {
// As per the documentation, CheckFeatureSupport will return E_INVALIDARG
// if the shader model is not known by the current runtime, so we loop in
// decreasing shader model version to determine the highest supported
// model:
// https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_feature_data_shader_model.
const D3D_SHADER_MODEL shader_models[] = {
D3D_SHADER_MODEL_6_7, D3D_SHADER_MODEL_6_6, D3D_SHADER_MODEL_6_5,
D3D_SHADER_MODEL_6_4, D3D_SHADER_MODEL_6_3, D3D_SHADER_MODEL_6_2,
D3D_SHADER_MODEL_6_1, D3D_SHADER_MODEL_6_0, D3D_SHADER_MODEL_5_1,
};
for (auto model : shader_models) {
D3D12_FEATURE_DATA_SHADER_MODEL shader_model_data = {};
shader_model_data.HighestShaderModel = model;
if (SUCCEEDED(d3d12_device->CheckFeatureSupport(
D3D12_FEATURE_SHADER_MODEL, &shader_model_data,
sizeof(shader_model_data)))) {
highest_shader_model_version = shader_model_data.HighestShaderModel;
break;
}
}
// DirectML is supported starting on D3D12.
base::ScopedNativeLibrary dml_library(
base::ScopedNativeLibrary(base::LoadSystemLibrary(L"directml.dll")));
if (!dml_library.is_valid()) {
return;
}
// On older versions of windows DMLCreateDevice accepts a different
// number of parameters. We should use DMLCreateDevice1 which always
// takes the same number of parameters to ensure consistency
// among all versions of windows.
auto dml_create_device1_proc =
reinterpret_cast<decltype(DMLCreateDevice1)*>(
dml_library.GetFunctionPointer("DMLCreateDevice1"));
if (!dml_create_device1_proc) {
return;
}
Microsoft::WRL::ComPtr<IDMLDevice> dml_device;
if (FAILED(dml_create_device1_proc(
d3d12_device.Get(), DML_CREATE_DEVICE_FLAG_NONE,
DML_FEATURE_LEVEL_1_0, IID_PPV_ARGS(&dml_device)))) {
return;
}
DML_FEATURE_LEVEL feature_levels_requested[] = {
DML_FEATURE_LEVEL_1_0, DML_FEATURE_LEVEL_2_0, DML_FEATURE_LEVEL_2_1,
DML_FEATURE_LEVEL_3_0, DML_FEATURE_LEVEL_3_1, DML_FEATURE_LEVEL_4_0,
DML_FEATURE_LEVEL_4_1, DML_FEATURE_LEVEL_5_0};
DML_FEATURE_QUERY_FEATURE_LEVELS feature_levels_query = {
std::size(feature_levels_requested), feature_levels_requested};
DML_FEATURE_DATA_FEATURE_LEVELS feature_levels_supported = {};
if (SUCCEEDED(dml_device->CheckFeatureSupport(
DML_FEATURE_FEATURE_LEVELS, sizeof(feature_levels_query),
&feature_levels_query, sizeof(feature_levels_supported),
&feature_levels_supported))) {
directml_feature_level =
feature_levels_supported.MaxSupportedFeatureLevel;
}
}
}
}
// The old graphics drivers are installed to the Windows system directory
// c:\windows\system32 or SysWOW64. Those versions can be detected without
// specifying the absolute directory. For a newer version (>= ~2018), this won't
// work. The newer graphics drivers are located in
// c:\windows\system32\DriverStore\FileRepository\xxx.infxxx which contains a
// different number at each installation
bool BadAMDVulkanDriverVersion() {
// Both 32-bit and 64-bit dll are broken. If 64-bit doesn't exist,
// 32-bit dll will be used to detect the AMD Vulkan driver.
const base::FilePath kAmdDriver64(FILE_PATH_LITERAL("amdvlk64.dll"));
const base::FilePath kAmdDriver32(FILE_PATH_LITERAL("amdvlk32.dll"));
std::unique_ptr<FileVersionInfoWin> file_version_info =
FileVersionInfoWin::CreateFileVersionInfoWin(kAmdDriver64);
if (!file_version_info) {
file_version_info =
FileVersionInfoWin::CreateFileVersionInfoWin(kAmdDriver32);
if (!file_version_info)
return false;
}
base::Version amd_version = file_version_info->GetFileVersion();
// From the Canary crash logs, the broken amdvlk64.dll versions
// are 1.0.39.0, 1.0.51.0 and 1.0.54.0. In the manual test, version
// 9.2.10.1 dated 12/6/2017 works and version 1.0.54.0 dated 11/2/1017
// crashes. All version numbers small than 1.0.54.0 will be marked as
// broken.
const base::Version kBadAMDVulkanDriverVersion("1.0.54.0");
// CompareTo() returns -1, 0, 1 for <, ==, >.
if (amd_version.CompareTo(kBadAMDVulkanDriverVersion) != 1)
return true;
return false;
}
// Vulkan 1.1 was released by the Khronos Group on March 7, 2018.
// Blocklist all driver versions without Vulkan 1.1 support and those that cause
// lots of crashes.
bool BadGraphicsDriverVersions(const gpu::GPUInfo::GPUDevice& gpu_device) {
// GPU Device info is not available in gpu_integration_test.info-collection
// with --no-delay-for-dx12-vulkan-info-collection.
if (gpu_device.driver_version.empty())
return false;
base::Version driver_version(gpu_device.driver_version);
if (!driver_version.IsValid())
return true;
// AMD Vulkan drivers - amdvlk64.dll
constexpr uint32_t kAMDVendorId = 0x1002;
if (gpu_device.vendor_id == kAMDVendorId) {
// 26.20.12028.2 (2019)- number of crashes 1,188,048 as of 5/14/2020.
// Returns -1, 0, 1 for <, ==, >.
if (driver_version.CompareTo(base::Version("26.20.12028.2")) == 0)
return true;
}
return false;
}
bool InitVulkan(base::NativeLibrary* vulkan_library,
PFN_vkGetInstanceProcAddr* vkGetInstanceProcAddr,
PFN_vkCreateInstance* vkCreateInstance,
uint32_t* vulkan_version) {
*vulkan_version = 0;
*vulkan_library =
base::LoadNativeLibrary(base::FilePath(L"vulkan-1.dll"), nullptr);
if (!(*vulkan_library)) {
return false;
}
*vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(
base::GetFunctionPointerFromNativeLibrary(*vulkan_library,
"vkGetInstanceProcAddr"));
if (*vkGetInstanceProcAddr) {
*vulkan_version = VK_MAKE_VERSION(1, 0, 0);
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
vkEnumerateInstanceVersion =
reinterpret_cast<PFN_vkEnumerateInstanceVersion>(
(*vkGetInstanceProcAddr)(nullptr, "vkEnumerateInstanceVersion"));
// If the vkGetInstanceProcAddr returns nullptr for
// vkEnumerateInstanceVersion, it is a Vulkan 1.0 implementation.
if (!vkEnumerateInstanceVersion) {
return false;
}
// Return value can be VK_SUCCESS or VK_ERROR_OUT_OF_HOST_MEMORY.
if (vkEnumerateInstanceVersion(vulkan_version) != VK_SUCCESS) {
return false;
}
// The minimum version required for Vulkan to be enabled is 1.1.0.
// No further queries will be called for early versions. They are unstable
// and might cause crashes.
if (*vulkan_version < VK_MAKE_VERSION(1, 1, 0)) {
return false;
}
*vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(
(*vkGetInstanceProcAddr)(nullptr, "vkCreateInstance"));
if (*vkCreateInstance)
return true;
}
// From the crash reports, unloading the library here might cause a crash in
// the Vulkan loader or in the Vulkan driver. To work around it, don't
// explicitly unload the DLL. Instead, GPU process shutdown will unload all
// loaded DLLs.
// base::UnloadNativeLibrary(*vulkan_library);
return false;
}
bool InitVulkanInstanceProc(
const VkInstance& vk_instance,
const PFN_vkGetInstanceProcAddr& vkGetInstanceProcAddr,
PFN_vkEnumeratePhysicalDevices* vkEnumeratePhysicalDevices,
PFN_vkEnumerateDeviceExtensionProperties*
vkEnumerateDeviceExtensionProperties) {
*vkEnumeratePhysicalDevices =
reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
vkGetInstanceProcAddr(vk_instance, "vkEnumeratePhysicalDevices"));
*vkEnumerateDeviceExtensionProperties =
reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(
vkGetInstanceProcAddr(vk_instance,
"vkEnumerateDeviceExtensionProperties"));
if ((*vkEnumeratePhysicalDevices) &&
(*vkEnumerateDeviceExtensionProperties)) {
return true;
}
return false;
}
uint32_t GetGpuSupportedVulkanVersion(
const gpu::GPUInfo::GPUDevice& gpu_device) {
TRACE_EVENT0("gpu", "GetGpuSupportedVulkanVersion");
base::NativeLibrary vulkan_library;
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkCreateInstance vkCreateInstance;
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
VkInstance vk_instance = VK_NULL_HANDLE;
uint32_t physical_device_count = 0;
// Skip if the system has an older AMD Vulkan driver amdvlk64.dll or
// amdvlk32.dll which crashes when vkCreateInstance() is called. This bug has
// been fixed in the latest AMD driver.
// Detected by the file version of amdvlk64.dll.
if (BadAMDVulkanDriverVersion()) {
return 0;
}
// Don't collect any info if the graphics vulkan driver is blocklisted or
// doesn't support Vulkan 1.1
// Detected by the graphic driver version returned by DXGI
if (BadGraphicsDriverVersions(gpu_device))
return 0;
// Only supports a version >= 1.1.0.
uint32_t vulkan_version = 0;
if (!InitVulkan(&vulkan_library, &vkGetInstanceProcAddr, &vkCreateInstance,
&vulkan_version)) {
return 0;
}
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
const std::vector<const char*> enabled_instance_extensions = {
"VK_KHR_surface", "VK_KHR_win32_surface"};
VkInstanceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = &app_info;
create_info.enabledExtensionCount = enabled_instance_extensions.size();
create_info.ppEnabledExtensionNames = enabled_instance_extensions.data();
// Get the Vulkan API version supported in the GPU driver
int highest_minor_version = VK_VERSION_MINOR(vulkan_version);
for (int minor_version = highest_minor_version; minor_version >= 1;
--minor_version) {
app_info.apiVersion = VK_MAKE_VERSION(1, minor_version, 0);
VkResult result = vkCreateInstance(&create_info, nullptr, &vk_instance);
if (result == VK_SUCCESS && vk_instance &&
InitVulkanInstanceProc(vk_instance, vkGetInstanceProcAddr,
&vkEnumeratePhysicalDevices,
&vkEnumerateDeviceExtensionProperties)) {
result = vkEnumeratePhysicalDevices(vk_instance, &physical_device_count,
nullptr);
if (result == VK_SUCCESS && physical_device_count > 0) {
return app_info.apiVersion;
} else {
// Skip destroy here. GPU process shutdown will unload all loaded DLLs.
// vkDestroyInstance(vk_instance, nullptr);
vk_instance = VK_NULL_HANDLE;
}
}
}
// From the crash reports, calling the following two functions might cause a
// crash in the Vulkan loader or in the Vulkan driver. To work around it,
// don't explicitly unload the DLL. Instead, GPU process shutdown will unload
// all loaded DLLs.
// if (vk_instance) {
// vkDestroyInstance(vk_instance, nullptr);
// }
// base::UnloadNativeLibrary(vulkan_library);
return 0;
}
void RecordGpuSupportedDx12VersionHistograms(
uint32_t d3d12_feature_level,
uint32_t highest_shader_model_version) {
bool supports_dx12 =
(d3d12_feature_level >= D3D_FEATURE_LEVEL_12_0) ? true : false;
UMA_HISTOGRAM_BOOLEAN("GPU.SupportsDX12", supports_dx12);
UMA_HISTOGRAM_ENUMERATION(
"GPU.D3D12FeatureLevel",
ConvertToHistogramFeatureLevel(d3d12_feature_level));
UMA_HISTOGRAM_ENUMERATION(
"GPU.D3D12HighestShaderModel2",
ConvertToHistogramShaderVersion(highest_shader_model_version));
}
bool CollectD3D11FeatureInfo(D3D_FEATURE_LEVEL* d3d11_feature_level,
bool* has_discrete_gpu) {
Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
if (FAILED(::CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory))))
return false;
base::ScopedNativeLibrary d3d11_library(
base::FilePath(FILE_PATH_LITERAL("d3d11.dll")));
if (!d3d11_library.is_valid())
return false;
PFN_D3D11_CREATE_DEVICE D3D11CreateDevice =
reinterpret_cast<PFN_D3D11_CREATE_DEVICE>(
d3d11_library.GetFunctionPointer("D3D11CreateDevice"));
if (!D3D11CreateDevice)
return false;
// The order of feature levels to attempt to create in D3D CreateDevice.
// TODO(crbug.com/40831714): Using 12_2 in kFeatureLevels[] will cause failure
// in D3D11CreateDevice(). Limit the highest feature to 12_1.
const D3D_FEATURE_LEVEL kFeatureLevels[] = {
D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1};
bool detected_discrete_gpu = false;
D3D_FEATURE_LEVEL max_level = D3D_FEATURE_LEVEL_1_0_CORE;
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
for (UINT ii = 0; SUCCEEDED(dxgi_factory->EnumAdapters(ii, &dxgi_adapter));
++ii) {
DXGI_ADAPTER_DESC desc;
CHECK_EQ(S_OK, dxgi_adapter->GetDesc(&desc));
if (desc.VendorId == 0x1414) {
// Bypass Microsoft software renderer.
continue;
}
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
D3D_FEATURE_LEVEL returned_feature_level = D3D_FEATURE_LEVEL_1_0_CORE;
if (FAILED(D3D11CreateDevice(dxgi_adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN,
/*Software=*/0,
/*Flags=*/0, kFeatureLevels,
_countof(kFeatureLevels), D3D11_SDK_VERSION,
&d3d11_device, &returned_feature_level,
/*ppImmediateContext=*/nullptr))) {
continue;
}
if (returned_feature_level > max_level)
max_level = returned_feature_level;
Microsoft::WRL::ComPtr<ID3D11Device3> d3d11_device_3;
if (FAILED(d3d11_device.As(&d3d11_device_3)))
continue;
D3D11_FEATURE_DATA_D3D11_OPTIONS2 data = {};
if (FAILED(d3d11_device_3->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2,
&data, sizeof(data)))) {
continue;
}
if (!data.UnifiedMemoryArchitecture)
detected_discrete_gpu = true;
}
if (max_level > D3D_FEATURE_LEVEL_1_0_CORE) {
*d3d11_feature_level = max_level;
*has_discrete_gpu = detected_discrete_gpu;
return true;
}
return false;
}
bool CollectContextGraphicsInfo(GPUInfo* gpu_info) {
TRACE_EVENT0("gpu", "CollectGraphicsInfo");
DCHECK(gpu_info);
if (!CollectGraphicsInfoGL(gpu_info, gl::GetDefaultDisplayEGL())) {
return false;
}
// ANGLE's renderer strings are of the form:
// ANGLE (<adapter_identifier> Direct3D<version> vs_x_x ps_x_x)
std::string direct3d_version;
int vertex_shader_major_version = 0;
int vertex_shader_minor_version = 0;
int pixel_shader_major_version = 0;
int pixel_shader_minor_version = 0;
if (RE2::FullMatch(gpu_info->gl_renderer, "ANGLE \\(.*\\)") &&
RE2::PartialMatch(gpu_info->gl_renderer, " Direct3D(\\w+)",
&direct3d_version) &&
RE2::PartialMatch(gpu_info->gl_renderer, " vs_(\\d+)_(\\d+)",
&vertex_shader_major_version,
&vertex_shader_minor_version) &&
RE2::PartialMatch(gpu_info->gl_renderer, " ps_(\\d+)_(\\d+)",
&pixel_shader_major_version,
&pixel_shader_minor_version)) {
gpu_info->vertex_shader_version = base::StringPrintf(
"%d.%d", vertex_shader_major_version, vertex_shader_minor_version);
gpu_info->pixel_shader_version = base::StringPrintf(
"%d.%d", pixel_shader_major_version, pixel_shader_minor_version);
DCHECK(!gpu_info->vertex_shader_version.empty());
// Note: do not reorder, used by UMA_HISTOGRAM below
enum ShaderModel {
SHADER_MODEL_UNKNOWN,
SHADER_MODEL_2_0,
SHADER_MODEL_3_0,
SHADER_MODEL_4_0,
SHADER_MODEL_4_1,
SHADER_MODEL_5_0,
NUM_SHADER_MODELS
};
ShaderModel shader_model = SHADER_MODEL_UNKNOWN;
if (gpu_info->vertex_shader_version == "5.0") {
shader_model = SHADER_MODEL_5_0;
} else if (gpu_info->vertex_shader_version == "4.1") {
shader_model = SHADER_MODEL_4_1;
} else if (gpu_info->vertex_shader_version == "4.0") {
shader_model = SHADER_MODEL_4_0;
} else if (gpu_info->vertex_shader_version == "3.0") {
shader_model = SHADER_MODEL_3_0;
} else if (gpu_info->vertex_shader_version == "2.0") {
shader_model = SHADER_MODEL_2_0;
}
UMA_HISTOGRAM_ENUMERATION("GPU.D3DShaderModel", shader_model,
NUM_SHADER_MODELS);
// DirectX diagnostics are collected asynchronously because it takes a
// couple of seconds.
}
return true;
}
bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
TRACE_EVENT0("gpu", "CollectPreliminaryGraphicsInfo");
DCHECK(gpu_info);
// TODO(zmo): we only need to call CollectDriverInfoD3D() if we use ANGLE.
return CollectDriverInfoD3D(gpu_info);
}
bool IdentifyActiveGPUWithLuid(GPUInfo* gpu_info) {
LUID luid;
if (!GetActiveAdapterLuid(&luid))
return false;
gpu_info->gpu.active = false;
for (size_t i = 0; i < gpu_info->secondary_gpus.size(); i++)
gpu_info->secondary_gpus[i].active = false;
if (gpu_info->gpu.luid.HighPart == luid.HighPart &&
gpu_info->gpu.luid.LowPart == luid.LowPart) {
gpu_info->gpu.active = true;
return true;
}
for (size_t i = 0; i < gpu_info->secondary_gpus.size(); i++) {
if (gpu_info->secondary_gpus[i].luid.HighPart == luid.HighPart &&
gpu_info->secondary_gpus[i].luid.LowPart == luid.LowPart) {
gpu_info->secondary_gpus[i].active = true;
return true;
}
}
return false;
}
} // namespace gpu