mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
Use native APIs for GPU discovery (#5602)
This commit is contained in:
commit
5b051e7d49
4 changed files with 125 additions and 117 deletions
|
|
@ -13,7 +13,7 @@ runs:
|
||||||
dpkg-dev \
|
dpkg-dev \
|
||||||
ninja-build extra-cmake-modules pkg-config scdoc \
|
ninja-build extra-cmake-modules pkg-config scdoc \
|
||||||
cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \
|
cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \
|
||||||
libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev
|
libxcb-cursor-dev libtomlplusplus-dev
|
||||||
|
|
||||||
- name: Setup AppImage tooling
|
- name: Setup AppImage tooling
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ RUN apt-get --assume-yes --no-install-recommends install \
|
||||||
# Build system
|
# Build system
|
||||||
cmake ninja-build extra-cmake-modules pkg-config \
|
cmake ninja-build extra-cmake-modules pkg-config \
|
||||||
# Dependencies
|
# Dependencies
|
||||||
cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev libvulkan-dev scdoc zlib1g-dev \
|
cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev scdoc zlib1g-dev \
|
||||||
# Tooling
|
# Tooling
|
||||||
clang-format clang-tidy git
|
clang-format clang-tidy git
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,84 +18,45 @@
|
||||||
|
|
||||||
#include "HardwareInfo.h"
|
#include "HardwareInfo.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QDebug>
|
||||||
#include <QOffscreenSurface>
|
#include <QStringList>
|
||||||
#include <QOpenGLFunctions>
|
|
||||||
#include <QProcessEnvironment>
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
|
|
||||||
#ifndef Q_OS_MACOS
|
|
||||||
#include <QVulkanInstance>
|
|
||||||
#include <QVulkanWindow>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
|
||||||
namespace {
|
namespace {
|
||||||
bool vulkanInfo(QStringList& out)
|
QString afterColon(QString str)
|
||||||
{
|
{
|
||||||
if (!QProcessEnvironment::systemEnvironment()
|
return str.remove(0, str.indexOf(':') + 2).trimmed();
|
||||||
.value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME))
|
|
||||||
.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifndef Q_OS_MACOS
|
|
||||||
QVulkanInstance inst;
|
|
||||||
if (!inst.create()) {
|
|
||||||
qWarning() << "Vulkan instance creation failed, VkResult:" << inst.errorCode();
|
|
||||||
out << "Couldn't get Vulkan device information";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVulkanWindow window;
|
|
||||||
window.setVulkanInstance(&inst);
|
|
||||||
|
|
||||||
for (auto device : window.availablePhysicalDevices()) {
|
|
||||||
const auto supportedVulkanVersion = QVersionNumber(VK_API_VERSION_MAJOR(device.apiVersion), VK_API_VERSION_MINOR(device.apiVersion),
|
|
||||||
VK_API_VERSION_PATCH(device.apiVersion));
|
|
||||||
out << QString("Found Vulkan device: %1 (API version %2)").arg(device.deviceName).arg(supportedVulkanVersion.toString());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openGlInfo(QStringList& out)
|
template <typename F>
|
||||||
|
bool readFromOutput(const char* command, F function)
|
||||||
{
|
{
|
||||||
if (!QProcessEnvironment::systemEnvironment()
|
FILE* file = popen(command, "r"); // NOLINT(*-command-processor)
|
||||||
.value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME))
|
if (!file) {
|
||||||
.isEmpty()) {
|
qWarning().nospace() << "Could not execute command '" << command << "': " << strerror(errno);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QOpenGLContext ctx;
|
|
||||||
if (!ctx.create()) {
|
|
||||||
qWarning() << "OpenGL context creation failed";
|
|
||||||
out << "Couldn't get OpenGL device information";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QOffscreenSurface surface;
|
constexpr size_t bufferSize = 512;
|
||||||
surface.create();
|
std::array<char, bufferSize> buffer{};
|
||||||
ctx.makeCurrent(&surface);
|
while (fgets(buffer.data(), bufferSize, file) != nullptr) {
|
||||||
|
function(buffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
auto* f = ctx.functions();
|
const int exitCode = pclose(file);
|
||||||
f->initializeOpenGLFunctions();
|
if (exitCode != 0) {
|
||||||
|
if (exitCode == -1) {
|
||||||
|
qWarning().nospace() << "Could not close stream for command '" << command << "': " << strerror(errno);
|
||||||
|
} else {
|
||||||
|
qWarning().nospace() << "Command '" << command << "' exited with code " << exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
auto toQString = [](const GLubyte* str) { return QString(reinterpret_cast<const char*>(str)); };
|
return false;
|
||||||
out << "OpenGL driver vendor: " + toQString(f->glGetString(GL_VENDOR));
|
}
|
||||||
out << "OpenGL renderer: " + toQString(f->glGetString(GL_RENDERER));
|
|
||||||
out << "OpenGL driver version: " + toQString(f->glGetString(GL_VERSION));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#ifndef Q_OS_LINUX
|
|
||||||
QStringList HardwareInfo::gpuInfo()
|
|
||||||
{
|
|
||||||
QStringList info;
|
|
||||||
vulkanInfo(info);
|
|
||||||
openGlInfo(info);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
|
|
@ -104,7 +65,11 @@ QStringList HardwareInfo::gpuInfo()
|
||||||
#endif
|
#endif
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "windows.h"
|
#include <dxgi1_6.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <wrl/client.h>
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
|
||||||
QString HardwareInfo::cpuInfo()
|
QString HardwareInfo::cpuInfo()
|
||||||
{
|
{
|
||||||
|
|
@ -140,15 +105,42 @@ uint64_t HardwareInfo::availableRamMiB()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList HardwareInfo::gpuInfo()
|
||||||
|
{
|
||||||
|
ComPtr<IDXGIFactory6> factory;
|
||||||
|
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
qWarning() << "Could not create DXGI factory:" << Qt::hex << hr;
|
||||||
|
return { "GPU discovery failed: could not create DXGI factory" };
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT i = 0;
|
||||||
|
ComPtr<IDXGIAdapter> adapter;
|
||||||
|
QStringList out;
|
||||||
|
while (factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND) {
|
||||||
|
DXGI_ADAPTER_DESC desc;
|
||||||
|
hr = adapter->GetDesc(&desc);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
out << "GPU: " + QString::fromWCharArray(desc.Description); // NOLINT(*-pro-bounds-array-to-pointer-decay, *-no-array-decay)
|
||||||
|
} else {
|
||||||
|
qWarning() << "Could not get DXGI adapter description:" << Qt::hex << hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_MACOS)
|
||||||
#include "sys/sysctl.h"
|
#include "sys/sysctl.h"
|
||||||
|
|
||||||
QString HardwareInfo::cpuInfo()
|
QString HardwareInfo::cpuInfo()
|
||||||
{
|
{
|
||||||
std::array<char, 512> buffer;
|
std::array<char, 512> buffer{};
|
||||||
size_t bufferSize = buffer.size();
|
size_t bufferSize = buffer.size();
|
||||||
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) {
|
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) {
|
||||||
return QString(buffer.data());
|
return { buffer.data() };
|
||||||
}
|
}
|
||||||
|
|
||||||
qWarning() << "Could not get CPU model: sysctlbyname";
|
qWarning() << "Could not get CPU model: sysctlbyname";
|
||||||
|
|
@ -157,7 +149,7 @@ QString HardwareInfo::cpuInfo()
|
||||||
|
|
||||||
uint64_t HardwareInfo::totalRamMiB()
|
uint64_t HardwareInfo::totalRamMiB()
|
||||||
{
|
{
|
||||||
uint64_t memsize;
|
uint64_t memsize = 0;
|
||||||
size_t memsizeSize = sizeof memsize;
|
size_t memsizeSize = sizeof memsize;
|
||||||
if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) {
|
if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) {
|
||||||
// transforming bytes -> mib
|
// transforming bytes -> mib
|
||||||
|
|
@ -175,7 +167,7 @@ uint64_t HardwareInfo::availableRamMiB()
|
||||||
|
|
||||||
MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel()
|
MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel()
|
||||||
{
|
{
|
||||||
uint32_t level;
|
uint32_t level = 0;
|
||||||
size_t levelSize = sizeof level;
|
size_t levelSize = sizeof level;
|
||||||
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) {
|
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) {
|
||||||
return static_cast<MemoryPressureLevel>(level);
|
return static_cast<MemoryPressureLevel>(level);
|
||||||
|
|
@ -195,25 +187,37 @@ QString MacOSHardwareInfo::memoryPressureLevelName()
|
||||||
return "Yellow";
|
return "Yellow";
|
||||||
case MemoryPressureLevel::Critical:
|
case MemoryPressureLevel::Critical:
|
||||||
return "Red";
|
return "Red";
|
||||||
|
default:
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList HardwareInfo::gpuInfo()
|
||||||
|
{
|
||||||
|
QStringList out;
|
||||||
|
const bool success = readFromOutput("system_profiler SPDisplaysDataType", [&](const QString& str) {
|
||||||
|
// Chipset Model: Intel HD Graphics 620
|
||||||
|
if (str.contains("Chipset Model")) {
|
||||||
|
out << "GPU: " + afterColon(str);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
return { "GPU discovery failed: could not read from system_profiler" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
namespace {
|
|
||||||
QString afterColon(QString& str)
|
|
||||||
{
|
|
||||||
return str.remove(0, str.indexOf(':') + 2).trimmed();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
QString HardwareInfo::cpuInfo()
|
QString HardwareInfo::cpuInfo()
|
||||||
{
|
{
|
||||||
std::ifstream cpuin("/proc/cpuinfo");
|
std::ifstream cpuin("/proc/cpuinfo");
|
||||||
for (std::string line; std::getline(cpuin, line);) {
|
for (std::string line; std::getline(cpuin, line);) {
|
||||||
// model name : AMD Ryzen 7 5800X 8-Core Processor
|
// model name : AMD Ryzen 7 5800X 8-Core Processor
|
||||||
if (QString str = QString::fromStdString(line); str.startsWith("model name")) {
|
if (const QString str = QString::fromStdString(line); str.startsWith("model name")) {
|
||||||
return afterColon(str);
|
return afterColon(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,12 +226,13 @@ QString HardwareInfo::cpuInfo()
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t readMemInfo(QString searchTarget)
|
namespace {
|
||||||
|
uint64_t readMemInfo(const QString& searchTarget)
|
||||||
{
|
{
|
||||||
std::ifstream memin("/proc/meminfo");
|
std::ifstream memin("/proc/meminfo");
|
||||||
for (std::string line; std::getline(memin, line);) {
|
for (std::string line; std::getline(memin, line);) {
|
||||||
// MemTotal: 16287480 kB
|
// MemTotal: 16287480 kB
|
||||||
if (QString str = QString::fromStdString(line); str.startsWith(searchTarget)) {
|
if (const QString str = QString::fromStdString(line); str.startsWith(searchTarget)) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok);
|
const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
|
@ -243,6 +248,7 @@ uint64_t readMemInfo(QString searchTarget)
|
||||||
qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget;
|
qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
uint64_t HardwareInfo::totalRamMiB()
|
uint64_t HardwareInfo::totalRamMiB()
|
||||||
{
|
{
|
||||||
|
|
@ -256,52 +262,50 @@ uint64_t HardwareInfo::availableRamMiB()
|
||||||
|
|
||||||
QStringList HardwareInfo::gpuInfo()
|
QStringList HardwareInfo::gpuInfo()
|
||||||
{
|
{
|
||||||
QStringList list;
|
|
||||||
const bool vulkanSuccess = vulkanInfo(list);
|
|
||||||
const bool openGlSuccess = openGlInfo(list);
|
|
||||||
if (vulkanSuccess || openGlSuccess) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<char, 512> buffer;
|
|
||||||
FILE* lspci = popen("lspci -k", "r");
|
|
||||||
|
|
||||||
if (!lspci) {
|
|
||||||
return { "Could not detect GPUs: lspci is not present" };
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readingGpuInfo = false;
|
bool readingGpuInfo = false;
|
||||||
QString currentModel = "";
|
QString gpu;
|
||||||
while (fgets(buffer.data(), 512, lspci) != nullptr) {
|
QString driverInUse = "NONE";
|
||||||
QString str(buffer.data());
|
QString driversAvailable = "NONE";
|
||||||
|
QStringList out;
|
||||||
|
|
||||||
|
const bool success = readFromOutput("lspci -k", [&](const QString& str) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7)
|
// 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7)
|
||||||
// Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB
|
// Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB
|
||||||
// Kernel driver in use: amdgpu
|
// Kernel driver in use: amdgpu
|
||||||
// Kernel modules: amdgpu
|
// Kernel modules: amdgpu
|
||||||
// clang-format on
|
// clang-format on
|
||||||
if (str.contains("VGA compatible controller")) {
|
if (str.contains("VGA compatible controller") || str.contains("3D controller")) {
|
||||||
readingGpuInfo = true;
|
readingGpuInfo = true;
|
||||||
} else if (!str.startsWith('\t')) {
|
} else if (!str.startsWith('\t')) {
|
||||||
|
if (readingGpuInfo) {
|
||||||
|
out << QString("GPU: %1 (driver in use: %2; drivers available: %3)").arg(gpu, driverInUse, driversAvailable);
|
||||||
|
driverInUse = "NONE";
|
||||||
|
driversAvailable = "NONE";
|
||||||
|
}
|
||||||
readingGpuInfo = false;
|
readingGpuInfo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!readingGpuInfo) {
|
if (!readingGpuInfo) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString value = afterColon(str);
|
||||||
if (str.contains("Subsystem")) {
|
if (str.contains("Subsystem")) {
|
||||||
currentModel = "Found GPU: " + afterColon(str);
|
gpu = value;
|
||||||
}
|
}
|
||||||
if (str.contains("Kernel driver in use")) {
|
if (str.contains("Kernel driver in use")) {
|
||||||
currentModel += " (using driver " + afterColon(str);
|
driverInUse = value;
|
||||||
}
|
}
|
||||||
if (str.contains("Kernel modules")) {
|
if (str.contains("Kernel modules")) {
|
||||||
currentModel += ", available drivers: " + afterColon(str) + ")";
|
driversAvailable = value;
|
||||||
list.append(currentModel);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
return { "GPU discovery failed: could not read from lspci" };
|
||||||
}
|
}
|
||||||
pclose(lspci);
|
|
||||||
return list;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
@ -316,19 +320,20 @@ QString HardwareInfo::cpuInfo()
|
||||||
|
|
||||||
uint64_t HardwareInfo::totalRamMiB()
|
uint64_t HardwareInfo::totalRamMiB()
|
||||||
{
|
{
|
||||||
char buff[512];
|
uint64_t out = 0;
|
||||||
FILE* fp = popen("sysctl hw.physmem", "r");
|
|
||||||
if (fp != nullptr) {
|
|
||||||
if (fgets(buff, 512, fp) != nullptr) {
|
|
||||||
std::string str(buff);
|
|
||||||
uint64_t mem = std::stoull(str.substr(12, std::string::npos));
|
|
||||||
|
|
||||||
// transforming kib -> mib
|
const bool success = readFromOutput("sysctl hw.physmem", [&](const QString& str) {
|
||||||
return mem / 1024;
|
const uint64_t mem = str.mid(12).toULong();
|
||||||
}
|
|
||||||
|
// transforming kib -> mib
|
||||||
|
out = mem / 1024;
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
qWarning() << "Could not get total RAM: could not read from sysctl";
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
@ -343,4 +348,8 @@ uint64_t HardwareInfo::availableRamMiB()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList HardwareInfo::gpuInfo()
|
||||||
|
{
|
||||||
|
return { "GPU discovery failed: not implemented for this OS" };
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tomlplusplus",
|
"tomlplusplus",
|
||||||
"zlib",
|
"zlib"
|
||||||
"vulkan-headers"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue